blob: 6d3fade19918b23e2a869757d65e2d5035462965 [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):
Guido van Rossum16b22191992-12-15 21:44:31 +0000249 from select import select
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000250 if len(rest) == 0:
251 bufsize = 8192
252 elif len(rest) > 1:
253 raise TypeError, 'too many args'
254 else:
255 bufsize = rest[0] + 512
256 call = self.packer.get_buf()
Guido van Rossum16b22191992-12-15 21:44:31 +0000257 timeout = 1
258 count = 5
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000259 self.sock.send(call)
Guido van Rossum16b22191992-12-15 21:44:31 +0000260 while 1:
261 r, w, x = select([self.sock], [], [], timeout)
262 if self.sock not in r:
263 count = count - 1
264 if count < 0: raise RuntimeError, 'timeout'
265 if timeout < 25: timeout = timeout *2
266 print 'RESEND', timeout, count
267 self.sock.send(call)
268 continue
269 reply = self.sock.recv(bufsize)
270 u = self.unpacker
271 u.reset(reply)
272 xid, verf = u.unpack_replyheader()
273 if xid <> self.lastxid:
274 print 'BAD xid'
275 continue
276 break
Guido van Rossume3cafbe1992-12-14 23:25:04 +0000277
278 def end_call(self):
279 self.unpacker.done()
280
281
282# Port mapper interface
283
284PMAP_PORT = 111
285PMAP_PROG = 100000
286PMAP_VERS = 2
287PMAPPROC_NULL = 0 # (void) -> void
288PMAPPROC_SET = 1 # (mapping) -> bool
289PMAPPROC_UNSET = 2 # (mapping) -> bool
290PMAPPROC_GETPORT = 3 # (mapping) -> unsigned int
291PMAPPROC_DUMP = 4 # (void) -> pmaplist
292PMAPPROC_CALLIT = 5 # (call_args) -> call_result
293
294# A mapping is (prog, vers, prot, port) and prot is one of:
295
296IPPROTO_TCP = 6
297IPPROTO_UDP = 17
298
299# A pmaplist is a variable-length list of mappings, as follows:
300# either (1, mapping, pmaplist) or (0).
301
302# A call_args is (prog, vers, proc, args) where args is opaque;
303# a call_result is (port, res) where res is opaque.
304
305
306class PortMapperPacker(Packer):
307
308 def pack_mapping(self, mapping):
309 prog, vers, prot, port = mapping
310 self.pack_uint(prog)
311 self.pack_uint(vers)
312 self.pack_uint(prot)
313 self.pack_uint(port)
314
315 def pack_pmaplist(self, list):
316 self.pack_list(list, self.pack_mapping)
317
318
319class PortMapperUnpacker(Unpacker):
320
321 def unpack_mapping(self):
322 prog = self.unpack_uint()
323 vers = self.unpack_uint()
324 prot = self.unpack_uint()
325 port = self.unpack_uint()
326 return prog, vers, prot, port
327
328 def unpack_pmaplist(self):
329 return self.unpack_list(self.unpack_mapping)
330
331
332class PartialPortMapperClient:
333
334 def addpackers(self):
335 self.packer = PortMapperPacker().init()
336 self.unpacker = PortMapperUnpacker().init('')
337
338 def Getport(self, mapping):
339 self.start_call(PMAPPROC_GETPORT)
340 self.packer.pack_mapping(mapping)
341 self.do_call(4)
342 port = self.unpacker.unpack_uint()
343 self.end_call()
344 return port
345
346 def Dump(self):
347 self.start_call(PMAPPROC_DUMP)
348 self.do_call(8192-512)
349 list = self.unpacker.unpack_pmaplist()
350 self.end_call()
351 return list
352
353
354class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient):
355
356 def init(self, host):
357 return RawTCPClient.init(self, \
358 host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
359
360
361class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient):
362
363 def init(self, host):
364 return RawUDPClient.init(self, \
365 host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
366
367
368class TCPClient(RawTCPClient):
369
370 def init(self, host, prog, vers):
371 pmap = TCPPortMapperClient().init(host)
372 port = pmap.Getport((prog, vers, IPPROTO_TCP, 0))
373 pmap.close()
374 return RawTCPClient.init(self, host, prog, vers, port)
375
376
377class UDPClient(RawUDPClient):
378
379 def init(self, host, prog, vers):
380 pmap = UDPPortMapperClient().init(host)
381 port = pmap.Getport((prog, vers, IPPROTO_UDP, 0))
382 pmap.close()
383 return RawUDPClient.init(self, host, prog, vers, port)
384
385
386def test():
387 import T
388 T.TSTART()
389 pmap = UDPPortMapperClient().init('')
390 T.TSTOP()
391 pmap.Null()
392 T.TSTOP()
393 list = pmap.Dump()
394 T.TSTOP()
395 list.sort()
396 for prog, vers, prot, port in list:
397 print prog, vers,
398 if prot == IPPROTO_TCP: print 'tcp',
399 elif prot == IPPROTO_UDP: print 'udp',
400 else: print prot,
401 print port