/*****************************************************************************
 *
 * File ..................: mbuser/mbuser.c
 * Purpose ...............: User Pack Util
 * Last modification date : 12-Aug-2001
 *
 *****************************************************************************
 * Copyright (C) 1997-2001
 *   
 * Michiel Broek		FIDO:		2:280/2802
 * Beekmansbos 10
 * 1971 BV IJmuiden
 * the Netherlands
 *
 * This file is part of MBSE BBS.
 *
 * This BBS 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, or (at your option) any
 * later version.
 *
 * MBSE BBS 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 MBSE BBS; see the file COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************/

#include "../lib/libs.h"
#include "../lib/structs.h"
#include "../lib/records.h"
#include "../lib/common.h"
#include "../lib/clcomm.h"
#include "../lib/dbcfg.h"
#include "mbuser.h"


extern	int e_pid;			/* External pid			     */
extern	int do_quiet;			/* Quiet flag			     */
time_t	t_start;			/* Start time			     */
time_t	t_end;				/* End time			     */
int	Days, Level;			/* Kill days and up to level	     */
struct	userhdr	usrhdr;			/* Database header		     */
struct	userrec	usr;			/* Database record		     */
mode_t	oldmask;			/* Old umask value		     */


int main(int argc, char **argv)
{
	int	i, pack = FALSE;
	char	*cmd;
	struct	passwd *pw;

#ifdef MEMWATCH
        mwInit();
#endif
	InitConfig();
	TermInit(1);
	Days = 0;
	Level = 0;

	time(&t_start);

	if (argc < 2)
		Help();

	cmd = xstrcpy((char *)"Command line:");

	for (i = 1; i < argc; i++) {
		cmd = xstrcat(cmd, (char *)" ");
		cmd = xstrcat(cmd, tl(argv[i]));

		if (strncasecmp(tl(argv[i]), "-q", 2) == 0)
			do_quiet = TRUE;
		if (strncasecmp(tl(argv[i]), "p", 1) == 0)
			pack = TRUE;
		if (strncasecmp(tl(argv[i]), "k", 1) == 0) {
			if (argc  <= (i + 2))
				Help();
			i++;
			cmd = xstrcat(cmd, (char *)" ");
			cmd = xstrcat(cmd, argv[i]);
			Days = atoi(argv[i]);
			i++;
			cmd = xstrcat(cmd, (char *)" ");
			cmd = xstrcat(cmd, argv[i]);
			Level = atoi(argv[i]);

			if ((Days == 0) || (Level == 0))
				Help();
		}
	}

	if ((Days + Level + pack) == 0)
		Help();

	ProgName();
	pw = getpwuid(getuid());
	InitClient(pw->pw_name, (char *)"mbuser", CFG.location, CFG.logfile, CFG.util_loglevel, CFG.error_log);
	Syslog(' ', " ");
	Syslog(' ', "MBUSER v%s", VERSION);
	Syslog(' ', cmd);
	free(cmd);

	if (!diskfree(CFG.freespace))
		ExitClient(101);

	oldmask = umask(027);
	if (!do_quiet)
		colour(3, 0);
	UserPack(Days, Level, pack);
	umask(oldmask);

	time(&t_end);
	Syslog(' ', "MBUSER finished in %s", t_elapsed(t_start, t_end));

	if (!do_quiet)
		colour(7, 0);
	ExitClient(0);
	return 0;
}



/*
 * Program header
 */
void ProgName(void)
{
	if (do_quiet)
		return;

	colour(15, 0);
	printf("\nMBUSER: MBSE BBS %s - User maintenance utility\n", VERSION);
	colour(14, 0);
	printf("        %s\n\n", Copyright);
	colour(7, 0);
}



void Help(void)
{
	do_quiet = FALSE;
	ProgName();

	colour(11, 0);
	printf("\nUsage:	mbuser [commands] <options>\n\n");
	colour(9, 0);
	printf("	Commands are:\n\n");
	colour(3, 0);
	printf("	kill [n] [l]	Kill users not called in \"n\" days below level \"l\"\n");
	printf("	pack		Pack the userbase\n");
	colour(9, 0);
	printf("\n	Options are:\n\n");
	colour(3, 0);
	printf("	-quiet		Quiet mode, (no screen output)\n\n");

	colour(7, 0);
	printf("\n");
	ExitClient(1);
}



/*
 * Userpack routine
 */
