/* * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* ----------------------------------------------------------------------------- * * Theory of operation : * * plugin to add support for authentication against a radius server. * ----------------------------------------------------------------------------- */ /* ----------------------------------------------------------------------------- Includes ----------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* struct iovec */ #include #include #include #define APPLE 1 #include "../../Helpers/pppd/pppd.h" #include "../../Helpers/pppd/fsm.h" #include "../../Helpers/pppd/lcp.h" #include "../../Helpers/pppd/chap-new.h" #include "../../Helpers/pppd/chap_ms.h" #include "../../Helpers/pppd/md5.h" #include "../../Family/ppp_comp.h" #include "radlib.h" #include "radlib_vs.h" /* ----------------------------------------------------------------------------- Definitions ----------------------------------------------------------------------------- */ /* ----------------------------------------------------------------------------- Forward declarations ----------------------------------------------------------------------------- */ static int radius_papchap_check(); static int radius_pap_auth(char *user, char *passwd, char **msgp, struct wordlist **paddrs, struct wordlist **popts); static int radius_chap_auth(char *name, char *ourname, int id, struct chap_digest_type *digest, unsigned char *challenge, unsigned char *response, unsigned char *message, int message_space); static int radius_ip_allowed_address(u_int32_t addr); static void radius_ip_choose(u_int32_t *addr); static void radius_ip_up(void *arg, int p); static void radius_ip_down(void *arg, int p); static int radius_decryptmppekey(char *key, u_int8_t *attr_value, size_t attr_len, char *secret, char *authenticator); /* ----------------------------------------------------------------------------- PPP globals ----------------------------------------------------------------------------- */ extern u_char mppe_send_key[MPPE_MAX_KEY_LEN]; extern u_char mppe_recv_key[MPPE_MAX_KEY_LEN]; extern int mppe_keys_set; /* Have the MPPE keys been set? */ static CFBundleRef bundle = 0; /* our bundle ref */ /* option variables */ // all the settings are adjustable for each authentication server // primary authentication server static int pri_auth_port = 0; // by default, let the radius library decide static int pri_auth_timeout = 10; // 10 seconds timeout static int pri_auth_retries = 4; // try 4 times each server static u_char *pri_auth_server = NULL; // by default, let the library use the system files static u_char *pri_auth_secret = NULL; // by default, let the library use the system files // secondary authentication server static int sec_auth_port = 0; // if null, use primary port static int sec_auth_timeout = 0; // if null, use primary timeout static int sec_auth_retries = 0; // if null, use primary retries static u_char *sec_auth_server = NULL; // by default, let the library use the system files static u_char *sec_auth_secret = NULL; // if null, use primary secret // reponses from the radius server //static struct in_addr user_ipaddr; /* ip address for the connecting user */ int (*old_pap_auth_hook) __P((char *user, char *passwd, char **msgp, struct wordlist **paddrs, struct wordlist **popts)); int (*old_chap_verify_hook) __P((char *name, char *ourname, int id, struct chap_digest_type *digest, unsigned char *challenge, unsigned char *response, unsigned char *message, int message_space)); /* option descriptors */ option_t radius_options[] = { // primary authentication server { "radius_primary_auth_server", o_string, &pri_auth_server, "Primary radius server" }, { "radius_primary_auth_secret", o_string, &pri_auth_secret, "Primary radius server secret", OPT_HIDE }, { "radius_primary_auth_port", o_int, &pri_auth_port, "Port for server" }, { "radius_primary_auth_timeout", o_int, &pri_auth_timeout, "Timeout to contact server" }, { "radius_primary_auth_retries", o_int, &pri_auth_retries, "Retries to contact server" }, // secondary authentication server { "radius_secondary_auth_server", o_string, &sec_auth_server, "Primary radius server" }, { "radius_secondary_auth_secret", o_string, &sec_auth_secret, "Primary radius server secret", OPT_HIDE }, { "radius_secondary_auth_port", o_int, &sec_auth_port, "Port for server" }, { "radius_secondary_auth_timeout", o_int, &sec_auth_timeout, "Timeout to contact server" }, { "radius_secondary_auth_retries", o_int, &sec_auth_retries, "Retries to contact server" }, // shorter parameters... { "radius_primary", o_string, &pri_auth_server, "Primary radius server" }, { "radius_secondary", o_string, &sec_auth_server, "Secondary radius server" }, { "radius_secret", o_string, &pri_auth_secret, "Radius server", OPT_HIDE}, { "radius_port", o_int, &pri_auth_port, "Port for server" }, { "radius_timeout", o_int, &pri_auth_timeout, "Timeout to contact server" }, { "radius_retries", o_int, &pri_auth_retries, "Retries to contact server" }, { NULL } }; /* ----------------------------------------------------------------------------- plugin entry point, called by pppd ----------------------------------------------------------------------------- */ int start(CFBundleRef ref) { bundle = ref; CFRetain(bundle); // hookup our handlers pap_check_hook = radius_papchap_check; chap_check_hook = radius_papchap_check; old_pap_auth_hook = pap_auth_hook; pap_auth_hook = radius_pap_auth; old_chap_verify_hook = chap_verify_hook; chap_verify_hook = radius_chap_auth; ip_choose_hook = radius_ip_choose; allowed_address_hook = radius_ip_allowed_address; add_notifier(&ip_up_notify, radius_ip_up, 0); add_notifier(&ip_down_notify, radius_ip_down, 0); // add the radius specific options add_options(radius_options); return 0; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static int radius_papchap_check() { // radius will provide authentication // may be we should check for valid radius settings at this point ? return 1; } /* ----------------------------------------------------------------------------- type : 0 = PAP, clear text 5 = CHAP_DIGEST_MD5 0x80 = CHAP_MICROSOFT 0x81 = CHAP_MICROSOFT_V2 ----------------------------------------------------------------------------- */ static int radius_authenticate_user(char *user, char *passwd, int type, char *challenge, int chal_len, int chal_id, void *remotemd, int remotemd_len, unsigned char *message, int message_space) { struct rad_handle *h; int err, ret = 0, attr_type; void *attr_value; size_t attr_len; u_int32_t attr_vendor; char buf[MS_CHAP_RESPONSE_LEN + 1]; char auth[MD4_SIGNATURE_SIZE + 1]; // do some option checking in case not all the options are supplied if (sec_auth_server) { // transfer primary options to secondary server options, if they are not set if (sec_auth_secret == NULL) sec_auth_secret = pri_auth_secret; if (sec_auth_port == 0) sec_auth_port = pri_auth_port; if (sec_auth_timeout == 0) sec_auth_timeout = pri_auth_timeout; if (sec_auth_retries == 0) sec_auth_retries = pri_auth_retries; } h = rad_auth_open(); if (h == NULL) novm("Radius : can't open context.\n"); // will die... if (pri_auth_server) { err = rad_add_server(h, pri_auth_server, pri_auth_port, pri_auth_secret, pri_auth_timeout, pri_auth_retries); if (err != 0) { error("Radius : Can't use primary server '%s'\n", pri_auth_server); goto done; } } if (sec_auth_server) { err = rad_add_server(h, sec_auth_server, sec_auth_port, sec_auth_secret, sec_auth_timeout, sec_auth_retries); if (err != 0) { error("Radius : Can't use secondary server '%s'\n", sec_auth_server); // continue with primary server only } } rad_create_request(h, RAD_ACCESS_REQUEST); rad_put_string(h, RAD_USER_NAME, user); switch (type) { case 0: /* PAP, clear text */ rad_put_string(h, RAD_USER_PASSWORD, passwd); break; case CHAP_MD5: #define MD5_HASH_SIZE 16 rad_put_attr(h, RAD_CHAP_CHALLENGE, challenge, chal_len); buf[0] = chal_id; memcpy(&buf[1], remotemd, MD5_HASH_SIZE); rad_put_attr(h, RAD_CHAP_PASSWORD, buf, MD5_HASH_SIZE + 1); break; case CHAP_MICROSOFT: break; case CHAP_MICROSOFT_V2: rad_put_vendor_attr(h, RAD_VENDOR_MICROSOFT, RAD_MICROSOFT_MS_CHAP_CHALLENGE, challenge, chal_len); /* MSCHAP and MSCHAPv2 use the same structure length fields UseNT and Flags are at the same place MSCHAP : first put id and UseNT, then LANManResp and NTResp MSCHAPv2 : put id and Flags, then PeerChallenge, Reserved and NTResp */ buf[0] = chal_id; buf[1] = ((MS_ChapResponse *) remotemd)->UseNT[0]; memcpy(&buf[2], remotemd, MS_CHAP_RESPONSE_LEN - 1); rad_put_vendor_attr(h, RAD_VENDOR_MICROSOFT, type == CHAP_MICROSOFT ? RAD_MICROSOFT_MS_CHAP_RESPONSE : RAD_MICROSOFT_MS_CHAP2_RESPONSE, buf, MS_CHAP_RESPONSE_LEN + 1); break; } #if 0 if (remoteaddress) rad_put_string(h, RAD_CALLING_STATION_ID, remoteaddress); #endif rad_put_int(h, RAD_SERVICE_TYPE, RAD_FRAMED); rad_put_int(h, RAD_FRAMED_PROTOCOL, RAD_PPP); err = rad_send_request(h); switch (err) { case RAD_ACCESS_ACCEPT: /* TO DO: fetch interesting information from the response */ ret = 1; switch (type) { case 0: /* PAP, clear text */ break; case CHAP_MD5: slprintf(message, message_space, "Access granted"); break; case CHAP_MICROSOFT: case CHAP_MICROSOFT_V2: while ((attr_type = rad_get_attr(h, (const void **)&attr_value, &attr_len)) > 0 ) { switch (attr_type) { case RAD_VENDOR_SPECIFIC: attr_type = rad_get_vendor_attr(&attr_vendor, (const void **)&attr_value, &attr_len); switch (attr_type) { case RAD_MICROSOFT_MS_MPPE_SEND_KEY: rad_request_authenticator(h, auth, sizeof(auth)); radius_decryptmppekey(mppe_send_key, attr_value, attr_len, (u_char*)rad_server_secret(h), auth); mppe_keys_set = 1; break; case RAD_MICROSOFT_MS_MPPE_RECV_KEY: rad_request_authenticator(h, auth, sizeof(auth)); radius_decryptmppekey(mppe_recv_key, attr_value, attr_len, (u_char*)rad_server_secret(h), auth); mppe_keys_set = 1; break; case RAD_MICROSOFT_MS_CHAP2_SUCCESS: if ((attr_len - 1) < message_space) { memcpy(message, attr_value + 1, attr_len - 1); message[attr_len - 1] = 0; } break; } break; } } break; } break; case RAD_ACCESS_REJECT: switch (type) { case 0: /* PAP, clear text */ break; case CHAP_MD5: slprintf(message, message_space, "Access denied"); break; case CHAP_MICROSOFT: case CHAP_MICROSOFT_V2: break; } break; case RAD_ACCESS_CHALLENGE: error("Radius : Received Access Challenge\n"); break; default: error("Radius : Authentication error %d. %s.\n", err, rad_strerror(h)); } done: if (h) rad_close(h); return ret; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static int radius_pap_auth(char *user, char *passwd, char **msgp, struct wordlist **paddrs, struct wordlist **popts) { return radius_authenticate_user(user, passwd, 0, 0, 0, 0, 0, 0, 0, 0); } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static int radius_chap_auth(char *user, char *ourname, int id, struct chap_digest_type *digest, unsigned char *challenge, unsigned char *response, unsigned char *message, int message_space) { int challenge_len = *challenge++; int response_len = *response++; return radius_authenticate_user(user, 0, digest->code, challenge, challenge_len, id, response, response_len, message, message_space); } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static int radius_ip_allowed_address(u_int32_t addr) { // any address is OK // need to implement address option in radius packet return 1; } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void radius_ip_choose(u_int32_t* addr) { // need to implement address option in radius packet } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void radius_ip_up(void *arg, int p) { // need to implement accounting } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static void radius_ip_down(void *arg, int p) { // need to implement accounting } /* ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- */ static int radius_decryptmppekey(char *key, u_int8_t *attr_value, size_t attr_len, char *secret, char *authenticator) { int i; u_char plain[32]; u_char buf[16]; MD5_CTX ctx; memcpy(plain, attr_value + 2, sizeof(plain)); /* key string */ MD5Init(&ctx); MD5Update(&ctx, secret, strlen(secret)); MD5Update(&ctx, authenticator, strlen(authenticator)); MD5Update(&ctx, attr_value, 2); /* salt */ MD5Final(buf, &ctx); for (i = 0; i < 16; i++) plain[i] ^= buf[i]; MD5Init(&ctx); MD5Update(&ctx, secret, strlen(secret)); MD5Update(&ctx, attr_value + 2, 16); /* key string */ MD5Final(buf, &ctx); for (i = 0; i < 16; i++) plain[16 + i] ^= buf[i]; memcpy(key, plain + 1, 16); return 0; }