cpython/Demo/rpc/rpc.py
1992-12-15 20:52:53 +00:00

391 lines
9.2 KiB
Python

# Implement (a subset of) Sun RPC, version 2 -- RFC1057.
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
class Unpacker(xdr.Unpacker):
def unpack_auth(self):
flavor = self.unpack_enum()
stuff = self.unpack_opaque()
return (flavor, stuff)
def unpack_replyheader(self):
xid = self.unpack_uint()
mtype = self.unpack_enum()
if mtype <> REPLY:
raise RuntimeError, 'no REPLY but ' + str(mtype)
stat = self.unpack_enum()
if stat <> MSG_ACCEPTED:
if stat == MSG_DENIED:
stat = self.unpack_enum()
if stat == RPC_MISMATCH:
low = self.unpack_uint()
high = self.unpack_uint()
raise 'RPC_MISMATCH', (low, high)
if stat == AUTH_ERROR:
stat = self.unpack_uint()
raise 'AUTH_ERROR', str(stat)
raise 'MSG_REJECTED', str(stat)
raise RuntimeError, 'no MSG_ACCEPTED but ' + str(stat)
verf = self.unpack_auth()
stat = self.unpack_enum()
if stat <> SUCCESS:
raise RuntimeError, 'no SUCCESS but ' + str(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().init()
p.pack_auth_unix(seed, host, uid, gid, groups)
return p.get_buf()
def make_auth_unix_default():
return make_auth_unix(0, socket.gethostname(), \
os.getuid(), os.getgid(), [])
# Common base class for clients
class Client:
def init(self, host, prog, vers, port, type):
self.host = host
self.prog = prog
self.vers = vers
self.port = port
self.type = type
self.sock = socket.socket(socket.AF_INET, type)
self.sock.connect((host, port))
self.lastxid = 0
self.addpackers()
self.cred = None
self.verf = None
return self
def Null(self): # Procedure 0 is always like this
self.start_call(0)
self.do_call(0)
self.end_call()
def close(self):
self.sock.close()
# Functions that may be overridden by specific derived classes
def addpackers(self):
self.packer = Packer().init()
self.unpacker = Unpacker().init('')
def mkcred(self, proc):
if self.cred == None:
self.cred = (AUTH_NULL, make_auth_null())
return self.cred
def mkverf(self, proc):
if self.verf == None:
self.verf = (AUTH_NULL, make_auth_null())
return self.verf
# 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)
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
# Raw TCP-based client
class RawTCPClient(Client):
def init(self, host, prog, vers, port):
return Client.init(self, host, prog, vers, port, \
socket.SOCK_STREAM)
def start_call(self, proc):
self.lastxid = xid = self.lastxid + 1
cred = self.mkcred(proc)
verf = self.mkverf(proc)
p = self.packer
p.reset()
p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
def do_call(self, *rest):
# rest is used for UDP buffer size; ignored for TCP
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 ' + `xid` + \
' instead of ' + `self.lastxid`
def end_call(self):
self.unpacker.done()
# Raw UDP-based client
# XXX This class does not recover from missed/duplicated packets!
class RawUDPClient(Client):
def init(self, host, prog, vers, port):
return Client.init(self, host, prog, vers, port, \
socket.SOCK_DGRAM)
def start_call(self, proc):
self.lastxid = xid = self.lastxid + 1
cred = self.mkcred(proc)
verf = self.mkverf(proc)
p = self.packer
p.reset()
p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
def do_call(self, *rest):
if len(rest) == 0:
bufsize = 8192
elif len(rest) > 1:
raise TypeError, 'too many args'
else:
bufsize = rest[0] + 512
call = self.packer.get_buf()
self.sock.send(call)
# XXX What about time-out and retry?
reply = self.sock.recv(bufsize)
u = self.unpacker
u.reset(reply)
xid, verf = u.unpack_replyheader()
if xid <> self.lastxid:
# XXX Should assume it's an old reply
raise RuntimeError, 'wrong xid in reply ' + `xid` + \
' instead of ' + `self.lastxid`
def end_call(self):
self.unpacker.done()
# Port mapper interface
PMAP_PORT = 111
PMAP_PROG = 100000
PMAP_VERS = 2
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)
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)
class PartialPortMapperClient:
def addpackers(self):
self.packer = PortMapperPacker().init()
self.unpacker = PortMapperUnpacker().init('')
def Getport(self, mapping):
self.start_call(PMAPPROC_GETPORT)
self.packer.pack_mapping(mapping)
self.do_call(4)
port = self.unpacker.unpack_uint()
self.end_call()
return port
def Dump(self):
self.start_call(PMAPPROC_DUMP)
self.do_call(8192-512)
list = self.unpacker.unpack_pmaplist()
self.end_call()
return list
class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient):
def init(self, host):
return RawTCPClient.init(self, \
host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient):
def init(self, host):
return RawUDPClient.init(self, \
host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
class TCPClient(RawTCPClient):
def init(self, host, prog, vers):
pmap = TCPPortMapperClient().init(host)
port = pmap.Getport((prog, vers, IPPROTO_TCP, 0))
pmap.close()
return RawTCPClient.init(self, host, prog, vers, port)
class UDPClient(RawUDPClient):
def init(self, host, prog, vers):
pmap = UDPPortMapperClient().init(host)
port = pmap.Getport((prog, vers, IPPROTO_UDP, 0))
pmap.close()
return RawUDPClient.init(self, host, prog, vers, port)
def test():
import T
T.TSTART()
pmap = UDPPortMapperClient().init('')
T.TSTOP()
pmap.Null()
T.TSTOP()
list = pmap.Dump()
T.TSTOP()
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