/* passwd.c: Changing passwords and managing account information

   Copyright 1999, 2000, 2001, 2002, 2003 Red Hat, Inc.

   Written by Corinna Vinschen <corinna.vinschen@cityweb.de>

This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include <windows.h>
#include <wininet.h>
#include <lmaccess.h>
#include <lmerr.h>
#include <lmcons.h>
#include <lmapibuf.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <pwd.h>
#include <sys/cygwin.h>
#include <sys/types.h>
#include <time.h>

#define USER_PRIV_ADMIN		 2

static const char version[] = "$Revision$";
static char *prog_name;

static struct option longopts[] =
{
  {"cannot-change", no_argument, NULL, 'c'},
  {"can-change", no_argument, NULL, 'C'},
  {"logonserver", required_argument, NULL, 'd'},
  {"never-expires", no_argument, NULL, 'e'},
  {"expires", no_argument, NULL, 'E'},
  {"help", no_argument, NULL, 'h' },
  {"inactive", required_argument, NULL, 'i'},
  {"lock", no_argument, NULL, 'l'},
  {"minage", required_argument, NULL, 'n'},
  {"pwd-not-required", no_argument, NULL, 'p'},
  {"pwd-required", no_argument, NULL, 'P'},
  {"unlock", no_argument, NULL, 'u'},
  {"version", no_argument, NULL, 'v'},
  {"maxage", required_argument, NULL, 'x'},
  {"length", required_argument, NULL, 'L'},
  {"status", no_argument, NULL, 'S'},
  {NULL, 0, NULL, 0}
};

static char opts[] = "cCd:eEhi:ln:pPuvx:L:S";

int
eprint (int with_name, const char *fmt, ...)
{
  va_list ap;

  if (with_name)
    fprintf(stderr, "%s: ", prog_name);
  va_start (ap, fmt);
  vfprintf (stderr, fmt, ap);
  va_end (ap);
  fprintf(stderr, "\n");
  return 1;
}

int
EvalRet (int ret, const char *user)
{
  switch (ret)
    {
    case NERR_Success:
      return 0;

    case ERROR_ACCESS_DENIED:
      if (! user)
        eprint (0, "You may not change password expiry information.");
      else
        eprint (0, "You may not change the password for %s.", user);
      break;

      eprint (0, "Bad password: Invalid.");
      break;

    case NERR_PasswordTooShort:
      eprint (0, "Bad password: Too short.");
      break;

    case NERR_UserNotFound:
      eprint (1, "unknown user %s", user);
      break;

    case ERROR_INVALID_PASSWORD:
    case NERR_BadPassword:
      eprint (0, "Incorrect password for %s.", user);
      eprint (0, "The password for %s is unchanged.", user);
      break;

    default:
      eprint (1, "unrecoverable error %d", ret);
      break;
    }
  return 1;
}

PUSER_INFO_3
GetPW (char *user, int print_win_name, LPCWSTR server)
{
  char usr_buf[UNLEN + 1];
  WCHAR name[2 * (UNLEN + 1)];
  DWORD ret;
  PUSER_INFO_3 ui;
  struct passwd *pw;
  char *domain = (char *) alloca (INTERNET_MAX_HOST_NAME_LENGTH + 1);
     
  /* Try getting a Win32 username in case the user edited /etc/passwd */
  if ((pw = getpwnam (user)))
    {
      cygwin_internal (CW_EXTRACT_DOMAIN_AND_USER, pw, domain, usr_buf);
      if (strcasecmp (pw->pw_name, usr_buf))
	{
	  /* Hack to avoid problem with LookupAccountSid after impersonation */
	  if (strcasecmp (usr_buf, "SYSTEM"))
	    {
	      user = usr_buf;
	      if (print_win_name)
		printf ("Windows username : %s\n", user);
	    }
	}
    }
  MultiByteToWideChar (CP_ACP, 0, user, -1, name, 2 * (UNLEN + 1));
  ret = NetUserGetInfo (server, name, 3, (void *) &ui);
  return EvalRet (ret, user) ? NULL : ui;
}

