[BACK]Return to pam_ssh.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / lib / libpam / modules / pam_ssh

Annotation of src/lib/libpam/modules/pam_ssh/pam_ssh.c, Revision 1.25

1.25    ! christos    1: /*     $NetBSD: pam_ssh.c,v 1.24 2018/04/07 13:57:12 christos Exp $    */
1.2       christos    2:
1.1       christos    3: /*-
                      4:  * Copyright (c) 2003 Networks Associates Technology, Inc.
                      5:  * All rights reserved.
                      6:  *
                      7:  * This software was developed for the FreeBSD Project by ThinkSec AS and
                      8:  * NAI Labs, the Security Research Division of Network Associates, Inc.
                      9:  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
                     10:  * DARPA CHATS research program.
                     11:  *
                     12:  * Redistribution and use in source and binary forms, with or without
                     13:  * modification, are permitted provided that the following conditions
                     14:  * are met:
                     15:  * 1. Redistributions of source code must retain the above copyright
                     16:  *    notice, this list of conditions and the following disclaimer.
                     17:  * 2. Redistributions in binary form must reproduce the above copyright
                     18:  *    notice, this list of conditions and the following disclaimer in the
                     19:  *    documentation and/or other materials provided with the distribution.
                     20:  * 3. The name of the author may not be used to endorse or promote
                     21:  *    products derived from this software without specific prior written
                     22:  *    permission.
                     23:  *
                     24:  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
                     25:  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
                     26:  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
                     27:  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
                     28:  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
                     29:  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
                     30:  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
                     31:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
                     32:  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
                     33:  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
                     34:  * SUCH DAMAGE.
                     35:  */
                     36:
                     37: #include <sys/cdefs.h>
1.3       lukem      38: #ifdef __FreeBSD__
1.1       christos   39: __FBSDID("$FreeBSD: src/lib/libpam/modules/pam_ssh/pam_ssh.c,v 1.40 2004/02/10 10:13:21 des Exp $");
1.2       christos   40: #else
1.25    ! christos   41: __RCSID("$NetBSD: pam_ssh.c,v 1.24 2018/04/07 13:57:12 christos Exp $");
1.2       christos   42: #endif
1.1       christos   43:
                     44: #include <sys/param.h>
                     45: #include <sys/wait.h>
                     46:
                     47: #include <errno.h>
                     48: #include <fcntl.h>
                     49: #include <paths.h>
                     50: #include <pwd.h>
                     51: #include <signal.h>
                     52: #include <stdio.h>
                     53: #include <string.h>
                     54: #include <unistd.h>
                     55:
                     56: #define PAM_SM_AUTH
                     57: #define PAM_SM_SESSION
                     58:
                     59: #include <security/pam_appl.h>
                     60: #include <security/pam_modules.h>
                     61: #include <security/openpam.h>
                     62:
                     63: #include <openssl/evp.h>
                     64:
                     65: #include "key.h"
1.13      dogcow     66: #include "buffer.h"
1.1       christos   67: #include "authfd.h"
                     68: #include "authfile.h"
                     69:
1.18      drochner   70: #define ssh_add_identity(auth, key, comment) \
1.25    ! christos   71:        ssh_add_identity_constrained(auth, key, comment, 0, 0, 0)
1.18      drochner   72:
1.1       christos   73: extern char **environ;
                     74:
                     75: struct pam_ssh_key {
                     76:        Key     *key;
                     77:        char    *comment;
                     78: };
                     79:
                     80: static const char *pam_ssh_prompt = "SSH passphrase: ";
                     81: static const char *pam_ssh_have_keys = "pam_ssh_have_keys";
                     82:
                     83: static const char *pam_ssh_keyfiles[] = {
                     84:        ".ssh/identity",        /* SSH1 RSA key */
                     85:        ".ssh/id_rsa",          /* SSH2 RSA key */
                     86:        ".ssh/id_dsa",          /* SSH2 DSA key */
1.20      drochner   87:        ".ssh/id_ecdsa",        /* SSH2 ECDSA key */
1.1       christos   88:        NULL
                     89: };
                     90:
                     91: static const char *pam_ssh_agent = "/usr/bin/ssh-agent";
1.18      drochner   92: static const char *const pam_ssh_agent_argv[] = { "ssh_agent", "-s", NULL };
                     93: static const char *const pam_ssh_agent_envp[] = { NULL };
