blob: 607a25ef4a55cb760ec339e8827c6fbdbf99ada0 [file] [log] [blame]
Guido van Rossume3cafbe1992-12-14 23:25:04 +00001# Implement (a subset of) Sun RPC, version 2 -- RFC1057.
2
3import xdr
4import socket
5import os
6
7RPCVERSION = 2
8
9CALL = 0
10REPLY = 1
11
12AUTH_NULL = 0
13AUTH_UNIX = 1
14AUTH_SHORT = 2
15AUTH_DES = 3
16
17MSG_ACCEPTED = 0
18MSG_DENIED = 1
19
20SUCCESS = 0 # RPC executed successfully
21PROG_UNAVAIL = 1 # remote hasn't exported program
22PROG_MISMATCH = 2 # remote can't support version #
23PROC_UNAVAIL = 3 # program can't support procedure
24GARBAGE_ARGS = 4 # procedure can't decode params
25
26RPC_MISMATCH = 0 # RPC version number != 2
27AUTH_ERROR = 1 # remote can't authenticate caller
28
29AUTH_BADCRED = 1 # bad credentials (seal broken)
30AUTH_REJECTEDCRED = 2 # client must begin new session
31AUTH_BADVERF = 3 # bad verifier (seal broken)
32AUTH_REJECTEDVERF = 4 # verifier expired or replayed
33AUTH_TOOWEAK = 5 # rejected for security reasons
34
35
36class Packer(xdr.Packer):
37
38 def pack_auth(self, auth):
39 flavor, stuff = auth
40 self.pack_enum(flavor)
41 self.pack_opaque(stuff)
42
43 def pack_auth_unix(self, stamp, machinename, uid, gid, gids):
44 self.pack_uint(stamp)
45 self.pack_string(machinename)
46 self.pack_uint(uid)
47 self.pack_uint(gid)
48 self.pack_uint(len(gids))
49 for i in gids:
50 self.pack_uint(i)
51
52 def pack_callheader(self, xid, prog, vers, proc, cred, verf):
53 self.pack_uint(xid)
54 self.pack_enum(CALL)
55 self.pack_uint(RPCVERSION)
56 self.pack_uint(prog)
57 self.pack_uint(vers)
58 self.pack_uint(proc)
59 self.pack_auth(cred)
60 self.pack_auth(verf)
61 # Caller must add procedure-specific part of call
62
63 def pack_replyheader(self, xid, verf):
64 self.pack_uint(xid)
65 self.pack_enum(REPLY)
66 self.pack_uint(MSG_ACCEPTED)
67 self.pack_auth(verf)
68 self.pack_enum(SUCCESS)
69 # Caller must add procedure-specific part of reply
70
71
72class Unpacker(xdr.Unpacker):
73
74 def unpack_auth(self):
75 flavor = self.unpack_enum()
76 stuff = self.unpack_opaque()
77 return (flavor, stuff)
78
79 def unpack_replyheader(self):
80 xid = self.unpack_uint()
81 mtype = self.unpack_enum()
82 if mtype <> REPLY:
83 raise RuntimeError, 'no REPLY but ' + str(mtype)
84 stat = self.unpack_enum()
85 if stat <> MSG_ACCEPTED:
86 if stat == MSG_DENIED:
87 stat = self.unpack_enum()
88 if stat == RPC_MISMATCH:
89 low = self.unpack_uint()
90 high = self.unpack_uint()
91 raise 'RPC_MISMATCH', (low, high)
92 if stat == AUTH_ERROR:
93 stat = self.unpack_uint()
94 raise 'AUTH_ERROR', str(stat)
95 raise 'MSG_REJECTED', str(stat)
96 raise RuntimeError, 'no MSG_ACCEPTED but ' + str(stat)
97 verf = self.unpack_auth()
98 stat = self.unpack_enum()
99 if stat <> SUCCESS:
100 raise RuntimeError, 'no SUCCESS but ' + str(stat)
101 return xid, verf
102 # Caller must get procedure-specific part of reply
103
104
Guido van Rossum749d0bb1992-12-15 20:52:53 +0000105# Subroutines to create opaque authentication objects
106
107def make_auth_null():
108 return ''
109
110def make_auth_unix(seed, host, uid, gid, groups):
111 p = Packer().init()
112 p.pack_auth_unix(seed, host, uid, gid, groups)
113 return p.get_buf()
114
115def make_auth_unix_default():
116 return make_auth_unix(0, socket.gethostname(), \
117 os.getuid(), os.getgid(), [])
118
119
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000120# Common base class for clients
121
122class Client:
123
124 def init(self, host, prog, vers, port, type):
125 self.host = host
126 self.prog = prog
127 self.vers = vers
128 self.port = port
129 self.type = type
130 self.sock = socket.socket(socket.AF_INET, type)
131 self.sock.connect((host, port))
132 self.lastxid = 0
133 self.addpackers()
134 self.cred = None
135 self.verf = None
136 return self
137
138 def Null(self): # Procedure 0 is always like this
139 self.start_call(0)
140 self.do_call(0)
141 self.end_call()
142
143 def close(self):
144 self.sock.close()
145
146 # Functions that may be overridden by specific derived classes
147
148 def addpackers(self):
149 self.packer = Packer().init()
150 self.unpacker = Unpacker().init('')
151
152 def mkcred(self, proc):
153 if self.cred == None:
Guido van Rossum749d0bb1992-12-15 20:52:53 +0000154 self.cred = (AUTH_NULL, make_auth_null())
155 return self.cred
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000156
157 def mkverf(self, proc):
Guido van Rossum749d0bb1992-12-15 20:52:53 +0000158 if self.verf == None:
159 self.verf = (AUTH_NULL, make_auth_null())
160 return self.verf
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000161
162
163# Record-Marking standard support
164
165def sendfrag(sock, last, frag):
166 x = len(frag)
167 if last: x = x | 0x80000000L
168 header = (chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \
169 chr(int(x>>8 & 0xff)) + chr(int(x & 0xff)))
170 sock.send(header + frag)
171
172def sendrecord(sock, record):
173 sendfrag(sock, 1, record)
174
175def recvfrag(sock):
176 header = sock.recv(4)
177 x = long(ord(header[0]))<<24 | ord(header[1])<<16 | \
178 ord(header[2])<<8 | ord(header[3])
179 last = ((x & 0x80000000) != 0)
180 n = int(x & 0x7fffffff)
181 frag = ''
182 while n > 0:
183 buf = sock.recv(n)
184 if not buf: raise EOFError
185 n = n - len(buf)
186 frag = frag + buf
187 return last, frag
188
189def recvrecord(sock):
190 record = ''
191 last = 0
192 while not last:
193 last, frag = recvfrag(sock)
194 record = record + frag
195 return record
196
197
198# Raw TCP-based client
199
200class RawTCPClient(Client):
201
202 def init(self, host, prog, vers, port):
203 return Client.init(self, host, prog, vers, port, \
204 socket.SOCK_STREAM)
205
206 def start_call(self, proc):
207 self.lastxid = xid = self.lastxid + 1
208 cred = self.mkcred(proc)
209 verf = self.mkverf(proc)
210 p = self.packer
211 p.reset()
212 p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
213
214 def do_call(self, *rest):
215 # rest is used for UDP buffer size; ignored for TCP
216 call = self.packer.get_buf()
217 sendrecord(self.sock, call)
218 reply = recvrecord(self.sock)
219 u = self.unpacker
220 u.reset(reply)
221 xid, verf = u.unpack_replyheader()
222 if xid <> self.lastxid:
223 # Can't really happen since this is TCP...
224 raise RuntimeError, 'wrong xid in reply ' + `xid` + \
225 ' instead of ' + `self.lastxid`
226
227 def end_call(self):
228 self.unpacker.done()
229
230
231# Raw UDP-based client
232# XXX This class does not recover from missed/duplicated packets!
233
234class RawUDPClient(Client):
235
236 def init(self, host, prog, vers, port):
237 return Client.init(self, host, prog, vers, port, \
238 socket.SOCK_DGRAM)
239
240 def start_call(self, proc):
241 self.lastxid = xid = self.lastxid + 1
242 cred = self.mkcred(proc)
243 verf = self.mkverf(proc)
244 p = self.packer
245 p.reset()
246 p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
247
248 def do_call(self, *rest):
249 if len(rest) == 0:
250 bufsize = 8192
251 elif len(rest) > 1:
252 raise TypeError, 'too many args'
253 else:
254 bufsize = rest[0] + 512
255 call = self.packer.get_buf()
256 self.sock.send(call)
257 # XXX What about time-out and retry?
258 reply = self.sock.recv(bufsize)
259 u = self.unpacker
260 u.reset(reply)
261 xid, verf = u.unpack_replyheader()
262 if xid <> self.lastxid:
263 # XXX Should assume it's an old reply
264 raise RuntimeError, 'wrong xid in reply ' + `xid` + \
265 ' instead of ' + `self.lastxid`
266
267 def end_call(self):
268 self.unpacker.done()
269
270
271# Port mapper interface
272
273PMAP_PORT = 111
274PMAP_PROG = 100000
275PMAP_VERS = 2
276PMAPPROC_NULL = 0 # (void) -> void
277PMAPPROC_SET = 1 # (mapping) -> bool
278PMAPPROC_UNSET = 2 # (mapping) -> bool
279PMAPPROC_GETPORT = 3 # (mapping) -> unsigned int
280PMAPPROC_DUMP = 4 # (void) -> pmaplist
281PMAPPROC_CALLIT = 5 # (call_args) -> call_result
282
283# A mapping is (prog, vers, prot, port) and prot is one of:
284
285IPPROTO_TCP = 6
286IPPROTO_UDP = 17
287
288# A pmaplist is a variable-length list of mappings, as follows:
289# either (1, mapping, pmaplist) or (0).
290
291# A call_args is (prog, vers, proc, args) where args is opaque;
292# a call_result is (port, res) where res is opaque.
293
294
295class PortMapperPacker(Packer):
296
297 def pack_mapping(self, mapping):
298 prog, vers, prot, port = mapping
299 self.pack_uint(prog)
300 self.pack_uint(vers)
301 self.pack_uint(prot)
302 self.pack_uint(port)
303
304 def pack_pmaplist(self, list):
305 self.pack_list(list, self.pack_mapping)
306
307
308class PortMapperUnpacker(Unpacker):
309
310 def unpack_mapping(self):
311 prog = self.unpack_uint()
312 vers = self.unpack_uint()
313 prot = self.unpack_uint()
314 port = self.unpack_uint()
315 return prog, vers, prot, port
316
317 def unpack_pmaplist(self):
318 return self.unpack_list(self.unpack_mapping)
319
320
321class PartialPortMapperClient:
322
323 def addpackers(self):
324 self.packer = PortMapperPacker().init()
325 self.unpacker = PortMapperUnpacker().init('')
326
327 def Getport(self, mapping):
328 self.start_call(PMAPPROC_GETPORT)
329 self.packer.pack_mapping(mapping)
330 self.do_call(4)
331 port = self.unpacker.unpack_uint()
332 self.end_call()
333 return port
334
335 def Dump(self):
336 self.start_call(PMAPPROC_DUMP)
337 self.do_call(8192-512)
338 list = self.unpacker.unpack_pmaplist()
339 self.end_call()
340 return list
341
342
343class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient):
344
345 def init(self, host):
346 return RawTCPClient.init(self, \
347 host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
348
349
350class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient):
351
352 def init(self, host):
353 return RawUDPClient.init(self, \
354 host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
355
356
357class TCPClient(RawTCPClient):
358
359 def init(self, host, prog, vers):
360 pmap = TCPPortMapperClient().init(host)
361 port = pmap.Getport((prog, vers, IPPROTO_TCP, 0))
362 pmap.close()
363 return RawTCPClient.init(self, host, prog, vers, port)
364
365
366class UDPClient(RawUDPClient):
367
368 def init(self, host, prog, vers):
369 pmap = UDPPortMapperClient().init(host)
370 port = pmap.Getport((prog, vers, IPPROTO_UDP, 0))
371 pmap.close()
372 return RawUDPClient.init(self, host, prog, vers, port)
373
374
375def test():
376 import T
377 T.TSTART()
378 pmap = UDPPortMapperClient().init('')
379 T.TSTOP()
380 pmap.Null()
381 T.TSTOP()
382 list = pmap.Dump()
383 T.TSTOP()
384 list.sort()
385 for prog, vers, prot, port in list:
386 print prog, vers,
387 if prot == IPPROTO_TCP: print 'tcp',
388 elif prot == IPPROTO_UDP: print 'udp',
389 else: print prot,
390 print port