view pwm.c @ 13:cf81eb0c2d5a

Warn before quitting if there are unsaved changes If there are unsaved changes emit a warning when the quit command is used. Only quit if the quit command is used twice with no other command in between. Add a new Quit command which immediatly quits pwm without a warning.
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Mon, 07 Aug 2017 16:59:47 +0200
parents 0b1bce8db371
children a07665727c19
line wrap: on
line source

/*
 * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "compat.h"

#ifdef	HAVE_ERR_H
#include <err.h>
#endif /* HAVE_ERR_H */
#include <errno.h>
#include <langinfo.h>
#include <locale.h>
#include <pwd.h>
#ifdef	HAVE_READPASSPHRASE_H
#include <readpassphrase.h>
#endif /* READPASSPHRASE_H */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#include "pwm.h"
#include "cmd.h"
#include "pwfile.h"
#include "tok.h"
#include "util.h"

#ifndef	PWM_LINE_MAX
#define	PWM_LINE_MAX	16384
#endif /* !PWM_LINE_MAX */

static void
usage(void)
{
	fprintf(stderr, "usage: %s [-P file] [filename]\n", getprogname());
}

static int
run_input_loop(struct pwm_ctx *ctx, int is_interactive)
{
	int		retval = -1;
	char		buf[PWM_LINE_MAX];
	int		c;
	int		argc = 0;
	char		**argv = NULL;
	struct cmd	*cmd;
	int		i;

	for (;;) {
		cmd = NULL;
		if (fgets(buf, (int)sizeof (buf), stdin) == NULL) {
			if (ferror(stdin)) {
				/* error */
				warn("failed to read command");
				goto out;
			} else if (feof(stdin)) {
				/* EOF */
				break;
			}
		}
		if ((buf[strlen(buf) - 1] != '\n') && !feof(stdin)) {
			/* line was truncated */
			fprintf(stderr, "line too long\n");
			if (is_interactive) {
				/* skip input to next newline */
				do {
					errno = 0;
					c = fgetc(stdin);
					if ((c == EOF) && (errno != 0)) {
						warn("failed to read command");
						goto out;
					}
				} while ((c != '\n') && (c != EOF));
			} else {
				/* fatal error in non-interactive mode */
				goto out;
			}
		}

		/* tokenize line */
		switch(tok_tokenize(buf, &argc, &argv)) {
		case TOK_ERR_SYSTEM_ERROR:
			err(1, "tok_tokenize");
		case TOK_ERR_UNTERMINATED_QUOTE:
			fprintf(stderr, "unterminated quote\n");
			if (!is_interactive) {
				goto out;
			}
			goto next;
		case TOK_ERR_TRAILING_BACKSLASH:
			fprintf(stderr, "trailing backslash\n");
			if (!is_interactive) {
				goto out;
			}
			goto next;
		}

		if (argc == 0) {
			/* empty line */
			goto next;
		}

		/* find and execute the command */
		cmd = cmd_match(argv[0]);
		if (cmd == NULL) {
			fprintf(stderr, "unknown command: %s\n", argv[0]);
			if (is_interactive) {
				goto next;
			} else {
				goto out;
			}
		}
		switch (cmd->cmd_func(ctx, argc, argv)) {
		case CMD_OK:
			break;
		case CMD_USAGE:
			fprintf(stderr, "usage: %s\n", cmd->usage);
		case CMD_ERR:	/* FALLTHROUGH */
			if (!is_interactive) {
				goto out;
			}
			break;
		case CMD_QUIT:
			goto quit;
		}

next:
		if (cmd != NULL) {
			ctx->prev_cmd = cmd->full_cmd;
		}

		for (i = 0; i < argc; i++) {
			free(argv[i]);
		}
		free(argv);
		argc = 0;
		argv = NULL;
	}

quit:
	retval = 0;

out:
	for (i = 0; i < argc; i++) {
		free(argv[i]);
	}
	free(argv);

	return (retval);
}

static int
read_password_from_file(const char *filename, char *password,
    size_t password_size)
{
	int	retval = -1;
	char	*buf = NULL;
	FILE	*fp = NULL;
	size_t	password_len = 0;

	buf = xmalloc(password_size);

	fp = fopen(filename, "r");
	if (fp == NULL) {
		warn("failed to open master password file \"%s\"", filename);
		goto out;
	}
	if (fgets(buf, password_size, fp) == NULL) {
		/* read error or empty file */
		if (ferror(fp)) {
			/* read error */
			warn("failed to read master password from \"%s\"",
			    filename);
		}
		goto out;
	}
	password_len = strlen(buf);
	if (buf[password_len - 1] == '\n') {
		/* strip trailing newline */
		password_len--;
		if (password_len == 0) {
			/* first line is empty */
			fprintf(stderr, "malformed password file\n");
			goto out;
		}
	} else if (!feof(fp)) {
		/* the first line was truncated, password is too long */
		fprintf(stderr, "malformed password file\n");
		goto out;
	}
	memcpy(password, buf, password_size);
	retval = 0;

out:
	password[password_len] = '\0';

	if (fp != NULL) {
		fclose(fp);
	}
	free(buf);

	return (retval);
}