1.1       christos   94:
                     95: /*
                     96:  * Attempts to load a private key from the specified file in the specified
                     97:  * directory, using the specified passphrase.  If successful, returns a
                     98:  * struct pam_ssh_key containing the key and its comment.
                     99:  */
                    100: static struct pam_ssh_key *
1.19      drochner  101: pam_ssh_load_key(const char *dir, const char *kfn, const char *passphrase,
                    102:     int nullok)
1.1       christos  103: {
                    104:        struct pam_ssh_key *psk;
                    105:        char fn[PATH_MAX];
                    106:        char *comment;
                    107:        Key *key;
                    108:
1.18      drochner  109:        if (snprintf(fn, sizeof(fn), "%s/%s", dir, kfn) > (int)sizeof(fn))
1.1       christos  110:                return (NULL);
                    111:        comment = NULL;
1.19      drochner  112:        /*
                    113:         * If the key is unencrypted, OpenSSL ignores the passphrase, so
                    114:         * it will seem like the user typed in the right one.  This allows
                    115:         * a user to circumvent nullok by providing a dummy passphrase.
                    116:         * Verify that the key really *is* encrypted by trying to load it
                    117:         * with an empty passphrase, and if the key is not encrypted,
                    118:         * accept only an empty passphrase.
                    119:         */
                    120:        key = key_load_private(fn, "", &comment);
                    121:        if (key != NULL && !(*passphrase == '\0' && nullok)) {
                    122:                key_free(key);
                    123:                free(comment);
                    124:                return (NULL);
                    125:        }
                    126:        if (key == NULL)
                    127:                key = key_load_private(fn, passphrase, &comment);
1.1       christos  128:        if (key == NULL) {
1.17      drochner  129:                openpam_log(PAM_LOG_DEBUG, "failed to load key from %s", fn);
1.12      jnemeth   130:                if (comment != NULL)
                    131:                        free(comment);
1.1       christos  132:                return (NULL);
                    133:        }
                    134:
1.17      drochner  135:        openpam_log(PAM_LOG_DEBUG, "loaded '%s' from %s", comment, fn);
1.1       christos  136:        if ((psk = malloc(sizeof(*psk))) == NULL) {
                    137:                key_free(key);
                    138:                free(comment);
                    139:                return (NULL);
                    140:        }
                    141:        psk->key = key;
                    142:        psk->comment = comment;
                    143:        return (psk);
                    144: }
                    145:
                    146: /*
                    147:  * Wipes a private key and frees the associated resources.
                    148:  */
                    149: static void
                    150: pam_ssh_free_key(pam_handle_t *pamh __unused,
                    151:     void *data, int pam_err __unused)
                    152: {
                    153:        struct pam_ssh_key *psk;
                    154:
                    155:        psk = data;
                    156:        key_free(psk->key);
                    157:        free(psk->comment);
                    158:        free(psk);
                    159: }
                    160:
                    161: PAM_EXTERN int
                    162: pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
                    163:     int argc __unused, const char *argv[] __unused)
                    164: {
                    165:        const char **kfn, *passphrase, *user;
1.18      drochner  166:        const void *item;
1.10      thorpej   167:        struct passwd *pwd, pwres;
1.1       christos  168:        struct pam_ssh_key *psk;
1.19      drochner  169:        int nkeys, nullok, pam_err, pass;
1.10      thorpej   170:        char pwbuf[1024];
1.1       christos  171:
1.19      drochner  172:        nullok = (openpam_get_option(pamh, "nullok") != NULL);
                    173:
1.1       christos  174:        /* PEM is not loaded by default */
                    175:        OpenSSL_add_all_algorithms();
                    176:
                    177:        /* get user name and home directory */
                    178:        pam_err = pam_get_user(pamh, &user, NULL);
                    179:        if (pam_err != PAM_SUCCESS)
                    180:                return (pam_err);
1.11      christos  181:        if (getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 ||
                    182:            pwd == NULL)
1.1       christos  183:                return (PAM_USER_UNKNOWN);
                    184:        if (pwd->pw_dir == NULL)
                    185:                return (PAM_AUTH_ERR);
                    186:
1.19      drochner  187:        nkeys = 0;
1.18      drochner  188:        pass = (pam_get_item(pamh, PAM_AUTHTOK, &item) == PAM_SUCCESS &&
                    189:            item != NULL);
1.1       christos  190:  load_keys:
                    191:        /* get passphrase */
                    192:        pam_err = pam_get_authtok(pamh, PAM_AUTHTOK,
                    193:            &passphrase, pam_ssh_prompt);
1.22      drochner  194:        if (pam_err != PAM_SUCCESS)
                    195:                return (pam_err);
                    196:
                    197:        /* switch to user credentials */
                    198:        pam_err = openpam_borrow_cred(pamh, pwd);
                    199:        if (pam_err != PAM_SUCCESS)
1.1       christos  200:                return (pam_err);
                    201:
                    202:        /* try to load keys from all keyfiles we know of */
                    203:        for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) {
1.19      drochner  204:                psk = pam_ssh_load_key(pwd->pw_dir, *kfn, passphrase, nullok);
1.1       christos  205:                if (psk != NULL) {
                    206:                        pam_set_data(pamh, *kfn, psk, pam_ssh_free_key);
                    207:                        ++nkeys;
                    208:                }
                    209:        }
                    210:
