def __init__(self, input, output, version=nxparser.base.parser.DEFAULT_VERSION, program=DEFAULT_PROGRAM): """server_parser constructor Args: input: The file object to read parser input from. output: The file object to write the results to. version: The version string used to negotiate the protocol. program: The program name used in the protocol banner. """ nxparser.base.parser.__init__(self, input, output, version=version, program=program) self.port = 0 username = os.getenv('NX_TRUSTED_USER') if username: self.status = self.STATUS_LOGGEDIN self.username = username else: self.status = self.STATUS_CONNECTED commfd = os.getenv('NX_COMMFD') if commfd: self.nxnode_commfd = int(commfd) self.nxnode_rfile = os.fdopen(self.nxnode_commfd, 'r') self.nxnode_wfile = os.fdopen(self.nxnode_commfd, 'w') nxlog.log(nxlog.LOG_DEBUG, 'Got commfd %d\n' % self.nxnode_commfd)
def _nx_hello_handler(self, command): """Handle the hello NX command. 'hello' is used to handshake the commandline session, and appears to support negotiation of the protocol version. We currently only accept versions which look like 3.x.x, matched using a regex. Args: command: The NX command and arguments invoked Returns: None """ # Basic checking of the right status if self.status >= self.STATUS_LOGGEDIN: self.prompt( 554, 'Error: the command \'hello\' cannot ' 'be called after login') return if len(command) < 5: nxlog.log(nxlog.LOG_DEBUG, 'Hello too short') return if not re.match('^3(\.[0-9]+(\.[0-9]+))', command[4]): nxlog.log(nxlog.LOG_DEBUG, 'Version too f****d') self.prompt(552, 'Protocol you requested is not supported') return # If the proffered version is the same as ours, or older.. if self._diff_version(self.version, command[4]) < 1: accept_ver = self.version else: accept_ver = command[4] self.prompt(134, 'Accepted protocol: %s' % accept_ver)
def check_command_var(varname, defval): def is_valid(path): try: cmd = path.split()[0] except IndexError: # Can happen if path is None, or "" etc cmd = path return cmd is not None and os.path.isfile(cmd) and os.access(cmd, os.X_OK) global __conf_errors varval = conf.get(varname) if varval is not None: # Is there a value set already? if is_valid(varval): return True # Everything checks out. else: nxlog.log(nxlog.LOG_WARNING, "Invalid command variable %s: \"%s\"\n" % (varname, varval)) if defval == "" or is_valid(defval): # Everything ok now, we assume it's blank if deliberately unset # (or if the command isn't available conf[varname] = defval return True else: nxlog.log(nxlog.LOG_ERR, "Invalid default command variable %s: \"%s\"\n" % (varname, defval)) __conf_errors = True return False
def __set_vars(self, parameters): """Set instances parameters from key=value string list Args: parameters: A list of key=value strings """ self.params = copy.deepcopy(default_params) # Read values from supplied set for pair in parameters.split('\n'): if not pair: continue name,val = pair.split('=', 1) if name not in default_params: nxlog.log(nxlog.LOG_ERR, "Invalid session parameter passed in: %s" % pair) else: self.params[name] = val if not self.params['id']: self.params['id'] = gen_uniq_id() # Always generate full_id self.params['full_id'] = "%(hostname)s-%(display)s-%(id)s" % self.params if not self.params['cookie']: self.params['cookie'] = gen_uniq_id() if not self.params['user']: self.params['user'] = pwd.getpwuid(os.getuid())[0] if not self.params['cache']: type = self.params['type'] if type.startswith('unix-'): type = type.split('-', 1)[0] self.params['cache'] = "cache-%s" % type
def _write_opts(self): """Create the session's 'options' file The options file is a comma-delimited list of options that are read in by nxagent """ try: opts_file = open(self.opts_file_path, 'w') opts = ['nx/nx', 'keyboard=%(keyboard)s' % self, 'geometry=%(geometry)s' % self, 'client=%(client)s' % self, 'cache=8M', 'images=32M', 'link=%(link)s' % self, 'type=%(shorttype)s' % self, 'clipboard=both', 'composite=1', 'cleanup=0', 'accept=127.0.0.1', 'product=Freenx-gpl', 'shmem=1', 'backingstore=1', 'shpix=1', 'cookie=%s' % self.cookie, 'id=%s' % self.full_id, 'strict=0'] if self.type == 'unix-application': opts.append('application=%(application)s' % self) if self.fullscreen == '1': opts.append('fullscreen=%(fullscreen)s' % self) opts_file.write("%s:%d\n" % (",".join(opts), self.display)) opts_file.close() except IOError, e: nxlog.log(nxlog.LOG_ERR, 'IOError when writing ' 'session options file: %s' % e)
def check_command_var(varname, defval): def is_valid(path): try: cmd = path.split()[0] except IndexError: # Can happen if path is None, or "" etc cmd = path return cmd is not None and os.path.isfile(cmd) and os.access( cmd, os.X_OK) global __conf_errors varval = conf.get(varname) if varval is not None: # Is there a value set already? if is_valid(varval): return True # Everything checks out. else: nxlog.log( nxlog.LOG_WARNING, "Invalid command variable %s: \"%s\"\n" % (varname, varval)) if defval == "" or is_valid(defval): # Everything ok now, we assume it's blank if deliberately unset # (or if the command isn't available conf[varname] = defval return True else: nxlog.log( nxlog.LOG_ERR, "Invalid default command variable %s: \"%s\"\n" % (varname, defval)) __conf_errors = True return False
def check_dir_var(varname, defval): def is_valid(path): return path is not None and os.path.isdir(path) global __conf_errors varval = conf.get(varname) if varval is not None: # Is there a value set already? if is_valid(varval): return True # Everything checks out. else: nxlog.log( nxlog.LOG_WARNING, "Invalid directory variable %s: \"%s\"\n" % (varname, varval)) if is_valid(defval): # Everything ok now conf[varname] = defval return True else: nxlog.log( nxlog.LOG_ERR, "Invalid default directory variable %s: \"%s\"\n" % (varname, defval)) __conf_errors = True return False
def _nx_hello_handler(self, command): """Handle the hello NX command. 'hello' is used to handshake the commandline session, and appears to support negotiation of the protocol version. We currently only accept versions which look like 3.x.x, matched using a regex. Args: command: The NX command and arguments invoked Returns: None """ # Basic checking of the right status if self.status >= self.STATUS_LOGGEDIN: self.prompt(554, 'Error: the command \'hello\' cannot ' 'be called after login') return if len(command) < 5: nxlog.log(nxlog.LOG_DEBUG, 'Hello too short') return if not re.match('^3(\.[0-9]+(\.[0-9]+))', command[4]): nxlog.log(nxlog.LOG_DEBUG, 'Version too f****d') self.prompt(552, 'Protocol you requested is not supported') return # If the proffered version is the same as ours, or older.. if self._diff_version(self.version, command[4]) < 1: accept_ver = self.version else: accept_ver = command[4] self.prompt(134, 'Accepted protocol: %s' % accept_ver)
def __set_vars(self, parameters): """Set instances parameters from key=value string list Args: parameters: A list of key=value strings """ self.params = copy.deepcopy(default_params) # Read values from supplied set for pair in parameters.split("\n"): if not pair: continue name, val = pair.split("=", 1) if name not in default_params: nxlog.log(nxlog.LOG_ERR, "Invalid session parameter passed in: %s" % pair) else: self.params[name] = val if not self.params["id"]: self.params["id"] = gen_uniq_id() # Always generate full_id self.params["full_id"] = "%(hostname)s-%(display)s-%(id)s" % self.params if not self.params["cookie"]: self.params["cookie"] = gen_uniq_id() if not self.params["user"]: self.params["user"] = pwd.getpwuid(os.getuid())[0] if not self.params["cache"]: type = self.params["type"] if type.startswith("unix-"): type = type.split("-", 1)[0] self.params["cache"] = "cache-%s" % type
def __conf_load(conf_file): conf_file_path = os.path.join(conf["PATH_ETC"], "%s.conf" % conf_file) if not os.path.exists(conf_file_path): nxlog.log(nxlog.LOG_DEBUG, "Requested file %s doesn't exist\n" % conf_file_path) return for line in subprocess.Popen('nxloadconfig-helper.sh %s' % conf_file_path, shell=True, stdout=subprocess.PIPE, env=conf).stdout: var, val = line.split('=') conf[var] = val.rstrip()
def setup(conf_file=None): global __conf_errors __conf_errors = False __conf_load("general") if conf_file: __conf_load(conf_file) __check_command_vars() __check_dir_vars() if __conf_errors: nxlog.log(nxlog.LOG_CRIT, "Configuration errors, exiting\n") sys.exit(1)
def set_state(self, name): """Set the state of the session Does some sanity checking to make sure the new state is valid Args: name: Name of the state to set the session to. Returns: None """ if name not in state_names: nxlog.log(nxlog.LOG_ERR, "Invalid state name passed in: %r" % name) # FIXME(diamond): handle error better else: self.params['state'] = name
def set_state(self, name): """Set the state of the session Does some sanity checking to make sure the new state is valid Args: name: Name of the state to set the session to. Returns: None """ if name not in state_names: nxlog.log(nxlog.LOG_ERR, "Invalid state name passed in: %r" % name) # FIXME(diamond): handle error better else: self.params["state"] = name
def write(self, output, newline=True, flush=True, log=True, log_level=nxlog.LOG_DEBUG, fd=None): """Write given string to output, and optionally: - append a newline - flush output afterwards - log the output, with a specified log level.""" if newline: output += '\n' use_fd = self.output if fd: use_fd = fd use_fd.write(output) if flush: use_fd.flush() if log: nxlog.log(log_level, 'Sent: %r\n' % output)
def __init__(self, input, output, version=DEFAULT_VERSION, program=DEFAULT_PROGRAM): """base_parser constructor Args: input: The file object to read parser input from. output: The file object to write the results to. version: The version string used to negotiate the protocol. program: The program name used in the protocol banner. """ self.input = input self.output = output self.state = 105 self.running = True self.parse_args(version=version, program=program) nxlog.log(nxlog.LOG_DEBUG, "Version: %s Program: %s" % (self.version, self.program))
def _write_args(self): """Create the session's 'args' file The args file is a newline-delimited list of arguments to be passed to nxagent """ try: args_file = open(self.args_file_path, 'w') #DEBUG, FIXME(diamond): args_file.write("\n".join([self.mode, '-options', self.opts_file_path, '-name', 'FreeNX - %(user)s@%(hostname)s:%(display)s' % self, '-nolisten', 'tcp', ':%d' % self.display])) args_file.write("\n") args_file.close() except IOError, e: nxlog.log(nxlog.LOG_ERR, 'IOError when writing ' 'session args file: %s' % e)
def __del__(self): """Destructor for the server_parser class This is needed to cleanup after server_parser is done. In particular, the file descriptors used for comms with nxnode may be in an errored state if nxnode has exited. """ for i in ['r', 'w']: var = "nxnode_%sfile" % i try: getattr(self, var).close() except IOError, e: if e.args[0] != errno.EBADF: nxlog.log(nxlog.LOG_WARNING, "Got error closing %s: %s\n" % (var, e)) except AttributeError: pass # self.nxnode_(r|w)file doesn't exist
def _parse_param(self, param): """Check that param is correctly formatted via the NX_PARAM_RX regex Args: param: parameter string to be checked, of the form --key="value" Returns: key,value tuple if param was correctly formatted, returns None,None otherwise. """ m = self.NX_PARAM_RX.search(param) if m: key = m.group('key') value = m.group('value') nxlog.log(nxlog.LOG_DEBUG, 'Param matched: %r=%r' % (key, value)) else: key = value = None nxlog.log(nxlog.LOG_WARNING, "Param didn't match: %r" % param) return key, value
def _parse_param(self, param): """Check that param is correctly formatted via the NX_PARAM_RX regex Args: param: parameter string to be checked, of the form --key="value" Returns: key,value tuple if param was correctly formatted, returns None,None otherwise. """ m = self.NX_PARAM_RX.search(param) if m: key = m.group('key') value = m.group('value') nxlog.log(nxlog.LOG_DEBUG, 'Param matched: %r=%r' % (key, value)) else: key = value = None nxlog.log(nxlog.LOG_WARNING, "Param didn't match: %r" % param) return key,value
def daemonize(self): """Drop into the background.""" # I am assumuing this throws if fork fails. pid = os.fork() # In the parent, return. if pid != 0: # self.nxnode_rfile.close() # self.nxnode_wfile.close() os.close(self.nxnode_commfd) # del(self.nxnode_rfile) # del(self.nxnode_wfile) # del(self.nxnode_commfd) nxlog.setup('nxserver-outer') nxlog.log(nxlog.LOG_INFO, "Forked child to take care of nxsession stuff") return False # Dissociate from the nxserver terminal os.setsid() # If we need to change signal behavior, do it here. # Close the stdio fds. os.close(0) os.close(1) os.close(2) self.input = self.nxnode_rfile self.output = self.nxnode_wfile # I'm not sure what to do here with self.nxnode_rfile and self.nxnode_wfile # Closing the fd is enough, but the file objects would linger on. del (self.nxnode_rfile) del (self.nxnode_wfile) del (self.nxnode_commfd) nxlog.setup('nxserver-inner') nxlog.log(nxlog.LOG_INFO, "Successfully forked, " "taking care of nxsession stuff\n") try: self._session_read_loop() except Exception: trace = traceback.format_exc() nxlog.log( nxlog.LOG_ERR, 'Going down because exception caught ' 'at the top level.') for line in trace.split('\n'): nxlog.log(nxlog.LOG_ERR, '%s' % line) return True
def daemonize(self): """Drop into the background.""" # I am assumuing this throws if fork fails. pid = os.fork() # In the parent, return. if pid != 0: # self.nxnode_rfile.close() # self.nxnode_wfile.close() os.close(self.nxnode_commfd) # del(self.nxnode_rfile) # del(self.nxnode_wfile) # del(self.nxnode_commfd) nxlog.setup('nxserver-outer') nxlog.log(nxlog.LOG_INFO, "Forked child to take care of nxsession stuff") return False # Dissociate from the nxserver terminal os.setsid() # If we need to change signal behavior, do it here. # Close the stdio fds. os.close(0) os.close(1) os.close(2) self.input = self.nxnode_rfile self.output = self.nxnode_wfile # I'm not sure what to do here with self.nxnode_rfile and self.nxnode_wfile # Closing the fd is enough, but the file objects would linger on. del(self.nxnode_rfile) del(self.nxnode_wfile) del(self.nxnode_commfd) nxlog.setup('nxserver-inner') nxlog.log(nxlog.LOG_INFO, "Successfully forked, " "taking care of nxsession stuff\n") try: self._session_read_loop() except Exception: trace = traceback.format_exc() nxlog.log(nxlog.LOG_ERR, 'Going down because exception caught ' 'at the top level.') for line in trace.split('\n'): nxlog.log(nxlog.LOG_ERR, '%s' % line) return True
def check_dir_var(varname, defval): def is_valid(path): return path is not None and os.path.isdir(path) global __conf_errors varval = conf.get(varname) if varval is not None: # Is there a value set already? if is_valid(varval): return True # Everything checks out. else: nxlog.log(nxlog.LOG_WARNING, "Invalid directory variable %s: \"%s\"\n" % (varname, varval)) if is_valid(defval): # Everything ok now conf[varname] = defval return True else: nxlog.log(nxlog.LOG_ERR, "Invalid default directory variable %s: \"%s\"\n" % (varname, defval)) __conf_errors = True return False
def _nx_startsession_handler(self, command): # Remove 'startsession' from the front of the list of args command.pop(0) id = command.pop(0) req = {} for param in command: key, val = self._parse_param(param) if key: req[key] = val sess = self.node_session(id, req) # FIXME(diamond): change number to something sensible self.write("NX> 8888 sessioncreate %s" % sess.info()) # Let stdout go directly to our stdout, i.e. to nxserver # Check stderr for error messages if things go badly p = subprocess.Popen('/usr/freenx/bin/nxagent-helper', stdin=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) p.stdin.write('start %s\n' % sess.full_id) p.stdin.flush() nxlog.log(nxlog.LOG_DEBUG, 'Starting session') child_status = p.wait() if child_status != 0: lines = p.stderr.readlines() if not lines: out_msg = ", no output printed" else: out_msg = ", with %d lines of output (shown below):" % len( lines) nxlog.log(nxlog.LOG_ERR, 'Start session failed %d%s' % (child_status, out_msg)) for line in lines: nxlog.log(nxlog.LOG_ERR, 'from nxagent-helper: %s' % line) self.prompt(500, 'Error: Startsession failed') self.running = False return nxlog.log(nxlog.LOG_ERR, 'Session completed %d' % child_status) self.running = False
def _nx_startsession_handler(self, command): # Remove 'startsession' from the front of the list of args command.pop(0) id = command.pop(0) req = {} for param in command: key,val = self._parse_param(param) if key: req[key] = val sess = self.node_session(id, req) # FIXME(diamond): change number to something sensible self.write("NX> 8888 sessioncreate %s" % sess.info()) # Let stdout go directly to our stdout, i.e. to nxserver # Check stderr for error messages if things go badly p = subprocess.Popen('/usr/freenx/bin/nxagent-helper', stdin=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) p.stdin.write('start %s\n' % sess.full_id) p.stdin.flush() nxlog.log(nxlog.LOG_DEBUG, 'Starting session') child_status = p.wait() if child_status != 0: lines = p.stderr.readlines() if not lines: out_msg = ", no output printed" else: out_msg = ", with %d lines of output (shown below):" % len(lines) nxlog.log(nxlog.LOG_ERR, 'Start session failed %d%s' % (child_status, out_msg)) for line in lines: nxlog.log(nxlog.LOG_ERR, 'from nxagent-helper: %s' % line) self.prompt(500, 'Error: Startsession failed') self.running = False return nxlog.log(nxlog.LOG_ERR, 'Session completed %d' % child_status) self.running = False
def loop(self): """Write the protocol prompt to the output, and accept commands.""" try: while self.running: self.prompt(self.state) line = self.input.readline() if not line: nxlog.log(nxlog.LOG_DEBUG, "Exiting due to EOF") return line = line.rstrip() nxlog.log(nxlog.LOG_DEBUG, 'Got %r' % line) command = line.split() if not command: # If the line was all whitespace this could happen. continue cmd = command[0].lower() if cmd == 'set': self.write("%s %s: %s" % (cmd.capitalize(), command[1].lower(), command[2].lower())) elif cmd == 'startsession': self.write("Start session with: %s" % " ".join(command[1:])) else: self.write(line.capitalize()) if cmd not in self.NX_COMMANDS: self.prompt(503, 'Error: undefined command: \'%s\'' % cmd) continue handler_name = '_nx_%s_handler' % cmd try: handler_method = getattr(self, handler_name) except AttributeError: nxlog.log(nxlog.LOG_DEBUG, 'Unhandled nx command %r' % cmd) continue handler_method(command) except IOError, e: nxlog.log(nxlog.LOG_ERR, 'IOError. Connection lost: %s' % e)
'cache=8M', 'images=32M', 'link=%(link)s' % self, 'type=%(shorttype)s' % self, 'clipboard=both', 'composite=1', 'cleanup=0', 'accept=127.0.0.1', 'product=Freenx-gpl', 'shmem=1', 'backingstore=1', 'shpix=1', 'cookie=%s' % self.cookie, 'id=%s' % self.full_id, 'strict=0'] if self.type == 'unix-application': opts.append('application=%(application)s' % self) if self.fullscreen == '1': opts.append('fullscreen=%(fullscreen)s' % self) opts_file.write("%s:%d\n" % (",".join(opts), self.display)) opts_file.close() except IOError, e: nxlog.log(nxlog.LOG_ERR, 'IOError when writing ' 'session options file: %s' % e) except OSError, e: nxlog.log(nxlog.LOG_ERR, 'OSError when writing ' 'session options file: %s' % e) def info(self): #This is for reporting back to nxserver """Return a string with all parameter values encoded into it""" # Needed for session list: # Display number # type # id # options(?) FRD--PSA (F=fullscreen, R=render, # D=non-rootless(Desktop?), PSA?) # depth # resolution # status # name # Needed for session start: # hostname
def testLog(self): """Test calling log function""" nxlog.log(0, "test log message")
sess.params['full_id']) # Needed to get nxagent to open it's port again. try: os.kill(int(sess.params['agent_pid']), signal.SIGHUP) except OSError, e: nxlog.log(nxlog.LOG_WARNING, "Attempted to send SIGHUP to nxagent, " "got error from kill[%d]: %s\n" % e.args) self.prompt(500, 'Error: Fatal error in module %s, ' 'check log file for more details.' % self.program.lower()) self.prompt(999, 'Bye.') self.running = False return except (TypeError, ValueError), e: nxlog.log(nxlog.LOG_WARNING, "Session does not have a valid nxagent pid " "stored (instead has %r), got error: %s" % (sess.params['agent_pid'], e)) self.prompt(500, 'Error: Fatal error in module %s, ' 'check log file for more details.' % self.program.lower()) self.prompt(999, 'Bye.') self.running = False return else: nxlog.log(nxlog.LOG_NOTICE, "Sent SIGHUP to nxagent\n") self.__print_sess_info(sess) self.prompt(710, 'Session status: %s' % sess.params['state']) #FIXME(diamond): use configurable offset self.port = int(sess.params['display']) + 4000 def daemonize(self):
def _nx_login_handler(self, unused_command): """Handle the login NX command. 'login' is used to start the process of authenticating to NX. The username and password is send in response to requests from the server. If no options have been set, nxserver will ask for the password, and will then ask for an 'MD5 Password' if no password is given. If 'SET AUTH_MODE PASSWORD' has been sent by the client, it does not do this however. This code currently never requests the MD5 Password. It is possible that making use of this feature requires storing the users password and verifying it with the hash. Args: command: The NX command and arguments invoked Returns: None """ # Basic checking of the right status if self.status >= self.STATUS_LOGGEDIN: self.prompt(554, 'Error: the command \'login\' cannot be ' 'called after login') return self.prompt(101, 'User: '******'Error: Username is not in expected format') return self.write('') # Print newline after username self.username = split_line[0] nxlog.log(nxlog.LOG_DEBUG, 'Got user %r' % self.username) self.prompt(102, 'Password: '******'Got fd %r' % fd) # Save the terminal settings try: old = termios.tcgetattr(fd) new = old[:] # Disable the echo flag new[3] = new[3] & ~termios.ECHO # 3 == 'lflags' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) passwd = self.input.readline() finally: termios.tcsetattr(fd, termios.TCSADRAIN, old) except termios.error: passwd = self.input.readline() nxlog.log(nxlog.LOG_DEBUG, 'Got a passwd') self.write('\n') # FIXME(diamond): ssh to localhost to verify the username and password are # correct. Also store the authentication information we need in a secure # way. del passwd self.status = self.STATUS_LOGGEDIN self.banner()
def _nx_restoresession_handler(self, command): """Handle the restoresession NX command. 'restoresession' requests an existing session be resume. It requires parameters be specified. The following parameters have been seen, at a minimum the session id must be specified: '--link="lan"' '--backingstore="1"' '--encryption="1"' '--cache="16M"' '--images="64M"' '--shmem="1"' '--shpix="1"' '--strict="0"' '--composite="1"' '--media="0"' '--session="localtest"' '--type="unix-gnome"' '--geometry="3840x1150"' '--client="linux"' '--keyboard="pc102/gb"' '--screeninfo="3840x1150x24+render"' --id="A28EBF5AAC354E9EEAFEEB867980C543" Args: command: The NX command and arguments invoked Returns: None """ # Basic checking of the right status if self.status < self.STATUS_LOGGEDIN: self.prompt( 554, 'Error: the command \'%s\' cannot ' 'be called before login' % command[0]) return # Make sure the state is consistent assert (hasattr(self, 'username')) # Ask for parameters if none have been given if len(command) > 1: parameters = command[1:] else: self.prompt(106, 'Parameters: ', override_newline=False) response = self.input.readline() self.write('') # Do newline after parameters. parameters = response.split() req = {} # Check the parameters fit with the expected syntax for param in parameters: key, val = self._parse_param(param) if key: req[key] = val # FIXME(diamond): DO something with the params. if not req.has_key('id'): msg = "Restore session requested, but no session specified" nxlog.log(nxlog.LOG_ERR, "%s (args: %r)\n" % (msg, req)) self.prompt(500, 'Error: %s. check log file for more details.' % msg) self.running = False return nxlog.log(nxlog.LOG_DEBUG, "Got id param: %s" % req['id']) sessions = nxsession.db_find_sessions(id=req['id'], users=[self.username], states=['suspended', 'running']) if len(sessions) != 1: nxlog.log( nxlog.LOG_ERR, "%d sessions found matching %s in " "session db %s\n" % (len(sessions), req['id'], sessions)) self.prompt( 500, 'Error: Fatal error in module %s, ' 'check log file for more details.' % self.program.lower()) self.running = False return sess = sessions[0] nxlog.log(nxlog.LOG_DEBUG, "Session %s found in session db\n" % sess.params['full_id']) # Needed to get nxagent to open it's port again. try: os.kill(int(sess.params['agent_pid']), signal.SIGHUP) except OSError, e: nxlog.log( nxlog.LOG_WARNING, "Attempted to send SIGHUP to nxagent, " "got error from kill[%d]: %s\n" % e.args) self.prompt( 500, 'Error: Fatal error in module %s, ' 'check log file for more details.' % self.program.lower()) self.prompt(999, 'Bye.') self.running = False return
def _nx_resumesession_handler(self, unused_command): nxlog.log(nxlog.LOG_DEBUG, 'Resuming session')
def _nx_restoresession_handler(self, command): """Handle the restoresession NX command. 'restoresession' requests an existing session be resume. It requires parameters be specified. The following parameters have been seen, at a minimum the session id must be specified: '--link="lan"' '--backingstore="1"' '--encryption="1"' '--cache="16M"' '--images="64M"' '--shmem="1"' '--shpix="1"' '--strict="0"' '--composite="1"' '--media="0"' '--session="localtest"' '--type="unix-gnome"' '--geometry="3840x1150"' '--client="linux"' '--keyboard="pc102/gb"' '--screeninfo="3840x1150x24+render"' --id="A28EBF5AAC354E9EEAFEEB867980C543" Args: command: The NX command and arguments invoked Returns: None """ # Basic checking of the right status if self.status < self.STATUS_LOGGEDIN: self.prompt(554, 'Error: the command \'%s\' cannot ' 'be called before login' % command[0]) return # Make sure the state is consistent assert(hasattr(self, 'username')) # Ask for parameters if none have been given if len(command) > 1: parameters = command[1:] else: self.prompt(106, 'Parameters: ', override_newline=False) response = self.input.readline() self.write('') # Do newline after parameters. parameters = response.split() req = {} # Check the parameters fit with the expected syntax for param in parameters: key,val = self._parse_param(param) if key: req[key] = val # FIXME(diamond): DO something with the params. if not req.has_key('id'): msg = "Restore session requested, but no session specified" nxlog.log(nxlog.LOG_ERR, "%s (args: %r)\n" % (msg, req)) self.prompt(500, 'Error: %s. check log file for more details.' % msg) self.running = False return nxlog.log(nxlog.LOG_DEBUG, "Got id param: %s" % req['id']) sessions = nxsession.db_find_sessions(id=req['id'], users=[self.username], states=['suspended', 'running']) if len(sessions) != 1: nxlog.log(nxlog.LOG_ERR, "%d sessions found matching %s in " "session db %s\n" % (len(sessions), req['id'], sessions)) self.prompt(500, 'Error: Fatal error in module %s, ' 'check log file for more details.' % self.program.lower()) self.running = False return sess = sessions[0] nxlog.log(nxlog.LOG_DEBUG, "Session %s found in session db\n" % sess.params['full_id']) # Needed to get nxagent to open it's port again. try: os.kill(int(sess.params['agent_pid']), signal.SIGHUP) except OSError, e: nxlog.log(nxlog.LOG_WARNING, "Attempted to send SIGHUP to nxagent, " "got error from kill[%d]: %s\n" % e.args) self.prompt(500, 'Error: Fatal error in module %s, ' 'check log file for more details.' % self.program.lower()) self.prompt(999, 'Bye.') self.running = False return
def _session_read_loop(self): sess = None while True: line = self.input.readline() if not line: return line = line.rstrip() nxlog.log(nxlog.LOG_DEBUG, 'Got from nxnode %r\n' % line) # FIXME(diamond): change number to something sensible if line.startswith('NX> 8888 sessioncreate'): if sess: nxlog.log(nxlog.LOG_ERR, 'Nxnode tried to create a session when one ' 'already exists: %s\n' % line) else: args = line.split(' ', 3)[-1].replace(' ', '\n') sess = nxsession.nxsession(args) elif line.startswith('NX> 8888 agentpid:'): if not sess: nxlog.log(nxlog.LOG_ERR, 'Nxagent-helper tried to change session ' 'when none exists: %s\n' % line) else: agent_pid = line.rstrip().split(' ')[3] sess.params['agent_pid'] = agent_pid sess.save() nxlog.log(nxlog.LOG_DEBUG, "Agent pid set to '%s'\n" % sess.params['agent_pid']) elif line.startswith('NX> 1009 Session status:'): if not sess: nxlog.log(nxlog.LOG_ERR, 'Nxagent-helper tried to change session ' 'when none exists: %s\n' % line) else: state_name = line.rstrip().split(' ')[4] sess.set_state(state_name) sess.save() nxlog.log(nxlog.LOG_DEBUG, "Session state updated to '%s'\n" % sess.params['state']) elif line.startswith('NX> 500 Error:'): if sess: sess.set_state('terminated') sess.save() nxlog.log(nxlog.LOG_DEBUG, "Session state updated to '%s', exiting\n" % sess.params['state']) break
# Needed to get nxagent to open it's port again. try: os.kill(int(sess.params['agent_pid']), signal.SIGHUP) except OSError, e: nxlog.log( nxlog.LOG_WARNING, "Attempted to send SIGHUP to nxagent, " "got error from kill[%d]: %s\n" % e.args) self.prompt( 500, 'Error: Fatal error in module %s, ' 'check log file for more details.' % self.program.lower()) self.prompt(999, 'Bye.') self.running = False return except (TypeError, ValueError), e: nxlog.log( nxlog.LOG_WARNING, "Session does not have a valid nxagent pid " "stored (instead has %r), got error: %s" % (sess.params['agent_pid'], e)) self.prompt( 500, 'Error: Fatal error in module %s, ' 'check log file for more details.' % self.program.lower()) self.prompt(999, 'Bye.') self.running = False return else: nxlog.log(nxlog.LOG_NOTICE, "Sent SIGHUP to nxagent\n") self.__print_sess_info(sess) self.prompt(710, 'Session status: %s' % sess.params['state']) #FIXME(diamond): use configurable offset self.port = int(sess.params['display']) + 4000
def _nx_login_handler(self, unused_command): """Handle the login NX command. 'login' is used to start the process of authenticating to NX. The username and password is send in response to requests from the server. If no options have been set, nxserver will ask for the password, and will then ask for an 'MD5 Password' if no password is given. If 'SET AUTH_MODE PASSWORD' has been sent by the client, it does not do this however. This code currently never requests the MD5 Password. It is possible that making use of this feature requires storing the users password and verifying it with the hash. Args: command: The NX command and arguments invoked Returns: None """ # Basic checking of the right status if self.status >= self.STATUS_LOGGEDIN: self.prompt( 554, 'Error: the command \'login\' cannot be ' 'called after login') return self.prompt(101, 'User: '******'Error: Username is not in expected format') return self.write('') # Print newline after username self.username = split_line[0] nxlog.log(nxlog.LOG_DEBUG, 'Got user %r' % self.username) self.prompt(102, 'Password: '******'Got fd %r' % fd) # Save the terminal settings try: old = termios.tcgetattr(fd) new = old[:] # Disable the echo flag new[3] = new[3] & ~termios.ECHO # 3 == 'lflags' try: termios.tcsetattr(fd, termios.TCSADRAIN, new) passwd = self.input.readline() finally: termios.tcsetattr(fd, termios.TCSADRAIN, old) except termios.error: passwd = self.input.readline() nxlog.log(nxlog.LOG_DEBUG, 'Got a passwd') self.write('\n') # FIXME(diamond): ssh to localhost to verify the username and password are # correct. Also store the authentication information we need in a secure # way. del passwd self.status = self.STATUS_LOGGEDIN self.banner()
def _nx_startsession_handler(self, command): """Handle the startsession NX command. 'startsession' seems to request a new session be started. It requires parameters be specified. The following parameters have been seen. '--link="lan"' '--backingstore="1"' '--encryption="1"' '--cache="16M"' '--images="64M"' '--shmem="1"' '--shpix="1"' '--strict="0"' '--composite="1"' '--media="0"' '--session="localtest"' '--type="unix-gnome"' '--geometry="3840x1150"' '--client="linux"' '--keyboard="pc102/gb"' '--screeninfo="3840x1150x24+render"' Experiments with this command by directly invoked nxserver have not worked, as it refuses to create a session saying the unencrypted sessions are not supported. This is independent of whether the --encryption option has been set, so probably is related to the fact the nxserver has not been launched by sshd. Args: command: The NX command and arguments invoked Returns: None """ # Basic checking of the right status if self.status < self.STATUS_LOGGEDIN: self.prompt( 554, 'Error: the command \'%s\' cannot ' 'be called before login' % command[0]) return # Make sure the state is consistent assert (hasattr(self, 'username')) # Ask for parameters if none have been given if len(command) > 1: parameters = command[1:] else: self.prompt(106, 'Parameters: ', override_newline=False) response = self.input.readline() self.write('') # Do newline after parameters. parameters = response.split() # Check the parameters fit with the expected syntax for param in parameters: key, val = self._parse_param(param) # FIXME(diamond): DO something with the params. # FIXME(diamond): Start the session. if not hasattr(self, 'nxnode_commfd'): nxlog.log(nxlog.LOG_ERR, 'Nxserver does not have an nxnode yet.') return # Send the command to the connected nxnode running # FIXME(diamond): Convert the arguments to the form expected by nxnode. sess_id = nxsession.gen_uniq_id() self.write('startsession %s %s' % (sess_id, " ".join(parameters)), fd=self.nxnode_wfile) if self.daemonize(): # Two threads return here, one connected to the client, one connected to # nxnode. self.running = False return start_waiting = time.time() wait_time = 30 #FIXME(diamond): make configurable while True: if time.time() - start_waiting > wait_time: nxlog.log( nxlog.LOG_ERR, "Session %s has not appeared in session db " "within %d seconds\n" % (sess_id, wait_time)) sys.exit(1) #FIXME(diamond): raise proper error sessions = nxsession.db_find_sessions(id=sess_id) if len(sessions) == 1: sess = sessions[0] nxlog.log( nxlog.LOG_DEBUG, "Session %s has appeared in session db\n" % sess.params['full_id']) break elif len(sessions) > 1: nxlog.log( nxlog.LOG_DEBUG, "Multiple sessions matching %d have been " "found in the session db: %r\n" % sess_id) #FIXME(diamond): raise proper error break else: time.sleep(1) self.__print_sess_info(sess) start_waiting = time.time() wait_time = 30 #FIXME(diamond): make configurable while True: if time.time() - start_waiting > wait_time: nxlog.log( nxlog.LOG_ERR, "Session %s has not achieved running status " "within %d seconds\n" % (sess_id, wait_time)) sys.exit(1) #FIXME(diamond): raise proper error sess.reload() if sess.params['state'] == 'starting': break elif sess.params['state'] in ['terminating', 'terminated']: nxlog.log( nxlog.LOG_ERR, "Session %(full_id)s has status " "'%(state)s', exiting." % sess.params) self.prompt( 500, "Error: Session %(full_id)s has status '%(state)s'." % sess.params) self.prompt(999, "Bye.") self.running = False return #FIXME(diamond): raise proper error else: time.sleep(1) self.prompt(710, 'Session status: %s' % sess.params['state']) #FIXME(diamond): use configurable offset self.port = int(sess.params['display']) + 4000
class parser: """Base parser for NX protocol parsers. This class handles breaking up the messages into components, and dispatching them. """ DEFAULT_VERSION = '3.0.0' DEFAULT_PROGRAM = 'NXBASE' NX_PROMPT = 'NX>' NX_COMMANDS = [ 'hello', 'login', 'bye', 'set', 'listsession', 'restoresession', 'startsession', 'terminate' ] NX_PARAM_RX = re.compile(r'^--(?P<key>[a-z]+)="(?P<value>.+)"') def __init__(self, input, output, version=DEFAULT_VERSION, program=DEFAULT_PROGRAM): """base_parser constructor Args: input: The file object to read parser input from. output: The file object to write the results to. version: The version string used to negotiate the protocol. program: The program name used in the protocol banner. """ self.input = input self.output = output self.state = 105 self.running = True self.parse_args(version=version, program=program) nxlog.log(nxlog.LOG_DEBUG, "Version: %s Program: %s" % (self.version, self.program)) def banner(self): """Write the protocol banner to the output.""" self.write('HELLO %s - Version %s - GPL' % (self.program, self.version)) def prompt(self, state, message='', override_newline=None): """Write the protocol prompt to the output. If no message is given, just print the prompt & state, no newline. If a message is provided, by default append a newline. Args: state: The state number to put after the NX> prompt message: Optional message to print after the state override_newline: Optional param to force a trailing newline on/off""" newline = False if override_newline is not None: newline = override_newline elif message: newline = True self.write('%s %d %s' % (self.NX_PROMPT, state, message), newline=newline) def loop(self): """Write the protocol prompt to the output, and accept commands.""" try: while self.running: self.prompt(self.state) line = self.input.readline() if not line: nxlog.log(nxlog.LOG_DEBUG, "Exiting due to EOF") return line = line.rstrip() nxlog.log(nxlog.LOG_DEBUG, 'Got %r' % line) command = line.split() if not command: # If the line was all whitespace this could happen. continue cmd = command[0].lower() if cmd == 'set': self.write("%s %s: %s" % (cmd.capitalize(), command[1].lower(), command[2].lower())) elif cmd == 'startsession': self.write("Start session with: %s" % " ".join(command[1:])) else: self.write(line.capitalize()) if cmd not in self.NX_COMMANDS: self.prompt(503, 'Error: undefined command: \'%s\'' % cmd) continue handler_name = '_nx_%s_handler' % cmd try: handler_method = getattr(self, handler_name) except AttributeError: nxlog.log(nxlog.LOG_DEBUG, 'Unhandled nx command %r' % cmd) continue handler_method(command) except IOError, e: nxlog.log(nxlog.LOG_ERR, 'IOError. Connection lost: %s' % e) except Exception, e: trace = traceback.format_exc() nxlog.log( nxlog.LOG_ERR, 'Going down because exception caught ' 'at the top level.') for line in trace.split('\n'): nxlog.log(nxlog.LOG_ERR, '%s' % line) self.prompt( 500, 'Error: Fatal error in module %s, ' 'check log file for more details.' % self.program.lower()) self.prompt(999, 'Bye.')
def _nx_startsession_handler(self, command): """Handle the startsession NX command. 'startsession' seems to request a new session be started. It requires parameters be specified. The following parameters have been seen. '--link="lan"' '--backingstore="1"' '--encryption="1"' '--cache="16M"' '--images="64M"' '--shmem="1"' '--shpix="1"' '--strict="0"' '--composite="1"' '--media="0"' '--session="localtest"' '--type="unix-gnome"' '--geometry="3840x1150"' '--client="linux"' '--keyboard="pc102/gb"' '--screeninfo="3840x1150x24+render"' Experiments with this command by directly invoked nxserver have not worked, as it refuses to create a session saying the unencrypted sessions are not supported. This is independent of whether the --encryption option has been set, so probably is related to the fact the nxserver has not been launched by sshd. Args: command: The NX command and arguments invoked Returns: None """ # Basic checking of the right status if self.status < self.STATUS_LOGGEDIN: self.prompt(554, 'Error: the command \'%s\' cannot ' 'be called before login' % command[0]) return # Make sure the state is consistent assert(hasattr(self, 'username')) # Ask for parameters if none have been given if len(command) > 1: parameters = command[1:] else: self.prompt(106, 'Parameters: ', override_newline=False) response = self.input.readline() self.write('') # Do newline after parameters. parameters = response.split() # Check the parameters fit with the expected syntax for param in parameters: key,val = self._parse_param(param) # FIXME(diamond): DO something with the params. # FIXME(diamond): Start the session. if not hasattr(self, 'nxnode_commfd'): nxlog.log(nxlog.LOG_ERR, 'Nxserver does not have an nxnode yet.') return # Send the command to the connected nxnode running # FIXME(diamond): Convert the arguments to the form expected by nxnode. sess_id = nxsession.gen_uniq_id() self.write('startsession %s %s' % (sess_id, " ".join(parameters)), fd=self.nxnode_wfile) if self.daemonize(): # Two threads return here, one connected to the client, one connected to # nxnode. self.running = False return start_waiting = time.time() wait_time = 30 #FIXME(diamond): make configurable while True: if time.time() - start_waiting > wait_time: nxlog.log(nxlog.LOG_ERR, "Session %s has not appeared in session db " "within %d seconds\n" % (sess_id, wait_time)) sys.exit(1) #FIXME(diamond): raise proper error sessions = nxsession.db_find_sessions(id=sess_id) if len(sessions) == 1: sess = sessions[0] nxlog.log(nxlog.LOG_DEBUG, "Session %s has appeared in session db\n" % sess.params['full_id']) break elif len(sessions) > 1: nxlog.log(nxlog.LOG_DEBUG, "Multiple sessions matching %d have been " "found in the session db: %r\n" % sess_id) #FIXME(diamond): raise proper error break else: time.sleep(1) self.__print_sess_info(sess) start_waiting = time.time() wait_time = 30 #FIXME(diamond): make configurable while True: if time.time() - start_waiting > wait_time: nxlog.log(nxlog.LOG_ERR, "Session %s has not achieved running status " "within %d seconds\n" % (sess_id, wait_time)) sys.exit(1) #FIXME(diamond): raise proper error sess.reload() if sess.params['state'] == 'starting': break elif sess.params['state'] in ['terminating', 'terminated']: nxlog.log(nxlog.LOG_ERR, "Session %(full_id)s has status " "'%(state)s', exiting." % sess.params) self.prompt(500, "Error: Session %(full_id)s has status '%(state)s'." % sess.params) self.prompt(999, "Bye.") self.running = False return #FIXME(diamond): raise proper error else: time.sleep(1) self.prompt(710, 'Session status: %s' % sess.params['state']) #FIXME(diamond): use configurable offset self.port = int(sess.params['display']) + 4000
def testLog(self): """Test calling log function""" nxlog.log(0, 'test log message')
class node_session: """Internal representation of a session This class is used by nxnode to store session parameters, to create the needed session args & options file, and to print out the parameters for transmission to nxserver-inner. """ def __init__(self, id, args): """node_session constructor Args: args: The id of the session, followed by all the other parameters the client requested """ self.id = id self.args = args self.display = self._gen_disp_num() self.hostname = socket.getfqdn() self.full_id = "%s-%s-%s" % (self.hostname, self.display, self.id) self.cookie = nxsession.gen_uniq_id() self.dir = os.path.join('/tmp/nx', 'S-%s' % self.full_id) #FIXME(diamond): needs error checking, maybe different mode os.makedirs(self.dir, 0755) self.opts_file_path = os.path.join(self.dir, 'options') self.args_file_path = os.path.join(self.dir, 'args') self.application = self.args.get('application') self.user = pwd.getpwuid(os.getuid())[0] self.name = self.args.get('session', "%s:%s" % (self.hostname, self.display)) self.keyboard = self.args.get('keyboard', 'pc105/gb') self.geometry = self.args.get('geometry', '640x480') self.client = self.args.get('client', 'unknown') self.link = self.args.get('link', 'isdn') self.fullscreen = self.args.get('fullscreen', '0') #DEBUG, FIXME(diamond): all of these self.type = self.args.get('type', 'unix-default') self.options = '-----PSA' #FIXME(diamond): see note in self.info() self.depth = 24 self.resolution = "640x480" #FIXME(diamond): Not ipv6 compatible self.proxyip = socket.gethostbyname(self.hostname) self.ssl = 1 #End DEBUG/FIXME(diamond) if self.type == 'unix-application': assert(self.application) self.mode = '-R' # Run nxagent in rootless mode else: self.mode = '-D' # Run nxagent in desktop mode # We need to write the type without the 'unix-' prefix for nxagent. if self.type.startswith('unix-'): self.shorttype = self.type.split('-', 1)[1] else: self.shorttype = self.type self._write_args() self._write_opts() def __getitem__(self, item): """Allow node_session instances to be treated as dicts This is used in places like self.info(), to allow cleaner variable substitution of variables in strings. """ return getattr(self, item) def _gen_disp_num(self): """Return an unused display number (corresponding to an unused port)""" return 20 #DEBUG, FIXME(diamond) def _write_args(self): """Create the session's 'args' file The args file is a newline-delimited list of arguments to be passed to nxagent """ try: args_file = open(self.args_file_path, 'w') #DEBUG, FIXME(diamond): args_file.write("\n".join([self.mode, '-options', self.opts_file_path, '-name', 'FreeNX - %(user)s@%(hostname)s:%(display)s' % self, '-nolisten', 'tcp', ':%d' % self.display])) args_file.write("\n") args_file.close() except IOError, e: nxlog.log(nxlog.LOG_ERR, 'IOError when writing ' 'session args file: %s' % e) except OSError, e: nxlog.log(nxlog.LOG_ERR, 'OSError when writing ' 'session args file: %s' % e)
def _session_read_loop(self): sess = None while True: line = self.input.readline() if not line: return line = line.rstrip() nxlog.log(nxlog.LOG_DEBUG, 'Got from nxnode %r\n' % line) # FIXME(diamond): change number to something sensible if line.startswith('NX> 8888 sessioncreate'): if sess: nxlog.log( nxlog.LOG_ERR, 'Nxnode tried to create a session when one ' 'already exists: %s\n' % line) else: args = line.split(' ', 3)[-1].replace(' ', '\n') sess = nxsession.nxsession(args) elif line.startswith('NX> 8888 agentpid:'): if not sess: nxlog.log( nxlog.LOG_ERR, 'Nxagent-helper tried to change session ' 'when none exists: %s\n' % line) else: agent_pid = line.rstrip().split(' ')[3] sess.params['agent_pid'] = agent_pid sess.save() nxlog.log( nxlog.LOG_DEBUG, "Agent pid set to '%s'\n" % sess.params['agent_pid']) elif line.startswith('NX> 1009 Session status:'): if not sess: nxlog.log( nxlog.LOG_ERR, 'Nxagent-helper tried to change session ' 'when none exists: %s\n' % line) else: state_name = line.rstrip().split(' ')[4] sess.set_state(state_name) sess.save() nxlog.log( nxlog.LOG_DEBUG, "Session state updated to '%s'\n" % sess.params['state']) elif line.startswith('NX> 500 Error:'): if sess: sess.set_state('terminated') sess.save() nxlog.log( nxlog.LOG_DEBUG, "Session state updated to '%s', exiting\n" % sess.params['state']) break