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