/*
    Copyright (C) 2001-2006  Ben Kibbey <bjk@luxsci.net>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "passwd.h"

#ifndef HAVE_STRSEP
#include "../strsep.c"
#endif

#ifndef HAVE_ERR_H
#include "../err.c"
#endif

void ui_module_init(int *chainable)
{
#ifdef DEBUG
    fprintf(stderr, "%s: ui_module_init()\n", __FILE__);
#endif

    /*
     * Keep the password file open if possible (*BSD). 
     */
#ifdef HAVE_SETPASSENT
    setpassent(1);
#endif

    if (getuid() == 0)
	amroot = 1;

    *chainable = 0;
    return;
}

void ui_module_exit()
{
#ifdef DEBUG
    fprintf(stderr, "%s: ui_module_exit()\n", __FILE__);
#endif

#ifdef HAVE_GETSPNAM
    if (amroot)
	endspent();
#endif

    endpwent();
    endgrent();
    return;
}

/* See if the gecos options are valid. */
static int parse_gecos_options(const char *args)
{
    int i = 0;

    for (i = 0; i < strlen(args); i++) {
	switch (args[i]) {
	    case 'n':
	    case '1':
	    case '2':
	    case '3':
	    case 'a':
		break;
	    default:
		return 1;
	}
    }

    return 0;
}

/* Break up the gecos string into sections and add the sections to the output
 * string array if needed. */
static void gecos_strings(char *str)
{
    int i = 0;
    char *buf;
    const char *name, *first, *second, *third;

    name = first = second = third = "-";

    while ((buf = strsep(&str, ",")) != NULL) {
	if (!buf[0])
	    continue;

	switch (i++) {
	    case 0:
		name = buf;
		break;
	    case 1:
		first = buf;
		break;
	    case 2:
		second = buf;
		break;
	    case 3:
		third = buf;
		break;
	    default:
		break;
	}
    }

    for (i = 0; i < strlen(gecos_options); i++) {
	switch (gecos_options[i]) {
	    case 'n':
		add_string(&strings, name);
		break;
	    case '1':
		add_string(&strings, first);
		break;
	    case '2':
		add_string(&strings, second);
		break;
	    case '3':
		add_string(&strings, third);
		break;
	    case 'a':
		add_string(&strings, name);
		add_string(&strings, first);
		add_string(&strings, second);
		add_string(&strings, third);
		break;
	    default:
		break;
	}
    }

    return;
}

/* Get all groups that a user is a member of. The primary group will be the
 * first added. */
static void groups(const struct passwd *pw, const int multi,
		   const int verbose)
{
    struct group *grp;
    char tmp[255];
    char line[LINE_MAX];
    gid_t primary = -1;

    line[0] = '\0';

    if ((grp = getgrgid(pw->pw_gid)) == NULL) {
	snprintf(tmp, sizeof(tmp), "%li%s%s%s", (long) pw->pw_gid,
		 (verbose) ? "(" : "", (verbose) ? "!" : "",
		 (verbose) ? ")" : "");
	add_string(&strings, tmp);
	return;
    }

    primary = grp->gr_gid;
    snprintf(tmp, sizeof(tmp), "%li%s%s%s%c", (long) pw->pw_gid,
	     (verbose) ? "(" : "", (verbose) ? grp->gr_name : "",
	     (verbose) ? ")" : "", multi);
    strncat(line, tmp, sizeof(line));

#ifdef HAVE_SETGROUPENT
    setgroupent(1);
#else
    setgrent();
#endif

    while ((grp = getgrent()) != NULL) {
	char **members = grp->gr_mem;

	while (*members) {
	    if (strcmp(*members++, pw->pw_name) == 0) {
		if (grp->gr_gid == primary)
		    continue;

		snprintf(tmp, sizeof(tmp), "%li%s%s%s%c", (long) grp->gr_gid,
			 (verbose) ? "(" : "", (verbose) ? grp->gr_name : "",
			 (verbose) ? ")" : "", multi);
		strncat(line, tmp, sizeof(line));
	    }
	}
    }

    /*
     * Trim the remaining multi-string deliminator. 
     */
    line[strlen(line) - 1] = '\0';

    add_string(&strings, line);
    return;
}

/* This is output if the -h command line option is passed to the main program.
 */
void ui_module_help()
{
#ifdef DEBUG
    fprintf(stderr, "%s: ui_module_help()\n", __FILE__);
#endif

    printf("  Password/Group file information [-P (-%s)]:\n",
	   PASSWD_OPTION_ORDER);
    printf("\t-l  login name\t\t");
    printf("\t-p  encrypted password\n");
    printf("\t-u  user id (uid)\t");
    printf("\t-g  group id (gid)\n");
    printf("\t-c  password change time");
    printf("\t-e  password expire time\n");
    printf("\t-d  home directory\t");
    printf("\t-m  home directory mode\n");
    printf("\t-s  login shell\n");
    printf("\t-i  gecos (any of [n]ame,[1]st,[2]nd,[3]rd or [a]ll)\n\n");
    return;
}

