/*
 * Copyright (C) 2000-2002, 2004 Red Hat, Inc.
 *
 * This is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library 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 Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>
#include <sys/time.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libintl.h>
#include <locale.h>
#include <popt.h>
#include <glib.h>
#include "../lib/user.h"
#include "../lib/user_private.h"
#include "apputil.h"

#define INVALID_LONG LONG_MIN

/* Return the first G_TYPE_LONG value of ATTR if ENT, if exists, or -1 if the
   attribute is missing or invalid. */
static glong
read_ndays(struct lu_ent *ent, const char *attr)
{
	GValueArray *values;
	GValue *value;

	values = lu_ent_get(ent, attr);
	if (values == NULL)
		return -1;

	value = g_value_array_get_nth(values, 0);
	g_assert(G_VALUE_HOLDS_LONG(value));
	return g_value_get_long(value);
}

/* Format a count of days into a string that's intelligible to a user. */
static void
date_to_string(glong n_days, char *buf, size_t len)
{
	if ((n_days >= 0) && (n_days < 99999)) {
		GDate *date;

		date = g_date_new_dmy(1, G_DATE_JANUARY, 1970);
		g_date_add_days(date, n_days);
		g_date_strftime(buf, len, "%x", date);
		g_date_free(date);
	}
}

int
main(int argc, const char **argv)
{
	long shadowMin = INVALID_LONG, shadowMax = INVALID_LONG,
	     shadowLastChange = INVALID_LONG, shadowInactive = INVALID_LONG,
	     shadowExpire = INVALID_LONG, shadowWarning = INVALID_LONG;
	const char *user = NULL;
	struct lu_context *ctx = NULL;
	struct lu_ent *ent = NULL;
	struct lu_error *error = NULL;
	int interactive = FALSE;
	int list_only = FALSE;
	int c;
	int result;

	poptContext popt;
	struct poptOption options[] = {
		{"interactive", 'i', POPT_ARG_NONE, &interactive, 0,
		 N_("prompt for all information"), NULL},
		{"list", 'l', POPT_ARG_NONE, &list_only, 0,
		 N_("list aging parameters for the user"), NULL},
		{"mindays", 'm', POPT_ARG_LONG, &shadowMin, 0,
		 N_("minimum days between password changes"), N_("DAYS")},
		{"maxdays", 'M', POPT_ARG_LONG, &shadowMax, 0,
		 N_("maximum days between password changes"), N_("DAYS")},
		{"date", 'd', POPT_ARG_LONG, &shadowLastChange, 0,
		 N_("date of last password change in days since 1/1/70"),
		 N_("DAYS")},
		{"inactive", 'I', POPT_ARG_LONG, &shadowInactive, 0,
		 N_("number of days after password expiration date when "
		    "account is considered inactive"), N_("DAYS")},
		{"expire", 'E', POPT_ARG_LONG, &shadowExpire, 0,
		 N_("password expiration date in days since 1/1/70"),
		 N_("DAYS")},
		{"warndays", 'W', POPT_ARG_LONG, &shadowWarning, 0,
		 N_("days before expiration to begin warning user"),
		 N_("DAYS")},
		POPT_AUTOHELP
		POPT_TABLEEND
	};

	/* Set up i18n. */
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	setlocale(LC_ALL, "");

	/* Parse arguments. */
	popt = poptGetContext("lchage", argc, argv, options, 0);
	poptSetOtherOptionHelp(popt, _("[OPTION...] user"));
	c = poptGetNextOpt(popt);
	if (c != -1) {
		fprintf(stderr, _("Error parsing arguments: %s.\n"),
			poptStrerror(c));
		poptPrintUsage(popt, stderr, 0);
		result = 1;
		goto done;
	}
	user = poptGetArg(popt);

	/* We need exactly one argument, and that's the user's name. */
	if (user == NULL) {
		fprintf(stderr, _("No user name specified.\n"));
		poptPrintUsage(popt, stderr, 0);
		result = 1;
		goto done;
	}

	/* Start up the library. */
	ctx = lu_start(user, lu_user, NULL, NULL,
		       interactive ? lu_prompt_console :
		       lu_prompt_console_quiet, NULL, &error);
	if (ctx == NULL) {
		fprintf(stderr, _("Error initializing %s: %s.\n"), PACKAGE,
			lu_strerror(error));
		result = 1;
		goto done;
	}

	ent = lu_ent_new();

	/* Look up information about the user. */
	if (lu_user_lookup_name(ctx, user, ent, &error) == FALSE) {
		fprintf(stderr, _("User %s does not exist.\n"), user);
		result = 2;
		goto done;
	}

	if (list_only) {
		char buf[LINE_MAX];

		/* Just print out what we can find out, in a format similar
		   to the chage(1) utility from the shadow suite.

		   To make it a little easier to understand, convert "-1" to
		   "0" in cases where the two have the same meaning. */
		if (lu_user_islocked(ctx, ent, &error)) {
			printf(_("Account is locked.\n"));
		} else {
			printf(_("Account is not locked.\n"));
		}

		shadowMin = read_ndays(ent, LU_SHADOWMIN);
		printf(_("Minimum:\t%ld\n"), shadowMin != -1 ? shadowMin : 0);

		shadowMax = read_ndays(ent, LU_SHADOWMAX);
		if (shadowMax != -1)
			printf(_("Maximum:\t%ld\n"), shadowMax);
		else
			printf(_("Maximum:\tNone\n"));

		shadowWarning = read_ndays(ent, LU_SHADOWWARNING);
		printf(_("Warning:\t%ld\n"),
		       shadowWarning != -1 ? shadowWarning : 0);

		shadowInactive = read_ndays(ent, LU_SHADOWINACTIVE);
		if (shadowInactive != -1)
			printf(_("Inactive:\t%ld\n"), shadowInactive);
		else
			printf(_("Inactive:\tNever\n"));

		shadowLastChange = read_ndays(ent, LU_SHADOWLASTCHANGE);
		if (shadowLastChange == 0)
			strcpy(buf, _("Must change password on next login"));
		else {
			strcpy(buf, _("Never"));
			date_to_string(shadowLastChange, buf, sizeof(buf));
		}
		printf(_("Last Change:\t%s\n"), buf);

		if (shadowLastChange == 0)
			strcpy(buf, _("Must change password on next login"));
		else {
			strcpy(buf, _("Never"));
			if (shadowLastChange != -1 && shadowMax != -1)
				date_to_string(shadowLastChange + shadowMax,
					       buf, sizeof(buf));
		}
		printf(_("Password Expires:\t%s\n"), buf);

		if (shadowLastChange == 0)
			strcpy(buf, _("Must change password on next login"));
		else {
			strcpy(buf, _("Never"));
			if (shadowLastChange != -1 && shadowMax != -1
			    && shadowInactive != -1)
				date_to_string(shadowLastChange + shadowMax
					       + shadowInactive, buf,
					       sizeof(buf));
		}
		printf(_("Password Inactive:\t%s\n"), buf);

		strcpy(buf, _("Never"));
		shadowExpire = read_ndays(ent, LU_SHADOWEXPIRE);
		if (shadowExpire != -1)
			date_to_string(shadowExpire, buf, sizeof(buf));
		printf(_("Account Expires:\t%s\n"), buf);
	} else {
		/* Set values using parameters given on the command-line. */
		if (shadowLastChange != INVALID_LONG)
			lu_ent_set_long(ent, LU_SHADOWLASTCHANGE,
					shadowLastChange);
		if (shadowMin != INVALID_LONG)
			lu_ent_set_long(ent, LU_SHADOWMIN, shadowMin);
		if (shadowMax != INVALID_LONG)
			lu_ent_set_long(ent, LU_SHADOWMAX, shadowMax);
		if (shadowWarning != INVALID_LONG)
			lu_ent_set_long(ent, LU_SHADOWWARNING, shadowWarning);
		if (shadowInactive != INVALID_LONG)
			lu_ent_set_long(ent, LU_SHADOWINACTIVE, shadowInactive);
		if (shadowExpire != INVALID_LONG)
			lu_ent_set_long(ent, LU_SHADOWEXPIRE, shadowExpire);

		/* Now actually modify the user's data in the system
		 * information store. */
		if (lu_user_modify(ctx, ent, &error) == FALSE) {
			fprintf(stderr,
				_("Failed to modify aging information for %s: "
				  "%s\n"), user, lu_strerror(error));
			lu_audit_logger(AUDIT_USER_MGMT, "change-age", user,
				AUDIT_NO_ID, 0);
			result = 3;
			goto done;
		}
		lu_audit_logger(AUDIT_USER_MGMT, "change-age", user,
				AUDIT_NO_ID, 1);

		lu_nscd_flush_cache(LU_NSCD_CACHE_PASSWD);
	}

	result = 0;

 done:
	if (ent) lu_ent_free(ent);

	if (ctx) lu_end(ctx);

	poptFreeContext(popt);

	return result;
}
