root/branches/gajim_0.11.1/src/common/nslookup.py

Revision 7477, 9.0 kB (checked in by nk, 2 years ago)

calling the module SRE is deprecated [in py25] in favor of RE. so use RE

Line 
1##      common/nslookup.py
2##
3## Copyright (C) 2006 Dimitur Kirov <dkirov@gmail.com>
4##
5## This program is free software; you can redistribute it and/or modify
6## it under the terms of the GNU General Public License as published
7## by the Free Software Foundation; version 2 only.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14
15import sys
16import os
17import re
18
19from xmpp.idlequeue import *
20
21if os.name == 'nt':
22        from subprocess import * # python24 only. we ask this for Windows
23elif os.name == 'posix':
24        import fcntl
25
26# it is good to check validity of arguments, when calling system commands
27ns_type_pattern = re.compile('^[a-z]+$')
28
29# match srv host_name
30host_pattern = re.compile('^[a-z0-9\-._]*[a-z0-9]\.[a-z]{2,}$')
31
32class Resolver:
33        def __init__(self, idlequeue):
34                self.idlequeue = idlequeue
35                # dict {host : list of srv records}
36                self.resolved_hosts = {} 
37                # dict {host : list of callbacks}
38                self.handlers = {} 
39       
40        def parse_srv_result(self, fqdn, result):
41                ''' parse the output of nslookup command and return list of
42                properties: 'host', 'port','weight', 'priority' corresponding to the found
43                srv hosts '''
44                if os.name == 'nt':
45                        return self._parse_srv_result_nt(fqdn, result)
46                elif os.name == 'posix':
47                        return self._parse_srv_result_posix(fqdn, result)
48       
49        def _parse_srv_result_nt(self, fqdn, result):
50                # output from win32 nslookup command
51                if not result: 
52                        return []
53                hosts = []
54                lines = result.replace('\r','').split('\n')
55                current_host = None
56                for line in lines:
57                        line = line.lstrip()
58                        if line == '':
59                                continue
60                        if line.startswith(fqdn):
61                                rest = line[len(fqdn):]
62                                if rest.find('service') > -1:
63                                        current_host = {}
64                        elif isinstance(current_host, dict):
65                                res = line.strip().split('=')
66                                if len(res) != 2:
67                                        if len(current_host) == 4:
68                                                hosts.append(current_host)
69                                        current_host = None
70                                        continue
71                                prop_type = res[0].strip() 
72                                prop_value = res[1].strip()
73                                if prop_type.find('prio') > -1:
74                                        try:
75                                                current_host['prio'] = int(prop_value)
76                                        except ValueError:
77                                                continue
78                                elif prop_type.find('weight') > -1:
79                                        try:
80                                                current_host['weight'] = int(prop_value)
81                                        except ValueError:
82                                                continue
83                                elif prop_type.find('port') > -1:
84                                        try:
85                                                current_host['port'] = int(prop_value)
86                                        except ValueError:
87                                                continue
88                                elif prop_type.find('host') > -1:
89                                        # strip '.' at the end of hostname
90                                        if prop_value[-1] == '.':
91                                                prop_value = prop_value[:-1]
92                                        current_host['host'] = prop_value
93                                if len(current_host) == 4:
94                                        hosts.append(current_host)
95                                        current_host = None
96                return hosts
97       
98        def _parse_srv_result_posix(self, fqdn, result):
99                # typical output of bind-tools nslookup command:
100                # _xmpp-client._tcp.jabber.org    service = 30 30 5222 jabber.org.
101                if not result: 
102                        return []
103                hosts = []
104                lines = result.split('\n')
105                for line in lines:
106                        if line == '':
107                                continue
108                        if line.startswith(fqdn):
109                                rest = line[len(fqdn):].split('=')
110                                if len(rest) != 2:
111                                        continue
112                                answer_type, props_str = rest
113                                if answer_type.strip() != 'service':
114                                        continue
115                                props = props_str.strip().split(' ')
116                                if len(props) < 4:
117                                        continue
118                                prio, weight, port, host  = props[-4:]
119                                if host[-1] == '.':
120                                        host = host[:-1]
121                                try:
122                                        prio = int(prio)
123                                        weight = int(weight)
124                                        port = int(port)
125                                except ValueError:
126                                        continue
127                                hosts.append({'host': host, 'port': port,'weight': weight,
128                                                'prio': prio})
129                return hosts
130       
131        def _on_ready(self, host, result):
132                # nslookup finished, parse the result and call the handlers
133                result_list = self.parse_srv_result(host, result)
134               
135                # practically it is impossible to be the opposite, but who knows :)
136                if not self.resolved_hosts.has_key(host):
137                        self.resolved_hosts[host] = result_list
138                if self.handlers.has_key(host):
139                        for callback in self.handlers[host]:
140                                callback(host, result_list)
141                        del(self.handlers[host])
142       
143        def start_resolve(self, host):
144                ''' spawn new nslookup process and start waiting for results '''
145                ns = NsLookup(self._on_ready, host)
146                ns.set_idlequeue(self.idlequeue)
147                ns.commandtimeout = 10
148                ns.start()
149       
150        def resolve(self, host, on_ready):
151                if not host:
152                        # empty host, return empty list of srv records
153                        on_ready([])
154                        return
155                if self.resolved_hosts.has_key(host):
156                        # host is already resolved, return cached values
157                        on_ready(host, self.resolved_hosts[host])
158                        return
159                if self.handlers.has_key(host):
160                        # host is about to be resolved by another connection,
161                        # attach our callback
162                        self.handlers[host].append(on_ready)
163                else:
164                        # host has never been resolved, start now
165                        self.handlers[host] = [on_ready]
166                        self.start_resolve(host)
167
168# TODO: move IdleCommand class in other file, maybe helpers ?
169class IdleCommand(IdleObject):
170        def __init__(self, on_result):
171                # how long (sec.) to wait for result ( 0 - forever )
172                # it is a class var, instead of a constant and we can override it.
173                self.commandtimeout = 0 
174                # when we have some kind of result (valid, ot not) we call this handler
175                self.result_handler = on_result
176                # if it is True, we can safetely execute the command
177                self.canexecute = True
178                self.idlequeue = None
179                self.result =''
180       
181        def set_idlequeue(self, idlequeue):
182                self.idlequeue = idlequeue
183       
184        def _return_result(self):
185                if self.result_handler:
186                        self.result_handler(self.result)
187                self.result_handler = None
188       
189        def _compose_command_args(self):
190                return ['echo', 'da']
191       
192        def _compose_command_line(self):
193                ''' return one line representation of command and its arguments '''
194                return  reduce(lambda left, right: left + ' ' + right,  self._compose_command_args())
195       
196        def wait_child(self):
197                if self.pipe.poll() is None:
198                        # result timeout
199                        if self.endtime < self.idlequeue.current_time():
200                                self._return_result()
201                                self.pipe.stdout.close()
202                                self.pipe.stdin.close()
203                        else:
204                                # child is still active, continue to wait
205                                self.idlequeue.set_alarm(self.wait_child, 0.1)
206                else:
207                        # child has quit
208                        self.result = self.pipe.stdout.read()
209                        self._return_result()
210                        self.pipe.stdout.close()
211                        self.pipe.stdin.close()
212        def start(self):
213                if not self.canexecute:
214                        self.result = ''
215                        self._return_result()
216                        return
217                if os.name == 'nt':
218                        self._start_nt()
219                elif os.name == 'posix':
220                        self._start_posix()
221       
222        def _start_nt(self):
223                # if gajim is started from noninteraactive shells stdin is closed and
224                # cannot be forwarded, so we have to keep it open
225                self.pipe = Popen(self._compose_command_args(), stdout=PIPE, 
226                        bufsize = 1024, shell = True, stderr = STDOUT, stdin = PIPE)
227                if self.commandtimeout >= 0:
228                        self.endtime = self.idlequeue.current_time() + self.commandtimeout
229                        self.idlequeue.set_alarm(self.wait_child, 0.1)
230       
231        def _start_posix(self):
232                self.pipe = os.popen(self._compose_command_line())
233                self.fd = self.pipe.fileno()
234                fcntl.fcntl(self.pipe, fcntl.F_SETFL, os.O_NONBLOCK)
235                self.idlequeue.plug_idle(self, False, True)
236                if self.commandtimeout >= 0:
237                        self.idlequeue.set_read_timeout(self.fd, self.commandtimeout)
238               
239        def end(self):
240                self.idlequeue.unplug_idle(self.fd)
241                try:
242                        self.pipe.close()
243                except:
244                        pass
245       
246        def pollend(self):
247                self.idlequeue.remove_timeout(self.fd)
248                self.end()
249                self._return_result()
250       
251        def pollin(self):
252                try:
253                        res = self.pipe.read()
254                except Exception, e:
255                        res = ''
256                if res == '':
257                        return self.pollend()
258                else:
259                        self.result += res
260       
261        def read_timeout(self):
262                self.end()
263                self._return_result()
264       
265class NsLookup(IdleCommand):
266        def __init__(self, on_result, host='_xmpp-client', type = 'srv'):
267                IdleCommand.__init__(self, on_result)
268                self.commandtimeout = 10 
269                self.host = host.lower()
270                self.type = type.lower()
271                if not host_pattern.match(self.host):
272                        # invalid host name
273                        print >> sys.stderr, 'Invalid host: %s' % self.host
274                        self.canexecute = False
275                        return
276                if not ns_type_pattern.match(self.type):
277                        print >> sys.stderr, 'Invalid querytype: %s' % self.type
278                        self.canexecute = False
279                        return
280       
281        def _compose_command_args(self):
282                return ['nslookup', '-type=' + self.type , self.host]
283       
284        def _return_result(self):
285                if self.result_handler:
286                        self.result_handler(self.host, self.result)
287                self.result_handler = None
288       
289# below lines is on how to use API and assist in testing
290if __name__ == '__main__':
291        if os.name == 'posix':
292                idlequeue = IdleQueue()
293        elif os.name == 'nt':
294                idlequeue = SelectIdleQueue()
295        # testing Resolver class
296        import gobject
297        import gtk
298       
299        resolver = Resolver(idlequeue)
300       
301        def clicked(widget):
302                global resolver
303                host = text_view.get_text()
304                def on_result(host, result_array):
305                        print 'Result:\n' + repr(result_array)
306                resolver.resolve(host, on_result)
307        win = gtk.Window()
308        win.set_border_width(6)
309        text_view = gtk.Entry()
310        text_view.set_text('_xmpp-client._tcp.jabber.org')
311        hbox = gtk.HBox()
312        hbox.set_spacing(3)
313        but = gtk.Button(' Lookup SRV ')
314        hbox.pack_start(text_view, 5)
315        hbox.pack_start(but, 0)
316        but.connect('clicked', clicked)
317        win.add(hbox)
318        win.show_all()
319        gobject.timeout_add(200, idlequeue.process)
320        gtk.main()
Note: See TracBrowser for help on using the browser.