]> git.ozlabs.org Git - ppp.git/blobdiff - pppd/session.c
Added new "enable-session" option to enable session accounting and
[ppp.git] / pppd / session.c
diff --git a/pppd/session.c b/pppd/session.c
new file mode 100644 (file)
index 0000000..05dcb76
--- /dev/null
@@ -0,0 +1,405 @@
+/*
+ * session.c - PPP session control.
+ *
+ * Copyright (c) 2007 Diego Rivera. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. The name(s) of the authors of this software must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission.
+ *
+ * 3. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by Paul Mackerras
+ *     <paulus@samba.org>".
+ *
+ * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Derived from auth.c, which is:
+ *
+ * Copyright (c) 1984-2000 Carnegie Mellon University. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The name "Carnegie Mellon University" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For permission or any legal
+ *    details, please contact
+ *      Office of Technology Transfer
+ *      Carnegie Mellon University
+ *      5000 Forbes Avenue
+ *      Pittsburgh, PA  15213-3890
+ *      (412) 268-4387, fax: (412) 268-7395
+ *      tech-transfer@andrew.cmu.edu
+ *
+ * 4. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by Computing Services
+ *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
+ *
+ * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
+ * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+#include <crypt.h>
+#include "pppd.h"
+#include "session.h"
+
+#ifdef USE_PAM
+#include <security/pam_appl.h>
+#endif /* #ifdef USE_PAM */
+
+#define SET_MSG(var, msg) if (var != NULL) { var[0] = msg; }
+#define COPY_STRING(s) ((s) ? strdup(s) : NULL)
+
+#define SUCCESS_MSG "Session started successfully"
+#define ABORT_MSG "Session can't be started without a username"
+#define SERVICE_NAME "ppp"
+
+#define SESSION_FAILED  0
+#define SESSION_OK      1
+
+/* We have successfully started a session */
+static bool logged_in = 0;
+
+#ifdef USE_PAM
+/*
+ * Static variables used to communicate between the conversation function
+ * and the server_login function
+ */
+static const char *PAM_username;
+static const char *PAM_password;
+static int   PAM_session = 0;
+static pam_handle_t *pamh = NULL;
+
+/* PAM conversation function
+ * Here we assume (for now, at least) that echo on means login name, and
+ * echo off means password.
+ */
+
+static int conversation (int num_msg,
+#ifndef SOL2
+    const
+#endif
+    struct pam_message **msg,
+    struct pam_response **resp, void *appdata_ptr)
+{
+    int replies = 0;
+    struct pam_response *reply = NULL;
+
+    reply = malloc(sizeof(struct pam_response) * num_msg);
+    if (!reply) return PAM_CONV_ERR;
+
+    for (replies = 0; replies < num_msg; replies++) {
+        switch (msg[replies]->msg_style) {
+            case PAM_PROMPT_ECHO_ON:
+                reply[replies].resp_retcode = PAM_SUCCESS;
+                reply[replies].resp = COPY_STRING(PAM_username);
+                /* PAM frees resp */
+                break;
+            case PAM_PROMPT_ECHO_OFF:
+                reply[replies].resp_retcode = PAM_SUCCESS;
+                reply[replies].resp = COPY_STRING(PAM_password);
+                /* PAM frees resp */
+                break;
+            case PAM_TEXT_INFO:
+                /* fall through */
+            case PAM_ERROR_MSG:
+                /* ignore it, but pam still wants a NULL response... */
+                reply[replies].resp_retcode = PAM_SUCCESS;
+                reply[replies].resp = NULL;
+                break;
+            default:
+                /* Must be an error of some sort... */
+                free (reply);
+                return PAM_CONV_ERR;
+        }
+    }
+    *resp = reply;
+    return PAM_SUCCESS;
+}
+
+static struct pam_conv pam_conv_data = {
+    &conversation,
+    NULL
+};
+#endif /* #ifdef USE_PAM */
+
+int
+session_start(flags, user, passwd, ttyName, msg)
+    const int flags;
+    const char *user;
+    const char *passwd;
+    const char *ttyName;
+    char **msg;
+{
+    bool ok = 1;
+#ifdef USE_PAM
+    const char *usr;
+    int pam_error;
+    bool try_session = 0;
+#else /* #ifdef USE_PAM */
+    struct passwd *pw;
+#ifdef HAS_SHADOW
+    struct spwd *spwd;
+    struct spwd *getspnam();
+    long now = 0;
+#endif /* #ifdef HAS_SHADOW */
+#endif /* #ifdef USE_PAM */
+
+    SET_MSG(msg, SUCCESS_MSG);
+
+    /* If no verification is requested, then simply return an OK */
+    if (!(SESS_ALL & flags)) {
+        return SESSION_OK;
+    }
+
+    if (user == NULL) {
+       SET_MSG(msg, ABORT_MSG);
+       return SESSION_FAILED;
+    }
+
+#ifdef USE_PAM
+    /* Find the '\\' in the username */
+    /* This needs to be fixed to support different username schemes */
+    if ((usr = strchr(user, '\\')) == NULL)
+       usr = user;
+    else
+       usr++;
+
+    PAM_session = 0;
+    PAM_username = usr;
+    PAM_password = passwd;
+
+    dbglog("Initializing PAM (%d) for user %s", flags, usr);
+    pam_error = pam_start (SERVICE_NAME, usr, &pam_conv_data, &pamh);
+    dbglog("---> PAM INIT Result = %d", pam_error);
+    ok = (pam_error == PAM_SUCCESS);
+
+    if (ok) {
+        ok = (pam_set_item(pamh, PAM_TTY, ttyName) == PAM_SUCCESS) &&
+           (pam_set_item(pamh, PAM_RHOST, ifname) == PAM_SUCCESS);
+    }
+
+    if (ok && (SESS_AUTH & flags)) {
+        dbglog("Attempting PAM authentication");
+        pam_error = pam_authenticate (pamh, PAM_SILENT);
+        if (pam_error == PAM_SUCCESS) {
+            /* PAM auth was OK */
+            dbglog("PAM Authentication OK for %s", user);
+        } else {
+            /* No matter the reason, we fail because we're authenticating */
+            ok = 0;
+            if (pam_error == PAM_USER_UNKNOWN) {
+                dbglog("User unknown, failing PAM authentication");
+                SET_MSG(msg, "User unknown - cannot authenticate via PAM");
+            } else {
+                /* Any other error means authentication was bad */
+                dbglog("PAM Authentication failed: %d: %s", pam_error,
+                      pam_strerror(pamh, pam_error));
+                SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
+            }
+        }
+    }
+
+    if (ok && (SESS_ACCT & flags)) {
+        dbglog("Attempting PAM account checks");
+        pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
+        if (pam_error == PAM_SUCCESS) {
+            /*
+            * PAM account was OK, set the flag which indicates that we should
+            * try to perform the session checks.
+            */
+            try_session = 1;
+            dbglog("PAM Account OK for %s", user);
+        } else {
+            /*
+            * If the account checks fail, then we should not try to perform
+            * the session check, because they don't make sense.
+            */
+            try_session = 0;
+            if (pam_error == PAM_USER_UNKNOWN) {
+                /*
+                * We're checking the account, so it's ok to not have one
+                * because the user might come from the secrets files, or some
+                * other plugin.
+                */
+                dbglog("User unknown, ignoring PAM restrictions");
+                SET_MSG(msg, "User unknown - ignoring PAM restrictions");
+            } else {
+                /* Any other error means session is rejected */
+                ok = 0;
+                dbglog("PAM Account checks failed: %d: %s", pam_error,
+                      pam_strerror(pamh, pam_error));
+                SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
+            }
+        }
+    }
+
+    if (ok && try_session && (SESS_ACCT & flags)) {
+        /* Only open a session if the user's account was found */
+        pam_error = pam_open_session (pamh, PAM_SILENT);
+        if (pam_error == PAM_SUCCESS) {
+            dbglog("PAM Session opened for user %s", user);
+            PAM_session = 1;
+        } else {
+            dbglog("PAM Session denied for user %s", user);
+            SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
+            ok = 0;
+        }
+    }
+
+    /* This is needed because apparently the PAM stuff closes the log */
+    reopen_log();
+
+    /* If our PAM checks have already failed, then we must return a failure */
+    if (!ok) return SESSION_FAILED;
+
+#else /* #ifdef USE_PAM */
+
+/*
+ * Use the non-PAM methods directly
+ */
+
+    if ((SESS_AUTH & flags)) {
+       pw = getpwnam(user);
+
+       endpwent();
+       /*
+        * Here, we bail if we have no user account, because there is nothing
+        * to verify against.
+        */
+       if (pw == NULL)
+           return SESSION_FAILED;
+
+#ifdef HAS_SHADOW
+
+       spwd = getspnam(user);
+       endspent();
+
+       /*
+        * If there is no shadow entry for the user, then we can't verify the
+        * account.
+        */
+       if (spwd == NULL)
+           return SESSION_FAILED;
+
+       /*
+        * We check validity all the time, because if the password has expired,
+        * then clearly we should not authenticate against it (if we're being
+        * called for authentication only).  Thus, in this particular instance,
+        * there is no real difference between using the AUTH, SESS or ACCT
+        * flags, or combinations thereof.
+        */
+       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)) {
+           warn("Password for %s has expired", user);
+           return SESSION_FAILED;
+       }
+
+       /* We have a valid shadow entry, keep the password */
+       pw->pw_passwd = spwd->sp_pwdp;
+
+#endif /* #ifdef HAS_SHADOW */
+
+       /*
+        * If no passwd, don't let them login if we're authenticating.
+        */
+        if (pw->pw_passwd == NULL || strlen(pw->pw_passwd) < 2
+            || strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd) != 0)
+            return SESSION_FAILED;
+    }
+
+#endif /* #ifdef USE_PAM */
+
+    /*
+     * Write a wtmp entry for this user.
+     */
+
+    if (SESS_ACCT & flags) {
+       if (strncmp(ttyName, "/dev/", 5) == 0)
+           ttyName += 5;
+       logwtmp(ttyName, user, ifname); /* Add wtmp login entry */
+       logged_in = 1;
+
+#if defined(_PATH_LASTLOG) && !defined(USE_PAM)
+       {
+            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, ttyName, sizeof(ll.ll_line));
+                (void)strncpy(ll.ll_host, ifname, sizeof(ll.ll_host));
+                (void)write(fd, (char *)&ll, sizeof(ll));
+                (void)close(fd);
+            }
+       }
+#endif /* _PATH_LASTLOG and not USE_PAM */
+       info("user %s logged in on tty %s intf %s", user, ttyName, ifname);
+    }
+
+    return SESSION_OK;
+}
+
+/*
+ * session_end - Logout the user.
+ */
+void
+session_end(const char* ttyName)
+{
+#ifdef USE_PAM
+    int pam_error = PAM_SUCCESS;
+
+    if (pamh != NULL) {
+        if (PAM_session) pam_error = pam_close_session (pamh, PAM_SILENT);
+        PAM_session = 0;
+        pam_end (pamh, pam_error);
+        pamh = NULL;
+       /* Apparently the pam stuff does closelog(). */
+       reopen_log();
+    }
+#endif
+    if (logged_in) {
+       if (strncmp(ttyName, "/dev/", 5) == 0)
+           ttyName += 5;
+       logwtmp(ttyName, "", ""); /* Wipe out utmp logout entry */
+       logged_in = 0;
+    }
+}