314 lines
10 KiB
C
314 lines
10 KiB
C
/*
|
|
* Copyright (c) 2000 Markus Friedl. 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.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* Some miniscule portions Copyright, 2008 Jamie Beverly
|
|
* for pam_ssh_agent_auth PAM module
|
|
*
|
|
* Those portions retain the BSD style license of the original
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <pwd.h>
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include "xmalloc.h"
|
|
#include "ssh.h"
|
|
#include "ssh2.h"
|
|
#include "buffer.h"
|
|
#include "log.h"
|
|
#include "compat.h"
|
|
#include "key.h"
|
|
#include "pathnames.h"
|
|
#include "misc.h"
|
|
#include "secure_filename.h"
|
|
#include "uidswap.h"
|
|
|
|
#include "identity.h"
|
|
|
|
/* return 1 if user allows given key */
|
|
/* Modified slightly from original found in auth2-pubkey.c */
|
|
static int
|
|
pamsshagentauth_check_authkeys_file(FILE * f, char *file, Key * key)
|
|
{
|
|
char line[SSH_MAX_PUBKEY_BYTES];
|
|
int found_key = 0;
|
|
u_long linenum = 0;
|
|
Key *found;
|
|
char *fp;
|
|
|
|
found_key = 0;
|
|
found = pamsshagentauth_key_new(key->type);
|
|
|
|
while(read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
|
|
char *cp = NULL; /* *key_options = NULL; */
|
|
|
|
/* Skip leading whitespace, empty and comment lines. */
|
|
for(cp = line; *cp == ' ' || *cp == '\t'; cp++);
|
|
if(!*cp || *cp == '\n' || *cp == '#')
|
|
continue;
|
|
|
|
if(pamsshagentauth_key_read(found, &cp) != 1) {
|
|
/* no key? check if there are options for this key */
|
|
int quoted = 0;
|
|
|
|
pamsshagentauth_verbose("user_key_allowed: check options: '%s'", cp);
|
|
/* key_options = cp; */
|
|
for(; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
|
|
if(*cp == '\\' && cp[1] == '"')
|
|
cp++; /* Skip both */
|
|
else if(*cp == '"')
|
|
quoted = !quoted;
|
|
}
|
|
/* Skip remaining whitespace. */
|
|
for(; *cp == ' ' || *cp == '\t'; cp++);
|
|
if(pamsshagentauth_key_read(found, &cp) != 1) {
|
|
pamsshagentauth_verbose("user_key_allowed: advance: '%s'", cp);
|
|
/* still no key? advance to next line */
|
|
continue;
|
|
}
|
|
}
|
|
if(pamsshagentauth_key_equal(found, key)) {
|
|
found_key = 1;
|
|
pamsshagentauth_logit("matching key found: file/command %s, line %lu", file,
|
|
linenum);
|
|
fp = pamsshagentauth_key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX);
|
|
pamsshagentauth_logit("Found matching %s key: %s",
|
|
pamsshagentauth_key_type(found), fp);
|
|
pamsshagentauth_xfree(fp);
|
|
break;
|
|
}
|
|
}
|
|
pamsshagentauth_key_free(found);
|
|
if(!found_key)
|
|
pamsshagentauth_verbose("key not found");
|
|
return found_key;
|
|
}
|
|
|
|
/*
|
|
* Checks whether key is allowed in file.
|
|
* returns 1 if the key is allowed or 0 otherwise.
|
|
*/
|
|
int
|
|
pamsshagentauth_user_key_allowed2(struct passwd *pw, Key * key, char *file)
|
|
{
|
|
FILE *f;
|
|
int found_key = 0;
|
|
struct stat st;
|
|
char buf[SSH_MAX_PUBKEY_BYTES];
|
|
|
|
/* Temporarily use the user's uid. */
|
|
pamsshagentauth_verbose("trying public key file %s", file);
|
|
|
|
/* Fail not so quietly if file does not exist */
|
|
if(stat(file, &st) < 0) {
|
|
pamsshagentauth_verbose("File not found: %s", file);
|
|
return 0;
|
|
}
|
|
|
|
/* Open the file containing the authorized keys. */
|
|
f = fopen(file, "r");
|
|
if(!f) {
|
|
return 0;
|
|
}
|
|
|
|
if(pamsshagentauth_secure_filename(f, file, pw, buf, sizeof(buf)) != 0) {
|
|
fclose(f);
|
|
pamsshagentauth_logit("Authentication refused: %s", buf);
|
|
return 0;
|
|
}
|
|
|
|
found_key = pamsshagentauth_check_authkeys_file(f, file, key);
|
|
fclose(f);
|
|
return found_key;
|
|
}
|
|
|
|
/*
|
|
* Checks whether key is allowed in output of command.
|
|
* returns 1 if the key is allowed or 0 otherwise.
|
|
*/
|
|
int
|
|
pamsshagentauth_user_key_command_allowed2(char *authorized_keys_command,
|
|
char *authorized_keys_command_user,
|
|
struct passwd *user_pw, Key * key)
|
|
{
|
|
FILE *f;
|
|
int ok, found_key = 0;
|
|
struct passwd *pw;
|
|
struct stat st;
|
|
int status, devnull, p[2], i;
|
|
pid_t pid;
|
|
char errmsg[512];
|
|
char username[512] = { 0 };
|
|
|
|
|
|
|
|
if(authorized_keys_command == NULL || authorized_keys_command[0] != '/')
|
|
return 0;
|
|
|
|
|
|
/* getpwnam of authorized_keys_command_user will overwrite the statics used by getpwnam (including pw_name) */
|
|
strncpy(username, user_pw->pw_name, sizeof(username) - 1);
|
|
|
|
/* If no user specified to run commands the default to target user */
|
|
if(authorized_keys_command_user == NULL) {
|
|
pw = user_pw;
|
|
}
|
|
else {
|
|
pw = getpwnam(authorized_keys_command_user);
|
|
if(pw == NULL) {
|
|
pamsshagentauth_logerror("authorized_keys_command_user \"%s\" not found: %s",
|
|
authorized_keys_command_user, strerror(errno));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
pamsshagentauth_temporarily_use_uid(pw);
|
|
|
|
if(stat(authorized_keys_command, &st) < 0) {
|
|
pamsshagentauth_logerror
|
|
("Could not stat AuthorizedKeysCommand \"%s\": %s",
|
|
authorized_keys_command, strerror(errno));
|
|
goto out;
|
|
}
|
|
if(pamsshagentauth_auth_secure_path
|
|
(authorized_keys_command, &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) {
|
|
pamsshagentauth_logerror("Unsafe AuthorizedKeysCommand: %s", errmsg);
|
|
goto out;
|
|
}
|
|
|
|
/* open the pipe and read the keys */
|
|
if(pipe(p) != 0) {
|
|
pamsshagentauth_logerror("%s: pipe: %s", __func__, strerror(errno));
|
|
goto out;
|
|
}
|
|
|
|
pamsshagentauth_debug("Running AuthorizedKeysCommand: \"%s\" as \"%s\" with argument: \"%s\"",
|
|
authorized_keys_command, pw->pw_name, username);
|
|
|
|
/*
|
|
* Don't want to call this in the child, where it can fatal() and
|
|
* run cleanup_exit() code.
|
|
*/
|
|
pamsshagentauth_restore_uid();
|
|
|
|
switch ((pid = fork())) {
|
|
case -1: /* error */
|
|
pamsshagentauth_logerror("%s: fork: %s", __func__, strerror(errno));
|
|
close(p[0]);
|
|
close(p[1]);
|
|
return 0;
|
|
case 0: /* child */
|
|
for(i = 0; i < NSIG; i++)
|
|
signal(i, SIG_DFL);
|
|
|
|
/* do this before the setresuid so thta they can be logged */
|
|
if((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
|
|
pamsshagentauth_logerror("%s: open %s: %s", __func__, _PATH_DEVNULL,
|
|
strerror(errno));
|
|
_exit(1);
|
|
}
|
|
if(dup2(devnull, STDIN_FILENO) == -1 || dup2(p[1], STDOUT_FILENO) == -1
|
|
|| dup2(devnull, STDERR_FILENO) == -1) {
|
|
pamsshagentauth_logerror("%s: dup2: %s", __func__, strerror(errno));
|
|
_exit(1);
|
|
}
|
|
#if defined(HAVE_SETRESGID) && !defined(BROKEN_SETRESGID)
|
|
if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) {
|
|
#else
|
|
if (setgid(pw->pw_gid) != 0 || setegid(pw->pw_gid) != 0) {
|
|
#endif
|
|
pamsshagentauth_logerror("setresgid %u: %s", (u_int) pw->pw_gid,
|
|
strerror(errno));
|
|
_exit(1);
|
|
}
|
|
|
|
#ifdef HAVE_SETRESUID
|
|
if(setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) {
|
|
#else
|
|
if (setuid(pw->pw_uid) != 0 || seteuid(pw->pw_uid) != 0) {
|
|
#endif
|
|
pamsshagentauth_logerror("setresuid %u: %s", (u_int) pw->pw_uid,
|
|
strerror(errno));
|
|
_exit(1);
|
|
}
|
|
|
|
close(p[0]);
|
|
closefrom(STDERR_FILENO + 1);
|
|
|
|
execl(authorized_keys_command, authorized_keys_command, username, (char *)NULL);
|
|
|
|
/* pretty sure this will barf because we are now suid, but since we
|
|
should't reach this anyway, I'll leave it here */
|
|
pamsshagentauth_logerror("AuthorizedKeysCommand %s exec failed: %s",
|
|
authorized_keys_command, strerror(errno));
|
|
_exit(127);
|
|
default: /* parent */
|
|
break;
|
|
}
|
|
|
|
pamsshagentauth_temporarily_use_uid(pw);
|
|
|
|
close(p[1]);
|
|
if((f = fdopen(p[0], "r")) == NULL) {
|
|
pamsshagentauth_logerror("%s: fdopen: %s", __func__, strerror(errno));
|
|
close(p[0]);
|
|
/* Don't leave zombie child */
|
|
while(waitpid(pid, NULL, 0) == -1 && errno == EINTR);
|
|
goto out;
|
|
}
|
|
ok = pamsshagentauth_check_authkeys_file(f, authorized_keys_command, key);
|
|
fclose(f);
|
|
|
|
while(waitpid(pid, &status, 0) == -1) {
|
|
if(errno != EINTR) {
|
|
pamsshagentauth_logerror("%s: waitpid: %s", __func__,
|
|
strerror(errno));
|
|
goto out;
|
|
}
|
|
}
|
|
if(WIFSIGNALED(status)) {
|
|
pamsshagentauth_logerror("AuthorizedKeysCommand %s exited on signal %d",
|
|
authorized_keys_command, WTERMSIG(status));
|
|
goto out;
|
|
} else if(WEXITSTATUS(status) != 0) {
|
|
pamsshagentauth_logerror("AuthorizedKeysCommand %s returned status %d",
|
|
authorized_keys_command, WEXITSTATUS(status));
|
|
goto out;
|
|
}
|
|
found_key = ok;
|
|
out:
|
|
pamsshagentauth_restore_uid();
|
|
return found_key;
|
|
}
|