/* Copyright (C) 1999 Beau Kuiper
   this is a quick hack for mudpasswd, servicable 1 nighter
   this has had some more work on it now, it is safer to use and can't
   destroy your password file
   warning, this code is not buffer overflow safe. DONT RUN AS SETUID ROOT

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

#include "../config.h"

#define FALSE 0
#define TRUE !FALSE
#define MAXSTRLEN 4096

#define ERRORMSGFATAL(x)	{	printf("mudpasswd: %s\n", x); exit(1); 	}
#define NOSIGNALINTR(x)		(x)
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <pwd.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif

#define ROOTMSK 1
#define HOMEMSK 2
#define PASSMSK 4

extern void showversion(char *desc);

char *strsplit(char *in, char *cmd, char *data)
{
	int pos = 0;
	int pos2 = 0;
	
	if (in[strlen(in) - 1] == 10)
		in[strlen(in) - 1] = 0;
		
	while((in[pos] <= 32) && (in[pos] != 0))
		pos++;
	if ((in[pos] == '#') || (in[pos] == 0))
		return(NULL);
	
	while((in[pos] != 32) && (in[pos] != 0))
	{
		cmd[pos2] = in[pos];
		pos2++;
		pos++;
	}
	
	if (in[pos] == 0)
		return(NULL);
	
	cmd[pos2] = 0;
	
	while((in[pos] <= 32) && (in[pos] != 0))
		pos++;
	if (in[pos] == 0)
		return(NULL);

	pos2 = 0;
	while((in[pos] != 32) && (in[pos] != 0) && (in[pos] != '\n'))
	{
		if ((in[pos] == '\\') && (in[pos+1] != 0))
			pos++;
		
		data[pos2] = in[pos];
		pos2++;
		pos++;
	}
	
	data[pos2] = 0;
	return(data);
}

char getsaltchar(void)
{
	char outchr;

	outchr = random() & 63;
	if (outchr < 26)
		outchr += 'A';
	else if (outchr < 52)
		outchr += 'a' - 26;
	else if (outchr < 62)
		outchr += '0' - 52;
	else if (outchr == 62)
		outchr = '.';
	else
		outchr = '/';
	
	return(outchr);
}

char *cryptedpass(char *clearpass)
{
	char salt[5];
	
	srandom((unsigned int)time(NULL));
	salt[0] = getsaltchar();
	salt[1] = getsaltchar();
	salt[2] = 0;
	
	return(crypt(clearpass, salt));
}

void usage(void)
{
	printf("\nMuddleftpd Password File Editor.\n\n");
	printf("Usage: mudpasswd [options]\n\n");
	printf("	-p passfile	The filename of the password file to use\n");
	printf("	-a username	Adds a new username\n");
	printf("	-e username	Edits the properties of a username\n");
	printf("	-d username	Deletes the username from the password file\n");
	printf("	-P password	Sets a new password\n");
	printf("	-R directory	Sets a new root directory for the user.\n");
	printf("	-H directory	Sets a new home directory for the user.\n");
	printf("	-W		Changes the password using user input.\n");
	printf("	-h		Displays this usage message.\n");
	printf("	-V		Displays version information.\n\n");

	printf("You must specify -p and  only one of -a -e or -d when running mudpasswd\n\n");
	exit(0);
}

int searchuser(char *passfile, char *username)
{
	FILE *passwdfile;
	char instring[MAXSTRLEN], username2[MAXSTRLEN], data[MAXSTRLEN];
	
	passwdfile = fopen(passfile, "r");
	if (!passwdfile)
		return(FALSE);
	
	while (fgets(instring, MAXSTRLEN, passwdfile) != NULL)
	{
		if (strsplit(instring, username2, data) != NULL)
			if (strcmp(username2, username) == 0)
			{
				fclose(passwdfile);
				return(TRUE);
			}
	}
	
	fclose(passwdfile);
	return(FALSE);	 
}
	
void adduser(char *passfile, char *username, char *rootdir, char *homedir,
	     char *password)
{
	FILE *passwdfile;
	int pos;
	
	if (searchuser(passfile, username))
		ERRORMSGFATAL("Username already exists - Operation Failed!");

	passwdfile = fopen(passfile, "a+");
	if (passwdfile == NULL)
		ERRORMSGFATAL("Cannot open password file!");
		
	pos = ftell(passwdfile);
	
	if (pos > 0)
	{
		char chkchr;
		
		fseek(passwdfile, -1, SEEK_CUR);
		chkchr = fgetc(passwdfile);
		if (chkchr != '\n')
		{
			fseek(passwdfile, 1, SEEK_CUR);
			fprintf(passwdfile, "\n");
		}
	}
	
	if (!passwdfile)
		ERRORMSGFATAL("Couldn't open password file for writing!");
	
	fprintf(passwdfile, "%s %s:%s:%s\n", username, cryptedpass(password),
		homedir, rootdir);
	fclose(passwdfile);
}

void deleteuser(char *passfile, char *username)
{
	FILE *passwdfilein, *passwdfileout;
	char instring[MAXSTRLEN], username2[MAXSTRLEN], data[MAXSTRLEN];
	char newpassfile[MAXSTRLEN];
	
	if (!searchuser(passfile, username))
		ERRORMSGFATAL("Username doesn't exists - Operation Failed!");

	strncpy(newpassfile, passfile, 4096);
	strncat(newpassfile, ".new", 4096 - strlen(newpassfile));

	passwdfilein = fopen(passfile, "r");
	if (!passwdfilein)
		ERRORMSGFATAL("Could not open password file!");

	passwdfileout = fopen(newpassfile, "w");
	
	if (!passwdfileout)
		ERRORMSGFATAL("Could not open replacement password file!");
	
	while (fgets(instring, MAXSTRLEN, passwdfilein) != NULL)
	{
		int n = TRUE;
		
		if (strsplit(instring, username2, data) != NULL)
			if (strcmp(username2, username) == 0)
				n = FALSE;
		if (n)
			fprintf(passwdfileout, "%s\n", instring);	
	}
	
	fclose(passwdfilein);
	fclose(passwdfileout);
	if (rename(newpassfile, passfile) == -1)
	{
		unlink(newpassfile);
		ERRORMSGFATAL("Could not replace password file!");
	}
}

void edituserstr(char *userstr, int attrmask, char *password, char *rootdir, 
		char *homedir, char *username, char *data)
{
	char passwordold[MAXSTRLEN], homedirold[MAXSTRLEN], rootdirold[MAXSTRLEN];
	char *pos;
	
	strcpy(passwordold, "");
	strcpy(homedirold, "");
	strcpy(rootdirold, "");
	
	/* get old settings */
	if ((pos = strchr(data, ':')) != NULL)
	{
		strncpy(passwordold, data, 4096);
		pos = strchr(passwordold, ':');
		*pos = 0;
		pos++;
	}
	if (pos != NULL)
		if ((strchr(pos, ':')) != NULL)
		{
			strncpy(homedirold, pos, 4096);
			pos = strchr(homedirold, ':');
			*pos = 0;
			pos++;
		}
	if (pos != NULL)
		strncpy(rootdirold, pos, 4096);
	
	if (attrmask & ROOTMSK)
		strncpy(rootdirold, rootdir, 4096);
	if (attrmask & HOMEMSK)
		strncpy(homedirold, homedir, 4096);
	if (attrmask & PASSMSK)
		strncpy(passwordold, cryptedpass(password), 4096);
	
	snprintf(userstr, MAXSTRLEN, "%s %s:%s:%s", username, passwordold, homedirold,
		rootdirold);
	userstr[MAXSTRLEN-1] = 0;
}

