Mercurial > projects > pwm
view pwm.c @ 32:b5ebed168e59
Only invoke pager in the help command for the summary of all commands
Display usage information for a single command directly and prefix it with
"usage:" like usage error messages.
author | Guido Berhoerster <guido+pwm@berhoerster.name> |
---|---|
date | Tue, 28 Nov 2017 17:16:24 +0100 |
parents | 2552eec9b913 |
children | 2a8298bafec2 |
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 <fcntl.h> #include <langinfo.h> #include <locale.h> #include <pwd.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 "io.h" #include "macro.h" #include "pwfile.h" #include "pwmrc.h" #include "tok.h" #include "util.h" static void usage(void) { fprintf(stderr, "usage: %s [-P file] [-R] [filename]\n", getprogname()); } void pwm_err(struct pwm_ctx *ctx, char *fmt, ...) { va_list args; free(ctx->errmsg); if (fmt != NULL) { va_start(args, fmt); xvasprintf(&ctx->errmsg, fmt, args); va_end(args); fprintf(stderr, "%s\n", ctx->errmsg); } else { ctx->errmsg = NULL; } } void pwm_block_signals(void) { sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGTERM); sigaddset(&set, SIGHUP); sigaddset(&set, SIGQUIT); if (sigprocmask(SIG_BLOCK, &set, NULL) != 0) { err(1, "sigprocmask"); } } void pwm_unblock_signals(void) { sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGTERM); sigaddset(&set, SIGHUP); sigaddset(&set, SIGQUIT); if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) { err(1, "sigprocmask"); } } static int run_input_loop(struct pwm_ctx *ctx) { int retval = -1; char prompt[8 + 2 + 1]; GetLine *gl = NULL; char buf[PWM_LINE_MAX + 1]; int io_retval; size_t tokenc = 0; union tok **tokenv = NULL; int argc = 0; char **argv = NULL; struct cmd *cmd; int i; snprintf(prompt, sizeof (prompt), "%.*s> ", 8, getprogname()); pwm_block_signals(); /* initialize libtecla */ gl = new_GetLine(PWM_LINE_MAX, PWM_HISTORY_MAX); if (gl == NULL) { err(1, "new_GetLine"); } gl_catch_blocked(gl); gl_limit_history(gl, PWM_HISTORY_LINES_MAX); /* disable default filename completion */ gl_customize_completion(gl, NULL, io_gl_complete_nothing); for (;;) { /* read next line */ cmd = NULL; buf[0] = '\0'; io_retval = io_get_line(gl, prompt, 1, NULL, 0, sizeof (buf), buf); switch (io_retval) { case IO_OK: break; case IO_TRUNCATED: /* line was truncated in non-interactive mode */ fprintf(stderr, "line too long\n"); goto out; case IO_EOF: if (ctx->is_interactive) { /* treat as "q" command */ strcpy(buf, "q\n"); io_retval = IO_OK; break; } /* FALLTHORUGH */ case IO_SIGNAL: if (ctx->unsaved_changes) { pwfile_write_autosave_file(ctx); } goto quit; default: fprintf(stderr, "unknown error\n"); goto quit; } /* tokenize line */ switch (tok_tokenize(buf, &tokenc, &tokenv)) { case TOK_ERR_UNTERMINATED_QUOTE: fprintf(stderr, "unterminated quote\n"); if (!ctx->is_interactive) { goto out; } goto next; case TOK_ERR_TRAILING_BACKSLASH: fprintf(stderr, "trailing backslash\n"); if (!ctx->is_interactive) { goto out; } goto next; case TOK_ERR_INVALID_MACRO_NAME: fprintf(stderr, "invalid macro name\n"); if (!ctx->is_interactive) { goto out; } goto next; case TOK_ERR_OK: break; } /* expand macros */ if (macro_expand_macros(ctx->macro_head, tokenc, tokenv, &argc, &argv) != 0) { fprintf(stderr, "undefined macro\n"); if (!ctx->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) { pwm_err(ctx, "unknown command: %s", argv[0]); if (ctx->is_interactive) { goto next; } else { goto out; } } switch (cmd->cmd_func(ctx, argc, argv)) { case CMD_OK: pwm_err(ctx, NULL); /* clear error */ break; case CMD_USAGE: fprintf(stderr, "usage: %s\n", cmd->usage); case CMD_ERR: /* FALLTHROUGH */ if (!ctx->is_interactive) { goto out; } break; case CMD_SIGNAL: fprintf(stderr, "received signal, quitting\n"); case CMD_QUIT: /* FALLTHROUGH */ goto quit; default: break; } ctx->prev_cmd = cmd->full_cmd; next: for (i = 0; i < argc; i++) { free(argv[i]); } free(argv); argc = 0; argv = NULL; tok_free(tokenv); tokenc = 0; tokenv = NULL; } quit: retval = 0; out: for (i = 0; i < argc; i++) { free(argv[i]); } free(argv); tok_free(tokenv); del_GetLine(gl); return (retval); } int pwm_read_password(struct pwm_ctx *ctx, int is_new_password) { switch (io_get_password(is_new_password ? "New Password:" : "Password:", is_new_password ? "Confirm Password:" : NULL, sizeof (ctx->password), ctx->password)) { case IO_OK: return (0); case IO_SIGNAL: return (-2); case IO_PASSWORD_EMPTY: pwm_err(ctx, "password must not be empty"); return (-1); case IO_PASSWORD_MISMATCH: pwm_err(ctx, "passwords do not match"); return (-1); default: return (-1); } } 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 errflag = 0; int c; const char *master_password_filename = NULL; struct pwm_ctx ctx = { 0 }; struct passwd *passwd; FILE *fp = NULL; 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; } ctx.is_interactive = isatty(STDIN_FILENO); macro_init(&ctx.macro_head); while (!errflag && (c = getopt(argc, argv, "P:Rh")) != -1) { switch (c) { case 'P': master_password_filename = optarg; break; case 'R': ctx.is_readonly = 1; break; case 'h': usage(); status = EXIT_SUCCESS; goto out; default: errflag = 1; } } if (errflag || (optind + 1 < argc)) { usage(); status = EXIT_USAGE; goto out; } passwd = getpwuid(getuid()); if (passwd == NULL) { err(1, "getpwuid"); } xasprintf(&ctx.dirname, "%s/.pwm", passwd->pw_dir); /* create ~/.pwm directory if necessary */ if ((mkdir(ctx.dirname, S_IRWXU) != 0) && (errno != EEXIST)) { warn("failed to create directory \"%s\"", ctx.dirname); goto out; } if (optind == argc) { xasprintf(&ctx.filename, "%s/pwm.psafe3", ctx.dirname); } else if (optind + 1 == argc) { ctx.filename = xstrdup(argv[optind++]); } else { usage(); status = EXIT_USAGE; goto out; } if (ctx.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; } /* read ~/.pwm/pwmrc */ if (pwmrc_read(&ctx) != 0) { 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 (pwm_read_password(&ctx, (fp == NULL)) != 0) { 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) != 0); out: macro_destroy(ctx.macro_head); pwfile_destroy(&ctx); if (fp != NULL) { fclose(fp); } free(ctx.filename); free(ctx.dirname); free(ctx.errmsg); free(ctx.pipecmd); exit(status); }