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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/mman.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

char * progname;
char password[RSBAC_MAXNAMELEN] = "";
rsbac_um_set_t vset = RSBAC_UM_VIRTUAL_KEEP;

#define ROOM 20

void use(void)
    {
      printf(gettext("%s (RSBAC %s)\n***\n"), progname, VERSION);
      printf(gettext("Use: %s [flags] username\n"), progname);
      printf(gettext(" -h = this help, -- = no more flags,\n"));
      printf(gettext(" -c comment = fullname or comment,\n"));
      printf(gettext(" -d dir = homedir of user,\n"));
      printf(gettext(" -g group = main / initial Linux group,\n"));
      printf(gettext(" -G group1[,group2,...] = add more Linux groups,\n"));
      printf(gettext(" -H group1[,group2,...] = remove Linux groups,\n"));
      printf(gettext(" -I group1[,group2,...] = set Linux groups,\n"));
      printf(gettext(" -p password = password in plaintext,\n"));
      printf(gettext(" -P = disable password,\n"));
      printf(gettext(" -Q password = encrypted password (from backup),\n"));
      printf(gettext(" -s shell = user shell,\n"));
      printf(gettext(" -u name = change username,\n"));
      printf(gettext(" -i val = number of passwords to keep in history (0-255),\n"));
      printf(gettext(" -n minchange-days = minimum days between password changes,\n"));
      printf(gettext(" -x maxchange-days = maximum days between password changes,\n"));
      printf(gettext(" -w warnchange-days = warning days before password must be changed,\n"));
      printf(gettext(" -f inactive-days = period between password expiry and account disabling,\n"));
      printf(gettext(" -e expire-days = days since 1/Jan/1970 when account gets disabled,\n"));
      printf(gettext(" -z = do not also try numeric username as uid\n"));
      printf(gettext(" -t = set relative time-to-live in secs (role/type comp, admin, assign only)\n"));
      printf(gettext(" -T = set absolute time-to-live in secs (role/type comp, admin, assign only)\n"));
      printf(gettext(" -D = set relative time-to-live in days (role/type comp, admin, assign only)\n"));
      printf(gettext(" -S n = virtual user set n\n"));
      printf(gettext(" -N ta = transaction number (default = value of RSBAC_TA, if set, or 0)\n"));
    }

int password_read(char * to, char * from)
  {
    char * f = from;
    char * t = to;
    char   tmp[3];
    int i;

    if(strlen(from) != RSBAC_UM_PASS_LEN * 2)
      {
        fprintf(stderr, "Wrong encrypted password length!\n");
        return -RSBAC_EINVALIDVALUE;
      }
    tmp[2] = 0;
    while(f[0] && f[1])
      {
        tmp[0] = f[0];
        tmp[1] = f[1];
        i = strtoul(tmp, 0, 16);
        if(i < 0 || i > 255)
          return -RSBAC_EINVALIDVALUE;
        *t = i;
        t++;
        f += 2;
      }
    return 0;
  }

void mod_show_error(int res, char * item)
  {
    if(res < 0)
      {
        char tmp1[80];

        fprintf(stderr, "%s: %s\n",
                item,
                get_error_name(tmp1,res));
      }
  }

