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