/* SMArT Check username/password combination using PAM and require membership in the configured SMArT administrator Unix group. Usage: check_login [smart.conf] Passwords are never written to the log. */ #include #include #include #include #include #include #include #include #include #include #include #include #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 [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 ); }