/* virtdb.c 1.0.2 - maintain slforum virtuser db from command line.
 *	(Compiles to a standalone binary -- "make virtdb" to compile)
 *	Part of the Shadowlands Forum package.
 *	From 2005 to 2008, only compiler warnings were fixed with casts;
 *	no functional code changes were made, so keeping version# 1.0.2.
 *
 * Copyright (C) 2003-2008 Jeremy Monin <jeremy@shadowlands.org>
 *	http://shadowlands.org/forum/
 *
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU 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 General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
const char *verstring = "1.0.2";

#include "world.h"
#include "slfprefs.h"

int admin_gid, users_gid;  /* needed here for passwd-db-inc.c */

#include "passwd-db-inc.h"
#include "passwd-db-inc.c"

#ifndef USE_VIRTUSERS
#error You must define USE_VIRTUSERS to compile this file.
#endif

#define RETERR_CMDLINE  1  /* cmdline error; must edit cmdline to rerun */
#define RETERR_DATA     2  /* database content or i/o error */
#define RETERR_INTERNAL 3  /* internal error; edit slfprefs.h or debug */

static char virtuserdbfname[PATH_MAX];


static void cmdline_to_action (int argc, const char **argv);
static void print_usage_and_exit(int exitval);



static void find_virtdb_or_exit (void)
{
	/* Patterned after code in slforumroom constructor */
	struct stat st;
	FILE *f;

	virtuserdbfname[0] = '\0';
	if (-1 == snprintf(virtuserdbfname, PATH_MAX-5, ".%c%s.db",
			   PATH_SEP, SLF_VIRTUSER_DB))
	{
		fprintf (stderr, "virtdb: couldn't set virtuser database file to %s.db: path too long\n", SLF_VIRTUSER_DB);
		exit (RETERR_INTERNAL);
	}

	/* make sure we can open it: */
	f = fopen (virtuserdbfname, "ab+");
	if (f == NULL)
	{
		fprintf (stderr,
			 "virtdb: Cannot open or create .%c%s.db (error %d)\n",
			 PATH_SEP, SLF_VIRTUSER_DB, (int) errno);
		exit (RETERR_DATA);
	}
	fclose (f);
	/* get stat -- if we created it, unlink (0-byte file is bad) */
	
	if ((0 == stat (virtuserdbfname, &st))
	    && (0 == st.st_size))
	{
		unlink (virtuserdbfname);
	}

	/* Remove extension from filename */
	snprintf (virtuserdbfname, PATH_MAX-5, ".%c%s",
		  PATH_SEP, SLF_VIRTUSER_DB);
	
	/* Tell our db module we found it: */
	slf_virtuser_set_dbfname(virtuserdbfname);

	/* set up things */
	admin_gid = 0;
	users_gid = 1;

	/* Success! */
}  /* find_virtdb_or_exit */



const char *u_uname = NULL, *u_passwd = NULL, *u_gecos = NULL,
	*u_dir = "", *u_shell = "", *u_sponsor = NULL, *u_email = NULL;
int u_gid = -1;  /* admin is 0, user is 1, unspecified is -1 */



/* If current argument (index *pi) matches this one, set it up in *pfield,
 * adjust *pi and return 1.  Otherwise return 0.  EXIT PROGRAM if error.
 * *pi is a pointer to an index so it can increment it in the calling program
 * as necessary (add 1 or add 2).
 * Used in addedit_gather_args, cmd_del.  Assumes *pi < argc when called.
 * Assumes *flagstr is 2 chars long, as "-u" or "-p" .
 * Correct args on cmdline are like -u uname or -uuname .
 */
static int argcheck (int argc, const char **argv, int *pi,
		     const char *flagstr, const char **pfield)
{
	const char *valuestart;
	if (argc <= *pi)
		return 0;  /* end of argv array */
	if (0 != strncasecmp (flagstr, argv[*pi], 2))
		return 0;  /* flag does not match */
	if (NULL != *pfield)
	{
		fprintf (stderr, "virtdb: can only specify %s once\n",
		    flagstr);
		exit (RETERR_CMDLINE);
	}

	if (argv[*pi][2] != '\0')
	{
		valuestart = &(argv[*pi][2]);  /* 1 arg: -uuname */
	} else {
		/* 2 args: -u uname */
		++(*pi);
		if ((argc <= *pi) || (argv[*pi][0] == '-'))
		{
			fprintf (stderr,
			    "virtdb: %s must be followed by value\n", flagstr);
			if (argc > *pi)
			    fprintf (stderr,
				"and value cannot start with '-'\n");
			exit (RETERR_CMDLINE);
		}
		valuestart = &(argv[*pi][0]);	       
	}
	*(pfield) = valuestart;
	++(*pi);  /* becomes index of next argument (or argc) */
	return 1;
}  /* argcheck */



/* gather args -u -p etc into string buffers - ensure at most 1 each.
 * EXIT PROGRAM if args are missing or duplicated.
 */
