• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
13import xdr
14import socket
15import os
16
17RPCVERSION = 2
18
19CALL = 0
20REPLY = 1
21
22AUTH_NULL = 0
23AUTH_UNIX = 1
24AUTH_SHORT = 2
25AUTH_DES = 3
26
27MSG_ACCEPTED = 0
28MSG_DENIED = 1
29
30SUCCESS = 0                             # RPC executed successfully
31PROG_UNAVAIL  = 1                       # remote hasn't exported program
32PROG_MISMATCH = 2                       # remote can't support version #
33PROC_UNAVAIL  = 3                       # program can't support procedure
34GARBAGE_ARGS  = 4                       # procedure can't decode params
35
36RPC_MISMATCH = 0                        # RPC version number != 2
37AUTH_ERROR = 1                          # remote can't authenticate caller
38
39AUTH_BADCRED      = 1                   # bad credentials (seal broken)
40AUTH_REJECTEDCRED = 2                   # client must begin new session
41AUTH_BADVERF      = 3                   # bad verifier (seal broken)
42AUTH_REJECTEDVERF = 4                   # verifier expired or replayed
43AUTH_TOOWEAK      = 5                   # rejected for security reasons
44
45
46class 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
83class BadRPCFormat(Exception): pass
84class BadRPCVersion(Exception): pass
85class GarbageArgs(Exception): pass
86
87class 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
152def make_auth_null():
153    return ''
154
155def 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
160def 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
172def 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
202class 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
283def 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
290def sendrecord(sock, record):
291    sendfrag(sock, 1, record)
292
293def 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
309def 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
320last_resv_port_tried = None
321def 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
341class 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
361class 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
400class 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
462PMAP_PROG = 100000
463PMAP_VERS = 2
464PMAP_PORT = 111
465
466# Procedure numbers
467PMAPPROC_NULL = 0                       # (void) -> void
468PMAPPROC_SET = 1                        # (mapping) -> bool
469PMAPPROC_UNSET = 2                      # (mapping) -> bool
470PMAPPROC_GETPORT = 3                    # (mapping) -> unsigned int
471PMAPPROC_DUMP = 4                       # (void) -> pmaplist
472PMAPPROC_CALLIT = 5                     # (call_args) -> call_result
473
474# A mapping is (prog, vers, prot, port) and prot is one of:
475
476IPPROTO_TCP = 6
477IPPROTO_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
486class 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
506class 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
524class 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
556class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient):
557
558    def __init__(self, host):
559        RawTCPClient.__init__(self, \
560                host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
561
562
563class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient):
564
565    def __init__(self, host):
566        RawUDPClient.__init__(self, \
567                host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
568
569
570class 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
580class 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
591class 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
602class 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
651class 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
750class 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
804class 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
823def 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
837def 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
857def 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
880def 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)
894