Package common :: Module resolver
[hide private]
[frames] | no frames]

Source Code for Module common.resolver

  1  ##      common/resolver.py 
  2  ## 
  3  ## Copyright (C) 2006 Dimitur Kirov <dkirov@gmail.com> 
  4  ## 
  5  ## This file is part of Gajim. 
  6  ## 
  7  ## Gajim is free software; you can redistribute it and/or modify 
  8  ## it under the terms of the GNU General Public License as published 
  9  ## by the Free Software Foundation; version 3 only. 
 10  ## 
 11  ## Gajim is distributed in the hope that it will be useful, 
 12  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  ## GNU General Public License for more details. 
 15  ## 
 16  ## You should have received a copy of the GNU General Public License 
 17  ## along with Gajim.  If not, see <http://www.gnu.org/licenses/>. 
 18  ## 
 19   
 20  import sys 
 21  import os 
 22  import re 
 23  import logging 
 24  log = logging.getLogger('gajim.c.resolver') 
 25   
 26  if __name__ == '__main__': 
 27      sys.path.append('..') 
 28      from common import i18n 
 29      import common.configpaths 
 30      common.configpaths.gajimpaths.init(None) 
 31   
 32  from common import helpers 
 33  from common.xmpp.idlequeue import IdleCommand 
 34   
 35  # it is good to check validity of arguments, when calling system commands 
 36  ns_type_pattern = re.compile('^[a-z]+$') 
 37   
 38  # match srv host_name 
 39  host_pattern = re.compile('^[a-z0-9\-._]*[a-z0-9]\.[a-z]{2,}$') 
 40   
 41  try: 
 42      #raise ImportError("Manually disabled libasync") 
 43      import libasyncns 
 44      USE_LIBASYNCNS = True 
 45      log.info("libasyncns-python loaded") 
 46  except ImportError: 
 47      USE_LIBASYNCNS = False 
 48      log.debug("Import of libasyncns-python failed, getaddrinfo will block", exc_info=True) 
 49   
 50   
