xref: /reactos/dll/win32/secur32/wine/dispatcher.c (revision 40462c92)
1 /*
2  * Copyright 2005, 2006 Kai Blin
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  *
18  * A dispatcher to run ntlm_auth for wine's sspi module.
19  */
20 
21 #include "precomp.h"
22 
23 #include <stdio.h>
24 #include <process.h>
25 #include <io.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 
29 #ifdef __REACTOS__
30 #define close _close
31 #define read  _read
32 #define write _write
33 #endif
34 
35 #include <wine/debug.h>
36 WINE_DEFAULT_DEBUG_CHANNEL(ntlm);
37 
38 #define INITIAL_BUFFER_SIZE 200
39 
40 char* flatten_cmdline(const char *prog, char* const argv[])
41 {
42     int i;
43     SIZE_T argstr_size = 0;
44     char *argstr, *p;
45 
46     /* Compute space needed for the new string, and allocate it */
47     argstr_size += strlen(prog) + 3; // 3 == 2 quotes between 'prog', and 1 space
48     for(i = 0; argv[i] != NULL; ++i)
49     {
50         argstr_size += strlen(argv[i]) + 1; // 1 for space
51     }
52     argstr = HeapAlloc(GetProcessHeap(), 0, (argstr_size + 1) * sizeof(CHAR));
53     if (argstr == NULL)
54     {
55         ERR("ERROR: Not enough memory\n");
56         return NULL;
57     }
58 
59     /* Copy the contents and NULL-terminate the string */
60     p = argstr;
61     strcpy(p, "\"");    // Open quote
62     strcat(p, prog);
63     strcat(p, "\" ");   // Close quote + space
64     p += strlen(p);
65     for(i = 0; argv[i] != NULL; ++i)
66     {
67         strcpy(p, argv[i]);
68         p += strlen(argv[i]);
69         *p++ = ' ';
70     }
71     *p = '\0';
72 
73     return argstr;
74 }
75 
76 SECURITY_STATUS fork_helper(PNegoHelper *new_helper, const char *prog,
77         char* const argv[])
78 {
79     int pipe_in[2];
80     int pipe_out[2];
81 #ifdef __REACTOS__
82     HANDLE hPipe;
83     PROCESS_INFORMATION pi;
84     STARTUPINFOA si;
85     char* cmdline;
86 #endif
87     int i;
88     PNegoHelper helper;
89 
90     TRACE("%s ", debugstr_a(prog));
91     for(i = 0; argv[i] != NULL; ++i)
92     {
93         TRACE("%s ", debugstr_a(argv[i]));
94     }
95     TRACE("\n");
96 
97 #ifndef __REACTOS__
98 
99 #ifdef HAVE_PIPE2
100     if (pipe2( pipe_in, O_CLOEXEC ) < 0 )
101 #endif
102     {
103         if( pipe(pipe_in) < 0 ) return SEC_E_INTERNAL_ERROR;
104         fcntl( pipe_in[0], F_SETFD, FD_CLOEXEC );
105         fcntl( pipe_in[1], F_SETFD, FD_CLOEXEC );
106     }
107 #ifdef HAVE_PIPE2
108     if (pipe2( pipe_out, O_CLOEXEC ) < 0 )
109 #endif
110     {
111         if( pipe(pipe_out) < 0 )
112         {
113             close(pipe_in[0]);
114             close(pipe_in[1]);
115             return SEC_E_INTERNAL_ERROR;
116         }
117         fcntl( pipe_out[0], F_SETFD, FD_CLOEXEC );
118         fcntl( pipe_out[1], F_SETFD, FD_CLOEXEC );
119     }
120 
121 #else
122 
123     if (_pipe(pipe_in, 0, _O_BINARY /* _O_TEXT */ | _O_NOINHERIT) < 0)
124     {
125         return SEC_E_INTERNAL_ERROR;
126     }
127 
128     if (_pipe(pipe_out, 0, _O_BINARY /* _O_TEXT */ | _O_NOINHERIT) < 0)
129     {
130         close(pipe_in[0]);
131         close(pipe_in[1]);
132         return SEC_E_INTERNAL_ERROR;
133     }
134 
135 #endif
136 
137     if (!(helper = HeapAlloc(GetProcessHeap(),0, sizeof(NegoHelper))))
138     {
139         close(pipe_in[0]);
140         close(pipe_in[1]);
141         close(pipe_out[0]);
142         close(pipe_out[1]);
143         return SEC_E_INSUFFICIENT_MEMORY;
144     }
145 
146 #ifndef __REACTOS__
147     helper->helper_pid = fork();
148 #else
149 
150     cmdline = flatten_cmdline(prog, argv);
151     if (!cmdline)
152     {
153         close(pipe_in[0]);
154         close(pipe_in[1]);
155         close(pipe_out[0]);
156         close(pipe_out[1]);
157         HeapFree( GetProcessHeap(), 0, helper );
158         return SEC_E_INSUFFICIENT_MEMORY;
159     }
160 
161     ZeroMemory(&pi, sizeof(pi));
162     ZeroMemory(&si, sizeof(si));
163     si.cb = sizeof(si);
164 
165     si.dwFlags |= STARTF_USESTDHANDLES;
166 
167     /* The reading side of the pipe is STDIN for this process */
168     hPipe = (HANDLE)_get_osfhandle(pipe_out[0]);
169     SetHandleInformation(hPipe, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
170     si.hStdInput = hPipe;
171 
172     /* The writing side of the pipe is STDOUT for this process */
173     hPipe = (HANDLE)_get_osfhandle(pipe_in[1]);
174     SetHandleInformation(hPipe, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
175     si.hStdOutput = hPipe;
176     si.hStdError  = hPipe;
177 
178     if (!CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
179     {
180         /* We fail just afterwards */
181         helper->helper_pid = (HANDLE)-1;
182     }
183     else
184     {
185         helper->helper_pid = pi.hProcess;
186         CloseHandle(pi.hThread);
187         CloseHandle(pi.hProcess);
188     }
189 
190     HeapFree( GetProcessHeap(), 0, cmdline );
191 
192 #endif
193 
194 #ifndef __REACTOS__
195     if(helper->helper_pid == -1)
196 #else
197     if(helper->helper_pid == (HANDLE)-1)
198 #endif
199     {
200         close(pipe_in[0]);
201         close(pipe_in[1]);
202         close(pipe_out[0]);
203         close(pipe_out[1]);
204         HeapFree( GetProcessHeap(), 0, helper );
205         return SEC_E_INTERNAL_ERROR;
206     }
207 
208 #ifndef __REACTOS__
209     if(helper->helper_pid == 0)
210     {
211         /* We're in the child now */
212         dup2(pipe_out[0], 0);
213         close(pipe_out[0]);
214         close(pipe_out[1]);
215 
216         dup2(pipe_in[1], 1);
217         close(pipe_in[0]);
218         close(pipe_in[1]);
219 
220         execvp(prog, argv);
221 
222         /* Whoops, we shouldn't get here. Big badaboom.*/
223         write(STDOUT_FILENO, "BH\n", 3);
224         _exit(1);
225     }
226     else
227 #endif
228     {
229         *new_helper = helper;
230         helper->major = helper->minor = helper->micro = -1;
231         helper->com_buf = NULL;
232         helper->com_buf_size = 0;
233         helper->com_buf_offset = 0;
234         helper->session_key = NULL;
235         helper->neg_flags = 0;
236         helper->crypt.ntlm.a4i = NULL;
237         helper->crypt.ntlm2.send_a4i = NULL;
238         helper->crypt.ntlm2.recv_a4i = NULL;
239         helper->crypt.ntlm2.send_sign_key = NULL;
240         helper->crypt.ntlm2.send_seal_key = NULL;
241         helper->crypt.ntlm2.recv_sign_key = NULL;
242         helper->crypt.ntlm2.recv_seal_key = NULL;
243         helper->pipe_in = pipe_in[0];   // Keep in(read)
244         close(pipe_in[1]);              // Close in(write)
245         helper->pipe_out = pipe_out[1]; // Keep out(write)
246         close(pipe_out[0]);             // Close out(read)
247     }
248 
249     return SEC_E_OK;
250 }
251 
252 static SECURITY_STATUS read_line(PNegoHelper helper, int *offset_len)
253 {
254     char *newline;
255     int read_size;
256 
257     if(helper->com_buf == NULL)
258     {
259         TRACE("Creating a new buffer for the helper\n");
260         if((helper->com_buf = HeapAlloc(GetProcessHeap(), 0, INITIAL_BUFFER_SIZE)) == NULL)
261             return SEC_E_INSUFFICIENT_MEMORY;
262 
263         /* Created a new buffer, size is INITIAL_BUFFER_SIZE, offset is 0 */
264         helper->com_buf_size = INITIAL_BUFFER_SIZE;
265         helper->com_buf_offset = 0;
266     }
267 
268     do
269     {
270         TRACE("offset = %d, size = %d\n", helper->com_buf_offset, helper->com_buf_size);
271         if(helper->com_buf_offset + INITIAL_BUFFER_SIZE > helper->com_buf_size)
272         {
273             /* increment buffer size in INITIAL_BUFFER_SIZE steps */
274             char *buf = HeapReAlloc(GetProcessHeap(), 0, helper->com_buf,
275                                     helper->com_buf_size + INITIAL_BUFFER_SIZE);
276             TRACE("Resizing buffer!\n");
277             if (!buf) return SEC_E_INSUFFICIENT_MEMORY;
278             helper->com_buf_size += INITIAL_BUFFER_SIZE;
279             helper->com_buf = buf;
280         }
281         if((read_size = read(helper->pipe_in, helper->com_buf + helper->com_buf_offset,
282                     helper->com_buf_size - helper->com_buf_offset)) <= 0)
283         {
284             return SEC_E_INTERNAL_ERROR;
285         }
286 
287         TRACE("read_size = %d, read: %s\n", read_size,
288                 debugstr_a(helper->com_buf + helper->com_buf_offset));
289         helper->com_buf_offset += read_size;
290         newline = memchr(helper->com_buf, '\n', helper->com_buf_offset);
291     }while(newline == NULL);
292 
293     /* Now, if there's a newline character, and we read more than that newline,
294      * we have to store the offset so we can preserve the additional data.*/
295     if( newline != helper->com_buf + helper->com_buf_offset)
296     {
297         TRACE("offset_len is calculated from %p - %p\n",
298                 (helper->com_buf + helper->com_buf_offset), newline+1);
299         /* the length of the offset is the number of chars after the newline */
300         *offset_len = (helper->com_buf + helper->com_buf_offset) - (newline + 1);
301     }
302     else
303     {
304         *offset_len = 0;
305     }
306 
307     *newline = '\0';
308 
309     return SEC_E_OK;
310 }
311 
312 static SECURITY_STATUS preserve_unused(PNegoHelper helper, int offset_len)
313 {
314     TRACE("offset_len = %d\n", offset_len);
315 
316     if(offset_len > 0)
317     {
318         memmove(helper->com_buf, helper->com_buf + helper->com_buf_offset,
319                 offset_len);
320         helper->com_buf_offset = offset_len;
321     }
322     else
323     {
324         helper->com_buf_offset = 0;
325     }
326 
327     TRACE("helper->com_buf_offset was set to: %d\n", helper->com_buf_offset);
328     return SEC_E_OK;
329 }
330 
331 SECURITY_STATUS run_helper(PNegoHelper helper, char *buffer,
332         unsigned int max_buflen, int *buflen)
333 {
334     int offset_len;
335     SECURITY_STATUS sec_status = SEC_E_OK;
336 
337     TRACE("In helper: sending %s\n", debugstr_a(buffer));
338 
339     /* buffer + '\n' */
340     write(helper->pipe_out, buffer, lstrlenA(buffer));
341     write(helper->pipe_out, "\n", 1);
342 
343     if((sec_status = read_line(helper, &offset_len)) != SEC_E_OK)
344     {
345         return sec_status;
346     }
347 
348     TRACE("In helper: received %s\n", debugstr_a(helper->com_buf));
349     *buflen = lstrlenA(helper->com_buf);
350 
351     if( *buflen > max_buflen)
352     {
353         ERR("Buffer size too small(%d given, %d required) dropping data!\n",
354                 max_buflen, *buflen);
355         return SEC_E_BUFFER_TOO_SMALL;
356     }
357 
358     if( *buflen < 2 )
359     {
360         return SEC_E_ILLEGAL_MESSAGE;
361     }
362 
363     /* We only get ERR if the input size is too big. On a GENSEC error,
364      * ntlm_auth will return BH */
365     if(strncmp(helper->com_buf, "ERR", 3) == 0)
366     {
367         return SEC_E_INVALID_TOKEN;
368     }
369 
370     memcpy(buffer, helper->com_buf, *buflen+1);
371 
372     sec_status = preserve_unused(helper, offset_len);
373 
374     return sec_status;
375 }
376 
377 void cleanup_helper(PNegoHelper helper)
378 {
379 
380     TRACE("Killing helper %p\n", helper);
381     if(helper == NULL)
382         return;
383 
384     HeapFree(GetProcessHeap(), 0, helper->com_buf);
385     HeapFree(GetProcessHeap(), 0, helper->session_key);
386 
387     /* closing stdin will terminate ntlm_auth */
388     close(helper->pipe_out);
389     close(helper->pipe_in);
390 
391 #ifndef __REACTOS__
392 
393 #ifdef HAVE_FORK
394     if (helper->helper_pid > 0) /* reap child */
395     {
396         pid_t wret;
397         do {
398             wret = waitpid(helper->helper_pid, NULL, 0);
399         } while (wret < 0 && errno == EINTR);
400     }
401 #endif
402 
403 #endif
404 
405     HeapFree(GetProcessHeap(), 0, helper);
406 }
407 
408 void check_version(PNegoHelper helper)
409 {
410     char temp[80];
411     char *newline;
412     int major = 0, minor = 0, micro = 0, ret;
413 
414     TRACE("Checking version of helper\n");
415     if(helper != NULL)
416     {
417         int len = read(helper->pipe_in, temp, sizeof(temp)-1);
418         if (len > 8)
419         {
420             if((newline = memchr(temp, '\n', len)) != NULL)
421                 *newline = '\0';
422             else
423                 temp[len] = 0;
424 
425             TRACE("Exact version is %s\n", debugstr_a(temp));
426             ret = sscanf(temp, "Version %d.%d.%d", &major, &minor, &micro);
427             if(ret != 3)
428             {
429                 ERR("Failed to get the helper version.\n");
430                 helper->major = helper->minor = helper->micro = -1;
431             }
432             else
433             {
434                 TRACE("Version recognized: %d.%d.%d\n", major, minor, micro);
435                 helper->major = major;
436                 helper->minor = minor;
437                 helper->micro = micro;
438             }
439         }
440     }
441 }
442