void edituser(char *passfile, char *username, char *rootdir, char *homedir,
	     char *password, int attrmask)
{
	FILE *passwdfilein, *passwdfileout;
	char instring[MAXSTRLEN], username2[MAXSTRLEN], data[MAXSTRLEN];
	char newpassfile[MAXSTRLEN];
	
	if (!searchuser(passfile, username))
		ERRORMSGFATAL("Username doesn't exists - Operation Failed!");

	strncpy(newpassfile, passfile, 4096);
	strncat(newpassfile, ".new", 4096 - strlen(newpassfile));

	passwdfilein = fopen(passfile, "r");
	if (!passwdfilein)
		ERRORMSGFATAL("Could not open password file!");
		
	passwdfileout = fopen(newpassfile, "w");
	
	if (!passwdfileout)
		ERRORMSGFATAL("Could not open replacement password file!");
	
	while (fgets(instring, MAXSTRLEN, passwdfilein) != NULL)
	{
		int n = TRUE;
		
		if (strsplit(instring, username2, data) != NULL)
			if (strcmp(username2, username) == 0)
				edituserstr(instring, attrmask, password,
					    rootdir, homedir, username, data);
		if (n)
			fprintf(passwdfileout, "%s\n", instring);	
	}
	
	fclose(passwdfilein);
	fclose(passwdfileout);
	if (rename(newpassfile, passfile) == -1)
	{
		unlink(newpassfile);
		ERRORMSGFATAL("Could not replace password file!");
	}
}

