/* 
 * $Id: passwd.c,v 1.15 1999/02/18 12:29:43 saw Rel $
 *
 * passwd.c
 *
 * Written for Linux-PAM by Andrew G. Morgan <morgan@linux.kernel.org>
 *
 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

#include <security/pam_appl.h>
#include <security/pam_misc.h>

#include "../../common/include/config.h"
#include "../../common/include/passwd_indep.h"
#include "../../common/include/checkfds.h"

#ifdef HAVE_PWDB
#include <pwdb/pwdb_public.h>
#else
#include <pwd.h>
#endif

/* ------ some static data objects ------- */

static struct pam_conv conv = {
    misc_conv,
    NULL
};

#define PASSWD_MAX_TOKEN                100
#define PASSWD_COMMENT_CHAR             '#'
#define PASSWD_SUFFIX_FILE              "/etc/security/passwd.conf"
#define PASSWD_SERVICE_FORMAT           "passwd%s"
#define PASSWD_SERVICE_DEFAULT          "passwd"
#define PASSWD_FAIL_DELAY               2000000 /* usec delay on failure */
#define PASSWD_KEEP_UPTODATE            01
#define PASSWD_SERVICE_SUFFIX           02
#define PASSWD_GARBLED                  04

#define PASSWD_TRUE                     1
#define PASSWD_FALSE                    0

/* ------- the application itself -------- */

void main(int argc, const char **argv)
{
    const char *service, *user, *suffix;
    int passwd_flags, retval;
    pam_handle_t *pamh=NULL;

    checkfds();

    /* obtain user's specific request */

    passwd_flags = parse_pass_args(argc, argv, &suffix, &user);

    /* obtain the correct name for the service - check suffix is in list */

    if (suffix == NULL || !(passwd_flags & PASSWD_SERVICE_SUFFIX) ) {
	service = PASSWD_SERVICE_DEFAULT;
    } else if (service_ok(suffix)) {
	char *tmp;

	tmp = malloc(sizeof(PASSWD_SERVICE_FORMAT) + strlen(suffix));
	if (tmp == NULL) {
	    fprintf(stderr, "passwd: no memory for service name\n");
	    exit(1);
	}
	sprintf(tmp, PASSWD_SERVICE_FORMAT, suffix);
	service = tmp;
    } else {
	fprintf(stderr, "passwd: invalid scheme-suffix \"%s\"\n", suffix);
	exit(1);
    }

    if (user == NULL) {
#ifndef HAVE_PWDB
	struct passwd *pwent;
#endif /* HAVE_PWDB */
	if ((user = getlogin()) == NULL) {
	    fprintf(stderr, "passwd: cannot retrieve user's name\n");
	    exit(1);
	}
#ifndef HAVE_PWDB
	/* attempt to patch over libc's inability to handle longer that
	   fixed length login names from the utmp file */

	if ((pwent = getpwnam(user)) == NULL
	    || (pwent = getpwuid(getuid())) == NULL
	    || !(pwent->pw_name
		 && !strncmp(pwent->pw_name, user, strlen(user)))) {
	    fprintf(stderr, "passwd: cannot retrieve user's name\n");
	    exit(1);
	} else {
	    user = pwent->pw_name;
	}
#endif /* HAVE_PWDB */
    }

    /* here we know whose passwords are to be changed and whether
       we'll change everything or just the expired ones */

    D(("service=%s, user=%s\n", service, user));
    retval = pam_start(service, user, &conv, &pamh);
    user = NULL;                         /* can no longer rely on this */

#ifdef HAVE_PAM_FAIL_DELAY
    /* have to pause on failure. At least this long (doubles..) */
    retval = pam_fail_delay(pamh, PASSWD_FAIL_DELAY);
    if (retval != PAM_SUCCESS) {
	fprintf(stderr, "passwd: unable to set failure delay\n");
	exit(1);
    }
#endif /* HAVE_PAM_FAIL_DELAY */

    while (retval == PAM_SUCCESS) {      /* use loop to avoid goto... */

	/* the user is authenticated by the passwd module; change
	   the password(s) too. */

	retval = pam_chauthtok(pamh, (passwd_flags & PASSWD_KEEP_UPTODATE)
			       ? PAM_CHANGE_EXPIRED_AUTHTOK : 0 );
	if (retval != PAM_SUCCESS)
	    break;

	/* all done */

	retval = pam_end(pamh, PAM_SUCCESS);
	if (retval != PAM_SUCCESS)
	    break;

	/* quit gracefully */

	fprintf(stderr,
		"passwd: %s authentication tokens updated successfully\n"
		, (passwd_flags & PASSWD_KEEP_UPTODATE) ? "expired":"all" );

	exit(0);
    }

    if (pamh != NULL) {
	(void) pam_end(pamh,PAM_SUCCESS);
	pamh = NULL;
    }

    if (retval != PAM_SUCCESS)
	fprintf(stderr, "passwd: %s\n", pam_strerror(pamh, retval));

    exit(1);
}