1.22      drochner  211:        /* switch back to arbitrator credentials */
                    212:        openpam_restore_cred(pamh);
                    213:
1.1       christos  214:        /*
                    215:         * If we tried an old token and didn't get anything, and
                    216:         * try_first_pass was specified, try again after prompting the
                    217:         * user for a new passphrase.
                    218:         */
                    219:        if (nkeys == 0 && pass == 1 &&
                    220:            openpam_get_option(pamh, "try_first_pass") != NULL) {
                    221:                pam_set_item(pamh, PAM_AUTHTOK, NULL);
                    222:                pass = 0;
                    223:                goto load_keys;
                    224:        }
                    225:
                    226:        /* no keys? */
                    227:        if (nkeys == 0)
                    228:                return (PAM_AUTH_ERR);
                    229:
                    230:        pam_set_data(pamh, pam_ssh_have_keys, NULL, NULL);
                    231:        return (PAM_SUCCESS);
                    232: }
                    233:
                    234: PAM_EXTERN int
                    235: pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
                    236:     int argc __unused, const char *argv[] __unused)
                    237: {
                    238:
                    239:        return (PAM_SUCCESS);
                    240: }
                    241:
                    242: /*
                    243:  * Parses a line from ssh-agent's output.
                    244:  */
                    245: static void
                    246: pam_ssh_process_agent_output(pam_handle_t *pamh, FILE *f)
                    247: {
                    248:        char *line, *p, *key, *val;
                    249:        size_t len;
                    250:
                    251:        while ((line = fgetln(f, &len)) != NULL) {
                    252:                if (len < 4 || strncmp(line, "SSH_", 4) != 0)
                    253:                        continue;
                    254:
                    255:                /* find equal sign at end of key */
                    256:                for (p = key = line; p < line + len; ++p)
                    257:                        if (*p == '=')
                    258:                                break;
                    259:                if (p == line + len || *p != '=')
                    260:                        continue;
                    261:                *p = '\0';
                    262:
                    263:                /* find semicolon at end of value */
                    264:                for (val = ++p; p < line + len; ++p)
                    265:                        if (*p == ';')
                    266:                                break;
                    267:                if (p == line + len || *p != ';')
                    268:                        continue;
                    269:                *p = '\0';
                    270:
                    271:                /* store key-value pair in environment */
                    272:                openpam_log(PAM_LOG_DEBUG, "got %s: %s", key, val);
                    273:                pam_setenv(pamh, key, val, 1);
                    274:        }
                    275: }
                    276:
                    277: /*
                    278:  * Starts an ssh agent and stores the environment variables derived from
                    279:  * its output.
                    280:  */
                    281: static int
