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