51 -def get_resolver(idlequeue):
52 if USE_LIBASYNCNS: 53 return LibAsyncNSResolver() 54 else: 55 return NSLookupResolver(idlequeue)
56
57 -class CommonResolver():
58 - def __init__(self):
59 # dict {"host+type" : list of records} 60 self.resolved_hosts = {} 61 # dict {"host+type" : list of callbacks} 62 self.handlers = {}
63
64 - def resolve(self, host, on_ready, type='srv'):
65 log.debug('resolve %s type=%s' % (host, type)) 66 assert(type in ['srv', 'txt']) 67 if not host: 68 # empty host, return empty list of srv records 69 on_ready([]) 70 return 71 if self.resolved_hosts.has_key(host+type): 72 # host is already resolved, return cached values 73 log.debug('%s already resolved: %s') 74 on_ready(host, self.resolved_hosts[host+type]) 75 return 76 if self.handlers.has_key(host+type): 77 # host is about to be resolved by another connection, 78 # attach our callback 79 log.debug('already resolving %s' % host) 80 self.handlers[host+type].append(on_ready) 81 else: 82 # host has never been resolved, start now 83 log.debug('Starting to resolve %s using %s' % (host, self)) 84 self.handlers[host+type] = [on_ready] 85 self.start_resolve(host, type)
86
87 - def _on_ready(self, host, type, result_list):
88 # practically it is impossible to be the opposite, but who knows :) 89 log.debug('Resolving result for %s: %s' % (host, result_list)) 90 if not self.resolved_hosts.has_key(host+type): 91 self.resolved_hosts[host+type] = result_list 92 if self.handlers.has_key(host+type): 93 for callback in self.handlers[host+type]: 94 callback(host, result_list) 95 del(self.handlers[host+type])
96
97 - def start_resolve(self, host, type):
98 pass
99 100 # FIXME: API usage is not consistent! This one requires that process is called
101 -class LibAsyncNSResolver(CommonResolver):
102 """ 103 Asynchronous resolver using libasyncns-python. process() method has to be 104 called in order to proceed the pending requests. Based on patch submitted by 105 Damien Thebault. 106 """ 107
108 - def __init__(self):
109 self.asyncns = libasyncns.Asyncns() 110 CommonResolver.__init__(self)
111
112 - def start_resolve(self, host, type):
113 type = libasyncns.ns_t_srv 114 if type == 'txt': type = libasyncns.ns_t_txt 115 resq = self.asyncns.res_query(host, libasyncns.ns_c_in, type) 116 resq.userdata = {'host':host, 'type':type}
117 118 # getaddrinfo to be done 119 #def resolve_name(self, dname, callback): 120 #resq = self.asyncns.getaddrinfo(dname) 121 #resq.userdata = {'callback':callback, 'dname':dname} 122
123 - def _on_ready(self, host, type, result_list):
124 if type == libasyncns.ns_t_srv: type = 'srv' 125 elif type == libasyncns.ns_t_txt: type = 'txt' 126 127 CommonResolver._on_ready(self, host, type, result_list)
128
129 - def process(self):
130 try: 131 self.asyncns.wait(False) 132 resq = self.asyncns.get_next() 133 except: 134 return True 135 if type(resq) == libasyncns.ResQuery: 136 # TXT or SRV result 137 while resq is not None: 138 try: 139 rl = resq.get_done() 140 except Exception: 141 rl = [] 142 hosts = [] 143 requested_type = resq.userdata['type'] 144 requested_host = resq.userdata['host'] 145 if rl: 146 for r in rl: 147 if r['type'] != requested_type: 148 # Answer doesn't contain valid SRV data 149 continue 150 r['prio'] = r['pref'] 151 hosts.append(r) 152 self._on_ready(host=requested_host, type=requested_type, 153 result_list=hosts) 154 try: 155 resq = self.asyncns.get_next() 156 except Exception: 157 resq = None 158 elif type(resq) == libasyncns.AddrInfoQuery: 159 # getaddrinfo result (A or AAAA) 160 rl = resq.get_done() 161 resq.userdata['callback'](resq.userdata['dname'], rl) 162 return True
163 164
165 -class NSLookupResolver(CommonResolver):
166 """ 167 Asynchronous DNS resolver calling nslookup. Processing of pending requests 168 is invoked from idlequeue which is watching file descriptor of pipe of 169 stdout of nslookup process. 170 """ 171
172 - def __init__(self, idlequeue):
173 self.idlequeue = idlequeue 174 self.process = False 175 CommonResolver.__init__(self)
176
177 - def parse_srv_result(self, fqdn, result):
178 """ 179 Parse the output of nslookup command and return list of properties: 180 'host', 'port','weight', 'priority' corresponding to the found srv hosts 181 """ 182 if os.name == 'nt': 183 return self._parse_srv_result_nt(fqdn, result) 184 elif os.name == 'posix': 185 return self._parse_srv_result_posix(fqdn, result)
186
187 - def _parse_srv_result_nt(self, fqdn, result):
188 # output from win32 nslookup command 189 if not result: 190 return [] 191 hosts = [] 192 lines = result.replace('\r', '').split('\n') 193 current_host = None 194 for line in lines: 195 line = line.lstrip() 196 if line == '': 197 continue 198 if line.startswith(fqdn): 199 rest = line[len(fqdn):] 200 if rest.find('service') > -1: 201 current_host = {} 202 elif isinstance(current_host, dict): 203 res = line.strip().split('=') 204 if len(res) != 2: 205 if len(current_host) == 4: 206 hosts.append(current_host) 207 current_host = None 208 continue 209 prop_type = res[0].strip() 210 prop_value = res[1].strip() 211 if prop_type.find('prio') > -1: 212 try: 213 current_host['prio'] = int(prop_value) 214 except ValueError: 215 continue 216 elif prop_type.find('weight') > -1: 217 try: 218 current_host['weight'] = int(prop_value) 219 except ValueError: 220 continue 221 elif prop_type.find('port') > -1: 222 try: 223 current_host['port'] = int(prop_value) 224 except ValueError: 225 continue 226 elif prop_type.find('host') > -1: 227 # strip '.' at the end of hostname 228 if prop_value[-1] == '.': 229 prop_value = prop_value[:-1] 230 current_host['host'] = prop_value 231 if len(current_host) == 4: 232 hosts.append(current_host) 233 current_host = None 234 return hosts
235
236 - def _parse_srv_result_posix(self, fqdn, result):
237 # typical output of bind-tools nslookup command: 238 # _xmpp-client._tcp.jabber.org service = 30 30 5222 jabber.org. 239 if not result: 240 return [] 241 ufqdn = helpers.ascii_to_idn(fqdn) # Unicode domain name 242 hosts = [] 243 lines = result.split('\n') 244 for line in lines: 245 if line == '': 246 continue 247 domain = None 248 if line.startswith(fqdn): 249 domain = fqdn # For nslookup 9.5 250 elif helpers.decode_string(line).startswith(ufqdn): 251 line = helpers.decode_string(line) 252 domain = ufqdn # For nslookup 9.6 253 if domain: 254 rest = line[len(domain):].split('=') 255 if len(rest) != 2: 256 continue 257 answer_type, props_str = rest 258 if answer_type.strip() != 'service': 259 continue 260 props = props_str.strip().split(' ') 261 if len(props) < 4: 262 continue 263 prio, weight, port, host = props[-4:] 264 if host[-1] == '.': 265 host = host[:-1] 266 try: 267 prio = int(prio) 268 weight = int(weight) 269 port = int(port) 270 except ValueError: 271 continue 272 hosts.append({'host': host, 'port': port, 'weight': weight, 273 'prio': prio}) 274 return hosts
275
276 - def _on_ready(self, host, type, result):
277 # nslookup finished, parse the result and call the handlers 278 result_list = self.parse_srv_result(host, result) 279 CommonResolver._on_ready(self, host, type, result_list)
280
281 - def start_resolve(self, host, type):
282 """ 283 Spawn new nslookup process and start waiting for results 284 """ 285 ns = NsLookup(self._on_ready, host, type) 286 ns.set_idlequeue(self.idlequeue) 287 ns.commandtimeout = 20 288 ns.start()
289 290
291 -class NsLookup(IdleCommand):
292 - def __init__(self, on_result, host='_xmpp-client', type='srv'):
293 IdleCommand.__init__(self, on_result) 294 self.commandtimeout = 10 295 self.host = host.lower() 296 self.type = type.lower() 297 if not host_pattern.match(self.host): 298 # invalid host name 299 log.error('Invalid host: %s' % self.host) 300 self.canexecute = False 301 return 302 if not ns_type_pattern.match(self.type): 303 log.error('Invalid querytype: %s' % self.type) 304 self.canexecute = False 305 return
306
307 - def _compose_command_args(self):
308 return ['nslookup', '-type=' + self.type, self.host]
309
310 - def _return_result(self):
311 if self.result_handler: 312 self.result_handler(self.host, self.type, self.result) 313 self.result_handler = None
314 315 # below lines is on how to use API and assist in testing 316 if __name__ == '__main__': 317 import gobject 318 import gtk 319 from xmpp import idlequeue 320 321 idlequeue = idlequeue.get_idlequeue() 322 resolver = get_resolver(idlequeue) 323
324 - def clicked(widget):
325 global resolver 326 host = text_view.get_text() 327 def on_result(host, result_array): 328 print 'Result:\n' + repr(result_array)
329 resolver.resolve(host, on_result) 330 win = gtk.Window() 331 win.set_border_width(6) 332 text_view = gtk.Entry() 333 text_view.set_text('_xmpp-client._tcp.jabber.org') 334 hbox = gtk.HBox() 335 hbox.set_spacing(3) 336 but = gtk.Button(' Lookup SRV ') 337 hbox.pack_start(text_view, 5) 338 hbox.pack_start(but, 0) 339 but.connect('clicked', clicked) 340 win.add(hbox) 341 win.show_all() 342 gobject.timeout_add(200, idlequeue.process) 343 if USE_LIBASYNCNS: 344 gobject.timeout_add(200, resolver.process) 345 gtk.main() 346