]> git.ozlabs.org Git - ppp.git/blobdiff - pppd/auth.c
Make auth be the default if we don't have the noauth option and
[ppp.git] / pppd / auth.c
index f384cb800cabcd066ca62ad670c1d7fc936e01b7..47ecae36f341d8302ce4a7f65a91c7f868eaeaac 100644 (file)
@@ -33,7 +33,7 @@
  */
 
 #ifndef lint
-static char rcsid[] = "$Id: auth.c,v 1.29 1996/10/08 06:43:15 paulus Exp $";
+static char rcsid[] = "$Id: auth.c,v 1.42 1999/03/02 05:33:09 paulus Exp $";
 #endif
 
 #include <stdio.h>
@@ -46,28 +46,22 @@ static char rcsid[] = "$Id: auth.c,v 1.29 1996/10/08 06:43:15 paulus Exp $";
 #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>
 
-#ifdef SVR4
-#include <crypt.h>
-#else
-#if defined(SUNOS4) || defined(ULTRIX)
-extern char *crypt();
-#endif
-#endif
-
 #ifdef USE_PAM
 #include <security/pam_appl.h>
-#include <security/pam_modules.h>
-int isexpired (struct passwd *, struct spwd *);
 #endif
 
 #ifdef HAS_SHADOW
 #include <shadow.h>
-#include <shadow/pwauth.h>
 #ifndef PW_PPP
 #define PW_PPP PW_LOGIN
 #endif
@@ -105,11 +99,12 @@ char peer_authname[MAXNAMELEN];
 /* Records which authentication operations haven't completed yet. */
 static int auth_pending[NUM_PPP];
 
-/* Set if we have successfully called login() */
+/* Set if we have successfully called plogin() */
 static int logged_in;
 
 /* Set if we have run the /etc/ppp/auth-up script. */
 static int did_authup;
+static pid_t authup_pid;       /* process ID of auth-up/down script */
 
 /* List of addresses which the peer may use. */
 static struct wordlist *addresses[NUM_PPP];
@@ -123,18 +118,32 @@ static int num_np_up;
 /* Set if we got the contents of passwd[] from the pap-secrets file. */
 static int passwd_from_file;
 
