28b83f8cde789ce8da4880dfcf85fd4224808fcd
[samba.git] / source3 / torture / test_ntlm_auth.py
1 #!/usr/bin/env python
2
3 # Unix SMB/CIFS implementation.
4 # A test for the ntlm_auth tool
5 # Copyright (C) Kai Blin <kai@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 """Test ntlm_auth
21 This test program will start ntlm_auth with the given command line switches and
22 see if it will get the expected results.
23 """
24
25 import os
26 import sys
27 from optparse import OptionParser
28
29
30 class ReadChildError(Exception):
31     pass
32
33
34 class WriteChildError(Exception):
35     pass
36
37
38 def readLine(pipe):
39     """readLine(pipe) -> str
40     Read a line from the child's pipe, returns the string read.
41     Throws ReadChildError if the read fails.
42     """
43     newline = -1
44     buf = ""
45     while newline == -1:
46         more = os.read(pipe, 2047)
47         buf = buf + more
48         newline = buf.find('\n')
49         if more == "":
50             raise ReadChildError()
51
52     return buf[:newline]
53
54
55 def writeLine(pipe, buf):
56     """writeLine(pipe, buf) -> nul
57     Write a line to the child's pipe.
58     Raises WriteChildError if the write fails.
59     """
60     written = os.write(pipe, buf)
61     if written != len(buf):
62         raise WriteChildError()
63     os.write(pipe, "\n")
64
65
66 def parseCommandLine():
67     """parseCommandLine() -> (opts, ntlm_auth_path)
68     Parse the command line.
69     Return a tuple consisting of the options and the path to ntlm_auth.
70     """
71     usage = "usage: %prog [options] path/to/ntlm_auth"
72     parser = OptionParser(usage)
73
74     parser.set_defaults(client_username="foo")
75     parser.set_defaults(client_password="secret")
76     parser.set_defaults(client_domain="FOO")
77     parser.set_defaults(client_helper="ntlmssp-client-1")
78
79     parser.set_defaults(server_username="foo")
80     parser.set_defaults(server_password="secret")
81     parser.set_defaults(server_domain="FOO")
82     parser.set_defaults(server_helper="squid-2.5-ntlmssp")
83     parser.set_defaults(config_file="/etc/samba/smb.conf")
84
85     parser.add_option("--client-username", dest="client_username",\
86                       help="User name for the client. [default: foo]")
87     parser.add_option("--client-password", dest="client_password",\
88                       help="Password the client will send. [default: secret]")
89     parser.add_option("--client-domain", dest="client_domain",\
90                       help="Domain the client authenticates for. [default: FOO]")
91     parser.add_option("--client-helper", dest="client_helper",\
92                       help="Helper mode for the ntlm_auth client. [default: ntlmssp-client-1]")
93     parser.add_option("--client-use-cached-creds", dest="client_use_cached_creds",\
94                       help="Use winbindd credentials cache (rather than default username/pw)", action="store_true")
95
96     parser.add_option("--target-hostname", dest="target_hostname",\
97                       help="Target hostname for kerberos")
98     parser.add_option("--target-service", dest="target_service",\
99                       help="Target service for kerberos")
100
101     parser.add_option("--server-username", dest="server_username",\
102                       help="User name server uses for local auth. [default: foo]")
103     parser.add_option("--server-password", dest="server_password",\
104                       help="Password server uses for local auth. [default: secret]")
105     parser.add_option("--server-domain", dest="server_domain",\
106                       help="Domain server uses for local auth. [default: FOO]")
107     parser.add_option("--server-helper", dest="server_helper",\
108                       help="Helper mode for the ntlm_auth server. [default: squid-2.5-server]")
109     parser.add_option("--server-use-winbindd", dest="server_use_winbindd",\
110                       help="Use winbindd to check the password (rather than default username/pw)", action="store_true")
111     parser.add_option("--require-membership-of", dest="sid",\
112                       help="Require that the user is a member of this group to authenticate.")
113
114     parser.add_option("-s", "--configfile", dest="config_file",\
115                       help="Path to smb.conf file. [default:/etc/samba/smb.conf")
116
117     (opts, args) = parser.parse_args()
118     if len(args) != 1:
119         parser.error("Invalid number of arguments.")
120
121     if not os.access(args[0], os.X_OK):
122         parser.error("%s is not executable." % args[0])
123
124     return (opts, args[0])
125
126
127 def main():
128     """main() -> int
129     Run the test.
130     Returns 0 if test succeeded, <>0 otherwise.
131     """
132     (opts, ntlm_auth_path) = parseCommandLine()
133
134     (client_in_r, client_in_w) = os.pipe()
135     (client_out_r, client_out_w) = os.pipe()
136
137     client_pid = os.fork()
138
139     if not client_pid:
140         # We're in the client child
141         os.close(0)
142         os.close(1)
143
144         os.dup2(client_out_r, 0)
145         os.close(client_out_r)
146         os.close(client_out_w)
147
148         os.dup2(client_in_w, 1)
149         os.close(client_in_r)
150         os.close(client_in_w)
151
152         client_args = []
153         client_args.append("--helper-protocol=%s" % opts.client_helper)
154         client_args.append("--username=%s" % opts.client_username)
155         if opts.client_use_cached_creds:
156             client_args.append("--use-cached-creds")
157         else:
158             client_args.append("--password=%s" % opts.client_password)
159         client_args.append("--domain=%s" % opts.client_domain)
160         client_args.append("--configfile=%s" % opts.config_file)
161         if opts.target_service:
162             client_args.append("--target-service=%s" % opts.target_service)
163         if opts.target_hostname:
164             client_args.append("--target-hostname=%s" % opts.target_hostname)
165
166         os.execv(ntlm_auth_path, client_args)
167
168     client_in = client_in_r
169     os.close(client_in_w)
170
171     client_out = client_out_w
172     os.close(client_out_r)
173
174     (server_in_r, server_in_w) = os.pipe()
175     (server_out_r, server_out_w) = os.pipe()
176
177     server_pid = os.fork()
178
179     if not server_pid:
180         # We're in the server child
181         os.close(0)
182         os.close(1)
183
184         os.dup2(server_out_r, 0)
185         os.close(server_out_r)
186         os.close(server_out_w)
187
188         os.dup2(server_in_w, 1)
189         os.close(server_in_r)
190         os.close(server_in_w)
191
192         server_args = []
193         server_args.append("--helper-protocol=%s" % opts.server_helper)
194         if not opts.server_use_winbindd:
195             server_args.append("--username=%s" % opts.server_username)
196             server_args.append("--password=%s" % opts.server_password)
197             server_args.append("--domain=%s" % opts.server_domain)
198             if opts.sid:
199                 raise Exception("Server must be using winbindd for require-membership-of.")
200         else:
201             if opts.sid:
202                 server_args.append("--require-membership-of=%s" % opts.sid)
203
204         server_args.append("--configfile=%s" % opts.config_file)
205
206         os.execv(ntlm_auth_path, server_args)
207
208     server_in = server_in_r
209     os.close(server_in_w)
210
211     server_out = server_out_w
212     os.close(server_out_r)
213
214     if opts.client_helper == "ntlmssp-client-1" and opts.server_helper == "squid-2.5-ntlmssp":
215
216         # We're in the parent
217         writeLine(client_out, "YR")
218         buf = readLine(client_in)
219
220         if buf.count("YR ", 0, 3) != 1:
221             sys.exit(1)
222
223         writeLine(server_out, buf)
224         buf = readLine(server_in)
225
226         if buf.count("TT ", 0, 3) != 1:
227             sys.exit(2)
228
229         writeLine(client_out, buf)
230         buf = readLine(client_in)
231
232         if buf.count("AF ", 0, 3) != 1:
233             sys.exit(3)
234
235         # Client sends 'AF <base64 blob>' but server expects 'KK <abse64 blob>'
236         buf = buf.replace("AF", "KK", 1)
237
238         writeLine(server_out, buf)
239         buf = readLine(server_in)
240
241         if buf.count("AF ", 0, 3) != 1:
242             sys.exit(4)
243
244     elif opts.client_helper == "ntlmssp-client-1" and opts.server_helper == "gss-spnego":
245         # We're in the parent
246         writeLine(client_out, "YR")
247         buf = readLine(client_in)
248
249         if buf.count("YR ", 0, 3) != 1:
250             sys.exit(1)
251
252         writeLine(server_out, buf)
253         buf = readLine(server_in)
254
255         if buf.count("TT ", 0, 3) != 1:
256             sys.exit(2)
257
258         writeLine(client_out, buf)
259         buf = readLine(client_in)
260
261         if buf.count("AF ", 0, 3) != 1:
262             sys.exit(3)
263
264         # Client sends 'AF <base64 blob>' but server expects 'KK <abse64 blob>'
265         buf = buf.replace("AF", "KK", 1)
266
267         writeLine(server_out, buf)
268         buf = readLine(server_in)
269
270         if buf.count("AF * ", 0, 5) != 1:
271             sys.exit(4)
272
273     elif opts.client_helper == "gss-spnego-client" and opts.server_helper == "gss-spnego":
274         # We're in the parent
275         writeLine(server_out, "YR")
276         buf = readLine(server_in)
277
278         while True:
279             if buf.count("AF ", 0, 3) != 1 and buf.count("TT ", 0, 3) != 1:
280                 sys.exit(1)
281
282             writeLine(client_out, buf)
283             buf = readLine(client_in)
284
285             if buf.count("AF", 0, 2) == 1:
286                 break
287
288             if buf.count("AF ", 0, 5) != 1 and buf.count("KK ", 0, 3) != 1 and buf.count("TT ", 0, 3) != 1:
289                 sys.exit(2)
290
291             writeLine(server_out, buf)
292             buf = readLine(server_in)
293
294             if buf.count("AF * ", 0, 5) == 1:
295                 break
296
297     else:
298         sys.exit(5)
299
300     if opts.client_helper == "ntlmssp-client-1":
301         writeLine(client_out, "GK")
302         buf = readLine(client_in)
303
304         if buf.count("GK ", 0, 3) != 1:
305             sys.exit(4)
306
307         writeLine(client_out, "GF")
308         buf = readLine(client_in)
309
310         if buf.count("GF ", 0, 3) != 1:
311             sys.exit(4)
312
313     if opts.server_helper == "squid-2.5-ntlmssp":
314         writeLine(server_out, "GK")
315         buf = readLine(server_in)
316
317         if buf.count("GK ", 0, 3) != 1:
318             sys.exit(4)
319
320         writeLine(server_out, "GF")
321         buf = readLine(server_in)
322
323         if buf.count("GF ", 0, 3) != 1:
324             sys.exit(4)
325
326     os.close(server_in)
327     os.close(server_out)
328     os.close(client_in)
329     os.close(client_out)
330     os.waitpid(server_pid, 0)
331     os.waitpid(client_pid, 0)
332     sys.exit(0)
333
334
335 if __name__ == "__main__":
336     main()
337
338