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