+/*
+ * Option variables.
+ */
+bool uselogin = 0;             /* Use /etc/passwd for checking PAP */
+bool cryptpap = 0;             /* Passwords in pap-secrets are encrypted */
+bool refuse_pap = 0;           /* Don't wanna auth. ourselves with PAP */
+bool refuse_chap = 0;          /* Don't wanna auth. ourselves with CHAP */
+bool usehostname = 0;          /* Use hostname for our_name */
+bool auth_required = 0;                /* Always require authentication from peer */
+bool allow_any_ip = 0;         /* Allow peer to use any IP address */
+
 /* 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((caddr_t));
-static int  login __P((char *, char *, char **, int *));
-static void logout __P((void));
+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));
@@ -144,9 +153,93 @@ 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_script __P((char *));
-#ifdef CBCP_SUPPORT
-static void callback_phase __P((int));
-#endif
+static void set_allowed_addrs __P((int, struct wordlist *));
+static int setupapfile __P((char **));
+
+/*
+ * Authentication-related options.
+ */
+option_t auth_options[] = {
+    { "require-pap", o_bool, &lcp_wantoptions[0].neg_upap,
+      "Require PAP authentication from peer", 1, &auth_required },
+    { "+pap", o_bool, &lcp_wantoptions[0].neg_upap,
+      "Require PAP authentication from peer", 1, &auth_required },
+    { "refuse-pap", o_bool, &refuse_pap,
+      "Don't agree to auth to peer with PAP", 1 },
+    { "-pap", o_bool, &refuse_pap,
+      "Don't allow PAP authentication with peer", 1 },
+    { "require-chap", o_bool, &lcp_wantoptions[0].neg_chap,
+      "Require CHAP authentication from peer", 1, &auth_required },
+    { "+chap", o_bool, &lcp_wantoptions[0].neg_chap,
+      "Require CHAP authentication from peer", 1, &auth_required },
+    { "refuse-chap", o_bool, &refuse_chap,
+      "Don't agree to auth to peer with CHAP", 1 },
+    { "-chap", o_bool, &refuse_chap,
+      "Don't allow CHAP authentication with peer", 1 },
+    { "name", o_string, our_name,
+      "Set local name for authentication",
+      OPT_PRIV|OPT_STATIC, NULL, MAXNAMELEN },
+    { "user", o_string, user,
+      "Set name for auth with peer", OPT_STATIC, NULL, MAXNAMELEN },
+    { "usehostname", o_bool, &usehostname,
+      "Must use hostname for authentication", 1 },
+    { "remotename", o_string, remote_name,
+      "Set remote name for authentication", OPT_STATIC, NULL, MAXNAMELEN },
+    { "auth", o_bool, &auth_required,
+      "Require authentication from peer", 1 },
+    { "noauth", o_bool, &auth_required,
+      "Don't require peer to authenticate", OPT_PRIV, &allow_any_ip },
+    {  "login", o_bool, &uselogin,
+      "Use system password database for PAP", 1 },
+    { "papcrypt", o_bool, &cryptpap,
+      "PAP passwords are encrypted", 1 },
+    { "+ua", o_special, setupapfile,
+      "Get PAP user and password from file" },
+    { NULL }
+};
+
+/*
+ * setupapfile - specifies UPAP info for authenticating with peer.
+ */
+static int
+setupapfile(argv)
+    char **argv;
+{
+    FILE * ufile;
+    int l;
+
+    lcp_allowoptions[0].neg_upap = 1;
+
+    /* open user info file */
+    if ((ufile = fopen(*argv, "r")) == NULL) {
+       option_error("unable to open user login data file %s", *argv);
+       return 0;
+    }
+    if (!readable(fileno(ufile))) {
+       option_error("%s: access denied", *argv);
+       return 0;
+    }
+    check_access(ufile, *argv);
+
+    /* get username */
+    if (fgets(user, MAXNAMELEN - 1, ufile) == NULL
+       || fgets(passwd, MAXSECRETLEN - 1, ufile) == NULL){
+       option_error("unable to read user login data file %s", *argv);
+       return 0;
+    }
+    fclose(ufile);
+
+    /* get rid of newlines */
+    l = strlen(user);
+    if (l > 0 && user[l-1] == '\n')
+       user[l-1] = 0;
+    l = strlen(passwd);
+    if (l > 0 && passwd[l-1] == '\n')
+       passwd[l-1] = 0;
+
+    return (1);
+}
+
 
 /*
  * An Open on LCP has requested a change from Dead to Establish phase.
@@ -169,7 +262,7 @@ link_terminated(unit)
     if (phase == PHASE_DEAD)
        return;
     if (logged_in)
-       logout();
+       plogout();
     phase = PHASE_DEAD;
     syslog(LOG_NOTICE, "Connection terminated.");
 }
@@ -198,7 +291,8 @@ link_down(unit)
     }
     num_np_open = 0;
     num_np_up = 0;
-    phase = PHASE_TERMINATE;
+    if (phase != PHASE_DEAD)
+       phase = PHASE_TERMINATE;
 }
 
 /*
@@ -306,6 +400,10 @@ network_phase(unit)
            if (protp->protocol != PPP_CCP)
                ++num_np_open;
        }
+
+    if (num_np_open == 0)
+       /* nothing to do */
+       lcp_close(0, "No network protocols running");
 }
 
 /*
@@ -352,6 +450,7 @@ auth_peer_success(unit, protocol, name, namelen)
        namelen = sizeof(peer_authname) - 1;
     BCOPY(name, peer_authname, namelen);
     peer_authname[namelen] = 0;
+    script_setenv("PEERNAME", peer_authname);
 
     /*
      * If there is no more authentication still to be done,
@@ -417,8 +516,27 @@ void
 np_up(unit, proto)
     int unit, proto;
 {
-    if (num_np_up == 0 && idle_time_limit > 0) {
-       TIMEOUT(check_idle, NULL, idle_time_limit);
+    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 (updetach && !nodetach)
+           detach();
     }
     ++num_np_up;
 }
@@ -454,7 +572,7 @@ np_finished(unit, proto)
  */
 static void
 check_idle(arg)
