diff pwm.c @ 0:a7e41e1a79c8

Initial revision
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Thu, 19 Jan 2017 22:39:51 +0100
parents
children 0b1bce8db371
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pwm.c	Thu Jan 19 22:39:51 2017 +0100
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2016 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 (;;) {
+		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:
+		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 */
+			goto out;
+		}
+	} else if (!feof(fp)) {
+		/* the first line was truncated, password is too long */
+		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) {
+			fprintf(stderr, "malformed password database\n");
+			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);
+}