#ifndef DEFAULT_CONF_FILE
#define DEFAULT_CONF_FILE "/etc/security/pam_env.conf"
#endif
#define DEFAULT_ETC_ENVFILE "/etc/environment"
#define DEFAULT_READ_ENVFILE 1
#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define PAM_SM_AUTH
#define PAM_SM_SESSION
#define PAM_SM_PASSWORD
#define PAM_SM_ACCOUNT
#include <security/pam_modules.h>
#include <security/pam_appl.h>
typedef struct var {
char *name;
char *value;
char *defval;
char *override;
} VAR;
#define BUF_SIZE 1024
#define MAX_ENV 8192
#define GOOD_LINE 0
#define BAD_LINE 100
#define DEFINE_VAR 101
#define UNDEFINE_VAR 102
#define ILLEGAL_VAR 103
static int _assemble_line(pam_handle_t *, FILE *, char *, int);
static int _parse_line(pam_handle_t *, char *, VAR *);
static int _check_var(pam_handle_t *, VAR *);
static void _clean_var(pam_handle_t *, VAR *);
static int _expand_arg(pam_handle_t *, char **);
static const char * _pam_get_item_byname(pam_handle_t *, const char *);
static int _define_var(pam_handle_t *, VAR *);
static int _undefine_var(pam_handle_t *, VAR *);
static char quote='Z';
static void _debug_message(pam_handle_t *pamh, const char *format, ...)
{
va_list args;
va_start(args, format);
if (NULL != openpam_get_option(pamh, "debug")) {
syslog(LOG_DEBUG, format, args);
}
va_end(args);
}
static void _log_err(int err, const char *format, ...)
{
va_list args;
va_start(args, format);
openlog("PAM-env", LOG_CONS|LOG_PID, LOG_AUTH);
vsyslog(err, format, args);
va_end(args);
closelog();
}
#define PAM_DEBUG_ARG 0x01
#define PAM_NEW_CONF_FILE 0x02
#define PAM_ENV_SILENT 0x04
#define PAM_NEW_ENV_FILE 0x10
static int _pam_parse(pam_handle_t *pamh, int flags, int argc, const char **argv, char **conffile,
char **envfile, int *readenv)
{
int ctrl=0;
const char *readenvchar = NULL;
if (NULL != openpam_get_option(pamh, "debug")) {
_debug_message(pamh, "debugging on");
ctrl |= PAM_DEBUG_ARG;
}
if (NULL != (*conffile = (char *)openpam_get_option(pamh, "conffile"))) {
_debug_message(pamh, "new Configuration File: %s", *conffile);
ctrl |= PAM_NEW_CONF_FILE;
}
if (NULL != (*envfile = (char *)openpam_get_option(pamh, "conffile"))) {
_debug_message(pamh, "new Env File: %s", *envfile);
ctrl |= PAM_NEW_ENV_FILE;
}
readenvchar = (char *)openpam_get_option(pamh, "readenv");
if (NULL != readenvchar && 0 != (*readenv = atoi(readenvchar))) {
_debug_message(pamh, "readenv: %s", *readenv);
}
return ctrl;
}
static int _parse_config_file(pam_handle_t *pamh, int ctrl, char **conffile)
{
int retval;
const char *file;
char buffer[BUF_SIZE];
FILE *conf;
VAR Var, *var=&Var;
var->name=NULL; var->defval=NULL; var->override=NULL;
_debug_message(pamh, "Called.");
if (ctrl & PAM_NEW_CONF_FILE) {
file = *conffile;
} else {
file = DEFAULT_CONF_FILE;
}
_debug_message(pamh, "Config file name is: %s", file);
if ((conf = fopen(file,"r")) == NULL) {
_log_err(LOG_ERR, "Unable to open config file: %s",
strerror(errno));
return PAM_IGNORE;
}
while (( retval = _assemble_line(pamh, conf, buffer, BUF_SIZE)) > 0) {
_debug_message(pamh, "Read line: %s", buffer);
if ((retval = _parse_line(pamh, buffer, var)) == GOOD_LINE) {
retval = _check_var(pamh, var);
if (DEFINE_VAR == retval) {
retval = _define_var(pamh, var);
} else if (UNDEFINE_VAR == retval) {
retval = _undefine_var(pamh, var);
}
}
if (PAM_SUCCESS != retval && ILLEGAL_VAR != retval
&& BAD_LINE != retval) break;
_clean_var(pamh, var);
}
(void) fclose(conf);
_clean_var(pamh, var);
if (NULL != conffile) {
memset(conffile, 0, sizeof(conffile));
*conffile = NULL;
}
file = NULL;
_debug_message(pamh, "Exit.");
return (retval<0?PAM_ABORT:PAM_SUCCESS);
}
static int _parse_env_file(pam_handle_t *pamh, int ctrl, char **env_file)
{
int retval=PAM_SUCCESS, i, t;
const char *file;
char buffer[BUF_SIZE], *key, *mark;
FILE *conf;
if (ctrl & PAM_NEW_ENV_FILE)
file = *env_file;
else
file = DEFAULT_ETC_ENVFILE;
_debug_message(pamh, "Env file name is: %s", file);
if ((conf = fopen(file,"r")) == NULL) {
_debug_message(pamh, "Unable to open env file: %s", strerror(errno));
return PAM_ABORT;
}
while (_assemble_line(pamh, conf, buffer, BUF_SIZE) > 0) {
_debug_message(pamh, "Read line: %s", buffer);
key = buffer;
key += strspn(key, " \n\t");
if (!key || key[0] == '#')
continue;
if (strncmp(key, "export ", (size_t) 7) == 0)
key += 7;
mark = key;
while(mark[0] != '\n' && mark[0] != '#' && mark[0] != '\0')
mark++;
if (mark[0] != '\0')
mark[0] = '\0';
for ( i = 0 ; key[i] != '=' && key[i] != '\0' ; i++ )
if (!isalnum(key[i]) && key[i] != '_') {
_debug_message(pamh, "key is not alpha numeric - '%s', ignoring", key);
continue;
}
if (key[i] == '=' && (key[++i] == '\"' || key[i] == '\'')) {
for ( t = i+1 ; key[t] != '\0' ; t++)
if (key[t] != '\"' && key[t] != '\'')
key[i++] = key[t];
else if (key[t+1] != '\0')
key[i++] = key[t];
key[i] = '\0';
}
retval = pam_putenv(pamh, key);
if (retval != PAM_SUCCESS) {
_debug_message(pamh, "error setting env \"%s\"", key);
break;
}
}
(void) fclose(conf);
if (NULL != env_file) {
memset(env_file, 0, sizeof(env_file));
*env_file = NULL;
}
file = NULL;
_debug_message(pamh, "Exit.");
return (retval<0?PAM_IGNORE:PAM_SUCCESS);
}
static int _assemble_line(pam_handle_t *pamh, FILE *f, char *buffer, int buf_len)
{
char *p = buffer;
char *s, *os;
int used = 0;
_debug_message(pamh, "called.");
for (;;) {
if (used >= buf_len) {
_debug_message(pamh, "_assemble_line: overflow");
return -1;
}
if (fgets(p, buf_len - used, f) == NULL) {
if (used) {
return -1;
} else {
return 0;
}
}
s = p + strspn(p, " \n\t");
if (*s && (*s != '#')) {
os = s;
while (*s && *s != '#')
++s;
if (*s == '#') {
*s = '\0';
used += strlen(os);
break;
}
s = os;
s += strlen(s);
while (s > os && ((*--s == ' ') || (*s == '\t')
|| (*s == '\n')));
if (*s == '\\') {
*s = '\0';
used += strlen(os);
p = s;
} else {
used += strlen(os);
break;
}
} else {
}
}
return used;
}
static int _parse_line(pam_handle_t *pamh, char *buffer, VAR *var)
{
int length, quoteflg=0;
char *ptr, **valptr, *tmpptr;
_debug_message(pamh, "Called buffer = <%s>", buffer);
length = strcspn(buffer," \t\n");
if ((var->name = malloc(length + 1)) == NULL) {
_log_err(LOG_ERR, "Couldn't malloc %d bytes", length+1);
return PAM_BUF_ERR;
}
strncpy(var->name, buffer, length);
var->name[length] = '\0';
_debug_message(pamh, "var->name = <%s>, length = %d", var->name, length);
ptr = buffer+length;
while ((length = strspn(ptr, " \t")) > 0) {
ptr += length;
_debug_message(pamh, ptr);
if (strncmp(ptr,"DEFAULT=",8) == 0) {
ptr+=8;
_debug_message(pamh, "Default arg found: <%s>", ptr);
valptr=&(var->defval);
} else if (strncmp(ptr, "OVERRIDE=", 9) == 0) {
ptr+=9;
_debug_message(pamh, "Override arg found: <%s>", ptr);
valptr=&(var->override);
} else {
_debug_message(pamh, "Unrecognized options: <%s> - ignoring line", ptr);
_log_err(LOG_ERR, "Unrecognized Option: %s - ignoring line", ptr);
return BAD_LINE;
}
if ('"' != *ptr) {
length = strcspn(ptr, " \t\n");
tmpptr = ptr+length;
} else {
tmpptr = strchr(++ptr, '"');
if (!tmpptr) {
_debug_message(pamh, "Unterminated quoted string: %s", ptr-1);
_log_err(LOG_ERR, "Unterminated quoted string: %s", ptr-1);
return BAD_LINE;
}
length = tmpptr - ptr;
if (*++tmpptr && ' ' != *tmpptr && '\t' != *tmpptr && '\n' != *tmpptr) {
_debug_message(pamh, "Quotes must cover the entire string: <%s>", ptr);
_log_err(LOG_ERR, "Quotes must cover the entire string: <%s>", ptr);
return BAD_LINE;
}
quoteflg++;
}
if (length) {
if ((*valptr = malloc(length + 1)) == NULL) {
_debug_message(pamh, "Couldn't malloc %d bytes", length+1);
_log_err(LOG_ERR, "Couldn't malloc %d bytes", length+1);
return PAM_BUF_ERR;
}
(void)strncpy(*valptr,ptr,length);
(*valptr)[length]='\0';
} else if (quoteflg--) {
*valptr = "e;
}
ptr = tmpptr;
}
_debug_message(pamh, "Exit.");
ptr = NULL; tmpptr = NULL; valptr = NULL;
return GOOD_LINE;
}
static int _check_var(pam_handle_t *pamh, VAR *var)
{
int retval;
_debug_message(pamh, "Called.");
if (var->defval && ("e != var->defval) &&
((retval = _expand_arg(pamh, &(var->defval))) != PAM_SUCCESS)) {
return retval;
}
if (var->override && ("e != var->override) &&
((retval = _expand_arg(pamh, &(var->override))) != PAM_SUCCESS)) {
return retval;
}
if (var->override && *(var->override) && "e != var->override) {
_debug_message(pamh, "OVERRIDE variable <%s> being used: <%s>", var->name, var->override);
var->value = var->override;
retval = DEFINE_VAR;
} else {
var->value = var->defval;
if ("e == var->defval) {
*var->defval = '\0';
_debug_message(pamh, "An empty variable: <%s>", var->name);
retval = DEFINE_VAR;
} else if (var->defval) {
_debug_message(pamh, "DEFAULT variable <%s> being used: <%s>", var->name, var->defval);
retval = DEFINE_VAR;
} else {
_debug_message(pamh, "UNDEFINE variable <%s>", var->name);
retval = UNDEFINE_VAR;
}
}
_debug_message(pamh, "Exit.");
return retval;
}
static int _expand_arg(pam_handle_t *pamh, char **value)
{
const char *orig=*value, *tmpptr=NULL;
char *ptr;
char type, tmpval[BUF_SIZE];
char tmp[MAX_ENV];
_debug_message(pamh, "Remember to initialize tmp!");
memset(tmp, 0, MAX_ENV);
_debug_message(pamh, "Expanding <%s>",orig);
while (*orig) {
if ('\\' == *orig) {
++orig;
if ('$' != *orig && '@' != *orig) {
_debug_message(pamh, "Unrecognized escaped character: <%c> - ignoring", *orig);
_log_err(LOG_ERR, "Unrecognized escaped character: <%c> - ignoring",
*orig);
} else if ((strlen(tmp) + 1) < MAX_ENV) {
tmp[strlen(tmp)] = *orig++;
} else {
_debug_message(pamh, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr);
_log_err(LOG_ERR, "Variable buffer overflow: <%s> + <%s>",
tmp, tmpptr);
}
continue;
}
if ('$' == *orig || '@' == *orig) {
if ('{' != *(orig+1)) {
_debug_message(pamh, "Expandable variables must be wrapped in {}"
" <%s> - ignoring", orig);
_log_err(LOG_ERR, "Expandable variables must be wrapped in {}"
" <%s> - ignoring", orig);
if ((strlen(tmp) + 1) < MAX_ENV) {
tmp[strlen(tmp)] = *orig++;
}
continue;
} else {
_debug_message(pamh, "Expandable argument: <%s>", orig);
type = *orig;
orig+=2;
ptr = strchr(orig, '}');
if (ptr) {
*ptr++ = '\0';
} else {
_debug_message(pamh, "Unterminated expandable variable: <%s>", orig-2);
_log_err(LOG_ERR, "Unterminated expandable variable: <%s>", orig-2);
return PAM_ABORT;
}
strncpy(tmpval, orig, sizeof(tmpval));
tmpval[sizeof(tmpval)-1] = '\0';
orig=ptr;
switch (type) {
case '$':
_debug_message(pamh, "Expanding env var: <%s>",tmpval);
tmpptr = pam_getenv(pamh, tmpval);
_debug_message(pamh, "Expanded to <%s>", tmpptr);
break;
case '@':
_debug_message(pamh, "Expanding pam item: <%s>",tmpval);
tmpptr = _pam_get_item_byname(pamh, tmpval);
_debug_message(pamh, "Expanded to <%s>", tmpptr);
break;
default:
_debug_message(pamh, "Impossible error, type == <%c>", type);
_log_err(LOG_CRIT, "Impossible error, type == <%c>", type);
return PAM_ABORT;
}
if (tmpptr) {
if (strlcat(tmp, tmpptr, MAX_ENV) >= MAX_ENV) {
_debug_message(pamh, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr);
_log_err(LOG_ERR, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr);
}
}
}
} else {
if ((strlen(tmp) + 1) < MAX_ENV) {
tmp[strlen(tmp)] = *orig++;
} else {
_debug_message(pamh, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr);
_log_err(LOG_ERR, "Variable buffer overflow: <%s> + <%s>", tmp, tmpptr);
}
}
}
size_t tmp_len = strlen(tmp);
if (tmp_len > strlen(*value)) {
free(*value);
if ((*value = malloc(tmp_len+1)) == NULL) {
_debug_message(pamh, "Couldn't malloc %d bytes for expanded var", tmp_len+1);
_log_err(LOG_ERR,"Couldn't malloc %d bytes for expanded var",
tmp_len+1);
return PAM_BUF_ERR;
}
}
strlcpy(*value, tmp, tmp_len+1);
_debug_message(pamh, "Exit.");
return PAM_SUCCESS;
}
static const char * _pam_get_item_byname(pam_handle_t *pamh, const char *name)
{
int item;
const char *itemval;
_debug_message(pamh, "Called.");
if (strcmp(name, "PAM_USER") == 0) {
item = PAM_USER;
} else if (strcmp(name, "PAM_USER_PROMPT") == 0) {
item = PAM_USER_PROMPT;
} else if (strcmp(name, "PAM_TTY") == 0) {
item = PAM_TTY;
} else if (strcmp(name, "PAM_RUSER") == 0) {
item = PAM_RUSER;
} else if (strcmp(name, "PAM_RHOST") == 0) {
item = PAM_RHOST;
} else {
_debug_message(pamh, "Unknown PAM_ITEM: <%s>", name);
_log_err(LOG_ERR, "Unknown PAM_ITEM: <%s>", name);
return NULL;
}
if (pam_get_item(pamh, item, (const void **)&itemval) != PAM_SUCCESS) {
_debug_message(pamh, "pam_get_item failed");
return NULL;
}
_debug_message(pamh, "Exit.");
return itemval;
}
static int _define_var(pam_handle_t *pamh, VAR *var)
{
char *envvar;
int size, retval=PAM_SUCCESS;
_debug_message(pamh, "Called.");
size = strlen(var->name)+strlen(var->value)+2;
if ((envvar = malloc(size)) == NULL) {
_debug_message(pamh, "Malloc fail, size = %d", size);
_log_err(LOG_ERR, "Malloc fail, size = %d", size);
return PAM_BUF_ERR;
}
(void) snprintf(envvar,size,"%s=%s",var->name,var->value);
retval = pam_putenv(pamh, envvar);
free(envvar); envvar=NULL;
_debug_message(pamh, "Exit.");
return retval;
}
static int _undefine_var(pam_handle_t *pamh, VAR *var)
{
_debug_message(pamh, "Called and exit.");
return pam_putenv(pamh, var->name);
}
static void _clean_var(pam_handle_t *pamh, VAR *var)
{
if (var->name) {
free(var->name);
}
if (var->defval && ("e != var->defval)) {
free(var->defval);
}
if (var->override && ("e != var->override)) {
free(var->override);
}
var->name = NULL;
var->value = NULL;
var->defval = NULL;
var->override = NULL;
return;
}
PAM_EXTERN
int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
const char **argv)
{
return PAM_IGNORE;
}
PAM_EXTERN
int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
const char **argv)
{
int retval, ctrl, readenv=DEFAULT_READ_ENVFILE;
char *conf_file=NULL, *env_file=NULL;
_debug_message(pamh, "Called.");
ctrl = _pam_parse(pamh, flags, argc, argv, &conf_file, &env_file, &readenv);
retval = _parse_config_file(pamh, ctrl, &conf_file);
if(readenv)
_parse_env_file(pamh, ctrl, &env_file);
_debug_message(pamh, "Exit.");
return retval;
}
PAM_EXTERN
int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc,
const char **argv)
{
_log_err(LOG_NOTICE, "pam_sm_acct_mgmt called inappropriatly");
return PAM_SERVICE_ERR;
}
PAM_EXTERN
int pam_sm_open_session(pam_handle_t *pamh,int flags,int argc
,const char **argv)
{
int retval, ctrl, readenv=DEFAULT_READ_ENVFILE;
char *conf_file=NULL, *env_file=NULL;
_debug_message(pamh, "Called.");
ctrl = _pam_parse(pamh, flags, argc, argv, &conf_file, &env_file, &readenv);
retval = _parse_config_file(pamh, ctrl, &conf_file);
if(readenv)
_parse_env_file(pamh, ctrl, &env_file);
_debug_message(pamh, "Exit.");
return retval;
}
PAM_EXTERN
int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc,
const char **argv)
{
_debug_message(pamh, "Called and Exit");
return PAM_SUCCESS;
}
PAM_EXTERN
int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc,
const char **argv)
{
_log_err(LOG_NOTICE, "pam_sm_chauthtok called inappropriatly");
return PAM_SERVICE_ERR;
}
#ifdef PAM_STATIC
struct pam_module _pam_env_modstruct = {
"pam_env",
pam_sm_authenticate,
pam_sm_setcred,
pam_sm_acct_mgmt,
pam_sm_open_session,
pam_sm_close_session,
pam_sm_chauthtok,
};
#endif