1/***********************************************************************
2*
3* winbind.c
4*
5* WINBIND plugin for pppd.  Performs PAP, CHAP, MS-CHAP, MS-CHAPv2
6* authentication using WINBIND to contact a NT-style PDC.
7*
8* Based on the structure of the radius module.
9*
10* Copyright (C) 2003 Andrew Bartlet <abartlet@samba.org>
11*
12* Copyright 1999 Paul Mackerras, Alan Curry.
13* (pipe read code from passpromt.c)
14*
15* Copyright (C) 2002 Roaring Penguin Software Inc.
16*
17* Based on a patch for ipppd, which is:
18*    Copyright (C) 1996, Matjaz Godec <gody@elgo.si>
19*    Copyright (C) 1996, Lars Fenneberg <in5y050@public.uni-hamburg.de>
20*    Copyright (C) 1997, Miguel A.L. Paraz <map@iphil.net>
21*
22* Uses radiusclient library, which is:
23*    Copyright (C) 1995,1996,1997,1998 Lars Fenneberg <lf@elemental.net>
24*    Copyright (C) 2002 Roaring Penguin Software Inc.
25*
26* MPPE support is by Ralf Hofmann, <ralf.hofmann@elvido.net>, with
27* modification from Frank Cusack, <frank@google.com>.
28*
29* Updated on 2003-12-12 to support updated PPP plugin API from latest CVS
30*    Copyright (C) 2003, Sean E. Millichamp <sean at bruenor dot org>
31*
32* This plugin may be distributed according to the terms of the GNU
33* General Public License, version 2 or (at your option) any later version.
34*
35***********************************************************************/
36
37#include "pppd.h"
38#include "chap-new.h"
39#include "chap_ms.h"
40#ifdef MPPE
41#include "md5.h"
42#endif
43#include "fsm.h"
44#include "ipcp.h"
45#include <syslog.h>
46#include <sys/types.h>
47#include <sys/stat.h>
48#include <fcntl.h>
49#include <sys/time.h>
50#include <sys/wait.h>
51#include <string.h>
52#include <unistd.h>
53#include <stdlib.h>
54#include <errno.h>
55#include <ctype.h>
56
57#define BUF_LEN 1024
58
59#define NOT_AUTHENTICATED 0
60#define AUTHENTICATED 1
61
62static char *ntlm_auth = NULL;
63
64static 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
84static 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
90static int
91winbind_secret_check(void);
92
93static int winbind_pap_auth(char *user,
94			   char *passwd,
95			   char **msgp,
96			   struct wordlist **paddrs,
97			   struct wordlist **popts);
98static 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);
103static int winbind_allowed_address(u_int32_t addr);
104
105char pppd_version[] = VERSION;
106
107/**********************************************************************
108* %FUNCTION: plugin_init
109* %ARGUMENTS:
110*  None
111* %RETURNS:
112*  Nothing
113* %DESCRIPTION:
114*  Initializes WINBIND plugin.
115***********************************************************************/
116void
117plugin_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
168size_t strhex_to_str(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
203static 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 **/
210char * base64_encode(const char *data)
211{
212	int bits = 0;
213	int char_count = 0;
214	size_t out_cnt = 0;
215	size_t len = strlen(data);
216	size_t output_len = strlen(data) * 2;
217	char *result = malloc(output_len); /* get us plenty of space */
218
219	while (len-- && out_cnt < (output_len) - 5) {
220		int c = (unsigned char) *(data++);
221		bits += c;
222		char_count++;
223		if (char_count == 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	    bits = 0;
229	    char_count = 0;
230	} else {
231	    bits <<= 8;
232	}
233    }
234    if (char_count != 0) {
235	bits <<= 16 - (8 * char_count);
236	result[out_cnt++] = b64[bits >> 18];
237	result[out_cnt++] = b64[(bits >> 12) & 0x3f];
238	if (char_count == 1) {
239	    result[out_cnt++] = '=';
240	    result[out_cnt++] = '=';
241	} else {
242	    result[out_cnt++] = b64[(bits >> 6) & 0x3f];
243	    result[out_cnt++] = '=';
244	}
245    }
246    result[out_cnt] = '\0';	/* terminate */
247    return result;
248}
249
250unsigned int run_ntlm_auth(const char *username,
251			   const char *domain,
252			   const char *full_username,
253			   const char *plaintext_password,
254			   const u_char *challenge,
255			   size_t challenge_length,
256			   const u_char *lm_response,
257			   size_t lm_response_length,
258			   const u_char *nt_response,
259			   size_t nt_response_length,
260			   u_char nt_key[16],
261			   char **error_string)
262{
263
264	pid_t forkret;
265        int child_in[2];
266        int child_out[2];
267	int status;
268
269	int authenticated = NOT_AUTHENTICATED; /* not auth */
270	int got_user_session_key = 0; /* not got key */
271
272	char buffer[1024];
273
274	FILE *pipe_in;
275	FILE *pipe_out;
276
277	int i;
278	char *challenge_hex;
279	char *lm_hex_hash;
280	char *nt_hex_hash;
281
282	/* First see if we have a program to run... */
283	if (ntlm_auth == NULL)
284		return NOT_AUTHENTICATED;
285
286        /* Make first child */
287        if (pipe(child_out) == -1) {
288                error("pipe creation failed for child OUT!");
289		return NOT_AUTHENTICATED;
290        }
291
292        if (pipe(child_in) == -1) {
293                error("pipe creation failed for child IN!");
294		return NOT_AUTHENTICATED;
295        }
296
297        forkret = safe_fork(child_in[0], child_out[1], 2);
298        if (forkret == -1) {
299		if (error_string) {
300			*error_string = strdup("fork failed!");
301		}
302
303                return NOT_AUTHENTICATED;
304        }
305
306	if (forkret == 0) {
307		/* child process */
308		close(child_out[0]);
309		close(child_in[1]);
310
311		/* run winbind as the user that invoked pppd */
312		setgid(getgid());
313		setuid(getuid());
314		execl("/bin/sh", "sh", "-c", ntlm_auth, NULL);
315		perror("pppd/winbind: could not exec /bin/sh");
316		exit(1);
317	}
318
319        /* parent */
320	close(child_out[1]);
321	close(child_in[0]);
322
323	/* Need to write the User's info onto the pipe */
324
325	pipe_in = fdopen(child_in[1], "w");
326
327	pipe_out = fdopen(child_out[0], "r");
328
329	/* look for session key coming back */
330
331	if (username) {
332		char *b64_username = base64_encode(username);
333		fprintf(pipe_in, "Username:: %s\n", b64_username);
334		free(b64_username);
335	}
336
337	if (domain) {
338		char *b64_domain = base64_encode(domain);
339		fprintf(pipe_in, "NT-Domain:: %s\n", b64_domain);
340		free(b64_domain);
341	}
342
343	if (full_username) {
344		char *b64_full_username = base64_encode(full_username);
345		fprintf(pipe_in, "Full-Username:: %s\n", b64_full_username);
346		free(b64_full_username);
347	}
348
349	if (plaintext_password) {
350		char *b64_plaintext_password = base64_encode(plaintext_password);
351		fprintf(pipe_in, "Password:: %s\n", b64_plaintext_password);
352		free(b64_plaintext_password);
353	}
354
355	if (challenge_length) {
356		fprintf(pipe_in, "Request-User-Session-Key: yes\n");
357
358		challenge_hex = malloc(challenge_length*2+1);
359
360		for (i = 0; i < challenge_length; i++)
361			sprintf(challenge_hex + i * 2, "%02X", challenge[i]);
362
363		fprintf(pipe_in, "LANMAN-Challenge: %s\n", challenge_hex);
364		free(challenge_hex);
365	}
366
367	if (lm_response_length) {
368		lm_hex_hash = malloc(lm_response_length*2+1);
369
370		for (i = 0; i < lm_response_length; i++)
371			sprintf(lm_hex_hash + i * 2, "%02X", lm_response[i]);
372
373		fprintf(pipe_in, "LANMAN-response: %s\n", lm_hex_hash);
374		free(lm_hex_hash);
375	}
376
377	if (nt_response_length) {
378		nt_hex_hash = malloc(nt_response_length*2+1);
379
380		for (i = 0; i < nt_response_length; i++)
381			sprintf(nt_hex_hash + i * 2, "%02X", nt_response[i]);
382
383		fprintf(pipe_in, "NT-response: %s\n", nt_hex_hash);
384		free(nt_hex_hash);
385	}
386
387	fprintf(pipe_in, ".\n");
388	fflush(pipe_in);
389
390	while (fgets(buffer, sizeof(buffer)-1, pipe_out) != NULL) {
391		char *message, *parameter;
392		if (buffer[strlen(buffer)-1] != '\n') {
393			break;
394		}
395		buffer[strlen(buffer)-1] = '\0';
396		message = buffer;
397
398		if (!(parameter = strstr(buffer, ": "))) {
399			break;
400		}
401
402		parameter[0] = '\0';
403		parameter++;
404		parameter[0] = '\0';
405		parameter++;
406
407		if (strcmp(message, ".") == 0) {
408			/* end of sequence */
409			break;
410		} else if (strcasecmp(message, "Authenticated") == 0) {
411			if (strcasecmp(parameter, "Yes") == 0) {
412				authenticated = AUTHENTICATED;
413			} else {
414				notice("Winbind has declined authentication for user!");
415				authenticated = NOT_AUTHENTICATED;
416			}
417		} else if (strcasecmp(message, "User-session-key") == 0) {
418			/* length is the number of characters to parse */
419			if (nt_key) {
420				if (strhex_to_str(nt_key, 32, parameter) == 16) {
421					got_user_session_key = 1;
422				} else {
423					notice("NT session key for user was not 16 bytes!");
424				}
425			}
426		} else if (strcasecmp(message, "Error") == 0) {
427			authenticated = NOT_AUTHENTICATED;
428			if (error_string)
429				*error_string = strdup(parameter);
430		} else if (strcasecmp(message, "Authentication-Error") == 0) {
431			authenticated = NOT_AUTHENTICATED;
432			if (error_string)
433				*error_string = strdup(parameter);
434		} else {
435			notice("unrecognised input from ntlm_auth helper - %s: %s", message, parameter);
436		}
437	}
438
439        /* parent */
440        if (close(child_out[0]) == -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)
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***********************************************************************/
471static int
472winbind_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***********************************************************************/
490static int
491winbind_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
516static int
517winbind_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	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		MS_ChapResponse *rmd = (MS_ChapResponse *) response;
561		u_char session_key[16];
562
563		if (response_len != MS_CHAP_RESPONSE_LEN)
564			break;			/* not even the right length */
565
566		/* Determine which part of response to verify against */
567		if (rmd->UseNT[0]) {
568			nt_response = rmd->NTResp;
569			nt_response_size = sizeof(rmd->NTResp);
570		} else {
571#ifdef MSLANMAN
572			lm_response = rmd->LANManResp;
573			lm_response_size = sizeof(rmd->LANManResp);
574#else
575			/* Should really propagate this into the error packet. */
576			notice("Peer request for LANMAN auth not supported");
577			return NOT_AUTHENTICATED;
578#endif /* MSLANMAN */
579		}
580
581		/* ship off to winbind, and check */
582
583		if (run_ntlm_auth(username,
584				  domain,
585				  NULL,
586				  NULL,
587				  challenge,
588				  challenge_len,
589				  lm_response,
590				  lm_response ? lm_response_size: 0,
591				  nt_response,
592				  nt_response ? nt_response_size: 0,
593				  session_key,
594				  &error_string) == AUTHENTICATED) {
595			mppe_set_keys(challenge, session_key);
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		MS_Chap2Response *rmd = (MS_Chap2Response *) response;
614		u_char Challenge[8];
615		u_char session_key[MD4_SIGNATURE_SIZE];
616		char *error_string = NULL;
617
618		if (response_len != MS_CHAP2_RESPONSE_LEN)
619			break;			/* not even the right length */
620
621		ChallengeHash(rmd->PeerChallenge, challenge, 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,
630				  8,
631				  NULL,
632				  0,
633				  rmd->NTResp,
634				  sizeof(rmd->NTResp),
635
636				  session_key,
637				  &error_string) == AUTHENTICATED) {
638
639			GenerateAuthenticatorResponse(session_key,
640						      rmd->NTResp, rmd->PeerChallenge,
641						      challenge, user,
642						      saresponse);
643			mppe_set_keys2(session_key, rmd->NTResp, MS_CHAP2_AUTHENTICATOR);
644			if (rmd->Flags[0]) {
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
673static int
674winbind_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}
682