1 # Sun RPC version 2 -- RFC1057.
3 # XXX There should be separate exceptions for the various reasons why
4 # XXX an RPC can fail, rather than using RuntimeError for everything
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!
9 # XXX There is no provision for call timeout on TCP connections
28 SUCCESS
= 0 # RPC executed successfully
29 PROG_UNAVAIL
= 1 # remote hasn't exported program
30 PROG_MISMATCH
= 2 # remote can't support version #
31 PROC_UNAVAIL
= 3 # program can't support procedure
32 GARBAGE_ARGS
= 4 # procedure can't decode params
34 RPC_MISMATCH
= 0 # RPC version number != 2
35 AUTH_ERROR
= 1 # remote can't authenticate caller
37 AUTH_BADCRED
= 1 # bad credentials (seal broken)
38 AUTH_REJECTEDCRED
= 2 # client must begin new session
39 AUTH_BADVERF
= 3 # bad verifier (seal broken)
40 AUTH_REJECTEDVERF
= 4 # verifier expired or replayed
41 AUTH_TOOWEAK
= 5 # rejected for security reasons
44 class Packer(xdr
.Packer
):
46 def pack_auth(self
, auth
):
48 self
.pack_enum(flavor
)
49 self
.pack_opaque(stuff
)
51 def pack_auth_unix(self
, stamp
, machinename
, uid
, gid
, gids
):
53 self
.pack_string(machinename
)
56 self
.pack_uint(len(gids
))
60 def pack_callheader(self
, xid
, prog
, vers
, proc
, cred
, verf
):
63 self
.pack_uint(RPCVERSION
)
69 # Caller must add procedure-specific part of call
71 def pack_replyheader(self
, xid
, verf
):
74 self
.pack_uint(MSG_ACCEPTED
)
76 self
.pack_enum(SUCCESS
)
77 # Caller must add procedure-specific part of reply
81 BadRPCFormat
= 'rpc.BadRPCFormat'
82 BadRPCVersion
= 'rpc.BadRPCVersion'
83 GarbageArgs
= 'rpc.GarbageArgs'
85 class Unpacker(xdr
.Unpacker
):
87 def unpack_auth(self
):
88 flavor
= self
.unpack_enum()
89 stuff
= self
.unpack_opaque()
90 return (flavor
, stuff
)
92 def unpack_callheader(self
):
93 xid
= self
.unpack_uint(xid
)
94 temp
= self
.unpack_enum()
96 raise BadRPCFormat
, 'no CALL but ' + `temp`
97 temp
= self
.unpack_uint()
98 if temp
<> RPCVERSION
:
99 raise BadRPCVerspion
, 'bad RPC version ' + `temp`
100 prog
= self
.unpack_uint()
101 vers
= self
.unpack_uint()
102 proc
= self
.unpack_uint()
103 cred
= self
.unpack_auth()
104 verf
= self
.unpack_auth()
105 return xid
, prog
, vers
, proc
, cred
, verf
106 # Caller must add procedure-specific part of call
108 def unpack_replyheader(self
):
109 xid
= self
.unpack_uint()
110 mtype
= self
.unpack_enum()
112 raise RuntimeError, 'no REPLY but ' + `mtype`
113 stat
= self
.unpack_enum()
114 if stat
== MSG_DENIED
:
115 stat
= self
.unpack_enum()
116 if stat
== RPC_MISMATCH
:
117 low
= self
.unpack_uint()
118 high
= self
.unpack_uint()
119 raise RuntimeError, \
120 'MSG_DENIED: RPC_MISMATCH: ' + `low
, high`
121 if stat
== AUTH_ERROR
:
122 stat
= self
.unpack_uint()
123 raise RuntimeError, \
124 'MSG_DENIED: AUTH_ERROR: ' + `stat`
125 raise RuntimeError, 'MSG_DENIED: ' + `stat`
126 if stat
<> MSG_ACCEPTED
:
127 raise RuntimeError, \
128 'Neither MSG_DENIED nor MSG_ACCEPTED: ' + `stat`
129 verf
= self
.unpack_auth()
130 stat
= self
.unpack_enum()
131 if stat
== PROG_UNAVAIL
:
132 raise RuntimeError, 'call failed: PROG_UNAVAIL'
133 if stat
== PROG_MISMATCH
:
134 low
= self
.unpack_uint()
135 high
= self
.unpack_uint()
136 raise RuntimeError, \
137 'call failed: PROG_MISMATCH: ' + `low
, high`
138 if stat
== PROC_UNAVAIL
:
139 raise RuntimeError, 'call failed: PROC_UNAVAIL'
140 if stat
== GARBAGE_ARGS
:
141 raise RuntimeError, 'call failed: GARBAGE_ARGS'
143 raise RuntimeError, 'call failed: ' + `stat`
145 # Caller must get procedure-specific part of reply
148 # Subroutines to create opaque authentication objects
150 def make_auth_null():
153 def make_auth_unix(seed
, host
, uid
, gid
, groups
):
155 p
.pack_auth_unix(seed
, host
, uid
, gid
, groups
)
158 def make_auth_unix_default():
160 from os
import getuid
, getgid
166 return make_auth_unix(int(time
.time()), \
167 socket
.gethostname(), uid
, gid
, [])
170 # Common base class for clients
174 def __init__(self
, host
, prog
, vers
, port
):
179 self
.makesocket() # Assigns to self.sock
182 self
.lastxid
= 0 # XXX should be more random?
190 def makesocket(self
):
191 # This MUST be overridden
192 raise RuntimeError, 'makesocket not defined'
194 def connsocket(self
):
195 # Override this if you don't want/need a connection
196 self
.sock
.connect((self
.host
, self
.port
))
198 def bindsocket(self
):
199 # Override this to bind to a different port (e.g. reserved)
200 self
.sock
.bind(('', 0))
202 def addpackers(self
):
203 # Override this to use derived classes from Packer/Unpacker
204 self
.packer
= Packer()
205 self
.unpacker
= Unpacker('')
207 def make_call(self
, proc
, args
, pack_func
, unpack_func
):
208 # Don't normally override this (but see Broadcast)
209 if pack_func
is None and args
is not None:
210 raise TypeError, 'non-null args with null pack_func'
211 self
.start_call(proc
)
216 result
= unpack_func()
222 def start_call(self
, proc
):
223 # Don't override this
224 self
.lastxid
= xid
= self
.lastxid
+ 1
229 p
.pack_callheader(xid
, self
.prog
, self
.vers
, proc
, cred
, verf
)
232 # This MUST be overridden
233 raise RuntimeError, 'do_call not defined'
236 # Override this to use more powerful credentials
237 if self
.cred
== None:
238 self
.cred
= (AUTH_NULL
, make_auth_null())
242 # Override this to use a more powerful verifier
243 if self
.verf
== None:
244 self
.verf
= (AUTH_NULL
, make_auth_null())
247 def call_0(self
): # Procedure 0 is always like this
248 return self
.make_call(0, None, None, None)
251 # Record-Marking standard support
253 def sendfrag(sock
, last
, frag
):
255 if last
: x
= x |
0x80000000L
256 header
= (chr(int(x
>>24 & 0xff)) + chr(int(x
>>16 & 0xff)) + \
257 chr(int(x
>>8 & 0xff)) + chr(int(x
& 0xff)))
258 sock
.send(header
+ frag
)
260 def sendrecord(sock
, record
):
261 sendfrag(sock
, 1, record
)
264 header
= sock
.recv(4)
267 x
= long(ord(header
[0]))<<24 |
ord(header
[1])<<16 | \
268 ord(header
[2])<<8 |
ord(header
[3])
269 last
= ((x
& 0x80000000) != 0)
270 n
= int(x
& 0x7fffffff)
274 if not buf
: raise EOFError
279 def recvrecord(sock
):
283 last
, frag
= recvfrag(sock
)
284 record
= record
+ frag
288 # Try to bind to a reserved port (must be root)
290 last_resv_port_tried
= None
291 def bindresvport(sock
, host
):
292 global last_resv_port_tried
293 FIRST
, LAST
= 600, 1024 # Range of ports to try
294 if last_resv_port_tried
== None:
296 last_resv_port_tried
= FIRST
+ os
.getpid() % (LAST
-FIRST
)
297 for i
in range(last_resv_port_tried
, LAST
) + \
298 range(FIRST
, last_resv_port_tried
):
299 last_resv_port_tried
= i
302 return last_resv_port_tried
303 except socket
.error
, (errno
, msg
):
305 raise socket
.error
, (errno
, msg
)
306 raise RuntimeError, 'can\'t assign reserved port'
309 # Client using TCP to a specific port
311 class RawTCPClient(Client
):
313 def makesocket(self
):
314 self
.sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
317 call
= self
.packer
.get_buf()
318 sendrecord(self
.sock
, call
)
319 reply
= recvrecord(self
.sock
)
322 xid
, verf
= u
.unpack_replyheader()
323 if xid
<> self
.lastxid
:
324 # Can't really happen since this is TCP...
325 raise RuntimeError, 'wrong xid in reply ' + `xid`
+ \
326 ' instead of ' + `self
.lastxid`
329 # Client using UDP to a specific port
331 class RawUDPClient(Client
):
333 def makesocket(self
):
334 self
.sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
)
337 call
= self
.packer
.get_buf()
340 from select
import select
342 print 'WARNING: select not found, RPC may hang'
344 BUFSIZE
= 8192 # Max UDP buffer size
348 r
, w
, x
= [self
.sock
], [], []
350 r
, w
, x
= select(r
, w
, x
, timeout
)
351 if self
.sock
not in r
:
353 if count
< 0: raise RuntimeError, 'timeout'
354 if timeout
< 25: timeout
= timeout
*2
355 ## print 'RESEND', timeout, count
358 reply
= self
.sock
.recv(BUFSIZE
)
361 xid
, verf
= u
.unpack_replyheader()
362 if xid
<> self
.lastxid
:
368 # Client using UDP broadcast to a specific port
370 class RawBroadcastUDPClient(RawUDPClient
):
372 def __init__(self
, bcastaddr
, prog
, vers
, port
):
373 RawUDPClient
.__init
__(self
, bcastaddr
, prog
, vers
, port
)
374 self
.reply_handler
= None
377 def connsocket(self
):
378 # Don't connect -- use sendto
379 self
.sock
.allowbroadcast(1)
381 def set_reply_handler(self
, reply_handler
):
382 self
.reply_handler
= reply_handler
384 def set_timeout(self
, timeout
):
385 self
.timeout
= timeout
# Use None for infinite timeout
387 def make_call(self
, proc
, args
, pack_func
, unpack_func
):
388 if pack_func
is None and args
is not None:
389 raise TypeError, 'non-null args with null pack_func'
390 self
.start_call(proc
)
393 call
= self
.packer
.get_buf()
394 self
.sock
.sendto(call
, (self
.host
, self
.port
))
396 from select
import select
398 print 'WARNING: select not found, broadcast will hang'
400 BUFSIZE
= 8192 # Max UDP buffer size (for reply)
402 if unpack_func
is None:
406 r
, w
, x
= [self
.sock
], [], []
408 if self
.timeout
is None:
409 r
, w
, x
= select(r
, w
, x
)
411 r
, w
, x
= select(r
, w
, x
, self
.timeout
)
412 if self
.sock
not in r
:
414 reply
, fromaddr
= self
.sock
.recvfrom(BUFSIZE
)
417 xid
, verf
= u
.unpack_replyheader()
418 if xid
<> self
.lastxid
:
421 reply
= unpack_func()
423 replies
.append((reply
, fromaddr
))
424 if self
.reply_handler
:
425 self
.reply_handler(reply
, fromaddr
)
429 # Port mapper interface
431 # Program number, version and (fixed!) port number
437 PMAPPROC_NULL
= 0 # (void) -> void
438 PMAPPROC_SET
= 1 # (mapping) -> bool
439 PMAPPROC_UNSET
= 2 # (mapping) -> bool
440 PMAPPROC_GETPORT
= 3 # (mapping) -> unsigned int
441 PMAPPROC_DUMP
= 4 # (void) -> pmaplist
442 PMAPPROC_CALLIT
= 5 # (call_args) -> call_result
444 # A mapping is (prog, vers, prot, port) and prot is one of:
449 # A pmaplist is a variable-length list of mappings, as follows:
450 # either (1, mapping, pmaplist) or (0).
452 # A call_args is (prog, vers, proc, args) where args is opaque;
453 # a call_result is (port, res) where res is opaque.
456 class PortMapperPacker(Packer
):
458 def pack_mapping(self
, mapping
):
459 prog
, vers
, prot
, port
= mapping
465 def pack_pmaplist(self
, list):
466 self
.pack_list(list, self
.pack_mapping
)
468 def pack_call_args(self
, ca
):
469 prog
, vers
, proc
, args
= ca
473 self
.pack_opaque(args
)
476 class PortMapperUnpacker(Unpacker
):
478 def unpack_mapping(self
):
479 prog
= self
.unpack_uint()
480 vers
= self
.unpack_uint()
481 prot
= self
.unpack_uint()
482 port
= self
.unpack_uint()
483 return prog
, vers
, prot
, port
485 def unpack_pmaplist(self
):
486 return self
.unpack_list(self
.unpack_mapping
)
488 def unpack_call_result(self
):
489 port
= self
.unpack_uint()
490 res
= self
.unpack_opaque()
494 class PartialPortMapperClient
:
496 def addpackers(self
):
497 self
.packer
= PortMapperPacker()
498 self
.unpacker
= PortMapperUnpacker('')
500 def Set(self
, mapping
):
501 return self
.make_call(PMAPPROC_SET
, mapping
, \
502 self
.packer
.pack_mapping
, \
503 self
.unpacker
.unpack_uint
)
505 def Unset(self
, mapping
):
506 return self
.make_call(PMAPPROC_UNSET
, mapping
, \
507 self
.packer
.pack_mapping
, \
508 self
.unpacker
.unpack_uint
)
510 def Getport(self
, mapping
):
511 return self
.make_call(PMAPPROC_GETPORT
, mapping
, \
512 self
.packer
.pack_mapping
, \
513 self
.unpacker
.unpack_uint
)
516 return self
.make_call(PMAPPROC_DUMP
, None, \
518 self
.unpacker
.unpack_pmaplist
)
520 def Callit(self
, ca
):
521 return self
.make_call(PMAPPROC_CALLIT
, ca
, \
522 self
.packer
.pack_call_args
, \
523 self
.unpacker
.unpack_call_result
)
526 class TCPPortMapperClient(PartialPortMapperClient
, RawTCPClient
):
528 def __init__(self
, host
):
529 RawTCPClient
.__init
__(self
, \
530 host
, PMAP_PROG
, PMAP_VERS
, PMAP_PORT
)
533 class UDPPortMapperClient(PartialPortMapperClient
, RawUDPClient
):
535 def __init__(self
, host
):
536 RawUDPClient
.__init
__(self
, \
537 host
, PMAP_PROG
, PMAP_VERS
, PMAP_PORT
)
540 class BroadcastUDPPortMapperClient(PartialPortMapperClient
, \
541 RawBroadcastUDPClient
):
543 def __init__(self
, bcastaddr
):
544 RawBroadcastUDPClient
.__init
__(self
, \
545 bcastaddr
, PMAP_PROG
, PMAP_VERS
, PMAP_PORT
)
548 # Generic clients that find their server through the Port mapper
550 class TCPClient(RawTCPClient
):
552 def __init__(self
, host
, prog
, vers
):
553 pmap
= TCPPortMapperClient(host
)
554 port
= pmap
.Getport((prog
, vers
, IPPROTO_TCP
, 0))
557 raise RuntimeError, 'program not registered'
558 RawTCPClient
.__init
__(self
, host
, prog
, vers
, port
)
561 class UDPClient(RawUDPClient
):
563 def __init__(self
, host
, prog
, vers
):
564 pmap
= UDPPortMapperClient(host
)
565 port
= pmap
.Getport((prog
, vers
, IPPROTO_UDP
, 0))
568 raise RuntimeError, 'program not registered'
569 RawUDPClient
.__init
__(self
, host
, prog
, vers
, port
)
572 class BroadcastUDPClient(Client
):
574 def __init__(self
, bcastaddr
, prog
, vers
):
575 self
.pmap
= BroadcastUDPPortMapperClient(bcastaddr
)
576 self
.pmap
.set_reply_handler(self
.my_reply_handler
)
579 self
.user_reply_handler
= None
585 def set_reply_handler(self
, reply_handler
):
586 self
.user_reply_handler
= reply_handler
588 def set_timeout(self
, timeout
):
589 self
.pmap
.set_timeout(timeout
)
591 def my_reply_handler(self
, reply
, fromaddr
):
593 self
.unpacker
.reset(res
)
594 result
= self
.unpack_func()
596 self
.replies
.append((result
, fromaddr
))
597 if self
.user_reply_handler
is not None:
598 self
.user_reply_handler(result
, fromaddr
)
600 def make_call(self
, proc
, args
, pack_func
, unpack_func
):
604 if unpack_func
is None:
606 self
.unpack_func
= dummy
608 self
.unpack_func
= unpack_func
610 packed_args
= self
.packer
.get_buf()
611 dummy_replies
= self
.pmap
.Callit( \
612 (self
.prog
, self
.vers
, proc
, packed_args
))
618 # These are not symmetric to the Client classes
619 # XXX No attempt is made to provide authorization hooks yet
623 def __init__(self
, host
, prog
, vers
, port
):
624 self
.host
= host
# Should normally be '' for default interface
627 self
.port
= port
# Should normally be 0 for random port
628 self
.makesocket() # Assigns to self.sock and self.prot
630 self
.host
, self
.port
= self
.sock
.getsockname()
634 mapping
= self
.prog
, self
.vers
, self
.prot
, self
.port
635 p
= TCPPortMapperClient(self
.host
)
636 if not p
.Set(mapping
):
637 raise RuntimeError, 'register failed'
639 def unregister(self
):
640 mapping
= self
.prog
, self
.vers
, self
.prot
, self
.port
641 p
= TCPPortMapperClient(self
.host
)
642 if not p
.Unset(mapping
):
643 raise RuntimeError, 'unregister failed'
645 def handle(self
, call
):
646 # Don't use unpack_header but parse the header piecewise
647 # XXX I have no idea if I am using the right error responses!
648 self
.unpacker
.reset(call
)
650 xid
= self
.unpacker
.unpack_uint()
651 self
.packer
.pack_uint(xid
)
652 temp
= self
.unpacker
.unpack_enum()
654 return None # Not worthy of a reply
655 self
.packer
.pack_uint(REPLY
)
656 temp
= self
.unpacker
.unpack_uint()
657 if temp
<> RPCVERSION
:
658 self
.packer
.pack_uint(MSG_DENIED
)
659 self
.packer
.pack_uint(RPC_MISMATCH
)
660 self
.packer
.pack_uint(RPCVERSION
)
661 self
.packer
.pack_uint(RPCVERSION
)
662 return self
.packer
.get_buf()
663 self
.packer
.pack_uint(MSG_ACCEPTED
)
664 self
.packer
.pack_auth((AUTH_NULL
, make_auth_null()))
665 prog
= self
.unpacker
.unpack_uint()
666 if prog
<> self
.prog
:
667 self
.packer
.pack_uint(PROG_UNAVAIL
)
668 return self
.packer
.get_buf()
669 vers
= self
.unpacker
.unpack_uint()
670 if vers
<> self
.vers
:
671 self
.packer
.pack_uint(PROG_MISMATCH
)
672 self
.packer
.pack_uint(self
.vers
)
673 self
.packer
.pack_uint(self
.vers
)
674 return self
.packer
.get_buf()
675 proc
= self
.unpacker
.unpack_uint()
676 methname
= 'handle_' + `proc`
678 meth
= getattr(self
, methname
)
679 except AttributeError:
680 self
.packer
.pack_uint(PROC_UNAVAIL
)
681 return self
.packer
.get_buf()
682 cred
= self
.unpacker
.unpack_auth()
683 verf
= self
.unpacker
.unpack_auth()
685 meth() # Unpack args, call turn_around(), pack reply
686 except (EOFError, GarbageArgs
):
687 # Too few or too many arguments
689 self
.packer
.pack_uint(xid
)
690 self
.packer
.pack_uint(REPLY
)
691 self
.packer
.pack_uint(MSG_ACCEPTED
)
692 self
.packer
.pack_auth((AUTH_NULL
, make_auth_null()))
693 self
.packer
.pack_uint(GARBAGE_ARGS
)
694 return self
.packer
.get_buf()
696 def turn_around(self
):
701 self
.packer
.pack_uint(SUCCESS
)
703 def handle_0(self
): # Handle NULL message
706 def makesocket(self
):
707 # This MUST be overridden
708 raise RuntimeError, 'makesocket not defined'
710 def bindsocket(self
):
711 # Override this to bind to a different port (e.g. reserved)
712 self
.sock
.bind((self
.host
, self
.port
))
714 def addpackers(self
):
715 # Override this to use derived classes from Packer/Unpacker
716 self
.packer
= Packer()
717 self
.unpacker
= Unpacker('')
720 class TCPServer(Server
):
722 def makesocket(self
):
723 self
.sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
724 self
.prot
= IPPROTO_TCP
729 self
.session(self
.sock
.accept())
731 def session(self
, connection
):
732 sock
, (host
, port
) = connection
735 call
= recvrecord(sock
)
738 except socket
.error
, msg
:
739 print 'socket error:', msg
741 reply
= self
.handle(call
)
742 if reply
is not None:
743 sendrecord(sock
, reply
)
745 def forkingloop(self
):
746 # Like loop but uses forksession()
749 self
.forksession(self
.sock
.accept())
751 def forksession(self
, connection
):
752 # Like session but forks off a subprocess
754 # Wait for deceased children
757 pid
, sts
= os
.waitpid(0, 1)
764 connection
[0].close()
767 self
.session(connection
)
769 # Make sure we don't fall through in the parent
774 class UDPServer(Server
):
776 def makesocket(self
):
777 self
.sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_DGRAM
)
778 self
.prot
= IPPROTO_UDP
785 call
, host_port
= self
.sock
.recvfrom(8192)
786 reply
= self
.handle(call
)
788 self
.sock
.sendto(reply
, host_port
)
791 # Simple test program -- dump local portmapper status
794 pmap
= UDPPortMapperClient('')
797 for prog
, vers
, prot
, port
in list:
799 if prot
== IPPROTO_TCP
: print 'tcp',
800 elif prot
== IPPROTO_UDP
: print 'udp',
805 # Test program for broadcast operation -- dump everybody's portmapper status
810 bcastaddr
= sys
.argv
[1]
812 bcastaddr
= '<broadcast>'
813 def rh(reply
, fromaddr
):
814 host
, port
= fromaddr
815 print host
+ '\t' + `reply`
816 pmap
= BroadcastUDPPortMapperClient(bcastaddr
)
817 pmap
.set_reply_handler(rh
)
819 replies
= pmap
.Getport((100002, 1, IPPROTO_UDP
, 0))
822 # Test program for server, with corresponding client
823 # On machine A: python -c 'import rpc; rpc.testsvr()'
824 # On machine B: python -c 'import rpc; rpc.testclt()' A
828 # Simple test class -- proc 1 doubles its string argument as reply
831 arg
= self
.unpacker
.unpack_string()
833 print 'RPC function 1 called, arg', `arg`
834 self
.packer
.pack_string(arg
+ arg
)
836 s
= S('', 0x20000000, 1, 0)
839 except RuntimeError, msg
:
840 print 'RuntimeError:', msg
, '(ignored)'
842 print 'Service started...'
847 print 'Service interrupted.'
852 if sys
.argv
[1:]: host
= sys
.argv
[1]
854 # Client for above server
856 def call_1(self
, arg
):
857 return self
.make_call(1, arg
, \
858 self
.packer
.pack_string
, \
859 self
.unpacker
.unpack_string
)
860 c
= C(host
, 0x20000000, 1)
861 print 'making call...'
862 reply
= c
.call_1('hello, world, ')
863 print 'call returned', `reply`