void UserPack(int days, int level, int pack)
{
	FILE	*fin, *fout;
	char	*fnin, *fnout;
	long	oldsize, curpos;
	int	updated, delete = 0, rc, highest = 0, record = 0, sysop = FALSE;

	fnin  = calloc(PATH_MAX, sizeof(char));
	fnout = calloc(PATH_MAX, sizeof(char));
	sprintf(fnin,  "%s/etc/users.data", getenv("MBSE_ROOT"));
	sprintf(fnout, "%s/etc/users.temp", getenv("MBSE_ROOT"));

	/*
	 * First copy the users database, all packing will be done
	 * on a the copy.
	 */
	if ((fin = fopen(fnin, "r")) == NULL) {
		WriteError("Can't open %s", fnin);
		free(fnin);
		free(fnout);
		return;
	}
	if ((fout = fopen(fnout, "w+")) == NULL) {
		WriteError("Can't create %s", fnout);
		fclose(fin);
                free(fnin);
                free(fnout);
		return;
	}
	fread(&usrhdr, sizeof(usrhdr), 1, fin);
	oldsize = usrhdr.recsize;
	updated = FALSE;

	/*
	 * First count records and blanks at the end. Check if the sysop name
	 * in the main configuration exists in the userdatabase.
	 */
	while (fread(&usr, oldsize, 1,fin) == 1) {
		delete++;
		if (!usr.Deleted && strlen(usr.sUserName)) {
			highest = (ftell(fin) / oldsize);
			if (!strcmp(usr.sUserName, CFG.sysop_name) && !strcmp(usr.Name, CFG.sysop))
				sysop = TRUE;
		}
	}
	if (highest != delete) {
		Syslog('+', "Blank records at the end, truncating userbase");
		updated = TRUE;
	}
	if (!sysop)
		WriteError("No valid Sysop Fidoname and/or Unixname found in userbase, check setup!");

	fseek(fin, usrhdr.hdrsize, SEEK_SET);

	if (oldsize != sizeof(usr)) {
		updated = TRUE;
		Syslog('+', "Userbase recordsize is changed, making update");
	}

	usrhdr.hdrsize = sizeof(usrhdr);
	usrhdr.recsize = sizeof(usr);
	fwrite(&usrhdr, sizeof(usrhdr), 1, fout);

	/*
	 * The datarecord is filled with zero's before each read
	 * so that if the record format changed, the new fields will
	 * be empty by default. The blank records at the end of the
	 * database are dropped.
	 */
	memset(&usr, 0, sizeof(usr));
	while (fread(&usr, oldsize, 1,fin) == 1) {
		record++;
		fwrite(&usr, sizeof(usr), 1, fout);
		memset(&usr, 0, sizeof(usr));
		if (CFG.slow_util && do_quiet)
			usleep(1);
	}
	fclose(fin);
	delete = 0;

	/*
	 * Handle packing for days below level
	 */
	if ((days) && (level)) {
		fseek(fout, sizeof(usrhdr), SEEK_SET);
		curpos = sizeof(usrhdr);

		while (fread(&usr, sizeof(usr), 1, fout)  == 1) {
			/*
			 * Wow, killing on the second exact!. Don't kill
			 * the guest accounts.
			 */
			if ((((t_start - usr.tLastLoginDate) / 86400) > days) &&
			    (usr.Security.level < level) && (!usr.Guest) &&
			    (usr.sUserName[0] != '\0') && (!usr.NeverDelete)) {
				Syslog('+', "Mark user %s", usr.sUserName);
				if (!do_quiet) {
					printf("Mark user %s\n", usr.sUserName);
					fflush(stdout);
				}
				delete++;
				updated = TRUE;
				fseek(fout, - sizeof(usr), SEEK_CUR);
				/*
				 * Just mark for deletion
				 */
				usr.Deleted = TRUE;
				fwrite(&usr, sizeof(usr), 1, fout);
			}
			if (CFG.slow_util && do_quiet)
				usleep(1);
		}
		Syslog('+', "Marked %d users to delete", delete);
 	}

	/*
	 * Pack the userbase if told so
	 */
	if (pack) {
		Syslog('+', "Packing userbase");
		delete = 0;
		fseek(fout, sizeof(usrhdr), SEEK_SET);
		while (fread(&usr, sizeof(usr), 1, fout) == 1) {
			if (CFG.slow_util && do_quiet)
				usleep(1);

			if (usr.Deleted) {
				if (!do_quiet) {
					printf("Delete user %s\n", usr.Name);
					fflush(stdout);
				}
				if (usr.Name[0] != '\0') {
					if ((setuid(0) == -1) || (setgid(0) == -1)) {
						WriteError("Cannot setuid(root) or setgid(root)");
						WriteError("Cannot delete unix account %s", usr.Name);
					} else {
#ifndef __FreeBSD__
						rc = execute((char *)"/usr/sbin/userdel ", usr.Name, NULL,
							(char *)"/dev/null",(char *)"/dev/null",(char *)"/dev/null");
#else
						rc = execute((char *)"/usr/sbin/pw userdel ", usr.Name, NULL,
							(char *)"/dev/null",(char *)"/dev/null",(char *)"/dev/null");
#endif
						if (chdir(CFG.bbs_usersdir) == 0)
							rc = execute((char *)"/bin/rm -Rf ", usr.Name, NULL,
								(char *)"/dev/null",(char *)"/dev/null",(char *)"/dev/null");
					}
				}

				fseek(fout, - sizeof(usr), SEEK_CUR);
				/*
				 * Blank the deleted records for reuse.
				 */
				memset(&usr, 0, sizeof(usr));
				fwrite(&usr, sizeof(usr), 1, fout);
				delete++;
				updated = TRUE;
			}
		}
		Syslog('+', "Deleted %d records", delete);
	}

	if (updated) {
		/*
		 *  Copy file back to the original file, truncate any
		 *  deleted records at the end.
		 */
		fseek(fout, 0, SEEK_SET);
		if ((fin = fopen(fnin, "w")) == NULL) {
			WriteError("Can't open %s", fnin);
			free(fnin);
			free(fnout);
			return;
		}
		fread(&usrhdr, sizeof(usrhdr), 1, fout);
		fwrite(&usrhdr, sizeof(usrhdr), 1, fin);
		record = 0;

		while (fread(&usr, sizeof(usr), 1,fout) == 1) {
			record++;
			fwrite(&usr, sizeof(usr), 1, fin);
			if (record >= highest)
				break;
		}
		fclose(fin);
		fclose(fout);
		Syslog('+', "Userbase is updated, written %d records", record);
	}
	unlink(fnout);
	free(fnin);
	free(fnout);
}