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