439 lines
9.0 KiB
C
439 lines
9.0 KiB
C
/*
|
|
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:
|
|
username<TAB>uid<TAB>gid<TAB>gecos<TAB>home<TAB>shell
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <errno.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"
|
|
|
|
|
|
#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;
|
|
}
|