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)
|
---|