diff --git a/image_creator/os_type/__init__.py b/image_creator/os_type/__init__.py index 2af3d044edcebf596b98a9c02c567f09c5980cdc..818ad6589b73286cd024f9c4ff96fdf7f0d53603 100644 --- a/image_creator/os_type/__init__.py +++ b/image_creator/os_type/__init__.py @@ -517,6 +517,8 @@ class OSBase(object): http://libguestfs.org/guestfs.3.html#guestfs_readdir * exclude: Exclude all files that follow this pattern. + + * include: Only include files that follow this pattern. """ if not self.image.g.is_dir(directory): self.out.warn("Directory: `%s' does not exist!" % directory) @@ -531,6 +533,7 @@ class OSBase(object): kargs['maxdepth'] = maxdepth exclude = None if 'exclude' not in kargs else kargs['exclude'] + include = None if 'include' not in kargs else kargs['include'] ftype = None if 'ftype' not in kargs else kargs['ftype'] has_ftype = lambda x, y: y is None and True or x['ftyp'] == y @@ -543,6 +546,9 @@ class OSBase(object): if exclude and re.match(exclude, full_path): continue + if include and not re.match(include, full_path): + continue + if has_ftype(f, 'd'): self._foreach_file(full_path, action, **kargs) diff --git a/image_creator/os_type/freebsd.py b/image_creator/os_type/freebsd.py index 08c734f12420c04d7a856332d689fc9f6ce42c7e..bacd0aa26323f3e57c5b30dbbe148a294a6ceb71 100644 --- a/image_creator/os_type/freebsd.py +++ b/image_creator/os_type/freebsd.py @@ -53,7 +53,10 @@ class Freebsd(Unix): def _do_collect_metadata(self): """Collect metadata about the OS""" super(Freebsd, self)._do_collect_metadata() - self.meta["USERS"] = " ".join(self._get_passworded_users()) + + users = self._get_passworded_users() + + self.meta["USERS"] = " ".join(users) # The original product name key is long and ugly self.meta['DESCRIPTION'] = \ @@ -64,6 +67,44 @@ class Freebsd(Unix): self.out.warn("No passworded users found!") del self.meta['USERS'] + # Check if ssh is enabled + sshd_enabled = False + sshd_service = re.compile(r'^sshd_enable=.+$') + + # Freebsd has a checkyesno() functions that tests the service variable + # against all those different values in a case insensitive manner!!! + sshd_yes = re.compile(r"^sshd_enable=(['\"]?)(YES|TRUE|ON|1)\1$", + re.IGNORECASE) + for rc_conf in ('/etc/rc.conf', '/etc/rc.conf.local'): + if not self.image.g.is_file(rc_conf): + continue + + for line in self.image.g.cat(rc_conf).splitlines(): + line = line.split('#')[0].strip() + # Be paranoid. Don't stop examining lines after a match. This + # is a shell variable and can be overwritten many times. Only + # the last match counts. + if sshd_service.match(line): + sshd_enabled = sshd_yes.match(line) is not None + + if sshd_enabled: + ssh = [] + opts = self.ssh_connection_options(users) + for user in opts['users']: + ssh.append("ssh:port=%d,user=%s" % (opts['port'], user)) + + if 'REMOTE_CONNECTION' not in self.meta: + self.meta['REMOTE_CONNECTION'] = "" + else: + self.meta['REMOTE_CONNECTION'] += " " + + if len(users): + self.meta['REMOTE_CONNECTION'] += " ".join(ssh) + else: + self.meta['REMOTE_CONNECTION'] += "ssh:port=%d" % opts['port'] + else: + self.out.warn("OpenSSH Daemon is not configured to run on boot") + def _do_inspect(self): """Run various diagnostics to check if media is supported""" @@ -93,7 +134,11 @@ class Freebsd(Unix): if len(passwd) > 0 and passwd[0] == '!': self.out.warn("Ignoring locked %s account." % user) else: - users.append(user) + # Put root in the beginning. + if user == 'root': + users.insert(0, user) + else: + users.append(user) return users diff --git a/image_creator/os_type/linux.py b/image_creator/os_type/linux.py index c5b82a0d8df0c385447a64eaa5b4a87af032fee2..8569d03eb4344019eb963e692a4dd0c6268172ee 100644 --- a/image_creator/os_type/linux.py +++ b/image_creator/os_type/linux.py @@ -22,6 +22,19 @@ from image_creator.os_type.unix import Unix, sysprep import re import time +X2GO_DESKTOPSESSIONS = { + 'CINNAMON': 'cinnamon', + 'KDE': 'startkde', + 'GNOME': 'gnome-session', + 'MATE': 'mate-session', + 'XFCE': 'xfce4-session', + 'LXDE': 'startlxde', + 'TRINITY': 'starttrinity', + 'UNITY': 'unity', +} + +X2GO_EXECUTABLE = "x2goruncommand" + class Linux(Unix): """OS class for Linux""" @@ -300,13 +313,101 @@ class Linux(Unix): def _do_collect_metadata(self): """Collect metadata about the OS""" super(Linux, self)._do_collect_metadata() - self.meta["USERS"] = " ".join(self._get_passworded_users()) + users = self._get_passworded_users() + self.meta["USERS"] = " ".join(users) # Delete the USERS metadata if empty if not len(self.meta['USERS']): self.out.warn("No passworded users found!") del self.meta['USERS'] + if self.is_enabled('sshd'): + ssh = [] + opts = self.ssh_connection_options(users) + for user in opts['users']: + ssh.append("ssh:port=%d,user=%s" % (opts['port'], user)) + + if 'REMOTE_CONNECTION' not in self.meta: + self.meta['REMOTE_CONNECTION'] = "" + else: + self.meta['REMOTE_CONNECTION'] += " " + + if len(ssh): + self.meta['REMOTE_CONNECTION'] += " ".join(ssh) + else: + self.meta['REMOTE_CONNECTION'] += "ssh:port=%d" % opts['port'] + + # Check if x2go is installed + x2go_installed = False + desktops = set() + for path in ('/bin', '/usr/bin', '/usr/local/bin'): + if self.image.g.is_file("%s/%s" % (path, X2GO_EXECUTABLE)): + x2go_installed = True + for name, exe in X2GO_DESKTOPSESSIONS.items(): + if self.image.g.is_file("%s/%s" % (path, exe)): + desktops.add(name) + + if x2go_installed: + self.meta['REMOTE_CONNECTION'] += " " + if len(desktops) == 0: + self.meta['REMOTE_CONNECTION'] += "x2go" + else: + self.meta['REMOTE_CONNECTION'] += \ + " ".join(["x2go:session=%s" % d for d in desktops]) + else: + self.out.warn("OpenSSH Daemon is not configured to run on boot") + + def is_enabled(self, service): + """Check if a service is enabled to run on boot""" + + systemd_services = '/etc/systemd/system/multi-user.target.wants' + exec_start = re.compile(r'^\s*ExecStart=.+bin/%s\s?' % service) + if self.image.g.is_dir(systemd_services): + for entry in self.image.g.readdir(systemd_services): + if entry['ftyp'] not in ('l', 'f'): + continue + service_file = "%s/%s" % (systemd_services, entry['name']) + for line in self.image.g.cat(service_file).splitlines(): + if exec_start.search(line): + return True + + found = set() + + def check_file(path): + regexp = re.compile(r"[/=\s'\"]%s('\")?\s" % service) + for line in self.image.g.cat(path).splitlines(): + line = line.split('#', 1)[0].strip() + if len(line) == 0: + continue + if regexp.search(line): + found.add(path) + return + + # Check upstart config files under /etc/init + # Only examine *.conf files + if self.image.g.is_dir('/etc/init'): + self._foreach_file('/etc/init', check_file, maxdepth=1, + include='.+\.conf$') + if len(found): + return True + + # Check scripts under /etc/rc[1-5].d/ and /etc/rc.d/rc[1-5].d/ + for conf in ["/etc/%src%d.d" % (d, i) for i in xrange(1, 6) + for d in ('', 'rc.d/')]: + try: + for entry in self.image.g.readdir(conf): + if entry['ftyp'] not in ('l', 'f'): + continue + check_file("%s/%s" % (conf, entry['name'])) + + if len(found): + return True + + except RuntimeError: + continue + + return False + def _get_passworded_users(self): """Returns a list of non-locked user accounts""" users = [] diff --git a/image_creator/os_type/slackware.py b/image_creator/os_type/slackware.py index 6914e023b0ce25b9f65b7170f77869ad28cc80ae..475f69f240d7cec3d78c453119e72ffafbf78c3d 100644 --- a/image_creator/os_type/slackware.py +++ b/image_creator/os_type/slackware.py @@ -32,4 +32,16 @@ class Slackware(Linux): self._foreach_file('/var/log', self.image.g.truncate, ftype='r', exclude='/var/log/packages') + def is_enabled(self, service): + """Check if a service is enabled to start on boot""" + + name = '/etc/rc.d/%s' % service + # In slackware a service will be executed during boot if the + # execute bit is set for the root + if self.image.g.is_file(name): + return self.image.g.stat(name)['mode'] & 0400 + + self.out.warn('Service %s not found on the media' % service) + return False + # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/os_type/unix.py b/image_creator/os_type/unix.py index 27fa2a8b2bf8feedb365e92f5ff8f66f5d7cbd94..be953e25f9f742c5bd545690232d0153a0e58a22 100644 --- a/image_creator/os_type/unix.py +++ b/image_creator/os_type/unix.py @@ -70,6 +70,37 @@ class Unix(OSBase): return True + def ssh_connection_options(self, users): + """Returns a list of valid ssh connection options""" + + def sshd_config(): + """Read /etc/ssh/sshd_config and return it as a dictionary""" + config = {} + fname = '/etc/ssh/sshd_config' + + if not self.image.g.is_file(fname): + return {} + + for line in self.image.g.cat(fname).splitlines(): + line = line.split('#')[0].strip() + if not len(line): + continue + line = line.split() + config[line[0]] = line[1:] + return config + + config = sshd_config() + try: + port = int(config['Port'][0]) + except: + port = 22 + + if 'PermitRootLogin' in config and config['PermitRootLogin'] == 'no': + if 'root' in users: + users.remove('root') + + return {'port': port, 'users': users} + @sysprep('Removing files under /var/cache') def _cleanup_cache(self): """Remove all regular files under /var/cache""" diff --git a/image_creator/os_type/windows/__init__.py b/image_creator/os_type/windows/__init__.py index 2117d8b229e2a5a57a149bca8b5086c27cc766c8..fb12e98b9d5c91ab4689483c3f22db1eea2d8ffb 100644 --- a/image_creator/os_type/windows/__init__.py +++ b/image_creator/os_type/windows/__init__.py @@ -697,8 +697,26 @@ class Windows(OSBase): super(Windows, self)._do_collect_metadata() # We only care for active users - self.meta["USERS"] = \ - " ".join([self.usernames[a] for a in self.active_users]) + active = [self.usernames[a] for a in self.active_users] + self.meta["USERS"] = " ".join(active) + + # Get RDP settings + settings = self.registry.get_rdp_settings() + + if settings['disabled']: + self.out.warn("RDP is disabled on the image") + else: + if 'REMOTE_CONNECTION' not in self.meta: + self.meta['REMOTE_CONNECTION'] = "" + else: + self.meta['REMOTE_CONNECTION'] += " " + + port = settings['port'] + if len(active): + rdp = ["rdp:port=%d,user=%s" % (port, user) for user in active] + self.meta['REMOTE_CONNECTION'] += " ".join(rdp) + else: + self.meta['REMOTE_CONNECTION'] += "rdp:port=%d" % port def _check_connectivity(self): """Check if winexe works on the Windows VM""" diff --git a/image_creator/os_type/windows/registry.py b/image_creator/os_type/windows/registry.py index e8650c5024465828c2ddac1a52432d68c5771ae9..b4f6f516dabad6b3f311bbf0c29b4ea60858a77f 100644 --- a/image_creator/os_type/windows/registry.py +++ b/image_creator/os_type/windows/registry.py @@ -170,6 +170,29 @@ class Registry(object): raise FatalError("Unknown Windows Setup State: %s" % value) + def get_rdp_settings(self): + """Returns Remote Desktop Settings of the image""" + + settings = {} + with self.open_hive('SYSTEM') as hive: + path = '%s/Control/Terminal Server' % self.current_control_set + term_server = traverse(hive, path) + + disabled = hive.node_get_value(term_server, "fDenyTSConnections") + # expecting a little endian dword + assert hive.value_type(disabled)[1] == 4 + settings['disabled'] = hive.value_dword(disabled) != 0 + + path += "/WinStations/RDP-Tcp" + rdp_tcp = traverse(hive, path) + + port = hive.node_get_value(rdp_tcp, "PortNumber") + # expecting a little endian dword + assert hive.value_type(port)[1] == 4 + settings['port'] = hive.value_dword(port) + + return settings + def runonce(self, commands): """Add commands to the RunOnce registry key"""