/*
* Linux-PAM session chroot()er
* account, session, authentication
*
* $Id: pam_chroot.c,v 0.6 2001/09/15 18:07:21 schmolli Exp schmolli $
*/
#include <syslog.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <regex.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <stdarg.h>
#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_SESSION
#include <security/pam_modules.h>
#include <security/_pam_macros.h>
#define CONFIG "/etc/security/chroot.conf"
#define LINELEN 1024 /* max length (bytes) of line in config file */
/* defines for flags */
#define _PAM_OPTS_NOOPTS 0x0000
#define _PAM_OPTS_DEBUG 0x0001
#define _PAM_OPTS_SILENT 0x0002
#define _PAM_OPTS_NOTFOUNDFAILS 0x0004
#define _PAM_OPTS_NO_CHROOT 0x0008
#define _PAM_OPTS_USE_REGEX 0x0010
/* defines for (internal) return values */
#define _PAM_CHROOT_INTERNALERR -2
#define _PAM_CHROOT_SYSERR -1
#define _PAM_CHROOT_OK 0
#define _PAM_CHROOT_USERNOTFOUND 1
struct _pam_opts {
int16_t flags; /* combined option flags */
char* chroot_dir; /* where to chroot to */
char* conf; /* name of pam_chroot config file */
char* module; /* module currently being processed */
};
static void _pam_log(int err, const char *format, ...) {
va_list args;
va_start(args, format);
openlog("pam_chroot", LOG_PID, LOG_AUTHPRIV);
vsyslog(err, format, args);
va_end(args);
closelog();
}
/* initialize opts to a standard known state */
int _pam_opts_init(struct _pam_opts* opts) {
if(NULL == opts) {
_pam_log(LOG_ERR, "%s: NULL opts pointer", __FUNCTION__);
return _PAM_CHROOT_INTERNALERR;
}
opts->flags = _PAM_OPTS_NOOPTS;
opts->chroot_dir = NULL;
opts->conf = x_strdup(CONFIG);
if(NULL == opts->conf) {
_pam_log(LOG_ERR, "strdup: %s", strerror(errno));
return _PAM_CHROOT_SYSERR;
}
return _PAM_CHROOT_OK;
}
/* configure opts per the passed flags and cmd line args */
int _pam_opts_config(struct _pam_opts* opts, int flags,
int argc, const char** argv)
{
int i;
if(NULL == opts) {
_pam_log(LOG_ERR, "%s: NULL opts pointer", __FUNCTION__);
return _PAM_CHROOT_INTERNALERR;
}
if(flags & PAM_SILENT) { opts->flags = opts->flags | _PAM_OPTS_SILENT; }
if((flags & PAM_DISALLOW_NULL_AUTHTOK) &&
(!strcmp(opts->module, "auth") || !strcmp(opts->module, "account")))
{
opts->flags = opts->flags | _PAM_OPTS_NOTFOUNDFAILS;
}
/* parse command line args */
for(i = 0; i < argc; i++) {
if(!strcmp(argv[i], "debug")) {
opts->flags = opts->flags | _PAM_OPTS_DEBUG;
} else if(!strcmp(argv[i], "no_warn")) {
opts->flags = opts->flags | _PAM_OPTS_SILENT;
} else if(!strcmp(argv[i], "use_first_pass") ||
!strcmp(argv[i], "try_first_pass") ||
!strcmp(argv[i], "use_mapped_pass")) {
/* ignore these, pam_chroot doesn't care about passwds */
} else if(!strcmp(argv[i], "no_chroot")) {
opts->flags = opts->flags | _PAM_OPTS_NO_CHROOT;
} else if(!strcmp(argv[i], "use_regex")) {
opts->flags = opts->flags | _PAM_OPTS_USE_REGEX;
} else if(!strncmp(argv[i], "notfound=", 9)) {
if(!strcmp(argv[i] + 9, "success")) {
opts->flags = opts->flags & (~_PAM_OPTS_NOTFOUNDFAILS);
} else if(!strcmp(argv[i] + 9, "failure")) {
opts->flags = opts->flags | _PAM_OPTS_NOTFOUNDFAILS;
} else {
_pam_log(LOG_ERR, "bad config option: \"%s\"", argv[i]);
}
} else if(!strncmp(argv[i], "onerr=", 6)) {
if(!strcmp(argv[i] + 6, "succeed")) {
opts->flags = opts->flags & (~_PAM_OPTS_NOTFOUNDFAILS);
} else if(!strcmp(argv[i] + 9, "fail")) {
opts->flags = opts->flags | _PAM_OPTS_NOTFOUNDFAILS;
} else {
_pam_log(LOG_ERR, "bad config option: \"%s\"", argv[i]);
}
} else if(!strncmp(argv[i], "chroot_dir=", 11)) {
if(*(argv[i] + 11) == '\0') {
_pam_log(LOG_ERR,
"bad config option: \"%s\": specify a directory", argv[i]);
} else if(NULL != opts->chroot_dir) {
_pam_log(LOG_ERR,
"bad config option: \"%s\": chroot dir already set", argv[i]);
} else {
opts->chroot_dir = x_strdup(argv[i] + 11);
if(NULL == opts->chroot_dir) {
_pam_log(LOG_ERR, "strdup: %s", strerror(errno));
}
}
} else {
_pam_log(LOG_ERR, "unrecognized config option: \"%s\"", argv[i]);
}
}
return _PAM_CHROOT_OK;
}
int _pam_get_chrootdir(const char* user, struct _pam_opts* opts) {
FILE* conf;
char conf_line[LINELEN];
int lineno, err;
char *name, *mark;
if(!(conf = fopen(opts->conf, "r"))) {
_pam_log(LOG_ERR,
"%s: fopen(%s): %s", opts->module, opts->conf, strerror(errno));
opts->chroot_dir = NULL;
return _PAM_CHROOT_SYSERR;
}
lineno = 0; err = 0;
while(fgets(conf_line, LINELEN, conf)) {
++lineno;
/* ignore comments and blank lines */
if((mark = strchr(conf_line, '#'))) *mark = 0;
if(!(name = strtok(conf_line, " \t\r\n"))) continue;
/* ignore lines that contain usernames/regexps but not directories */
if(!(mark = strtok(NULL, " \t\r\n"))) {
_pam_log(LOG_ERR,
"%s: %s %d: no directory", opts->module, opts->conf, lineno);
continue;
}
if(opts->flags & _PAM_OPTS_USE_REGEX) {
regex_t name_regex;
if((err = regcomp(&name_regex, name, REG_ICASE))) {
char *errbuf; size_t len;
len = regerror(err, &name_regex, NULL, 0);
errbuf = malloc(len+1);
if(NULL == errbuf) {
_pam_log(LOG_ERR,
"%s: %s: malloc: %s",
opts->module, __FUNCTION__, strerror(errno));
return _PAM_CHROOT_SYSERR;
}
regerror(err, &name_regex, errbuf, len);
_pam_log(LOG_ERR, "%s: %s %d: illegal regex \"%s\": %s",
opts->module, opts->conf, lineno, name, errbuf);
free(errbuf);
regfree(&name_regex);
continue; /* with the next line */
}
err = regexec(&name_regex, user, 0, NULL, 0);
regfree(&name_regex);
if(!err) {
fclose(conf);
opts->chroot_dir = x_strdup(mark);
if(NULL == opts->chroot_dir) {
_pam_log(LOG_ERR,
"%s: strdup: %s", opts->module, strerror(errno));
return _PAM_CHROOT_SYSERR;
} else if (opts->flags & _PAM_OPTS_DEBUG) {
_pam_log(LOG_NOTICE,
"%s: found chroot_dir \"%s\" for user \"%s\"",
opts->module, opts->chroot_dir, user);
}
return _PAM_CHROOT_OK;
}
} else {
char* tmp = conf_line;
/* run to end of username field */
while(('\0' != *tmp) && !isspace(*tmp)) tmp++;
*tmp = '\0';
if(!strcmp(user,conf_line)) {
fclose(conf);
opts->chroot_dir = x_strdup(mark);
if(NULL == opts->chroot_dir) {
_pam_log(LOG_ERR,
"%s: strdup: %s", opts->module, strerror(errno));
return _PAM_CHROOT_SYSERR;
} else if (opts->flags & _PAM_OPTS_DEBUG) {
_pam_log(LOG_NOTICE,
"%s: found chroot_dir \"%s\" for user \"%s\"",
opts->module, opts->chroot_dir, user);
}
return _PAM_CHROOT_OK;
}
}
if(opts->flags & _PAM_OPTS_DEBUG) {
_pam_log(LOG_NOTICE,
"%s: \"%s\" does not match \"%s\"",
opts->module, user, conf_line);
}
} /* end while(fgets(conf_line, LINELEN, conf)) */
if(opts->flags & _PAM_OPTS_DEBUG) {
_pam_log(LOG_NOTICE,
"%s: user \"%s\" not found in conf file \"%s\"",
opts->module, user, opts->conf);
}
fclose(conf);
opts->chroot_dir = NULL;
return _PAM_CHROOT_USERNOTFOUND;
}
/* This is the workhorse function. All of the pam_sm_* functions should
* initialize a _pam_opts struct with the command line args and flags,
* then pass it to this function */
int _pam_do_chroot(pam_handle_t *pamh, struct _pam_opts *opts) {
int err,debug;
char *name;
char const *user;
name = NULL;
debug = opts->flags & _PAM_OPTS_DEBUG;
if(pam_get_user(pamh, &user, NULL) != PAM_SUCCESS) {
_pam_log(LOG_ERR, "%s: can't get username", opts->module);
return _PAM_CHROOT_SYSERR;
}
if(opts->chroot_dir) { /* overrides the conf file */
if(debug) {
_pam_log(LOG_NOTICE,
"%s: chrootdir (%s) specified, ignoring conf file",
opts->module, opts->chroot_dir);
}
err = _PAM_CHROOT_OK;
} else {
if(debug) {
_pam_log(LOG_NOTICE,
"%s: reading config file (%s)", opt