-    caddr_t arg;
+     void *arg;
 {
     struct ppp_idle idle;
     time_t itime;
@@ -465,13 +583,23 @@ check_idle(arg)
     if (itime >= idle_time_limit) {
        /* link is idle: shut it down. */
        syslog(LOG_INFO, "Terminating connection due to lack of activity.");
-       need_holdoff = 0;
        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.
  */
@@ -490,11 +618,23 @@ auth_check_options()
        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;
+    if (auth_required) {
+       if (!wo->neg_chap && !wo->neg_upap) {
+           wo->neg_chap = 1;
+           wo->neg_upap = 1;
+       }
+    } else {
+       wo->neg_chap = 0;
+       wo->neg_upap = 0;
     }
 
+    /*
+     * If we have a default route, require the peer to authenticate
+     * unless the noauth option was given.
+     */
+    if (!auth_required && !allow_any_ip && have_route_to(0))
+       auth_required = 1;
+
     /*
      * Check whether we have appropriate secrets to use
      * to authenticate the peer.
@@ -519,16 +659,12 @@ auth_check_options()
      * 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");
+    if (allow_any_ip) {
+       if ((connector != NULL && connector_info.priv == 0)
+           || (disconnector != NULL && disconnector_info.priv == 0)
+           || (welcomer != NULL && welcomer_info.priv == 0)) {
+           option_error("connect, disconnect and welcome options");
+           option_error("are privileged when noauth option is used");
            exit(1);
        }
     }
@@ -559,7 +695,6 @@ auth_reset(unit)
        if (!have_chap_secret(remote_name, our_name, remote))
            go->neg_chap = 0;
     }
-
 }
 
 
@@ -611,10 +746,8 @@ check_passwd(unit, auser, userlen, apasswd, passwdlen, msg, msglen)
     ret = UPAP_AUTHACK;
     f = fopen(filename, "r");
     if (f == NULL) {
-       if (!uselogin) {
-           syslog(LOG_ERR, "Can't open PAP password file %s: %m", filename);
-           ret = UPAP_AUTHNAK;
-       }
+       syslog(LOG_ERR, "Can't open PAP password file %s: %m", filename);
+       ret = UPAP_AUTHNAK;
 
     } else {
        check_access(f, filename);
@@ -630,7 +763,7 @@ check_passwd(unit, auser, userlen, apasswd, passwdlen, msg, msglen)
     }
 
     if (uselogin && ret == UPAP_AUTHACK) {
-       ret = login(user, passwd, msg, msglen);
+       ret = plogin(user, passwd, msg, msglen);
        if (ret == UPAP_AUTHNAK) {
            syslog(LOG_WARNING, "PAP login failure for %s", user);
        }
@@ -660,9 +793,7 @@ check_passwd(unit, auser, userlen, apasswd, passwdlen, msg, msglen)
        if (*msg == (char *) 0)
            *msg = "Login ok";
        *msglen = strlen(*msg);
-       if (addresses[unit] != NULL)
-           free_wordlist(addresses[unit]);
-       addresses[unit] = addrs;
+       set_allowed_addrs(unit, addrs);
     }
 
     BZERO(passwd, sizeof(passwd));
@@ -671,83 +802,71 @@ check_passwd(unit, auser, userlen, apasswd, passwdlen, msg, msglen)
     return ret;
 }
 
-#ifdef HAS_SHADOW
-/**************
- * This function was lifted from the shadow-3.3.2 version by John Haugh II.
- * It is included because the function was not in the standard libshadow
- * library. If it is included in the library then I can remove it from here.
- */
-
-#define        DAY     (24L*3600L)
 /*
- * isexpired - determine if account is expired yet
- *
- *     isexpired calculates the expiration date based on the
- *     password expiration criteria.
+ * This function is needed for PAM.
  */
 
-/*ARGSUSED*/
-int
-isexpired (pw, sp)
-struct passwd  *pw;
-struct spwd    *sp;
-{
-       long    clock;
-
-       clock = time ((time_t *) 0) / DAY;
-
-       /*
-        * Quick and easy - there is an expired account field
-        * along with an inactive account field.  Do the expired
-        * one first since it is worse.
-        */
-
-       if (sp->sp_expire > 0 && sp->sp_expire < clock)
-               return 3;
-
-       if (sp->sp_inact > 0 && sp->sp_lstchg > 0 && sp->sp_max > 0 &&
-                       sp->sp_inact + sp->sp_lstchg + sp->sp_max < clock)
-               return 2;
-
-       /*
-        * The last and max fields must be present for an account
-        * to have an expired password.  A maximum of >10000 days
-        * is considered to be infinite.
-        */
+#ifdef USE_PAM
+static char *PAM_username = "";
+static char *PAM_password = "";
 
-       if (sp->sp_lstchg == -1 ||
-                       sp->sp_max == -1 || sp->sp_max >= 10000L)
-               return 0;
+#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
 
-       /*
-        * Calculate today's day and the day on which the password
-        * is going to expire.  If that date has already passed,
-        * the password has expired.
-        */
+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;
 
-       if (sp->sp_lstchg + sp->sp_max < clock)
-               return 1;
+       case PAM_PROMPT_ECHO_OFF:
+           reply[replies].resp_retcode = PAM_SUCCESS;
+           reply[replies++].resp = strdup(PAM_password); /* never NULL */
+           break;
 
-       return 0;
-}
-#endif
+       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;
+         }
+      }
 
