/*
** $Id: cvspw.c,v 1.1 1999/02/10 23:16:56 tanchs1 Exp $
**
** Add, remove, change entries in 'passwd'.
**
** Copyright (C) 1998  Andreas Huber <ahuber@ping.at>
**
** 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; see the file COPYING. If not, write to
** the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
*/
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <malloc.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <crypt.h>

#define EXIT_USAGE	2
#define EXIT_SIGNAL	3

static char const* argv0 = "cvspw";
static char const passwd_name[] = "passwd";
static char const temp_name[] = "passwd.tmp";

static void usage(void)
{
	printf("usage: %s -add    username password\n", argv0);
	printf("       %s -remove username password\n", argv0);
	printf("       %s -passwd username password new_password\n", argv0);
	exit(EXIT_USAGE);
}

static void log(char const* format, ...)
{
	va_list argp;

	fprintf(stderr, "%s: ", argv0);
	va_start(argp, format);
	vfprintf(stderr, format, argp);
	va_end(argp);
	fputc('\n', stderr);
	fflush(stderr);
}

static void signal_handler(int sig)
{
	fcloseall();
	(void)remove(temp_name);
	log("terminated by %s", sig == SIGINT ? "SIGINT" : "SIGBREAK");
	exit(EXIT_SIGNAL);
}

int main(int argc, char const* argv[])
{
	int option, result;
	char const* username;
	char const* password;
	char const* new_password;
	FILE* passwd_file;
	FILE* temp_file;
	char buffer[BUFSIZ];

	argv0 = (argv0 = strrchr(argv[0], '\\')) == NULL &&
			(argv0 = strrchr(argv[0], '/')) == NULL &&
			(argv0 = strrchr(argv[0], ':')) == NULL ? argv[0] : argv0+1;
	(void)signal(SIGINT, signal_handler);
	(void)signal(SIGBREAK, signal_handler);
	if (argc < 2)
		usage();
	if (strncmp(argv[1], "-add", strlen(argv[1])) == 0)
		option = 'a';
	else if (strncmp(argv[1], "-remove", strlen(argv[1])) == 0)
		option = 'r';
	else if (strncmp(argv[1], "-passwd", strlen(argv[1])) == 0)
		option = 'p';
	else
		option = -1;
	if (option < 0 || argc != (option == 'p' ? 5 : 4))
		usage();
	result = -1;
	passwd_file = temp_file = NULL;
	username = argv[2];
	new_password = NULL;
	if ((password = strdup(crypt(argv[3], argv[3]))) == NULL) {
		errno = ENOMEM;
		perror("strdup");
		goto cleanup;
	}
	if (option == 'p' &&
			(new_password = strdup(crypt(argv[4], argv[4]))) == NULL) {
		errno = ENOMEM;
		perror("strdup");
		goto cleanup;
	}
	if ((passwd_file = fopen(passwd_name, "rt")) == NULL) {
		if (errno != ENOENT || option != 'a') {
			perror(passwd_name);
			goto cleanup;
		}
	}
	if ((temp_file = fopen(temp_name, "wt")) == NULL) {
		perror(temp_name);
		goto cleanup;
	}
	result = 0;
	if (passwd_file == NULL)
		log("file '%s' doesn't exist - created", passwd_name);
	else while (fgets(buffer, sizeof(buffer), passwd_file) == buffer) {
		char const* old_username;
		char const* old_password;

		old_username = strtok(buffer, ":\n");
		old_password = strtok(NULL, ":\n");
		if (old_username == NULL || old_password == NULL ||
				strtok(NULL, "\n") != NULL) {
			log("error while reading file '%s' - aborted",
				passwd_name);
			result = -1;
			break;
		}
		if (strcmp(old_username, username) == 0) {
			if (result == 1 || option == 'a') {
				log("user '%s' already exists - aborted", old_username);
				result = -1;
				break;
			}
			else if (strcmp(old_password, password) != 0) {
				log("password doesn't match for user '%s'"
					" - aborted", old_username);
				result = -1;
				break;
			}
			else if (option == 'r') {
				result = 1;
				continue;
			}
			else if (option == 'p') {
				fprintf(temp_file, "%s:%s\n", username, new_password);
				result = 1;
			}
		}
		else
			fprintf(temp_file, "%s:%s\n", old_username, old_password);
	}
	if (passwd_file != NULL && ferror(passwd_file)) {
		perror(passwd_name);
		goto cleanup;
	}
	if (result == 0) {
		if (option == 'a') {
			fprintf(temp_file, "%s:%s\n", username, password);
			result = 1;
		}
		else {
			log("user '%s' not found - aborted", username);
			result = -1;
		}
	}
cleanup:
	if (temp_file != NULL)
		(void)fclose(temp_file);
	(void)signal(SIGINT, SIG_IGN);
	(void)signal(SIGBREAK, SIG_IGN);
	if (passwd_file != NULL) {
		(void)fclose(passwd_file);
		if (result == 1 && (chmod(passwd_name, S_IREAD|S_IWRITE) < 0 ||
				remove(passwd_name) < 0)) {
			perror(passwd_name);
			result = -1;
		}
	}
	if (result <= 0) {
		if (remove(temp_name) < 0 && errno != ENOENT) {
			perror(temp_name);
			result = -1;
		}
	}
	else if (rename(temp_name, passwd_name) < 0 ||
			chmod(passwd_name, S_IREAD) < 0) {
		perror(temp_name);
		result = -1;
	}
	free((void*)password);
	free((void*)new_password);
	return result < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

