#!/usr/bin/python #Copyright 2006 Sebastian Hagen # This file is part of eucharis. # eucharis is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 # as published by the Free Software Foundation # eucharis 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 eucharis; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # basic dict (RFC 2229) client implementation # requires python >= 2.4 import shlex from socket_management import sock_stream_connection_linebased STATE_DEFAULT = 0 STATE_TEXT = 1 CAPABILITIES = { 'mime': 0, 'auth': 1, 'kerberos_v4': 2, 'gssapi': 3, 'skey': 4, 'external': 5, } class NotConnectedError(StandardError): pass class dict_connection(sock_stream_connection_linebased): def __init__(self, address, close_handler=None, *args, **kwargs): kwargs['line_delimiters'] = '\r\n' sock_stream_connection_linebased.__init__(self, *args, **kwargs) self.input_handler = self.input_parse self.state = STATE_DEFAULT self.msg_id = None self.capabilities = [] self.banner = None self.callback_handlers = [] self.close_handler = close_handler self.result_numeric = None self.result_buffer = [] self.string_buffer = [] self.db_data = {} self.strat_data = {} self.logger.log(20, 'Connecting to dictd at address %s.' % (address,)) self.connection_init(address) if (self.connection_up()): self.clientidsend() self.showdb(self.response_ignore) self.showstrat(self.response_ignore) def input_parse(self): while (self.buffer_lines): line = self.buffer_lines.pop(0) self.logger.log(10, '> %r' % (line,)) if not (len(line.strip()) > 0): continue if (self.state == STATE_DEFAULT): try: line_split = shlex.split(line) except ValueError: self.logger.log(38, 'Got invalid line (unable to split) from %r: %r' % (self.target, line)) self.clean_up() return False try: numeric = int(line_split[0]) except ValueError, OverflowError: self.logger.log(37, 'Got invalid line (unable to parse numeric) from %r: %r' % (self.target, line)) continue if (400 <= numeric < 500): self.logger.log(34, 'Got transitive failure mode %d from dictd %r: %r.' % (numeric, self.target, line)) if (numeric == 421): self.logger.log(35, 'Disconnecting on 421 received from dictd %r: %r' % (self.target, line)) self.clean_up() self.response(False, numeric, line_split[1:]) elif (500 <= numeric < 600): self.logger.log(24, 'Got permanent failure mode %d from dictd %r: %r.' % (numeric, self.target, line)) if (numeric == 530): self.logger.log(35, 'Disconnecting on 530 received from dictd %r: %r' % (self.target, line)) self.clean_up() else: self.response(False, numeric, line_split[1:]) elif (numeric == 150): self.result_buffer = [] self.result_numeric = numeric elif (numeric == 151): self.string_buffer = [] self.state = STATE_TEXT word = dbname = dbdesc = '' if (len(line_split) >= 2): word = line_split[1].strip('"') if (len(line_split) >= 3): dbname = line_split[2] if (len(line_split) >= 4): dbdesc = ' '.join(line_split[3:]) self.result_buffer.append([word, dbname, dbdesc, []]) elif (numeric in (110, 111, 112, 114, 152)): self.result_buffer = [] self.result_numeric = numeric self.string_buffer = [] self.state = STATE_TEXT elif (numeric == 250): if (self.result_numeric == 152): results = {} for result in self.result_buffer: result_split = shlex.split(result) if (len(result_split) < 2): continue dbname, result_string = result_split[:2] if (dbname in results): results[dbname].append(result_string) else: results[dbname] = [result_string] self.result_buffer = results self.response(True, self.result_numeric, self.result_buffer) if (self.result_numeric == 110): self.db_data = {} for line in self.result_buffer: line_split = shlex.split(line) if (len(line_split) >= 1): if (len(line_split) >= 2): self.db_data[line_split[0]] = line_split[1] else: self.db_data[line_split[0]] = None elif (self.result_numeric == 111): self.strat_data = {} for line in self.result_buffer: line_split = shlex.split(line) if (len(line_split) >= 1): if (len(line_split) >= 2): self.strat_data[line_split[0]] = line_split[1] else: self.strat_data[line_split[0]] = None self.result_numeric = None self. result_buffer = [] if (len(line_split) > 1): self.logger.log(20, 'Got extended date on 250 numeric response: %r' % line_split) elif (numeric == 200): if (len(line_split) > 1): self.msg_id = line_split[-1] else: self.msg_id = None if (len(line_split) > 2): del(self.capabilities[:]) for capability in line_split[-2].split('.'): if (capability in CAPABILITIES): self.capabilities.append(CAPABILITIES[capability]) else: self.capabilities.append(capability) if (len(line_split) > 3): self.banner = line_split[1:-2] elif (self.state == STATE_TEXT): if (len(line) == 0): self.result_buffer[-1][3].append('') elif (line[0] == '.'): if ((len(line) >= 2) and (line[1] == '.')): self.string_buffer.append(line[1:]) else: if (self.result_numeric == 150): self.result_buffer[-1][3] = self.string_buffer elif (self.result_numeric in (110, 111, 112, 114, 152)): self.result_buffer = self.string_buffer else: self.logger.log(40, 'Unable to process data buffered for unknown response numeric %d: %r.' % (self.result_numeric, self.string_buffer)) self.string_buffer = [] self.state = STATE_DEFAULT else: self.string_buffer.append(line) def line_send(self, line): self.send_data(line + '\r\n') def response(self, success, numeric, result): if not (self.callback_handlers): self.logger.log(40, 'Unable to relay lookup result (success %r, numeric %r, result %r) since there are no handlers registered.' % (success, numeric, result)) return False callback_handler, sessiondata = self.callback_handlers.pop(0) if not (callable(callback_handler)): self.logger.log(40, 'Unable to relay lookup result (success %r, numeric %r, result %r) since the last registered handler %r is not callable. Sessiondata was %r.' % (success, numeric, result, callback_handler, sessiondata)) return False try: callback_handler(dict_connection=self, success=success, numeric=numeric, result=result, sessiondata=sessiondata) except StandardError: self.logger.log(40, 'Error in callback handler:', exc_info=True) return False return True @staticmethod def request_string_test(variable, varname): if not (isinstance(variable, basestring)): raise TypeError('Expected string for argument %s, got %r which is of type %r.' % (varname, variable, type(variable))) if (variable.isspace()): raise ValueError('Expected non-whitespace argument for %s, got %r.' % (varname, variable)) @staticmethod def response_ignore(dict_connection, success, numeric, result, sessiondata): pass def define(self, database, word, callback_handler, sessiondata=None): self.request_string_test(database, 'database') self.request_string_test(word, 'word') if not (callable(callback_handler)): raise TypeError('callback_handler argument %r is not callable.' % (callback_handler,)) if not (self.connection_up()): raise NotConnectedError('No connection to dictd present.') self.callback_handlers.append((callback_handler, sessiondata)) self.line_send('DEFINE %r %r' % (database, word)) def match(self, database, strategy, word, callback_handler, sessiondata=None): self.request_string_test(database, 'database') self.request_string_test(strategy, 'strategy') self.request_string_test(word, 'word') if not (callable(callback_handler)): raise TypeError('callback_handler argument %r is not callable.' % (callback_handler,)) if not (self.connection_up()): raise NotConnectedError('No connection to dictd present.') self.callback_handlers.append((callback_handler, sessiondata)) self.line_send('MATCH %r %r %r' % (database, strategy, word)) def showdb(self, callback_handler, sessiondata=None): if not (callable(callback_handler)): raise TypeError('callback_handler argument %r is not callable.' % (callback_handler,)) if not (self.connection_up()): raise NotConnectedError('No connection to dictd present.') self.callback_handlers.append((callback_handler, sessiondata)) self.line_send('SHOW DATABASES') def showstrat(self, callback_handler, sessiondata=None): if not (callable(callback_handler)): raise TypeError('callback_handler argument %r is not callable.' % (callback_handler,)) if not (self.connection_up()): raise NotConnectedError('No connection to dictd present.') self.callback_handlers.append((callback_handler, sessiondata)) self.line_send('SHOW STRATEGIES') def showinfo(self, database, callback_handler, sessiondata=None): self.request_string_test(database, 'database') if not (callable(callback_handler)): raise TypeError('callback_handler argument %r is not callable.' % (callback_handler,)) if not (self.connection_up()): raise NotConnectedError('No connection to dictd present.') self.callback_handlers.append((callback_handler, sessiondata)) self.line_send('SHOW INFO %r' % (database,)) def showserver(self, callback_handler, sessiondata=None): if not (callable(callback_handler)): raise TypeError('callback_handler argument %r is not callable.' % (callback_handler,)) if not (self.connection_up()): raise NotConnectedError('No connection to dictd present.') self.callback_handlers.append((callback_handler, sessiondata)) self.line_send('SHOW SERVER') def clientidsend(self): self.callback_handlers.append((self.response_ignore, None)) self.line_send('CLIENT Eucharis class %r' % (self.__class__.__name__,))