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