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