def start(self, addr, retries=5): """Start the web server listening on addr in a new coroutine. Try up to retries time to bind to that address. Raises an exception if the bind fails.""" server_s = self._make_socket() done = 0 save_errno = 0 self.addr = addr while not done: for x in xrange(retries): try: was_eaddrinuse = 0 server_s.bind(addr) except OSError, why: if why.errno not in (errno.EADDRNOTAVAIL, errno.EADDRINUSE): raise else: save_errno = 0 if why.errno == errno.EADDRINUSE: was_eaddrinuse = 1 else: done = 1 break coro.sleep_relative(1) # ... and retry else: coro.print_stderr( "cannot bind to %s:%d after 5 attempts, errno = %d\n" % (addr[0], addr[1], save_errno)) if was_eaddrinuse: qlog.write('WEBUI.PORT_IN_USE', addr[0], str(addr[1])) coro.sleep_relative(15)
def start (self, addr, retries=5): """Start the web server listening on addr in a new coroutine. Try up to retries time to bind to that address. Raises an exception if the bind fails.""" server_s = self._make_socket() done = 0 save_errno = 0 self.addr = addr while not done: for x in xrange (retries): try: was_eaddrinuse = 0 server_s.bind (addr) except OSError, why: if why.errno not in (errno.EADDRNOTAVAIL, errno.EADDRINUSE): raise else: save_errno = 0 if why.errno == errno.EADDRINUSE: was_eaddrinuse = 1 else: done = 1 break coro.sleep_relative(1) # ... and retry else: coro.print_stderr ("cannot bind to %s:%d after 5 attempts, errno = %d\n" % (addr[0], addr[1], save_errno)) if was_eaddrinuse: qlog.write('WEBUI.PORT_IN_USE', addr[0], str(addr[1])) coro.sleep_relative(15)
def cmd_retr (self, line): if len(line) < 2: self.command_not_understood (string.join (line)) else: file = line[1] if not self.filesystem.isfile (file): self.respond ('550 No such file') else: try: # FIXME: for some reason, 'rt' isn't working on win95 mode = 'r'+self.type_mode_map[self.current_mode] fd = self.open (file, mode) except (OSError, IOError), why: self.respond ('553 could not open file for reading: %r' % why[0]) return try: self.respond ( "150 Opening %s mode data connection for file '%s'" % ( self.type_map[self.current_mode], file ) ) conn, addr = self.make_data_channel() if conn: try: fd.seek(0, 2) filesize = fd.tell() fd.seek(0, 0) qlog.write('FTPD.DOWNLOAD', self.session_id, file, filesize) if self.restart_position: # try to position the file as requested, but # give up silently on failure (the 'file object' # may not support seek()) try: fd.seek (self.restart_position) except: pass self.restart_position = 0 try: self.send_file (conn, fd) except OSError, why: self.respond ('451 Transfer aborted. %s' % (str(why))) except coro.TimeoutError: self.respond ('421 Transfer timed out.') except coro.Shutdown: self.respond ('451 Transfer aborted. FTP service shutting down.') else: self.respond ('226 Transfer Complete') finally: conn.close()
def run (self): try: try: self._run() except coro.Shutdown: # We've been asked to shutdown return except: qlog.write('COMMON.APP_FAILURE', tb.traceback_string() + `self`) finally: if self.user: qlog.write('FTPD.LOGOUT', self.session_id, self.user) self.close() self.server.session_done(self) # remove cycle del self.stream
def _run (self): """Listens on the FTP port accepting connections and spawning sessions.""" self.thread_id = coro.current().thread_id() s = coro.make_socket (socket.AF_INET, socket.SOCK_STREAM) try: s.set_reuse_addr() done = 0 while not done: for x in xrange (5): try: was_eaddrinuse = 0 s.bind ((self.ip, self.port)) except OSError, why: if why[0] == errno.EACCES: coro.print_stderr( 'Access denied binding to %s:%i. Are you running as root?\n' % (self.ip, self.port)) return elif why[0] == errno.EADDRINUSE: was_eaddrinuse = 1 elif why[0] != errno.EADDRNOTAVAIL: raise else: done = 1 break coro.sleep_relative (1) else: coro.print_stderr ("cannot bind to %s:%d after 5 attempts\n" % (self.ip, self.port)) if was_eaddrinuse: qlog.write('FTPD.PORT_IN_USE', self.ip, str(self.port)) coro.sleep_relative (15) s.listen (1024) while 1: conn_list = s.accept_many() for conn, addr in conn_list: qlog.write('FTPD.CONNECTION', self.session_id, addr[0], self.ip) session = self.channel (self, conn, addr, self.session_id) self.session_id += 1 thread = coro.spawn (session.run) thread.set_name ( "%s_%d" % ( session.__class__.__name__, thread.thread_id() ) ) self.clients.append(session)
def _run (self): """Listens on the FTP port accepting connections and spawning sessions.""" self.thread_id = coro.current().thread_id() s = coro.make_socket (socket.AF_INET, socket.SOCK_STREAM) try: s.set_reuse_addr() done = 0 while not done: for x in xrange (5): try: was_eaddrinuse = 0 s.bind ((self.ip, self.port)) except OSError, why: if why[0] == errno.EACCES: coro.print_stderr('Access denied binding to %s:%i. Are you running as root?\n' % (self.ip, self.port)) return elif why[0] == errno.EADDRINUSE: was_eaddrinuse = 1 elif why[0] != errno.EADDRNOTAVAIL: raise else: done = 1 break coro.sleep_relative (1) else: coro.print_stderr ("cannot bind to %s:%d after 5 attempts\n" % (self.ip, self.port)) if was_eaddrinuse: qlog.write('FTPD.PORT_IN_USE', self.ip, str(self.port)) coro.sleep_relative (15) s.listen (1024) while 1: conn_list = s.accept_many() for conn, addr in conn_list: qlog.write('FTPD.CONNECTION', self.session_id, addr[0], self.ip) session = self.channel (self, conn, addr, self.session_id) self.session_id += 1 thread = coro.spawn (session.run) thread.set_name ( "%s_%d" % ( session.__class__.__name__, thread.thread_id() ) ) self.clients.append(session)
def done (self, with_body=1): if not self._sent_headers: self.send_headers() if with_body and self._chunking: # note: there's an invisible 'footer' between the pair of CRLF's. # it can be used to send certain additional types of headers. self.send ('0\r\n\r\n') if self._close: self._client.close () self._done = 1 qlog.write('WEBUI.HTTP_REQUEST', self._client.peer[0], self._user_id, self._session_id, self._reply_code, self._request, '' # XXX: User agent )
def cmd_pass (self, line): if len(line) < 2: pw = '' else: pw = line[1] try: result, message, fs = self.server.authorizer.authorize (self, self.user, pw) except SystemError: result = None message = 'User %s access denied' % self.user if result: self.respond ('230 %s' % message) self.filesystem = fs self.authorized = 1 qlog.write('FTPD.LOGIN', self.session_id, self.user) else: qlog.write('FTPD.LOGIN_FAILED', self.session_id, self.user) self.respond ('530 %s' % message)
def done(self, with_body=1): if not self._sent_headers: self.send_headers() if with_body and self._chunking: # note: there's an invisible 'footer' between the pair of CRLF's. # it can be used to send certain additional types of headers. self.send('0\r\n\r\n') if self._close: self._client.close() self._done = 1 qlog.write( 'WEBUI.HTTP_REQUEST', self._client.peer[0], self._user_id, self._session_id, self._reply_code, self._request, '' # XXX: User agent )
def _run (self, server_s): secure = self.is_secure() self.thread_id = coro.current().thread_id() while not self.shutdown_flag: try: conn, addr = server_s.accept() client = http_client() coro.spawn (client.run, conn, addr, self, self._handlers) except coro.Shutdown: # server-shutdown break except: qlog.write('COMMON.APP_FAILURE', ('%s accept handler error %s' % (self.__class__.__name__, coro.compact_traceback()))) coro.sleep_relative(0.25) continue server_s.close() return None
def read_body(self): """Read the message body, if any, so that it's cleared from the input stream. This avoids problems with keep-alives if the request handler doesn't read the body itself. This used to be done in the __init__method, but that can lead to a fatal error in the Python interpreter (see bug 3367). The ultimate solution is to fix the way connections are handled to ensure that we don't reuse the connection if the body wasn't fully read by the request handler.""" self._body = '' clen = self.get_request_header('Content-Length') if clen: try: clen = int(clen) self._body = coro.with_timeout(60, self._client.read, clen) if len(self._body) < clen: qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'Truncated body (%d<%d) (req:%s)' % \ (len(self._body), clen, self._request)) self._error = 1 # didn't get the body we were promised except coro.TimeoutError: qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'Body read timeout (req:%s)' % self._request) self._error = 1 except ValueError: qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'Invalid Content-Length (%s) (req:%s)' % \ (clen, self._request) ) self._error = 1
def _run(self, server_s): secure = self.is_secure() self.thread_id = coro.current().thread_id() while not self.shutdown_flag: try: conn, addr = server_s.accept() client = http_client() coro.spawn(client.run, conn, addr, self, self._handlers) except coro.Shutdown: # server-shutdown break except: qlog.write( 'COMMON.APP_FAILURE', ('%s accept handler error %s' % (self.__class__.__name__, coro.compact_traceback()))) coro.sleep_relative(0.25) continue server_s.close() return None
def read_body(self): """Read the message body, if any, so that it's cleared from the input stream. This avoids problems with keep-alives if the request handler doesn't read the body itself. This used to be done in the __init__method, but that can lead to a fatal error in the Python interpreter (see bug 3367). The ultimate solution is to fix the way connections are handled to ensure that we don't reuse the connection if the body wasn't fully read by the request handler.""" self._body = '' clen = self.get_request_header('Content-Length') if clen: try: clen = int(clen) self._body = coro.with_timeout( 60, self._client.read, clen) if len(self._body) < clen: qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'Truncated body (%d<%d) (req:%s)' % \ (len(self._body), clen, self._request)) self._error = 1 # didn't get the body we were promised except coro.TimeoutError: qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'Body read timeout (req:%s)' % self._request) self._error = 1 except ValueError: qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'Invalid Content-Length (%s) (req:%s)' % \ (clen, self._request) ) self._error = 1
def run(self, conn, peer, server_obj, handlers): self.conn = conn self.server = server_obj self.peer = peer # Note that peer could be a fake address, and server_obj can be None. # These indicate a "backdoor" request from the gui. try: try: count = 0 qlog.write('WEBUI.CONN_INIT', 'http', id(self), peer[0], peer[1]) while 1: if self.server and self.server.shutdown_flag: break try: # We use self.stream to read the header line-by-line # and then switch to reading directly from the socket # for the body (if needed). Reuse the previous # instance if it exists, to support HTTP pipelining. if not self.stream: self.stream = read_stream.stream_reader( self.conn.recv) request_line = self.read_line() if not request_line: break except socket.error: qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'socket error') break count = count + 1 headers = self.read_header() #print '\n'.join (headers) + '\n\n' request = http_request(self, request_line, headers) request.read_body() if request._error: # Bad Request request.error(400) return else: try: try: handler = self.pick_handler(handlers, request) if handler: handler.handle_request(request) else: self.not_found(request) if not request._done: request.done() except OSError, err: if err[0] == errno.EPIPE: pass # ignore broken pipe error else: raise # process exception in outer try # These exceptions are used inside the coro # stuff and shouldn't be thrown away except (coro.TimeoutError, coro.Interrupted): raise except: tb = coro.compact_traceback() ## sys.stderr.write (repr(tb)) request.error(500, tb) qlog.write('COMMON.APP_FAILURE', tb + ' request: ' + ` request `) tb = None if request._close: # ok, this gets interesting. the connection needs to close # here. the finally clause below isn't getting hit because # the session and client are running in the same coroutine. # that's bad, I think. conn.close() break # this should be a policy decision of the owner of logfp # self.logfp.flush() except read_stream.BufferOverflow: # Indicates a request header that exceeded the line # buffer, which may indicate an attack on the server. # We just close the connection without a response. # TODO:lrosenstein - log this since it may be an attack? qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'line buffer limit exceeded') pass except sslip.Error, why: # Most likely a problem with SSL negotiation qlog.write('WEBUI.CONN_ERROR', 'https', id(self), why[1]) pass except OSError, err: # We got some kind of I/O error that wasn't handled # elsewhere. Since this seem to happen because the # client closed the connection, it is safe to ignore # the exception. qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'OS error %s' % str(err[1])) pass
def respond (self, resp): qlog.write('FTPD.SEND', self.session_id, resp) self.send (resp + '\r\n')
def _run (self): self.thread_id = coro.current().thread_id() try: # send the greeting self.send_greeting() while not self.shutdown_flag: line, eof = self.read_line_timeout() if eof: break line = orig_line = line.lstrip() parts = line.split() if len (parts) < 1: self.command_not_understood ('') continue command = parts[0].lower() if len(parts) > 1: args = ' '.join (parts[1:]) # If command arguments include null character, python path parsing # function will complain. Remove the null characters. line = [command, args.replace('\0', '')] else: line = [command] # watch especially for 'urgent' abort commands. if command.find ('abor') != -1: # strip off telnet sync chars and the like... while command and command[0] not in string.letters: command = command[1:] fun_name = 'cmd_%s' % command if command != 'pass': qlog.write('FTPD.RECV', self.session_id, repr(orig_line)[1:-1]) else: qlog.write('FTPD.RECV', self.session_id, line[0]+' <password>') self.in_buffer = '' if not hasattr (self, fun_name): self.command_not_understood (line[0]) continue fun = getattr (self, fun_name) if (not self.authorized) and (command not in ('user', 'pass', 'help', 'quit')): self.respond ('530 Please log in with USER and PASS') elif (not self.check_command_authorization (self.user, command)): self.command_not_authorized (command) else: if hasattr (self, '_syntax_%s' % command): r = getattr (self, '_syntax_%s' % command) m = re.match (r, orig_line, re.IGNORECASE) if m is None: self.respond ('501 Syntax error in parameters or arguments') continue try: result = apply (fun, (line,)) except OSError, why: if why[0] in self.disconnect_errors: # log it & ignore qlog.write ('FTPD.DISCONNECT', self.session_id, why.strerror) break else: raise except coro.TimeoutError: qlog.write('FTPD.DISCONNECT', self.session_id, 'Remote side timed out') break except coro.Interrupted: raise except: self.server.total_exceptions.increment() qlog.write('COMMON.APP_FAILURE', tb.traceback_string() + ' fun: ' + `fun` + ' line: ' + `line`) self.respond ('451 Server Error') self.close_passive_acceptor() else: if result == 'quit': break
self.respond ('500 line too long. good-bye') except coro.TimeoutError: pass except OSError: pass except coro.TimeoutError: try: self.respond ('421 timeout. good-bye') except coro.TimeoutError: pass except OSError: pass except OSError, why: if why[0] in self.disconnect_errors: # log it & ignore qlog.write('FTPD.DISCONNECT', self.session_id, why.strerror) else: # Unknown error. If it's something like EBADF, then there is # something seriously wrong. raise # the set of errors that indicate a connection problem disconnect_errors = ( errno.ECONNRESET, errno.EHOSTUNREACH, errno.ECONNREFUSED, errno.EHOSTDOWN, errno.EPIPE, errno.ETIMEDOUT )
def run (self, conn, peer, server_obj, handlers): self.conn = conn self.server = server_obj self.peer = peer # Note that peer could be a fake address, and server_obj can be None. # These indicate a "backdoor" request from the gui. try: try: count = 0 qlog.write('WEBUI.CONN_INIT', 'http', id(self), peer[0], peer[1]) while 1: if self.server and self.server.shutdown_flag: break try: # We use self.stream to read the header line-by-line # and then switch to reading directly from the socket # for the body (if needed). Reuse the previous # instance if it exists, to support HTTP pipelining. if not self.stream: self.stream = read_stream.stream_reader(self.conn.recv) request_line = self.read_line() if not request_line: break except socket.error: qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'socket error') break count = count + 1 headers = self.read_header() #print '\n'.join (headers) + '\n\n' request = http_request (self, request_line, headers) request.read_body() if request._error: # Bad Request request.error (400) return else: try: try: handler = self.pick_handler (handlers, request) if handler: handler.handle_request (request) else: self.not_found (request) if not request._done: request.done() except OSError, err: if err[0] == errno.EPIPE: pass # ignore broken pipe error else: raise # process exception in outer try # These exceptions are used inside the coro # stuff and shouldn't be thrown away except (coro.TimeoutError, coro.Interrupted): raise except: tb = coro.compact_traceback() ## sys.stderr.write (repr(tb)) request.error (500, tb) qlog.write('COMMON.APP_FAILURE', tb + ' request: ' + `request`) tb = None if request._close: # ok, this gets interesting. the connection needs to close # here. the finally clause below isn't getting hit because # the session and client are running in the same coroutine. # that's bad, I think. conn.close() break # this should be a policy decision of the owner of logfp # self.logfp.flush() except read_stream.BufferOverflow: # Indicates a request header that exceeded the line # buffer, which may indicate an attack on the server. # We just close the connection without a response. # TODO:lrosenstein - log this since it may be an attack? qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'line buffer limit exceeded') pass except sslip.Error, why: # Most likely a problem with SSL negotiation qlog.write('WEBUI.CONN_ERROR', 'https', id(self), why[1]) pass except OSError, err: # We got some kind of I/O error that wasn't handled # elsewhere. Since this seem to happen because the # client closed the connection, it is safe to ignore # the exception. qlog.write('WEBUI.CONN_ERROR', 'http', id(self), 'OS error %s' % str(err[1])) pass