/*************************************************** */
/* Rule Set Based Access Control                     */
/*                                                   */
/* Author and (c) 1999-2019: Amon Ott <ao@rsbac.org> */
/*                                                   */
/* Last modified: 11/Dec/2019                        */
/*************************************************** */

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <rsbac/types.h>
#include <rsbac/syscalls.h>
#include <rsbac/error.h>
#include "nls.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

int main(int argc, char ** argv)
{
  int res = 0;
  char * progname;
  rsbac_um_set_t vset = RSBAC_UM_VIRTUAL_KEEP;
  rsbac_uid_t user = RSBAC_GEN_UID(vset, RSBAC_NO_USER);
  int verbose = 0;
  int noold = 0;
  int onetime = 0;
  int removeonetime = 0;
  int countonetime = 0;
  rsbac_time_t ttl = 0;
  char * old_pass;
  char * new_pass;
  char * new_pass2;
  char * hash_algo = NULL;
  struct termios old_term;
  struct termios tmp_term;
  u_int stopflags = FALSE;

  locale_init();
  progname = argv[0];

  while((argc > 1) && (argv[1][0] == '-') && !stopflags)
    {
      char * pos = argv[1];
      pos++;
      while(*pos)
        {
          switch(*pos)
            {
              case '-':
                stopflags = TRUE;
                break;
              case 'v':
                verbose++;
                break;

              case 'n':
                noold = 1;
                break;

              case 'o':
                onetime = 1;
                break;

              case 'O':
                removeonetime = 1;
                break;

              case 'C':
                countonetime = 1;
                break;

              case 'h':
                printf(gettext("%s (RSBAC %s)\n***\n"), argv[0], VERSION);
                printf(gettext("Use: %s [flags] [username]\n"), progname);
                printf(gettext(" -h = this help, -- = no more flags,\n"));
                printf(gettext(" -v = verbose,\n"));
                printf(gettext(" -n = do not ask for old password\n"));
                printf(gettext(" -S n = virtual user set n\n"));
                printf(gettext(" -A hash-algo = hash algorithm to use, e.g. sha256 (default: use kernel default),\n"));
                printf(gettext(" -o = add a onetime password, do not change main password\n"));
                printf(gettext(" -O = remove all onetime passwords, do not change main password\n"));
                printf(gettext(" -C = count onetime passwords\n"));
                printf(gettext(" -t = set relative time-to-live of one-time password in secs\n"));
                printf(gettext(" -T = set absolute time-to-live of one-time password in secs\n"));
                printf(gettext(" -D = set relative time-to-live of one-time password in days\n"));
                exit(0);

              case 't':
                if(argc > 2)
                  {
                    res = strtokmgu32(argv[2], &ttl);
                    error_exit(res);
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing ttl value for parameter %c\n"), progname, *pos);
                break;
              case 'D':
                if(argc > 2)
                  {
                    ttl = 86400 * strtoul(argv[2], 0, 10);
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing ttl value for parameter %c\n"), progname, *pos);
                break;
              case 'T':
                if(argc > 2)
                  {
                    rsbac_time_t now = time(NULL);
                    ttl = strtoul(argv[2], 0, 10);
                    if(ttl > now)
                      {
                        ttl -= now;
                        argc--;
                        argv++;
                      }
                    else
                      {
                        fprintf(stderr,
                                gettext("%s: ttl value for parameter %c is in the past, exiting\n"), progname, *pos);
                        exit(1);
                      }
                  }
                else
                  fprintf(stderr, gettext("%s: missing ttl value for parameter %c\n"), progname, *pos);
                break;

              case 'S':
                if(argc > 2)
                  {
                    if (rsbac_get_vset_num(argv[2], &vset))
                      {
                        fprintf(stderr, gettext("%s: invalid virtual set number for parameter %c\n"), progname, *pos);
                        exit(1);
                      }
                    user = RSBAC_GEN_UID(vset, user);
                    argc--;
                    argv++;
                  }
                else
                  {
                    fprintf(stderr, gettext("%s: missing virtual set number for parameter %c\n"), progname, *pos);
                    exit(1);
                  }
                break;

              case 'A':
                if(argc > 2)
                  {
                    hash_algo = argv[2];
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing value for parameter %c\n"), progname, *pos);
                break;
              default:
                fprintf(stderr, gettext("%s: unknown parameter %c\n"), progname, *pos);
                exit(1);
            }
          pos++;
        }
      argv++;
      argc--;
    }

  if (argc > 1)
    {
      if(rsbac_um_get_uid(0, argv[1], &user))
        {
              char * tmp_name = argv[1];
              char * p = tmp_name;
              rsbac_um_set_t tmp_vset = vset;

              while (*p && (*p != '/'))
                p++;
              if (*p) {
                    *p = 0;
                    if (rsbac_get_vset_num(tmp_name, &tmp_vset))
                      {
                        fprintf(stderr, gettext("%s: invalid virtual set number %s\n"), progname, tmp_name);
                        exit(1);
                      }
                    tmp_vset = strtoul(tmp_name, NULL, 0);
                    *p = '/';
                    p++;
                    tmp_name = p;
              }
              for (p = tmp_name; *p; p++)
                {
                  if ((*p < '0') || (*p > '9'))
                    {
                      fprintf(stderr, gettext("%s: Unknown user %s\n"), progname, argv[1]);
                      return 1;
                    }
                }
              user = strtoul(tmp_name, NULL, 0);
              user = RSBAC_GEN_UID(tmp_vset, user);
        }
    }
  else
    user = RSBAC_GEN_UID(vset, getuid());

  if(isatty(STDIN_FILENO))
    {
      res = tcgetattr(STDIN_FILENO, &old_term);
      error_exit(res);
      memcpy(&tmp_term, &old_term, sizeof(old_term));
      tmp_term.c_lflag &= ~(ECHO);
    }
  if(noold)
    old_pass = NULL;
  else
    {
     old_pass = malloc(RSBAC_MAXNAMELEN);
     res = mlock(old_pass, RSBAC_MAXNAMELEN);
     if (res) {
	     fprintf(stderr, gettext("Unable to lock old password into physical memory, continue anyway!\n"));
     }
     if(isatty(STDIN_FILENO))
        {
          res = tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmp_term);
          error_exit(res);
        }
      if(argc > 1) {
        if (RSBAC_UID_SET(user))
          printf("Old RSBAC password for user %s (uid %u/%u): ",
                 argv[1], RSBAC_UID_SET(user), RSBAC_UID_NUM(user));
        else
          printf("Old RSBAC password for user %s (uid %u): ",
                 argv[1], RSBAC_UID_NUM(user));
      } else {
        if (RSBAC_UID_SET(user) != RSBAC_UM_VIRTUAL_KEEP)
          printf("Old RSBAC password for user %u/%u: ",
                 RSBAC_UID_SET(user), RSBAC_UID_NUM(user));
        else
          printf("Old RSBAC password for user %u: ", RSBAC_UID_NUM(user));
      }
      res = scanf("%254s", old_pass);
      if(isatty(STDIN_FILENO))
        tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_term);
      printf("\n");
      if(res <= 0)
        {
          fprintf(stderr, gettext("%s: invalid old password!\n"), progname);
          exit(1);
        }
    }
  if(countonetime)
    {
      res = rsbac_um_count_onetime(user, old_pass);
      error_exit(res);
      printf("%u one-time passwords\n", res);
      if (!onetime && !removeonetime)
        exit(0);
    }
  if(removeonetime)
    {
      if(verbose)
        printf("Removing all one-time passwords\n");
      error_exit(rsbac_um_remove_all_onetime(user, old_pass));
      if (!onetime)
        exit(0);
    }
  new_pass = malloc(RSBAC_MAXNAMELEN);
  res = mlock(new_pass, RSBAC_MAXNAMELEN);
  if (res) {
	  fprintf(stderr, gettext("Unable to lock new password into physical memory, continue anyway!\n"));
  }
  if(onetime)
    printf("Add one-time password: ");
  if(noold)
    {
      if(argc > 1) {
        if (RSBAC_UID_SET(user) != RSBAC_UM_VIRTUAL_KEEP)
          printf("New RSBAC password for user %s (uid %u/%u): ",
                 argv[1], RSBAC_UID_SET(user), RSBAC_UID_NUM(user));
        else
          printf("New RSBAC password for user %s (uid %u): ",
                 argv[1], RSBAC_UID_NUM(user));
      } else {
        if (RSBAC_UID_SET(user) != RSBAC_UM_VIRTUAL_KEEP)
          printf("New RSBAC password for user %u/%u: ",
                 RSBAC_UID_SET(user), RSBAC_UID_NUM(user));
        else
          printf("New RSBAC password for user %u: ", RSBAC_UID_NUM(user));
      }
    }
  else
    printf("New password: ");
  if(isatty(STDIN_FILENO))
    {
      res = tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmp_term);
      error_exit(res);
    }
  res = scanf("%254s", new_pass);
  if(isatty(STDIN_FILENO))
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_term);
  printf("\n");
  if(res <= 0)
    {
      fprintf(stderr, gettext("%s: invalid new password!\n"), progname);
      exit(1);
    }
  new_pass2 = malloc(RSBAC_MAXNAMELEN);
  res = mlock(new_pass2, RSBAC_MAXNAMELEN);
  if (res) {
	  fprintf(stderr, gettext("Unable to lock retyped new password into physical memory, continue anyway!\n"));
  }
  printf("Repeat new password: ");
  if(isatty(STDIN_FILENO))
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmp_term);
  res = scanf("%254s", new_pass2);
  if(isatty(STDIN_FILENO))
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_term);
  printf("\n");
  if(res <= 0)
    {
      fprintf(stderr, gettext("%s: invalid repeated new password!\n"), progname);
      exit(1);
    }
  if(strcmp(new_pass, new_pass2))
    {
      fprintf(stderr, gettext("%s: new passwords do not match!\n"), progname);
      exit(1);
    }

  if(onetime) {
    if (hash_algo)
      res = rsbac_um_add_onetime_hash(user, old_pass, new_pass, hash_algo, ttl);
    else
      res = rsbac_um_add_onetime(user, old_pass, new_pass, ttl);
  } else {
    if (hash_algo)
      res = rsbac_um_set_pass_hash(user, old_pass, new_pass, hash_algo);
    else
      res = rsbac_um_set_pass(user, old_pass, new_pass);
  }
  error_exit(res);
  exit(0);
}