1.4       christos  282: pam_ssh_start_agent(pam_handle_t *pamh, struct passwd *pwd)
1.1       christos  283: {
                    284:        int agent_pipe[2];
                    285:        pid_t pid;
                    286:        FILE *f;
                    287:
                    288:        /* get a pipe which we will use to read the agent's output */
1.4       christos  289:        if (pipe(agent_pipe) == -1)
1.1       christos  290:                return (PAM_SYSTEM_ERR);
                    291:
                    292:        /* start the agent */
                    293:        openpam_log(PAM_LOG_DEBUG, "starting an ssh agent");
                    294:        pid = fork();
                    295:        if (pid == (pid_t)-1) {
                    296:                /* failed */
                    297:                close(agent_pipe[0]);
                    298:                close(agent_pipe[1]);
                    299:                return (PAM_SYSTEM_ERR);
                    300:        }
                    301:        if (pid == 0) {
1.2       christos  302: #ifndef F_CLOSEM
1.1       christos  303:                int fd;
1.2       christos  304: #endif
1.1       christos  305:                /* child: drop privs, close fds and start agent */
1.4       christos  306:                if (setgid(pwd->pw_gid) == -1) {
1.21      christos  307:                        openpam_log(PAM_LOG_DEBUG, "%s: Cannot setgid %d (%s)",
                    308:                            __func__, (int)pwd->pw_gid, strerror(errno));
1.4       christos  309:                        goto done;
                    310:                }
                    311:                if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) {
                    312:                        openpam_log(PAM_LOG_DEBUG,
1.21      christos  313:                            "%s: Cannot initgroups for %s (%s)",
                    314:                            __func__, pwd->pw_name, strerror(errno));
1.4       christos  315:                        goto done;
                    316:                }
                    317:                if (setuid(pwd->pw_uid) == -1) {
1.21      christos  318:                        openpam_log(PAM_LOG_DEBUG, "%s: Cannot setuid %d (%s)",
                    319:                            __func__, (int)pwd->pw_uid, strerror(errno));
1.4       christos  320:                        goto done;
                    321:                }
                    322:                (void)close(STDIN_FILENO);
                    323:                (void)open(_PATH_DEVNULL, O_RDONLY);
                    324:                (void)dup2(agent_pipe[1], STDOUT_FILENO);
                    325:                (void)dup2(agent_pipe[1], STDERR_FILENO);
1.2       christos  326: #ifdef F_CLOSEM
                    327:                (void)fcntl(3, F_CLOSEM, 0);
                    328: #else
1.1       christos  329:                for (fd = 3; fd < getdtablesize(); ++fd)
1.4       christos  330:                        (void)close(fd);
1.2       christos  331: #endif
1.4       christos  332:                (void)execve(pam_ssh_agent,
                    333:                    (char **)__UNCONST(pam_ssh_agent_argv),
1.2       christos  334:                    (char **)__UNCONST(pam_ssh_agent_envp));
1.4       christos  335: done:
1.1       christos  336:                _exit(127);
                    337:        }
                    338:
                    339:        /* parent */
                    340:        close(agent_pipe[1]);
                    341:        if ((f = fdopen(agent_pipe[0], "r")) == NULL)
                    342:                return (PAM_SYSTEM_ERR);
                    343:        pam_ssh_process_agent_output(pamh, f);
                    344:        fclose(f);
                    345:
                    346:        return (PAM_SUCCESS);
                    347: }
                    348:
                    349: /*
                    350:  * Adds previously stored keys to a running agent.
                    351:  */
                    352: static int
                    353: pam_ssh_add_keys_to_agent(pam_handle_t *pamh)
                    354: {
1.15      christos  355:        const struct pam_ssh_key *psk;
1.1       christos  356:        const char **kfn;
                    357:        char **envlist, **env;
                    358:        int pam_err;
1.23      christos  359:        int agent_fd;
1.1       christos  360:
                    361:        /* switch to PAM environment */
                    362:        envlist = environ;
                    363:        if ((environ = pam_getenvlist(pamh)) == NULL) {
1.4       christos  364:                openpam_log(PAM_LOG_DEBUG, "%s: cannot get envlist",
1.14      ragge     365:                    __func__);
1.1       christos  366:                environ = envlist;
                    367:                return (PAM_SYSTEM_ERR);
                    368:        }
                    369:
                    370:        /* get a connection to the agent */
1.23      christos  371:        if (ssh_get_authentication_socket(&agent_fd) != 0) {
1.4       christos  372:                openpam_log(PAM_LOG_DEBUG,
                    373:                    "%s: cannot get authentication connection",
1.14      ragge     374:                    __func__);
1.1       christos  375:                pam_err = PAM_SYSTEM_ERR;
1.23      christos  376:                agent_fd = -1;
1.1       christos  377:                goto end;
                    378:        }
                    379:
                    380:        /* look for keys to add to it */
                    381:        for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) {
1.15      christos  382:                const void *vp;
                    383:                pam_err = pam_get_data(pamh, *kfn, &vp);
                    384:                psk = vp;
1.1       christos  385:                if (pam_err == PAM_SUCCESS && psk != NULL) {
1.25    ! christos  386:                        if (ssh_add_identity(agent_fd, psk->key, psk->comment))
1.1       christos  387:                                openpam_log(PAM_LOG_DEBUG,
                    388:                                    "added %s to ssh agent", psk->comment);
                    389:                        else
                    390:                                openpam_log(PAM_LOG_DEBUG, "failed "
                    391:                                    "to add %s to ssh agent", psk->comment);
                    392:                        /* we won't need the key again, so wipe it */
                    393:                        pam_set_data(pamh, *kfn, NULL, NULL);
                    394:                }
                    395:        }
                    396:        pam_err = PAM_SUCCESS;
                    397:  end:
                    398:        /* disconnect from agent */
