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