static void addedit_gather_args (int argc, const char **argv, int add_not_edit)
{
	int i = 2;
	while (i < argc)
	{
		if (!
		    (argcheck (argc, argv, &i, "-u", (const char**) (&u_uname))
		     || argcheck (argc, argv, &i, "-p", (const char**) (&u_passwd))
		     || argcheck (argc, argv, &i, "-f", (const char**) (&u_gecos))
		     || argcheck (argc, argv, &i, "-e", (const char**) (&u_email))
		     || argcheck (argc, argv, &i, "-s", (const char**) (&u_sponsor))
			    ))
		{
			if (0 == strncasecmp ("-a", argv[i], 2))
			{
				char adminval = 'y';
				if ('\0' != argv[i][2])
					adminval = argv[i][2]; /* 1 arg: -an */
				else if ((i < (argc-1)) &&
					 ('-' != argv[i+1][0]))
				{
					adminval = argv[i+1][0];
					++i;  /* since it took 2 args: -a n */
				}
				if ((adminval == 'y')||(adminval == 'Y'))
					u_gid = 0;
				else if ((adminval == 'n')||(adminval == 'N'))
					u_gid = 1;
				else {
					fprintf (stderr, "virtdb: the -a flag must be either -ay or -an\n");
					exit (RETERR_CMDLINE);
				}
				++i;  /* set to index of next arg */
			} else {
				fprintf (stderr, "virtdb: Unknown add/edit flag.  Type virtdb -h for help.\n");
				exit (RETERR_CMDLINE);
			}
		}
	}  /* end while (i<argc) */

	if (NULL == u_uname)
	{
		fprintf (stderr, "virtdb: Username required (-u) to add/edit\n");
		exit (RETERR_CMDLINE);
	}

	if (add_not_edit)
	{
		if ((NULL == u_passwd) || (NULL == u_gecos))
		{
			fprintf (stderr, "virtdb: Add requires password (-p), full name (-f)\n");
			exit (RETERR_CMDLINE);
		}
	}
}  /* addedit_gather_args */



static void addedit_check_user_existence
	(struct slf_userinfo *puv, int add_not_edit)
{
	if (add_not_edit)		
	{
		if (NULL != puv)
		{
			fprintf (stderr,
			     "virtdb: user %s already exists, cannot add\n",
			     u_uname);
			exit (RETERR_DATA);
		}
	} else {
		if (NULL == puv)
		{
			fprintf (stderr,
			     "virtdb: user %s does not exist, cannot edit\n",
			     u_uname);
			exit (RETERR_DATA);
		}
	}
}  /* addedit_check_user_existence */



static void cmd_addedit (int argc, const char **argv, int add_not_edit)
{
	struct slf_userinfo uv, *puv;

	/* gather args -u -p etc into string buffers - ensure at most 1 each.
	 * EXIT PROGRAM if args are missing or duplicated.
	 */
	addedit_gather_args (argc, argv, add_not_edit);

	/* ensure user exists if editing, doesn't if adding-- OR EXIT */
	puv = slf_virtuser_get_info (u_uname, NULL, false);
	addedit_check_user_existence (puv, add_not_edit);

	/* create userinfo from string buffers, and retrieved info for edits */
	/* (string contents won't be modified, just pointers to strings)     */
	if (! add_not_edit)
	{
		memcpy (&uv, puv, sizeof(struct slf_userinfo));   /* copy in */
		if (u_gid != -1) uv.u.pw_gid = u_gid;  /* set new flag value */
	} else {
		memset (&uv, 0, sizeof(struct slf_userinfo));
		uv.u.pw_name = (char*) u_uname;    /* can't forget that */
		uv.u.pw_dir = (char*) u_dir;            /* empty string */
		uv.u.pw_shell = (char*) u_shell;        /* empty string */
		if (u_gid == -1)
			uv.u.pw_gid = 1;             /* default to not admin */
		else
			uv.u.pw_gid = u_gid;
	}
	if (u_gecos != NULL) uv.u.pw_gecos = (char*) u_gecos;
	if (u_email != NULL) uv.uv_email = u_email;
	if (u_sponsor != NULL) uv.uv_sponsor = u_sponsor;
	if (u_passwd != NULL)
	{
		uv.u.pw_passwd = (char*) slf_cryptnewpw (u_passwd);
	}

	/* slf_virtuser_put_info */
	if (slf_virtuser_put_info(&uv) == false)
	{
		fprintf (stderr, "virtdb: Could not store user '%s' to database (errno %d)\n", u_uname, errno);
		exit (RETERR_DATA);
	} else {
		fprintf (stderr, "virtdb: Stored user '%s' to database\n", u_uname);
	}

}  /* cmd_addedit */