int
ChangePW (const char *user, const char *oldpwd, const char *pwd, int justcheck,
	  LPCWSTR server)
{
  WCHAR name[2 * (UNLEN + 1)], oldpass[512], pass[512];
  DWORD ret;

  MultiByteToWideChar (CP_ACP, 0, user, -1, name, 2 * (UNLEN + 1));
  MultiByteToWideChar (CP_ACP, 0, pwd, -1, pass, 512);
  if (! oldpwd)
    {
      USER_INFO_1003 ui;

      ui.usri1003_password = pass;
      ret = NetUserSetInfo (server, name, 1003, (LPBYTE) &ui, NULL);
    }
  else
    {
      MultiByteToWideChar (CP_ACP, 0, oldpwd, -1, oldpass, 512);
      ret = NetUserChangePassword (server, name, oldpass, pass);
    }
  if (justcheck && ret != ERROR_INVALID_PASSWORD)
    return 0;
  if (! EvalRet (ret, user) && ! justcheck)
    {
      eprint (0, "Password changed.");
    }
  return ret;
}

void
PrintPW (PUSER_INFO_3 ui, LPCWSTR server)
{
  time_t t = time (NULL) - ui->usri3_password_age;
  int ret;
  PUSER_MODALS_INFO_0 mi;

  printf ("Account disabled           : %s",
  	(ui->usri3_flags & UF_ACCOUNTDISABLE) ? "yes\n" : "no\n");
  printf ("Password not required      : %s",
  	(ui->usri3_flags & UF_PASSWD_NOTREQD) ? "yes\n" : "no\n");
  printf ("User can't change password : %s",
  	(ui->usri3_flags & UF_PASSWD_CANT_CHANGE) ? "yes\n" : "no\n");
  printf ("Password never expires     : %s",
  	(ui->usri3_flags & UF_DONT_EXPIRE_PASSWD) ? "yes\n" : "no\n");
  printf ("Password expired           : %s",
	(ui->usri3_password_expired) ? "yes\n" : "no\n");
  printf ("Latest password change     : %s", ctime(&t));
  ret = NetUserModalsGet (server, 0, (void *) &mi);
  if (! ret)
    {
      if (mi->usrmod0_max_passwd_age == TIMEQ_FOREVER)
        mi->usrmod0_max_passwd_age = 0;
      if (mi->usrmod0_min_passwd_age == TIMEQ_FOREVER)
        mi->usrmod0_min_passwd_age = 0;
      if (mi->usrmod0_force_logoff == TIMEQ_FOREVER)
        mi->usrmod0_force_logoff = 0;
      if (ui->usri3_priv == USER_PRIV_ADMIN)
        mi->usrmod0_min_passwd_len = 0;
      printf ("\nSystem password settings:\n");
      printf ("Max. password age %ld days\n",
              mi->usrmod0_max_passwd_age / ONE_DAY);
      printf ("Min. password age %ld days\n",
              mi->usrmod0_min_passwd_age / ONE_DAY);
      printf ("Force logout after %ld days\n",
              mi->usrmod0_force_logoff / ONE_DAY);
      printf ("Min. password length: %ld\n",
              mi->usrmod0_min_passwd_len);
    }
}