/* This is the equivalent to main() only without argc and argv available. */
int ui_module_exec(char ***s, const struct passwd *pw, const int multi_char,
		const int verbose, char *tf)
{
    char *p = options;
    struct stat st;

#ifdef HAVE_GETSPNAM
    struct spwd *spwd = NULL;
#endif

#ifdef DEBUG
    fprintf(stderr, "%s: ui_module_exec()\n", __FILE__);
#endif

#ifdef HAVE_GETSPNAM
    if (amroot) {
	if ((spwd = getspnam(pw->pw_name)) == NULL)
	    warnx("%s", "getspnam(): unknown error");
    }
#endif

    strings = *s;

    while (*p) {
	char tmp[32];

	switch (*p) {
#ifdef HAVE_GETSPNAM
	    case 'c':
		if (!amroot) {
		    add_string(&strings, "!");
		    break;
		}

		snprintf(tmp, sizeof(tmp), "%li", (long) spwd->sp_max);
		add_string(&strings, tmp);
		break;
	    case 'e':
		if (!amroot) {
		    add_string(&strings, "!");
		    break;
		}

		snprintf(tmp, sizeof(tmp), "%li", (long) spwd->sp_expire);
		add_string(&strings, tmp);
		break;
#else
	    case 'c':
		snprintf(tmp, sizeof(tmp), "%li", (long) pw->pw_change);
		add_string(&strings, tmp);
		break;
	    case 'e':
		snprintf(tmp, sizeof(tmp), "%li", (long) pw->pw_expire);
		add_string(&strings, tmp);
		break;
#endif
	    case 'l':
		add_string(&strings, pw->pw_name);
		break;
	    case 'd':
		add_string(&strings, (pw->pw_dir && pw->pw_dir[0]) ? pw->pw_dir : "-");
		break;
	    case 's':
		add_string(&strings, (pw->pw_shell && pw->pw_shell[0]) ? pw->pw_shell : "-");
		break;
	    case 'p':
#ifdef HAVE_GETSPNAM
		if (!amroot)
		    add_string(&strings, (pw->pw_passwd
				&& pw->pw_passwd[0]) ? pw->pw_passwd : "-");
		else
		    add_string(&strings, (spwd->sp_pwdp
				&& spwd->sp_pwdp[0]) ? spwd->sp_pwdp : "-");
#else
		add_string(&strings, (pw->pw_passwd
			    && pw->pw_passwd[0]) ? pw->pw_passwd : "-");
#endif
		break;
	    case 'u':
		sprintf(tmp, "%li", (long) pw->pw_uid);
		add_string(&strings, tmp);
		break;
	    case 'g':
		groups(pw, multi_char, verbose);
		break;
	    case 'm':
		if (stat(pw->pw_dir, &st) == -1) {
		    add_string(&strings, "!");
		    break;
		}

		sprintf(tmp, "%.4o", (unsigned) st.st_mode & ALLPERMS);
		add_string(&strings, tmp);
		break;
	    case 'i':
		gecos_strings(pw->pw_gecos);
		break;
	    default:
		break;
	}

	p++;
    }

    *s = strings;
    return EXIT_SUCCESS;
}

char *ui_module_options_init(char **defaults)
{
    *defaults = "P";
    return PASSWD_OPTION_STRING;
}

/* Check module option validity. */
int ui_module_options(int argc, char **argv)
{
    int opt;
    char *p = options;

#ifdef DEBUG
    fprintf(stderr, "%s: ui_module_options()\n", __FILE__);
#endif

    while ((opt = getopt(argc, argv, PASSWD_OPTION_STRING)) != -1) {
	switch (opt) {
	    case 'i':
		gecos_options = optarg;
		break;
	    case 'P':
	    case 'l':
	    case 'p':
	    case 'u':
	    case 'g':
	    case 'c':
	    case 'e':
	    case 'd':
	    case 's':
	    case 'm':
		break;
	    case '?':
		warnx("passwd: invalid option -- %c", optopt);
	    default:
		return 1;
	}

	if (opt == 'i') {
	    if (parse_gecos_options(gecos_options))
		return 1;
	}

	/*
	 * This option '-P' sets all available options for this module. 
	 */
	if (opt == 'P') {
	    strncpy(options, PASSWD_OPTION_ORDER, sizeof(options));
	    gecos_options = "a";
	    break;
	}

	*p++ = opt;
	*p = '\0';
    }

    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1