19
|
1 "A an RPC module, with optimizations for VXI-11 rpc calls"
|
|
2 _rcsid="$Id: rpc.py 323 2011-04-06 19:10:03Z marcus $"
|
|
3
|
|
4 #this module is 99% from the python distribution, Demo/rpc/rpc.py, with a few modifications
|
|
5 #by Marcus Mendenhall to improve timeout handling, etc. It probably should follow any licensing
|
|
6 #intent of whoever originated it.
|
|
7
|
|
8 # Sun RPC version 2 -- RFC1057.
|
|
9
|
|
10 # XXX There should be separate exceptions for the various reasons why
|
|
11 # XXX an RPC can fail, rather than using RuntimeError for everything
|
|
12
|
|
13 # XXX The UDP version of the protocol resends requests when it does
|
|
14 # XXX not receive a timely reply -- use only for idempotent calls!
|
|
15
|
|
16 # XXX There is no provision for call timeout on TCP connections
|
|
17 # Now there is, on receives. 2003.02.20 Marcus Mendenhall, Vanderbilt University marcus.h.mendenhall@vanderbilt.edu
|
|
18
|
|
19 import xdrlib as xdr
|
|
20 import socket
|
|
21 import os
|
|
22 from exceptions import *
|
|
23
|
|
24 RPCVERSION = 2
|
|
25
|
|
26 CALL = 0
|
|
27 REPLY = 1
|
|
28
|
|
29 AUTH_NULL = 0
|
|
30 AUTH_UNIX = 1
|
|
31 AUTH_SHORT = 2
|
|
32 AUTH_DES = 3
|
|
33
|
|
34 MSG_ACCEPTED = 0
|
|
35 MSG_DENIED = 1
|
|
36
|
|
37 SUCCESS = 0 # RPC executed successfully
|
|
38 PROG_UNAVAIL = 1 # remote hasn't exported program
|
|
39 PROG_MISMATCH = 2 # remote can't support version #
|
|
40 PROC_UNAVAIL = 3 # program can't support procedure
|
|
41 GARBAGE_ARGS = 4 # procedure can't decode params
|
|
42
|
|
43 RPC_MISMATCH = 0 # RPC version number != 2
|
|
44 AUTH_ERROR = 1 # remote can't authenticate caller
|
|
45
|
|
46 AUTH_BADCRED = 1 # bad credentials (seal broken)
|
|
47 AUTH_REJECTEDCRED = 2 # client must begin new session
|
|
48 AUTH_BADVERF = 3 # bad verifier (seal broken)
|
|
49 AUTH_REJECTEDVERF = 4 # verifier expired or replayed
|
|
50 AUTH_TOOWEAK = 5 # rejected for security reasons
|
|
51
|
|
52
|
|
53 class Packer(xdr.Packer):
|
|
54
|
|
55 def pack_auth(self, auth):
|
|
56 flavor, stuff = auth
|
|
57 self.pack_enum(flavor)
|
|
58 self.pack_opaque(stuff)
|
|
59
|
|
60 def pack_auth_unix(self, stamp, machinename, uid, gid, gids):
|
|
61 self.pack_uint(stamp)
|
|
62 self.pack_string(machinename)
|
|
63 self.pack_uint(uid)
|
|
64 self.pack_uint(gid)
|
|
65 self.pack_uint(len(gids))
|
|
66 for i in gids:
|
|
67 self.pack_uint(i)
|
|
68
|
|
69 def pack_callheader(self, xid, prog, vers, proc, cred, verf):
|
|
70 self.pack_uint(xid)
|
|
71 self.pack_enum(CALL)
|
|
72 self.pack_uint(RPCVERSION)
|
|
73 self.pack_uint(prog)
|
|
74 self.pack_uint(vers)
|
|
75 self.pack_uint(proc)
|
|
76 self.pack_auth(cred)
|
|
77 self.pack_auth(verf)
|
|
78 # Caller must add procedure-specific part of call
|
|
79
|
|
80 def pack_replyheader(self, xid, verf):
|
|
81 self.pack_uint(xid)
|
|
82 self.pack_enum(REPLY)
|
|
83 self.pack_uint(MSG_ACCEPTED)
|
|
84 self.pack_auth(verf)
|
|
85 self.pack_enum(SUCCESS)
|
|
86 # Caller must add procedure-specific part of reply
|
|
87
|
|
88
|
|
89 # Exceptions
|
|
90 BadRPCFormat = 'rpc.BadRPCFormat'
|
|
91 BadRPCVersion = 'rpc.BadRPCVersion'
|
|
92 GarbageArgs = 'rpc.GarbageArgs'
|
|
93
|
|
94 class Unpacker(xdr.Unpacker):
|
|
95
|
|
96 def unpack_auth(self):
|
|
97 flavor = self.unpack_enum()
|
|
98 stuff = self.unpack_opaque()
|
|
99 return (flavor, stuff)
|
|
100
|
|
101 def unpack_callheader(self):
|
|
102 xid = self.unpack_uint(xid)
|
|
103 temp = self.unpack_enum()
|
|
104 if temp <> CALL:
|
|
105 raise BadRPCFormat, 'no CALL but ' + `temp`
|
|
106 temp = self.unpack_uint()
|
|
107 if temp <> RPCVERSION:
|
|
108 raise BadRPCVerspion, 'bad RPC version ' + `temp`
|
|
109 prog = self.unpack_uint()
|
|
110 vers = self.unpack_uint()
|
|
111 proc = self.unpack_uint()
|
|
112 cred = self.unpack_auth()
|
|
113 verf = self.unpack_auth()
|
|
114 return xid, prog, vers, proc, cred, verf
|
|
115 # Caller must add procedure-specific part of call
|
|
116
|
|
117 def unpack_replyheader(self):
|
|
118 xid = self.unpack_uint()
|
|
119 mtype = self.unpack_enum()
|
|
120 if mtype <> REPLY:
|
|
121 raise RuntimeError, 'no REPLY but ' + `mtype`
|
|
122 stat = self.unpack_enum()
|
|
123 if stat == MSG_DENIED:
|
|
124 stat = self.unpack_enum()
|
|
125 if stat == RPC_MISMATCH:
|
|
126 low = self.unpack_uint()
|
|
127 high = self.unpack_uint()
|
|
128 raise RuntimeError, \
|
|
129 'MSG_DENIED: RPC_MISMATCH: ' + `low, high`
|
|
130 if stat == AUTH_ERROR:
|
|
131 stat = self.unpack_uint()
|
|
132 raise RuntimeError, \
|
|
133 'MSG_DENIED: AUTH_ERROR: ' + `stat`
|
|
134 raise RuntimeError, 'MSG_DENIED: ' + `stat`
|
|
135 if stat <> MSG_ACCEPTED:
|
|
136 raise RuntimeError, \
|
|
137 'Neither MSG_DENIED nor MSG_ACCEPTED: ' + `stat`
|
|
138 verf = self.unpack_auth()
|
|
139 stat = self.unpack_enum()
|
|
140 if stat == PROG_UNAVAIL:
|
|
141 raise RuntimeError, 'call failed: PROG_UNAVAIL'
|
|
142 if stat == PROG_MISMATCH:
|
|
143 low = self.unpack_uint()
|
|
144 high = self.unpack_uint()
|
|
145 raise RuntimeError, \
|
|
146 'call failed: PROG_MISMATCH: ' + `low, high`
|
|
147 if stat == PROC_UNAVAIL:
|
|
148 raise RuntimeError, 'call failed: PROC_UNAVAIL'
|
|
149 if stat == GARBAGE_ARGS:
|
|
150 raise RuntimeError, 'call failed: GARBAGE_ARGS'
|
|
151 if stat <> SUCCESS:
|
|
152 raise RuntimeError, 'call failed: ' + `stat`
|
|
153 return xid, verf
|
|
154 # Caller must get procedure-specific part of reply
|
|
155
|
|
156
|
|
157 # Subroutines to create opaque authentication objects
|
|
158
|
|
159 def make_auth_null():
|
|
160 return ''
|
|
161
|
|
162 def make_auth_unix(seed, host, uid, gid, groups):
|
|
163 p = Packer()
|
|
164 p.pack_auth_unix(seed, host, uid, gid, groups)
|
|
165 return p.get_buf()
|
|
166
|
|
167 def make_auth_unix_default():
|
|
168 try:
|
|
169 from os import getuid, getgid
|
|
170 uid = getuid()
|
|
171 gid = getgid()
|
|
172 except ImportError:
|
|
173 uid = gid = 0
|
|
174 import time
|
|
175 return make_auth_unix(int(time.time()-unix_epoch()), \
|
|
176 socket.gethostname(), uid, gid, [])
|
|
177
|
|
178 _unix_epoch = -1
|
|
179 def unix_epoch():
|
|
180 """Very painful calculation of when the Unix Epoch is.
|
|
181
|
|
182 This is defined as the return value of time.time() on Jan 1st,
|
|
183 1970, 00:00:00 GMT.
|
|
184
|
|
185 On a Unix system, this should always return 0.0. On a Mac, the
|
|
186 calculations are needed -- and hard because of integer overflow
|
|
187 and other limitations.
|
|
188
|
|
189 """
|
|
190 global _unix_epoch
|
|
191 if _unix_epoch >= 0: return _unix_epoch
|
|
192 import time
|
|
193 now = time.time()
|
|
194 localt = time.localtime(now) # (y, m, d, hh, mm, ss, ..., ..., ...)
|
|
195 gmt = time.gmtime(now)
|
|
196 offset = time.mktime(localt) - time.mktime(gmt)
|
|
197 y, m, d, hh, mm, ss = 1970, 1, 1, 0, 0, 0
|
|
198 offset, ss = divmod(ss + offset, 60)
|
|
199 offset, mm = divmod(mm + offset, 60)
|
|
200 offset, hh = divmod(hh + offset, 24)
|
|
201 d = d + offset
|
|
202 _unix_epoch = time.mktime((y, m, d, hh, mm, ss, 0, 0, 0))
|
|
203 print "Unix epoch:", time.ctime(_unix_epoch)
|
|
204 return _unix_epoch
|
|
205
|
|
206
|
|
207 # Common base class for clients
|
|
208
|
|
209 class Client:
|
|
210
|
|
211 def __init__(self, host, prog, vers, port):
|
|
212 self.host = host
|
|
213 self.prog = prog
|
|
214 self.vers = vers
|
|
215 self.port = port
|
|
216 self.makesocket() # Assigns to self.sock
|
|
217 self.bindsocket()
|
|
218 self.connsocket()
|
|
219 self.lastxid = 0 # XXX should be more random?
|
|
220 self.addpackers()
|
|
221 self.cred = None
|
|
222 self.verf = None
|
|
223
|
|
224 def close(self):
|
|
225 self.sock.close()
|
|
226
|
|
227 def makesocket(self):
|
|
228 # This MUST be overridden
|
|
229 raise RuntimeError, 'makesocket not defined'
|
|
230
|
|
231 def connsocket(self):
|
|
232 # Override this if you don't want/need a connection
|
|
233 self.sock.connect((self.host, self.port))
|
|
234
|
|
235 def bindsocket(self):
|
|
236 # Override this to bind to a different port (e.g. reserved)
|
|
237 self.sock.bind(('', 0))
|
|
238
|
|
239 def addpackers(self):
|
|
240 # Override this to use derived classes from Packer/Unpacker
|
|
241 self.packer = Packer()
|
|
242 self.unpacker = Unpacker('')
|
|
243
|
|
244 def make_call(self, proc, args, pack_func, unpack_func):
|
|
245 #print "make_call() args = " + str(args)
|
|
246 # Don't normally override this (but see Broadcast)
|
|
247 if pack_func is None and args is not None:
|
|
248 raise TypeError, 'non-null args with null pack_func'
|
|
249 #print "packed args"
|
|
250 self.start_call(proc)
|
|
251 if pack_func:
|
|
252 pack_func(args)
|
|
253 self.do_call()
|
|
254 if unpack_func:
|
|
255 result = unpack_func()
|
|
256 else:
|
|
257 result = None
|
|
258 #print "result = " + str(result)
|
|
259 self.unpacker.done()
|
|
260 return result
|
|
261
|
|
262 def start_call(self, proc):
|
|
263 # Don't override this
|
|
264 self.lastxid = xid = self.lastxid + 1
|
|
265 cred = self.mkcred()
|
|
266 verf = self.mkverf()
|
|
267 p = self.packer
|
|
268 p.reset()
|
|
269 p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
|
|
270
|
|
271 def do_call(self):
|
|
272 # This MUST be overridden
|
|
273 raise RuntimeError, 'do_call not defined'
|
|
274
|
|
275 def mkcred(self):
|
|
276 # Override this to use more powerful credentials
|
|
277 if self.cred == None:
|
|
278 self.cred = (AUTH_NULL, make_auth_null())
|
|
279 return self.cred
|
|
280
|
|
281 def mkverf(self):
|
|
282 # Override this to use a more powerful verifier
|
|
283 if self.verf == None:
|
|
284 self.verf = (AUTH_NULL, make_auth_null())
|
|
285 return self.verf
|
|
286
|
|
287 def call_0(self): # Procedure 0 is always like this
|
|
288 return self.make_call(0, None, None, None)
|
|
289
|
|
290
|
|
291 # Record-Marking standard support
|
|
292
|
|
293 try:
|
|
294 from select import select as _select
|
|
295 except:
|
|
296 _select=None
|
|
297
|
|
298 def sendfrag_with_timeout(sock, last, frag, timeout_seconds=None):
|
|
299 x = len(frag)
|
|
300 if last: x = x | 0x80000000L
|
|
301 header = (chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \
|
|
302 chr(int(x>>8 & 0xff)) + chr(int(x & 0xff)))
|
|
303 block=header+frag
|
|
304 n=len(block)
|
|
305 nsent=0
|
|
306 while(nsent<n):
|
|
307 if _select and timeout_seconds:
|
|
308 rlist, wlist, xlist=_select([],[sock],[], timeout_seconds)
|
|
309 if not wlist:
|
|
310 raise EOFError, "Blocked write in sendfrag()"
|
|
311 nsent+=sock.send(block[nsent:])
|
|
312
|
|
313 def recvfrag_with_timeout(sock, timeout_seconds=None):
|
|
314 #print "receiving from ", sock
|
|
315 if _select and timeout_seconds:
|
|
316 #print "Selecting with timeout...", timeout_seconds
|
|
317 rlist, wlist, xlist=_select([sock],[],[], timeout_seconds)
|
|
318 if not rlist:
|
|
319 raise EOFError, "No header at all in recvfrag()"
|
|
320
|
|
321 header = sock.recv(4)
|
|
322 if len(header) < 4:
|
|
323 raise EOFError
|
|
324 x = long(ord(header[0]))<<24 | ord(header[1])<<16 | \
|
|
325 ord(header[2])<<8 | ord(header[3])
|
|
326 last = ((x & 0x80000000L) != 0)
|
|
327 n = int(x & 0x7fffffff)
|
|
328
|
|
329 frag=''
|
|
330
|
|
331 while(len(frag) < n):
|
|
332 if _select and timeout_seconds:
|
|
333 #print "Selecting with timeout...", timeout_seconds
|
|
334 rlist, wlist, xlist=_select([sock],[],[], timeout_seconds)
|
|
335 if not rlist:
|
|
336 raise EOFError, "No data after header in recvfrag()"
|
|
337 frag += sock.recv(n-len(frag))
|
|
338
|
|
339 return last, frag
|
|
340
|
|
341
|
|
342 def recvrecord(sock, timeout_seconds=None):
|
|
343 record = ''
|
|
344 last = 0
|
|
345 while not last:
|
|
346 last, frag = recvfrag_with_timeout(sock, timeout_seconds)
|
|
347 record = record + frag
|
|
348 return record
|
|
349
|
|
350 def sendrecord(sock, record, timeout_seconds=None):
|
|
351 sendfrag_with_timeout(sock, 1, record, timeout_seconds)
|
|
352
|
|
353
|
|
354
|
|
355 # Try to bind to a reserved port (must be root)
|
|
356
|
|
357 last_resv_port_tried = None
|
|
358 def bindresvport(sock, host):
|
|
359 global last_resv_port_tried
|
|
360 FIRST, LAST = 600, 1024 # Range of ports to try
|
|
361 if last_resv_port_tried == None:
|
|
362 import os
|
|
363 last_resv_port_tried = FIRST + os.getpid() % (LAST-FIRST)
|
|
364 for i in range(last_resv_port_tried, LAST) + \
|
|
365 range(FIRST, last_resv_port_tried):
|
|
366 last_resv_port_tried = i
|
|
367 try:
|
|
368 sock.bind((host, i))
|
|
369 return last_resv_port_tried
|
|
370 except socket.error, (errno, msg):
|
|
371 if errno <> 114:
|
|
372 raise socket.error, (errno, msg)
|
|
373 raise RuntimeError, 'can\'t assign reserved port'
|
|
374
|
|
375
|
|
376 # Client using TCP to a specific port
|
|
377
|
|
378 class RawTCPClient(Client):
|
|
379
|
|
380 select_timeout_seconds=None
|
|
381
|
|
382 def makesocket(self):
|
|
383 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
384
|
|
385 def do_call(self):
|
|
386 call = self.packer.get_buf()
|
|
387 sendrecord(self.sock, call, self.select_timeout_seconds)
|
|
388 reply = recvrecord(self.sock, self.select_timeout_seconds)
|
|
389 u = self.unpacker
|
|
390 u.reset(reply)
|
|
391 xid, verf = u.unpack_replyheader()
|
|
392 if xid <> self.lastxid:
|
|
393 # Can't really happen since this is TCP...
|
|
394 raise RuntimeError, 'wrong xid in reply ' + `xid` + \
|
|
395 ' instead of ' + `self.lastxid`
|
|
396
|
|
397
|
|
398 # Client using UDP to a specific port
|
|
399
|
|
400 class RawUDPClient(Client):
|
|
401
|
|
402 def makesocket(self):
|
|
403 self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
404
|
|
405 def do_call(self):
|
|
406 call = self.packer.get_buf()
|
|
407 self.sock.send(call)
|
|
408 try:
|
|
409 from select import select
|
|
410 except ImportError:
|
|
411 print 'WARNING: select not found, RPC may hang'
|
|
412 select = None
|
|
413 BUFSIZE = 8192 # Max UDP buffer size
|
|
414 timeout = 1
|
|
415 count = 5
|
|
416 while 1:
|
|
417 r, w, x = [self.sock], [], []
|
|
418 if select:
|
|
419 r, w, x = select(r, w, x, timeout)
|
|
420 if self.sock not in r:
|
|
421 count = count - 1
|
|
422 if count < 0: raise RuntimeError, 'timeout'
|
|
423 if timeout < 25: timeout = timeout *2
|
|
424 ## print 'RESEND', timeout, count
|
|
425 self.sock.send(call)
|
|
426 continue
|
|
427 reply = self.sock.recv(BUFSIZE)
|
|
428 u = self.unpacker
|
|
429 u.reset(reply)
|
|
430 xid, verf = u.unpack_replyheader()
|
|
431 if xid <> self.lastxid:
|
|
432 ## print 'BAD xid'
|
|
433 continue
|
|
434 break
|
|
435
|
|
436
|
|
437 # Client using UDP broadcast to a specific port
|
|
438
|
|
439 class RawBroadcastUDPClient(RawUDPClient):
|
|
440
|
|
441 def __init__(self, bcastaddr, prog, vers, port):
|
|
442 RawUDPClient.__init__(self, bcastaddr, prog, vers, port)
|
|
443 self.reply_handler = None
|
|
444 self.timeout = 30
|
|
445
|
|
446 def connsocket(self):
|
|
447 # Don't connect -- use sendto
|
|
448 self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
|
449
|
|
450 def set_reply_handler(self, reply_handler):
|
|
451 self.reply_handler = reply_handler
|
|
452
|
|
453 def set_timeout(self, timeout):
|
|
454 self.timeout = timeout # Use None for infinite timeout
|
|
455
|
|
456 def make_call(self, proc, args, pack_func, unpack_func):
|
|
457 if pack_func is None and args is not None:
|
|
458 raise TypeError, 'non-null args with null pack_func'
|
|
459 self.start_call(proc)
|
|
460 if pack_func:
|
|
461 pack_func(args)
|
|
462 call = self.packer.get_buf()
|
|
463 self.sock.sendto(call, (self.host, self.port))
|
|
464 try:
|
|
465 from select import select
|
|
466 except ImportError:
|
|
467 print 'WARNING: select not found, broadcast will hang'
|
|
468 select = None
|
|
469 BUFSIZE = 8192 # Max UDP buffer size (for reply)
|
|
470 replies = []
|
|
471 if unpack_func is None:
|
|
472 def dummy(): pass
|
|
473 unpack_func = dummy
|
|
474 while 1:
|
|
475 r, w, x = [self.sock], [], []
|
|
476 if select:
|
|
477 if self.timeout is None:
|
|
478 r, w, x = select(r, w, x)
|
|
479 else:
|
|
480 r, w, x = select(r, w, x, self.timeout)
|
|
481 if self.sock not in r:
|
|
482 break
|
|
483 reply, fromaddr = self.sock.recvfrom(BUFSIZE)
|
|
484 u = self.unpacker
|
|
485 u.reset(reply)
|
|
486 xid, verf = u.unpack_replyheader()
|
|
487 if xid <> self.lastxid:
|
|
488 ## print 'BAD xid'
|
|
489 continue
|
|
490 reply = unpack_func()
|
|
491 self.unpacker.done()
|
|
492 replies.append((reply, fromaddr))
|
|
493 if self.reply_handler:
|
|
494 self.reply_handler(reply, fromaddr)
|
|
495 return replies
|
|
496
|
|
497
|
|
498 # Port mapper interface
|
|
499
|
|
500 # Program number, version and (fixed!) port number
|
|
501 PMAP_PROG = 100000
|
|
502 PMAP_VERS = 2
|
|
503 PMAP_PORT = 111
|
|
504
|
|
505 # Procedure numbers
|
|
506 PMAPPROC_NULL = 0 # (void) -> void
|
|
507 PMAPPROC_SET = 1 # (mapping) -> bool
|
|
508 PMAPPROC_UNSET = 2 # (mapping) -> bool
|
|
509 PMAPPROC_GETPORT = 3 # (mapping) -> unsigned int
|
|
510 PMAPPROC_DUMP = 4 # (void) -> pmaplist
|
|
511 PMAPPROC_CALLIT = 5 # (call_args) -> call_result
|
|
512
|
|
513 # A mapping is (prog, vers, prot, port) and prot is one of:
|
|
514
|
|
515 IPPROTO_TCP = 6
|
|
516 IPPROTO_UDP = 17
|
|
517
|
|
518 # A pmaplist is a variable-length list of mappings, as follows:
|
|
519 # either (1, mapping, pmaplist) or (0).
|
|
520
|
|
521 # A call_args is (prog, vers, proc, args) where args is opaque;
|
|
522 # a call_result is (port, res) where res is opaque.
|
|
523
|
|
524
|
|
525 class PortMapperPacker(Packer):
|
|
526
|
|
527 def pack_mapping(self, mapping):
|
|
528 prog, vers, prot, port = mapping
|
|
529 self.pack_uint(prog)
|
|
530 self.pack_uint(vers)
|
|
531 self.pack_uint(prot)
|
|
532 self.pack_uint(port)
|
|
533
|
|
534 def pack_pmaplist(self, list):
|
|
535 self.pack_list(list, self.pack_mapping)
|
|
536
|
|
537 def pack_call_args(self, ca):
|
|
538 prog, vers, proc, args = ca
|
|
539 self.pack_uint(prog)
|
|
540 self.pack_uint(vers)
|
|
541 self.pack_uint(proc)
|
|
542 self.pack_opaque(args)
|
|
543
|
|
544
|
|
545 class PortMapperUnpacker(Unpacker):
|
|
546
|
|
547 def unpack_mapping(self):
|
|
548 prog = self.unpack_uint()
|
|
549 vers = self.unpack_uint()
|
|
550 prot = self.unpack_uint()
|
|
551 port = self.unpack_uint()
|
|
552 return prog, vers, prot, port
|
|
553
|
|
554 def unpack_pmaplist(self):
|
|
555 return self.unpack_list(self.unpack_mapping)
|
|
556
|
|
557 def unpack_call_result(self):
|
|
558 port = self.unpack_uint()
|
|
559 res = self.unpack_opaque()
|
|
560 return port, res
|
|
561
|
|
562
|
|
563 class PartialPortMapperClient:
|
|
564
|
|
565 def addpackers(self):
|
|
566 self.packer = PortMapperPacker()
|
|
567 self.unpacker = PortMapperUnpacker('')
|
|
568
|
|
569 def Set(self, mapping):
|
|
570 return self.make_call(PMAPPROC_SET, mapping, \
|
|
571 self.packer.pack_mapping, \
|
|
572 self.unpacker.unpack_uint)
|
|
573
|
|
574 def Unset(self, mapping):
|
|
575 return self.make_call(PMAPPROC_UNSET, mapping, \
|
|
576 self.packer.pack_mapping, \
|
|
577 self.unpacker.unpack_uint)
|
|
578
|
|
579 def Getport(self, mapping):
|
|
580 return self.make_call(PMAPPROC_GETPORT, mapping, \
|
|
581 self.packer.pack_mapping, \
|
|
582 self.unpacker.unpack_uint)
|
|
583
|
|
584 def Dump(self):
|
|
585 return self.make_call(PMAPPROC_DUMP, None, \
|
|
586 None, \
|
|
587 self.unpacker.unpack_pmaplist)
|
|
588
|
|
589 def Callit(self, ca):
|
|
590 return self.make_call(PMAPPROC_CALLIT, ca, \
|
|
591 self.packer.pack_call_args, \
|
|
592 self.unpacker.unpack_call_result)
|
|
593
|
|
594
|
|
595 class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient):
|
|
596
|
|
597 def __init__(self, host, port=PMAP_PORT, timeout_seconds=None):
|
|
598 RawTCPClient.__init__(self, \
|
|
599 host, PMAP_PROG, PMAP_VERS, port)
|
|
600 self.select_timeout_seconds=timeout_seconds
|
|
601
|
|
602
|
|
603 class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient):
|
|
604
|
|
605 def __init__(self, host, port=PMAP_PORT):
|
|
606 RawUDPClient.__init__(self, \
|
|
607 host, PMAP_PROG, PMAP_VERS, port)
|
|
608
|
|
609 class BroadcastUDPPortMapperClient(PartialPortMapperClient, \
|
|
610 RawBroadcastUDPClient):
|
|
611
|
|
612 def __init__(self, bcastaddr):
|
|
613 RawBroadcastUDPClient.__init__(self, \
|
|
614 bcastaddr, PMAP_PROG, PMAP_VERS, PMAP_PORT)
|
|
615
|
|
616
|
|
617 # Generic clients that find their server through the Port mapper
|
|
618
|
|
619 class TCPClient(RawTCPClient):
|
|
620
|
|
621 def __init__(self, host, prog, vers, portmap_proxy_host=None, portmap_proxy_port=PMAP_PORT, timeout_seconds=None):
|
|
622
|
|
623 self.select_timeout_seconds=timeout_seconds
|
|
624 if portmap_proxy_host is None:
|
|
625 portmap_proxy_host=host #use a proxy to get around firewalled portmappers
|
|
626 pmap = TCPPortMapperClient(portmap_proxy_host, portmap_proxy_port,timeout_seconds)
|
|
627 port = pmap.Getport((prog, vers, IPPROTO_TCP, 0))
|
|
628 pmap.close()
|
|
629 if port == 0:
|
|
630 raise RuntimeError, 'program not registered'
|
|
631 RawTCPClient.__init__(self, host, prog, vers, port)
|
|
632
|
|
633
|
|
634 class UDPClient(RawUDPClient):
|
|
635
|
|
636 def __init__(self, host, prog, vers, portmap_proxy_host=None, portmap_proxy_port=PMAP_PORT):
|
|
637 if portmap_proxy_host is None:
|
|
638 portmap_proxy_host=host #use a proxy to get around firewalled portmappers
|
|
639 pmap = UDPPortMapperClient(portmap_proxy_host, portmap_proxy_port)
|
|
640 port = pmap.Getport((prog, vers, IPPROTO_UDP, 0))
|
|
641 pmap.close()
|
|
642 if port == 0:
|
|
643 raise RuntimeError, 'program not registered'
|
|
644 RawUDPClient.__init__(self, host, prog, vers, port)
|
|
645
|
|
646
|
|
647 class BroadcastUDPClient(Client):
|
|
648
|
|
649 def __init__(self, bcastaddr, prog, vers):
|
|
650 self.pmap = BroadcastUDPPortMapperClient(bcastaddr)
|
|
651 self.pmap.set_reply_handler(self.my_reply_handler)
|
|
652 self.prog = prog
|
|
653 self.vers = vers
|
|
654 self.user_reply_handler = None
|
|
655 self.addpackers()
|
|
656
|
|
657 def close(self):
|
|
658 self.pmap.close()
|
|
659
|
|
660 def set_reply_handler(self, reply_handler):
|
|
661 self.user_reply_handler = reply_handler
|
|
662
|
|
663 def set_timeout(self, timeout):
|
|
664 self.pmap.set_timeout(timeout)
|
|
665
|
|
666 def my_reply_handler(self, reply, fromaddr):
|
|
667 port, res = reply
|
|
668 self.unpacker.reset(res)
|
|
669 result = self.unpack_func()
|
|
670 self.unpacker.done()
|
|
671 self.replies.append((result, fromaddr))
|
|
672 if self.user_reply_handler is not None:
|
|
673 self.user_reply_handler(result, fromaddr)
|
|
674
|
|
675 def make_call(self, proc, args, pack_func, unpack_func):
|
|
676 self.packer.reset()
|
|
677 if pack_func:
|
|
678 pack_func(args)
|
|
679 if unpack_func is None:
|
|
680 def dummy(): pass
|
|
681 self.unpack_func = dummy
|
|
682 else:
|
|
683 self.unpack_func = unpack_func
|
|
684 self.replies = []
|
|
685 packed_args = self.packer.get_buf()
|
|
686 dummy_replies = self.pmap.Callit( \
|
|
687 (self.prog, self.vers, proc, packed_args))
|
|
688 return self.replies
|
|
689
|
|
690
|
|
691 # Server classes
|
|
692
|
|
693 # These are not symmetric to the Client classes
|
|
694 # XXX No attempt is made to provide authorization hooks yet
|
|
695
|
|
696 class Server:
|
|
697
|
|
698 def __init__(self, host, prog, vers, port):
|
|
699 self.host = host # Should normally be '' for default interface
|
|
700 self.prog = prog
|
|
701 self.vers = vers
|
|
702 self.port = port # Should normally be 0 for random port
|
|
703 self.makesocket() # Assigns to self.sock and self.prot
|
|
704 self.bindsocket()
|
|
705 self.host, self.port = self.sock.getsockname()
|
|
706 self.addpackers()
|
|
707
|
|
708 def handle(self, call):
|
|
709 # Don't use unpack_header but parse the header piecewise
|
|
710 # XXX I have no idea if I am using the right error responses!
|
|
711 self.unpacker.reset(call)
|
|
712 self.packer.reset()
|
|
713 xid = self.unpacker.unpack_uint()
|
|
714 self.packer.pack_uint(xid)
|
|
715 temp = self.unpacker.unpack_enum()
|
|
716 if temp <> CALL:
|
|
717 return None # Not worthy of a reply
|
|
718 self.packer.pack_uint(REPLY)
|
|
719 temp = self.unpacker.unpack_uint()
|
|
720 if temp <> RPCVERSION:
|
|
721 self.packer.pack_uint(MSG_DENIED)
|
|
722 self.packer.pack_uint(RPC_MISMATCH)
|
|
723 self.packer.pack_uint(RPCVERSION)
|
|
724 self.packer.pack_uint(RPCVERSION)
|
|
725 return self.packer.get_buf()
|
|
726 self.packer.pack_uint(MSG_ACCEPTED)
|
|
727 self.packer.pack_auth((AUTH_NULL, make_auth_null()))
|
|
728 prog = self.unpacker.unpack_uint()
|
|
729 if prog <> self.prog:
|
|
730 self.packer.pack_uint(PROG_UNAVAIL)
|
|
731 return self.packer.get_buf()
|
|
732 vers = self.unpacker.unpack_uint()
|
|
733 if vers <> self.vers:
|
|
734 self.packer.pack_uint(PROG_MISMATCH)
|
|
735 self.packer.pack_uint(self.vers)
|
|
736 self.packer.pack_uint(self.vers)
|
|
737 return self.packer.get_buf()
|
|
738 proc = self.unpacker.unpack_uint()
|
|
739 methname = 'handle_' + `proc`
|
|
740 try:
|
|
741 meth = getattr(self, methname)
|
|
742 except AttributeError:
|
|
743 self.packer.pack_uint(PROC_UNAVAIL)
|
|
744 return self.packer.get_buf()
|
|
745 cred = self.unpacker.unpack_auth()
|
|
746 verf = self.unpacker.unpack_auth()
|
|
747 try:
|
|
748 meth() # Unpack args, call turn_around(), pack reply
|
|
749 except (EOFError, GarbageArgs):
|
|
750 # Too few or too many arguments
|
|
751 self.packer.reset()
|
|
752 self.packer.pack_uint(xid)
|
|
753 self.packer.pack_uint(REPLY)
|
|
754 self.packer.pack_uint(MSG_ACCEPTED)
|
|
755 self.packer.pack_auth((AUTH_NULL, make_auth_null()))
|
|
756 self.packer.pack_uint(GARBAGE_ARGS)
|
|
757 return self.packer.get_buf()
|
|
758
|
|
759 def turn_around(self):
|
|
760 try:
|
|
761 self.unpacker.done()
|
|
762 except RuntimeError:
|
|
763 raise GarbageArgs
|
|
764 self.packer.pack_uint(SUCCESS)
|
|
765
|
|
766 def handle_0(self): # Handle NULL message
|
|
767 self.turn_around()
|
|
768
|
|
769 def makesocket(self):
|
|
770 # This MUST be overridden
|
|
771 raise RuntimeError, 'makesocket not defined'
|
|
772
|
|
773 def bindsocket(self):
|
|
774 # Override this to bind to a different port (e.g. reserved)
|
|
775 self.sock.bind((self.host, self.port))
|
|
776
|
|
777 def addpackers(self):
|
|
778 # Override this to use derived classes from Packer/Unpacker
|
|
779 self.packer = Packer()
|
|
780 self.unpacker = Unpacker('')
|
|
781
|
|
782
|
|
783 class TCPServer(Server):
|
|
784
|
|
785 def register(self):
|
|
786 mapping = self.prog, self.vers, self.prot, self.port
|
|
787 p = TCPPortMapperClient(self.host)
|
|
788 if not p.Set(mapping):
|
|
789 raise RuntimeError, 'register failed'
|
|
790
|
|
791 def unregister(self):
|
|
792 mapping = self.prog, self.vers, self.prot, self.port
|
|
793 p = TCPPortMapperClient(self.host)
|
|
794 if not p.Unset(mapping):
|
|
795 raise RuntimeError, 'unregister failed'
|
|
796
|
|
797 def makesocket(self):
|
|
798 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
799 self.prot = IPPROTO_TCP
|
|
800
|
|
801 def loop(self):
|
|
802 self.sock.listen(0)
|
|
803 while 1:
|
|
804 self.session(self.sock.accept())
|
|
805
|
|
806 def session(self, connection):
|
|
807 sock, (host, port) = connection
|
|
808 while 1:
|
|
809 try:
|
|
810 call = recvrecord(sock)
|
|
811 except EOFError:
|
|
812 break
|
|
813 except socket.error, msg:
|
|
814 print 'socket error:', msg
|
|
815 break
|
|
816 reply = self.handle(call)
|
|
817 if reply is not None:
|
|
818 sendrecord(sock, reply)
|
|
819
|
|
820 def forkingloop(self):
|
|
821 # Like loop but uses forksession()
|
|
822 self.sock.listen(0)
|
|
823 while 1:
|
|
824 self.forksession(self.sock.accept())
|
|
825
|
|
826 def forksession(self, connection):
|
|
827 # Like session but forks off a subprocess
|
|
828 import os
|
|
829 # Wait for deceased children
|
|
830 try:
|
|
831 while 1:
|
|
832 pid, sts = os.waitpid(0, 1)
|
|
833 except os.error:
|
|
834 pass
|
|
835 pid = None
|
|
836 try:
|
|
837 pid = os.fork()
|
|
838 if pid: # Parent
|
|
839 connection[0].close()
|
|
840 return
|
|
841 # Child
|
|
842 self.session(connection)
|
|
843 finally:
|
|
844 # Make sure we don't fall through in the parent
|
|
845 if pid == 0:
|
|
846 os._exit(0)
|
|
847
|
|
848
|
|
849 class UDPServer(Server):
|
|
850
|
|
851 def register(self):
|
|
852 mapping = self.prog, self.vers, self.prot, self.port
|
|
853 p = UDPPortMapperClient(self.host)
|
|
854 if not p.Set(mapping):
|
|
855 raise RuntimeError, 'register failed'
|
|
856
|
|
857 def unregister(self):
|
|
858 mapping = self.prog, self.vers, self.prot, self.port
|
|
859 p = UDPPortMapperClient(self.host)
|
|
860 if not p.Unset(mapping):
|
|
861 raise RuntimeError, 'unregister failed'
|
|
862
|
|
863 def makesocket(self):
|
|
864 self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
865 self.prot = IPPROTO_UDP
|
|
866
|
|
867 def loop(self):
|
|
868 while 1:
|
|
869 self.session()
|
|
870
|
|
871 def session(self):
|
|
872 call, host_port = self.sock.recvfrom(8192)
|
|
873 reply = self.handle(call)
|
|
874 if reply <> None:
|
|
875 self.sock.sendto(reply, host_port)
|
|
876
|
|
877
|
|
878 # Simple test program -- dump local portmapper status
|
|
879
|
|
880 def test():
|
|
881 pmap = UDPPortMapperClient('')
|
|
882 list = pmap.Dump()
|
|
883 list.sort()
|
|
884 for prog, vers, prot, port in list:
|
|
885 print prog, vers,
|
|
886 if prot == IPPROTO_TCP: print 'tcp',
|
|
887 elif prot == IPPROTO_UDP: print 'udp',
|
|
888 else: print prot,
|
|
889 print port
|
|
890
|
|
891
|
|
892 # Test program for broadcast operation -- dump everybody's portmapper status
|
|
893
|
|
894 def testbcast():
|
|
895 import sys
|
|
896 if sys.argv[1:]:
|
|
897 bcastaddr = sys.argv[1]
|
|
898 else:
|
|
899 bcastaddr = '<broadcast>'
|
|
900 def rh(reply, fromaddr):
|
|
901 host, port = fromaddr
|
|
902 print host + '\t' + `reply`
|
|
903 pmap = BroadcastUDPPortMapperClient(bcastaddr)
|
|
904 pmap.set_reply_handler(rh)
|
|
905 pmap.set_timeout(5)
|
|
906 replies = pmap.Getport((100002, 1, IPPROTO_UDP, 0))
|
|
907
|
|
908
|
|
909 # Test program for server, with corresponding client
|
|
910 # On machine A: python -c 'import rpc; rpc.testsvr()'
|
|
911 # On machine B: python -c 'import rpc; rpc.testclt()' A
|
|
912 # (A may be == B)
|
|
913
|
|
914 def testsvr():
|
|
915 # Simple test class -- proc 1 doubles its string argument as reply
|
|
916 class S(UDPServer):
|
|
917 def handle_1(self):
|
|
918 arg = self.unpacker.unpack_string()
|
|
919 self.turn_around()
|
|
920 print 'RPC function 1 called, arg', `arg`
|
|
921 self.packer.pack_string(arg + arg)
|
|
922 #
|
|
923 s = S('', 0x20000000, 1, 0)
|
|
924 try:
|
|
925 s.unregister()
|
|
926 except RuntimeError, msg:
|
|
927 print 'RuntimeError:', msg, '(ignored)'
|
|
928 s.register()
|
|
929 print 'Service started...'
|
|
930 try:
|
|
931 s.loop()
|
|
932 finally:
|
|
933 s.unregister()
|
|
934 print 'Service interrupted.'
|
|
935
|
|
936
|
|
937 def testclt():
|
|
938 import sys
|
|
939 if sys.argv[1:]: host = sys.argv[1]
|
|
940 else: host = ''
|
|
941 # Client for above server
|
|
942 class C(UDPClient):
|
|
943 def call_1(self, arg):
|
|
944 return self.make_call(1, arg, \
|
|
945 self.packer.pack_string, \
|
|
946 self.unpacker.unpack_string)
|
|
947 c = C(host, 0x20000000, 1)
|
|
948 print 'making call...'
|
|
949 reply = c.call_1('hello, world, ')
|
|
950 print 'call returned', `reply`
|
|
951
|
|
952 def testclt2():
|
|
953 import sys
|
|
954 host = '127.0.0.1'
|
|
955 # Client for above server
|
|
956 class C(UDPClient):
|
|
957 def call_1(self, arg):
|
|
958 return self.make_call(1, arg, \
|
|
959 self.packer.pack_string, \
|
|
960 self.unpacker.unpack_string)
|
|
961 c = C(host, 0x20000000, 1)
|
|
962 print 'making call...'
|
|
963 reply = c.call_1('hello, world, ')
|
|
964 print 'call returned', `reply`
|
|
965
|