int main(int argc, char ** argv)
{
  int res = 0;
  rsbac_uid_t user;
  int verbose = 0;
  int err;
  union rsbac_um_mod_data_t data;
  char * full = NULL;
  char * dir = NULL;
  int do_pass = 0;
  char * pass = NULL;
  char * crypt_pass = NULL;
  char * shell = NULL;
  char * name = NULL;
  char * moregroups = NULL;
  char * lessgroups = NULL;
  char * setgroups = NULL;
  int do_group = 0;
  rsbac_gid_t group = 0;
  int do_last = 0;
  rsbac_um_days_t last = 0;
  int do_min = 0;
  rsbac_um_days_t min = 0;
  int do_max = 0;
  rsbac_um_days_t max = 0;
  int do_warn = 0;
  rsbac_um_days_t warn = 0;
  int do_inactive = 0;
  rsbac_um_days_t inactive = 0;
  int do_expire = 0;
  rsbac_um_days_t expire = 0;
  int do_ttl = 0;
  rsbac_time_t ttl = 0;
  int do_max_history = 0;
  __u8 max_history = 0;
  rsbac_list_ta_number_t ta_number = 0;
  u_int stopflags = FALSE;
  int nonumeric = 0;
  int i;

  locale_init();

  progname = argv[0];
  {
    char * env = getenv("RSBAC_TA");

    if(env)
      ta_number = strtoul(env,0,0);
  }
  while((argc > 1) && (argv[1][0] == '-') && !stopflags)
    {
      char * pos = argv[1];
      pos++;
      while(*pos)
        {
          switch(*pos)
            {
              case '-':
                stopflags = TRUE;
                break;
              case 'h':
                use();
                return 0;
              case 'v':
                verbose++;
                break;
              case 'c':
                if(argc > 2)
                  {
                    full=argv[2];
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'd':
                if(argc > 2)
                  {
                    dir=argv[2];
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'p':
                if(argc > 2)
                  {
		    pass=argv[2];
                    do_pass = 1;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'P':
                 pass = NULL;
                 do_pass = 1;
                 break;
              case 'z':
                 nonumeric = 1;
                 break;
              case 'Q':
                if(argc > 2)
                  {
                    err = mlock(password, RSBAC_MAXNAMELEN);
		    if (err) {
			    fprintf(stderr, gettext("Unable to lock password into physical memory!\n"));
		    }
		    err = password_read(password, argv[2]);
                    error_exit(err);
                    crypt_pass = password;
                    do_pass = 1;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 's':
                if(argc > 2)
                  {
                    shell=argv[2];
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'u':
                if(argc > 2)
                  {
                    name=argv[2];
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'i':
                if(argc > 2)
                  {
                    max_history = strtoul(argv[2],0,0);
                    do_max_history = 1;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'g':
                if(argc > 2)
                  {
                    rsbac_gid_t tmp_group = RSBAC_GEN_GID(RSBAC_UM_VIRTUAL_KEEP, RSBAC_NO_GROUP);

                    if(rsbac_get_gid_name(ta_number, &tmp_group, NULL, argv[2]))
                      {
                        fprintf(stderr, gettext("%s: Unknown group %s\n"), progname, argv[2]);
                        return 1;
                      }
                    group = RSBAC_GID_NUM(tmp_group);
                    do_group = 1;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'G':
                if(argc > 2)
                  {
                    moregroups = argv[2];
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'H':
                if(argc > 2)
                  {
                    lessgroups = argv[2];
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'I':
                if(argc > 2)
                  {
                    setgroups = argv[2];
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'l':
                if(argc > 2)
                  {
                    last = strtoul(argv[2],0,0);
                    do_last = 1;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'n':
                if(argc > 2)
                  {
                    min = strtoul(argv[2],0,0);
                    do_min = 1;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'x': // cannot be smaller than minchange - albeiro
                if(argc > 2)
                  {
                    max = strtoul(argv[2],0,0);
                    do_max = 1;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'w': // warnchange should not be larger than maxchange is and less than minchange - albeiro
                if(argc > 2)
                  {
                    warn = strtoul(argv[2],0,0);
                    do_warn = 1;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'f':
                if(argc > 2)
                  {
                    inactive = strtoul(argv[2],0,0);
                    do_inactive = 1;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 'e': // mayby additional sanity checks here for checking if date is not backward ? - albeiro
                if(argc > 2)
                  {
                    expire = strtoul(argv[2],0,0);
                    do_expire = 1;
                    argc--;
                    argv++;
                  }
                else
                  fprintf(stderr, gettext("%s: missing argument for parameter %c\n"), progname, *pos);
                break;
              case 't':
                if(argc > 2)
                  {
                    ttl = strtoul(argv[2], 0, 10);
                    do_ttl = 1;
                    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);
                    do_ttl = 1;
                    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;
                        do_ttl = 1;
                        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 'N':
                if(argc > 2)
                  {
                    ta_number = strtoul(argv[2], 0, 10);
                    argc--;
                    argv++;
                  }
                else
                  {
                    fprintf(stderr, gettext("%s: missing transaction number value for parameter %c\n"), progname, *pos);
                    exit(1);
                  }
                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;

              default:
                fprintf(stderr, gettext("%s: unknown parameter %c\n"), progname, *pos);
                exit(1);
            }
          pos++;
        }
      argv++;
      argc--;
    }

  if (argc > 1)
    {
      for(i=1; i< argc; i++)
        {
          user = RSBAC_GEN_UID(vset, RSBAC_NO_USER);
          if(rsbac_um_get_uid(ta_number, argv[i], &user))
            {
              char * tmp_name = argv[i];
              char * p = tmp_name;
              rsbac_um_set_t tmp_vset = vset;
    
              if (nonumeric) {
                fprintf(stderr, gettext("%s: Unknown user %s\n"), progname, argv[i]);
                return 1;
              }
              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, skipping\n"), progname, tmp_name);
                            continue;
                          }
                        *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[i]);
                      return 1;
                    }
                }
              user = strtoul(tmp_name, NULL, 0);
              user = RSBAC_GEN_UID(tmp_vset, user);
            }
          if(verbose) {
              printf("Modifying user %s, uid %u/%u\n",
                     argv[i], RSBAC_UID_SET(user), RSBAC_UID_NUM(user));
          }
          res = rsbac_um_get_user_item(ta_number, user, UM_name, &data);
          if(res)
            {
              fprintf(stderr, gettext("%s: Unknown user %s\n"), progname, argv[i]);
              exit(1);
            }
          if(full)
            {
              strncpy(data.string, full, RSBAC_MAXNAMELEN);
              data.string[RSBAC_MAXNAMELEN - 1] = 0;
              res = rsbac_um_mod_user(ta_number, user, UM_fullname, &data);
              mod_show_error(res, "Fullname");
            }
          if(dir)
            {
              strncpy(data.string, dir, RSBAC_MAXNAMELEN);
              data.string[RSBAC_MAXNAMELEN - 1] = 0;
              res = rsbac_um_mod_user(ta_number, user, UM_homedir, &data);
              mod_show_error(res, "Homedir");
            }
          if(do_pass)
            {
              res = mlock(&data, RSBAC_UM_PASS_LEN);
    	      if (res) {
    		  fprintf(stderr, gettext("Unable to lock password into physical memory, continue anyway!\n"));
    	      }
    	      if(crypt_pass)
                {
                  memcpy(data.string, crypt_pass, RSBAC_UM_PASS_LEN);
                  memset(crypt_pass, 0, RSBAC_UM_PASS_LEN);
                  res = rsbac_um_mod_user(ta_number, user, UM_cryptpass, &data);
                  memset(&data, 0, sizeof(data));
                }
              else
              if(pass)
                {
                  strncpy(data.string, pass, RSBAC_MAXNAMELEN);
                  data.string[RSBAC_MAXNAMELEN - 1] = 0;
                  memset(pass, 0, strlen(pass));
                  res = rsbac_um_mod_user(ta_number, user, UM_pass, &data);
                  memset(&data, 0, sizeof(data));
                }
              else
                res = rsbac_um_mod_user(ta_number, user, UM_pass, NULL);
              mod_show_error(res, "Password");
    	      memset(data.string, 0, RSBAC_UM_PASS_LEN);
    	      munlock(data.string, RSBAC_UM_PASS_LEN);
            }
          if(shell)
            {
              strncpy(data.string, shell, RSBAC_MAXNAMELEN);
              data.string[RSBAC_MAXNAMELEN - 1] = 0;
              res = rsbac_um_mod_user(ta_number, user, UM_shell, &data);
              mod_show_error(res, "Shell");
            }
          if(name)
            {
              strncpy(data.string, name, RSBAC_MAXNAMELEN);
              data.string[RSBAC_MAXNAMELEN - 1] = 0;
              res = rsbac_um_mod_user(ta_number, user, UM_name, &data);
              mod_show_error(res, "Username");
            }
          if(do_group)
            {
              data.group = group;
              res = rsbac_um_mod_user(ta_number, user, UM_group, &data);
              mod_show_error(res, "Group");
            }
          if(moregroups)
            {
              char * p;
              rsbac_gid_t group = RSBAC_GEN_GID(vset, RSBAC_NO_GROUP);
              char * tmpgroups, * tmpgroupsmarker;

              tmpgroups = malloc(strlen(moregroups)+1);
              if (!tmpgroups)
                error_exit(-ENOMEM);
              strcpy(tmpgroups, moregroups);
              tmpgroupsmarker=tmpgroups;
              p = tmpgroups;
              while(*tmpgroups)
                {
                  while(*p && (*p != ','))
                    p++;
                  if(*p)
                    {
                      *p = 0;
                      if(rsbac_get_gid_name(ta_number, &group, NULL, tmpgroups))
                        {
                          fprintf(stderr, gettext("%s: Invalid group %s\n"),
                                  progname,
                                  tmpgroups);
                          group = RSBAC_GEN_GID(vset, RSBAC_NO_USER);
                        }
                      p++;
                      tmpgroups = p;
                    }
                  else
                    {
                      if(rsbac_get_gid_name(ta_number, &group, NULL, tmpgroups))
                        {
                          fprintf(stderr, gettext("%s: Invalid group %s\n"),
                                  progname,
                                  tmpgroups);
                          group = RSBAC_GEN_GID(vset, RSBAC_NO_USER);
                        }
                      tmpgroups = p;
                    }
                  if(RSBAC_GID_NUM(group) != RSBAC_NO_USER)
                    rsbac_um_add_gm(ta_number, user, RSBAC_GID_NUM(group), 0);
                }
              free(tmpgroupsmarker);
            }
          if(lessgroups)
            {
              char * p;
              rsbac_gid_t group = RSBAC_GEN_GID(vset, RSBAC_NO_GROUP);
              char * tmpgroups, * tmpgroupsmarker;

              tmpgroups = malloc(strlen(lessgroups)+1);
              if (!tmpgroups)
                error_exit(-ENOMEM);
              strcpy(tmpgroups, lessgroups);
              tmpgroupsmarker=tmpgroups;

              p = tmpgroups;
              while(*tmpgroups)
                {
                  while(*p && (*p != ','))
                    p++;
                  if(*p)
                    {
                      *p = 0;
                      if(rsbac_get_gid_name(ta_number, &group, NULL, tmpgroups))
                        {
                          fprintf(stderr, gettext("%s: Invalid group %s\n"),
                                  progname,
                                  tmpgroups);
                          group = RSBAC_GEN_GID(vset, RSBAC_NO_USER);
                        }
                      p++;
                      tmpgroups = p;
                    }
                  else
                    {
                      if(rsbac_get_gid_name(ta_number, &group, NULL, tmpgroups))
                        {
                          fprintf(stderr, gettext("%s: Invalid group %s\n"),
                                  progname,
                                  tmpgroups);
                          group = RSBAC_GEN_GID(vset, RSBAC_NO_USER);
                        }
                      tmpgroups = p;
                    }
                  if(RSBAC_GID_NUM(group) != RSBAC_NO_USER)
                    rsbac_um_remove_gm(ta_number, user, RSBAC_GID_NUM(group));
                }
              free(tmpgroupsmarker);
            }
          if(setgroups)
            {
              char * p;
              rsbac_gid_t group = RSBAC_GEN_GID(vset, RSBAC_NO_GROUP);
              rsbac_gid_num_t * group_array = NULL;
              int group_num = 0;
              char * tmpgroups, * tmpgroupsmarker;

              tmpgroups = malloc(strlen(setgroups)+1);
              if (!tmpgroups)
                error_exit(-ENOMEM);
              strcpy(tmpgroups, setgroups);
              tmpgroupsmarker=tmpgroups;

              /* Cleanup existing groups */
              group_num = rsbac_um_get_gm_list(ta_number, user, NULL, 0);
              if(group_num > 0)
                {
                  group_num += ROOM;
                  group_array = malloc(group_num * sizeof(*group_array));
	          if(!group_array)
                    {
                      error_exit(-RSBAC_ENOMEM);
                    }
                  group_num = rsbac_um_get_gm_list(ta_number, user, group_array, group_num);
                  if(group_num > 0)
                    {
                      u_int j;

                      for(j=0; j<group_num; j++)
                        rsbac_um_remove_gm(ta_number, user, group_array[j]);
                    }
                  free(group_array);
                }

              p = tmpgroups;
              while(*tmpgroups)
                {
                  while(*p && (*p != ','))
                    p++;
                  if(*p)
                    {
                      *p = 0;
                      if(rsbac_get_gid_name(ta_number, &group, NULL, tmpgroups))
                        {
                          fprintf(stderr, gettext("%s: Invalid group %s\n"),
                                  progname,
                                  tmpgroups);
                          group = RSBAC_GEN_GID(vset, RSBAC_NO_USER);
                        }
                      p++;
                      tmpgroups = p;
                    }
                  else
                    {
                      if(rsbac_get_gid_name(ta_number, &group, NULL, tmpgroups))
                        {
                          fprintf(stderr, gettext("%s: Invalid group %s\n"),
                                  progname,
                                  tmpgroups);
                          group = RSBAC_GEN_GID(vset, RSBAC_NO_USER);
                        }
                      tmpgroups = p;
                    }
                  if(RSBAC_GID_NUM(group) != RSBAC_NO_USER)
                    rsbac_um_add_gm(ta_number, user, RSBAC_GID_NUM(group), 0);
                }
              free(tmpgroupsmarker);
            }
          if(do_last)
            {
              data.days = last;
              res = rsbac_um_mod_user(ta_number, user, UM_lastchange, &data);
              mod_show_error(res, "Lastchange");
            }
          if(do_min)
            {
              data.days = min;
              res = rsbac_um_mod_user(ta_number, user, UM_minchange, &data);
              mod_show_error(res, "Minchange");
            }
          if(do_max)
            {
              data.days = max;
              res = rsbac_um_mod_user(ta_number, user, UM_maxchange, &data);
              mod_show_error(res, "Maxchange");
            }
          if(do_warn)
            {
              data.days = warn;
              res = rsbac_um_mod_user(ta_number, user, UM_warnchange, &data);
              mod_show_error(res, "Warnchange");
            }
          if(do_inactive)
            {
              data.days = inactive;
              res = rsbac_um_mod_user(ta_number, user, UM_inactive, &data);
              mod_show_error(res, "Inactive");
            }
          if(do_expire)
            {
              data.days = expire;
              res = rsbac_um_mod_user(ta_number, user, UM_expire, &data);
              mod_show_error(res, "Expire");
            }
          if(do_ttl)
            {
              data.ttl = ttl;
              res = rsbac_um_mod_user(ta_number, user, UM_ttl, &data);
              mod_show_error(res, "TTL");
            }
          if (do_max_history)
            {
              res = rsbac_um_set_max_history(ta_number, user, max_history);
              mod_show_error(res, "Max-history");
            }
        }
      exit(0);
    }
  else
    {
      use();
      return 1;
    }
  return (res);
}

