Files
mars-smart/check_login.c
2026-05-22 10:42:58 +02:00

432 lines
8.6 KiB
C

/*
SMArT
Check username/password combination using PAM and require membership in
the configured SMArT administrator Unix group.
Usage:
check_login <user> <password> <admin-group> [smart.conf]
Passwords are never written to the log.
*/
#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <security/pam_appl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "config.h"
int my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr);
static int user_in_group(const char *username, const char *groupname);
#define SMART_LOG_ERROR 0
#define SMART_LOG_WARNING 1
#define SMART_LOG_INFO 2
#define SMART_LOG_DEBUG 3
#define SMART_LOG_TRACE 4
typedef struct {
char log_path[512];
char debug_level[64];
char admin_group[256];
int level;
} smart_helper_config_t;
static void trim(char *s)
{
char *p = s;
size_t len;
while (*p && isspace((unsigned char)*p)) {
p++;
}
if (p != s) {
memmove(s, p, strlen(p) + 1);
}
len = strlen(s);
while (len > 0 && isspace((unsigned char)s[len - 1])) {
s[len - 1] = '\0';
len--;
}
}
static void strip_quotes(char *s)
{
size_t len = strlen(s);
if (len >= 2) {
if ((s[0] == '\'' && s[len - 1] == '\'') ||
(s[0] == '"' && s[len - 1] == '"')) {
memmove(s, s + 1, len - 2);
s[len - 2] = '\0';
}
}
}
static int parse_perl_assignment(const char *line, char *key, size_t ksz, char *val, size_t vsz)
{
const char *p = line;
size_t ki = 0;
size_t vi = 0;
while (*p && isspace((unsigned char)*p)) {
p++;
}
if (*p != '$') {
return 0;
}
p++;
while (*p && (isalnum((unsigned char)*p) || *p == '_')) {
if (ki + 1 < ksz) {
key[ki++] = *p;
}
p++;
}
key[ki] = '\0';
while (*p && isspace((unsigned char)*p)) {
p++;
}
if (*p != '=') {
return 0;
}
p++;
while (*p && isspace((unsigned char)*p)) {
p++;
}
while (*p && *p != ';' && *p != '\n' && *p != '\r') {
if (vi + 1 < vsz) {
val[vi++] = *p;
}
p++;
}
val[vi] = '\0';
trim(key);
trim(val);
strip_quotes(val);
return key[0] != '\0';
}
static int parse_log_level(const char *value)
{
char buf[64];
size_t i;
if (value == NULL || value[0] == '\0') {
return SMART_LOG_INFO;
}
snprintf(buf, sizeof(buf), "%s", value);
trim(buf);
for (i = 0; buf[i]; i++) {
buf[i] = (char)tolower((unsigned char)buf[i]);
}
if (strcmp(buf, "error") == 0 || strcmp(buf, "err") == 0 || strcmp(buf, "0") == 0) {
return SMART_LOG_ERROR;
}
if (strcmp(buf, "warning") == 0 || strcmp(buf, "warn") == 0 || strcmp(buf, "1") == 0) {
return SMART_LOG_WARNING;
}
if (strcmp(buf, "info") == 0 || strcmp(buf, "2") == 0) {
return SMART_LOG_INFO;
}
if (strcmp(buf, "debug") == 0 || strcmp(buf, "3") == 0) {
return SMART_LOG_DEBUG;
}
if (strcmp(buf, "trace") == 0 || strcmp(buf, "4") == 0) {
return SMART_LOG_TRACE;
}
return SMART_LOG_INFO;
}
static const char *level_name(int level)
{
if (level <= SMART_LOG_ERROR) {
return "ERROR";
}
if (level == SMART_LOG_WARNING) {
return "WARNING";
}
if (level == SMART_LOG_DEBUG) {
return "DEBUG";
}
if (level >= SMART_LOG_TRACE) {
return "TRACE";
}
return "INFO";
}
static void smart_cfg_init(smart_helper_config_t *cfg)
{
memset(cfg, 0, sizeof(*cfg));
snprintf(cfg->log_path, sizeof(cfg->log_path), "%s", DEFAULT_SMART_LOG_PATH);
snprintf(cfg->debug_level, sizeof(cfg->debug_level), "%s", DEFAULT_SMART_LOG_LEVEL);
snprintf(cfg->admin_group, sizeof(cfg->admin_group), "%s", "root");
cfg->level = parse_log_level(cfg->debug_level);
}
static void smart_cfg_load(smart_helper_config_t *cfg, const char *path)
{
FILE *fh;
char line[2048];
if (path == NULL || path[0] == '\0') {
return;
}
fh = fopen(path, "r");
if (fh == NULL) {
return;
}
while (fgets(line, sizeof(line), fh) != NULL) {
char key[256];
char val[1024];
if (!parse_perl_assignment(line, key, sizeof(key), val, sizeof(val))) {
continue;
}
if (strcmp(key, "smart_log_path") == 0) {
snprintf(cfg->log_path, sizeof(cfg->log_path), "%s", val);
} else if (strcmp(key, "smart_debug_level") == 0 ||
strcmp(key, "smart_log_level") == 0) {
snprintf(cfg->debug_level, sizeof(cfg->debug_level), "%s", val);
cfg->level = parse_log_level(val);
} else if (strcmp(key, "smart_admin_group") == 0) {
snprintf(cfg->admin_group, sizeof(cfg->admin_group), "%s", val);
}
}
fclose(fh);
}
static void helper_log(smart_helper_config_t *cfg, const char *component, int level, const char *fmt, ...)
{
FILE *fh = stderr;
int close_fh = 0;
time_t now;
struct tm tm_now;
char tbuf[64];
va_list ap;
if (cfg != NULL && level > cfg->level) {
return;
}
if (cfg != NULL && cfg->log_path[0] != '\0') {
fh = fopen(cfg->log_path, "a");
if (fh != NULL) {
close_fh = 1;
} else {
fh = stderr;
}
}
now = time(NULL);
localtime_r(&now, &tm_now);
strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm_now);
fprintf(fh, "[%s] [%s] [SMArT helper] [%s] ", tbuf, level_name(level), component);
va_start(ap, fmt);
vfprintf(fh, fmt, ap);
va_end(ap);
fputc('\n', fh);
fflush(fh);
if (close_fh) {
fclose(fh);
}
}
static struct pam_conv conv = {
my_conv,
NULL
};
char *user;
char *pass;
int main( int argc, char **argv )
{
pam_handle_t *pamh = NULL;
int retval, st = 1;
const char *admin_group;
const char *smart_conf = DEFAULT_SMART_CONF;
smart_helper_config_t cfg;
smart_cfg_init(&cfg);
if( argc < 4 )
{
fprintf( stderr, "Usage: %s <user> <password> <admin-group> [smart.conf]\n", argv[0] );
return( 3 );
}
user = argv[1];
pass = argv[2];
admin_group = argv[3];
if (argc >= 5 && argv[4] != NULL && argv[4][0] != '\0') {
smart_conf = argv[4];
}
smart_cfg_load(&cfg, smart_conf);
if (admin_group == NULL || admin_group[0] == '\0' || strcmp(admin_group, "-") == 0) {
admin_group = cfg.admin_group;
}
if( user == NULL || user[0] == '\0' ||
pass == NULL ||
admin_group == NULL || admin_group[0] == '\0' )
{
helper_log(&cfg, "check_login", SMART_LOG_ERROR, "invalid helper arguments");
return( 3 );
}
helper_log(&cfg, "check_login", SMART_LOG_INFO, "authentication requested user='%s' admin_group='%s'", user, admin_group);
retval = pam_start( "smart", user, &conv, &pamh );
if( retval == PAM_SUCCESS )
retval = pam_authenticate( pamh, PAM_SILENT );
if( retval == PAM_SUCCESS )
st = retval = pam_acct_mgmt( pamh, PAM_SILENT );
if( pamh != NULL && pam_end( pamh, retval ) != PAM_SUCCESS ) {
helper_log(&cfg, "check_login", SMART_LOG_ERROR, "pam_end failed user='%s'", user);
return( 1 );
}
if( st != PAM_SUCCESS ) {
helper_log(&cfg, "check_login", SMART_LOG_WARNING, "pam authentication failed user='%s' pam_status=%d", user, st);
return( 1 );
}
helper_log(&cfg, "check_login", SMART_LOG_DEBUG, "pam authentication ok user='%s'", user);
if( ! user_in_group( user, admin_group ) ) {
helper_log(&cfg, "check_login", SMART_LOG_WARNING, "group authorization failed user='%s' required_group='%s'", user, admin_group);
return( 2 );
}
helper_log(&cfg, "check_login", SMART_LOG_INFO, "login accepted user='%s' required_group='%s'", user, admin_group);
return( 0 );
}
static int user_in_group(const char *username, const char *groupname)
{
struct passwd *pw;
struct group *gr;
int ngroups = 0;
gid_t *groups;
int i;
if( username == NULL || username[0] == '\0' ||
groupname == NULL || groupname[0] == '\0' )
{
return( 0 );
}
pw = getpwnam( username );
gr = getgrnam( groupname );
if( pw == NULL || gr == NULL )
{
return( 0 );
}
if( pw->pw_gid == gr->gr_gid )
{
return( 1 );
}
#if defined(__linux__) || defined(__GLIBC__)
getgrouplist( username, pw->pw_gid, NULL, &ngroups );
if( ngroups > 0 )
{
groups = calloc( (size_t) ngroups, sizeof( gid_t ) );
if( groups != NULL )
{
if( getgrouplist( username, pw->pw_gid, groups, &ngroups ) >= 0 )
{
for( i = 0; i < ngroups; i++ )
{
if( groups[i] == gr->gr_gid )
{
free( groups );
return( 1 );
}
}
}
free( groups );
}
}
#endif
if( gr->gr_mem != NULL )
{
for( i = 0; gr->gr_mem[i] != NULL; i++ )
{
if( strcmp( gr->gr_mem[i], username ) == 0 )
{
return( 1 );
}
}
}
return( 0 );
}
int my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
{
struct pam_response *reply;
int i;
(void) msg;
(void) appdata_ptr;
reply = (struct pam_response *) calloc( (size_t) num_msg, sizeof( struct pam_response ) );
if( reply == NULL )
{
return( PAM_BUF_ERR );
}
for( i = 0; i < num_msg; i ++ )
{
reply[i].resp = (char *) strdup( pass );
reply[i].resp_retcode = 0;
}
*resp = reply;
return( PAM_SUCCESS );
}