/******************************************************************************
* Check user type based on login.defs.
*
* Copyright (c) 2020 Red Hat, Inc.
* Written by Pavel Březina <pbrezina@redhat.com>
*
* 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, and the entire permission notice in its entirety,
* including the disclaimer of warranties.
* 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.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* ALTERNATIVELY, this product may be distributed under the terms of
* the GNU Public License, in which case the provisions of the GPL are
* required INSTEAD OF the above restrictions. (This clause is
* necessary due to a potential bad interaction between the GPL and
* the restrictions contained in a BSD-style copyright.)
*
* THIS SOFTWARE IS PROVIDED ``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.
*
*/
#include "config.h"
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <pwd.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_SESSION
#define PAM_SM_PASSWORD
#include <security/pam_modules.h>
#include <security/pam_modutil.h>
#include <security/pam_ext.h>
#define LOGIN_DEFS "/etc/login.defs"
enum pam_usertype_op {
OP_IS_SYSTEM,
OP_IS_REGULAR,
OP_SENTINEL
};
struct pam_usertype_opts {
enum pam_usertype_op op;
int use_uid;
int audit;
};
static int
pam_usertype_parse_args(struct pam_usertype_opts *opts,
pam_handle_t *pamh,
int argc,
const char **argv)
{
int i;
memset(opts, 0, sizeof(struct pam_usertype_opts));
opts->op = OP_SENTINEL;
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "use_uid") == 0) {
opts->use_uid = 1;
} else if (strcmp(argv[i], "audit") == 0) {
opts->audit = 1;
} else if (strcmp(argv[i], "issystem") == 0) {
opts->op = OP_IS_SYSTEM;
} else if (strcmp(argv[i], "isregular") == 0) {
opts->op = OP_IS_REGULAR;
} else {
pam_syslog(pamh, LOG_WARNING, "Unknown argument: %s", argv[i]);
/* Just continue. */
}
}
if (opts->op == OP_SENTINEL) {
pam_syslog(pamh, LOG_ERR, "Operation not specified");
return PAM_SERVICE_ERR;
}
return PAM_SUCCESS;
}
static int
pam_usertype_get_uid(struct pam_usertype_opts *opts,
pam_handle_t *pamh,
uid_t *_uid)
{
struct passwd *pwd;
const void *prompt;
const char *username;
int ret;
/* Get uid of user that runs the application. */
if (opts->use_uid) {
pwd = pam_modutil_getpwuid(pamh, getuid());
if (pwd == NULL) {
pam_syslog(pamh, LOG_ERR,
"error retrieving information about user %lu",
(unsigned long)getuid());
return PAM_USER_UNKNOWN;
}
*_uid = pwd->pw_uid;
return PAM_SUCCESS;
}
/* Get uid of user that is being authenticated. */
ret = pam_get_item(pamh, PAM_USER_PROMPT, &prompt);
if (ret != PAM_SUCCESS || prompt == NULL || strlen(prompt) == 0) {
prompt = "login: ";
}
ret = pam_get_user(pamh, &username, prompt);
if (ret != PAM_SUCCESS || username == NULL) {
pam_syslog(pamh, LOG_ERR, "error retrieving user name: %s",
pam_strerror(pamh, ret));
return ret;
}
pwd = pam_modutil_getpwnam(pamh, username);
if (pwd == NULL) {
if (opts->audit) {
pam_syslog(pamh, LOG_NOTICE,
"error retrieving information about user %s", username);
}
return PAM_USER_UNKNOWN;
}
*_uid = pwd->pw_uid;
return PAM_SUCCESS;
}
#define MAX_UID_VALUE 0xFFFFFFFFUL
/* lookup a value for key in login.defs file or similar key value format */
char *
pam_usertype_search_key(pam_handle_t *pamh UNUSED,
const char *file_name,
const char *key)
{
FILE *fp;
char *buf = NULL;
size_t buflen = 0;
char *retval = NULL;
fp = fopen(file_name, "r");
if (NULL == fp)
return NULL;
while (!feof(fp)) {
char *tmp, *cp;
#if defined(HAVE_GETLINE)
ssize_t n = getline(&buf, &buflen, fp);
#elif defined (HAVE_GETDELIM)
ssize_t n = getdelim(&buf, &buflen, '\n', fp);
#else
ssize_t n;
if (buf == NULL) {
buflen = BUF_SIZE;
buf = malloc(buflen);
if (buf == NULL) {
fclose(fp);
return NULL;
}
}
buf[0] = '\0';
if (fgets(buf, buflen - 1, fp) == NULL)
break;
else if (buf != NULL)
n = strlen(buf);
else
n = 0;
#endif /* HAVE_GETLINE / HAVE_GETDELIM */
cp = buf;
if (n < 1)
break;
if (cp[n - 1] == '\n')
cp[n - 1] = '\0';
tmp = strchr(cp, '#'); /* remove comments */
if (tmp)
*tmp = '\0';
while (isspace((int)*cp)) /* remove spaces and tabs */
++cp;
if (*cp == '\0') /* ignore empty lines */
continue;
tmp = strsep (&cp, " \t=");
if (cp != NULL)
while (isspace((int)*cp) || *cp == '=')
++cp;
else
cp = "";
if (strcasecmp(tmp, key) == 0) {
retval = strdup(cp);
break;
}
}
fclose(fp);
free(buf);
return retval;
}
static uid_t
pam_usertype_get_id(pam_handle_t *pamh,
const char *key,
uid_t default_value)
{
unsigned long ul;
char *value;
char *ep;
uid_t uid;
value = pam_usertype_search_key(pamh, LOGIN_DEFS, key);
if (value == NULL) {
return default_value;
}
/* taken from get_lastlog_uid_max() */
ep = value + strlen(value);
while (ep > value && isspace(*(--ep))) {
*ep = '\0';
}
errno = 0;
ul = strtoul(value, &ep, 10);
if (!(ul >= MAX_UID_VALUE
|| (uid_t)ul >= MAX_UID_VALUE
|| (errno != 0 && ul == 0)
|| value == ep
|| *ep != '\0')) {
uid = (uid_t)ul;
} else {
uid = default_value;
}
free(value);
return uid;
}
static int
pam_usertype_is_system(pam_handle_t *pamh, uid_t uid)
{
uid_t uid_min;
uid_t sys_min;
uid_t sys_max;
if (uid == (uid_t)-1) {
pam_syslog(pamh, LOG_WARNING, "invalid uid");
return PAM_USER_UNKNOWN;
}
if (uid <= 99) {
/* Reserved. */
return PAM_SUCCESS;
}
if (uid == PAM_USERTYPE_OVERFLOW_UID) {
/* nobody */
return PAM_SUCCESS;
}
uid_min = pam_usertype_get_id(pamh, "UID_MIN", PAM_USERTYPE_UIDMIN);
sys_min = pam_usertype_get_id(pamh, "SYS_UID_MIN", PAM_USERTYPE_SYSUIDMIN);
sys_max = pam_usertype_get_id(pamh, "SYS_UID_MAX", uid_min - 1);
return uid >= sys_min && uid <= sys_max ? PAM_SUCCESS : PAM_AUTH_ERR;
}
static int
pam_usertype_is_regular(pam_handle_t *pamh, uid_t uid)
{
int ret;
ret = pam_usertype_is_system(pamh, uid);
switch (ret) {
case PAM_SUCCESS:
return PAM_AUTH_ERR;
case PAM_USER_UNKNOWN:
return PAM_USER_UNKNOWN;
default:
return PAM_SUCCESS;
}
}
static int
pam_usertype_evaluate(struct pam_usertype_opts *opts,
pam_handle_t *pamh,
uid_t uid)
{
switch (opts->op) {
case OP_IS_SYSTEM:
return pam_usertype_is_system(pamh, uid);
case OP_IS_REGULAR:
return pam_usertype_is_regular(pamh, uid);
default:
pam_syslog(pamh, LOG_ERR, "Unknown operation: %d", opts->op);
return PAM_SERVICE_ERR;
}
}
/**
* Arguments:
* - issystem: uid in <SYS_UID_MIN, SYS_UID_MAX>
* - isregular: not issystem
* - use_uid: use user that runs application not that is being authenticate (same as in pam_succeed_if)
* - audit: log unknown users to syslog
*/
int
pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,
int argc, const char **argv)
{
struct pam_usertype_opts opts;
uid_t uid;
int ret;
ret = pam_usertype_parse_args(&opts, pamh, argc, argv);
if (ret != PAM_SUCCESS) {
return ret;
}
ret = pam_usertype_get_uid(&opts, pamh, &uid);
if (ret != PAM_SUCCESS) {
return ret;
}
return pam_usertype_evaluate(&opts, pamh, uid);
}
int
pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
int argc UNUSED, const char **argv UNUSED)
{
return PAM_IGNORE;
}
int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
return pam_sm_authenticate(pamh, flags, argc, argv);
}
int
pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
return pam_sm_authenticate(pamh, flags, argc, argv);
}
int
pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
return pam_sm_authenticate(pamh, flags, argc, argv);
}
int
pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
return pam_sm_authenticate(pamh, flags, argc, argv);
}