-/*
- * This function is needed for PAM. However, it should not be called.
- * If it is, return the error code.
- */
+    if (resp)
+        *resp = reply;
+    else
+        free (reply);
 
-#ifdef USE_PAM
-static int pam_conv(int num_msg, const struct pam_message **msg,
-                   struct pam_response **resp, void *appdata_ptr)
-{
-    return PAM_CONV_ERR;
+    return PAM_SUCCESS;
 }
 #endif
 
 /*
- * login - Check the user name and password against the system
+ * plogin - Check the user name and password against the system
  * password database, and login the user if OK.
  *
  * returns:
@@ -757,20 +876,18 @@ static int pam_conv(int num_msg, const struct pam_message **msg,
  */
 
 static int
-login(user, passwd, msg, msglen)
+plogin(user, passwd, msg, msglen)
     char *user;
     char *passwd;
     char **msg;
     int *msglen;
 {
-    char *tty;
 
 #ifdef USE_PAM
+
     struct pam_conv pam_conversation;
     pam_handle_t *pamh;
     int pam_error;
-    char *pass;
-    char *dev;
 /*
  * Fill the pam_conversion structure
  */
@@ -778,23 +895,33 @@ login(user, passwd, msg, msglen)
     pam_conversation.conv = &pam_conv;
 
     pam_error = pam_start ("ppp", user, &pam_conversation, &pamh);
+
     if (pam_error != PAM_SUCCESS) {
-        *msg = (char *) pam_strerror (pam_error);
+        *msg = MY_PAM_STRERROR (pam_error);
        return UPAP_AUTHNAK;
     }
 /*
  * Define the fields for the credintial validation
  */
-    (void) pam_set_item (pamh, PAM_AUTHTOK, passwd);
-    (void) pam_set_item (pamh, PAM_TTY,     devnam);
+    (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)
+    if (pam_error == PAM_SUCCESS) {
         pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
 
-    *msg = (char *) pam_strerror (pam_error);
+       /* 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
  */
@@ -808,7 +935,7 @@ login(user, passwd, msg, msglen)
 #else /* #ifdef USE_PAM */
 
     struct passwd *pw;
-    char *epasswd;
+    char *tty;
 
 #ifdef HAS_SHADOW
     struct spwd *spwd;
@@ -816,6 +943,7 @@ login(user, passwd, msg, msglen)
 #endif
 
     pw = getpwnam(user);
+    endpwent();
     if (pw == NULL) {
        return (UPAP_AUTHNAK);
     }
@@ -825,8 +953,13 @@ login(user, passwd, msg, msglen)
     endspent();
     if (spwd) {
        /* check the age of the password entry */
-       if (isexpired(pw, spwd)) {
-           syslog(LOG_WARNING,"Expired password for %s",user);
+       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;
@@ -834,42 +967,85 @@ login(user, passwd, msg, msglen)
 #endif
 
     /*
-     * XXX If no passwd, let them login without one.
+     * If no passwd, don't let them login.
      */
-    if (pw->pw_passwd != NULL && *pw->pw_passwd != '\0') {
-       epasswd = crypt(passwd, pw->pw_passwd);
-       if (strcmp(epasswd, pw->pw_passwd) != 0) {
-           return (UPAP_AUTHNAK);
-       }
-    }
-#endif /* #ifdef USE_PAM */
+    if (pw->pw_passwd == NULL || *pw->pw_passwd == '\0'
+       || strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd) != 0)
+       return (UPAP_AUTHNAK);
 
-    syslog(LOG_INFO, "user %s logged in", user);
+    /* 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.                                                  */
 
     /*
      * Write a wtmp entry for this user.
      */
+
     tty = devnam;
     if (strncmp(tty, "/dev/", 5) == 0)
        tty += 5;
     logwtmp(tty, user, remote_name);           /* Add wtmp login entry */
+
+#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
+
+#endif /* #ifdef USE_PAM */
+
+    syslog(LOG_INFO, "user %s logged in", user);
     logged_in = TRUE;
 
     return (UPAP_AUTHACK);
 }
 
 /*
- * logout - Logout the user.
+ * plogout - Logout the user.
  */
 static void
-logout()
+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, "/dev/", 5) == 0)
        tty += 5;