1.23      christos  399:        if (agent_fd != -1)
                    400:                ssh_close_authentication_socket(agent_fd);
1.1       christos  401:
                    402:        /* switch back to original environment */
                    403:        for (env = environ; *env != NULL; ++env)
                    404:                free(*env);
                    405:        free(environ);
                    406:        environ = envlist;
                    407:
                    408:        return (pam_err);
                    409: }
                    410:
                    411: PAM_EXTERN int
                    412: pam_sm_open_session(pam_handle_t *pamh, int flags __unused,
                    413:     int argc __unused, const char *argv[] __unused)
                    414: {
1.10      thorpej   415:        struct passwd *pwd, pwres;
1.1       christos  416:        const char *user;
1.15      christos  417:        const void *data;
1.4       christos  418:        int pam_err = PAM_SUCCESS;
1.10      thorpej   419:        char pwbuf[1024];
1.1       christos  420:
                    421:        /* no keys, no work */
                    422:        if (pam_get_data(pamh, pam_ssh_have_keys, &data) != PAM_SUCCESS &&
                    423:            openpam_get_option(pamh, "want_agent") == NULL)
                    424:                return (PAM_SUCCESS);
                    425:
                    426:        /* switch to user credentials */
                    427:        pam_err = pam_get_user(pamh, &user, NULL);
                    428:        if (pam_err != PAM_SUCCESS)
                    429:                return (pam_err);
1.11      christos  430:        if (getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 ||
                    431:            pwd == NULL)
1.1       christos  432:                return (PAM_USER_UNKNOWN);
1.4       christos  433:
                    434:        /* start the agent */
                    435:        pam_err = pam_ssh_start_agent(pamh, pwd);
                    436:        if (pam_err != PAM_SUCCESS)
                    437:                return pam_err;
                    438:
1.1       christos  439:        pam_err = openpam_borrow_cred(pamh, pwd);
                    440:        if (pam_err != PAM_SUCCESS)
1.4       christos  441:                return pam_err;
1.1       christos  442:
                    443:        /* we have an agent, see if we can add any keys to it */
                    444:        pam_err = pam_ssh_add_keys_to_agent(pamh);
                    445:        if (pam_err != PAM_SUCCESS) {
                    446:                /* XXX ignore failures */
1.4       christos  447:                openpam_log(PAM_LOG_DEBUG, "failed adding keys to ssh agent");
                    448:                pam_err = PAM_SUCCESS;
1.1       christos  449:        }
                    450:
                    451:        openpam_restore_cred(pamh);
1.4       christos  452:        return pam_err;
1.1       christos  453: }
                    454:
                    455: PAM_EXTERN int
                    456: pam_sm_close_session(pam_handle_t *pamh, int flags __unused,
                    457:     int argc __unused, const char *argv[] __unused)
                    458: {
                    459:        const char *ssh_agent_pid;
                    460:        char *end;
                    461:        int status;
                    462:        pid_t pid;
                    463:
                    464:        if ((ssh_agent_pid = pam_getenv(pamh, "SSH_AGENT_PID")) == NULL) {
                    465:                openpam_log(PAM_LOG_DEBUG, "no ssh agent");
                    466:                return (PAM_SUCCESS);
                    467:        }
                    468:        pid = (pid_t)strtol(ssh_agent_pid, &end, 10);
                    469:        if (*ssh_agent_pid == '\0' || *end != '\0') {
                    470:                openpam_log(PAM_LOG_DEBUG, "invalid ssh agent pid");
                    471:                return (PAM_SESSION_ERR);
                    472:        }
                    473:        openpam_log(PAM_LOG_DEBUG, "killing ssh agent %d", (int)pid);
                    474:        if (kill(pid, SIGTERM) == -1 ||
                    475:            (waitpid(pid, &status, 0) == -1 && errno != ECHILD))
                    476:                return (PAM_SYSTEM_ERR);
                    477:        return (PAM_SUCCESS);
                    478: }
                    479:
                    480: PAM_MODULE_ENTRY("pam_ssh");

CVSweb <webmaster@jp.NetBSD.org>