blob: 4fbc795765138ec81ba7fb3ea2a617b367d7ac65 [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():
130 return make_auth_unix(0, socket.gethostname(), \
131 os.getuid(), os.getgid(), [])
132
133
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000134# Common base class for clients
135
136class Client:
137
138 def init(self, host, prog, vers, port, type):
139 self.host = host
140 self.prog = prog
141 self.vers = vers
142 self.port = port
143 self.type = type
144 self.sock = socket.socket(socket.AF_INET, type)
145 self.sock.connect((host, port))
146 self.lastxid = 0
147 self.addpackers()
148 self.cred = None
149 self.verf = None
150 return self
151
152 def Null(self): # Procedure 0 is always like this
153 self.start_call(0)
154 self.do_call(0)
155 self.end_call()
156
157 def close(self):
158 self.sock.close()
159
160 # Functions that may be overridden by specific derived classes
161
162 def addpackers(self):
163 self.packer = Packer().init()
164 self.unpacker = Unpacker().init('')
165
166 def mkcred(self, proc):
167 if self.cred == None:
Guido van Rossum749d0bb1992-12-15 20:52:53 +0000168 self.cred = (AUTH_NULL, make_auth_null())
169 return self.cred
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000170
171 def mkverf(self, proc):
Guido van Rossum749d0bb1992-12-15 20:52:53 +0000172 if self.verf == None:
173 self.verf = (AUTH_NULL, make_auth_null())
174 return self.verf
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000175
176
177# Record-Marking standard support
178
179def sendfrag(sock, last, frag):
180 x = len(frag)
181 if last: x = x | 0x80000000L
182 header = (chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \
183 chr(int(x>>8 & 0xff)) + chr(int(x & 0xff)))
184 sock.send(header + frag)
185
186def sendrecord(sock, record):
187 sendfrag(sock, 1, record)
188
189def recvfrag(sock):
190 header = sock.recv(4)
191 x = long(ord(header[0]))<<24 | ord(header[1])<<16 | \
192 ord(header[2])<<8 | ord(header[3])
193 last = ((x & 0x80000000) != 0)
194 n = int(x & 0x7fffffff)
195 frag = ''
196 while n > 0:
197 buf = sock.recv(n)
198 if not buf: raise EOFError
199 n = n - len(buf)
200 frag = frag + buf
201 return last, frag
202
203def recvrecord(sock):
204 record = ''
205 last = 0
206 while not last:
207 last, frag = recvfrag(sock)
208 record = record + frag
209 return record
210
211
212# Raw TCP-based client
213
214class RawTCPClient(Client):
215
216 def init(self, host, prog, vers, port):
217 return Client.init(self, host, prog, vers, port, \
218 socket.SOCK_STREAM)
219
220 def start_call(self, proc):
221 self.lastxid = xid = self.lastxid + 1
222 cred = self.mkcred(proc)
223 verf = self.mkverf(proc)
224 p = self.packer
225 p.reset()
226 p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
227
228 def do_call(self, *rest):
229 # rest is used for UDP buffer size; ignored for TCP
230 call = self.packer.get_buf()
231 sendrecord(self.sock, call)
232 reply = recvrecord(self.sock)
233 u = self.unpacker
234 u.reset(reply)
235 xid, verf = u.unpack_replyheader()
236 if xid <> self.lastxid:
237 # Can't really happen since this is TCP...
238 raise RuntimeError, 'wrong xid in reply ' + `xid` + \
239 ' instead of ' + `self.lastxid`
240
241 def end_call(self):
242 self.unpacker.done()
243
244
245# Raw UDP-based client
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000246
247class RawUDPClient(Client):
248
249 def init(self, host, prog, vers, port):
250 return Client.init(self, host, prog, vers, port, \
251 socket.SOCK_DGRAM)
252
253 def start_call(self, proc):
254 self.lastxid = xid = self.lastxid + 1
255 cred = self.mkcred(proc)
256 verf = self.mkverf(proc)
257 p = self.packer
258 p.reset()
259 p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
260
261 def do_call(self, *rest):
Guido van Rossum16b22191992-12-15 21:44:31 +0000262 from select import select
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000263 if len(rest) == 0:
264 bufsize = 8192
265 elif len(rest) > 1:
266 raise TypeError, 'too many args'
267 else:
268 bufsize = rest[0] + 512
269 call = self.packer.get_buf()
Guido van Rossum16b22191992-12-15 21:44:31 +0000270 timeout = 1
271 count = 5
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000272 self.sock.send(call)
Guido van Rossum16b22191992-12-15 21:44:31 +0000273 while 1:
274 r, w, x = select([self.sock], [], [], timeout)
275 if self.sock not in r:
276 count = count - 1
277 if count < 0: raise RuntimeError, 'timeout'
278 if timeout < 25: timeout = timeout *2
279 print 'RESEND', timeout, count
280 self.sock.send(call)
281 continue
282 reply = self.sock.recv(bufsize)
283 u = self.unpacker
284 u.reset(reply)
285 xid, verf = u.unpack_replyheader()
286 if xid <> self.lastxid:
287 print 'BAD xid'
288 continue
289 break
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000290
291 def end_call(self):
292 self.unpacker.done()
293
294
295# Port mapper interface
296
297PMAP_PORT = 111
298PMAP_PROG = 100000
299PMAP_VERS = 2
300PMAPPROC_NULL = 0 # (void) -> void
301PMAPPROC_SET = 1 # (mapping) -> bool
302PMAPPROC_UNSET = 2 # (mapping) -> bool
303PMAPPROC_GETPORT = 3 # (mapping) -> unsigned int
304PMAPPROC_DUMP = 4 # (void) -> pmaplist
305PMAPPROC_CALLIT = 5 # (call_args) -> call_result
306
307# A mapping is (prog, vers, prot, port) and prot is one of:
308
309IPPROTO_TCP = 6
310IPPROTO_UDP = 17
311
312# A pmaplist is a variable-length list of mappings, as follows:
313# either (1, mapping, pmaplist) or (0).
314
315# A call_args is (prog, vers, proc, args) where args is opaque;
316# a call_result is (port, res) where res is opaque.
317
318
319class PortMapperPacker(Packer):
320
321 def pack_mapping(self, mapping):
322 prog, vers, prot, port = mapping
323 self.pack_uint(prog)
324 self.pack_uint(vers)
325 self.pack_uint(prot)
326 self.pack_uint(port)
327
328 def pack_pmaplist(self, list):
329 self.pack_list(list, self.pack_mapping)
330
331
332class PortMapperUnpacker(Unpacker):
333
334 def unpack_mapping(self):
335 prog = self.unpack_uint()
336 vers = self.unpack_uint()
337 prot = self.unpack_uint()
338 port = self.unpack_uint()
339 return prog, vers, prot, port
340
341 def unpack_pmaplist(self):
342 return self.unpack_list(self.unpack_mapping)
343
344
345class PartialPortMapperClient:
346
347 def addpackers(self):
348 self.packer = PortMapperPacker().init()
349 self.unpacker = PortMapperUnpacker().init('')
350
351 def Getport(self, mapping):
352 self.start_call(PMAPPROC_GETPORT)
353 self.packer.pack_mapping(mapping)
354 self.do_call(4)
355 port = self.unpacker.unpack_uint()
356 self.end_call()
357 return port
358
359 def Dump(self):
360 self.start_call(PMAPPROC_DUMP)
361 self.do_call(8192-512)
362 list = self.unpacker.unpack_pmaplist()
363 self.end_call()
364 return list
365
366
367class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient):
368
369 def init(self, host):
370 return RawTCPClient.init(self, \
371 host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
372
373
374class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient):
375
376 def init(self, host):
377 return RawUDPClient.init(self, \
378 host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
379
380
381class TCPClient(RawTCPClient):
382
383 def init(self, host, prog, vers):
384 pmap = TCPPortMapperClient().init(host)
385 port = pmap.Getport((prog, vers, IPPROTO_TCP, 0))
386 pmap.close()
387 return RawTCPClient.init(self, host, prog, vers, port)
388
389
390class UDPClient(RawUDPClient):
391
392 def init(self, host, prog, vers):
393 pmap = UDPPortMapperClient().init(host)
394 port = pmap.Getport((prog, vers, IPPROTO_UDP, 0))
395 pmap.close()
396 return RawUDPClient.init(self, host, prog, vers, port)
397
398
399def test():
400 import T
401 T.TSTART()
402 pmap = UDPPortMapperClient().init('')
403 T.TSTOP()
404 pmap.Null()
405 T.TSTOP()
406 list = pmap.Dump()
407 T.TSTOP()
408 list.sort()
409 for prog, vers, prot, port in list:
410 print prog, vers,
411 if prot == IPPROTO_TCP: print 'tcp',
412 elif prot == IPPROTO_UDP: print 'udp',
413 else: print prot,
414 print port