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