static void cmd_del (int argc, const char **argv)
{
	const char *deluser = NULL;

	/* grab username from command line */
	if (argc >= 3)
	{
		int i = 2;
		if (0 == argcheck (argc, argv, &i, "-u", (const char **) (&deluser)))
		{
			/* succeeded in reading -u username.
			 * i should be argc (no additional arguments)
			 */
			if (i != argc)  deluser = NULL;
		}
	}
	if (NULL == deluser)
	{
	    fprintf (stderr, "virtdb: del usage is: virtdb del -u <uname>\n");
	    exit (RETERR_CMDLINE);
	}

	/* look up that user */
	{
	 const struct slf_userinfo *arethey;
	 arethey = slf_virtuser_get_info(deluser, NULL, false);
	 if (arethey == NULL)
	 {
	   fprintf (stderr,
	       "virtdb: del '%s': No user by that name was found.\n", deluser);
	   exit (RETERR_DATA);
	 }
	}

	if (slf_virtuser_remove(deluser))
		fprintf (stderr, "virtdb: del '%s': User deleted.\n", deluser);
	else {
		fprintf (stderr, "virtdb: del '%s': Could not delete.\n",
			 deluser);
		exit (RETERR_DATA);
	}

}  /* cmd_del */



static void cmd_list (int argc, const char **argv)
{
	const struct slf_userinfo *pw;
	char fmtbuf[81];

	if (argc != 2)
	{
		fprintf (stderr, "virtdb: unrecognized argument to list\n");
		exit (RETERR_CMDLINE);
	}
	/* Taken from slforumuser-adminsubstate.inc.cc */
	fmtbuf[79] = '\n';
	fmtbuf[80] = '\0';

	pw = slf_virtuser_getfirst();
	if (pw == NULL) {
		printf ("No virtual users in the database.\n");
	} else {
		printf ("Username  Full name\n");
	}

	while (pw != NULL)
	{
		snprintf(fmtbuf, 80, "%-9s %-49s %s\n", 
			 pw->u.pw_name, pw->u.pw_gecos,
			 (pw->u.pw_gid == 0 ? "[Admin]" : "") );
		printf (fmtbuf);
		pw = slf_virtuser_getnext();
	  }
	  slf_virtuser_getstop();

}  /* cmd_list */



void cmdline_to_action (int argc, const char **argv)
{
	if (! strncasecmp ("add", argv[1], 3))
	{
		/* virtdb add -u <name> -p <pw> [more flags and args] */
		if (argc < 5)
			print_usage_and_exit (RETERR_CMDLINE);
		else
			cmd_addedit (argc, argv, 1);
	} else if (! strncasecmp ("edit", argv[1], 4))
	{
		/* virtdb edit -u <name> [at least one more flag and arg] */
		if (argc < 5)
			print_usage_and_exit (RETERR_CMDLINE);
		else
			cmd_addedit (argc, argv, 0);
	} else if (! strncasecmp ("list", argv[1], 4))
	{
		cmd_list (argc, argv);
	} else if (! strncasecmp ("del", argv[1], 3))
	{
		cmd_del (argc, argv);
	} else if ((! strncasecmp ("-h", argv[1], 2))
		   && (! strncasecmp ("--help", argv[1], 6))
		   && (! strncasecmp ("help", argv[1], 4)))
	{
		print_usage_and_exit (0);
	} else {
		print_usage_and_exit (RETERR_CMDLINE);
	}
}


/* Lprintf - util function taken and adapted from slforummain.cc: 
// for logging from .c files.
// kinda a kludge but it works
// you'll recognize some of
// this code from logprintf...
// this func takes care of varargs
// and then calls the room logprintf
// (too bad we can't pass the varargs).
// It's c++ code, but linked with C
// linkage (no mangling) so the .c files
// can call it... all in the name of
// good debugging facilities :)
*/
void Lprintf(const char *fmt, ...)
{
  /* from vsprintf manpage example */
  char buf[256];
  va_list ap;
  va_start (ap, fmt);
  vsnprintf(buf, 255, fmt, ap);  buf[255] = '\0';
  va_end (ap);
  fprintf(stderr, "%s", buf);
}






static void print_usage_and_exit(int exitval)
{
	fprintf (stderr,
	     "virtdb - Shadowlands Forum virtual user database maintenance      %s\n", verstring);
	fprintf (stderr, "Usage:\n");
	fprintf (stderr, "   virtdb list\n");
	fprintf (stderr, "   virtdb add -u <username> -p ....\n");
	fprintf (stderr, "   virtdb edit -u <username> ....\n");
	fprintf (stderr, "   virtdb del -u <username>\n");
	fprintf (stderr, "\n");
	fprintf (stderr, "Flags for add/edit:\n");
	fprintf (stderr, "   -u username                -p password\n");
	fprintf (stderr, "   -f fullname (remember quote marks if multiple words)\n");
	fprintf (stderr, "   -e emailaddr (optional)    -a or -ay or -an : admin flag (optional)\n");
	fprintf (stderr, "   -s sponsoring user / notes (optional)\n");
	fprintf (stderr, "\n");
	fprintf (stderr, "Space between flag and value is optional.\n");
	fprintf (stderr, "Be sure to run virtdb in the directory containing your virtuser.db file,\n");
	fprintf (stderr, "typically /var/lib/slforum or ~/.slforum-7777 (port number)\n");

	exit (exitval);
}


int main(int argc, char **argv)
{
	if (argc <= 1)
		print_usage_and_exit(RETERR_CMDLINE);
	  
	find_virtdb_or_exit();

	/* Do Something */
	cmdline_to_action(argc, (const char**) argv);

	return 0;
}


/* virtdb.c ENDS. */