-    logwtmp(tty, "", "");              /* Wipe out wtmp logout entry */
+    logwtmp(tty, "", "");              /* Wipe out utmp logout entry */
+#endif
+
     logged_in = FALSE;
 }
 
@@ -904,11 +1080,10 @@ null_login(unit)
     ret = i >= 0 && (i & NONWILD_CLIENT) != 0 && secret[0] == 0;
     BZERO(secret, sizeof(secret));
 
-    if (ret) {
-       if (addresses[unit] != NULL)
-           free_wordlist(addresses[unit]);
-       addresses[unit] = addrs;
-    }
+    if (ret)
+       set_allowed_addrs(unit, addrs);
+    else
+       free_wordlist(addrs);
 
     fclose(f);
     return ret;
@@ -926,6 +1101,7 @@ get_pap_passwd(passwd)
 {
     char *filename;
     FILE *f;
+    int ret;
     struct wordlist *addrs;
     char secret[MAXWORDLEN];
 
@@ -935,9 +1111,11 @@ get_pap_passwd(passwd)
     if (f == NULL)
        return 0;
     check_access(f, filename);
-    if (scan_authfile(f, user,
-                     remote_name[0]? remote_name: NULL,
-                     (u_int32_t)0, secret, NULL, filename) < 0)
+    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);
@@ -1048,11 +1226,8 @@ get_secret(unit, client, server, secret, secret_len, save_addrs)
     if (ret < 0)
        return 0;
 
-    if (save_addrs) {
-       if (addresses[unit] != NULL)
-           free_wordlist(addresses[unit]);
-       addresses[unit] = addrs;
-    }
+    if (save_addrs)
+       set_allowed_addrs(unit, addrs);
 
     len = strlen(secbuf);
     if (len > MAXSECRETLEN) {
@@ -1066,6 +1241,40 @@ get_secret(unit, client, server, secret, secret_len, save_addrs)
     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 != '-' && *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;
+       }
+    }
+}
+
 /*
  * auth_ip_addr - check whether the peer is authorized to use
  * a given IP address.  Returns 1 if authorized, 0 otherwise.
@@ -1093,14 +1302,19 @@ ip_addr_check(addr, addrs)
     if (bad_ip_adrs(addr))
        return 0;
 
-    if (addrs == NULL)
-       return 1;               /* no restriction */
+    if (addrs == NULL) {
+       if (auth_required)
+           return 0;           /* no addresses authorized */
+       return allow_any_ip || !have_route_to(addr);
+    }
 
     for (; addrs != NULL; addrs = addrs->next) {
-       /* "-" means no addresses authorized */
+       /* "-" 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;
 
        accept = 1;
        if (*ptr_word == '!') {
@@ -1126,34 +1340,36 @@ ip_addr_check(addr, addrs)
 
        hp = gethostbyname(ptr_word);
        if (hp != NULL && hp->h_addrtype == AF_INET) {
-           a    = *(u_int32_t *)hp->h_addr;
-           mask = ~ (u_int32_t) 0;     /* are we sure we want this? */
+           a = *(u_int32_t *)hp->h_addr;
        } else {
            np = getnetbyname (ptr_word);
-           if (np != NULL && np->n_addrtype == AF_INET)
+           if (np != NULL && np->n_addrtype == AF_INET) {
                a = htonl (*(u_int32_t *)np->n_net);
-           else
+               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) {
-               /* 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;
            }
        }
 
        if (ptr_mask != NULL)
            *ptr_mask = '/';
 
-       if (a == -1L)
+       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;
     }
@@ -1317,6 +1533,8 @@ scan_authfile(f, client, server, ipaddr, secret, addrs, filename)
 
        /*
         * Check if the given IP address is allowed by the wordlist.
+        * XXX accepts this entry even if it has no allowed IP addresses
+        * if they didn't specify a remote IP address. XXX
         */
        if (ipaddr != 0 && !ip_addr_check(ipaddr, alist)) {
            free_wordlist(alist);
@@ -1391,5 +1609,5 @@ auth_script(script)
     argv[5] = strspeed;
     argv[6] = NULL;
 
-    run_program(script, argv, 0);
+    authup_pid = run_program(script, argv, 0, NULL, NULL);
 }