| # Sun RPC version 2 -- RFC1057. |
| |
| # XXX There should be separate exceptions for the various reasons why |
| # XXX an RPC can fail, rather than using RuntimeError for everything |
| |
| # XXX Need to use class based exceptions rather than string exceptions |
| |
| # XXX The UDP version of the protocol resends requests when it does |
| # XXX not receive a timely reply -- use only for idempotent calls! |
| |
| # XXX There is no provision for call timeout on TCP connections |
| |
| import xdr |
| import socket |
| import os |
| |
| RPCVERSION = 2 |
| |
| CALL = 0 |
| REPLY = 1 |
| |
| AUTH_NULL = 0 |
| AUTH_UNIX = 1 |
| AUTH_SHORT = 2 |
| AUTH_DES = 3 |
| |
| MSG_ACCEPTED = 0 |
| MSG_DENIED = 1 |
| |
| SUCCESS = 0 # RPC executed successfully |
| PROG_UNAVAIL = 1 # remote hasn't exported program |
| PROG_MISMATCH = 2 # remote can't support version # |
| PROC_UNAVAIL = 3 # program can't support procedure |
| GARBAGE_ARGS = 4 # procedure can't decode params |
| |
| RPC_MISMATCH = 0 # RPC version number != 2 |
| AUTH_ERROR = 1 # remote can't authenticate caller |
| |
| AUTH_BADCRED = 1 # bad credentials (seal broken) |
| AUTH_REJECTEDCRED = 2 # client must begin new session |
| AUTH_BADVERF = 3 # bad verifier (seal broken) |
| AUTH_REJECTEDVERF = 4 # verifier expired or replayed |
| AUTH_TOOWEAK = 5 # rejected for security reasons |
| |
| |
| class Packer(xdr.Packer): |
| |
| def pack_auth(self, auth): |
| flavor, stuff = auth |
| self.pack_enum(flavor) |
| self.pack_opaque(stuff) |
| |
| def pack_auth_unix(self, stamp, machinename, uid, gid, gids): |
| self.pack_uint(stamp) |
| self.pack_string(machinename) |
| self.pack_uint(uid) |
| self.pack_uint(gid) |
| self.pack_uint(len(gids)) |
| for i in gids: |
| self.pack_uint(i) |
| |
| def pack_callheader(self, xid, prog, vers, proc, cred, verf): |
| self.pack_uint(xid) |
| self.pack_enum(CALL) |
| self.pack_uint(RPCVERSION) |
| self.pack_uint(prog) |
| self.pack_uint(vers) |
| self.pack_uint(proc) |
| self.pack_auth(cred) |
| self.pack_auth(verf) |
| # Caller must add procedure-specific part of call |
| |
| def pack_replyheader(self, xid, verf): |
| self.pack_uint(xid) |
| self.pack_enum(REPLY) |
| self.pack_uint(MSG_ACCEPTED) |
| self.pack_auth(verf) |
| self.pack_enum(SUCCESS) |
| # Caller must add procedure-specific part of reply |
| |
| |
| # Exceptions |
| BadRPCFormat = 'rpc.BadRPCFormat' |
| BadRPCVersion = 'rpc.BadRPCVersion' |
| GarbageArgs = 'rpc.GarbageArgs' |
| |
| class Unpacker(xdr.Unpacker): |
| |
| def unpack_auth(self): |
| flavor = self.unpack_enum() |
| stuff = self.unpack_opaque() |
| return (flavor, stuff) |
| |
| def unpack_callheader(self): |
| xid = self.unpack_uint() |
| temp = self.unpack_enum() |
| if temp != CALL: |
| raise BadRPCFormat, 'no CALL but %r' % (temp,) |
| temp = self.unpack_uint() |
| if temp != RPCVERSION: |
| raise BadRPCVersion, 'bad RPC version %r' % (temp,) |
| prog = self.unpack_uint() |
| vers = self.unpack_uint() |
| proc = self.unpack_uint() |
| cred = self.unpack_auth() |
| verf = self.unpack_auth() |
| return xid, prog, vers, proc, cred, verf |
| # Caller must add procedure-specific part of call |
| |
| def unpack_replyheader(self): |
| xid = self.unpack_uint() |
| mtype = self.unpack_enum() |
| if mtype != REPLY: |
| raise RuntimeError, 'no REPLY but %r' % (mtype,) |
| stat = self.unpack_enum() |
| if stat == MSG_DENIED: |
| stat = self.unpack_enum() |
| if stat == RPC_MISMATCH: |
| low = self.unpack_uint() |
| high = self.unpack_uint() |
| raise RuntimeError, \ |
| 'MSG_DENIED: RPC_MISMATCH: %r' % ((low, high),) |
| if stat == AUTH_ERROR: |
| stat = self.unpack_uint() |
| raise RuntimeError, \ |
| 'MSG_DENIED: AUTH_ERROR: %r' % (stat,) |
| raise RuntimeError, 'MSG_DENIED: %r' % (stat,) |
| if stat != MSG_ACCEPTED: |
| raise RuntimeError, \ |
| 'Neither MSG_DENIED nor MSG_ACCEPTED: %r' % (stat,) |
| verf = self.unpack_auth() |
| stat = self.unpack_enum() |
| if stat == PROG_UNAVAIL: |
| raise RuntimeError, 'call failed: PROG_UNAVAIL' |
| if stat == PROG_MISMATCH: |
| low = self.unpack_uint() |
| high = self.unpack_uint() |
| raise RuntimeError, \ |
| 'call failed: PROG_MISMATCH: %r' % ((low, high),) |
| if stat == PROC_UNAVAIL: |
| raise RuntimeError, 'call failed: PROC_UNAVAIL' |
| if stat == GARBAGE_ARGS: |
| raise RuntimeError, 'call failed: GARBAGE_ARGS' |
| if stat != SUCCESS: |
| raise RuntimeError, 'call failed: %r' % (stat,) |
| return xid, verf |
| # Caller must get procedure-specific part of reply |
| |
| |
| # Subroutines to create opaque authentication objects |
| |
| def make_auth_null(): |
| return '' |
| |
| def make_auth_unix(seed, host, uid, gid, groups): |
| p = Packer() |
| p.pack_auth_unix(seed, host, uid, gid, groups) |
| return p.get_buf() |
| |
| def make_auth_unix_default(): |
| try: |
| from os import getuid, getgid |
| uid = getuid() |
| gid = getgid() |
| except ImportError: |
| uid = gid = 0 |
| import time |
| return make_auth_unix(int(time.time()-unix_epoch()), \ |
| socket.gethostname(), uid, gid, []) |
| |
| _unix_epoch = -1 |
| def unix_epoch(): |
| """Very painful calculation of when the Unix Epoch is. |
| |
| This is defined as the return value of time.time() on Jan 1st, |
| 1970, 00:00:00 GMT. |
| |
| On a Unix system, this should always return 0.0. On a Mac, the |
| calculations are needed -- and hard because of integer overflow |
| and other limitations. |
| |
| """ |
| global _unix_epoch |
| if _unix_epoch >= 0: return _unix_epoch |
| import time |
| now = time.time() |
| localt = time.localtime(now) # (y, m, d, hh, mm, ss, ..., ..., ...) |
| gmt = time.gmtime(now) |
| offset = time.mktime(localt) - time.mktime(gmt) |
| y, m, d, hh, mm, ss = 1970, 1, 1, 0, 0, 0 |
| offset, ss = divmod(ss + offset, 60) |
| offset, mm = divmod(mm + offset, 60) |
| offset, hh = divmod(hh + offset, 24) |
| d = d + offset |
| _unix_epoch = time.mktime((y, m, d, hh, mm, ss, 0, 0, 0)) |
| print "Unix epoch:", time.ctime(_unix_epoch) |
| return _unix_epoch |
| |
| |
| # Common base class for clients |
| |
| class Client: |
| |
| def __init__(self, host, prog, vers, port): |
| self.host = host |
| self.prog = prog |
| self.vers = vers |
| self.port = port |
| self.makesocket() # Assigns to self.sock |
| self.bindsocket() |
| self.connsocket() |
| self.lastxid = 0 # XXX should be more random? |
| self.addpackers() |
| self.cred = None |
| self.verf = None |
| |
| def close(self): |
| self.sock.close() |
| |
| def makesocket(self): |
| # This MUST be overridden |
| raise RuntimeError, 'makesocket not defined' |
| |
| def connsocket(self): |
| # Override this if you don't want/need a connection |
| self.sock.connect((self.host, self.port)) |
| |
| def bindsocket(self): |
| # Override this to bind to a different port (e.g. reserved) |
| self.sock.bind(('', 0)) |
| |
| def addpackers(self): |
| # Override this to use derived classes from Packer/Unpacker |
| self.packer = Packer() |
| self.unpacker = Unpacker('') |
| |
| def make_call(self, proc, args, pack_func, unpack_func): |
| # Don't normally override this (but see Broadcast) |
| if pack_func is None and args is not None: |
| raise TypeError, 'non-null args with null pack_func' |
| self.start_call(proc) |
| if pack_func: |
| pack_func(args) |
| self.do_call() |
| if unpack_func: |
| result = unpack_func() |
| else: |
| result = None |
| self.unpacker.done() |
| return result |
| |
| def start_call(self, proc): |
| # Don't override this |
| self.lastxid = xid = self.lastxid + 1 |
| cred = self.mkcred() |
| verf = self.mkverf() |
| p = self.packer |
| p.reset() |
| p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf) |
| |
| def do_call(self): |
| # This MUST be overridden |
| raise RuntimeError, 'do_call not defined' |
| |
| def mkcred(self): |
| # Override this to use more powerful credentials |
| if self.cred == None: |
| self.cred = (AUTH_NULL, make_auth_null()) |
| return self.cred |
| |
| def mkverf(self): |
| # Override this to use a more powerful verifier |
| if self.verf == None: |
| self.verf = (AUTH_NULL, make_auth_null()) |
| return self.verf |
| |
| def call_0(self): # Procedure 0 is always like this |
| return self.make_call(0, None, None, None) |
| |
| |
| # Record-Marking standard support |
| |
| def sendfrag(sock, last, frag): |
| x = len(frag) |
| if last: x = x | 0x80000000L |
| header = (chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \ |
| chr(int(x>>8 & 0xff)) + chr(int(x & 0xff))) |
| sock.send(header + frag) |
| |
| def sendrecord(sock, record): |
| sendfrag(sock, 1, record) |
| |
| def recvfrag(sock): |
| header = sock.recv(4) |
| if len(header) < 4: |
| raise EOFError |
| x = long(ord(header[0]))<<24 | ord(header[1])<<16 | \ |
| ord(header[2])<<8 | ord(header[3]) |
| last = ((x & 0x80000000) != 0) |
| n = int(x & 0x7fffffff) |
| frag = '' |
| while n > 0: |
| buf = sock.recv(n) |
| if not buf: raise EOFError |
| n = n - len(buf) |
| frag = frag + buf |
| return last, frag |
| |
| def recvrecord(sock): |
| record = '' |
| last = 0 |
| while not last: |
| last, frag = recvfrag(sock) |
| record = record + frag |
| return record |
| |
| |
| # Try to bind to a reserved port (must be root) |
| |
| last_resv_port_tried = None |
| def bindresvport(sock, host): |
| global last_resv_port_tried |
| FIRST, LAST = 600, 1024 # Range of ports to try |
| if last_resv_port_tried == None: |
| import os |
| last_resv_port_tried = FIRST + os.getpid() % (LAST-FIRST) |
| for i in range(last_resv_port_tried, LAST) + \ |
| range(FIRST, last_resv_port_tried): |
| last_resv_port_tried = i |
| try: |
| sock.bind((host, i)) |
| return last_resv_port_tried |
| except socket.error as e: |
| (errno, msg) = e |
| if errno != 114: |
| raise socket.error, (errno, msg) |
| raise RuntimeError, 'can\'t assign reserved port' |
| |
| |
| # Client using TCP to a specific port |
| |
| class RawTCPClient(Client): |
| |
| def makesocket(self): |
| self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| |
| def do_call(self): |
| call = self.packer.get_buf() |
| sendrecord(self.sock, call) |
| reply = recvrecord(self.sock) |
| u = self.unpacker |
| u.reset(reply) |
| xid, verf = u.unpack_replyheader() |
| if xid != self.lastxid: |
| # Can't really happen since this is TCP... |
| raise RuntimeError, 'wrong xid in reply %r instead of %r' % ( |
| xid, self.lastxid) |
| |
| |
| # Client using UDP to a specific port |
| |
| class RawUDPClient(Client): |
| |
| def makesocket(self): |
| self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| |
| def do_call(self): |
| call = self.packer.get_buf() |
| self.sock.send(call) |
| try: |
| from select import select |
| except ImportError: |
| print 'WARNING: select not found, RPC may hang' |
| select = None |
| BUFSIZE = 8192 # Max UDP buffer size |
| timeout = 1 |
| count = 5 |
| while 1: |
| r, w, x = [self.sock], [], [] |
| if select: |
| r, w, x = select(r, w, x, timeout) |
| if self.sock not in r: |
| count = count - 1 |
| if count < 0: raise RuntimeError, 'timeout' |
| if timeout < 25: timeout = timeout *2 |
| ## print 'RESEND', timeout, count |
| self.sock.send(call) |
| continue |
| reply = self.sock.recv(BUFSIZE) |
| u = self.unpacker |
| u.reset(reply) |
| xid, verf = u.unpack_replyheader() |
| if xid != self.lastxid: |
| ## print 'BAD xid' |
| continue |
| break |
| |
| |
| # Client using UDP broadcast to a specific port |
| |
| class RawBroadcastUDPClient(RawUDPClient): |
| |
| def __init__(self, bcastaddr, prog, vers, port): |
| RawUDPClient.__init__(self, bcastaddr, prog, vers, port) |
| self.reply_handler = None |
| self.timeout = 30 |
| |
| def connsocket(self): |
| # Don't connect -- use sendto |
| self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) |
| |
| def set_reply_handler(self, reply_handler): |
| self.reply_handler = reply_handler |
| |
| def set_timeout(self, timeout): |
| self.timeout = timeout # Use None for infinite timeout |
| |
| def make_call(self, proc, args, pack_func, unpack_func): |
| if pack_func is None and args is not None: |
| raise TypeError, 'non-null args with null pack_func' |
| self.start_call(proc) |
| if pack_func: |
| pack_func(args) |
| call = self.packer.get_buf() |
| self.sock.sendto(call, (self.host, self.port)) |
| try: |
| from select import select |
| except ImportError: |
| print 'WARNING: select not found, broadcast will hang' |
| select = None |
| BUFSIZE = 8192 # Max UDP buffer size (for reply) |
| replies = [] |
| if unpack_func is None: |
| def dummy(): pass |
| unpack_func = dummy |
| while 1: |
| r, w, x = [self.sock], [], [] |
| if select: |
| if self.timeout is None: |
| r, w, x = select(r, w, x) |
| else: |
| r, w, x = select(r, w, x, self.timeout) |
| if self.sock not in r: |
| break |
| reply, fromaddr = self.sock.recvfrom(BUFSIZE) |
| u = self.unpacker |
| u.reset(reply) |
| xid, verf = u.unpack_replyheader() |
| if xid != self.lastxid: |
| ## print 'BAD xid' |
| continue |
| reply = unpack_func() |
| self.unpacker.done() |
| replies.append((reply, fromaddr)) |
| if self.reply_handler: |
| self.reply_handler(reply, fromaddr) |
| return replies |
| |
| |
| # Port mapper interface |
| |
| # Program number, version and (fixed!) port number |
| PMAP_PROG = 100000 |
| PMAP_VERS = 2 |
| PMAP_PORT = 111 |
| |
| # Procedure numbers |
| PMAPPROC_NULL = 0 # (void) -> void |
| PMAPPROC_SET = 1 # (mapping) -> bool |
| PMAPPROC_UNSET = 2 # (mapping) -> bool |
| PMAPPROC_GETPORT = 3 # (mapping) -> unsigned int |
| PMAPPROC_DUMP = 4 # (void) -> pmaplist |
| PMAPPROC_CALLIT = 5 # (call_args) -> call_result |
| |
| # A mapping is (prog, vers, prot, port) and prot is one of: |
| |
| IPPROTO_TCP = 6 |
| IPPROTO_UDP = 17 |
| |
| # A pmaplist is a variable-length list of mappings, as follows: |
| # either (1, mapping, pmaplist) or (0). |
| |
| # A call_args is (prog, vers, proc, args) where args is opaque; |
| # a call_result is (port, res) where res is opaque. |
| |
| |
| class PortMapperPacker(Packer): |
| |
| def pack_mapping(self, mapping): |
| prog, vers, prot, port = mapping |
| self.pack_uint(prog) |
| self.pack_uint(vers) |
| self.pack_uint(prot) |
| self.pack_uint(port) |
| |
| def pack_pmaplist(self, list): |
| self.pack_list(list, self.pack_mapping) |
| |
| def pack_call_args(self, ca): |
| prog, vers, proc, args = ca |
| self.pack_uint(prog) |
| self.pack_uint(vers) |
| self.pack_uint(proc) |
| self.pack_opaque(args) |
| |
| |
| class PortMapperUnpacker(Unpacker): |
| |
| def unpack_mapping(self): |
| prog = self.unpack_uint() |
| vers = self.unpack_uint() |
| prot = self.unpack_uint() |
| port = self.unpack_uint() |
| return prog, vers, prot, port |
| |
| def unpack_pmaplist(self): |
| return self.unpack_list(self.unpack_mapping) |
| |
| def unpack_call_result(self): |
| port = self.unpack_uint() |
| res = self.unpack_opaque() |
| return port, res |
| |
| |
| class PartialPortMapperClient: |
| |
| def addpackers(self): |
| self.packer = PortMapperPacker() |
| self.unpacker = PortMapperUnpacker('') |
| |
| def Set(self, mapping): |
| return self.make_call(PMAPPROC_SET, mapping, \ |
| self.packer.pack_mapping, \ |
| self.unpacker.unpack_uint) |
| |
| def Unset(self, mapping): |
| return self.make_call(PMAPPROC_UNSET, mapping, \ |
| self.packer.pack_mapping, \ |
| self.unpacker.unpack_uint) |
| |
| def Getport(self, mapping): |
| return self.make_call(PMAPPROC_GETPORT, mapping, \ |
| self.packer.pack_mapping, \ |
| self.unpacker.unpack_uint) |
| |
| def Dump(self): |
| return self.make_call(PMAPPROC_DUMP, None, \ |
| None, \ |
| self.unpacker.unpack_pmaplist) |
| |
| def Callit(self, ca): |
| return self.make_call(PMAPPROC_CALLIT, ca, \ |
| self.packer.pack_call_args, \ |
| self.unpacker.unpack_call_result) |
| |
| |
| class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient): |
| |
| def __init__(self, host): |
| RawTCPClient.__init__(self, \ |
| host, PMAP_PROG, PMAP_VERS, PMAP_PORT) |
| |
| |
| class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient): |
| |
| def __init__(self, host): |
| RawUDPClient.__init__(self, \ |
| host, PMAP_PROG, PMAP_VERS, PMAP_PORT) |
| |
| |
| class BroadcastUDPPortMapperClient(PartialPortMapperClient, \ |
| RawBroadcastUDPClient): |
| |
| def __init__(self, bcastaddr): |
| RawBroadcastUDPClient.__init__(self, \ |
| bcastaddr, PMAP_PROG, PMAP_VERS, PMAP_PORT) |
| |
| |
| # Generic clients that find their server through the Port mapper |
| |
| class TCPClient(RawTCPClient): |
| |
| def __init__(self, host, prog, vers): |
| pmap = TCPPortMapperClient(host) |
| port = pmap.Getport((prog, vers, IPPROTO_TCP, 0)) |
| pmap.close() |
| if port == 0: |
| raise RuntimeError, 'program not registered' |
| RawTCPClient.__init__(self, host, prog, vers, port) |
| |
| |
| class UDPClient(RawUDPClient): |
| |
| def __init__(self, host, prog, vers): |
| pmap = UDPPortMapperClient(host) |
| port = pmap.Getport((prog, vers, IPPROTO_UDP, 0)) |
| pmap.close() |
| if port == 0: |
| raise RuntimeError, 'program not registered' |
| RawUDPClient.__init__(self, host, prog, vers, port) |
| |
| |
| class BroadcastUDPClient(Client): |
| |
| def __init__(self, bcastaddr, prog, vers): |
| self.pmap = BroadcastUDPPortMapperClient(bcastaddr) |
| self.pmap.set_reply_handler(self.my_reply_handler) |
| self.prog = prog |
| self.vers = vers |
| self.user_reply_handler = None |
| self.addpackers() |
| |
| def close(self): |
| self.pmap.close() |
| |
| def set_reply_handler(self, reply_handler): |
| self.user_reply_handler = reply_handler |
| |
| def set_timeout(self, timeout): |
| self.pmap.set_timeout(timeout) |
| |
| def my_reply_handler(self, reply, fromaddr): |
| port, res = reply |
| self.unpacker.reset(res) |
| result = self.unpack_func() |
| self.unpacker.done() |
| self.replies.append((result, fromaddr)) |
| if self.user_reply_handler is not None: |
| self.user_reply_handler(result, fromaddr) |
| |
| def make_call(self, proc, args, pack_func, unpack_func): |
| self.packer.reset() |
| if pack_func: |
| pack_func(args) |
| if unpack_func is None: |
| def dummy(): pass |
| self.unpack_func = dummy |
| else: |
| self.unpack_func = unpack_func |
| self.replies = [] |
| packed_args = self.packer.get_buf() |
| dummy_replies = self.pmap.Callit( \ |
| (self.prog, self.vers, proc, packed_args)) |
| return self.replies |
| |
| |
| # Server classes |
| |
| # These are not symmetric to the Client classes |
| # XXX No attempt is made to provide authorization hooks yet |
| |
| class Server: |
| |
| def __init__(self, host, prog, vers, port): |
| self.host = host # Should normally be '' for default interface |
| self.prog = prog |
| self.vers = vers |
| self.port = port # Should normally be 0 for random port |
| self.makesocket() # Assigns to self.sock and self.prot |
| self.bindsocket() |
| self.host, self.port = self.sock.getsockname() |
| self.addpackers() |
| |
| def register(self): |
| mapping = self.prog, self.vers, self.prot, self.port |
| p = TCPPortMapperClient(self.host) |
| if not p.Set(mapping): |
| raise RuntimeError, 'register failed' |
| |
| def unregister(self): |
| mapping = self.prog, self.vers, self.prot, self.port |
| p = TCPPortMapperClient(self.host) |
| if not p.Unset(mapping): |
| raise RuntimeError, 'unregister failed' |
| |
| def handle(self, call): |
| # Don't use unpack_header but parse the header piecewise |
| # XXX I have no idea if I am using the right error responses! |
| self.unpacker.reset(call) |
| self.packer.reset() |
| xid = self.unpacker.unpack_uint() |
| self.packer.pack_uint(xid) |
| temp = self.unpacker.unpack_enum() |
| if temp != CALL: |
| return None # Not worthy of a reply |
| self.packer.pack_uint(REPLY) |
| temp = self.unpacker.unpack_uint() |
| if temp != RPCVERSION: |
| self.packer.pack_uint(MSG_DENIED) |
| self.packer.pack_uint(RPC_MISMATCH) |
| self.packer.pack_uint(RPCVERSION) |
| self.packer.pack_uint(RPCVERSION) |
| return self.packer.get_buf() |
| self.packer.pack_uint(MSG_ACCEPTED) |
| self.packer.pack_auth((AUTH_NULL, make_auth_null())) |
| prog = self.unpacker.unpack_uint() |
| if prog != self.prog: |
| self.packer.pack_uint(PROG_UNAVAIL) |
| return self.packer.get_buf() |
| vers = self.unpacker.unpack_uint() |
| if vers != self.vers: |
| self.packer.pack_uint(PROG_MISMATCH) |
| self.packer.pack_uint(self.vers) |
| self.packer.pack_uint(self.vers) |
| return self.packer.get_buf() |
| proc = self.unpacker.unpack_uint() |
| methname = 'handle_' + repr(proc) |
| try: |
| meth = getattr(self, methname) |
| except AttributeError: |
| self.packer.pack_uint(PROC_UNAVAIL) |
| return self.packer.get_buf() |
| cred = self.unpacker.unpack_auth() |
| verf = self.unpacker.unpack_auth() |
| try: |
| meth() # Unpack args, call turn_around(), pack reply |
| except (EOFError, GarbageArgs): |
| # Too few or too many arguments |
| self.packer.reset() |
| self.packer.pack_uint(xid) |
| self.packer.pack_uint(REPLY) |
| self.packer.pack_uint(MSG_ACCEPTED) |
| self.packer.pack_auth((AUTH_NULL, make_auth_null())) |
| self.packer.pack_uint(GARBAGE_ARGS) |
| return self.packer.get_buf() |
| |
| def turn_around(self): |
| try: |
| self.unpacker.done() |
| except RuntimeError: |
| raise GarbageArgs |
| self.packer.pack_uint(SUCCESS) |
| |
| def handle_0(self): # Handle NULL message |
| self.turn_around() |
| |
| def makesocket(self): |
| # This MUST be overridden |
| raise RuntimeError, 'makesocket not defined' |
| |
| def bindsocket(self): |
| # Override this to bind to a different port (e.g. reserved) |
| self.sock.bind((self.host, self.port)) |
| |
| def addpackers(self): |
| # Override this to use derived classes from Packer/Unpacker |
| self.packer = Packer() |
| self.unpacker = Unpacker('') |
| |
| |
| class TCPServer(Server): |
| |
| def makesocket(self): |
| self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| self.prot = IPPROTO_TCP |
| |
| def loop(self): |
| self.sock.listen(0) |
| while 1: |
| self.session(self.sock.accept()) |
| |
| def session(self, connection): |
| sock, (host, port) = connection |
| while 1: |
| try: |
| call = recvrecord(sock) |
| except EOFError: |
| break |
| except socket.error as msg: |
| print 'socket error:', msg |
| break |
| reply = self.handle(call) |
| if reply is not None: |
| sendrecord(sock, reply) |
| |
| def forkingloop(self): |
| # Like loop but uses forksession() |
| self.sock.listen(0) |
| while 1: |
| self.forksession(self.sock.accept()) |
| |
| def forksession(self, connection): |
| # Like session but forks off a subprocess |
| import os |
| # Wait for deceased children |
| try: |
| while 1: |
| pid, sts = os.waitpid(0, 1) |
| except os.error: |
| pass |
| pid = None |
| try: |
| pid = os.fork() |
| if pid: # Parent |
| connection[0].close() |
| return |
| # Child |
| self.session(connection) |
| finally: |
| # Make sure we don't fall through in the parent |
| if pid == 0: |
| os._exit(0) |
| |
| |
| class UDPServer(Server): |
| |
| def makesocket(self): |
| self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
| self.prot = IPPROTO_UDP |
| |
| def loop(self): |
| while 1: |
| self.session() |
| |
| def session(self): |
| call, host_port = self.sock.recvfrom(8192) |
| reply = self.handle(call) |
| if reply != None: |
| self.sock.sendto(reply, host_port) |
| |
| |
| # Simple test program -- dump local portmapper status |
| |
| def test(): |
| pmap = UDPPortMapperClient('') |
| list = pmap.Dump() |
| list.sort() |
| for prog, vers, prot, port in list: |
| print prog, vers, |
| if prot == IPPROTO_TCP: print 'tcp', |
| elif prot == IPPROTO_UDP: print 'udp', |
| else: print prot, |
| print port |
| |
| |
| # Test program for broadcast operation -- dump everybody's portmapper status |
| |
| def testbcast(): |
| import sys |
| if sys.argv[1:]: |
| bcastaddr = sys.argv[1] |
| else: |
| bcastaddr = '<broadcast>' |
| def rh(reply, fromaddr): |
| host, port = fromaddr |
| print host + '\t' + repr(reply) |
| pmap = BroadcastUDPPortMapperClient(bcastaddr) |
| pmap.set_reply_handler(rh) |
| pmap.set_timeout(5) |
| replies = pmap.Getport((100002, 1, IPPROTO_UDP, 0)) |
| |
| |
| # Test program for server, with corresponding client |
| # On machine A: python -c 'import rpc; rpc.testsvr()' |
| # On machine B: python -c 'import rpc; rpc.testclt()' A |
| # (A may be == B) |
| |
| def testsvr(): |
| # Simple test class -- proc 1 doubles its string argument as reply |
| class S(UDPServer): |
| def handle_1(self): |
| arg = self.unpacker.unpack_string() |
| self.turn_around() |
| print 'RPC function 1 called, arg', repr(arg) |
| self.packer.pack_string(arg + arg) |
| # |
| s = S('', 0x20000000, 1, 0) |
| try: |
| s.unregister() |
| except RuntimeError as msg: |
| print 'RuntimeError:', msg, '(ignored)' |
| s.register() |
| print 'Service started...' |
| try: |
| s.loop() |
| finally: |
| s.unregister() |
| print 'Service interrupted.' |
| |
| |
| def testclt(): |
| import sys |
| if sys.argv[1:]: host = sys.argv[1] |
| else: host = '' |
| # Client for above server |
| class C(UDPClient): |
| def call_1(self, arg): |
| return self.make_call(1, arg, \ |
| self.packer.pack_string, \ |
| self.unpacker.unpack_string) |
| c = C(host, 0x20000000, 1) |
| print 'making call...' |
| reply = c.call_1('hello, world, ') |
| print 'call returned', repr(reply) |