pam-modules/pam_ldaphome/pam_ldaphome.c
Sergey Poznyakoff b4fb52526e Bugfix
2022-05-09 11:05:08 +03:00

2297 lines
47 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This file is part of pam-modules.
Copyright (C) 2005-2022 Sergey Poznyakoff
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>. */
#ifdef HAVE__PAM_ACONF_H
# include <security/_pam_aconf.h>
#endif
#ifndef LINUX_PAM
# include <security/pam_appl.h>
#endif /* LINUX_PAM */
#include <security/pam_modules.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <ldap.h>
#include <pwd.h>
#include <grp.h>
#include "graypam.h"
/* indicate the following groups are defined */
#define PAM_SM_AUTH
static long debug_level;
static int cntl_flags;
static char *config_file_name;
static int ldap_debug_level;
/* FIXME: This should be read from sshd_config */
static char *authorized_keys_file=".ssh/authorized_keys";
static char *ldap_config_name = "/etc/ldap.conf";
struct pam_opt pam_opt[] = {
{ PAM_OPTSTR(debug), pam_opt_long, &debug_level },
{ PAM_OPTSTR(debug), pam_opt_const, &debug_level, { 1 } },
{ PAM_OPTSTR(audit), pam_opt_const, &debug_level, { 100 } },
{ PAM_OPTSTR(waitdebug), pam_opt_null, NULL, { 0 },
gray_wait_debug_fun },
{ PAM_OPTSTR(config), pam_opt_string, &config_file_name },
{ NULL }
};
static void
_pam_parse(pam_handle_t *pamh, int argc, const char **argv)
{
cntl_flags = 0;
debug_level = 0;
config_file_name = SYSCONFDIR "/" MODULE_NAME ".conf";
gray_log_init(0, MODULE_NAME, LOG_AUTHPRIV);
gray_parseopt(pam_opt, argc, argv);
}
static void
argcv_free(int wc, char **wv)
{
int i;
for (i = 0; i < wc; i++)
free(wv[i]);
free(wv);
}
static void
argcvz_free(char **wv)
{
int i;
for (i = 0; wv[i]; i++)
free(wv[i]);
free(wv);
}
static int
argcv_split(const char *str, int *pargc, char ***pargv)
{
int argc, i;
char **argv;
const char *p;
int rc = 0;
argc = 1;
for (p = str; *p; p++) {
if (*p == ' ')
argc++;
}
argv = calloc(argc + 1, sizeof(argv[0]));
if (!argv)
return 1;
for (i = 0, p = str;;) {
size_t len = strcspn(p, " ");
char *q = malloc(len + 1);
if (!q) {
rc = errno;
break;
}
memcpy(q, p, len);
q[len] = 0;
argv[i++] = q;
p += len;
if (p)
p += strspn(p, " ");
if (!*p)
break;
}
if (rc) {
argcv_free(argc, argv);
errno = rc;
return 1;
}
argv[i] = NULL;
*pargc = argc;
*pargv = argv;
return 0;
}
static char *
argcv_concat(int wc, char **wv)
{
char *res, *p;
size_t size = 0;
int i;
for (i = 0; i < wc; i++)
size += strlen(wv[i]) + 1;
res = malloc(size);
if (!res)
return 0;
for (p = res, i = 0;;) {
strcpy(p, wv[i]);
p += strlen(wv[i]);
if (++i < wc)
*p++ = ' ';
else
break;
}
*p = 0;
return res;
}
static int
get_intval(struct gray_env *env, const char *name, int base, unsigned long *pv)
{
char *p;
char *v = gray_env_get(env, name);
if (!v)
return 1;
*pv = strtoul(v, &p, base);
if (*p) {
_pam_log(LOG_ERR, "configuration variable %s is not integer",
name);
return -1;
}
return 0;
}
static char *
parse_ldap_uri(const char *uri)
{
int wc;
char **wv;
LDAPURLDesc *ludlist, **ludp;
char **urls = NULL;
int nurls = 0;
char *ldapuri = NULL;
int rc;
rc = ldap_url_parse(uri, &ludlist);
if (rc != LDAP_URL_SUCCESS) {
_pam_log(LOG_ERR, "cannot parse LDAP URL(s)=%s (%d)",
uri, rc);
return NULL;
}
for (ludp = &ludlist; *ludp; ) {
LDAPURLDesc *lud = *ludp;
char **tmp;
if (lud->lud_dn && lud->lud_dn[0]
&& (lud->lud_host == NULL || lud->lud_host[0] == '\0')) {
/* if no host but a DN is provided, try
DNS SRV to gather the host list */
char *domain = NULL, *hostlist = NULL;
size_t i;
if (ldap_dn2domain(lud->lud_dn, &domain) ||
!domain) {
_pam_log(LOG_ERR,
"DNS SRV: cannot convert "
"DN=\"%s\" into a domain",
lud->lud_dn);
goto dnssrv_free;
}
rc = ldap_domain2hostlist(domain, &hostlist);
if (rc) {
_pam_log(LOG_ERR,
"DNS SRV: cannot convert "
"domain=%s into a hostlist",
domain);
goto dnssrv_free;
}
if (argcv_split(hostlist, &wc, &wv)) {
_pam_log(LOG_ERR,
"DNS SRV: could not parse "
"hostlist=\"%s\": %s",
hostlist, strerror(errno));
goto dnssrv_free;
}
tmp = realloc(urls, sizeof(char *) * (nurls + wc + 1));
if (!tmp) {
_pam_log(LOG_ERR,
"DNS SRV %s", strerror(errno));
goto dnssrv_free;
}
urls = tmp;
urls[nurls] = NULL;
for (i = 0; i < wc; i++) {
char *p = malloc(strlen(lud->lud_scheme) +
strlen(wv[i]) +
3);
if (!p) {
_pam_log(LOG_ERR, "DNS SRV %s",
strerror(errno));
goto dnssrv_free;
}
strcpy(p, lud->lud_scheme);
strcat(p, "//");
strcat(p, wv[i]);
urls[nurls + i + 1] = NULL;
urls[nurls + i] = p;
}
nurls += i;
dnssrv_free:
argcv_free(wc, wv);
ber_memfree(hostlist);
ber_memfree(domain);
} else {
tmp = realloc(urls, sizeof(char *) * (nurls + 2));
if (!tmp) {
_pam_log(LOG_ERR,
"DNS SRV %s", strerror(errno));
break;
}
urls = tmp;
urls[nurls + 1] = NULL;
urls[nurls] = ldap_url_desc2str(lud);
if (!urls[nurls]) {
_pam_log(LOG_ERR, "DNS SRV %s",
strerror(errno));
break;
}
nurls++;
}
*ludp = lud->lud_next;
lud->lud_next = NULL;
ldap_free_urldesc(lud);
}
if (ludlist) {
ldap_free_urldesc(ludlist);
return NULL;
} else if (!urls)
return NULL;
ldapuri = argcv_concat(nurls, urls);
if (!ldapuri)
_pam_log(LOG_ERR, "%s", strerror(errno));
ber_memvfree((void **)urls);
return ldapuri;
}
static void ldap_unbind(LDAP *ld);
static LDAP *
ldap_connect(struct gray_env *env)
{
int rc;
char *ldapuri = NULL;
LDAP *ld = NULL;
int protocol = LDAP_VERSION3;
char *val;
unsigned long lval;
enum { tls_no, tls_yes, tls_only } tls = tls_no;
if (ldap_debug_level) {
if (ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL,
&ldap_debug_level)
!= LBER_OPT_SUCCESS )
_pam_log(LOG_ERR,
"cannot set LBER_OPT_DEBUG_LEVEL %d",
ldap_debug_level);
if (ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL,
&ldap_debug_level)
!= LDAP_OPT_SUCCESS )
_pam_log(LOG_ERR,
"could not set LDAP_OPT_DEBUG_LEVEL %d",
ldap_debug_level);
}
val = gray_env_get(env, "uri");
if (val) {
ldapuri = parse_ldap_uri(val);
if (!ldapuri)
return NULL;
}
DEBUG(2, ("constructed LDAP URI: %s",
ldapuri ? ldapuri : "<DEFAULT>"));
rc = ldap_initialize(&ld, ldapuri);
if (rc != LDAP_SUCCESS) {
_pam_log(LOG_ERR,
"cannot create LDAP session handle for "
"URI=%s (%d): %s",
ldapuri, rc, ldap_err2string(rc));
free(ldapuri);
return NULL;
}
free(ldapuri);
if (get_intval(env, "ldap-version", 10, &lval) == 0) {
switch (lval) {
case 2:
protocol = LDAP_VERSION2;
break;
case 3:
protocol = LDAP_VERSION3;
break;
default:
_pam_log(LOG_ERR,
"%s: invalid variable value, "
"defaulting to 3",
"ldap-version");
}
}
ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol);
val = gray_env_get(env, "tls");
if (val) {
if (strcmp(val, "yes") == 0)
tls = tls_yes;
else if (strcmp(val, "no") == 0)
tls = tls_no;
else if (strcmp(val, "only") == 0)
tls = tls_only;
else {
_pam_log(LOG_ERR,
"wrong value for tls statement, "
"assuming \"no\"");
tls = tls_no;
}
} else {
val = gray_env_get(env, "ssl");
if (!val)
tls = tls_no;
else if (strcmp(val, "on") == 0)
tls = tls_only;
else if (strcmp(val, "start_tls") == 0)
tls = tls_only;
else
tls = tls_no;
/* FIXME: "tls-reqcert" */
}
if (tls != tls_no) {
rc = ldap_start_tls_s(ld, NULL, NULL);
if (rc != LDAP_SUCCESS) {
char *msg = NULL;
ldap_get_option(ld,
LDAP_OPT_DIAGNOSTIC_MESSAGE,
(void*)&msg);
_pam_log(LOG_ERR,
"ldap_start_tls failed: %s",
ldap_err2string(rc));
_pam_log(LOG_ERR,
"TLS diagnostics: %s", msg);
ldap_memfree(msg);
if (tls == tls_only) {
ldap_unbind(ld);
return NULL;
}
/* try to continue anyway */
} else {
val = gray_env_get(env, "tls-cacert");
if (val) {
rc = ldap_set_option(ld,
LDAP_OPT_X_TLS_CACERTFILE,
val);
if (rc != LDAP_SUCCESS) {
_pam_log(LOG_ERR,
"setting of LDAP_OPT_X_TLS_CACERTFILE failed");
if (tls == tls_only) {
ldap_unbind(ld);
return NULL;
}
}
}
}
}
/* FIXME: Timeouts, SASL, etc. */
return ld;
}
static int
full_read(int fd, char *file, char *buf, size_t size)
{
while (size) {
ssize_t n;
n = read(fd, buf, size);
if (n == -1) {
if (errno == EAGAIN || errno == EINTR)
continue;
_pam_log(LOG_ERR, "error reading from %s: %s",
file, strerror(errno));
return -1;
} else if (n == 0) {
_pam_log(LOG_ERR, "short read from %s", file);
return -1;
}
buf += n;
size -= n;
}
return 0;
}
static int
get_passwd(struct gray_env *env, struct berval *pwd, char **palloc)
{
char *file;
file = gray_env_get(env, "bindpwfile");
if (file) {
struct stat st;
int fd, rc;
char *mem, *p;
fd = open(file, O_RDONLY);
if (fd == -1) {
_pam_log(LOG_ERR, "can't open password file %s: %s",
file, strerror(errno));
return -1;
}
if (fstat(fd, &st)) {
_pam_log(LOG_ERR, "can't stat password file %s: %s",
file, strerror(errno));
close(fd);
return -1;
}
mem = malloc(st.st_size + 1);
if (!mem) {
_pam_log(LOG_ERR, "can't allocate memory (%lu bytes)",
(unsigned long) st.st_size+1);
close(fd);
return -1;
}
rc = full_read(fd, file, mem, st.st_size);
close(fd);
if (rc)
return rc;
mem[st.st_size] = 0;
p = strchr(mem, '\n');
if (p)
*p = 0;
*palloc = mem;
pwd->bv_val = mem;
} else
pwd->bv_val = gray_env_get(env, "bindpw");
pwd->bv_len = pwd->bv_val ? strlen(pwd->bv_val) : 0;
return 0;
}
static int
ldap_bind(LDAP *ld, struct gray_env *env)
{
int msgid, err, rc;
LDAPMessage *result;
LDAPControl **ctrls;
char msgbuf[256];
char *matched = NULL;
char *info = NULL;
char **refs = NULL;
struct berval passwd;
char *binddn;
char *alloc_ptr = NULL;
binddn = gray_env_get(env, "binddn");
if (get_passwd(env, &passwd, &alloc_ptr))
return 1;
msgbuf[0] = 0;
rc = ldap_sasl_bind(ld, binddn, LDAP_SASL_SIMPLE, &passwd,
NULL, NULL, &msgid);
if (msgid == -1) {
_pam_log(LOG_ERR,
"ldap_sasl_bind(SIMPLE) failed: %s",
ldap_err2string(rc));
free(alloc_ptr);
return 1;
}
if (ldap_result(ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1) {
_pam_log(LOG_ERR, "ldap_result failed");
free(alloc_ptr);
return 1;
}
rc = ldap_parse_result(ld, result, &err, &matched, &info, &refs,
&ctrls, 1);
if (rc != LDAP_SUCCESS) {
_pam_log(LOG_ERR, "ldap_parse_result failed: %s",
ldap_err2string(rc));
free(alloc_ptr);
return 1;
}
if (ctrls)
ldap_controls_free(ctrls);
if (err != LDAP_SUCCESS
|| msgbuf[0]
|| (matched && matched[0])
|| (info && info[0])
|| refs) {
DEBUG(2,("ldap_bind: %s (%d)%s",
ldap_err2string(err), err, msgbuf));
if (matched && *matched)
DEBUG(2,("matched DN: %s", matched));
if (info && *info)
DEBUG(2,("additional info: %s", info));
if (refs && *refs) {
int i;
DEBUG(3,("referrals:"));
for (i = 0; refs[i]; i++)
DEBUG(3,("%s", refs[i]));
}
}
if (matched)
ber_memfree(matched);
if (info)
ber_memfree(info);
if (refs)
ber_memvfree((void **)refs);
free(alloc_ptr);
return !(err == LDAP_SUCCESS);
}
static void
ldap_unbind(LDAP *ld)
{
if (ld) {
ldap_set_option(ld, LDAP_OPT_SERVER_CONTROLS, NULL);
ldap_unbind_ext(ld, NULL, NULL);
}
}
static void
trimnl(char *s)
{
size_t len = strlen(s);
while (len > 0 && s[len-1] == '\n')
--len;
s[len] = 0;
}
static int
keycmp(const void *a, const void *b)
{
return strcmp(*(char**)a, *(char**)b);
}
static char **
get_ldap_attrs(LDAP *ld, LDAPMessage *msg, const char *attr)
{
int rc, i, count;
BerElement *ber = NULL;
struct berval bv;
char *ufn = NULL;
char **ret;
struct berval **values;
rc = ldap_get_dn_ber(ld, msg, &ber, &bv);
if (rc != LDAP_SUCCESS) {
_pam_log(LOG_ERR, "ldap_get_dn_ber: %s", ldap_err2string(rc));
return NULL;
}
ufn = ldap_dn2ufn(bv.bv_val);
DEBUG(2, ("INFO: %s", ufn));
ldap_memfree(ufn);
values = ldap_get_values_len(ld, msg, attr);
if (!values) {
_pam_log(LOG_ERR,
"LDAP attribute `%s' has NULL value",
attr);
return NULL;
}
for (count = 0; values[count]; count++)
;
ret = calloc(count + 1, sizeof(ret[0]));
if (!ret)
_pam_log(LOG_ERR, "%s", strerror(errno));
else {
for (i = 0; values[i]; i++) {
char *p = malloc(values[i]->bv_len + 1);
if (!p) {
_pam_log(LOG_ERR, "%s", strerror(errno));
break;
}
memcpy(p, values[i]->bv_val, values[i]->bv_len);
p[values[i]->bv_len] = 0;
trimnl(p);
ret[i] = p;
DEBUG(10,("pubkey: %s", p));
}
if (i < count) {
argcv_free(i, ret);
ret = NULL;
} else {
ret[i] = NULL;
qsort(ret, i, sizeof(ret[0]), keycmp);
}
}
ldap_value_free_len(values);
return ret;
}
static char **
get_pubkeys(LDAP *ld, const char *base, const char *filter, const char *attr)
{
int rc;
LDAPMessage *res, *msg;
ber_int_t msgid;
char *attrs[2];
char **ret;
attrs[0] = (char*) attr;
attrs[1] = NULL;
rc = ldap_search_ext(ld, base, LDAP_SCOPE_SUBTREE,
filter, attrs, 0,
NULL, NULL, NULL, -1, &msgid);
if (rc != LDAP_SUCCESS) {
_pam_log(LOG_ERR, "ldap_search_ext: %s", ldap_err2string(rc));
return NULL;
}
rc = ldap_result(ld, msgid, LDAP_MSG_ALL, NULL, &res);
if (rc < 0) {
_pam_log(LOG_ERR, "ldap_result failed");
return NULL;
}
msg = ldap_first_entry(ld, res);
if (!msg) {
ldap_msgfree(res);
return NULL;
}
ret = get_ldap_attrs(ld, msg, attr);
ldap_msgfree(res);
return ret;
}
static int
check_groups(int gc, char **gv, const char *username, gid_t gid)
{
int i;
struct group *gp;
char *pgname;
int rc = 1;
gp = getgrgid(gid);
if (gp) {
pgname = strdup(gp->gr_name);
if (!pgname) {
errno_to_pam(errno);
return -1;
}
} else
gp = NULL;
for (i = 0; rc && i < gc; i++) {
if (strcmp(gv[i], pgname) == 0) {
rc = 0;
break;
}
gp = getgrnam(gv[i]);
if (gp) {
char **p;
for (p = gp->gr_mem; *p; p++) {
if (strcmp(username, *p) == 0) {
rc = 0;
break;
}
}
}
}
free(pgname);
return rc;
}
static int
check_user_groups(pam_handle_t *pamh, struct gray_env *env,
struct passwd **ppw, int *retval)
{
int rc;
const char *username;
struct passwd *pw;
unsigned long ival;
char *sval;
rc = pam_get_user(pamh, &username, NULL);
if (rc != PAM_SUCCESS || !username) {
DEBUG(1,("can not get the username"));
*retval = rc;
return 1;
}
pw = getpwnam(username);
if (!pw) {
*retval = PAM_USER_UNKNOWN;
return 1;
}
*ppw = pw;
if (get_intval(env, "min-uid", 10, &ival) == 0) {
if (pw->pw_uid < ival) {
DEBUG(10, ("ignoring user %s: has UID < %lu",
username, ival));
*retval = PAM_SUCCESS;
return 1;
}
}
if (get_intval(env, "min-gid", 10, &ival) == 0) {
if (pw->pw_gid < ival) {
DEBUG(10, ("ignoring user %s: has GID < %lu",
username, ival));
*retval = PAM_SUCCESS;
return 1;
}
}
sval = gray_env_get(env, "allow-groups");
if (sval) {
int gc;
char **gv;
int rc;
if (argcv_split(sval, &gc, &gv)) {
_pam_log(LOG_ERR, "cannot split allow-groups: %s",
strerror(errno));
*retval = PAM_AUTH_ERR;
return 1;
}
rc = check_groups(gc, gv, username, pw->pw_gid);
argcv_free(gc, gv);
if (rc) {
DEBUG(10, ("ignoring user %s: not in allowed group list",
username, ival));
*retval = PAM_SUCCESS;
return 1;
}
}
return 0;
}
static int
copy(int src_fd, int dst_fd, char *buffer, size_t bufsize)
{
ssize_t n;
while ((n = read(src_fd, buffer, bufsize)) > 0) {
n = write(dst_fd, buffer, n);
if (n < 0)
break;
}
return n;
}
static int
copy_file(pam_handle_t *pamh, const char *src, const char *dst,
char *buffer, size_t bufsize, struct stat *st)
{
int sfd, dfd, rc;
sfd = open(src, O_RDONLY);
if (sfd == -1) {
_pam_log(LOG_ERR, "cannot open %s: %s",
src, strerror(errno));
return 1;
}
dfd = open(dst, O_CREAT|O_TRUNC|O_RDWR, 0600);
if (dfd == -1) {
close(sfd);
_pam_log(LOG_ERR, "cannot create %s: %s",
dst, strerror(errno));
return 1;
}
if (fchown(dfd, st->st_uid, st->st_gid) ||
fchmod(dfd, st->st_mode & 07777)) {
_pam_log(LOG_ERR, "cannot set privileges of %s: %s",
dst, strerror(errno));
/* try to continue anyway */
}
rc = copy(sfd, dfd, buffer, bufsize);
if (rc)
_pam_log(LOG_ERR, "I/O error copying %s to %s: %s",
src, dst, strerror(errno));
close(sfd);
close(dfd);
return rc;
}
static int
read_link_name(const char *name, char **pbuf)
{
int rc = 0;
char *buf = NULL;
size_t size = 0;
ssize_t linklen = 0;
while (1) {
if (linklen == size) {
char *p = gray_2nrealloc(&buf, &size, 1);
if (!p) {
errno_to_pam(errno);
rc = 1;
break;
}
buf = p;
linklen = size;
}
linklen = readlink(name, buf, size);
if (linklen < 0 && errno != ERANGE) {
rc = 1;
break;
}
if (linklen < size) {
buf[linklen++] = '\0';
rc = 0;
break;
}
}
if (rc) {
if (buf) {
free(buf);
buf = NULL;
}
}
*pbuf = buf;
return rc;
}
static int
copy_link(pam_handle_t *pamh, const char *src, const char *dst,
char *buffer, size_t bufsize, struct stat *st)
{
char *lnkname;
int rc;
if (read_link_name(src, &lnkname)) {
_pam_log(LOG_ERR, "error reading link %s: %s",
src, strerror(errno));
return 1;
}
rc = symlink(lnkname, dst);
if (rc)
_pam_log(LOG_ERR, "can't link %s to %s: %s",
src, dst, strerror(errno));
else if (lchown(dst, st->st_uid, st->st_gid)) {
_pam_log(LOG_ERR, "cannot set privileges of %s: %s",
dst, strerror(errno));
/* try to continue anyway */
}
free(lnkname);
return rc;
}
/* Create the directory DIR, eventually creating all intermediate directories
starting from DIR + BASELEN. */
static int
create_hierarchy(char *dir, size_t baselen)
{
int rc;
struct stat st;
char *p;
if (stat(dir, &st) == 0) {
if (!S_ISDIR(st.st_mode)) {
_pam_log(LOG_ERR, "component %s is not a directory",
dir);
return 1;
}
return 0;
} else if (errno != ENOENT) {
_pam_log(LOG_ERR, "cannot stat file %s: %s",
dir, strerror(errno));
return 1;
}
p = strrchr(dir, '/');
if (p) {
if (p - dir + 1 < baselen) {
_pam_log(LOG_ERR, "base directory %s does not exist",
dir);
return 1;
}
*p = 0;
}
rc = create_hierarchy(dir, baselen);
if (rc == 0) {
if (p)
*p = '/';
if (mkdir(dir, 0755)) {
_pam_log(LOG_ERR, "cannot create directory %s: %s",
dir, strerror(errno));
rc = 1;
}
}
return rc;
}
static int
create_interdir(const char *path, struct passwd *pw)
{
char *dir, *p;
size_t len;
int rc;
p = strrchr(path, '/');
if (!p)
return 1;
len = p - path;
dir = malloc(len + 1);
if (!dir) {
errno_to_pam(errno);
return -1;
}
memcpy(dir, path, len);
dir[len] = 0;
rc = create_hierarchy(dir, strlen(pw->pw_dir));
if (rc == 0)
rc = chown(dir, pw->pw_uid, pw->pw_gid);
free(dir);
return rc;
}
struct namebuf {
char *name;
size_t size;
size_t prefix_len;
};
static void
namebuf_trimslash(struct namebuf *buf)
{
size_t len = strlen(buf->name);
while (len > 0 && buf->name[len-1] == '/')
--len;
buf->name[len] = 0;
}
static int
namebuf_init(struct namebuf *buf, const char *name)
{
buf->name = strdup(name);
if (!buf->name)
return 1;
buf->prefix_len = strlen(name);
buf->size = buf->prefix_len + 1;
namebuf_trimslash(buf);
return 0;
}
static int
namebuf_set(struct namebuf *buf, const char *name)
{
size_t len;
if (!buf->name)
return namebuf_init(buf, name);
len = strlen(name);
if (buf->prefix_len + len + 1 > buf->size) {
size_t ns;
char *np;
for (ns = buf->size; buf->prefix_len + len + 1 > ns;
ns += ns)
;
np = realloc(buf->name, ns);
if (!np)
return 1;
buf->size = ns;
buf->name = np;
}
strcpy(buf->name + buf->prefix_len, name);
//namebuf_trimslash(buf);
return 0;
}
static size_t
namebuf_set_prefix(struct namebuf *buf)
{
size_t ret;
ret = buf->prefix_len;
buf->prefix_len = strlen(buf->name);
if (namebuf_set(buf, "/"))
return 1;
++buf->prefix_len;
return ret;
}
static void
namebuf_set_prefix_len(struct namebuf *buf, size_t len)
{
buf->prefix_len = len;
buf->name[len] = 0;
}
static int recursive_copy(pam_handle_t *pamh, DIR *dir,
struct namebuf *srcbuf, struct namebuf *dstbuf,
char *buffer, size_t bufsize, struct passwd *pw,
struct stat *st);
static int
dir_copy_loop(pam_handle_t *pamh, DIR *dir,
struct namebuf *srcbuf, struct namebuf *dstbuf,
char *buffer, size_t bufsize, struct passwd *pw)
{
struct dirent *ent;
while ((ent = readdir(dir))) {
char const *ename = ent->d_name;
struct stat st;
int rc;
if (ename[ename[0] != '.' ? 0 : ename[1] != '.' ? 1 : 2] == 0)
continue;
if (namebuf_set(srcbuf, ename)) {
_pam_log(LOG_ERR, "copy error: %s", strerror(errno));
return 1;
}
if (namebuf_set(dstbuf, ename)) {
_pam_log(LOG_ERR, "copy error: %s", strerror(errno));
return 1;
}
if (lstat(srcbuf->name, &st)) {
_pam_log(LOG_ERR, "cannot stat %s: %s",
srcbuf->name, strerror(errno));
return 1;
}
st.st_uid = pw->pw_uid;
st.st_gid = pw->pw_gid;
if (S_ISREG(st.st_mode))
rc = copy_file(pamh, srcbuf->name, dstbuf->name,
buffer, bufsize, &st);
else if (S_ISDIR(st.st_mode)) {
DIR *nd = opendir(srcbuf->name);
if (!nd) {
_pam_log(LOG_ERR,
"cannot open directory %s: %s",
srcbuf->name, strerror(errno));
rc = 1;
} else {
size_t srclen = namebuf_set_prefix(srcbuf);
size_t dstlen = namebuf_set_prefix(dstbuf);
rc = recursive_copy(pamh, nd, srcbuf, dstbuf,
buffer, bufsize, pw,
&st);
closedir(nd);
namebuf_set_prefix_len(dstbuf, dstlen);
namebuf_set_prefix_len(srcbuf, srclen);
}
} else if (S_ISLNK(st.st_mode))
rc = copy_link(pamh, srcbuf->name, dstbuf->name,
buffer, bufsize, &st);
else {
_pam_log(LOG_NOTICE,
"ignoring file %s: unsupported file type",
srcbuf->name);
rc = 0;
}
if (rc)
return 1;
}
return 0;
}
static int
recursive_copy(pam_handle_t *pamh, DIR *dir,
struct namebuf *srcbuf, struct namebuf *dstbuf,
char *buffer, size_t bufsize, struct passwd *pw,
struct stat *st)
{
int rc;
struct stat dst_st;
if (stat(dstbuf->name, &dst_st)) {
if (errno == ENOENT) {
if (mkdir(dstbuf->name, 0700)) {
_pam_log(LOG_ERR, "cannot create %s: %s",
dstbuf->name, strerror(errno));
return 1;
}
} else {
_pam_log(LOG_ERR, "cannot stat %s: %s",
dstbuf->name, strerror(errno));
return 1;
}
}
rc = dir_copy_loop(pamh, dir, srcbuf, dstbuf, buffer, bufsize, pw);
dstbuf->name[dstbuf->prefix_len-1] = 0;
if (chown(dstbuf->name, pw->pw_uid, pw->pw_gid) ||
(st && chmod(dstbuf->name, st->st_mode & 07777))) {
_pam_log(LOG_ERR,
"cannot set privileges for %s:"
"%s",
dstbuf->name,
strerror(errno));
}
return rc;
}
#define MIN_BUF_SIZE 2
#define MAX_BUF_SIZE 16384
static int
populate_homedir(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env)
{
const char *skel;
char *buffer;
size_t bufsize;
struct stat st;
unsigned long n;
DIR *dir;
int rc;
skel = gray_env_get(env, "skel");
if (!skel)
return 0;
if (stat(skel, &st)) {
_pam_log(LOG_ERR, "cannot stat skeleton directory %s: %s",
pw->pw_dir, strerror(errno));
return 1;
} else if (!S_ISDIR(st.st_mode)) {
_pam_log(LOG_ERR, "%s exists, but is not a directory",
pw->pw_dir);
return 1;
}
if (get_intval(env, "copy-buf-size", 10, &n) == 0)
bufsize = n;
else
bufsize = MAX_BUF_SIZE;
for (; (buffer = malloc(bufsize)) == NULL; bufsize >>= 1)
if (bufsize < MIN_BUF_SIZE)
return ENOMEM;
dir = opendir(skel);
if (!dir) {
_pam_log(LOG_ERR, "cannot open skeleton directory %s: %s",
skel, strerror(errno));
rc = 1;
} else {
struct namebuf srcbuf, dstbuf;
if (namebuf_init(&srcbuf, skel) == 0) {
namebuf_set_prefix(&srcbuf);
if (namebuf_init(&dstbuf, pw->pw_dir) == 0) {
namebuf_set_prefix(&dstbuf);
rc = recursive_copy(pamh, dir,
&srcbuf, &dstbuf,
buffer, bufsize, pw,
NULL);
free(dstbuf.name);
} else
rc = 1;
free(srcbuf.name);
} else
rc = 1;
closedir(dir);
}
free(buffer);
return rc;
}
/* Operations on public key files */
struct pubkeyfile {
char *file_name; /* Name of the file */
int fd; /* File descriptor */
char *base; /* File contents */
size_t size; /* Size of base */
char **lnv; /* File contents parsed into nul-terminated lines */
size_t lnc; /* Number of lines in lnv */
size_t lnm; /* Max. capacity of lnv */
};
/* Close the public key file */
static void
pubkeyfile_close(struct pubkeyfile *pkb)
{
close(pkb->fd);
free(pkb->file_name);
free(pkb->base);
free(pkb->lnv);
pkb->fd = -1;
pkb->file_name = NULL;
pkb->base = NULL;
pkb->lnv = NULL;
}
/* Open public key file NAME. Return 0 on success. On error, issue a
diagnostic message and return -1. */
static int
pubkeyfile_open(struct pubkeyfile *pkb, char *name)
{
memset(pkb, 0, sizeof *pkb);
pkb->fd = open(name, O_CREAT|O_RDWR, 0666);
if (pkb->fd == -1) {
_pam_log(LOG_ERR, "can't open %s: %s",
name, strerror(errno));
return -1;
}
pkb->file_name = strdup(name);
if (!pkb->file_name) {
int rc = errno_to_pam(errno);
close(pkb->fd);
pkb->fd = -1;
return rc;
}
return 0;
}
/* Read in the contents of the open public key file PKB. */
static int
pubkeyfile_read(struct pubkeyfile *pkb)
{
struct stat st;
char *p;
size_t i;
if (fstat(pkb->fd, &st)) {
_pam_log(LOG_ERR, "fstat %s: %s",
pkb->file_name, strerror(errno));
return -1;
}
pkb->size = st.st_size;
pkb->base = malloc(st.st_size + 1);
if (!pkb->base) {
errno_to_pam(errno);
return -1;
}
if (full_read(pkb->fd, pkb->file_name, pkb->base, pkb->size)) {
_pam_log(LOG_ERR, "fread %s: %s",
pkb->file_name, strerror(errno));
return -1;
}
pkb->base[pkb->size] = 0;
pkb->lnc = 0;
for (p = pkb->base; *p; p++)
if (*p == '\n')
++pkb->lnc;
pkb->lnm = pkb->lnc + 1;
pkb->lnv = calloc(pkb->lnm, sizeof(pkb->lnv[0]));
if (!pkb->lnv) {
errno_to_pam(errno);
return -1;
}
i = 0;
for (p = pkb->base; *p; p++) {
if (p == pkb->base || p[-1] == 0)
pkb->lnv[i++] = p;
if (*p == '\n')
*p = 0;
}
pkb->lnv[i] = NULL;
return 0;
}
/* Open the public key file NAME and read its contents. */
static int
pubkeyfile_init(struct pubkeyfile *pkb, char *name)
{
if (pubkeyfile_open(pkb, name))
return -1;
if (pubkeyfile_read(pkb) == 0)
return 0;
pubkeyfile_close(pkb);
return -1;
}
/* Write data from lnv into the public key file, overwriting its current
content. */
static int
pubkeyfile_write(struct pubkeyfile *pkb)
{
int i;
if (lseek(pkb->fd, 0, SEEK_SET)) {
_pam_log(LOG_ERR, "lseek %s: %s",
pkb->file_name, strerror(errno));
return -1;
}
if (ftruncate(pkb->fd, 0)) {
_pam_log(LOG_ERR, "ftruncate %s: %s",
pkb->file_name, strerror(errno));
return -1;
}
for (i = 0; i < pkb->lnc; i++) {
if (pkb->lnv[i]) {
static char newline = '\n';
size_t len = strlen(pkb->lnv[i]);
if (write(pkb->fd, pkb->lnv[i], len) != len
|| write(pkb->fd, &newline, 1) != 1) {
_pam_log(LOG_ERR, "error writing %s: %s",
pkb->file_name, strerror(errno));
return -1;
}
}
}
return 0;
}
/* Remove COUNT lines starting from position POS in PKB. */
static void
pubkeyfile_remove_lines(struct pubkeyfile *pkb, int pos, int count)
{
if (count == 0)
return;
if (pos > pkb->lnc) {
_pam_log(LOG_ERR, "%s:%d: INTERNAL ERROR: pos out of range",
__FILE__, __LINE__);
abort();
}
if (pos + count > pkb->lnc) {
_pam_log(LOG_ERR, "%s:%d: INTERNAL ERROR: count out of range",
__FILE__, __LINE__);
abort();
}
memmove(pkb->lnv + pos, pkb->lnv + pos + count,
(pkb->lnc - pos - count + 1) * sizeof(pkb->lnv[0]));
pkb->lnc -= count;
}
/* Allocate COUNT lines starting from position POS in PKB, preserving
the existing data. */
static int
pubkeyfile_alloc_lines(struct pubkeyfile *pkb, size_t pos, size_t count)
{
if (pos > pkb->lnc) {
_pam_log(LOG_ERR, "%s:%d: INTERNAL ERROR: pos out of range",
__FILE__, __LINE__);
return PAM_SERVICE_ERR;
}
if (pkb->lnc + count + 1 > pkb->lnm) {
char **p;
size_t lnm = pkb->lnm + count;
p = realloc(pkb->lnv, lnm * sizeof(pkb->lnv[0]));
if (!p)
return errno_to_pam(errno);
pkb->lnv = p;
pkb->lnm = lnm;
}
memmove(pkb->lnv + pos + count, pkb->lnv + pos,
(pkb->lnc - pos + 1) * sizeof(pkb->lnv[0]));
pkb->lnc += count;
return PAM_SUCCESS;
}
/* Insert lines from LV in position POS in the file PKB, shifting down
existing lines as necessary. */
static int
pubkeyfile_insert_lines(struct pubkeyfile *pkb, size_t pos, char **lv)
{
size_t i;
size_t lc;
int rc;
for (lc = 0; lv[lc]; lc++)
;
rc = pubkeyfile_alloc_lines(pkb, pos, lc);
if (rc == 0) {
for (i = 0; i < lc; i++)
pkb->lnv[pos + i] = lv[i];
}
return rc;
}
static int
store_pubkeys(char **keys, struct passwd *pw, struct gray_env *env)
{
int rc;
char *file_name;
size_t homelen, pathlen, len;
int retval, i, j;
int update = 0;
int oldmask;
unsigned long mode;
struct pubkeyfile pkf;
homelen = strlen(pw->pw_dir);
pathlen = strlen(authorized_keys_file);
len = homelen + pathlen;
if (pw->pw_dir[homelen - 1] != '/')
len++;
file_name = malloc(len + 1);
if (!file_name)
return errno_to_pam(errno);
memcpy(file_name, pw->pw_dir, homelen);
if (pw->pw_dir[homelen - 1] != '/')
file_name[homelen++] = '/';
strcpy(file_name + homelen, authorized_keys_file);
switch (get_intval(env, "keyfile-mode", 8, &mode)) {
case -1:
return PAM_SERVICE_ERR;
case 1:
oldmask = -1;
break;
case 0:
oldmask = umask(0666 ^ (mode & 0777));
}
if (access(file_name, R_OK)
&& create_interdir(file_name, pw) == 0) {
update = 1;
i = 0;
}
rc = pubkeyfile_init(&pkf, file_name);
if (oldmask != -1)
umask(oldmask);
if (rc) {
free(file_name);
return PAM_SERVICE_ERR;
}
if (fchown(pkf.fd, pw->pw_uid, pw->pw_gid))
_pam_log(LOG_ERR, "chown %s: %s",
file_name, strerror(errno));
if (!update) {
char *boundary = gray_env_get(env, "user-keys-boundary");
j = 0;
for (i = 0; i < pkf.lnc; i++) {
char *kp;
char *p = pkf.lnv[i];
if (*p == '#') {
if (boundary && strcmp(p + 1, boundary) == 0)
break;
continue;
}
if (update)
continue;
if (*p == 0)
continue;
kp = keys[j++];
if (!kp) {
DEBUG(2, ("less keys in the database"));
update = 1;
} else if (strcmp(p, kp)) {
DEBUG(2, ("key %d mismatch", j));
update = 1;
}
}
if (!update && keys[j]) {
DEBUG(2, ("more keys in the database"));
update = 1;
}
}
if (update) {
pubkeyfile_remove_lines(&pkf, 0, i);
retval = pubkeyfile_insert_lines(&pkf, 0, keys);
if (retval == PAM_SUCCESS) {
pubkeyfile_write(&pkf);
retval = PAM_TRY_AGAIN;
}
} else
retval = PAM_SUCCESS;
pubkeyfile_close(&pkf);
free(file_name);
return retval;
}
static int
import_public_key(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env)
{
LDAP *ld;
int retval;
const char *base = gray_env_get(env, "base");
const char *filter_pat = gray_env_get(env, "filter");
const char *attr = gray_env_get(env, "pubkey-attr");
if (!gray_env_get_bool(env, "import-public-keys", 1))
return PAM_SUCCESS;
if (!filter_pat) {
_pam_log(LOG_ERR, "configuration variable `filter' not set");
return PAM_SERVICE_ERR;
}
if (!attr)
attr = "sshPublicKey";
ld = ldap_connect(env);
if (!ld)
return PAM_SERVICE_ERR;
if (ldap_bind(ld, env))
retval = PAM_SERVICE_ERR;
else {
char *filter;
retval = gray_expand_string(pamh, filter_pat, &filter);
if (retval == PAM_SUCCESS) {
char **keys = get_pubkeys(ld, base, filter, attr);
if (keys) {
retval = store_pubkeys(keys, pw, env);
argcvz_free(keys);
} else
retval = PAM_SUCCESS;
}
}
ldap_unbind(ld);
return retval;
}
static int
dir_in_path(const char *dir, const char *path)
{
char *p;
size_t dirlen;
p = strrchr(dir, '/');
if (p)
dirlen = p - dir;
else
return 0;
while (*path) {
size_t len = strcspn(path, ":");
while (len > 0 && path[len-1] == '/')
--len;
if (len == dirlen && memcmp(path, dir, len) == 0)
return 1;
path += len;
if (*path == ':')
++path;
}
return 0;
}
enum create_status {
create_ok,
create_exists,
create_failure,
create_skip
};
static enum create_status
create_home_dir(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env)
{
struct stat st;
if (stat(pw->pw_dir, &st)) {
unsigned long mode = 0755;
char *val;
if (errno != ENOENT) {
_pam_log(LOG_ERR, "cannot stat home directory %s: %s",
pw->pw_dir, strerror(errno));
return create_failure;
}
val = gray_env_get(env, "allow-home-dir");
if (val && !dir_in_path(pw->pw_dir, val))
return create_skip;
if (get_intval(env, "home-dir-mode", 8, &mode) == -1)
return create_failure;
mode &= 07777;
if (mkdir(pw->pw_dir, 0700)) {
_pam_log(LOG_ERR, "cannot create %s: %s",
pw->pw_dir, strerror(errno));
return create_failure;
}
populate_homedir(pamh, pw, env);
if (chown(pw->pw_dir, pw->pw_uid, pw->pw_gid) ||
chmod(pw->pw_dir, mode)) {
_pam_log(LOG_ERR,
"cannot change mode or ownership of %s: %s",
pw->pw_dir, strerror(errno));
return create_failure;
}
} else if (!S_ISDIR(st.st_mode)) {
_pam_log(LOG_ERR, "%s exists, but is not a directory",
pw->pw_dir);
return create_failure;
} else
return create_exists;
return create_ok;
}
extern char **environ;
static char *
find_env(char *name, int val)
{
int nlen = strcspn(name, "?+=");
int i;
for (i = 0; environ[i]; i++) {
size_t elen = strcspn(environ[i], "=");
if (elen == nlen && memcmp(name, environ[i], nlen) == 0)
return val ? environ[i] + elen + 1 : environ[i];
}
return NULL;
}
static int
locate_unset(char **env, const char *name)
{
volatile int i;
int nlen = strcspn(name, "=");
for (i = 0; env[i]; i++) {
if (env[i][0] == '-') {
size_t elen = strcspn(env[i] + 1, "=");
if (elen == nlen
&& memcmp(name, env[i] + 1, nlen) == 0) {
if (env[i][nlen + 1])
return strcmp(name + nlen,
env[i] + 1 + nlen) == 0;
else
return 1;
}
}
}
return 0;
}
/* Concatenate NAMELEN bytes from NAME, "=", A, and B. Allocate the
result using malloc.
On ENOMEM, log a diagnostic message and exit with code 127. This function
should be used from child process only.
*/
static char *
env_concat(char *name, size_t namelen, char *a, char *b)
{
char *res;
size_t len;
if (a && b) {
res = malloc(namelen + 1 + strlen(a) + strlen(b) + 1);
if (res) {
strcpy(res + namelen + 1, a);
strcat(res, b);
}
} else if (a) {
len = strlen(a);
if (ispunct(a[len-1]))
len--;
res = malloc(namelen + 1 + len + 1);
if (res) {
memcpy(res + namelen + 1, a, len);
res[namelen + 1 + len] = 0;
}
} else /* if (a == NULL) */ {
if (ispunct(b[0]))
b++;
len = strlen(b);
res = malloc(namelen + 1 + len + 1);
if (res)
strcpy(res + namelen + 1, b);
}
if (res) {
memcpy(res, name, namelen);
res[namelen] = '=';
} else {
errno_to_pam(errno);
_exit(127);
}
return res;
}
static char **
parsenv(char *str)
{
enum {
st_init,
st_kwd,
st_val,
st_eq,
st_dquote,
st_squote,
st_end
} state = st_init, prev_state;
# define setstate(s) do { prev_state = state; state = s; } while (0)
char *p, *kw;
char **wv = NULL;
size_t wi = 0, wc = 0;
# define ASSERT_NOTNULL(p) do { \
if (!(p)) { \
errno_to_pam(errno); \
_exit(127); \
} \
} while (0)
if (!str)
return NULL;
for (p = str; *p; ++p) {
switch (state) {
case st_init:
if (*p == ' ' || *p == '\t')
continue;
setstate(st_kwd);
kw = p;
break;
case st_kwd:
if (*p == ' ' || *p == '\t') {
setstate(st_end);
} else if (*p == '=') {
setstate(st_eq);
}
break;
case st_eq:
if (*p == '"') {
setstate(st_dquote);
} else if (*p == '\'') {
setstate(st_squote);
} else {
setstate(st_val);
}
/* fall through */
case st_val:
if (*p == ' ' || *p == '\t')
setstate(st_end);
break;
case st_dquote:
if (*p == '\\')
++p;
else if (*p == '"')
setstate(st_end);
break;
case st_squote:
if (*p == '\'')
setstate(st_end);
break;
case st_end:
/* can't happen */
break;
}
if (state == st_end) {
size_t len = p - kw;
char *q;
if (wi == wc) {
wv = gray_2nrealloc(wv, &wc, sizeof(wv[0]));
ASSERT_NOTNULL(wv);
}
switch (prev_state) {
case st_squote:
len -= 2;
wv[wi] = malloc(len + 1);
ASSERT_NOTNULL(wv[wi]);
for (q = wv[wi]; *kw; ) {
if (*kw == '\'')
++kw;
else
*q++ = *kw++;
}
*q = 0;
break;
case st_dquote:
len -= 2;
wv[wi] = malloc(len + 1);
ASSERT_NOTNULL(wv[wi]);
q = wv[wi];
while ((*q++ = *kw++) != '=')
;
while (*kw != '"')
*q++ = *kw++;
++kw;
while (*kw != '"') {
if (*kw == '\\')
++kw;
*q++ = *kw++;
}
*q = 0;
break;
default:
wv[wi] = malloc(len + 1);
ASSERT_NOTNULL(wv[wi]);
memcpy(wv[wi], kw, len);
wv[wi][len] = 0;
}
++wi;
setstate(st_init);
}
}
if (state != st_init) {
if (wc == wi) {
wv = gray_2nrealloc(wv, &wc, sizeof(wv[0]));
ASSERT_NOTNULL(wv);
}
wv[wi] = strdup(kw);
ASSERT_NOTNULL(wv[wi]);
++wi;
}
if (wc == wi) {
wv = gray_2nrealloc(wv, &wc, sizeof(wv[0]));
ASSERT_NOTNULL(wv);
}
wv[wi] = NULL;
return wv;
}
/* Setup environment for exec* family call. On ENOMEM, exit with code 127.
This function should be called from a child process.
*/
static char **
env_setup(char *envstr)
{
char **env;
char **old_env = environ;
char **new_env;
int count, i, n;
env = parsenv(envstr);
if (!env)
return old_env;
if (strcmp(env[0], "-") == 0) {
old_env = NULL;
env++;
}
/* Count new environment size */
count = 0;
if (old_env)
for (i = 0; old_env[i]; i++)
count++;
for (i = 0; env[i]; i++)
count++;
/* Allocate the new environment. */
new_env = calloc(count + 1, sizeof new_env[0]);
if (!new_env) {
errno_to_pam(errno);
_exit(127);
}
/* Populate the environment. */
n = 0;
if (old_env)
for (i = 0; old_env[i]; i++) {
if (!locate_unset(env, old_env[i]))
new_env[n++] = old_env[i];
}
for (i = 0; env[i]; i++) {
char *p;
if (env[i][0] == '-') {
/* Skip unset directives. */
continue;
} if ((p = strchr(env[i], '='))) {
if (p == env[i])
continue; /* Ignore erroneous entry */
if (p[-1] == '+')
new_env[n++] = env_concat(env[i],
p - env[i] - 1,
find_env(env[i], 1),
p + 1);
else if (p[1] == '+')
new_env[n++] = env_concat(env[i],
p - env[i],
p + 2,
find_env(env[i], 1));
else if (p[-1] == '?') {
if (!find_env(env[i], 0))
new_env[n++] = p + 1;
} else
new_env[n++] = env[i];
} else {
p = find_env(env[i], 0);
if (p)
new_env[n++] = p;
}
}
new_env[n] = NULL;
return new_env;
}
static int
runas(struct passwd *pw)
{
gid_t *sgv = NULL;
size_t sgc = 0, sgm = 0;
struct group *gr;
setgrent();
while ((gr = getgrent ())) {
char **p;
if (gr->gr_gid == pw->pw_gid)
continue;
for (p = gr->gr_mem; *p; p++) {
if (strcmp (*p, pw->pw_name) == 0) {
if (sgc == sgm) {
gid_t *p;
p = gray_2nrealloc(sgv, &sgm,
sizeof(sgv[0]));
if (!p) {
errno_to_pam(errno);
return 1; //FIXME: proper error code
}
sgv = p;
}
sgv[sgc++] = gr->gr_gid;
}
}
}
endgrent();
if (sgc) {
if (setgroups(sgc, sgv)) {
_pam_log(LOG_ERR, "setgroups: %s", strerror(errno));
free(sgv);
return 1;
}
free(sgv);
}
if (setgid(pw->pw_gid)) {
_pam_log(LOG_ERR, "setgid(%lu): %s",
(unsigned long) pw->pw_gid, strerror(errno));
return 1;
}
if (setuid(pw->pw_uid)) {
_pam_log(LOG_ERR, "setuid(%lu): %s",
(unsigned long) pw->pw_uid, strerror(errno));
return 1;
}
return 0;
}
static int
run_prog(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env,
const char *command, const char *logfile)
{
pid_t pid, rc;
int p[2];
long ttl;
time_t start;
int i, status;
struct timeval tv;
unsigned long timeout_option = 10;
DEBUG(2,("running command %s", command));
get_intval(env, "exec-timeout", 10, &timeout_option);
if (pipe(p)) {
_pam_log(LOG_ERR, "pipe: %s", strerror(errno));
return PAM_SYSTEM_ERR;
}
pid = fork();
if (pid == -1) {
close(p[0]);
close(p[1]);
_pam_log(LOG_ERR, "fork: %s", strerror(errno));
return PAM_SYSTEM_ERR;
}
if (pid == 0) {
/* child */
char *argv[3];
if (dup2(p[1], 1) == -1) {
_pam_log(LOG_ERR, "dup2: %s", strerror(errno));
_exit(127);
}
for (i = sysconf(_SC_OPEN_MAX); i >= 0; i--) {
if (i != 1)
close(i);
}
open("/dev/null", O_RDONLY);
if (logfile) {
if (open(logfile, O_CREAT|O_APPEND|O_WRONLY,
0644) == -1) {
_pam_log(LOG_ERR, "open(%s): %s",
logfile, strerror(errno));
_exit(127);
}
} else
dup2(1, 2);
if (chdir(pw->pw_dir)) {
_pam_log(LOG_ERR, "chdir: %s", strerror(errno));
_exit(127);
}
if (gray_env_get_bool(env, "initrc-root", 0))
setenv("PAM_LDAPHOME_USER", pw->pw_name, 1);
else if (runas(pw)) {
_pam_log(LOG_ERR, "cannot switch to privileges of %s",
pw->pw_name);
_exit(127);
}
argv[0] = (char*) command;
argv[1] = pw->pw_name;
argv[2] = NULL;
execve(command, argv,
env_setup(gray_env_get(env, "initrc-environ")));
_exit(127);
}
/* master */
close(p[1]);
start = time(NULL);
while (1) {
ttl = timeout_option - (time(NULL) - start);
if (ttl <= 0) {
_pam_log(LOG_ERR, "timed out waiting for %s", command);
break;
}
tv.tv_sec = ttl;
tv.tv_usec = 0;
rc = select(0, NULL, NULL, NULL, &tv);
if (rc == -1 && errno == EINTR) {
rc = waitpid(pid, &status, WNOHANG);
if (rc == pid)
break;
if (rc == (pid_t)-1) {
_pam_log(LOG_ERR, "waitpid: %s",
strerror(errno));
break;
}
}
}
close(p[0]);
if (rc != pid) {
_pam_log(LOG_NOTICE, "killing %s (pid %lu)",
command, (unsigned long) pid);
kill(pid, SIGKILL);
while ((rc = waitpid(pid, &status, 0)) == -1 &&
errno == EINTR);
if (rc == (pid_t)-1) {
_pam_log(LOG_ERR, "waitpid: %s", strerror(errno));
return PAM_SYSTEM_ERR;
}
} else if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
if (status) {
_pam_log(LOG_ERR, "%s exited with status %d",
command, status);
return PAM_SYSTEM_ERR;
} else
DEBUG(2,("%s finished successfully", command));
} else if (WIFSIGNALED(status)) {
status = WTERMSIG(status);
_pam_log(LOG_ERR, "%s got signal %d", command, status);
return PAM_SYSTEM_ERR;
} else if (status) {
_pam_log(LOG_ERR, "%s failed: unknown status 0x%x",
command, status);
return PAM_SYSTEM_ERR;
}
return PAM_SUCCESS;
}
static void
sigchld(int sig)
{
/* nothing */;
}
static int
run_initrc(pam_handle_t *pamh, struct passwd *pw, struct gray_env *env)
{
int rc;
struct sigaction sa, save_sa;
const char *command = gray_env_get(env, "initrc-command");
const char *logfile = gray_env_get(env, "initrc-log");
if (!command)
return PAM_SUCCESS;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = sigchld;
if (sigaction(SIGCHLD, &sa, &save_sa)) {
_pam_log(LOG_ERR, "sigaction: %m");
return PAM_SYSTEM_ERR;
}
rc = run_prog(pamh, pw, env, command, logfile);
if (sigaction(SIGCHLD, &save_sa, NULL)) {
_pam_log(LOG_ERR, "sigaction failed to restore SIGCHLD: %m");
return PAM_SYSTEM_ERR;
}
return rc;
}
static int
ldaphome_main(pam_handle_t *pamh, int flags, int argc, const char **argv,
const char *func)
{
int retval = PAM_AUTH_ERR;
struct gray_env *env;
_pam_parse(pamh, argc, argv);
DEBUG(90,("enter %s", func));
if (gray_env_read(config_file_name, &env) == 0) {
char *val;
struct passwd *pw;
if ((val = gray_env_get(env, "ldap-config"))) {
if (strcmp(val, "none") == 0)
ldap_config_name = NULL;
else
ldap_config_name = val;
}
if (ldap_config_name) {
static char *map[] = { "A-Z_", "a-z-" };
struct gray_env *tmp;
gray_env_read_tr(ldap_config_name, &tmp, map);
gray_env_merge(&env, &tmp);
}
if ((val = gray_env_get(env, "authorized_keys")))
authorized_keys_file = val;
if (check_user_groups(pamh, env, &pw, &retval) == 0) {
switch (create_home_dir(pamh, pw, env)) {
case create_ok:
retval = run_initrc(pamh, pw, env);
if (retval)
break;
/* fall through */
case create_exists:
retval = import_public_key(pamh, pw, env);
break;
case create_failure:
retval = PAM_SERVICE_ERR;
break;
case create_skip:
retval = PAM_SUCCESS;
}
}
gray_env_free(env);
}
DEBUG(90,("exit %s: %d", func, retval));
return retval;
}
PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
return ldaphome_main(pamh, flags, argc, argv, __FUNCTION__);
}
PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
return PAM_SUCCESS;
}
PAM_EXTERN int
pam_sm_open_session (pam_handle_t *pamh, int flags, int argc,
const char **argv)
{
return ldaphome_main(pamh, flags, argc, argv, __FUNCTION__);
}
PAM_EXTERN int
pam_sm_close_session (pam_handle_t *pamh, int flags, int argc,
const char **argv)
{
return PAM_SUCCESS;
}
#ifdef PAM_STATIC
struct pam_module _pam_ldaphome_modstruct = {
"pam_ldaphome", /* name of the module */
pam_sm_authenticate,
pam_sm_setcred,
NULL,
pam_sm_open_session,
pam_sm_close_session,
NULL
};
#endif