Mercurial > projects > pwm
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); +}