#!/usr/bin/env python ### Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved. import os import sys from optparse import OptionParser import logging as log import fileinput import plesk_subprocess as subprocess import re PLESK_LIBEXEC_DIR = '/usr/lib64/plesk-9.0' # http://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names def valid_hostname(hostname): """ Check that provided hostname is valid. """ if len(hostname) > 255: return False if hostname.endswith("."): # A single trailing dot is legal hostname = hostname[:-1] # strip exactly one dot from the right, if present disallowed = re.compile("[^A-Z0-9-]", re.IGNORECASE) # The RFC mandate that component hostname labels may contain only the ASCII letters 'a' through 'z' (in a case-insensitive manner), the digits '0' through '9', and the hyphen ('-'). return all( # Split by labels and verify individually (label and len(label) <= 63 # length is within proper range and not label.startswith("-") and not label.endswith("-") # no bordering hyphens and not disallowed.search(label)) # contains only legal characters for label in hostname.split(".")) def update_hostname_file(filename, fqdn): """ Function to write new hostname into files like /etc/hostname, /etc/mailname and so on """ if not os.path.exists(filename): log.debug("%s not updated, as it doesn't exists", filename) return False with open(filename, 'w') as f: f.write("%s\n" % fqdn) def _prepare_hosts_line(line, old_fqdn, hostname, domainname): """ Insert new entry in head of hosts line (after ip address) and remove old FQDN entries """ chunks = line.split() hostnames = ["%s.%s" % (hostname, domainname), hostname] hostnames.extend(filter(lambda x: (x != old_fqdn and x != hostname), chunks[1:])) return "%s\t%s\n" % (chunks[0], " ".join(hostnames)) def update_hosts_file(hosts_file, old_fqdn, hostname, domainname): """ Replace old entries in hosts file, or append new one """ new_fqdn = "%s.%s" % (hostname, domainname) with open(hosts_file, 'r+') as f: contents = f.readlines() found_old_fqdn = False found_localhost = False for line in contents: # Fully commented line, ignoring if line.strip().startswith("#"): continue have_comment = line.find('#') canonical_line = None if have_comment != -1: canonical_line = line[:have_comment].strip() else: canonical_line = line.strip() if canonical_line.startswith("127.0.0.1") or canonical_line.startswith("::1"): found_localhost = True if old_fqdn and canonical_line.find(old_fqdn) != -1: found_old_fqdn = True if not found_old_fqdn and not found_localhost: log.debug("Appending new entry to hosts file, as neither loopback entry nor old_fqdn not found") f.write("127.0.0.1\t%s %s" % (new_fqdn, hostname)) return # Truncate file inplace f.seek(0) f.truncate() for line in contents: if found_old_fqdn and line.find(old_fqdn) != -1: log.debug("Replace line '%s' in hosts, as old FQDN found", line.strip()) f.write(_prepare_hosts_line(line, old_fqdn, hostname, domainname)) continue if (not found_old_fqdn) and found_localhost and (line.startswith("127.0.0.1") or line.startswith("::1")): log.debug("Replace loopback line '%s' in hosts, as no old FQDN found", line.strip()) f.write(_prepare_hosts_line(line, old_fqdn, hostname, domainname)) continue f.write(line) def update_network_file(filename, new_fqdn): """ Replace hostname in /etc/sysconfig/network file """ if not os.path.exists(filename): log.debug("Do not update '%s', as it doesn't exist", filename) return False for line in fileinput.input(filename, inplace=True): if line.strip().startswith("HOSTNAME="): sys.stdout.write("HOSTNAME=%s" % new_fqdn) else: sys.stdout.write(line) def get_hostname(): """ Obtain current hostname from system """ try: return subprocess.check_output(["/bin/hostname", "--fqdn"]).rstrip() except subprocess.CalledProcessError: log.debug("Cannot get FQDN hostname") return None def set_hostname(hostname, domainname): """ Set hostname on Linux host """ new_fqdn = "%s.%s" % (hostname, domainname) old_fqdn = get_hostname() log.debug("Got current hostname %s", old_fqdn) log.debug("Update hostname in system") subprocess.check_call(["/bin/hostname", new_fqdn]) for hostname_file in ["/etc/hostname", "/etc/HOSTNAME"]: log.debug("Update hostname in %s, if exists", hostname_file) update_hostname_file(hostname_file, new_fqdn) log.debug("Update hostname in /etc/hosts") update_hosts_file("/etc/hosts", old_fqdn, hostname, domainname) log.debug("Update hostname in /etc/sysconfig/network file") update_network_file("/etc/sysconfig/network", new_fqdn) log.debug("Update hostname via mailsrv_set_hostname") toolpath = PLESK_LIBEXEC_DIR + "/mailsrv_set_hostname" if not os.path.exists(toolpath): log.debug("%s not updated, as utility doesn't exists") else: subprocess.check_call([toolpath, new_fqdn]) log.debug("Update hostname in /etc/mailname") update_hostname_file("/etc/mailname", new_fqdn) def reconfig(): """ Parse command line argument and do the work """ usage = "usage: %prog [-v] hostname domainname" parser = OptionParser(usage=usage) parser.description = "Utility set hostname" parser.add_option('-v', '--verbose', action='count', dest='verbose', help='Increase verbosity (can be specified multiple times)') (options, args) = parser.parse_args() log_level = log.WARNING if options.verbose == 1: log_level = log.INFO elif options.verbose >= 2: log_level = log.DEBUG log.basicConfig(format='%(levelname)s:%(message)s', level=log_level) log.debug("Options: %s", options) log.debug("Args: %s", args) if len(args) < 2: parser.error("Not enough arguments") hostname = args[0] domainname = args[1] if not hostname or not domainname: parser.error("Not enough arguments") if not valid_hostname("%s.%s" % (hostname, domainname)): parser.error("Specified hostname doesn't valid") if os.geteuid() != 0: raise RuntimeError("You need to have root privileges to run this utility") return set_hostname(hostname, domainname) def main(): """ reconfig main entry point """ try: return reconfig() except SystemExit: raise except Exception, ex: sys.stderr.write('%s\n' % ex) log.debug('This exception happened at:', exc_info=sys.exc_info()) sys.exit(1) if __name__ == '__main__': main() # vim: ts=4 sts=4 sw=4 et :