/*
 Copyright (C) 1999-2004 IC & S  dbmail@ic-s.nl
 Copyright (c) 2005-2006 NFG Net Facilities Group BV support@nfg.nl

 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.
*/

/* 
 * This is the dbmail-user program
 * It makes adding users easier 
 *
 *
 * - moving most code to dbmail-users.c. Just a thin wrapper left */

#include "dbmail.h"

char *configFile = DEFAULT_CONFIG_FILE;

#define SHADOWFILE "/etc/shadow"
#define PNAME "dbmail/user"

extern db_param_t _db_params;

/* UI policy */
int yes_to_all = 0;
int no_to_all = 0;
int verbose = 0;
int quiet = 0;          /* Don't be helpful. */
int reallyquiet = 0;    /* Don't print errors. */

int do_showhelp(void)
{
	printf(
//	Try to stay under the standard 80 column width
//	0........10........20........30........40........50........60........70........80
	"*** dbmail-users ***\n"
	"Use this program to manage your DBMail users.\n"
	"See the man page for more info. Modes of operation:\n\n"
	"     -a user   add a user\n"
	"     -d user   delete a user\n"
	"     -c user   change details for a user\n"
	"     -e user   empty all mailboxes for a user\n"
	"     -l uspec  list information for matching users\n"
	"     -x alias  create an external forwarding address\n"
	"\nSummary of options for all modes:\n"
	"     -w passwd specify user's password on the command line\n"
	"     -W [file] read from a file or prompt for a user's password\n"
	"     -p pwtype password type may be one of the following:\n"
	"               plaintext, crypt, md5-hash, md5-digest, md5-base64\n"
	"               each type may be given a '-raw' suffix to indicate\n"
	"               that the password argument has already been encoded.\n"
	"     -P [file] pull encrypted password from the shadow file\n"
	"     -u user   new username (only useful for -c, change)\n"
	"     -g client assign the user to a client\n"
	"     -m max    set the maximum mail quota in <bytes>B,\n"
	"               <kbytes>K, or <mbytes>M, default in bytes\n"
	"               specify 0 to remove any mail quota limits\n"
	"     -s alia.. adds a list of recipient aliases\n"
	"     -S alia.. removes a list of recipient aliases (wildcards supported)\n"
	"     -t fwds.. adds a list of deliver-to forwards\n"
	"     -T fwds.. removes a list of deliver-to forwards (wildcards supported)\n"
        "\nCommon options for all DBMail utilities:\n"
	"     -f file   specify an alternative config file\n"
	"     -q        quietly skip interactive prompts\n"
	"               use twice to suppress error messages\n"
	"     -n        show the intended action but do not perform it, no to all\n"
	"     -y        perform all proposed actions, as though yes to all\n"
	"     -v        verbose details\n"
	"     -V        show the version\n"
	"     -h        show this help message\n"
	);

	return 0;
}

