/* SMArT List local/NSS users for the WebUI. Usage: smart_userlist [--config /etc/mars_nwe/smart.conf] [--all] [--min-uid UID] [--pam-check] [--pam-service SERVICE] Output format on stdout stays unchanged: usernameuidgidgecoshomeshell */ #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #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 int empty_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { struct pam_response *reply; (void) msg; (void) appdata_ptr; if (num_msg <= 0) { *resp = NULL; return PAM_SUCCESS; } reply = calloc((size_t) num_msg, sizeof(struct pam_response)); if (reply == NULL) { return PAM_BUF_ERR; } *resp = reply; return PAM_SUCCESS; } static int pam_account_ok(const char *service, const char *user) { struct pam_conv conv = { empty_conv, NULL }; pam_handle_t *pamh = NULL; int rc; rc = pam_start(service, user, &conv, &pamh); if (rc == PAM_SUCCESS) { rc = pam_acct_mgmt(pamh, PAM_SILENT); } if (pamh != NULL) { pam_end(pamh, rc); } return rc == PAM_SUCCESS; } static void print_sanitized(const char *s) { const unsigned char *p = (const unsigned char *) (s != NULL ? s : ""); while (*p != '\0') { if (*p == '\t' || *p == '\n' || *p == '\r') { putchar(' '); } else { putchar((int) *p); } p++; } } static int is_safe_name(const char *s) { const unsigned char *p = (const unsigned char *) s; if (s == NULL || *s == '\0') { return 0; } while (*p != '\0') { if (!( (*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') || (*p >= '0' && *p <= '9') || *p == '_' || *p == '-' || *p == '.' )) { return 0; } p++; } return 1; } int main(int argc, char **argv) { struct passwd *pw; uid_t min_uid = 1000; int include_system = 0; int pam_check = 0; const char *pam_service = "smart"; const char *smart_conf = DEFAULT_SMART_CONF; int i; unsigned long emitted = 0; unsigned long skipped = 0; smart_helper_config_t cfg; smart_cfg_init(&cfg); for (i = 1; i < argc; i++) { if (strcmp(argv[i], "--all") == 0) { include_system = 1; min_uid = 0; } else if (strcmp(argv[i], "--min-uid") == 0 && i + 1 < argc) { char *end = NULL; unsigned long v = strtoul(argv[++i], &end, 10); if (end == NULL || *end != '\0') { smart_cfg_load(&cfg, smart_conf); helper_log(&cfg, "smart_userlist", SMART_LOG_ERROR, "invalid --min-uid value"); fprintf(stderr, "Invalid --min-uid value\n"); return 2; } min_uid = (uid_t) v; } else if (strcmp(argv[i], "--pam-check") == 0) { pam_check = 1; } else if (strcmp(argv[i], "--pam-service") == 0 && i + 1 < argc) { pam_service = argv[++i]; } else if (strcmp(argv[i], "--config") == 0 && i + 1 < argc) { smart_conf = argv[++i]; } else { smart_cfg_load(&cfg, smart_conf); helper_log(&cfg, "smart_userlist", SMART_LOG_ERROR, "invalid command line"); fprintf(stderr, "Usage: %s [--config FILE] [--all] [--min-uid UID] [--pam-check] [--pam-service SERVICE]\n", argv[0]); return 2; } } smart_cfg_load(&cfg, smart_conf); helper_log(&cfg, "smart_userlist", SMART_LOG_DEBUG, "user enumeration started include_system=%d min_uid=%lu pam_check=%d pam_service='%s'", include_system, (unsigned long) min_uid, pam_check, pam_service); errno = 0; setpwent(); while ((pw = getpwent()) != NULL) { if (!is_safe_name(pw->pw_name)) { skipped++; continue; } if (!include_system && pw->pw_uid < min_uid) { skipped++; continue; } if (!include_system && (strcmp(pw->pw_name, "root") == 0 || strcmp(pw->pw_name, "nobody") == 0)) { skipped++; continue; } if (pam_check && !pam_account_ok(pam_service, pw->pw_name)) { skipped++; continue; } print_sanitized(pw->pw_name); printf("\t%lu\t%lu\t", (unsigned long) pw->pw_uid, (unsigned long) pw->pw_gid); print_sanitized(pw->pw_gecos); putchar('\t'); print_sanitized(pw->pw_dir); putchar('\t'); print_sanitized(pw->pw_shell); putchar('\n'); emitted++; } endpwent(); if (errno != 0) { helper_log(&cfg, "smart_userlist", SMART_LOG_ERROR, "getpwent failed: %s", strerror(errno)); perror("getpwent"); return 1; } helper_log(&cfg, "smart_userlist", SMART_LOG_DEBUG, "user enumeration finished emitted=%lu skipped=%lu", emitted, skipped); return 0; }