]> git.ozlabs.org Git - ppp.git/blob - pppd/plugins/winbind.c
67c72f65ef3f0533e5a648bb8dda7647401f44ce
[ppp.git] / pppd / plugins / winbind.c
1 /***********************************************************************
2 *
3 * winbind.c
4 *
5 * WINBIND plugin for pppd.  Performs PAP, CHAP, MS-CHAP, MS-CHAPv2
6 * authentication using WINBIND to contact a NT-style PDC.
7
8 * Based on the structure of the radius module.
9 *
10 * Copyright (C) 2003 Andrew Bartlet <abartlet@samba.org>
11 *
12 * Copyright 1999 Paul Mackerras, Alan Curry. 
13 * (pipe read code from passpromt.c)
14 *
15 * Copyright (C) 2002 Roaring Penguin Software Inc.
16 *
17 * Based on a patch for ipppd, which is:
18 *    Copyright (C) 1996, Matjaz Godec <gody@elgo.si>
19 *    Copyright (C) 1996, Lars Fenneberg <in5y050@public.uni-hamburg.de>
20 *    Copyright (C) 1997, Miguel A.L. Paraz <map@iphil.net>
21 *
22 * Uses radiusclient library, which is:
23 *    Copyright (C) 1995,1996,1997,1998 Lars Fenneberg <lf@elemental.net>
24 *    Copyright (C) 2002 Roaring Penguin Software Inc.
25 *
26 * MPPE support is by Ralf Hofmann, <ralf.hofmann@elvido.net>, with
27 * modification from Frank Cusack, <frank@google.com>.
28 *
29 * Updated on 2003-12-12 to support updated PPP plugin API from latest CVS
30 *    Copyright (C) 2003, Sean E. Millichamp <sean at bruenor dot org>
31 *
32 * This plugin may be distributed according to the terms of the GNU
33 * General Public License, version 2 or (at your option) any later version.
34 *
35 ***********************************************************************/
36
37 #include "pppd.h"
38 #include "chap-new.h"
39 #include "chap_ms.h"
40 #include "fsm.h"
41 #include "ipcp.h"
42 #include "mppe.h"
43 #include <syslog.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <fcntl.h>
47 #include <sys/time.h>
48 #include <sys/wait.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <stdlib.h>
52 #include <errno.h>
53 #include <ctype.h>
54
55 #define BUF_LEN 1024
56
57 #define NOT_AUTHENTICATED 0
58 #define AUTHENTICATED 1
59
60 static char *ntlm_auth = NULL;
61
62 static int set_ntlm_auth(char **argv)
63 {
64         char *p;
65
66         p = argv[0];
67         if (p[0] != '/') {
68                 option_error("ntlm_auth-helper argument must be full path");
69                 return 0;
70         }
71         p = strdup(p);
72         if (p == NULL) {
73                 novm("ntlm_auth-helper argument");
74                 return 0;
75         }
76         if (ntlm_auth != NULL)
77                 free(ntlm_auth);
78         ntlm_auth = p;
79         return 1;
80 }
81
82 static option_t Options[] = {
83         { "ntlm_auth-helper", o_special, (void *) &set_ntlm_auth,
84           "Path to ntlm_auth executable", OPT_PRIV },
85         { NULL }
86 };
87
88 static int
89 winbind_secret_check(void);
90
91 static int winbind_pap_auth(char *user,
92                            char *passwd,
93                            char **msgp,
94                            struct wordlist **paddrs,
95                            struct wordlist **popts);
96 static int winbind_chap_verify(char *user, char *ourname, int id,
97                                struct chap_digest_type *digest,
98                                unsigned char *challenge,
99                                unsigned char *response,
100                                char *message, int message_space);
101 static int winbind_allowed_address(u_int32_t addr); 
102
103 char pppd_version[] = VERSION;
104
105 /**********************************************************************
106 * %FUNCTION: plugin_init
107 * %ARGUMENTS:
108 *  None
109 * %RETURNS:
110 *  Nothing
111 * %DESCRIPTION:
112 *  Initializes WINBIND plugin.
113 ***********************************************************************/
114 void
115 plugin_init(void)
116 {
117     pap_check_hook = winbind_secret_check;
118     pap_auth_hook = winbind_pap_auth;
119
120     chap_check_hook = winbind_secret_check;
121     chap_verify_hook = winbind_chap_verify;
122
123     allowed_address_hook = winbind_allowed_address;
124
125     /* Don't ask the peer for anything other than MS-CHAP or MS-CHAP V2 */
126     chap_mdtype_all &= (MDTYPE_MICROSOFT_V2 | MDTYPE_MICROSOFT);
127     
128     add_options(Options);
129
130     info("WINBIND plugin initialized.");
131 }
132
133 /**
134  Routine to get hex characters and turn them into a 16 byte array.
135  the array can be variable length, and any non-hex-numeric
136  characters are skipped.  "0xnn" or "0Xnn" is specially catered
137  for.
138
139  valid examples: "0A5D15"; "0x15, 0x49, 0xa2"; "59\ta9\te3\n"
140
141 **/
142
143 /* 
144    Unix SMB/CIFS implementation.
145    Samba utility functions
146    
147    Copyright (C) Andrew Tridgell 1992-2001
148    Copyright (C) Simo Sorce      2001-2002
149    Copyright (C) Martin Pool     2003
150    
151    This program is free software; you can redistribute it and/or modify
152    it under the terms of the GNU General Public License as published by
153    the Free Software Foundation; either version 2 of the License, or
154    (at your option) any later version.
155    
156    This program is distributed in the hope that it will be useful,
157    but WITHOUT ANY WARRANTY; without even the implied warranty of
158    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
159    GNU General Public License for more details.
160    
161    You should have received a copy of the GNU General Public License
162    along with this program; if not, write to the Free Software
163    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
164 */
165
166 size_t strhex_to_str(char *p, size_t len, const char *strhex)
167 {
168         size_t i;
169         size_t num_chars = 0;
170         unsigned char   lonybble, hinybble;
171         const char     *hexchars = "0123456789ABCDEF";
172         char           *p1 = NULL, *p2 = NULL;
173
174         for (i = 0; i < len && strhex[i] != 0; i++) {
175                 if (strncmp(hexchars, "0x", 2) == 0) {
176                         i++; /* skip two chars */
177                         continue;
178                 }
179
180                 if (!(p1 = strchr(hexchars, toupper(strhex[i]))))
181                         break;
182
183                 i++; /* next hex digit */
184
185                 if (!(p2 = strchr(hexchars, toupper(strhex[i]))))
186                         break;
187
188                 /* get the two nybbles */
189                 hinybble = (p1 - hexchars);
190                 lonybble = (p2 - hexchars);
191
192                 p[num_chars] = (hinybble << 4) | lonybble;
193                 num_chars++;
194
195                 p1 = NULL;
196                 p2 = NULL;
197         }
198         return num_chars;
199 }
200
201 static const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
202
203 /**
204  * Encode a base64 string into a malloc()ed string caller to free.
205  *
206  *From SQUID: adopted from http://ftp.sunet.se/pub2/gnu/vm/base64-encode.c with adjustments
207  **/
208 char * base64_encode(const char *data)
209 {
210         size_t out_cnt = 0;
211         size_t len = strlen(data);
212         size_t output_len = 4 * ((len + 2) / 3) + 2;
213         const unsigned char *ptr = (const unsigned char *) data;
214         char *result = malloc(output_len); /* get us plenty of space */
215         unsigned int bits;
216
217         for (; len >= 3; len -= 3) {
218                 bits = (ptr[0] << 16) + (ptr[1] << 8) + ptr[2];
219                 ptr += 3;
220                 result[out_cnt++] = b64[bits >> 18];
221                 result[out_cnt++] = b64[(bits >> 12) & 0x3f];
222                 result[out_cnt++] = b64[(bits >> 6) & 0x3f];
223                 result[out_cnt++] = b64[bits & 0x3f];
224         }
225         if (len != 0) {
226                 bits = ptr[0] << 16;
227                 if (len > 1)
228                         bits |= ptr[1] << 8;
229                 result[out_cnt++] = b64[bits >> 18];
230                 result[out_cnt++] = b64[(bits >> 12) & 0x3f];
231                 result[out_cnt++] = (len > 1)? b64[(bits >> 6) & 0x3f]: '=';
232                 result[out_cnt++] = '=';
233         }
234
235         result[out_cnt] = '\0'; /* terminate */
236         return result;
237 }
238
239 unsigned int run_ntlm_auth(const char *username, 
240                            const char *domain, 
241                            const char *full_username,
242                            const char *plaintext_password,
243                            const u_char *challenge,
244                            size_t challenge_length,
245                            const u_char *lm_response, 
246                            size_t lm_response_length,
247                            const u_char *nt_response, 
248                            size_t nt_response_length,
249                            u_char nt_key[16], 
250                            char **error_string) 
251 {
252         
253         pid_t forkret;
254         int child_in[2];
255         int child_out[2];
256         int status;
257
258         int authenticated = NOT_AUTHENTICATED; /* not auth */
259         int got_user_session_key = 0; /* not got key */
260
261         char buffer[1024];
262
263         FILE *pipe_in;
264         FILE *pipe_out;
265         
266         int i;
267         char *challenge_hex;
268         char *lm_hex_hash;
269         char *nt_hex_hash;
270
271         /* First see if we have a program to run... */
272         if (ntlm_auth == NULL)
273                 return NOT_AUTHENTICATED;
274
275         /* Make first child */
276         if (pipe(child_out) == -1) {
277                 error("pipe creation failed for child OUT!");
278                 return NOT_AUTHENTICATED;
279         }
280
281         if (pipe(child_in) == -1) {
282                 error("pipe creation failed for child IN!");
283                 return NOT_AUTHENTICATED;
284         }
285
286         forkret = safe_fork(child_in[0], child_out[1], 2);
287         if (forkret == -1) {
288                 if (error_string) {
289                         *error_string = strdup("fork failed!");
290                 }
291
292                 return NOT_AUTHENTICATED;
293         }
294
295         if (forkret == 0) {
296                 /* child process */
297                 uid_t uid;
298
299                 close(child_out[0]);
300                 close(child_in[1]);
301
302                 /* run winbind as the user that invoked pppd */
303                 setgid(getgid());
304                 uid = getuid();
305                 if (setuid(uid) == -1 || getuid() != uid)
306                         fatal("pppd/winbind: could not setuid to %d: %m", uid);
307                 execl("/bin/sh", "sh", "-c", ntlm_auth, NULL);  
308                 fatal("pppd/winbind: could not exec /bin/sh: %m");
309         }
310
311         /* parent */
312         close(child_out[1]);
313         close(child_in[0]);
314
315         /* Need to write the User's info onto the pipe */
316
317         pipe_in = fdopen(child_in[1], "w");
318
319         pipe_out = fdopen(child_out[0], "r");
320
321         /* look for session key coming back */
322
323         if (username) {
324                 char *b64_username = base64_encode(username);
325                 fprintf(pipe_in, "Username:: %s\n", b64_username);
326                 free(b64_username);
327         }
328
329         if (domain) {
330                 char *b64_domain = base64_encode(domain);
331                 fprintf(pipe_in, "NT-Domain:: %s\n", b64_domain);
332                 free(b64_domain);
333         }
334
335         if (full_username) {
336                 char *b64_full_username = base64_encode(full_username);
337                 fprintf(pipe_in, "Full-Username:: %s\n", b64_full_username);
338                 free(b64_full_username);
339         }
340
341         if (plaintext_password) {
342                 char *b64_plaintext_password = base64_encode(plaintext_password);
343                 fprintf(pipe_in, "Password:: %s\n", b64_plaintext_password);
344                 free(b64_plaintext_password);
345         }
346
347         if (challenge_length) {
348                 fprintf(pipe_in, "Request-User-Session-Key: yes\n");
349
350                 challenge_hex = malloc(challenge_length*2+1);
351                 
352                 for (i = 0; i < challenge_length; i++)
353                         sprintf(challenge_hex + i * 2, "%02X", challenge[i]);
354                 
355                 fprintf(pipe_in, "LANMAN-Challenge: %s\n", challenge_hex);
356                 free(challenge_hex);
357         }
358         
359         if (lm_response_length) {
360                 lm_hex_hash = malloc(lm_response_length*2+1);
361                 
362                 for (i = 0; i < lm_response_length; i++)
363                         sprintf(lm_hex_hash + i * 2, "%02X", lm_response[i]);
364                 
365                 fprintf(pipe_in, "LANMAN-response: %s\n", lm_hex_hash);
366                 free(lm_hex_hash);
367         }
368         
369         if (nt_response_length) {
370                 nt_hex_hash = malloc(nt_response_length*2+1);
371                 
372                 for (i = 0; i < nt_response_length; i++)
373                         sprintf(nt_hex_hash + i * 2, "%02X", nt_response[i]);
374                 
375                 fprintf(pipe_in, "NT-response: %s\n", nt_hex_hash);
376                 free(nt_hex_hash);
377         }
378         
379         fprintf(pipe_in, ".\n");
380         fflush(pipe_in);
381         
382         while (fgets(buffer, sizeof(buffer)-1, pipe_out) != NULL) {
383                 char *message, *parameter;
384                 if (buffer[strlen(buffer)-1] != '\n') {
385                         break;
386                 }
387                 buffer[strlen(buffer)-1] = '\0';
388                 message = buffer;
389
390                 if (!(parameter = strstr(buffer, ": "))) {
391                         break;
392                 }
393                 
394                 parameter[0] = '\0';
395                 parameter++;
396                 parameter[0] = '\0';
397                 parameter++;
398                 
399                 if (strcmp(message, ".") == 0) {
400                         /* end of sequence */
401                         break;
402                 } else if (strcasecmp(message, "Authenticated") == 0) {
403                         if (strcasecmp(parameter, "Yes") == 0) {
404                                 authenticated = AUTHENTICATED;
405                         } else {
406                                 notice("Winbind has declined authentication for user!");
407                                 authenticated = NOT_AUTHENTICATED;
408                         }
409                 } else if (strcasecmp(message, "User-session-key") == 0) {
410                         /* length is the number of characters to parse */
411                         if (nt_key) { 
412                                 if (strhex_to_str(nt_key, 32, parameter) == 16) {
413                                         got_user_session_key = 1;
414                                 } else {
415                                         notice("NT session key for user was not 16 bytes!");
416                                 }
417                         }
418                 } else if (strcasecmp(message, "Error") == 0) {
419                         authenticated = NOT_AUTHENTICATED;
420                         if (error_string)
421                                 *error_string = strdup(parameter);
422                 } else if (strcasecmp(message, "Authentication-Error") == 0) {
423                         authenticated = NOT_AUTHENTICATED;
424                         if (error_string)
425                                 *error_string = strdup(parameter);
426                 } else {
427                         notice("unrecognised input from ntlm_auth helper - %s: %s", message, parameter); 
428                 }
429         }
430
431         /* parent */
432         if (close(child_out[0]) == -1) {
433                 close(child_in[1]);
434                 notice("error closing pipe?!? for child OUT[0]");
435                 return NOT_AUTHENTICATED;
436         }
437
438        /* parent */
439         if (close(child_in[1]) == -1) {
440                 notice("error closing pipe?!? for child IN[1]");
441                 return NOT_AUTHENTICATED;
442         }
443
444         while ((wait(&status) == -1) && errno == EINTR && !got_sigterm)
445                 ;
446
447         if ((authenticated == AUTHENTICATED) && nt_key && !got_user_session_key) {
448                 notice("Did not get user session key, despite being authenticated!");
449                 return NOT_AUTHENTICATED;
450         }
451         return authenticated;
452 }
453
454 /**********************************************************************
455 * %FUNCTION: winbind_secret_check
456 * %ARGUMENTS:
457 *  None
458 * %RETURNS:
459 *  0 if we don't have an ntlm_auth program to run, otherwise 1.
460 * %DESCRIPTION:
461 * Tells pppd that we will try to authenticate the peer, and not to
462 * worry about looking in /etc/ppp/ *-secrets
463 ***********************************************************************/
464 static int
465 winbind_secret_check(void)
466 {
467         return ntlm_auth != NULL;
468 }
469
470 /**********************************************************************
471 * %FUNCTION: winbind_pap_auth
472 * %ARGUMENTS:
473 *  user -- user-name of peer
474 *  passwd -- password supplied by peer
475 *  msgp -- Message which will be sent in PAP response
476 *  paddrs -- set to a list of possible peer IP addresses
477 *  popts -- set to a list of additional pppd options
478 * %RETURNS:
479 *  1 if we can authenticate, -1 if we cannot.
480 * %DESCRIPTION:
481 * Performs PAP authentication using WINBIND
482 ***********************************************************************/
483 static int
484 winbind_pap_auth(char *user,
485                 char *password,
486                 char **msgp,
487                 struct wordlist **paddrs,
488                 struct wordlist **popts)
489 {
490         if (run_ntlm_auth(NULL, NULL, user, password, NULL, 0, NULL, 0, NULL, 0, NULL, msgp) == AUTHENTICATED) {
491                 return 1;
492         } 
493         return -1;
494 }
495
496 /**********************************************************************
497 * %FUNCTION: winbind_chap_auth
498 * %ARGUMENTS:
499 *  user -- user-name of peer
500 *  remmd -- hash received from peer
501 *  remmd_len -- length of remmd
502 *  cstate -- pppd's chap_state structure
503 * %RETURNS:
504 *  AUTHENTICATED (1) if we can authenticate, NOT_AUTHENTICATED (0) if we cannot.
505 * %DESCRIPTION:
506 * Performs MS-CHAP and MS-CHAPv2 authentication using WINBIND.
507 ***********************************************************************/
508
509 static int 
510 winbind_chap_verify(char *user, char *ourname, int id,
511                     struct chap_digest_type *digest,
512                     unsigned char *challenge,
513                     unsigned char *response,
514                     char *message, int message_space)
515 {
516         int challenge_len, response_len;
517         char domainname[256];
518         char *domain;
519         char *username;
520         char *p;
521         char saresponse[MS_AUTH_RESPONSE_LENGTH+1];
522
523         /* The first byte of each of these strings contains their length */
524         challenge_len = *challenge++;
525         response_len = *response++;
526         
527         /* remove domain from "domain\username" */
528         if ((username = strrchr(user, '\\')) != NULL)
529                 ++username;
530         else
531                 username = user;
532         
533         strlcpy(domainname, user, sizeof(domainname));
534         
535         /* remove domain from "domain\username" */
536         if ((p = strrchr(domainname, '\\')) != NULL) {
537                 *p = '\0';
538                 domain = domainname;
539         } else {
540                 domain = NULL;
541         }
542         
543         /*  generate MD based on negotiated type */
544         switch (digest->code) {
545                 
546         case CHAP_MICROSOFT:
547         {
548                 char *error_string = NULL;
549                 u_char *nt_response = NULL;
550                 u_char *lm_response = NULL;
551                 int nt_response_size = 0;
552                 int lm_response_size = 0;
553                 u_char session_key[16];
554                 
555                 if (response_len != MS_CHAP_RESPONSE_LEN)
556                         break;                  /* not even the right length */
557                 
558                 /* Determine which part of response to verify against */
559                 if (response[MS_CHAP_USENT]) {
560                         nt_response = &response[MS_CHAP_NTRESP];
561                         nt_response_size = MS_CHAP_NTRESP_LEN;
562                 } else {
563 #ifdef MSLANMAN
564                         lm_response = &response[MS_CHAP_LANMANRESP];
565                         lm_response_size = MS_CHAP_LANMANRESP_LEN;
566 #else
567                         /* Should really propagate this into the error packet. */
568                         notice("Peer request for LANMAN auth not supported");
569                         return NOT_AUTHENTICATED;
570 #endif /* MSLANMAN */
571                 }
572                 
573                 /* ship off to winbind, and check */
574                 
575                 if (run_ntlm_auth(username, 
576                                   domain,
577                                   NULL,
578                                   NULL,
579                                   challenge, challenge_len,
580                                   lm_response, lm_response_size,
581                                   nt_response, nt_response_size,
582                                   session_key,
583                                   &error_string) == AUTHENTICATED) {
584                         mppe_set_chapv1(challenge, session_key);
585                         slprintf(message, message_space, "Access granted");
586                         return AUTHENTICATED;
587                         
588                 } else {
589                         if (error_string) {
590                                 notice(error_string);
591                                 free(error_string);
592                         }
593                         slprintf(message, message_space, "E=691 R=1 C=%0.*B V=0",
594                                  challenge_len, challenge);
595                         return NOT_AUTHENTICATED;
596                 }
597                 break;
598         }
599         
600         case CHAP_MICROSOFT_V2:
601         {
602                 u_char Challenge[8];
603                 u_char session_key[MD4_SIGNATURE_SIZE];
604                 char *error_string = NULL;
605                 
606                 if (response_len != MS_CHAP2_RESPONSE_LEN)
607                         break;                  /* not even the right length */
608                 
609                 ChallengeHash(&response[MS_CHAP2_PEER_CHALLENGE], challenge,
610                               user, Challenge);
611                 
612                 /* ship off to winbind, and check */
613                 
614                 if (run_ntlm_auth(username, 
615                                   domain, 
616                                   NULL,
617                                   NULL,
618                                   Challenge, 8,
619                                   NULL, 0,
620                                   &response[MS_CHAP2_NTRESP],
621                                   MS_CHAP2_NTRESP_LEN,
622                                   session_key,
623                                   &error_string) == AUTHENTICATED) {
624                         
625                         GenerateAuthenticatorResponse(session_key,
626                                 &response[MS_CHAP2_NTRESP],
627                                 &response[MS_CHAP2_PEER_CHALLENGE],
628                                 challenge, user, saresponse);
629                         mppe_set_chapv2(session_key, &response[MS_CHAP2_NTRESP],
630                                        MS_CHAP2_AUTHENTICATOR);
631                         if (response[MS_CHAP2_FLAGS]) {
632                                 slprintf(message, message_space, "S=%s", saresponse);
633                         } else {
634                                 slprintf(message, message_space, "S=%s M=%s",
635                                          saresponse, "Access granted");
636                         }
637                         return AUTHENTICATED;
638                         
639                 } else {
640                         if (error_string) {
641                                 notice(error_string);
642                                 slprintf(message, message_space, "E=691 R=1 C=%0.*B V=0 M=%s",
643                                          challenge_len, challenge, error_string);
644                                 free(error_string);
645                         } else {
646                                 slprintf(message, message_space, "E=691 R=1 C=%0.*B V=0 M=%s",
647                                          challenge_len, challenge, "Access denied");
648                         }
649                         return NOT_AUTHENTICATED;
650                 }
651                 break;
652         }
653         
654         default:
655                 error("WINBIND: Challenge type %u unsupported", digest->code);
656         }
657         return NOT_AUTHENTICATED;
658 }
659
660 static int 
661 winbind_allowed_address(u_int32_t addr) 
662 {
663         ipcp_options *wo = &ipcp_wantoptions[0];
664         if (wo->hisaddr !=0 && wo->hisaddr == addr) {
665                 return 1;
666         }
667         return -1;
668 }