int main(int argc, char *argv[])
{
	int opt = 0, opt_prev = 0;
	int show_help = 0;
	int result = 0, mode = 0, mode_toomany = 0;
	char *user = NULL, *newuser = NULL, *userspec = NULL, *alias = NULL;
	char *passwd = NULL, *passwdtype = NULL, *passwdfile = NULL;
	char *password = NULL, *enctype = NULL;
	u64_t useridnr = 0, clientid = 0, maxmail = 0;
	GList *alias_add = NULL, *alias_del = NULL, *fwds_add = NULL, *fwds_del = NULL;
	GString *tmp = NULL;
	struct change_flags change_flags;
	size_t len = 0;

	openlog(PNAME, LOG_PID, LOG_MAIL);
	setvbuf(stdout, 0, _IONBF, 0);

	/* Set all changes to false. */
	memset(&change_flags, 0, sizeof(change_flags));

	/* get options */
	opterr = 0;		/* suppress error message from getopt() */
	while ((opt = getopt(argc, argv,
		"-a:d:c:e:l::x:" /* Major modes */
		"W::w:P::p:u:g:m:t:s:S:T:" /* Minor options */
		"i" "f:qnyvVh" /* Common options */ )) != -1) {
		/* The initial "-" of optstring allows unaccompanied
		 * options and reports them as the optarg to opt 1 (not '1') */
		if (opt == 1)
			opt = opt_prev;
		opt_prev = opt;

		switch (opt) {
		/* Major modes of operation
		 * (exactly one of these is required) */
		case 'a':
		case 'd':
		case 'c':
		case 'e':
			if (mode)
				mode_toomany = 1;
			mode = opt;
			if (optarg && strlen(optarg))
				user = optarg;
			break;

		case 'x':
			if (mode)
				mode_toomany = 1;
			mode = opt;
			if (optarg && strlen(optarg))
				alias = optarg;
			break;

		case 'l':
			/* It seems that the optional argument may
			 * be passed as a second instance of this flag. */
			if (mode != 0 && mode != 'l')
				mode_toomany = 1;
			mode = opt;
			if (optarg && strlen(optarg))
				userspec = optarg;
			break;

		case 'i':
			printf("Interactive console is not supported in this release.\n");
			return 1;


		/* Minor options */
		case 'w':
			change_flags.newpasswd = 1;
			passwd = optarg;
			break;

		case 'W':
			change_flags.newpasswd = 1;
			if (optarg && strlen(optarg)) {
				passwdfile = optarg;
				change_flags.newpasswdfile = 1;
			} else {
				change_flags.newpasswdstdin = 1;
			}
			break;

		case 'u':
			change_flags.newuser = 1;
			newuser = optarg;
			break;

		case 'p':
			if (!passwdtype)
				passwdtype = optarg;
			// else
				// Complain about only one type allowed.
			break;

		case 'P':
			change_flags.newpasswdshadow = 1;
			if (optarg && strlen(optarg))
				passwdfile = optarg;
			else
				passwdfile = SHADOWFILE;
			passwdtype = "shadow";
			break;

		case 'g':
			change_flags.newclientid = 1;
			clientid = strtoull(optarg, NULL, 10);
			break;

		case 'm':
			change_flags.newmaxmail = 1;
			maxmail = strtomaxmail(optarg);
			break;

		case 's':
			// Add this item to the user's aliases.
			if (optarg && (len = strlen(optarg))) {
				tmp = g_string_new(optarg);
				alias_add = g_string_split(tmp,",");
				g_string_free(tmp, TRUE);
			}
			break;

		case 'S':
			// Delete this item from the user's aliases.
			if (optarg && (len = strlen(optarg))) {
				tmp = g_string_new(optarg);
				alias_del = g_string_split(tmp,",");
				g_string_free(tmp, TRUE);
			}
			break;

		case 't':
			// Add this item to the alias's forwards.
			if (optarg && (len = strlen(optarg))) {
				tmp = g_string_new(optarg);
				fwds_add = g_string_split(tmp,",");
				g_string_free(tmp, TRUE);
			}
			break;

		case 'T':
			// Delete this item from the alias's forwards.
			if (optarg && (len = strlen(optarg))) {
				tmp = g_string_new(optarg);
				fwds_del = g_string_split(tmp,",");
				g_string_free(tmp, TRUE);
			}
			break;

		/* Common options */
		case 'f':
			if (optarg && strlen(optarg) > 0)
				configFile = optarg;
			else {
				qerrorf("dbmail-users: -f requires a filename\n\n");
				result = 1;
			}
			break;

		case 'h':
			show_help = 1;
			break;

		case 'n':
			no_to_all = 1;
			break;

		case 'y':
			yes_to_all = 1;
			break;

		case 'q':
			/* If we get q twice, be really quiet! */
			if (quiet)
				reallyquiet = 1;
			if (!verbose)
				quiet = 1;
			break;

		case 'v':
			if (!quiet)
				verbose = 1;
			break;

		case 'V':
			/* Show the version and return non-zero. */
			PRINTF_THIS_IS_DBMAIL;
			result = 1;
			break;

		default:
			printf("unrecognized option [%c]\n", optopt); 
			show_help = 1;
			break;
		}

		/* If there's a non-negative return code,
		 * it's time to free memory and bail out. */
		if (result)
			goto freeall;
	}	

	/* If nothing is happening, show the help text. */
	if (!mode || mode_toomany || show_help || (no_to_all && yes_to_all)) {
		do_showhelp();
		result = 1;
		goto freeall;
	}

	/* read the config file */
        if (config_read(configFile) == -1) {
                qerrorf("Failed. Unable to read config file %s\n",
                        configFile);
                result = -1;
                goto freeall;
        }
                
	SetTraceLevel("DBMAIL");
	GetDBParams(&_db_params);

	/* open database connection */
	if (db_connect() != 0) {
		qerrorf
		    ("Failed. Could not connect to database (check log)\n");
		result = -1;
		goto freeall;
	}

	/* open authentication connection */
	if (auth_connect() != 0) {
		qerrorf
		    ("Failed. Could not connect to authentication (check log)\n");
		result = -1;
		goto freeall;
	}

	switch (mode) {
	case 'c':
	case 'd':
	case 'e':
		/* Verify the existence of this user */
		if (auth_user_exists(user, &useridnr) == -1) {
			qerrorf("Error: cannot verify existence of user [%s].\n",
			     user);
			result = -1;
			goto freeall;
		}
		if (useridnr == 0) {
			qerrorf("Error: user [%s] does not exist.\n",
				     user);
			result = -1;
			goto freeall;
		}
	}

	/* Only get a password for those modes which require it. */
	switch (mode) {
	case 'a':
	case 'c':
		if (change_flags.newpasswdstdin) {
			char pw[50];
			struct termios oldattr, newattr;

			/* Get the current terminal state, then disable echo. */
			tcgetattr(fileno(stdin), &oldattr);
			newattr = oldattr;
			newattr.c_lflag &= ~ECHO;
			tcsetattr(fileno(stdin), TCSAFLUSH, &newattr);

			/* Prompt for a password and read until \n or EOF. */
			qprintf("Please enter a password (will not echo): ");
			fflush(stdout);
			fgets(pw, 50, stdin);

			/* We don't want the trailing newline. */
			len = strlen(pw);
			if (pw[len-1] == '\n')
			        pw[len-1] = '\0';
			/* fgets guarantees a nul terminated string. */
			passwd = g_strdup(pw);

			/* Restore the previous terminal state (with echo back on). */
			tcsetattr(fileno(stdin), TCSANOW, &oldattr);
			qprintf("\n");
		}

		/* If no password type was specified, and
		 * the user already exists, get their password type. */
		if (!passwdtype && useridnr)
			passwdtype = auth_getencryption(useridnr);
		/* Convert the password and password type into a 
		 * fully coded format, ready for the database. */
		if (mkpassword(user, passwd, passwdtype, passwdfile,
		               &password, &enctype)) {
			qerrorf("Error: unable to create a password.\n");
			result = -1;
			goto freeall;
		}
	}


	switch (mode) {
	case 'a':
		result = do_add(user, password, enctype, maxmail, clientid,
				alias_add, alias_del);
		break;
	case 'd':
		result = do_delete(useridnr, user);
		break;
	case 'c':
		qprintf("Performing changes for user [%s]...\n", user);
		if (change_flags.newuser) {
			result |= do_username(useridnr, newuser);
		}
		if (change_flags.newpasswd) {
			result |= do_password(useridnr, password, enctype);
		}
		if (change_flags.newclientid) {
			result |= do_clientid(useridnr, clientid);
		}
		if (change_flags.newmaxmail) {
			result |= do_maxmail(useridnr, maxmail);
		}
		result |= do_aliases(useridnr, alias_add, alias_del);
		break;
	case 'e':
		result = do_empty(useridnr);
		break;
	case 'l':
		result = do_show(userspec);
		break;
	case 'x':
		result = do_forwards(alias, clientid, fwds_add, fwds_del);
		break;
	default:
		result = 1;
	}

	/* Here's where we free memory and quit.
	 * Be sure that all of these are NULL safe! */
freeall:

	/* Free the lists. */
	if (alias_del) {
		g_list_destroy(alias_del);
	}
	if (alias_add) {
		g_list_destroy(alias_add);
	}
	if (fwds_del) {
		g_list_destroy(fwds_del);
	}
	if (fwds_add) {
		g_list_destroy(fwds_add);
	}

	db_disconnect();
	auth_disconnect();
	config_free();

	if (result < 0)
		qerrorf("Command failed.\n");
	return result;
}



syntax highlighted by Code2HTML, v. 0.9.1