int
main(int argc, char *argv[])
{
	int		status = EXIT_FAILURE;
	char		*locale;
	int		is_interactive;
	int		errflag = 0;
	int		c;
	const char	*master_password_filename = NULL;
	struct pwm_ctx	ctx = { 0 };
	struct passwd	*passwd;
	char		*pwm_dirname = NULL;
	FILE		*fp = NULL;
	char		password_buf[PWS3_MAX_PASSWORD_LEN + 1];

	setprogname(argv[0]);

	/* set up locale and check for UTF-8 codeset */
	locale = setlocale(LC_ALL, "");
	if (locale == NULL) {
		errx(1, "invalid locale");
	}
	if ((strcasecmp(nl_langinfo(CODESET), "UTF-8") != 0) &&
	    (strcasecmp(nl_langinfo(CODESET), "UTF8") != 0)) {
		fprintf(stderr, "pwm requires a locale with UTF-8 character "
		    "encoding.\n");
		goto out;
	}

	/* timestamps are processed as UTC */
	if (setenv("TZ", "", 1) != 0) {
		goto out;
	}
	tzset();

	/* initialize libpws */
	if (pws_init() != 0) {
		goto out;
	}

	is_interactive = isatty(STDIN_FILENO);

	while (!errflag && (c = getopt(argc, argv, "P:h")) != -1) {
		switch (c) {
		case 'P':
			master_password_filename = optarg;
			break;
		case 'h':
			usage();
			status = EXIT_SUCCESS;
			goto out;
		default:
			errflag = 1;
		}
	}
	if (errflag || (optind + 1 < argc)) {
		usage();
		status = EXIT_USAGE;
		goto out;
	}

	if (optind == argc) {
		passwd = getpwuid(getuid());
		if (passwd == NULL) {
			err(1, "getpwuid");
		}
		xasprintf(&pwm_dirname, "%s/.pwm", passwd->pw_dir);
		xasprintf(&ctx.filename, "%s/pwm.psafe3", pwm_dirname);

		/* create ~/.pwm directory if necessary */
		if ((mkdir(pwm_dirname, S_IRWXU) != 0) && (errno != EEXIST)) {
			warn("failed to create directory \"%s\"", pwm_dirname);
			goto out;
		}
	} else if (optind + 1 == argc) {
		ctx.filename = xstrdup(argv[optind++]);
	} else {
		usage();
		status = EXIT_USAGE;
		goto out;
	}

	if (is_interactive) {
		printf("pwm version %s\n", VERSION);
	} else if (master_password_filename == NULL) {
		fprintf(stderr, "master password file must be specified when "
		    "running non-interactively\n");
		goto out;
	}

	pwfile_init(&ctx);

	fp = fopen(ctx.filename, "r");
	if ((fp == NULL) && (errno != ENOENT)) {
		warn("failed to open password database");
		goto out;
	}
	/* obtain master password */
	if (master_password_filename != NULL) {
		if (read_password_from_file(master_password_filename,
		    ctx.password, sizeof (ctx.password)) != 0) {
			goto out;
		}
	} else {
		if (readpassphrase("Enter password: ", ctx.password,
		    sizeof (ctx.password), RPP_ECHO_OFF | RPP_REQUIRE_TTY) ==
		    NULL) {
			err(1, "readpassphrase");
		}
		if (ctx.password[0] == '\0') {
			fprintf(stderr, "password must not be empty\n");
			goto out;
		}
		if (fp == NULL) {
			if (readpassphrase("Confirm password: ", password_buf,
			    sizeof (password_buf),
			    RPP_ECHO_OFF | RPP_REQUIRE_TTY) == NULL) {
				err(1, "readpassphrase");
			}
			if (strcmp(ctx.password, password_buf) != 0) {
				fprintf(stderr, "passwords do not match\n");
				goto out;
			}
		}
	}
	if (fp != NULL) {
		if (pwfile_read_file(&ctx, fp) != 0) {
			goto out;
		}
		fclose(fp);
		fp = NULL;
	}

	/* run main input loop */
	status = (run_input_loop(&ctx, is_interactive) != 0);

out:
	pwfile_destroy(&ctx);
	if (fp != NULL) {
		fclose(fp);
	}
	free(ctx.filename);
	free(pwm_dirname);

	exit(status);
}