int main(int argc, char **argv)
{
	int ch;
	char *passfile = NULL;
	char *username = NULL;
	char *password = NULL;
	char *homedir = NULL;
	char *rootdir = NULL;
	int add_user = FALSE;
	int delete_user = FALSE;
	int edit_user = FALSE;
	int edit_mask = FALSE;
	extern char *optarg;
	
	/* stolen from smbpasswd - checking for setuid root! */
	
	/* Check the effective uid - make sure we are not setuid */
	if ((geteuid() == (uid_t)0) && (getuid() != (uid_t)0))
		ERRORMSGFATAL("mudpasswd must *NOT* be setuid root.\n");
                                                   
	while ((ch = getopt(argc, argv, "p:a:d:e:P:R:H:WhV")) != EOF) 
	{
		switch(ch) 
		{
		case 'V':
			showversion("mudpasswd");
		case 'p':
			passfile = optarg;
			break;
		case 'a':
			add_user = TRUE;
			username = optarg;
			break;
		case 'd':
			delete_user = TRUE;
			username = optarg;
			break;
		case 'e':
			edit_user = TRUE;
			username = optarg;
			break;
		case 'P':
			password = optarg;
			edit_mask |= PASSMSK;
			break;
		case 'R':
			rootdir = optarg;
			edit_mask |= ROOTMSK;
			break;
		case 'H':
			homedir = optarg;
			edit_mask |= HOMEMSK;
			break;
		case 'W':
			edit_mask |= PASSMSK;
			break;
		case 'h':
			usage();
			break;
		default:
			usage();
		}
	}

	if (!passfile)
		usage();
	
	if (add_user)
	{
		if (edit_user || delete_user)
			usage();
		if (!homedir)
		{
			homedir = (char *)malloc(MAXSTRLEN);
			printf("Enter Home Directory For New User: ");
			fgets(homedir, MAXSTRLEN, stdin);
			homedir[strlen(homedir) - 1] = 0;
		}
		if (!rootdir)
		{
			rootdir = (char *)malloc(MAXSTRLEN);
			printf("Enter Root Directory For New User: ");
			fgets(rootdir, MAXSTRLEN, stdin);
			rootdir[strlen(rootdir) - 1] = 0;
		}
		if (!password)
		{
			password = getpass("Enter Password For New User: ");
		}

		adduser(passfile, username, rootdir, homedir, password);
		exit(0);
	} 
	
	if (delete_user)
	{
		if (edit_user)
			usage();
		
		deleteuser(passfile, username);
		exit(0);
	}

	if (edit_user)
	{
		if (!edit_mask)	
			usage();
		
		if (!password && (edit_mask & PASSMSK))
		{
			password = getpass("Enter Password For New User: ");
		}
		
		edituser(passfile, username, rootdir, homedir, password, edit_mask);
		exit(0);
	}
			
	usage();	
	return(0);
}