int
SetModals (int xarg, int narg, int iarg, int Larg, LPCWSTR server)
{
  int ret;
  PUSER_MODALS_INFO_0 mi;

  ret = NetUserModalsGet (server, 0, (void *) &mi);
  if (! ret)
    {
      if (xarg == 0)
	mi->usrmod0_max_passwd_age = TIMEQ_FOREVER;
      else if (xarg > 0)
	mi->usrmod0_max_passwd_age = xarg * ONE_DAY;

      if (narg == 0)
	{
	  mi->usrmod0_min_passwd_age = TIMEQ_FOREVER;
	  mi->usrmod0_password_hist_len = 0;
	}
      else if (narg > 0)
	mi->usrmod0_min_passwd_age = narg * ONE_DAY;

      if (iarg == 0)
	mi->usrmod0_force_logoff = TIMEQ_FOREVER;
      else if (iarg > 0)
	mi->usrmod0_force_logoff = iarg * ONE_DAY;

      if (Larg >= 0)
	mi->usrmod0_min_passwd_len = Larg;

      ret = NetUserModalsSet (server, 0, (LPBYTE) mi, NULL);
      NetApiBufferFree (mi);
    }
  return EvalRet (ret, NULL);
}

static void usage (FILE * stream, int status) __attribute__ ((noreturn));
static void
usage (FILE * stream, int status)
{
  fprintf (stream, ""
  "Usage: %s [OPTION] [USER]\n"
  "Change USER's password or password attributes.\n"
  "\n"
  "User operations:\n"
  "  -l, --lock               lock USER's account.\n"
  "  -u, --unlock             unlock USER's account.\n"
  "  -c, --cannot-change      USER can't change password.\n"
  "  -C, --can-change         USER can change password.\n"
  "  -e, --never-expires      USER's password never expires.\n"
  "  -E, --expires            USER's password expires according to system's\n"
  "                           password aging rule.\n"
  "  -p, --pwd-not-required   no password required for USER.\n"
  "  -P, --pwd-required       password is required for USER.\n"
  "\n"
  "System operations:\n"
  "  -i, --inactive NUM       set NUM of days before inactive accounts are disabled\n"
  "                           (inactive accounts are those with expired passwords).\n"
  "  -n, --minage DAYS        set system minimum password age to DAYS days.\n"
  "  -x, --maxage DAYS        set system maximum password age to DAYS days.\n"
  "  -L, --length LEN         set system minimum password length to LEN.\n"
  "\n"
  "Other options:\n"
  "  -d, --logonserver SERVER connect to SERVER (e.g. domain controller).\n"
  "                           default server is the content of $LOGONSERVER.\n"
  "  -S, --status             display password status for USER (locked, expired,\n"
  "                           etc.) plus global system password settings.\n"
  "  -h, --help               output usage information and exit.\n"
  "  -v, --version            output version information and exit.\n"
  "\n"
  "If no option is given, change USER's password.  If no user name is given,\n"
  "operate on current user.  System operations must not be mixed with user\n"
  "operations.  Don't specify a USER when triggering a system operation. \n"
  "\n"
  "Report bugs to <cygwin@cygwin.com>\n", prog_name);
  exit (status);
}

static void
print_version ()
{
  const char *v = strchr (version, ':');
  int len;
  if (!v)
    {
      v = "?";
      len = 1;
    }
  else
    {
      v += 2;
      len = strchr (v, ' ') - v;
    }
  printf ("\
%s (cygwin) %.*s\n\
Password Utility\n\
Copyright 1999, 2000, 2001, 2002, 2003 Red Hat, Inc.\n\
Compiled on %s\n\
", prog_name, len, v, __DATE__);
}

