/***********************************************************************
*
* winbind.c
*
* WINBIND plugin for pppd. Performs PAP, CHAP, MS-CHAP, MS-CHAPv2
* authentication using WINBIND to contact a NT-style PDC.
*
* Based on the structure of the radius module.
*
* Copyright (C) 2003 Andrew Bartlet <abartlet@samba.org>
*
* Copyright 1999 Paul Mackerras, Alan Curry.
* (pipe read code from passpromt.c)
*
* Copyright (C) 2002 Roaring Penguin Software Inc.
*
* Based on a patch for ipppd, which is:
* Copyright (C) 1996, Matjaz Godec <gody@elgo.si>
* Copyright (C) 1996, Lars Fenneberg <in5y050@public.uni-hamburg.de>
* Copyright (C) 1997, Miguel A.L. Paraz <map@iphil.net>
*
* Uses radiusclient library, which is:
* Copyright (C) 1995,1996,1997,1998 Lars Fenneberg <lf@elemental.net>
* Copyright (C) 2002 Roaring Penguin Software Inc.
*
* MPPE support is by Ralf Hofmann, <ralf.hofmann@elvido.net>, with
* modification from Frank Cusack, <frank@google.com>.
*
* Updated on 2003-12-12 to support updated PPP plugin API from latest CVS
* Copyright (C) 2003, Sean E. Millichamp <sean at bruenor dot org>
*
* This plugin may be distributed according to the terms of the GNU
* General Public License, version 2 or (at your option) any later version.
*
***********************************************************************/
#include "pppd.h"
#include "chap-new.h"
#include "chap_ms.h"
#ifdef MPPE
#include "md5.h"
#endif
#include "fsm.h"
#include "ipcp.h"
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#define BUF_LEN 1024
#define NOT_AUTHENTICATED 0
#define AUTHENTICATED 1
static char *ntlm_auth = NULL;
static int set_ntlm_auth(char **argv)
{
char *p;
p = argv[0];
if (p[0] != '/') {
option_error("ntlm_auth-helper argument must be full path");
return 0;
}
p = strdup(p);
if (p == NULL) {
novm("ntlm_auth-helper argument");
return 0;
}
if (ntlm_auth != NULL)
free(ntlm_auth);
ntlm_auth = p;
return 1;
}
static option_t Options[] = {
{ "ntlm_auth-helper", o_special, (void *) &set_ntlm_auth,
"Path to ntlm_auth executable", OPT_PRIV },
{ NULL }
};
static int
winbind_secret_check(void);
static int winbind_pap_auth(char *user,
char *passwd,
char **msgp,
struct wordlist **paddrs,
struct wordlist **popts);
static int winbind_chap_verify(char *user, char *ourname, int id,
struct chap_digest_type *digest,
unsigned char *challenge,
unsigned char *response,
char *message, int message_space);
static int winbind_allowed_address(u_int32_t addr);
char pppd_version[] = VERSION;
/**********************************************************************
* %FUNCTION: plugin_init
* %ARGUMENTS:
* None
* %RETURNS:
* Nothing
* %DESCRIPTION:
* Initializes WINBIND plugin.
***********************************************************************/
void
plugin_init(void)
{
pap_check_hook = winbind_secret_check;
pap_auth_hook = winbind_pap_auth;
chap_check_hook = winbind_secret_check;
chap_verify_hook = winbind_chap_verify;
allowed_address_hook = winbind_allowed_address;
/* Don't ask the peer for anything other than MS-CHAP or MS-CHAP V2 */
chap_mdtype_all &= (MDTYPE_MICROSOFT_V2 | MDTYPE_MICROSOFT);
add_options(Options);
info("WINBIND plugin initialized.");
}
/**
Routine to get hex characters and turn them into a 16 byte array.
the array can be variable length, and any non-hex-numeric
characters are skipped. "0xnn" or "0Xnn" is specially catered
for.
valid examples: "0A5D15"; "0x15, 0x49, 0xa2"; "59\ta9\te3\n"
**/
/*
Unix SMB/CIFS implementation.
Samba utility functions
Copyright (C) Andrew Tridgell 1992-2001
Copyright (C) Simo Sorce 2001-2002
Copyright (C) Martin Pool 2003
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
size_t strhex_to_str(char *p, size_t len, const char *strhex)
{
size_t i;
size_t num_chars = 0;
unsigned char lonybble, hinybble;
const char *hexchars = "0123456789ABCDEF";
char *p1 = NULL, *p2 = NULL;
for (i = 0; i < len && strhex[i] != 0; i++) {
if (strncmp(hexchars, "0x", 2) == 0) {
i++; /* skip two chars */
continue;
}
if (!(p1 = strchr(hexchars, toupper(strhex[i]))))
break;
i++; /* next hex digit */
if (!(p2 = strchr(hexchars, toupper(strhex[i]))))
break;
/* get the two nybbles */
hinybble = (p1 - hexchars);
lonybble = (p2 - hexchars);
p[num_chars] = (hinybble << 4) | lonybble;
num_chars++;
p1 = NULL;
p2 = NULL;
}
return num_chars;
}
static const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* Encode a base64 string into a malloc()ed string caller to free.
*
*From SQUID: adopted from http://ftp.sunet.se/pub2/gnu/vm/base64-encode.c with adjustments
**/
char * base64_encode(const char *data)
{
int bits = 0;
int char_count = 0;
size_t out_cnt = 0;
size_t len = strlen(data);
size_t output_len = strlen(data) * 2;
char *result = malloc(output_len); /* get us plenty of space */
while (len-- && out_cnt < (output_len) - 5) {
int c = (unsigned char) *(data++);
bits += c;
char_count++;
if (char_count == 3) {
result[out_cnt++] = b64[bits >> 18];
result[out_cnt++] = b64[(bits >> 12) & 0x3f];
result[out_cnt++] = b64[(bits >> 6) & 0x3f];
result[out_cnt++] = b64[bits & 0x3f];
bits = 0;
char_count = 0;
} else {
bits <<= 8;
}
}
if (char_count != 0) {
bits <<= 16 - (8 * char_count);
result[out_cnt++] = b64[bits >> 18];
result[out_cnt++] = b64[(bits >> 12) & 0x3f];
if (char_count == 1) {
result[out_cnt++] = '=';
result[out_cnt++] = '=';
} else {
result[out_cnt++] = b64[(bits >> 6) & 0x3f];
result[out_cnt++] = '=';
}
}
result[out_cnt] = '\0'; /* terminate */
return result;
}
unsigned int run_ntlm_auth(const char *username,
const char *domain,
const char *full_username,
const char *plaintext_password,
const u_char *challenge,
size_t challenge_length,
const u_char *lm_response,
size_t lm_response_length,
const u_char *nt_response,
size_t nt_response_length,
u_char nt_key[16],
char **error_string)
{
pid_t forkret;
int child_in[2];
int child_out[2];
int status;
int authenticated = NOT_AUTHENTICATED; /* not auth */
int got_user_session_key = 0; /* not got key */
char buffer[1024];
FILE *pipe_in;
FILE *pipe_out;
int i;
char *challenge_hex;
char *lm_hex_hash;
char *nt_hex_hash;
/* First see if we have a program to run... */
if (ntlm_auth == NULL)
return NOT_AUTHENTICATED;
/* Make first child */
if (pipe(child_out) == -1) {
error("pipe creation failed for child OUT!");
return NOT_AUTHENTICATED;
}
if (pipe(child_in) == -1) {
error("pipe creation failed for child IN!");
return NOT_AUTHENTICATED;
}
forkret = safe_fork(child_in[0], child_out[1], 2);
if (forkret == -1) {
if (error_string) {
*error_string = strdup("fork failed!");
}
return NOT_AUTHENTICATED;
}
if (forkret == 0) {
/* child process */
close(child_out[0]);
close(child_in[1]);
/* run winbind as the user that invoked pppd */
setgid(getgid());
setuid(getuid());
execl("/bin/sh", "sh", "-c", ntlm_auth, NULL);
perror("pppd/winbind: could not exec /bin/sh");
exit(1);
}
/* parent */
close(child_out[1]);
close(child_in[0]);
/* Need to write the User's info onto the pipe */
pipe_in = fdopen(child_in[1], "w");
pipe_out = fdopen(child_out[0], "r");
/* look for session key coming back */
if (username) {
char *b64_username = base64_encode(username);
fprintf(pipe_in, "Username:: %s\n", b64_username);
free(b64_username);
}
if (domain) {
char *b64_domain = base64_encode(domain);
fprintf(pipe_in, "NT-Domain:: %s\n", b64_domain);
free(b64_domain);
}
if (full_username) {
char *b64_full_username = base64_encode(full_username);
fprintf(pipe_in, "Full-Username:: %s\n", b64_full_username);
free(b64_full_username);
}
if (plaintext_password) {
char *b64_plaintext_password = base64_encode(plaintext_password);
fprintf(pipe_in, "Password:: %s\n", b64_plaintext_password);
free(b64_plaintext_password);
}
if (challenge_length) {
fprintf(pipe_in, "Request-User-Session-Key: yes\n");
challenge_hex = malloc(challenge_length*2+1);
for (i = 0; i < challenge_length; i++)
sprintf(challenge_hex + i * 2, "%02X", challenge[i]);
fprintf(pipe_in, "LANMAN-Challenge: %s\n", challenge_hex);
free(challenge_hex);
}
if (lm_response_length) {
lm_hex_hash = malloc(lm_response_length*2+1);
for (i = 0; i < lm_response_length; i++)
sprintf(lm_hex_hash + i * 2, "%02X", lm_response[i]);
fprintf(pipe_in, "LANMAN-response: %s\n", lm_hex_hash);
free(lm_hex_hash);
}
if (nt_response_length) {
nt_hex_hash = malloc(nt_response_length*2+1);
for (i = 0; i < nt_response_length; i++)
sprintf(nt_hex_hash + i * 2, "%02X", nt_response[i]);
fprintf(pipe_in, "NT-response: %s\n", nt_hex_hash);
free(nt_hex_hash);
}
fprintf(pipe_in, ".\n");
fflush(pipe_in);
while (fgets(buffer, sizeof(buffer)-1, pipe_out) != NULL) {
char *message, *parameter;
if (buffer[strlen(buffer)-1] != '\n') {
break;
}
buffer[strlen(buffer)-1] = '\0';
message = buffer;
if (!(parameter = strstr(buffer, ": "))) {
break;
}
parameter[0] = '\0';
parameter++;
parameter[0] = '\0';
parameter++;
if (strcmp(message, ".") == 0) {
/* end of sequence */
break;
} else if (strcasecmp(message, "Authenticated") == 0) {
if (strcasecmp(parameter, "Yes") == 0) {
authenticated = AUTHENTICATED;
} else {
notice("Winbind has declined authentication for user!");
authenticated = NOT_AUTHENTICATED;
}
} else if (strcasecmp(message, "User-session-key") == 0) {
/* length is the number of characters to parse */
if (nt_key) {
if (strhex_to_str(nt_key, 32, parameter) == 16) {
got_user_session_key = 1;
} else {
notice("NT session key for user was not 16 bytes!");
}
}
} else if (strcasecmp(message, "Error") == 0) {
authenticated = NOT_AUTHENTICATED;
if (error_string)
*error_string = strdup(parameter);
} else if (strcasecmp(message, "Authentication-Error") == 0) {
authenticated = NOT_AUTHENTICATED;
if (error_string)
*error_string = strdup(parameter);
} else {
notice("unrecognised input from ntlm_auth helper - %s: %s", message, parameter);
}
}
/* parent */
if (close(child_out[0]) == -1) {
notice("error closing pipe?!? for child OUT[0]");
return NOT_AUTHENTICATED;
}
/* parent */
if (close(child_in[1]) == -1) {
notice("error closing pipe?!? for child IN[1]");
return NOT_AUTHENTICATED;
}
while ((wait(&status) == -1) && errno == EINTR)
;
if ((authenticated == AUTHENTICATED) && nt_key && !got_user_session_key) {
notice("Did not get user session key, despite being authenticated!");
return NOT_AUTHENTICATED;
}
return authenticated;
}
/**********************************************************************
* %FUNCTION: winbind_secret_check
* %ARGUMENTS:
* None
* %RETURNS:
* 0 if we don't have an ntlm_auth program to run, otherwise 1.
* %DESCRIPTION:
* Tells pppd that we will try to authenticate the peer, and not to
* worry about looking in /etc/ppp/ *-secrets
***********************************************************************/
static int
winbind_secret_check(void)
{
return ntlm_auth != NULL;
}
/**********************************************************************
* %FUNCTION: winbind_pap_auth
* %ARGUMENTS:
* user -- user-name of peer
* passwd -- password supplied by peer
* msgp -- Message which will be sent in PAP response
* paddrs -- set to a list of possible peer IP addresses
* popts -- set to a list of additional pppd options
* %RETURNS:
* 1 if we can authenticate, -1 if we cannot.
* %DESCRIPTION:
* Performs PAP authentication using WINBIND
***********************************************************************/
static int
winbind_pap_auth(char *user,
char *password,
char **msgp,
struct wordlist **paddrs,
struct wordlist **popts)
{
if (run_ntlm_auth(NULL, NULL, user, password, NULL, 0, NULL, 0, NULL, 0, NULL, msgp) == AUTHENTICATED) {
return 1;
}
return -1;
}
/**********************************************************************
* %FUNCTION: winbind_chap_auth
* %ARGUMENTS:
* user -- user-name of peer
* remmd -- hash received from peer
* remmd_len -- length of remmd
* cstate -- pppd's chap_state structure
* %RETURNS:
* AUTHENTICATED (1) if we can authenticate, NOT_AUTHENTICATED (0) if we cannot.
* %DESCRIPTION:
* Performs MS-CHAP and MS-CHAPv2 authentication using WINBIND.
***********************************************************************/
static int
winbind_chap_verify(char *user, char *ourname, int id,
struct chap_digest_type *digest,
unsigned char *challenge,
unsigned char *response,
char *message, int message_space)
{
int challenge_len, response_len;
char domainname[256];
char *domain;
char *username;
char *p;
char saresponse[MS_AUTH_RESPONSE_LENGTH+1];
/* The first byte of each of these strings contains their length */
challenge_len = *challenge++;
response_len = *response++;
/* remove domain from "domain\username" */
if ((username = strrchr(user, '\\')) != NULL)
++username;
else
username = user;
strlcpy(domainname, user, sizeof(domainname));
/* remove domain from "domain\username" */
if ((p = strrchr(domainname, '\\')) != NULL) {
*p = '\0';
domain = domainname;
} else {
domain = NULL;
}
/* generate MD based on negotiated type */
switch (digest->code) {
case CHAP_MICROSOFT:
{
char *error_string = NULL;
u_char *nt_response = NULL;
u_char *lm_response = NULL;
int nt_response_size = 0;
int lm_response_size = 0;
MS_ChapResponse *rmd = (MS_ChapResponse *) response;
u_char session_key[16];
if (response_len != MS_CHAP_RESPONSE_LEN)
break; /* not even the right length */
/* Determine which part of response to verify against */
if (rmd->UseNT[0]) {
nt_response = rmd->NTResp;
nt_response_size = sizeof(rmd->NTResp);
} else {
#ifdef MSLANMAN
lm_response = rmd->LANManResp;
lm_response_size = sizeof(rmd->LANManResp);
#else
/* Should really propagate this into the error packet. */
notice("Peer request for LANMAN auth not supported");
return NOT_AUTHENTICATED;
#endif /* MSLANMAN */
}
/* ship off to winbind, and check */
if (run_ntlm_auth(username,
domain,
NULL,
NULL,
challenge,
challenge_len,
lm_response,
lm_response ? lm_response_size: 0,
nt_response,
nt_response ? nt_response_size: 0,
session_key,
&error_string) == AUTHENTICATED) {
mppe_set_keys(challenge, session_key);
slprintf(message, message_space, "Access granted");
return AUTHENTICATED;
} else {
if (error_string) {
notice(error_string);
free(error_string);
}
slprintf(message, message_space, "E=691 R=1 C=%0.*B V=0",
challenge_len, challenge);
return NOT_AUTHENTICATED;
}
break;
}
case CHAP_MICROSOFT_V2:
{
MS_Chap2Response *rmd = (MS_Chap2Response *) response;
u_char Challenge[8];
u_char session_key[MD4_SIGNATURE_SIZE];
char *error_string = NULL;
if (response_len != MS_CHAP2_RESPONSE_LEN)
break; /* not even the right length */
ChallengeHash(rmd->PeerChallenge, challenge, user, Challenge);
/* ship off to winbind, and check */
if (run_ntlm_auth(username,
domain,
NULL,
NULL,
Challenge,
8,
NULL,
0,
rmd->NTResp,
sizeof(rmd->NTResp),
session_key,
&error_string) == AUTHENTICATED) {
GenerateAuthenticatorResponse(session_key,
rmd->NTResp, rmd->PeerChallenge,
challenge, user,
saresponse);
mppe_set_keys2(session_key, rmd->NTResp, MS_CHAP2_AUTHENTICATOR);
if (rmd->Flags[0]) {
slprintf(message, message_space, "S=%s", saresponse);
} else {
slprintf(message, message_space, "S=%s M=%s",
saresponse, "Access granted");
}
return AUTHENTICATED;
} else {
if (error_string) {
notice(error_string);
slprintf(message, message_space, "E=691 R=1 C=%0.*B V=0 M=%s",
challenge_len, challenge, error_string);
free(error_string);
} else {
slprintf(message, message_space, "E=691 R=1 C=%0.*B V=0 M=%s",
challenge_len, challenge, "Access denied");
}
return NOT_AUTHENTICATED;
}
break;
}
default:
error("WINBIND: Challenge type %u unsupported", digest->code);
}
return NOT_AUTHENTICATED;
}
static int
winbind_allowed_address(u_int32_t addr)
{
ipcp_options *wo = &ipcp_wantoptions[0];
if (wo->hisaddr !=0 && wo->hisaddr == addr) {
return 1;
}
return -1;
}