/*
   SMArT

   List local/NSS users for the WebUI.

   PAM itself cannot enumerate users.  User enumeration is done through NSS
   getpwent(), so /etc/nsswitch.conf is honored (files, sss, ldap, nis, ...).
   Optionally, each user can be checked with pam_acct_mgmt() against the "smart"
   PAM service.

   Output format:
     username<TAB>uid<TAB>gid<TAB>gecos<TAB>home<TAB>shell
*/

#include <errno.h>
#include <pwd.h>
#include <security/pam_appl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

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";
	int i;

	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') {
				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 {
			fprintf(stderr,
			        "Usage: %s [--all] [--min-uid UID] [--pam-check] [--pam-service SERVICE]\n",
			        argv[0]);
			return 2;
		}
	}

	errno = 0;
	setpwent();

	while ((pw = getpwent()) != NULL) {
		if (!is_safe_name(pw->pw_name)) {
			continue;
		}

		if (!include_system && pw->pw_uid < min_uid) {
			continue;
		}

		if (!include_system &&
		    (strcmp(pw->pw_name, "root") == 0 || strcmp(pw->pw_name, "nobody") == 0)) {
			continue;
		}

		if (pam_check && !pam_account_ok(pam_service, pw->pw_name)) {
			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');
	}

	endpwent();

	if (errno != 0) {
		perror("getpwent");
		return 1;
	}

	return 0;
}
