tests/usage: test for --help consistency
[samba.git] / source4 / scripting / bin / autoidl
1 #!/usr/bin/env python3
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 from __future__ import print_function
21
22 import sys
23
24 MAX_OPNUM = 1000
25 MAX_BASE_SIZE = 0x1000
26 MAX_IFACE_VERSION = 1000
27
28 DCERPC_FAULT_OP_RNG_ERROR = 0x1c010002
29 DCERPC_FAULT_NDR = 0x6f7
30 NT_STATUS_NET_WRITE_FAULT = -1073741614
31
32
33 sys.path.insert(0, "bin/python")
34
35 from samba.dcerpc import ClientConnection
36
37 def find_num_funcs(conn):
38     for i in xrange(MAX_OPNUM):
39         try:
40             conn.request(i, "")
41         except RuntimeError as e:
42             if e.args[0] == DCERPC_FAULT_OP_RNG_ERROR:
43                 return i
44     raise Exception("More than %d functions" % MAX_OPNUM)
45
46 class Function:
47     def __init__(self, conn, opnum):
48         self.conn = conn
49         self.opnum = opnum
50
51     def request(self, data):
52         assert isinstance(data, str)
53         self.conn.request(self.opnum, data)
54
55     def valid_ndr(self, data):
56         try:
57             self.request(data)
58         except RuntimeError as e:
59             (num, msg) = e.args
60             if num == DCERPC_FAULT_NDR:
61                 return False
62         return True
63
64     def find_base_request(self, start=""):
65         """Find the smallest possible request that we can do that is 
66         valid. 
67         
68         This generally means sending all zeroes so we get NULL pointers where 
69         possible."""
70         # TODO: Don't try with just 0's as there may be switch_is() variables
71         # for which 0 is not a valid value or variables with range() set
72         # See how much input bytes we need
73         for i in range(MAX_BASE_SIZE):
74             data = start + chr(0) * i
75             if self.valid_ndr(data):
76                 return data
77         return None
78
79     def check_decision_byte(self, base_request, i):
80         """Check whether the specified byte is a possible "decision" byte, 
81         e.g. a byte that is part of a pointer, array size variable or 
82         discriminant.
83         
84         Note that this function only checks if a byte is definitely a decision 
85         byte. It may return False in some cases while the byte is actually 
86         a decision byte."""
87         data = list(base_request)
88         data[i] = chr(1)
89         return not self.valid_ndr("".join(data))
90
91     def find_deferrant_data(self, base_request, idx):
92         data = list(base_request)
93         data[idx*4] = chr(1)
94         # Just check that this is a pointer to something non-empty:
95         assert not self.valid_ndr("".join(data))
96
97         newdata = self.find_base_request("".join(data))
98
99         if newdata is None:
100             return None
101         
102         assert newdata.startswith(data)
103
104         return newdata[len(data):]
105
106     def find_idl(self):
107         base_request = self.find_base_request()
108
109         if base_request is None:
110             raise Exception("Unable to determine base size for opnum %d" % self.opnum)
111
112         print("\tBase request is %r" % base_request)
113
114         decision_byte_map = map(lambda x: self.check_decision_byte(base_request, x), range(len(base_request)))
115
116         print(decision_byte_map)
117         
118         # find pointers
119         possible_pointers = map(all, 
120             [decision_byte_map[i*4:(i+1)*4] for i in range(int(len(base_request)/4))])
121         print(possible_pointers)
122
123         pointer_deferrant_bases = map(
124             lambda x: self.find_deferrant_data(base_request, x) if possible_pointers[x] else None, range(len(possible_pointers)))
125
126         print(pointer_deferrant_bases)
127
128
129 if len(sys.argv) < 3:
130     print("Usage: autoidl <binding> <UUID> [<version>]")
131     sys.exit(1)
132
133 (binding, uuid) = sys.argv[1:3]
134 if len(sys.argv) == 4:
135     version = sys.argv[3]
136 else:
137     version = None
138
139 if version is None:
140     for i in range(MAX_IFACE_VERSION):
141         try:
142             conn = ClientConnection(binding, (uuid, i))
143         except RuntimeError as e:
144             num, msg = e.args
145             if num == NT_STATUS_NET_WRITE_FAULT:
146                 continue
147             raise
148         else:
149             break
150 else:
151     conn = ClientConnection(binding, (uuid, version))
152
153 print("Figuring out number of connections... ", end='')
154 num_funcs = find_num_funcs(conn)
155 print("%d" % num_funcs)
156
157 # Figure out the syntax for each one
158 for i in range(num_funcs):
159     print("Function %d" % i)
160     data = Function(conn, i)
161     try:
162         data.find_idl()
163     except Exception as e:
164         print("Error: %r" % e)