--- ./pppd/auth.c.radius Thu May 13 06:33:10 1999 +++ ./pppd/auth.c Thu Jul 8 23:16:52 1999 @@ -319,6 +319,7 @@ auth_state = s_down; if (auth_script_state == s_up && auth_script_pid == 0) { auth_script_state = s_down; + script_setenv("STOP_TIME", format_time(0)); auth_script(_PATH_AUTHDOWN); } for (i = 0; (protp = protocols[i]) != NULL; ++i) { @@ -432,6 +433,15 @@ } #endif +#ifdef USE_RADIUS + if ((useradacct || radius_authentic) && + radius_acct(unit, PW_STATUS_START) != OK_RC && !radacct_optional) { + radius_term_cause(PW_NAS_ERROR); + lcp_close(0, "Accounting failed"); + return; + } +#endif + phase = PHASE_NETWORK; #if 0 if (!demand) @@ -445,9 +455,14 @@ ++num_np_open; } - if (num_np_open == 0) + if (num_np_open == 0) { /* nothing to do */ +#ifdef USE_RADIUS + if (useradacct) + radius_term_cause(PW_SERVICE_UNAVAILABLE); +#endif lcp_close(0, "No network protocols running"); + } } /* @@ -606,6 +621,10 @@ { if (--num_np_open <= 0) { /* no further use for the link: shut up shop. */ +#ifdef USE_RADIUS + if (useradacct) + radius_term_cause(PW_SERVICE_UNAVAILABLE); +#endif lcp_close(0, "No network protocols running"); } } @@ -627,6 +646,10 @@ if (itime >= idle_time_limit) { /* link is idle: shut it down. */ notice("Terminating connection due to lack of activity."); +#ifdef USE_RADIUS + if (useradacct) + radius_term_cause(PW_ACCT_IDLE_TIMEOUT); +#endif lcp_close(0, "Link inactive"); status = EXIT_IDLE_TIMEOUT; } else { @@ -642,6 +665,10 @@ void *arg; { info("Connect time expired"); +#ifdef USE_RADIUS + if (useradacct) + radius_term_cause(PW_ACCT_SESSION_TIMEOUT); +#endif lcp_close(0, "Connect time expired"); /* Close connection */ status = EXIT_CONNECT_TIME; } @@ -685,9 +712,17 @@ * to authenticate the peer. */ lacks_ip = 0; - can_auth = wo->neg_upap && (uselogin || have_pap_secret(&lacks_ip)); + can_auth = wo->neg_upap && (uselogin || +#ifdef USE_RADIUS + useradius || +#endif + have_pap_secret(&lacks_ip)); if (!can_auth && wo->neg_chap) { - can_auth = have_chap_secret((explicit_remote? remote_name: NULL), + can_auth = +#ifdef USE_RADIUS + useradius || +#endif + have_chap_secret((explicit_remote? remote_name: NULL), our_name, 1, &lacks_ip); } @@ -765,6 +800,9 @@ char passwd[256], user[256]; char secret[MAXWORDLEN]; static int attempts = 0; +#ifdef USE_RADIUS + int n = 0; /* makes gcc happy */ +#endif /* * Make copies of apasswd and auser, then null-terminate them. @@ -789,8 +827,13 @@ } else { check_access(f, filename); - if (scan_authfile(f, user, our_name, secret, &addrs, filename) < 0 - || (!uselogin && secret[0] != 0 +#ifdef USE_RADIUS + if (((n = scan_authfile(f, user, our_name, secret, &addrs, filename)) < 0 + && !useradius) || (n >= 0 && +#else + if (scan_authfile(f, user, our_name, secret, &addrs, filename) < 0 || ( +#endif + !uselogin && secret[0] != 0 && (cryptpap || strcmp(passwd, secret) != 0) && strcmp(crypt(passwd, secret), secret) != 0)) { warn("PAP authentication failure for %s", user); @@ -799,6 +842,22 @@ fclose(f); } +#ifdef USE_RADIUS + if (useradius && ret == UPAP_AUTHACK && n < 0) { + ret = UPAP_AUTHNAK; + if (radius_init() != OK_RC) + error("RADIUS client initialization failed"); + else for (n = 1; n <= 2 && ret == UPAP_AUTHNAK; n++) + switch (radius_auth_method(n)) { + case 1: ret = plogin(user, passwd, msg, msglen); + break; + case 2: ret = radius_auth(unit, user, passwd, NULL, msg, msglen); + break; + } + if (ret == UPAP_AUTHNAK) + warn("PAP RADIUS auth failure for %s", user); + } else +#endif if (uselogin && ret == UPAP_AUTHACK) { ret = plogin(user, passwd, msg, msglen); if (ret == UPAP_AUTHNAK) { @@ -1389,7 +1448,17 @@ int unit; u_int32_t addr; { +#ifdef USE_RADIUS + ipcp_options *wo; +#endif + if (addresses[unit] == NULL) { +#ifdef USE_RADIUS + if (radius_authentic && !bad_ip_adrs(addr) && + (wo = &ipcp_wantoptions[unit])->hisaddr && + (wo->accept_remote || addr == wo->hisaddr)) + return 1; +#endif if (auth_required) return 0; /* no addresses authorized */ return allow_any_ip || !have_route_to(addr); --- ./pppd/chap.c.radius Fri Mar 19 06:19:30 1999 +++ ./pppd/chap.c Thu Jul 8 22:19:39 1999 @@ -110,7 +110,7 @@ static void ChapReceiveResponse __P((chap_state *, u_char *, int, int)); static void ChapReceiveSuccess __P((chap_state *, u_char *, int, int)); static void ChapReceiveFailure __P((chap_state *, u_char *, int, int)); -static void ChapSendStatus __P((chap_state *, int)); +static void ChapSendStatus __P((chap_state *, int, char *)); static void ChapSendChallenge __P((chap_state *)); static void ChapSendResponse __P((chap_state *)); static void ChapGenChallenge __P((chap_state *)); @@ -502,6 +502,12 @@ MD5_CTX mdContext; char secret[MAXSECRETLEN]; u_char hash[MD5_SIGNATURE_SIZE]; +#ifdef NT_DOMAIN_HACK + u_char *s; +#endif +#ifdef USE_RADIUS + char *msg = NULL; +#endif if (cstate->serverstate == CHAPSS_CLOSED || cstate->serverstate == CHAPSS_PENDING) { @@ -518,11 +524,11 @@ * as we did for the first Response we saw. */ if (cstate->serverstate == CHAPSS_OPEN) { - ChapSendStatus(cstate, CHAP_SUCCESS); + ChapSendStatus(cstate, CHAP_SUCCESS, NULL); return; } if (cstate->serverstate == CHAPSS_BADAUTH) { - ChapSendStatus(cstate, CHAP_FAILURE); + ChapSendStatus(cstate, CHAP_FAILURE, NULL); return; } @@ -542,9 +548,19 @@ UNTIMEOUT(ChapChallengeTimeout, cstate); +#ifdef NT_DOMAIN_HACK + if (strip_ntdomain && (s = memchr(rhostname, '\\', len)) != NULL) + len -= (int) (++s - (u_char *) rhostname); + else + s = inp; + if (len >= sizeof(rhostname)) + len = sizeof(rhostname) - 1; + BCOPY(s, rhostname, len); +#else if (len >= sizeof(rhostname)) len = sizeof(rhostname) - 1; BCOPY(inp, rhostname, len); +#endif rhostname[len] = '\000'; /* @@ -554,6 +570,13 @@ code = CHAP_FAILURE; if (!get_secret(cstate->unit, (explicit_remote? remote_name: rhostname), cstate->chal_name, secret, &secret_len, 1)) { +#ifdef USE_RADIUS + if (useradius) + code = radius_auth(cstate->unit, + explicit_remote ? remote_name : rhostname, + remmd, cstate, &msg, NULL); + else +#endif warn("No CHAP secret found for authenticating %q", rhostname); } else { @@ -580,7 +603,11 @@ } BZERO(secret, sizeof(secret)); - ChapSendStatus(cstate, code); +#ifdef USE_RADIUS + ChapSendStatus(cstate, code, msg); +#else + ChapSendStatus(cstate, code, NULL); +#endif if (code == CHAP_SUCCESS) { old_state = cstate->serverstate; @@ -702,18 +729,21 @@ * ChapSendStatus - Send a status response (ack or nak). */ static void -ChapSendStatus(cstate, code) +ChapSendStatus(cstate, code, msg) chap_state *cstate; int code; + char *msg; { u_char *outp; int outlen, msglen; - char msg[256]; + char msgbuf[256]; - if (code == CHAP_SUCCESS) - slprintf(msg, sizeof(msg), "Welcome to %s.", hostname); - else - slprintf(msg, sizeof(msg), "I don't like you. Go 'way."); + if (msg == NULL) + if (code == CHAP_SUCCESS) { + slprintf(msgbuf, sizeof(msgbuf), "Welcome to %s.", hostname); + msg = msgbuf; + } else + msg = "I don't like you. Go 'way."; msglen = strlen(msg); outlen = CHAP_HEADERLEN + msglen; --- ./pppd/pppd.h.radius Fri May 14 07:09:08 1999 +++ ./pppd/pppd.h Thu Jul 8 23:20:19 1999 @@ -31,6 +31,7 @@ #include /* for MAXPATHLEN and BSD4_4, if defined */ #include /* for u_int32_t, if defined */ #include /* for struct timeval */ +#include /* for struct utmp */ #include #if __STDC__ @@ -150,6 +151,7 @@ extern int log_to_fd; /* logging to this fd as well as syslog */ extern char *no_ppp_msg; /* message to print if ppp not in kernel */ extern volatile int status; /* exit status for pppd */ +extern struct timeval start_time; /* Time when link was started */ /* * Variables set by command-line options. @@ -193,6 +195,10 @@ extern struct bpf_program active_filter; /* Filter for link-active pkts */ #endif +#ifdef NT_DOMAIN_HACK +extern bool strip_ntdomain; /* Strip NT domain name from username */ +#endif + #ifdef MSLANMAN extern bool ms_lanman; /* Use LanMan password instead of NT */ /* Has meaning only with MS-CHAP challenges */ @@ -261,6 +267,7 @@ */ /* Procedures exported from main.c. */ +char *format_time __P((time_t)); void detach __P((void)); /* Detach from controlling tty */ void die __P((int)); /* Cleanup and exit */ void quit __P((void)); /* like die(1) */ @@ -391,6 +398,7 @@ void unlock __P((void)); /* Delete previously-created lock file */ void logwtmp __P((const char *, const char *, const char *)); /* Write entry to wtmp file */ +struct utmp *logutmp __P((const char *, const char *, const char *)); int get_host_seed __P((void)); /* Get host-dependent random number seed */ int have_route_to __P((u_int32_t)); /* Check if route to addr exists */ #ifdef PPP_FILTER @@ -601,6 +609,51 @@ #endif #ifndef MAX #define MAX(a, b) ((a) > (b)? (a): (b)) +#endif + +#ifdef USE_RADIUS + +#define PW_STATUS_START 1 +#define PW_STATUS_STOP 2 + +#define PW_USER_REQUEST 1 +#define PW_LOST_CARRIER 2 +#define PW_LOST_SERVICE 3 +#define PW_ACCT_IDLE_TIMEOUT 4 +#define PW_ACCT_SESSION_TIMEOUT 5 +#define PW_ADMIN_RESET 6 +#define PW_PORT_ERROR 8 +#define PW_NAS_ERROR 9 +#define PW_SERVICE_UNAVAILABLE 15 + +#define STATISTICS_INTERVAL 2 +#define RADIUS_PPP_UP (void *) 1 +#define RADIUS_PPP_DOWN (void *) 2 + +#define OK_RC 0 +#define ERROR_RC -1 + +extern bool useradius; /* Use RADIUS authentication */ +extern bool useradacct; /* Use RADIUS accounting */ +extern bool radacct_optional; /* Don't terminate if accounting failed */ +extern bool radius_authentic; /* RADIUS did authentication */ +extern char *running_as; /* Name of the user started pppd */ + +int radius_init __P((void)); +int radius_auth_method __P((int)); +void radius_get_stats __P((void *)); +void radius_term_cause __P((int)); +int radius_acct __P((int, int)); + +#include "chap.h" +int radius_auth __P((int, char *, char *, chap_state *, char **, int *)); + +#if defined(DEBUGALL) || defined(DEBUGRADIUS) +#define RADDEBUG(x) if (debug) dbglog x +#else +#define RADDEBUG(x) +#endif + #endif #endif /* __PPP_H__ */ --- ./pppd/options.c.radius Fri May 14 07:09:08 1999 +++ ./pppd/options.c Thu Jul 8 21:56:49 1999 @@ -92,6 +92,16 @@ bool sync_serial = 0; /* Device is synchronous serial device */ int log_to_fd = 1; /* send log messages to this fd too */ +#ifdef USE_RADIUS +bool useradius = 0; /* Use RADIUS server for authentication */ +bool useradacct = 0; /* Use RADIUS server for accounting */ +bool radacct_optional = 0; /* Don't terminate if accounting failed */ +#endif + +#ifdef NT_DOMAIN_HACK +bool strip_ntdomain = 0; /* Strip NT's "DOMAIN\" prefix from username */ +#endif + extern option_t auth_options[]; extern struct stat devstat; extern int prepass; /* Doing pre-pass to find device name */ @@ -234,6 +244,20 @@ "set filter for packets to pass" }, { "active-filter", 1, setactivefilter, "set filter for active pkts" }, +#endif + +#ifdef USE_RADIUS + { "radius", o_bool, &useradius, + "Use RADIUS authentication", 1 }, + { "radacct", o_bool, &useradacct, + "Use RADIUS accounting", 1 }, + { "radacct-optional", o_bool, &radacct_optional, + "Don't terminate if RADIUS accounting request failed", 1 }, +#endif + +#ifdef NT_DOMAIN_HACK + { "strip-nt-domain", o_bool, &strip_ntdomain, + "Strip NT domain prefix from username", 1 }, #endif { NULL } --- ./pppd/Makefile.linux.radius Thu May 13 06:35:19 1999 +++ ./pppd/Makefile.linux Thu Jul 8 22:23:31 1999 @@ -78,6 +78,23 @@ LIBS := -lpam -ldl $(LIBS) endif +ifdef NT_DOMAIN_HACK +CFLAGS += -DNT_DOMAIN_HACK=1 +endif + +ifdef USE_RADIUS +ifdef RADIUS_CONF +CFLAGS += -D_PATH_ETC_RADIUSCLIENT_CONF=\"$(RADIUS_CONF)\" +else +CFLAGS += -D_PATH_ETC_RADIUSCLIENT_CONF=\"/etc/radiusclient/ppp.conf\" +endif +CFLAGS += -DUSE_RADIUS=1 +INCLUDE_DIRS += -L/usr/local/include +LIBS += -L/usr/local/lib -lradiusclient +PPPDSRCS += radius.c +PPPDOBJS += radius.o +endif + # Lock library binary for Linux is included in 'linux' subdirectory. ifdef LOCKLIB LIBS := -llock $(LIBS) @@ -86,9 +103,9 @@ install: pppd mkdir -p $(BINDIR) $(MANDIR) - install -s -c -m 4550 -o root pppd $(BINDIR)/pppd - if ! chgrp pppusers $(BINDIR)/pppd 2>/dev/null; then \ - chmod o+rx $(BINDIR)/pppd; fi + install -s -c -m 4550 -o root pppd $(BINDIR)/pppd-radius + if ! chgrp pppusers $(BINDIR)/pppd-radius 2>/dev/null; then \ + chmod o+rx $(BINDIR)/pppd-radius; fi install -c -m 444 -o root pppd.8 $(MANDIR)/man8 pppd: $(PPPDOBJS) --- ./pppd/radius.c.radius Thu Jul 8 21:56:49 1999 +++ ./pppd/radius.c Fri Jul 9 00:31:13 1999 @@ -0,0 +1,588 @@ +/* + * Copyright (C) 1996, Matjaz Godec + * Copyright (C) 1996, Lars Fenneberg + * Copyright (C) 1997, Miguel A.L. Paraz + * Copyright (C) 1999, Vladimir Pastukhov + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _linux_ +#if __GLIBC__ >= 2 +#include +#include +#else +#include +#include +#endif +#include +#include +#else +#include +#include +#include +#endif + +#include "pppd.h" +#include "fsm.h" +#include "lcp.h" +#include "upap.h" +#include "chap.h" +#include "ipcp.h" +#include "ccp.h" +#include "pathnames.h" + +#include + +/* USRobotics Extensions */ + +#define VENDOR_USR 429 + +#define PW_USR_GATEWAY_IP_ADDRESS 0xBE66 /* ipaddr */ +#define PW_USR_PRIMARY_DNS_SERVER 0x900F /* ipaddr */ +#define PW_USR_SECONDARY_DNS_SERVER 0x9010 /* ipaddr */ +#define PW_USR_PRIMARY_NBNS_SERVER 0x9011 /* ipaddr */ +#define PW_USR_SECONDARY_NBNS_SERVER 0x9012 /* ipaddr */ + +bool radius_authentic = 0; +char *running_as; + +static int radius_called_init = 0; +static UINT4 client_port = -1; +static int term_cause = -1; +static char radius_user[MAXNAMELEN]; +static int rcvd_bytes; +static int sent_bytes; +static int rcvd_packets; +static int sent_packets; + +int +radius_init() +{ + RADDEBUG(("radius_init called")); + + if (radius_called_init) + return OK_RC; + + if (rc_read_config(_PATH_ETC_RADIUSCLIENT_CONF) != 0) + return ERROR_RC; + + if (rc_read_dictionary(rc_conf_str("dictionary")) != 0) + return ERROR_RC; + + if (rc_read_mapfile(rc_conf_str("mapfile")) != 0) + return ERROR_RC; + + radius_called_init = 1; + return OK_RC; +} + +int +radius_auth_method(i) + int i; +{ + int order; + + RADDEBUG(("radius_auth_method called: i=%d", i)); + + if (!uselogin) + return (i == 1 ? 2 : 0); + + order = rc_conf_int("auth_order"); + switch (i) { + case 1: return (order & AUTH_LOCAL_FST ? 1 : + (order & AUTH_RADIUS_FST ? 2 : 0)); + case 2: return (order & AUTH_LOCAL_SND ? 1 : + (order & AUTH_RADIUS_SND ? 2 : 0)); + } + return 0; +} + +/* + * Function that makes username_realm from user + * (defined at startup or in pap) + * for RADIUS we login in as username@realm eventualy so + * here we add an @realm part of username if not already + * specified. + */ + +void +radius_make_realm(user) + char *user; +{ + char *default_realm; + int n; + + RADDEBUG(("radius_make_realm called: user='%s'", user)); + + radius_user[sizeof(radius_user)-1] = '\0'; + if (user != NULL) + strncpy(radius_user, user, sizeof(radius_user)-1); + else + radius_user[0] = '\0'; + + if (strchr(radius_user, '@') == NULL && + (n = strlen(radius_user)) < sizeof(radius_user)-2 && + (default_realm = rc_conf_str("default_realm")) != NULL && + *default_realm != '\0') { + radius_user[n] = '@'; + strncpy(radius_user+n+1, default_realm, sizeof(radius_user)-n-2); + } +} + +int +radius_setparams(unit, vp) + int unit; + VALUE_PAIR *vp; +{ + u_int32_t ipaddr; + ipcp_options *wo, *ao; + DICT_VENDOR *ven; + + RADDEBUG(("radius_setparams called")); + + /* + * service type (if not framed then quit), + * framed protocol (if not ppp then quit), + * user's IP address, + * new netmask, + * new MTU, + * idle time limit, + * session time limit, + * port limit (quit if less than 1), + * local IP address (USR extension), + * primary and secondary DNS servers (USR extension), + * primary and secondary NetBIOS nameservers (USR extension) + * + */ + + wo = &ipcp_wantoptions[unit]; + ao = &ipcp_allowoptions[unit]; + + while (vp) { + RADDEBUG(("radius_setparams: received attribute '%s' (%d)", vp->name, vp->attribute)); + switch (vp->attribute) { + case PW_SERVICE_TYPE: + if (vp->lvalue != PW_FRAMED) { + error("RADIUS specified wrong service type %ld for %s", + vp->lvalue, radius_user); + return ERROR_RC; + } + break; + + case PW_FRAMED_PROTOCOL: + if (vp->lvalue != PW_PPP) { + error("RADIUS specified wrong framed protocol %ld for %s", + vp->lvalue, radius_user); + return ERROR_RC; + } + break; + + case PW_FRAMED_IP_ADDRESS: + /* 0xfffffffe means NAS should select an ip address */ + /* 0xffffffff means user should be allowed to select one */ + if (vp->lvalue == 0xffffffff) { + if (wo->hisaddr == 0) + wo->hisaddr = 0xffffffff; + wo->accept_remote = 1; + } else { + if (vp->lvalue != 0xfffffffe) { + if (bad_ip_adrs(ipaddr = htonl(vp->lvalue))) { + error("RADIUS specified invalid remote IP address %s for %s", + ip_ntoa(ipaddr), radius_user); + return ERROR_RC; + } + wo->hisaddr = ipaddr; + } + wo->accept_remote = 0; + } + break; + + case PW_FRAMED_IP_NETMASK: + if (netmask != htonl(vp->lvalue)) { + netmask = htonl(vp->lvalue); + notice("RADIUS have changed netmask to %s", ip_ntoa(netmask)); + } + break; + + case PW_FRAMED_MTU: + if (lcp_allowoptions[unit].mru != vp->lvalue) { + lcp_allowoptions[unit].mru = vp->lvalue; + notice("RADIUS have changed MTU to %d", lcp_allowoptions[unit].mru); + } + break; + + case PW_IDLE_TIMEOUT: + idle_time_limit = vp->lvalue; + break; + + case PW_SESSION_TIMEOUT: + maxconnect = vp->lvalue; + break; + + case PW_PORT_LIMIT: + if (vp->lvalue <= 0) { + notice("RADIUS offered no ports for %s", radius_user); + return ERROR_RC; + } + break; + + default: + if ((ven = rc_dict_attrvendor(vp->attribute)) != NULL && + ven->vendorpec == VENDOR_USR) + switch(VENDOR_SPECIFIC(vp->attribute)) { + + case PW_USR_GATEWAY_IP_ADDRESS: + if (vp->lvalue == 0xffffffff) + wo->accept_local = 1; + else { + if (vp->lvalue != 0xfffffffe) { + if (bad_ip_adrs(ipaddr = htonl(vp->lvalue))) { + error("RADIUS specified invalid local IP address %s for %s", + ip_ntoa(ipaddr), radius_user); + return ERROR_RC; + } + wo->ouraddr = ipaddr; + } + wo->accept_local = 0; + } + break; + + case PW_USR_PRIMARY_DNS_SERVER: + ao->dnsaddr[0] = htonl(vp->lvalue); + break; + + case PW_USR_SECONDARY_DNS_SERVER: + ao->dnsaddr[1] = htonl(vp->lvalue); + if (ao->dnsaddr[0] == 0) + ao->dnsaddr[0] = ao->dnsaddr[1]; + break; + + case PW_USR_PRIMARY_NBNS_SERVER: + ao->winsaddr[0] = htonl(vp->lvalue); + break; + + case PW_USR_SECONDARY_NBNS_SERVER: + ao->winsaddr[1] = htonl(vp->lvalue); + if (ao->winsaddr[0] == 0) + ao->winsaddr[0] = ao->winsaddr[1]; + break; + } + } + vp = vp->next; + } + return OK_RC; +} + +int +radius_buildenv(vp) + VALUE_PAIR *vp; +{ + char name[2048], value[2048]; /* more than enough */ + char *p; + int acount[256], attr, count; + + RADDEBUG(("radius_buildenv called")); + + script_setenv("RADIUS_USER_NAME", radius_user); + + while (vp) { + strcpy(name, "RADIUS_"); + if (rc_avpair_tostr(vp, name+7, sizeof(name)-7, value, + sizeof(value)) < 0) + return 1; + + /* translate "-" => "_" and uppercase */ + for (p = name; *p; p++) + if (*p != '-') + *p = toupper(*p); + else + *p = '_'; + + /* add to the attribute count and append the var if necessary */ + if ((attr = vp->attribute) < 256 && (count = acount[attr]++) > 0) + sprintf(name + strlen(name), "_%d", count); + + script_setenv(name, value); + vp = vp->next; + } + return 0; +} + +/* + * Check the user name and password against RADIUS server. + */ +int +radius_auth(unit, user, passwd, cstate, msg, msglen) + int unit; + char *user; + char *passwd; + chap_state *cstate; + char **msg; + int *msglen; +{ + static char radius_msg[4096]; + VALUE_PAIR *send, *received; + UINT4 av_type; + int result; + u_char cpassword[MD5_SIGNATURE_SIZE+1]; + char *s; + + RADDEBUG(("radius_auth called: user='%s', %s", user, + cstate == NULL ? "PAP" : "CHAP")); + + /* may be called twice under some mysterious conditions */ + if (radius_authentic) + return (cstate == NULL ? UPAP_AUTHACK : CHAP_SUCCESS); + + /* we handle only md5 digest at the moment */ + if (cstate != NULL && cstate->chal_type != CHAP_DIGEST_MD5) + return CHAP_FAILURE; + + /* read in the config files if neccessary */ + if (!user[0] || (!radius_called_init && radius_init() != OK_RC)) + return (cstate == NULL ? UPAP_AUTHNAK : CHAP_FAILURE); + + /* map tty to port */ + client_port = rc_map2id(devnam); + + send = NULL; + + /* send username to RADIUS */ + radius_make_realm(user); + rc_avpair_add(&send, PW_USER_NAME, radius_user, 0); + + if (cstate == NULL) { /* PAP */ + /* send password to RADIUS */ + rc_avpair_add(&send, PW_USER_PASSWORD, passwd, 0); + } else { /* CHAP */ + /* add the CHAP-Password and CHAP-Challenge fields */ + cpassword[0] = cstate->chal_id; + memcpy(&cpassword[1], passwd, MD5_SIGNATURE_SIZE); + rc_avpair_add(&send, PW_CHAP_PASSWORD, cpassword, MD5_SIGNATURE_SIZE + 1); + rc_avpair_add(&send, PW_CHAP_CHALLENGE, cstate->challenge, cstate->chal_len); + } + + /* service type and framed protocol */ + av_type = PW_FRAMED; + rc_avpair_add(&send, PW_SERVICE_TYPE, &av_type, 0); + av_type = PW_PPP; + rc_avpair_add(&send, PW_FRAMED_PROTOCOL, &av_type, 0); + + /* authenticate with RADIUS server */ + if ((result = rc_auth(client_port, send, &received, radius_msg)) == OK_RC) { + if (radius_setparams(unit, received) != OK_RC) + result = ERROR_RC; + else { + radius_authentic = 1; + /* build the environment for ip-up and ip-down */ + radius_buildenv(received); + info("user %s logged in with RADIUS", user); + } + rc_avpair_free(received); + } + /* free value pairs */ + rc_avpair_free(send); + + if (msg != NULL) + if (radius_msg != NULL && radius_msg[0]) { + for (s = radius_msg; *s; s++) + if (*s == '\n' || *s == '\r' || *s == '\t') + *s = ' '; + *msg = radius_msg; + if (msglen != NULL) + *msglen = s - radius_msg; + } else + *msg = NULL; + + if (cstate == NULL) + return (result == OK_RC ? UPAP_AUTHACK : UPAP_AUTHNAK); + else + return (result == OK_RC ? CHAP_SUCCESS : CHAP_FAILURE); +} + +/* + * Send accounting request to RADIUS server. + */ +int +radius_acct(unit, type) + int unit; + int type; +{ + static int radius_in = 0; + static char session_id[33]; + struct timeval now; + char *tty, *s; + char buf[100]; + int result; + UINT4 av_type; + u_int32_t ipaddr; + VALUE_PAIR *send; + + RADDEBUG(("radius_acct called")); + + if (type == PW_STATUS_START ? radius_in : !radius_in) + return ERROR_RC; + + tty = devnam; + if (strncmp(tty, "/dev/", 5) == 0) + tty += 5; + + if (type == PW_STATUS_START) { + radius_in = 1; + + /* IPCP negotiation isn't done yet - log just what we want */ + ipaddr = ipcp_wantoptions[unit].hisaddr; + /* generate an id for this session. + rc_mksid() doesn't need radius_init() stuff */ + strncpy(session_id, rc_mksid(), sizeof(session_id)-1); + session_id[sizeof(session_id)-1] = '\0'; + info("RADIUS session ID is %s", session_id); + /* 'who' supposes that substring before the colon is a host name/IP */ + snprintf(buf, sizeof(buf), "%s:PID=%d;SID=%s", ip_ntoa(ipaddr), getpid(), session_id); + + if (radius_authentic) { + /* add both utmp and wtmp login entries */ + logwtmp(tty, radius_user, buf); + } else { + radius_make_realm(peer_authname[0] ? peer_authname : running_as); + /* only update utmp (wtmp should have record already) */ + logutmp(tty, radius_user, buf); + } + } else { + radius_in = 0; + ipaddr = ipcp_hisoptions[unit].hisaddr; + + /* wipe out utmp/wtmp entries */ + if (radius_authentic) + logwtmp(tty, "", ""); + else + logutmp(tty, "", ""); + } + + if (!useradacct) + return OK_RC; + + /* read in the config files if neccessary */ + if (!radius_called_init && radius_init() != OK_RC) + return ERROR_RC; + + if (client_port < 0) + client_port = rc_map2id(devnam); + + gettimeofday(&now, NULL); + av_type = now.tv_sec - start_time.tv_sec; + send = NULL; + + if (type == PW_STATUS_START) { + rc_avpair_add(&send, PW_ACCT_DELAY_TIME, &av_type, 0); + } else { + rc_avpair_add(&send, PW_ACCT_SESSION_TIME, &av_type, 0); + + if (term_cause < 0) + term_cause = PW_USER_REQUEST; + rc_avpair_add(&send, PW_ACCT_TERMINATE_CAUSE, &term_cause, 0); + term_cause = -1; + + rc_avpair_add(&send, PW_ACCT_INPUT_OCTETS, &rcvd_bytes, 0); + rc_avpair_add(&send, PW_ACCT_OUTPUT_OCTETS, &sent_bytes, 0); + rc_avpair_add(&send, PW_ACCT_INPUT_PACKETS, &rcvd_packets, 0); + rc_avpair_add(&send, PW_ACCT_OUTPUT_PACKETS, &sent_packets, 0); + } + + rc_avpair_add(&send, PW_USER_NAME, radius_user, 0); + rc_avpair_add(&send, PW_ACCT_SESSION_ID, session_id, 0); + + av_type = ntohl(ipaddr); + rc_avpair_add(&send, PW_FRAMED_IP_ADDRESS, &av_type, 0); + av_type = PW_FRAMED; + rc_avpair_add(&send, PW_SERVICE_TYPE, &av_type, 0); + av_type = PW_PPP; + rc_avpair_add(&send, PW_FRAMED_PROTOCOL, &av_type, 0); + + rc_avpair_add(&send, PW_ACCT_STATUS_TYPE, (UINT4 *) &type, 0); + av_type = radius_authentic ? PW_RADIUS : PW_LOCAL; + rc_avpair_add(&send, PW_ACCT_AUTHENTIC, &av_type, 0); + + if ((s = getenv("CONNECT_INFO")) != NULL || (s = getenv("CONNECT")) != NULL) + rc_avpair_add(&send, PW_CONNECT_INFO, s, + strlen(s) < AUTH_STRING_LEN ? 0 : AUTH_STRING_LEN); + + if ((s = getenv("CALLER_ID")) != NULL) + rc_avpair_add(&send, PW_CALLING_STATION_ID, s, + strlen(s) < AUTH_STRING_LEN ? 0 : AUTH_STRING_LEN); + + if ((result = rc_acct(client_port, send)) != OK_RC) + error("RADIUS accounting %s failed for '%s'", + type == PW_STATUS_START ? "START" : "STOP", radius_user); + + rc_avpair_free(send); + return result; +} + +void +radius_term_cause(reason) + int reason; +{ + RADDEBUG(("radius_term_cause called: reason=%d", reason)); + + if (term_cause < 0) + term_cause = reason; +} + +/* + * This routine gets called every two seconds, and copies the + * number of bytes transferred. + */ +void +radius_get_stats(arg) + void *arg; +{ + struct ifpppstatsreq req; + static int s = -1; + + RADDEBUG(("radius_get_stats called: arg=%d", (int) arg)); + + if (arg != RADIUS_PPP_UP) + UNTIMEOUT(radius_get_stats, RADIUS_PPP_UP); + + if (s < 0) { + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + return; + rcvd_bytes = sent_bytes = rcvd_packets = sent_packets = 0; + } + + memset(&req, 0, sizeof(req)); +#ifdef __linux__ + req.stats_ptr = (caddr_t) &req.stats; +#undef ifr_name +#define ifr_name ifr__name +#endif + + sprintf(req.ifr_name, "ppp%d", ifunit); + if (ioctl(s, SIOCGPPPSTATS, &req) < 0) { + warn("SIOCGPPPSTATS(ppp%d) failed: %m", ifunit); + return; + } + + if (req.stats.p.ppp_ibytes) + rcvd_bytes = req.stats.p.ppp_ibytes; + if (req.stats.p.ppp_obytes) + sent_bytes = req.stats.p.ppp_obytes; + if (req.stats.p.ppp_ipackets) + rcvd_packets = req.stats.p.ppp_ipackets; + if (req.stats.p.ppp_opackets) + sent_packets = req.stats.p.ppp_opackets; + + if (arg != RADIUS_PPP_DOWN) + TIMEOUT(radius_get_stats, RADIUS_PPP_UP, STATISTICS_INTERVAL); +} --- ./pppd/main.c.radius Thu May 13 06:35:29 1999 +++ ./pppd/main.c Thu Jul 8 23:47:33 1999 @@ -117,7 +117,7 @@ GIDSET_TYPE groups[NGROUPS_MAX];/* groups the user is in */ int ngroups; /* How many groups valid in groups */ -static struct timeval start_time; /* Time when link was started. */ +struct timeval start_time; /* Time when link was started. */ struct pppd_stats link_stats; int link_connect_time; @@ -163,6 +163,7 @@ static void charshunt __P((int, int, char *)); static int record_write __P((FILE *, int code, u_char *buf, int nb, struct timeval *)); +static void setresourcelimit __P((int, int)); extern char *ttyname __P((int)); extern char *getlogin __P((void)); @@ -215,6 +216,11 @@ struct stat statbuf; char numbuf[16]; + setresourcelimit(RLIMIT_RSS, 1024*1024*2); + setresourcelimit(RLIMIT_DATA, 1024*1024); + setresourcelimit(RLIMIT_STACK, 1024*256); + setresourcelimit(RLIMIT_CPU, 120); + phase = PHASE_INITIALIZE; /* @@ -398,8 +404,15 @@ pw = getpwuid(uid); if (pw != NULL && pw->pw_name != NULL) p = pw->pw_name; - else - p = "(unknown)"; + } + if (p == NULL) { + p = "(unknown)"; +#ifdef USE_RADIUS + sprintf(numbuf, "(%d)", uid); + running_as = numbuf; + } else { + running_as = p; +#endif } syslog(LOG_NOTICE, "pppd %s.%d%s started by %s, uid %d", VERSION, PATCHLEVEL, IMPLEMENTATION, p, uid); @@ -699,7 +712,11 @@ /* reopen tty if necessary to wait for carrier */ if (connector == NULL && modem && devnam[0] != 0) { for (;;) { - if ((i = open(devnam, O_RDWR)) >= 0) + /* hack to stop mysterious hangs on open when not nonblock */ + alarm(60); + i = open(devnam, O_RDWR); + alarm(0); + if (i >= 0) break; if (errno != EINTR) { error("Failed to reopen %s: %m", devnam); @@ -726,6 +743,10 @@ status = EXIT_FATAL_ERROR; goto disconnect; } +#ifdef USE_RADIUS + if (useradacct) + radius_get_stats(RADIUS_PPP_UP); +#endif if (!demand) { @@ -743,6 +764,8 @@ notice("Connect: %s <--> %s", ifname, ppp_devnam); gettimeofday(&start_time, NULL); link_stats_valid = 0; + script_setenv("START_TIME", format_time(start_time.tv_sec)); + script_unsetenv("STOP_TIME"); script_unsetenv("CONNECT_TIME"); script_unsetenv("BYTES_SENT"); script_unsetenv("BYTES_RCVD"); @@ -810,10 +833,18 @@ */ remove_fd(fd_ppp); clean_check(); +#ifdef USE_RADIUS + if (useradacct) + radius_get_stats(RADIUS_PPP_DOWN); +#endif if (demand) restore_loop(); disestablish_ppp(ttyfd); fd_ppp = -1; +#ifdef USE_RADIUS + if (useradacct || radius_authentic) + radius_acct(0, PW_STATUS_STOP); +#endif if (!hungup) lcp_lowerdown(0); @@ -999,6 +1030,10 @@ notice("Modem hangup"); hungup = 1; status = EXIT_HANGUP; +#ifdef USE_RADIUS + if (useradacct) + radius_term_cause(PW_LOST_CARRIER); +#endif lcp_lowerdown(0); /* serial link is no longer available */ link_terminated(0); return; @@ -1076,10 +1111,18 @@ static void cleanup() { +#ifdef USE_RADIUS + if (useradacct) + radius_get_stats(RADIUS_PPP_DOWN); +#endif sys_cleanup(); if (fd_ppp >= 0) disestablish_ppp(ttyfd); +#ifdef USE_RADIUS + if (useradacct || radius_authentic) + radius_acct(0, PW_STATUS_STOP); +#endif if (real_ttyfd >= 0) close_tty(); @@ -1301,6 +1344,10 @@ kill_link = 1; if (status != EXIT_HANGUP) status = EXIT_USER_REQUEST; +#ifdef USE_RADIUS + if (useradacct) + radius_term_cause(PW_LOST_CARRIER); +#endif if (conn_running) /* Send the signal to the [dis]connector process(es) also */ kill_my_pg(sig); @@ -1325,6 +1372,10 @@ persist = 0; /* don't try to restart */ kill_link = 1; status = EXIT_USER_REQUEST; +#ifdef USE_RADIUS + if (useradacct) + radius_term_cause(PW_ADMIN_RESET); +#endif if (conn_running) /* Send the signal to the [dis]connector process(es) also */ kill_my_pg(sig); @@ -1397,6 +1448,10 @@ _exit(127); crashed = 1; error("Fatal signal %d", sig); +#ifdef USE_RADIUS + if (useradacct) + radius_term_cause(PW_NAS_ERROR); +#endif if (conn_running) kill_my_pg(SIGTERM); if (charshunt_pid) @@ -1741,6 +1796,38 @@ break; } } +} + +/* + * setresourcelimit - set the requested resource limit. + * Complain to syslog on errors. + */ +static void +setresourcelimit(resource, value) + int resource, value; +{ + struct rlimit rl; + + if (getrlimit(resource, &rl) < 0) { + error("getrlimit(%d) failed: %m", resource); + return; + } + rl.rlim_cur = value; + rl.rlim_max = value; + if (setrlimit(resource, &rl) < 0) + error("setrlimit(%d)=%d failed: %m", resource, value); +} + +char * +format_time(tm) + time_t tm; +{ + static char buf[25]; + + if (!tm) + tm = time(NULL); + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&tm)); + return buf; } /* --- ./pppd/lcp.c.radius Thu May 13 06:33:32 1999 +++ ./pppd/lcp.c Thu Jul 8 22:26:00 1999 @@ -1055,6 +1055,10 @@ if (looped_back) { if (++try.numloops >= lcp_loopbackfail) { notice("Serial line is looped back."); +#ifdef USE_RADIUS + if (useradacct) + radius_term_cause(PW_PORT_ERROR); +#endif lcp_close(f->unit, "Loopback detected"); status = EXIT_LOOPBACK; } @@ -1805,6 +1809,10 @@ if (f->state == OPENED) { info("No response to %d echo-requests", lcp_echos_pending); notice("Serial link appears to be disconnected."); +#ifdef USE_RADIUS + if (useradacct) + radius_term_cause(PW_LOST_CARRIER); +#endif lcp_close(f->unit, "Peer not responding"); status = EXIT_PEER_DEAD; } --- ./pppd/ipcp.c.radius Thu May 13 06:35:28 1999 +++ ./pppd/ipcp.c Thu Jul 8 21:56:50 1999 @@ -1562,6 +1562,11 @@ { u_int32_t ouraddr, hisaddr; +#ifdef USE_RADIUS + if (useradacct) + radius_get_stats(NULL); +#endif + ouraddr = ipcp_gotoptions[unit].ouraddr; hisaddr = ipcp_hisoptions[unit].hisaddr; if (proxy_arp_set[unit]) { --- ./pppd/sys-linux.c.radius Thu Jul 8 21:55:57 1999 +++ ./pppd/sys-linux.c Thu Jul 8 22:33:51 1999 @@ -1705,10 +1705,10 @@ * Update the wtmp file with the appropriate user name and tty device. */ -void logwtmp (const char *line, const char *name, const char *host) +struct utmp *logutmp (const char *line, const char *name, const char *host) { - int wtmp; - struct utmp ut, *utp; + struct utmp *utp; + static struct utmp ut; pid_t mypid = getpid(); /* * Update the signon database for users. @@ -1754,15 +1754,22 @@ pututline(&ut); endutent(); -/* - * Update the wtmp file. - */ + + return &ut; +} + +void logwtmp (const char *line, const char *name, const char *host) +{ + int wtmp; + struct utmp *utp; + + utp = logutmp (line, name, host); wtmp = open(_PATH_WTMP, O_APPEND|O_WRONLY); if (wtmp >= 0) { flock(wtmp, LOCK_EX); /* we really should check for error on the write for a full disk! */ - write (wtmp, (char *)&ut, sizeof(ut)); + write (wtmp, (char *)utp, sizeof(struct utmp)); close (wtmp); flock(wtmp, LOCK_UN); --- ./pppd/upap.c.radius Wed Apr 28 08:44:18 1999 +++ ./pppd/upap.c Thu Jul 8 21:56:50 1999 @@ -352,6 +352,9 @@ int retcode; char *msg; int msglen; +#ifdef NT_DOMAIN_HACK + char *s; +#endif if (u->us_serverstate < UPAPSS_LISTEN) return; @@ -384,6 +387,12 @@ } ruser = (char *) inp; INCPTR(ruserlen, inp); +#ifdef NT_DOMAIN_HACK + if (strip_ntdomain && (s = memchr(ruser, '\\', ruserlen)) != NULL) { + ruserlen -= (u_char) (++s - ruser); + ruser = s; + } +#endif GETCHAR(rpasswdlen, inp); if (len < rpasswdlen) { UPAPDEBUG(("pap_rauth: rcvd short packet.")); --- ./pppd/fsm.c.radius Tue Mar 16 08:14:25 1999 +++ ./pppd/fsm.c Thu Jul 8 22:28:33 1999 @@ -258,6 +258,10 @@ case ACKSENT: if (f->retransmits <= 0) { warn("%s: timeout sending Config-Requests\n", PROTO_NAME(f)); +#ifdef USE_RADIUS + if (useradacct && f->protocol == PPP_LCP) + radius_term_cause(PW_PORT_ERROR); +#endif f->state = STOPPED; if( (f->flags & OPT_PASSIVE) == 0 && f->callbacks->finished ) (*f->callbacks->finished)(f); @@ -562,6 +566,10 @@ info("%s terminated by peer (%0.*v)", PROTO_NAME(f), len, p); } else info("%s terminated by peer", PROTO_NAME(f)); +#ifdef USE_RADIUS + if (useradacct && f->protocol == PPP_LCP) + radius_term_cause(PW_USER_REQUEST); +#endif if (f->callbacks->down) (*f->callbacks->down)(f); /* Inform upper layers */ f->retransmits = 0; --- ./pppingerd.radius Thu Jul 8 21:56:50 1999 +++ ./pppingerd Fri Jul 9 00:58:37 1999 @@ -0,0 +1,169 @@ +#!/usr/bin/perl +# +# This script acts as a drop-in replacement for fingerd which +# can serve checkrad requests. checkrad is used by the Cistron +# RADIUS server to check if its idea of a user logged in on a +# certain port/NAS is correct if a double login is detected. +# +# The script should be run by inetd (or tcpd) normally on finger +# port, although any other free port may be used. Simple access +# control can be done from the "secrets" file, see comments +# inside sample file for more. +# +# All information about PPP users is taken from utmp as written +# there by the RADIUS-enabled pppd. Also the file that maps +# device names to ports is required - this should be the same +# as pppd uses. In fingerd mode only users that exist locally +# are shown, as usual. +# +# Command line switches: +# -n enable stripping NT's "DOMAIN\" prefix +# -w print welcome message on finger requests +# -u user user name to run finger as +# -g group group name to run finger as +# + +$run_as = 'nobody.nobody'; +$syslog = 'local2'; +$secrets = '/etc/radiusclient/secrets'; +$port_id_map = '/etc/radiusclient/port-id-map'; +$who = '/usr/bin/who'; +$finger = '/usr/bin/finger'; + +# +# End of configurable part +# + +use POSIX; +use Socket; +use Net::hostent; +use Sys::Hostname; +use Sys::Syslog qw(:DEFAULT setlogsock); +use Getopt::Std; +use Text::ParseWords; + +sub bail { + my @msg = @_; + my $me = $0; + + if ($syslog) { + setlogsock 'unix'; + $me =~ s|^.*/||; + openlog $me, 'pid', $syslog; + syslog 'err', "[%s] @msg", $addr ? inet_ntoa($addr) : 'unknown'; + closelog; + } + die "ERROR @msg\n"; +} + +my ($opt_n, $opt_w, $opt_u, $opt_g); +getopts('nwu:g:'); +($user, $group) = split(/[.:]/, $run_as, 2) if $run_as; +$user = $opt_u if $opt_u; +$group = $opt_g if $opt_g; +$| = 1; + +$addr = getpeername(STDIN) or bail "Hey, you don't exist: $!."; +$addr = (sockaddr_in($addr))[1]; + +undef $secret; +open SECRET, '<' . $secrets or bail "Oops, can't read my secrets: $!."; +SECRET: while () { + next if /^\s*(#.*)?$/; + chomp; + ($host, $input, @tail) = parse_line('\s+', 0, $_); + if ($host eq '*') { + $secret = $input; + map { $flag{$_} = 1 } @tail; + next SECRET; + } + for ( @{gethost($host)->addr_list} ) { + if ($addr eq $_) { + $secret = $input; + map { $flag{$_} = 1 } @tail; + last SECRET; + } + } +} +undef $input; +close SECRET; +bail "I don't talk to strangers, bye." unless defined $secret; + +if ($user) { ### drop priviledges + if ($group) { + $gid = getgrnam($group); + $( = $) = $gid . ' ' . $gid; + } + $< = $> = getpwnam($user); +} + +$input = <>; +close STDIN; +$input =~ s/[\r\n]*$//s; +bail "I don't understand what you say." + if $input !~ /^[A-Za-z0-9 \@\+\/.=_-]*$/s; + +if (($secret eq 'finger' || ($input ne $secret && $flag{'finger'})) + && $input !~ /\@/ && $finger) { ### fingerd mode + undef $secret; + + $nusers = 0; + @args = ('-m'); + for (split(/\s+/, $input)) { + if (/^\/[Ww]$/) { + push @args, '-l'; + } elsif (/^-[lmps]$/) { + push @args, $_; + } elsif (/^[^-]/) { + push @args, $_; + $nusers++; + } + } + bail "Whom do you want to finger?" unless $flag{'list'} or $nusers > 0; + + printf "Welcome to %s version %s at %s !\r\n\r\n", + (uname())[0,2], gethost(hostname())->name if $opt_w; + + bail "Can't fork: $!." unless defined ($pid = open FINGER, '-|'); + exec $finger, @args or bail "Can't exec: $!." unless $pid; + + while () { + s/\r?\n/\r\n/g; + print; + } + close FINGER; + exit 0; + +} elsif ($secret eq 'finger' || $input ne $secret) { + undef $secret; + bail "Do you want to know a secret?"; +} + +undef $secret; +undef $input; + +open MAP, '<' . $port_id_map + or bail "I really wonder who's that damn admin here because: $!."; +while () { + next if /^\s*(#.*)?$/; + ($dev, $port) = split; + $dev =~ s|^/dev/||; + $port{$dev} = $port; +} +close MAP; + +open WHO, $who . '|' or bail "Hmm, it's not really your day: $!."; +print "Login Port Device Login Time PID Session ID Location\n"; +while () { + ($login, $dev, $month, $mday, $time, $host) = split(/\s+/, $_, 6); + next unless $host and $host =~ /^\((.+):PID=([0-9]+);SID=([0-9A-Fa-f]+)\)$/; + ($host, $pid, $sid) = ($1, $2, $3); + $login =~ s/^.*?\\// if $opt_n; + + printf "%-10s %-5d %-7s %-3s %02d %-5s %-5d %-12s %s\n", + $login, $port{$dev} || '???', $dev, + $month, $mday, $time, $pid, $sid, $host; +} +close WHO; + +exit 0; --- ./README.RADIUS.radius Thu Jul 8 21:56:50 1999 +++ ./README.RADIUS Fri Jul 9 00:56:47 1999 @@ -0,0 +1,152 @@ + +RADIUS authentication and accounting for pppd 2.3.8 +=================================================== + +Tested only with Linux 2.0 + GNU libc 2.0 + Cistron RADIUS server +with PAP authentication. + +Installation +------------ + +Compiling: make USE_RADIUS=y + +Pppd uses radiusclient library (ftp.cityline.net:/pub/radiusclient) +by Lars Fenneberg for all communications with RADIUS server. It reads +configuration file "/etc/radiusclient/ppp.conf" by default, but you +may change file name at compile time: + +make USE_RADIUS=y RADIUS_CONF=/etc/ppp/radius.conf + +Windows NT machines often authenticate themselves as "DOMAIN\username". +Adding NT_DOMAIN_HACK=y to make command line will compile in code that +strips domain prefix (this feature does not require RADIUS support to +be enabled). + +Four new pppd options are available: + +radius - use RADIUS server for authentication +radacct - use RADIUS server for accounting +radacct-optional - don't terminate session if accounting failed +strip-nt-domain - strip NT's "DOMAIN\" prefix from the username + +You may specify only one of "radius" and "radacct" or both - +authentication without accounting and accounting without +authentication is possible. + +The rest of configuration is done in radiusclient config file; +please read radiusclient documentation for more. + +Cistron RADIUS daemon supports limitting number of simultaneous +sessions for a user account. To accomplish this, radiusd executes +the program called checkrad that queries NAS for a list of logged in +users. With pppd you may use included perl script "pppingerd" that +resembles finger daemon (in fact, it may be queried with an ordinary +finger client) to serve these requests; see comments inside it for +more. For access control the "secrets" file is used; see comments. + +To add pppd support to checkrad you will need to install Net::Finger +package from CPAN, then patch checkrad (the patch is included too) +and finally add an entry for each NAS that requires password to +naspasswd file. Login name field isn't required in this case, but +if it consists only of decimal digits it is treated as a TCP port +number pppingerd is listening at (this feature needs at least +Net::Finger version 1.03), otherwise it's not used. + +Authentication +-------------- + +The following attributes received with RADIUS authentication reply +are supported: + +Framed-IP-Address +Framed-IP-Netmask +Framed-MTU +Port-Limit +Session-Timeout +Idle-Timeout +USR-Gateway-IP-Address +USR-Primary_DNS_Server +USR-Secondary_DNS_Server +USR-Primary_NBNS_Server +USR-Secondary_NBNS_Server + +Framed-IP-Address may be set to 255.255.255.255 in which case pppd +will accept peer's idea of its IP, or to 255.255.255.254 to use IP +address given on the pppd's command line or in the config file. +The same is true for USR-Gateway-IP-Address attribute which is used +for specifying local IP address (I'm not sure it is intended for that; +if someone knows more please contact me [VP]). + +If Port-Limit is set and is less than 1 the session will be closed. + +All of these and the rest of the attributes received from RADIUS +server are available to scripts via environment variables RADIUS_*. +Also new variables START_TIME and STOP_TIME (the latter may be used +only in *-down scripts of course) were added. + +Accounting +---------- + +If you don't use PAP or CHAP authentication then RADIUS username +for accounting purpose is taken from the "user" option if it is set, +otherwise current login name is used. + +In accounting request these attributes may be set by pppd: + +User-Name +NAS-IP-Address +NAS-Port-Id +Service-Type +Framed-Protocol +Framed-IP-Address +Calling-Station-Id +Connect-Info +Acct-Status-Type +Acct-Delay-Time +Acct-Authentic +Acct-Session-Id +Acct-Session-Time +Acct-Terminate-Cause +Acct-Input-Octets +Acct-Output-Octets +Acct-Input-Packets +Acct-Output-Packets + +Calling-Station-Id is taken from the environment variable CALLER_ID +(mgetty), Connect-Info - from CONNECT (mgetty) or CONNECT_INFO +(portslave). + +TODO list +--------- + +1. Test CHAP code. + +2. Port to other systems (hopefully just Makefile changes). + +3. USR-Gateway-IP-Address stuff - is it relevant attribute??? + +4. (?) Add Framed-Filter-Id functionality. Currently filters + can be set up from *-up scripts using RADIUS_FRAMED_FILTER_ID + variable but I'm not sure if it's sufficient. + +5. Maybe all the attributes received with RADIUS authentication + reply should be included into pppd's accounting packets. + +6. Send an account "Alive" packet to radiusd after user's IP + address becomes known in case when the user is allowed to + select his address himself (does radiusclient support this?). + +Credits +------- + +The patch is heavily based on the work by these people: + +Lars Fenneberg - radiusclient library +Matjaz Godec - original pppd patch +Miguel A.L. Paraz - pppd patch modifications +Jon Lewis - setresourcelimit stuff +Miquel van Smoorenburg + - ppp statistics code (libpsr) and radiusd of course :) + + +08.07.1999 Vladimir Pastukhov --- ./secrets.radius Thu Jul 8 21:56:50 1999 +++ ./secrets Fri Jul 9 01:00:24 1999 @@ -0,0 +1,30 @@ +# +# secrets This file contains a list of hosts which are allowed to +# make pppingerd requests and their passwords. +# +# Description of the fields: +# +# * The first field is a valid hostname or IP address +# for the client. Special entry "*" matches all hosts. +# On no match access is denied. +# +# * The second field (separated by blanks or tabs) is the +# password (may be empty - ""). If set to "finger" the +# program will act as an ordinary finger daemon. +# To get list of active pppd users you may use command +# "finger password@host.isp.com" or just telnet +# to finger port and enter password. +# +# * The third field consists of optional flags: +# "finger" - if password doesn't match go to fingerd mode +# (check that password doesn't collide with +# any valid username) +# "list" - allow requests of the form "finger @host". +# + +# Host Name Password Options +#---------------- ------------- ------------ +#radius.isp.com testing123 +#admin.isp.com "i am cool" finger list + +* ppp finger list --- ./checkrad-pppd.patch.radius Thu Jul 8 21:56:50 1999 +++ ./checkrad-pppd.patch Fri Jul 9 01:51:47 1999 @@ -0,0 +1,125 @@ +--- ./raddb/naslist.orig Mon Nov 9 15:09:01 1998 ++++ ./raddb/naslist Wed Mar 3 15:54:16 1999 +@@ -10,8 +10,8 @@ + # short name we use in the logfiles for this NAS. + # * The third field defines what type of device it is. Valid + # values are "livingston", "portslave", "cisco", "computone", +-# "max40xx", "tc", "pathras", "usrhiper", "other". This is +-# used to find out how to detect double logins. ++# "max40xx", "tc", "pathras", "usrhiper", "pppd", "other". ++# This is used to find out how to detect double logins. + # + + # NAS Name Short Name Type +--- ./raddb/naspasswd.orig Tue Jun 23 21:32:06 1998 ++++ ./raddb/naspasswd Sun Mar 7 20:24:37 1999 +@@ -15,6 +15,10 @@ + # Blank lines and lines with '#' as the first + # character are ignored. + # ++# For a NAS of type pppd login_name is not used. However, ++# if it consists only of decimal digits it is treated as ++# a TCP port number to connect to. ++# + # WARNING: Always make sure that this file has the "-r------" permission. + # And, don't set the passwords on your other systems to the same + # passwords that can be found below. +--- ./src/checkrad.pl.orig Sat Nov 28 17:16:47 1998 ++++ ./src/checkrad.pl Mon Mar 8 01:12:44 1999 +@@ -20,6 +20,7 @@ + # pathras_telnet 0.1 Author: accdias@sst.com.br + # usrhiper_snmp 1.0 Author: igor@ipass.net + # multitech_snmp 1.0 Author: ehonzay@willmar.com ++# pppd_finger 1.0 Author: vlpast@mail.ru + # + # Config: $debug is the file you want to put debug messages in + # $snmpget is the location of your ``snmpget'' program +@@ -47,6 +48,8 @@ + unshift @INC, "/usr/local/lib/site_perl"; + eval "use Net::Telnet 3.00;"; + $::HAVE_NET_TELNET = ($@ eq ""); ++ eval "use Net::Finger;"; ++ $::HAVE_NET_FINGER = ($@ eq ""); + }; + + # +@@ -494,6 +497,60 @@ + ($login eq $ARGV[3]) ? 1 : 0; + } + ++sub pppd_finger { ++ my($passwd, $port, $hdr_seen) = ('', '', 0); ++ ++ sub my_log { ++ $_ = shift; ++ print LOG " pppd_finger: @_\n" if $debug; ++ print STDERR "pppd_finger: @_\n" if $_; ++ } ++ ++ unless ($::HAVE_NET_FINGER) { ++ my_log 1, "Net::Finger module not installed"; ++ return 2; ++ } ++ ++ if (open NASPASS, '<' . $naspass) { ++ while () { ++ next if /^\s*(#.*)?$/; ++ chomp; ++ split; ++ if ($_[0] eq $ARGV[1]) { ++ $passwd = $_[2]; ++ $port = ':' . $_[1] if $_[1] =~ /^[0-9]+$/; ++ last; ++ } ++ } ++ close NASPASS; ++ } ++ ++ foreach (finger($passwd . '@' . $ARGV[1] . $port)) { ++ chomp; ++ unless ($hdr_seen) { ++ unless (/^\s*login\s+port\b.+\bsession.?id\b/i) { ++ my_log 1, "unrecognized output from server: \"$_\""; ++ return 2; ++ } ++ $hdr_seen = 1; ++ next; ++ } ++ split; ++ if ($_[1] == $ARGV[2]) { ++ my_log 0, "\"$_[0]\" " . ($_[0] eq $ARGV[3] ? ++ "matches" : "doesn't match") . ++ " \"$ARGV[3]\" on port $ARGV[2] of $ARGV[1]"; ++ return ($_[0] eq $ARGV[3] ? 1 : 0); ++ } ++ } ++ ++ unless ($hdr_seen) { ++ my_log 1, "Net::Finger error: " . $Net::Finger::error; ++ return 2; ++ } ++ 0; ++} ++ + if ($debug) { + open(LOG, ">>$debug"); + $now = localtime; +@@ -527,6 +584,8 @@ + $ret = &pathras_telnet; + } elsif ($ARGV[0] eq 'usrhiper') { + $ret = &usrhiper_snmp; ++} elsif ($ARGV[0] eq 'pppd') { ++ $ret = &pppd_finger; + } elsif ($ARGV[0] eq 'other') { + $ret = 0; + } else { +--- ./doc/README.simul.orig Sat Oct 17 21:41:48 1998 ++++ ./doc/README.simul Sun Mar 7 19:25:40 1999 +@@ -80,6 +80,7 @@ + tc USR/3com telnet CPAN Net::Telnet Yes + pathras Cyclades telnet CPAN Net::Telnet Yes + usrhyper USR/3com SNMP CMU SNMP snmpget No [2] ++ pppd - finger CPAN Net::Finger Optional + + [1] Needs at least ComOS 3.5, SNMP enabled. + [2] Set "Reported Port Desity" to 256 (default)