1992-12-15 07:25:04 +08:00
|
|
|
# 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
|
|
|
|
|
|
|
|
|
1992-12-16 04:52:53 +08:00
|
|
|
# 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(), [])
|
|
|
|
|
|
|
|
|
1992-12-15 07:25:04 +08:00
|
|
|
# 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:
|
1992-12-16 04:52:53 +08:00
|
|
|
self.cred = (AUTH_NULL, make_auth_null())
|
|
|
|
return self.cred
|
1992-12-15 07:25:04 +08:00
|
|
|
|
|
|
|
def mkverf(self, proc):
|
1992-12-16 04:52:53 +08:00
|
|
|
if self.verf == None:
|
|
|
|
self.verf = (AUTH_NULL, make_auth_null())
|
|
|
|
return self.verf
|
1992-12-15 07:25:04 +08:00
|
|
|
|
|
|
|
|
|
|
|
# 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
|