freebsd-dev/usr.sbin/pppd/auth.c
peter 65e05c6a3f utmp.ut_time and lastlog.ll_time are explicitly int32_t rather than
time_t.  Deal with the possibility that time_t != int32_t.  This boils
down to this sort of thing:
 -   time(&ut.ut_time);
 +   ut.ut_time = time(NULL);
and similar for ctime(3) etc.  I've kept it minimal for the stuff
that may need to be portable (or 3rd party code), but used Matt's time32
stuff for cases where that isn't as much of a concern.

Approved by: re (jhb)
2002-11-15 22:42:00 +00:00

1638 lines
38 KiB
C

/*
* auth.c - PPP authentication and phase control.
*
* Copyright (c) 1993 The Australian National University.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the Australian National University. The name of the University
* may not be used to endorse or promote products derived from this
* software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* Copyright (c) 1989 Carnegie Mellon University.
* All rights reserved.
*
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by Carnegie Mellon University. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef lint
static char rcsid[] = "$FreeBSD$";
#endif
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <paths.h>
#include <pwd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <utmp.h>
#include <fcntl.h>
#if defined(_PATH_LASTLOG) && defined(_linux_)
#include <lastlog.h>
#endif
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <utmp.h>
#ifdef USE_PAM
#include <security/pam_appl.h>
#endif
#ifdef HAS_SHADOW
#include <shadow.h>
#ifndef PW_PPP
#define PW_PPP PW_LOGIN
#endif
#endif
#include "pppd.h"
#include "fsm.h"
#include "lcp.h"
#include "ipcp.h"
#include "upap.h"
#include "chap.h"
#ifdef CBCP_SUPPORT
#include "cbcp.h"
#endif
#include "pathnames.h"
/* Used for storing a sequence of words. Usually malloced. */
struct wordlist {
struct wordlist *next;
char word[1];
};
/* Bits in scan_authfile return value */
#define NONWILD_SERVER 1
#define NONWILD_CLIENT 2
#define ISWILD(word) (word[0] == '*' && word[1] == 0)
#define FALSE 0
#define TRUE 1
/* The name by which the peer authenticated itself to us. */
char peer_authname[MAXNAMELEN];
/* Records which authentication operations haven't completed yet. */
static int auth_pending[NUM_PPP];
/* Set if we have successfully called plogin() */
static int logged_in;
/* Set if not wild or blank */
static int non_wildclient;
/* Set if we have run the /etc/ppp/auth-up script. */
static int did_authup;
/* List of addresses which the peer may use. */
static struct wordlist *addresses[NUM_PPP];
/* Number of network protocols which we have opened. */
static int num_np_open;
/* Number of network protocols which have come up. */
static int num_np_up;
/* Set if we got the contents of passwd[] from the pap-secrets file. */
static int passwd_from_file;
/* Bits in auth_pending[] */
#define PAP_WITHPEER 1
#define PAP_PEER 2
#define CHAP_WITHPEER 4
#define CHAP_PEER 8
extern char *crypt __P((const char *, const char *));
/* Prototypes for procedures local to this file. */
static void network_phase __P((int));
static void check_idle __P((void *));
static void connect_time_expired __P((void *));
static int plogin __P((char *, char *, char **, int *));
static void plogout __P((void));
static int null_login __P((int));
static int get_pap_passwd __P((char *));
static int have_pap_secret __P((void));
static int have_chap_secret __P((char *, char *, u_int32_t));
static int ip_addr_check __P((u_int32_t, struct wordlist *));
static int scan_authfile __P((FILE *, char *, char *, u_int32_t, char *,
struct wordlist **, char *));
static void free_wordlist __P((struct wordlist *));
static void auth_set_ip_addr __P((int));
static void auth_script __P((char *));
static void set_allowed_addrs __P((int, struct wordlist *));
/*
* An Open on LCP has requested a change from Dead to Establish phase.
* Do what's necessary to bring the physical layer up.
*/
void
link_required(unit)
int unit;
{
}
/*
* LCP has terminated the link; go to the Dead phase and take the
* physical layer down.
*/
void
link_terminated(unit)
int unit;
{
extern time_t etime, stime;
extern int minutes;
if (phase == PHASE_DEAD)
return;
if (logged_in)
plogout();
phase = PHASE_DEAD;
etime = time((time_t *) NULL);
minutes = (etime-stime)/60;
syslog(LOG_NOTICE, "Connection terminated, connected for %d minutes\n",
minutes > 1 ? minutes : 1);
}
/*
* LCP has gone down; it will either die or try to re-establish.
*/
void
link_down(unit)
int unit;
{
int i;
struct protent *protp;
if (did_authup) {
auth_script(_PATH_AUTHDOWN);
did_authup = 0;
}
for (i = 0; (protp = protocols[i]) != NULL; ++i) {
if (!protp->enabled_flag)
continue;
if (protp->protocol != PPP_LCP && protp->lowerdown != NULL)
(*protp->lowerdown)(unit);
if (protp->protocol < 0xC000 && protp->close != NULL)
(*protp->close)(unit, "LCP down");
}
num_np_open = 0;
num_np_up = 0;
if (phase != PHASE_DEAD)
phase = PHASE_TERMINATE;
}
/*
* The link is established.
* Proceed to the Dead, Authenticate or Network phase as appropriate.
*/
void
link_established(unit)
int unit;
{
int auth;
lcp_options *wo = &lcp_wantoptions[unit];
lcp_options *go = &lcp_gotoptions[unit];
lcp_options *ho = &lcp_hisoptions[unit];
int i;
struct protent *protp;
/*
* Tell higher-level protocols that LCP is up.
*/
for (i = 0; (protp = protocols[i]) != NULL; ++i)
if (protp->protocol != PPP_LCP && protp->enabled_flag
&& protp->lowerup != NULL)
(*protp->lowerup)(unit);
if (auth_required && !(go->neg_chap || go->neg_upap)) {
/*
* We wanted the peer to authenticate itself, and it refused:
* treat it as though it authenticated with PAP using a username
* of "" and a password of "". If that's not OK, boot it out.
*/
if (!wo->neg_upap || !null_login(unit)) {
syslog(LOG_WARNING, "peer refused to authenticate");
lcp_close(unit, "peer refused to authenticate");
return;
}
}
phase = PHASE_AUTHENTICATE;
auth = 0;
if (go->neg_chap) {
ChapAuthPeer(unit, our_name, go->chap_mdtype);
auth |= CHAP_PEER;
} else if (go->neg_upap) {
upap_authpeer(unit);
auth |= PAP_PEER;
}
if (ho->neg_chap) {
ChapAuthWithPeer(unit, user, ho->chap_mdtype);
auth |= CHAP_WITHPEER;
} else if (ho->neg_upap) {
if (passwd[0] == 0) {
passwd_from_file = 1;
if (!get_pap_passwd(passwd))
syslog(LOG_ERR, "No secret found for PAP login");
}
upap_authwithpeer(unit, user, passwd);
auth |= PAP_WITHPEER;
}
auth_pending[unit] = auth;
if (!auth)
network_phase(unit);
}
/*
* Proceed to the network phase.
*/
static void
network_phase(unit)
int unit;
{
int i;
struct protent *protp;
lcp_options *go = &lcp_gotoptions[unit];
/*
* If the peer had to authenticate, run the auth-up script now.
*/
if ((go->neg_chap || go->neg_upap) && !did_authup) {
auth_script(_PATH_AUTHUP);
did_authup = 1;
}
#ifdef CBCP_SUPPORT
/*
* If we negotiated callback, do it now.
*/
if (go->neg_cbcp) {
phase = PHASE_CALLBACK;
(*cbcp_protent.open)(unit);
return;
}
#endif
phase = PHASE_NETWORK;
#if 0
if (!demand)
set_filters(&pass_filter, &active_filter);
#endif
for (i = 0; (protp = protocols[i]) != NULL; ++i)
if (protp->protocol < 0xC000 && protp->enabled_flag
&& protp->open != NULL) {
(*protp->open)(unit);
if (protp->protocol != PPP_CCP)
++num_np_open;
}
if (num_np_open == 0)
/* nothing to do */
lcp_close(0, "No network protocols running");
}
/*
* The peer has failed to authenticate himself using `protocol'.
*/
void
auth_peer_fail(unit, protocol)
int unit, protocol;
{
/*
* Authentication failure: take the link down
*/
lcp_close(unit, "Authentication failed");
}
/*
* The peer has been successfully authenticated using `protocol'.
*/
void
auth_peer_success(unit, protocol, name, namelen)
int unit, protocol;
char *name;
int namelen;
{
int bit;
switch (protocol) {
case PPP_CHAP:
bit = CHAP_PEER;
break;
case PPP_PAP:
bit = PAP_PEER;
break;
default:
syslog(LOG_WARNING, "auth_peer_success: unknown protocol %x",
protocol);
return;
}
/*
* Save the authenticated name of the peer for later.
*/
if (namelen > sizeof(peer_authname) - 1)
namelen = sizeof(peer_authname) - 1;
BCOPY(name, peer_authname, namelen);
peer_authname[namelen] = 0;
/*
* If we have overridden addresses based on auth info
* then set that information now before continuing.
*/
auth_set_ip_addr(unit);
script_setenv("PEERNAME", peer_authname);
/*
* If there is no more authentication still to be done,
* proceed to the network (or callback) phase.
*/
if ((auth_pending[unit] &= ~bit) == 0)
network_phase(unit);
}
/*
* We have failed to authenticate ourselves to the peer using `protocol'.
*/
void
auth_withpeer_fail(unit, protocol)
int unit, protocol;
{
if (passwd_from_file)
BZERO(passwd, MAXSECRETLEN);
/*
* We've failed to authenticate ourselves to our peer.
* He'll probably take the link down, and there's not much
* we can do except wait for that.
*/
}
/*
* We have successfully authenticated ourselves with the peer using `protocol'.
*/
void
auth_withpeer_success(unit, protocol)
int unit, protocol;
{
int bit;
switch (protocol) {
case PPP_CHAP:
bit = CHAP_WITHPEER;
break;
case PPP_PAP:
if (passwd_from_file)
BZERO(passwd, MAXSECRETLEN);
bit = PAP_WITHPEER;
break;
default:
syslog(LOG_WARNING, "auth_peer_success: unknown protocol %x",
protocol);
bit = 0;
}
/*
* If we have overridden addresses based on auth info
* then set that information now before continuing.
*/
auth_set_ip_addr(unit);
/*
* If there is no more authentication still being done,
* proceed to the network (or callback) phase.
*/
if ((auth_pending[unit] &= ~bit) == 0)
network_phase(unit);
}
/*
* np_up - a network protocol has come up.
*/
void
np_up(unit, proto)
int unit, proto;
{
if (num_np_up == 0) {
/*
* At this point we consider that the link has come up successfully.
*/
need_holdoff = 0;
if (idle_time_limit > 0)
TIMEOUT(check_idle, NULL, idle_time_limit);
/*
* Set a timeout to close the connection once the maximum
* connect time has expired.
*/
if (maxconnect > 0)
TIMEOUT(connect_time_expired, 0, maxconnect);
/*
* Detach now, if the updetach option was given.
*/
if (nodetach == -1)
detach();
}
++num_np_up;
}
/*
* np_down - a network protocol has gone down.
*/
void
np_down(unit, proto)
int unit, proto;
{
if (--num_np_up == 0 && idle_time_limit > 0) {
UNTIMEOUT(check_idle, NULL);
}
}
/*
* np_finished - a network protocol has finished using the link.
*/
void
np_finished(unit, proto)
int unit, proto;
{
if (--num_np_open <= 0) {
/* no further use for the link: shut up shop. */
lcp_close(0, "No network protocols running");
}
}
/*
* check_idle - check whether the link has been idle for long
* enough that we can shut it down.
*/
static void
check_idle(arg)
void *arg;
{
struct ppp_idle idle;
time_t itime;
if (!get_idle_time(0, &idle))
return;
itime = MIN(idle.xmit_idle, idle.recv_idle);
if (itime >= idle_time_limit) {
/* link is idle: shut it down. */
syslog(LOG_INFO, "Terminating connection due to lack of activity.");
lcp_close(0, "Link inactive");
} else {
TIMEOUT(check_idle, NULL, idle_time_limit - itime);
}
}
/*
* connect_time_expired - log a message and close the connection.
*/
static void
connect_time_expired(arg)
void *arg;
{
syslog(LOG_INFO, "Connect time expired");
lcp_close(0, "Connect time expired"); /* Close connection */
}
/*
* auth_check_options - called to check authentication options.
*/
void
auth_check_options()
{
lcp_options *wo = &lcp_wantoptions[0];
int can_auth;
ipcp_options *ipwo = &ipcp_wantoptions[0];
u_int32_t remote;
/* Default our_name to hostname, and user to our_name */
if (our_name[0] == 0 || usehostname)
strcpy(our_name, hostname);
if (user[0] == 0)
strcpy(user, our_name);
/* If authentication is required, ask peer for CHAP or PAP. */
if (auth_required && !wo->neg_chap && !wo->neg_upap) {
wo->neg_chap = 1;
wo->neg_upap = 1;
}
/*
* Check whether we have appropriate secrets to use
* to authenticate the peer.
*/
can_auth = wo->neg_upap && (uselogin || have_pap_secret());
if (!can_auth && wo->neg_chap) {
remote = ipwo->accept_remote? 0: ipwo->hisaddr;
can_auth = have_chap_secret(remote_name, our_name, remote);
}
if (auth_required && !can_auth) {
option_error("peer authentication required but no suitable secret(s) found\n");
if (remote_name[0] == 0)
option_error("for authenticating any peer to us (%s)\n", our_name);
else
option_error("for authenticating peer %s to us (%s)\n",
remote_name, our_name);
exit(1);
}
/*
* Check whether the user tried to override certain values
* set by root.
*/
if (!auth_required && auth_req_info.priv > 0) {
if (!default_device && devnam_info.priv == 0) {
option_error("can't override device name when noauth option used");
exit(1);
}
if ((connector != NULL && connector_info.priv == 0)
|| (disconnector != NULL && disconnector_info.priv == 0)
|| (welcomer != NULL && welcomer_info.priv == 0)) {
option_error("can't override connect, disconnect or welcome");
option_error("option values when noauth option used");
exit(1);
}
}
}
/*
* auth_reset - called when LCP is starting negotiations to recheck
* authentication options, i.e. whether we have appropriate secrets
* to use for authenticating ourselves and/or the peer.
*/
void
auth_reset(unit)
int unit;
{
lcp_options *go = &lcp_gotoptions[unit];
lcp_options *ao = &lcp_allowoptions[0];
ipcp_options *ipwo = &ipcp_wantoptions[0];
u_int32_t remote;
ao->neg_upap = !refuse_pap && (passwd[0] != 0 || get_pap_passwd(NULL));
ao->neg_chap = !refuse_chap
&& have_chap_secret(user, remote_name, (u_int32_t)0);
if (go->neg_upap && !uselogin && !have_pap_secret())
go->neg_upap = 0;
if (go->neg_chap) {
remote = ipwo->accept_remote? 0: ipwo->hisaddr;
if (!have_chap_secret(remote_name, our_name, remote))
go->neg_chap = 0;
}
}
/*
* check_passwd - Check the user name and passwd against the PAP secrets
* file. If requested, also check against the system password database,
* and login the user if OK.
*
* returns:
* UPAP_AUTHNAK: Authentication failed.
* UPAP_AUTHACK: Authentication succeeded.
* In either case, msg points to an appropriate message.
*/
int
check_passwd(unit, auser, userlen, apasswd, passwdlen, msg, msglen)
int unit;
char *auser;
int userlen;
char *apasswd;
int passwdlen;
char **msg;
int *msglen;
{
int ret;
char *filename;
FILE *f;
struct wordlist *addrs;
u_int32_t remote;
ipcp_options *ipwo = &ipcp_wantoptions[unit];
char passwd[256], user[256];
char secret[MAXWORDLEN];
static int attempts = 0;
int len;
/*
* Make copies of apasswd and auser, then null-terminate them.
*/
len = MIN(passwdlen, sizeof(passwd) - 1);
BCOPY(apasswd, passwd, len);
passwd[len] = '\0';
len = MIN(userlen, sizeof(user) - 1);
BCOPY(auser, user, len);
user[len] = '\0';
*msg = (char *) 0;
/*
* Open the file of pap secrets and scan for a suitable secret
* for authenticating this user.
*/
filename = _PATH_UPAPFILE;
addrs = NULL;
ret = UPAP_AUTHACK;
f = fopen(filename, "r");
if (f == NULL) {
syslog(LOG_ERR, "Can't open PAP password file %s: %m", filename);
ret = UPAP_AUTHNAK;
} else {
check_access(f, filename);
remote = ipwo->accept_remote? 0: ipwo->hisaddr;
if (scan_authfile(f, user, our_name, remote,
secret, &addrs, filename) < 0) {
warn("no PAP secret found for %s", user);
} else {
if (secret[0] != 0) {
/* password given in pap-secrets - must match */
if ((cryptpap || strcmp(passwd, secret) != 0)
&& strcmp(crypt(passwd, secret), secret) != 0) {
ret = UPAP_AUTHNAK;
warn("PAP authentication failure for %s", user);
}
}
}
fclose(f);
}
if (uselogin && ret == UPAP_AUTHACK) {
ret = plogin(user, passwd, msg, msglen);
if (ret == UPAP_AUTHNAK) {
syslog(LOG_WARNING, "PAP login failure for %s", user);
}
}
if (ret == UPAP_AUTHNAK) {
if (*msg == (char *) 0)
*msg = "Login incorrect";
*msglen = strlen(*msg);
/*
* Frustrate passwd stealer programs.
* Allow 10 tries, but start backing off after 3 (stolen from login).
* On 10'th, drop the connection.
*/
if (attempts++ >= 10) {
syslog(LOG_WARNING, "%d LOGIN FAILURES ON %s, %s",
attempts, devnam, user);
quit();
}
if (attempts > 3)
sleep((u_int) (attempts - 3) * 5);
if (addrs != NULL)
free_wordlist(addrs);
} else {
attempts = 0; /* Reset count */
if (*msg == (char *) 0)
*msg = "Login ok";
*msglen = strlen(*msg);
set_allowed_addrs(unit, addrs);
}
BZERO(passwd, sizeof(passwd));
BZERO(secret, sizeof(secret));
return ret;
}
/*
* Check if an "entry" is in the file "fname" - used by ppplogin.
* Taken from libexec/ftpd/ftpd.c
* Returns: 0 if not found, 1 if found, 2 if file can't be opened for reading.
*/
static int
checkfile(fname, name)
char *fname;
char *name;
{
FILE *fd;
int found = 0;
char *p, line[BUFSIZ];
if ((fd = fopen(fname, "r")) != NULL) {
while (fgets(line, sizeof(line), fd) != NULL)
if ((p = strchr(line, '\n')) != NULL) {
*p = '\0';
if (line[0] == '#')
continue;
if (strcmp(line, name) == 0) {
found = 1;
break;
}
}
(void) fclose(fd);
} else {
return(2);
}
return (found);
}
/*
* This function is needed for PAM.
*/
#ifdef USE_PAM
static char *PAM_username = "";
static char *PAM_password = "";
#ifdef PAM_ESTABLISH_CRED /* new PAM defines :(^ */
#define MY_PAM_STRERROR(err_code) (char *) pam_strerror(pamh,err_code)
#else
#define MY_PAM_STRERROR(err_code) (char *) pam_strerror(err_code)
#endif
static int pam_conv (int num_msg,
const struct pam_message **msg,
struct pam_response **resp,
void *appdata_ptr)
{
int count = 0, replies = 0;
struct pam_response *reply = NULL;
int size = 0;
for (count = 0; count < num_msg; count++)
{
size += sizeof (struct pam_response);
reply = realloc (reply, size); /* ANSI: is malloc() if reply==NULL */
if (!reply)
return PAM_CONV_ERR;
switch (msg[count]->msg_style)
{
case PAM_PROMPT_ECHO_ON:
reply[replies].resp_retcode = PAM_SUCCESS;
reply[replies++].resp = strdup(PAM_username); /* never NULL */
break;
case PAM_PROMPT_ECHO_OFF:
reply[replies].resp_retcode = PAM_SUCCESS;
reply[replies++].resp = strdup(PAM_password); /* never NULL */
break;
case PAM_TEXT_INFO:
reply[replies].resp_retcode = PAM_SUCCESS;
reply[replies++].resp = NULL;
break;
case PAM_ERROR_MSG:
default:
free (reply);
return PAM_CONV_ERR;
}
}
if (resp)
*resp = reply;
else
free (reply);
return PAM_SUCCESS;
}
#endif
/*
* plogin - Check the user name and password against the system
* password database, and login the user if OK.
*
* returns:
* UPAP_AUTHNAK: Login failed.
* UPAP_AUTHACK: Login succeeded.
* In either case, msg points to an appropriate message.
*
* UPAP_AUTHACK should only be returned *after* wtmp and utmp are updated.
*/
static int
plogin(user, passwd, msg, msglen)
char *user;
char *passwd;
char **msg;
int *msglen;
{
#ifdef USE_PAM
struct pam_conv pam_conversation;
pam_handle_t *pamh;
int pam_error;
/*
* Fill the pam_conversion structure
*/
memset (&pam_conversation, '\0', sizeof (struct pam_conv));
pam_conversation.conv = &pam_conv;
pam_error = pam_start ("ppp", user, &pam_conversation, &pamh);
if (pam_error != PAM_SUCCESS) {
*msg = MY_PAM_STRERROR (pam_error);
return UPAP_AUTHNAK;
}
/*
* Define the fields for the credintial validation
*/
(void) pam_set_item (pamh, PAM_TTY, devnam);
PAM_username = user;
PAM_password = passwd;
/*
* Validate the user
*/
pam_error = pam_authenticate (pamh, PAM_SILENT);
if (pam_error == PAM_SUCCESS) {
pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
/* start a session for this user. Session closed when link ends. */
if (pam_error == PAM_SUCCESS)
(void) pam_open_session (pamh, PAM_SILENT);
}
*msg = MY_PAM_STRERROR (pam_error);
PAM_username =
PAM_password = "";
/*
* Clean up the mess
*/
(void) pam_end (pamh, pam_error);
if (pam_error != PAM_SUCCESS)
return UPAP_AUTHNAK;
/*
* Use the non-PAM methods directly
*/
#else /* #ifdef USE_PAM */
struct passwd *pw;
struct utmp utmp;
struct timeval tp;
char *tty;
#ifdef HAS_SHADOW
struct spwd *spwd;
struct spwd *getspnam();
#endif
pw = getpwnam(user);
endpwent();
if (pw == NULL) {
return (UPAP_AUTHNAK);
}
/*
* Check that the user is not listed in /etc/ppp/ppp.deny
* and that the user's shell is listed in /etc/ppp/ppp.shells
* if /etc/ppp/ppp.shells exists.
*/
if (checkfile(_PATH_PPPDENY, user) == 1) {
syslog(LOG_WARNING, "upap user %s: login denied in %s",
user, _PATH_PPPDENY);
return (UPAP_AUTHNAK);
}
if (checkfile(_PATH_PPPSHELLS, pw->pw_shell) == 0) {
syslog(LOG_WARNING, "upap user %s: shell %s not in %s",
user, pw->pw_shell, _PATH_PPPSHELLS);
return (UPAP_AUTHNAK);
}
#ifdef HAS_SHADOW
spwd = getspnam(user);
endspent();
if (spwd) {
/* check the age of the password entry */
long now = time(NULL) / 86400L;
if ((spwd->sp_expire > 0 && now >= spwd->sp_expire)
|| ((spwd->sp_max >= 0 && spwd->sp_max < 10000)
&& spwd->sp_lstchg >= 0
&& now >= spwd->sp_lstchg + spwd->sp_max)) {
syslog(LOG_WARNING, "Password for %s has expired", user);
return (UPAP_AUTHNAK);
}
pw->pw_passwd = spwd->sp_pwdp;
}
#endif
/*
* If no passwd, don't let them login.
*/
if (pw->pw_passwd == NULL || *pw->pw_passwd == '\0'
|| strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd) != 0)
return (UPAP_AUTHNAK);
if (pw->pw_expire) {
(void)gettimeofday(&tp, (struct timezone *)NULL);
if (tp.tv_sec >= pw->pw_expire) {
syslog(LOG_INFO, "pap user %s account expired", user);
return (UPAP_AUTHNAK);
}
}
/* These functions are not enabled for PAM. The reason for this is that */
/* there is not necessarily a "passwd" entry for this user. That is */
/* real purpose of 'PAM' -- to virtualize the account data from the */
/* application. If you want to do the same thing, write the entry in */
/* the 'session' hook. */
/* Log in wtmp and utmp using login() */
tty = devnam;
if (strncmp(tty, _PATH_DEV, sizeof _PATH_DEV - 1) == 0)
tty += 5;
if (logout(tty)) /* Already entered (by login?) */
logwtmp(tty, "", "");
#if defined(_PATH_LASTLOG)
{
struct lastlog ll;
int fd;
if ((fd = open(_PATH_LASTLOG, O_RDWR, 0)) >= 0) {
(void)lseek(fd, (off_t)(pw->pw_uid * sizeof(ll)), SEEK_SET);
memset((void *)&ll, 0, sizeof(ll));
(void)time(&ll.ll_time);
(void)strncpy(ll.ll_line, tty, sizeof(ll.ll_line));
(void)write(fd, (char *)&ll, sizeof(ll));
(void)close(fd);
}
}
#endif
memset((void *)&utmp, 0, sizeof(utmp));
utmp.ut_time = time(NULL);
(void)strncpy(utmp.ut_name, user, sizeof(utmp.ut_name));
(void)strncpy(utmp.ut_host, ":PPP", sizeof(utmp.ut_host));
(void)strncpy(utmp.ut_line, tty, sizeof(utmp.ut_line));
login(&utmp); /* This logs us in wtmp too */
#endif /* #ifdef USE_PAM */
syslog(LOG_INFO, "user %s logged in", user);
logged_in = TRUE;
return (UPAP_AUTHACK);
}
/*
* plogout - Logout the user.
*/
static void
plogout()
{
#ifdef USE_PAM
struct pam_conv pam_conversation;
pam_handle_t *pamh;
int pam_error;
/*
* Fill the pam_conversion structure. The PAM specification states that the
* session must be able to be closed by a totally different handle from which
* it was created. Hold the PAM group to their own specification!
*/
memset (&pam_conversation, '\0', sizeof (struct pam_conv));
pam_conversation.conv = &pam_conv;
pam_error = pam_start ("ppp", user, &pam_conversation, &pamh);
if (pam_error == PAM_SUCCESS) {
(void) pam_set_item (pamh, PAM_TTY, devnam);
(void) pam_close_session (pamh, PAM_SILENT);
(void) pam_end (pamh, PAM_SUCCESS);
}
#else
char *tty;
tty = devnam;
if (strncmp(tty, _PATH_DEV, sizeof _PATH_DEV - 1) == 0)
tty += 5;
logwtmp(tty, "", ""); /* Wipe out wtmp logout entry */
logout(tty); /* Wipe out utmp */
#endif
logged_in = FALSE;
}
/*
* null_login - Check if a username of "" and a password of "" are
* acceptable, and iff so, set the list of acceptable IP addresses
* and return 1.
*/
static int
null_login(unit)
int unit;
{
char *filename;
FILE *f;
int i, ret;
struct wordlist *addrs;
char secret[MAXWORDLEN];
/*
* Open the file of pap secrets and scan for a suitable secret.
* We don't accept a wildcard client.
*/
filename = _PATH_UPAPFILE;
addrs = NULL;
f = fopen(filename, "r");
if (f == NULL)
return 0;
check_access(f, filename);
i = scan_authfile(f, "", our_name, (u_int32_t)0, secret, &addrs, filename);
ret = i >= 0 && (i & NONWILD_CLIENT) != 0 && secret[0] == 0;
BZERO(secret, sizeof(secret));
if (ret)
set_allowed_addrs(unit, addrs);
else
free_wordlist(addrs);
fclose(f);
return ret;
}
/*
* get_pap_passwd - get a password for authenticating ourselves with
* our peer using PAP. Returns 1 on success, 0 if no suitable password
* could be found.
*/
static int
get_pap_passwd(passwd)
char *passwd;
{
char *filename;
FILE *f;
int ret;
struct wordlist *addrs;
char secret[MAXWORDLEN];
filename = _PATH_UPAPFILE;
addrs = NULL;
f = fopen(filename, "r");
if (f == NULL)
return 0;
check_access(f, filename);
ret = scan_authfile(f, user,
remote_name[0]? remote_name: NULL,
(u_int32_t)0, secret, NULL, filename);
fclose(f);
if (ret < 0)
return 0;
if (passwd != NULL) {
strncpy(passwd, secret, MAXSECRETLEN);
passwd[MAXSECRETLEN-1] = 0;
}
BZERO(secret, sizeof(secret));
return 1;
}
/*
* have_pap_secret - check whether we have a PAP file with any
* secrets that we could possibly use for authenticating the peer.
*/
static int
have_pap_secret()
{
FILE *f;
int ret;
char *filename;
ipcp_options *ipwo = &ipcp_wantoptions[0];
u_int32_t remote;
filename = _PATH_UPAPFILE;
f = fopen(filename, "r");
if (f == NULL)
return 0;
remote = ipwo->accept_remote? 0: ipwo->hisaddr;
ret = scan_authfile(f, NULL, our_name, remote, NULL, NULL, filename);
fclose(f);
if (ret < 0)
return 0;
return 1;
}
/*
* have_chap_secret - check whether we have a CHAP file with a
* secret that we could possibly use for authenticating `client'
* on `server'. Either can be the null string, meaning we don't
* know the identity yet.
*/
static int
have_chap_secret(client, server, remote)
char *client;
char *server;
u_int32_t remote;
{
FILE *f;
int ret;
char *filename;
filename = _PATH_CHAPFILE;
f = fopen(filename, "r");
if (f == NULL)
return 0;
if (client[0] == 0)
client = NULL;
else if (server[0] == 0)
server = NULL;
ret = scan_authfile(f, client, server, remote, NULL, NULL, filename);
fclose(f);
if (ret < 0)
return 0;
return 1;
}
/*
* get_secret - open the CHAP secret file and return the secret
* for authenticating the given client on the given server.
* (We could be either client or server).
*/
int
get_secret(unit, client, server, secret, secret_len, save_addrs)
int unit;
char *client;
char *server;
char *secret;
int *secret_len;
int save_addrs;
{
FILE *f;
int ret, len;
char *filename;
struct wordlist *addrs;
char secbuf[MAXWORDLEN];
filename = _PATH_CHAPFILE;
addrs = NULL;
secbuf[0] = 0;
f = fopen(filename, "r");
if (f == NULL) {
syslog(LOG_ERR, "Can't open chap secret file %s: %m", filename);
return 0;
}
check_access(f, filename);
ret = scan_authfile(f, client, server, (u_int32_t)0,
secbuf, &addrs, filename);
fclose(f);
if (ret < 0)
return 0;
if (save_addrs)
set_allowed_addrs(unit, addrs);
len = strlen(secbuf);
if (len > MAXSECRETLEN) {
syslog(LOG_ERR, "Secret for %s on %s is too long", client, server);
len = MAXSECRETLEN;
}
BCOPY(secbuf, secret, len);
BZERO(secbuf, sizeof(secbuf));
*secret_len = len;
return 1;
}
/*
* set_allowed_addrs() - set the list of allowed addresses.
*/
static void
set_allowed_addrs(unit, addrs)
int unit;
struct wordlist *addrs;
{
if (addresses[unit] != NULL)
free_wordlist(addresses[unit]);
addresses[unit] = addrs;
/*
* If there's only one authorized address we might as well
* ask our peer for that one right away
*/
if (addrs != NULL && addrs->next == NULL) {
char *p = addrs->word;
struct ipcp_options *wo = &ipcp_wantoptions[unit];
u_int32_t a;
struct hostent *hp;
if (*p != '!' && *p != '-' && strchr(p, '/') == NULL) {
hp = gethostbyname(p);
if (hp != NULL && hp->h_addrtype == AF_INET)
a = *(u_int32_t *)hp->h_addr;
else
a = inet_addr(p);
if (a != (u_int32_t) -1)
wo->hisaddr = a;
}
}
}
static void
auth_set_ip_addr(unit)
int unit;
{
struct wordlist *addrs;
if (non_wildclient && (addrs = addresses[unit]) != NULL) {
for (; addrs != NULL; addrs = addrs->next) {
/* Look for address overrides, and set them if we have any */
if (strchr(addrs->word, ':') != NULL) {
if (setipaddr(addrs->word))
break;
}
}
}
}
/*
* auth_ip_addr - check whether the peer is authorized to use
* a given IP address. Returns 1 if authorized, 0 otherwise.
*/
int
auth_ip_addr(unit, addr)
int unit;
u_int32_t addr;
{
return ip_addr_check(addr, addresses[unit]);
}
static int
ip_addr_check(addr, addrs)
u_int32_t addr;
struct wordlist *addrs;
{
int x, y;
u_int32_t a, mask, ah;
int accept;
char *ptr_word, *ptr_mask;
struct hostent *hp;
struct netent *np;
/* don't allow loopback or multicast address */
if (bad_ip_adrs(addr))
return 0;
if (addrs == NULL)
return !auth_required; /* no addresses authorized */
x = y = 0;
for (; addrs != NULL; addrs = addrs->next) {
y++;
/* "-" means no addresses authorized, "*" means any address allowed */
ptr_word = addrs->word;
if (strcmp(ptr_word, "-") == 0)
break;
if (strcmp(ptr_word, "*") == 0)
return 1;
/*
* A colon in the string means that we wish to force a specific
* local:remote address, but we ignore these for now.
*/
if (strchr(addrs->word, ':') != NULL)
x++;
else {
accept = 1;
if (*ptr_word == '!') {
accept = 0;
++ptr_word;
}
mask = ~ (u_int32_t) 0;
ptr_mask = strchr (ptr_word, '/');
if (ptr_mask != NULL) {
int bit_count;
bit_count = (int) strtol (ptr_mask+1, (char **) 0, 10);
if (bit_count <= 0 || bit_count > 32) {
syslog (LOG_WARNING,
"invalid address length %s in auth. address list",
ptr_mask);
continue;
}
*ptr_mask = '\0';
mask <<= 32 - bit_count;
}
hp = gethostbyname(ptr_word);
if (hp != NULL && hp->h_addrtype == AF_INET) {
a = *(u_int32_t *)hp->h_addr;
} else {
np = getnetbyname (ptr_word);
if (np != NULL && np->n_addrtype == AF_INET) {
a = htonl (*(u_int32_t *)np->n_net);
if (ptr_mask == NULL) {
/* calculate appropriate mask for net */
ah = ntohl(a);
if (IN_CLASSA(ah))
mask = IN_CLASSA_NET;
else if (IN_CLASSB(ah))
mask = IN_CLASSB_NET;
else if (IN_CLASSC(ah))
mask = IN_CLASSC_NET;
}
} else {
a = inet_addr (ptr_word);
}
}
if (ptr_mask != NULL)
*ptr_mask = '/';
if (a == (u_int32_t)-1L)
syslog (LOG_WARNING,
"unknown host %s in auth. address list",
addrs->word);
else
/* Here a and addr are in network byte order,
and mask is in host order. */
if (((addr ^ a) & htonl(mask)) == 0)
return accept;
} /* else */
}
return x == y; /* not in list => can't have it */
}
/*
* bad_ip_adrs - return 1 if the IP address is one we don't want
* to use, such as an address in the loopback net or a multicast address.
* addr is in network byte order.
*/
int
bad_ip_adrs(addr)
u_int32_t addr;
{
addr = ntohl(addr);
return (addr >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET
|| IN_MULTICAST(addr) || IN_BADCLASS(addr);
}
/*
* check_access - complain if a secret file has too-liberal permissions.
*/
void
check_access(f, filename)
FILE *f;
char *filename;
{
struct stat sbuf;
if (fstat(fileno(f), &sbuf) < 0) {
syslog(LOG_WARNING, "cannot stat secret file %s: %m", filename);
} else if ((sbuf.st_mode & (S_IRWXG | S_IRWXO)) != 0) {
syslog(LOG_WARNING, "Warning - secret file %s has world and/or group access", filename);
}
}
/*
* scan_authfile - Scan an authorization file for a secret suitable
* for authenticating `client' on `server'. The return value is -1
* if no secret is found, otherwise >= 0. The return value has
* NONWILD_CLIENT set if the secret didn't have "*" for the client, and
* NONWILD_SERVER set if the secret didn't have "*" for the server.
* Any following words on the line (i.e. address authorization
* info) are placed in a wordlist and returned in *addrs.
*/
static int
scan_authfile(f, client, server, ipaddr, secret, addrs, filename)
FILE *f;
char *client;
char *server;
u_int32_t ipaddr;
char *secret;
struct wordlist **addrs;
char *filename;
{
int newline, xxx;
int got_flag, best_flag;
FILE *sf;
struct wordlist *ap, *addr_list, *alist, *alast;
char word[MAXWORDLEN];
char atfile[MAXWORDLEN];
char lsecret[MAXWORDLEN];
if (addrs != NULL)
*addrs = NULL;
addr_list = NULL;
if (!getword(f, word, &newline, filename))
return -1; /* file is empty??? */
newline = 1;
best_flag = -1;
for (;;) {
/*
* Skip until we find a word at the start of a line.
*/
while (!newline && getword(f, word, &newline, filename))
;
if (!newline)
break; /* got to end of file */
/*
* Got a client - check if it's a match or a wildcard.
*/
got_flag = 0;
if (client != NULL && strcmp(word, client) != 0 && !ISWILD(word)) {
newline = 0;
continue;
}
if (!ISWILD(word))
got_flag = NONWILD_CLIENT;
/*
* Now get a server and check if it matches.
*/
if (!getword(f, word, &newline, filename))
break;
if (newline)
continue;
if (server != NULL && strcmp(word, server) != 0 && !ISWILD(word))
continue;
if (!ISWILD(word))
got_flag |= NONWILD_SERVER;
/*
* Got some sort of a match - see if it's better than what
* we have already.
*/
if (got_flag <= best_flag)
continue;
/*
* Get the secret.
*/
if (!getword(f, word, &newline, filename))
break;
if (newline)
continue;
/*
* Special syntax: @filename means read secret from file.
*/
if (word[0] == '@') {
strcpy(atfile, word+1);
if ((sf = fopen(atfile, "r")) == NULL) {
syslog(LOG_WARNING, "can't open indirect secret file %s",
atfile);
continue;
}
check_access(sf, atfile);
if (!getword(sf, word, &xxx, atfile)) {
syslog(LOG_WARNING, "no secret in indirect secret file %s",
atfile);
fclose(sf);
continue;
}
fclose(sf);
}
if (secret != NULL)
strcpy(lsecret, word);
/*
* Now read address authorization info and make a wordlist.
*/
alist = alast = NULL;
for (;;) {
if (!getword(f, word, &newline, filename) || newline)
break;
ap = (struct wordlist *) malloc(sizeof(struct wordlist)
+ strlen(word));
if (ap == NULL)
novm("authorized addresses");
ap->next = NULL;
strcpy(ap->word, word);
if (alist == NULL)
alist = ap;
else
alast->next = ap;
alast = ap;
}
/*
* Check if the given IP address is allowed by the wordlist.
*/
if (ipaddr != 0 && !ip_addr_check(ipaddr, alist)) {
free_wordlist(alist);
continue;
}
/*
* This is the best so far; remember it.
*/
best_flag = got_flag;
if (addr_list)
free_wordlist(addr_list);
addr_list = alist;
if (secret != NULL)
strcpy(secret, lsecret);
if (!newline)
break;
}
if (addrs != NULL)
*addrs = addr_list;
else if (addr_list != NULL)
free_wordlist(addr_list);
non_wildclient = (best_flag & NONWILD_CLIENT) && client != NULL &&
*client != '\0';
return best_flag;
}
/*
* free_wordlist - release memory allocated for a wordlist.
*/
static void
free_wordlist(wp)
struct wordlist *wp;
{
struct wordlist *next;
while (wp != NULL) {
next = wp->next;
free(wp);
wp = next;
}
}
/*
* auth_script - execute a script with arguments
* interface-name peer-name real-user tty speed
*/
static void
auth_script(script)
char *script;
{
char strspeed[32];
struct passwd *pw;
char struid[32];
char *user_name;
char *argv[8];
if ((pw = getpwuid(getuid())) != NULL && pw->pw_name != NULL)
user_name = pw->pw_name;
else {
sprintf(struid, "%d", getuid());
user_name = struid;
}
sprintf(strspeed, "%d", baud_rate);
argv[0] = script;
argv[1] = ifname;
argv[2] = peer_authname;
argv[3] = user_name;
argv[4] = devnam;
argv[5] = strspeed;
argv[6] = NULL;
run_program(script, argv, 0);
}