python: use '#!/usr/bin/env python' to cope with varying install locations
[kai/samba.git] / source4 / scripting / bin / autoidl
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # Unix SMB/CIFS implementation.
5 # Copyright © Jelmer Vernooij <jelmer@samba.org> 2008
6 #   
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #   
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #   
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20
21 import sys
22
23 MAX_OPNUM = 1000
24 MAX_BASE_SIZE = 0x1000
25 MAX_IFACE_VERSION = 1000
26
27 DCERPC_FAULT_OP_RNG_ERROR = 0x1c010002
28 DCERPC_FAULT_NDR = 0x6f7
29 NT_STATUS_NET_WRITE_FAULT = -1073741614
30
31
32 sys.path.insert(0, "bin/python")
33
34 from samba.dcerpc import ClientConnection
35
36 def find_num_funcs(conn):
37     for i in xrange(MAX_OPNUM):
38         try:
39             conn.request(i, "")
40         except RuntimeError, (num, msg):
41             if num == DCERPC_FAULT_OP_RNG_ERROR:
42                 return i
43     raise Exception("More than %d functions" % MAX_OPNUM)
44
45 class Function:
46     def __init__(self, conn, opnum):
47         self.conn = conn
48         self.opnum = opnum
49
50     def request(self, data):
51         assert isinstance(data, str)
52         self.conn.request(self.opnum, data)
53
54     def valid_ndr(self, data):
55         try:
56             self.request(data)
57         except RuntimeError, (num, msg):
58             if num == DCERPC_FAULT_NDR:
59                 return False
60         return True
61
62     def find_base_request(self, start=""):
63         """Find the smallest possible request that we can do that is 
64         valid. 
65         
66         This generally means sending all zeroes so we get NULL pointers where 
67         possible."""
68         # TODO: Don't try with just 0's as there may be switch_is() variables
69         # for which 0 is not a valid value or variables with range() set
70         # See how much input bytes we need
71         for i in range(MAX_BASE_SIZE):
72             data = start + chr(0) * i
73             if self.valid_ndr(data):
74                 return data
75         return None
76
77     def check_decision_byte(self, base_request, i):
78         """Check whether the specified byte is a possible "decision" byte, 
79         e.g. a byte that is part of a pointer, array size variable or 
80         discriminant.
81         
82         Note that this function only checks if a byte is definitely a decision 
83         byte. It may return False in some cases while the byte is actually 
84         a decision byte."""
85         data = list(base_request)
86         data[i] = chr(1)
87         return not self.valid_ndr("".join(data))
88
89     def find_deferrant_data(self, base_request, idx):
90         data = list(base_request)
91         data[idx*4] = chr(1)
92         # Just check that this is a pointer to something non-empty:
93         assert not self.valid_ndr("".join(data))
94
95         newdata = self.find_base_request("".join(data))
96
97         if newdata is None:
98             return None
99         
100         assert newdata.startswith(data)
101
102         return newdata[len(data):]
103
104     def find_idl(self):
105         base_request = self.find_base_request()
106
107         if base_request is None:
108             raise Exception("Unable to determine base size for opnum %d" % self.opnum)
109
110         print "\tBase request is %r" % base_request
111
112         decision_byte_map = map(lambda x: self.check_decision_byte(base_request, x), range(len(base_request)))
113
114         print decision_byte_map
115         
116         # find pointers
117         possible_pointers = map(all, 
118             [decision_byte_map[i*4:(i+1)*4] for i in range(int(len(base_request)/4))])
119         print possible_pointers
120
121         pointer_deferrant_bases = map(
122             lambda x: self.find_deferrant_data(base_request, x) if possible_pointers[x] else None, range(len(possible_pointers)))
123
124         print pointer_deferrant_bases
125
126
127 if len(sys.argv) < 3:
128     print "Usage: autoidl <binding> <UUID> [<version>]"
129     sys.exit(1)
130
131 (binding, uuid) = sys.argv[1:3]
132 if len(sys.argv) == 4:
133     version = sys.argv[3]
134 else:
135     version = None
136
137 if version is None:
138     for i in range(MAX_IFACE_VERSION):
139         try:
140             conn = ClientConnection(binding, (uuid, i))
141         except RuntimeError, (num, msg):
142             if num == NT_STATUS_NET_WRITE_FAULT:
143                 continue
144             raise
145         else:
146             break
147 else:
148     conn = ClientConnection(binding, (uuid, version))
149
150 print "Figuring out number of connections...",
151 num_funcs = find_num_funcs(conn)
152 print "%d" % num_funcs
153
154 # Figure out the syntax for each one
155 for i in range(num_funcs):
156     print "Function %d" % i
157     data = Function(conn, i)
158     try:
159         data.find_idl()
160     except Exception, e:
161         print "Error: %r" % e