int
main (int argc, char **argv)
{
  char *c;
  char user[64], oldpwd[64], newpwd[64];
  int ret = 0;
  int cnt = 0;
  int opt, len;
  int Larg = -1;
  int xarg = -1;
  int narg = -1;
  int iarg = -1;
  int lopt = 0;
  int uopt = 0;
  int copt = 0;
  int Copt = 0;
  int eopt = 0;
  int Eopt = 0;
  int popt = 0;
  int Popt = 0;
  int Sopt = 0;
  PUSER_INFO_3 ui, li;
  LPWSTR server = NULL;

  prog_name = strrchr (argv[0], '/');
  if (prog_name == NULL)
    prog_name = strrchr (argv[0], '\\');
  if (prog_name == NULL)
    prog_name = argv[0];
  else
    prog_name++;
  c = strrchr (prog_name, '.');
  if (c)
    *c = '\0';

  while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != EOF)
    switch (opt)
      {
      case 'h':
	usage (stdout, 0);
        break;

      case 'i':
	if (lopt || uopt || copt || Copt || eopt || Eopt || popt || Popt || Sopt)
	  usage (stderr, 1);
	if ((iarg = atoi (optarg)) < 0 || iarg > 999)
	  return eprint (1, "Force logout time must be between 0 and 999.");
        break;

      case 'l':
	if (xarg >= 0 || narg >= 0 || iarg >= 0 || Larg >= 0 || uopt || Sopt)
	  usage (stderr, 1);
	lopt = 1;
        break;

      case 'n':
	if (lopt || uopt || copt || Copt || eopt || Eopt || popt || Popt || Sopt)
	  usage (stderr, 1);
	if ((narg = atoi (optarg)) < 0 || narg > 999)
	  return eprint (1, "Minimum password age must be between 0 and 999.");
	if (xarg >= 0 && narg > xarg)
	  return eprint (1, "Minimum password age must be less than "
	                    "maximum password age.");
        break;

      case 'u':
	if (xarg >= 0 || narg >= 0 || iarg >= 0 || Larg >= 0 || lopt || Sopt)
	  usage (stderr, 1);
	uopt = 1;
        break;

      case 'c':
	if (xarg >= 0 || narg >= 0 || iarg >= 0 || Larg >= 0 || Sopt)
	  usage (stderr, 1);
	copt = 1;
        break;

      case 'C':
	if (xarg >= 0 || narg >= 0 || iarg >= 0 || Larg >= 0 || Sopt)
	  usage (stderr, 1);
	Copt = 1;
        break;

      case 'd':
        {
	  char *tmpbuf = alloca (strlen (optarg) + 3);
	  tmpbuf[0] = '\0';
	  if (*optarg != '\\')
	    strcpy (tmpbuf, "\\\\");
	  strcat (tmpbuf, optarg);
	  server = alloca ((strlen (tmpbuf) + 1) * sizeof (WCHAR));
	  if (MultiByteToWideChar (CP_ACP, 0, tmpbuf, -1, server,
				   strlen (tmpbuf) + 1) <= 0)
	    server = NULL;
	}
	break;

      case 'e':
	if (xarg >= 0 || narg >= 0 || iarg >= 0 || Larg >= 0 || Sopt)
	  usage (stderr, 1);
	eopt = 1;
        break;

      case 'E':
	if (xarg >= 0 || narg >= 0 || iarg >= 0 || Larg >= 0 || Sopt)
	  usage (stderr, 1);
	Eopt = 1;
        break;

      case 'p':
	if (xarg >= 0 || narg >= 0 || iarg >= 0 || Larg >= 0 || Sopt)
	  usage (stderr, 1);
	popt = 1;
        break;

      case 'P':
	if (xarg >= 0 || narg >= 0 || iarg >= 0 || Larg >= 0 || Sopt)
	  usage (stderr, 1);
	Popt = 1;
        break;

      case 'v':
	print_version ();
        exit (0);
        break;

      case 'x':
	if (lopt || uopt || copt || Copt || eopt || Eopt || popt || Popt || Sopt)
	  usage (stderr, 1);
	if ((xarg = atoi (optarg)) < 0 || xarg > 999)
	  return eprint (1, "Maximum password age must be between 0 and 999.");
	if (narg >= 0 && xarg < narg)
	  return eprint (1, "Maximum password age must be greater than "
	                    "minimum password age.");
        break;

      case 'L':
	if (lopt || uopt || copt || Copt || eopt || Eopt || popt || Popt || Sopt)
	  usage (stderr, 1);
	if ((Larg = atoi (optarg)) < 0 || Larg > LM20_PWLEN)
	  return eprint (1, "Minimum password length must be between "
	                    "0 and %d.", LM20_PWLEN);
        break;

      case 'S':
	if (xarg >= 0 || narg >= 0 || iarg >= 0 || Larg >= 0 || lopt || uopt
	    || copt || Copt || eopt || Eopt || popt || Popt)
	  usage (stderr, 1);
	Sopt = 1;
        break;

      default:
        usage (stderr, 1);
      }

  if (!server)
    {
      len = GetEnvironmentVariableW (L"LOGONSERVER", NULL, 0);
      if (len > 0)
	{
	  server = alloca (len * sizeof (WCHAR));
	  if (GetEnvironmentVariableW (L"LOGONSERVER", server, len) <= 0)
	    server = NULL;
	}
    }

  if (Larg >= 0 || xarg >= 0 || narg >= 0 || iarg >= 0)
    {
      if (optind < argc)
        usage (stderr, 1);
      return SetModals (xarg, narg, iarg, Larg, server);
    }

  strcpy (user, optind >= argc ? getlogin () : argv[optind]);

  li = GetPW (getlogin (), 0, server);
  if (! li)
    return 1;

  ui = GetPW (user, 1, server);
  if (! ui)
    return 1;

  if (lopt || uopt || copt || Copt || eopt || Eopt || popt || Popt || Sopt)
    {
      USER_INFO_1008 uif;

      if (li->usri3_priv != USER_PRIV_ADMIN)
        return eprint (0, "You have no maintenance privileges.");
      uif.usri1008_flags = ui->usri3_flags;
      if (lopt)
        {
	  if (ui->usri3_priv == USER_PRIV_ADMIN)
	    return eprint (0, "Locking an admin account is disallowed.");
          uif.usri1008_flags |= UF_ACCOUNTDISABLE;
        }
      if (uopt)
        uif.usri1008_flags &= ~UF_ACCOUNTDISABLE;
      if (copt)
        uif.usri1008_flags |= UF_PASSWD_CANT_CHANGE;
      if (Copt)
        uif.usri1008_flags &= ~UF_PASSWD_CANT_CHANGE;
      if (eopt)
        uif.usri1008_flags |= UF_DONT_EXPIRE_PASSWD;
      if (Eopt)
        uif.usri1008_flags &= ~UF_DONT_EXPIRE_PASSWD;
      if (popt)
        uif.usri1008_flags |= UF_PASSWD_NOTREQD;
      if (Popt)
        uif.usri1008_flags &= ~UF_PASSWD_NOTREQD;

      if (lopt || uopt || copt || Copt || eopt || Eopt || popt || Popt)
	{
          ret = NetUserSetInfo (server, ui->usri3_name, 1008, (LPBYTE) &uif,
	  			NULL);
          return EvalRet (ret, NULL);
	}
      // Sopt
      PrintPW (ui, server);
      return 0;
    }

  if (li->usri3_priv != USER_PRIV_ADMIN && strcmp (getlogin (), user))
    return eprint (0, "You may not change the password for %s.", user);

  eprint (0, "Enter the new password (minimum of 5, maximum of 8 characters).");
  eprint (0, "Please use a combination of upper and lower case letters and numbers.");

  oldpwd[0] = '\0';
  if (li->usri3_priv != USER_PRIV_ADMIN)
    {
      strcpy (oldpwd, getpass ("Old password: "));
      if (ChangePW (user, oldpwd, oldpwd, 1, server))
        return 1;
    }

  do
    {
      strcpy (newpwd, getpass ("New password: "));
      if (strcmp (newpwd, getpass ("Re-enter new password: ")))
        eprint (0, "Password is not identical.");
      else if (! ChangePW (user, *oldpwd ? oldpwd : NULL, newpwd, 0, server))
        ret = 1;
      if (! ret && cnt < 2)
        eprint (0, "Try again.");
    }
  while (! ret && ++cnt < 3);
  return ! ret;
}