Edit File: squid-migrate-conf.py
#!/usr/bin/python -tt # -*- coding: utf-8 -*- # # This script will help you with migration squid-3.3 conf files to squid-3.5 conf files # Copyright (C) 2016 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # he Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # Authors: Lubos Uhliarik <luhliari@redhat.com> import sys import os import re import shutil import traceback import argparse import glob class ConfMigration: RE_LOG_ACCESS="log_access\s+(\w+)\s+" RE_LOG_ACCESS_DENY_REP="access_log none " RE_LOG_ACCESS_ALLOW_REP="access_log daemon:/var/log/squid/access.log squid " RE_LOG_ACCESS_TEXT="log_access" RE_LOG_ICAP="log_icap\s+" RE_LOG_ICAP_REP="icap_log daemon:/var/log/squid/icap.log " RE_LOG_ICAP_TEXT="log_icap" RE_HIER_STOPLIST="hierarchy_stoplist\s+(.*)" RE_HIER_STOPLIST_REP="acl %s url_regex %s\nalways_direct allow %s" RE_HIER_STOPLIST_TEXT="hierarchy_stoplist" HIER_ACL_NAME="migrated_hs_%d_%d" RE_INCLUDE_CHECK="\s*include\s+(.*)" COMMENT_FMT="# migrated automatically by squid-migrate-conf, the original configuration was: %s\n%s" DEFAULT_SQUID_CONF="/etc/squid/squid.conf" DEFAULT_BACKUP_EXT=".bak" DEFAULT_LEVEL_INDENT=3 MAX_NESTED_INCLUDES=16 def __init__(self, args, level=0, squid_conf='', conf_seq=0): self.args = args if squid_conf: self.squid_conf = squid_conf else: self.squid_conf = args.squid_conf self.write_changes = args.write_changes self.debug = args.debug self.conf_seq = conf_seq self.acl_seq = 0 self.line_num = 0 self.level = level if (not os.path.isfile(self.squid_conf)): sys.stderr.write("%sError: the config file %s does not exist\n" % (self.get_prefix_str(), self.squid_conf)) sys.exit(1) self.squid_bak_conf = self.get_backup_name() self.migrated_squid_conf_data = [] self.squid_conf_data = None print ("Migrating: " + self.squid_conf) def print_info(self, text=''): if (self.debug): print "%s%s" % (self.get_prefix_str(), text) def get_backup_name(self): file_idx = 1 tmp_fn = self.squid_conf + self.DEFAULT_BACKUP_EXT while (os.path.isfile(tmp_fn)): tmp_fn = self.squid_conf + self.DEFAULT_BACKUP_EXT + str(file_idx) file_idx = file_idx + 1 return tmp_fn # # From squid config documentation: # # Configuration options can be included using the "include" directive. # Include takes a list of files to include. Quoting and wildcards are # supported. # # For example, # # include /path/to/included/file/squid.acl.config # # Includes can be nested up to a hard-coded depth of 16 levels. # This arbitrary restriction is to prevent recursive include references # from causing Squid entering an infinite loop whilst trying to load # configuration files. # def check_include(self, line=''): m = re.match(self.RE_INCLUDE_CHECK, line) include_list = "" if not (m is None): include_list = re.split('\s+', m.group(1)) for include_file_re in include_list: # included file can be written in regexp syntax for include_file in glob.glob(include_file_re): self.print_info("A config file %s was found and it will be included" % (include_file)) if os.path.isfile(include_file): self.print_info("Migrating the included config file %s" % (include_file)) conf = ConfMigration(self.args, self.level+1, include_file, self.conf_seq+1) conf.migrate() # check, if included file exists if (len(glob.glob(include_file_re)) == 0 and not (os.path.isfile(include_file_re))): self.print_info("The config file %s does not exist." % (include_file_re)) def print_sub_text(self, text, new_str): if self.write_changes: print "File: '%s', line: %d - the directive %s was replaced by %s" % (self.squid_conf, self.line_num, text, new_str) else: print "File: '%s', line: %d - the directive %s could be replaced by %s" % (self.squid_conf, self.line_num, text, new_str) def add_conf_comment(self, old_line, line): return self.COMMENT_FMT % (old_line, line) def sub_line_ad(self, line, line_re, allow_sub, deny_sub, text): new_line = line m = re.match(line_re, line) if not (m is None): # check, if allow or deny was used and select coresponding sub sub_text = allow_sub if (re.match('allow', m.group(1), re.IGNORECASE)): new_line = re.sub(line_re, sub_text, line) elif (re.match('deny', m.group(1), re.IGNORECASE)): sub_text = deny_sub new_line = re.sub(line_re, sub_text, line) # print out, if there was any change and add comment to conf line, if so if not (new_line is line): self.print_sub_text(text + " " + m.group(1), sub_text) new_line = self.add_conf_comment(line, new_line) return new_line def sub_line(self, line, line_re, sub, text): new_line = line m = re.match(line_re, line) if not (m is None): new_line = re.sub(line_re, sub, line) # print out, if there was any change and add comment to conf line, if so if not (new_line is line): self.print_sub_text(text, sub) new_line = self.add_conf_comment(line, new_line) return new_line def rep_hier_stoplist(self, line, sub, words): wordlist = words.split(' ') esc_wordlist = [] for w in wordlist: esc_wordlist.append(re.escape(w)) # unique acl name for hierarchy_stoplist acl acl_name = self.HIER_ACL_NAME % (self.conf_seq, self.acl_seq) return sub % (acl_name, ' '.join(esc_wordlist), acl_name) def sub_hier_stoplist(self, line, line_re, sub, text): new_line = line m = re.match(line_re, line) if (not (m is None)): new_line = self.rep_hier_stoplist(line, sub, m.group(1)) # print out, if there was any change and add comment to conf line, if so if not (new_line is line): self.print_sub_text(text, sub) new_line = self.add_conf_comment(line, new_line) return new_line def process_conf_lines(self): for line in self.squid_conf_data.split(os.linesep): # do not migrate comments if not line.strip().startswith('#'): self.check_include(line) line = self.sub_line_ad(line, self.RE_LOG_ACCESS, self.RE_LOG_ACCESS_ALLOW_REP, self.RE_LOG_ACCESS_DENY_REP, self.RE_LOG_ACCESS_TEXT) line = self.sub_line(line, self.RE_LOG_ICAP, self.RE_LOG_ICAP_REP, self.RE_LOG_ICAP_TEXT) line = self.sub_hier_stoplist(line, self.RE_HIER_STOPLIST, self.RE_HIER_STOPLIST_REP, self.RE_HIER_STOPLIST_TEXT) self.migrated_squid_conf_data.append(line) self.line_num = self.line_num + 1 def migrate(self): # prevent infinite loop if (self.level > ConfMigration.MAX_NESTED_INCLUDES): sys.stderr.write("WARNING: the maximum number of nested includes was reached\n") return self.read_conf() self.process_conf_lines() if self.write_changes: if (not (set(self.migrated_squid_conf_data) == set(self.squid_conf_data.split(os.linesep)))): self.write_conf() self.print_info("The migration finished successfully") def get_prefix_str(self): return ((" " * int(self.level)) + "["+ self.squid_conf + "@%d]: " % (self.line_num)) def read_conf(self): self.print_info("Reading squid conf: " + self.squid_conf) try: self.in_file = open(self.squid_conf, 'r') self.squid_conf_data = self.in_file.read() self.in_file.close() except Exception as e: sys.stderr.write("%sError: %s\n" % (self.get_prefix_str(), e)) sys.exit(1) def write_conf(self): self.print_info("Creating backup conf: %s" % (self.squid_bak_conf)) self.print_info("Writing changes to: %s" % (self.squid_conf)) try: shutil.copyfile(self.squid_conf, self.squid_bak_conf) self.out_file = open(self.squid_conf, "w") self.out_file.write(os.linesep.join(self.migrated_squid_conf_data)) self.out_file.close() except Exception as e: sys.stderr.write("%s Error: %s\n" % (self.get_prefix_str(), e)) sys.exit(1) def parse_args(): parser = argparse.ArgumentParser(description='The script migrates the squid 3.3 configuration files to configuration files which are compatible with squid 3.5.') parser.add_argument('--conf', dest='squid_conf', action='store', default=ConfMigration.DEFAULT_SQUID_CONF, help='specify filename of squid configuration (default: %s)' % (ConfMigration.DEFAULT_SQUID_CONF)) parser.add_argument('--write-changes', dest='write_changes', action='store_true', default=False, help='The changes are written to corresponding configuration files') parser.add_argument('--debug', dest="debug", action='store_true', default=False, help='print debug messages to stderr') return parser.parse_args() if __name__ == '__main__': # parse args from command line args = parse_args() # check if config file exists if (not os.path.exists(args.squid_conf)): sys.stderr.write("Error: the file %s does not exist\n" % (args.squid_conf)) sys.exit(1) # change working directory script_dir = os.getcwd() if (os.path.dirname(args.squid_conf)): os.chdir(os.path.dirname(args.squid_conf)) # start migration try: conf = ConfMigration(args, 0) conf.migrate() finally: print "" if not args.write_changes: print "The changes have NOT been written to config files.\nUse the --write-changes option to write the changes" else: print "The changes have been written to config files!" os.chdir(script_dir)