projects/pwm

view pwm.c @ 26:5bdea77d0c1d

Add pwm-clip utility for setting the X11 CLIPBOARD selection
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Thu Sep 21 09:45:59 2017 +0200 (2017-09-21)
parents ec01c579024a
children 722a45b4028b
line source
1 /*
2 * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
24 #include "compat.h"
26 #ifdef HAVE_ERR_H
27 #include <err.h>
28 #endif /* HAVE_ERR_H */
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <langinfo.h>
32 #include <locale.h>
33 #include <pwd.h>
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <pwd.h>
38 #include <sys/stat.h>
39 #include <time.h>
40 #include <unistd.h>
42 #include "pwm.h"
43 #include "cmd.h"
44 #include "io.h"
45 #include "pwfile.h"
46 #include "tok.h"
47 #include "util.h"
49 static void
50 usage(void)
51 {
52 fprintf(stderr, "usage: %s [-P file] [-R] [filename]\n", getprogname());
53 }
55 void
56 pwm_err(struct pwm_ctx *ctx, char *fmt, ...)
57 {
58 va_list args;
60 free(ctx->errmsg);
62 if (fmt != NULL) {
63 va_start(args, fmt);
64 xvasprintf(&ctx->errmsg, fmt, args);
65 va_end(args);
67 fprintf(stderr, "%s\n", ctx->errmsg);
68 } else {
69 ctx->errmsg = NULL;
70 }
71 }
73 void
74 pwm_block_signals(void)
75 {
76 sigset_t set;
78 sigemptyset(&set);
79 sigaddset(&set, SIGINT);
80 sigaddset(&set, SIGTERM);
81 sigaddset(&set, SIGHUP);
82 sigaddset(&set, SIGQUIT);
83 if (sigprocmask(SIG_BLOCK, &set, NULL) != 0) {
84 err(1, "sigprocmask");
85 }
86 }
88 void
89 pwm_unblock_signals(void)
90 {
91 sigset_t set;
93 sigemptyset(&set);
94 sigaddset(&set, SIGINT);
95 sigaddset(&set, SIGTERM);
96 sigaddset(&set, SIGHUP);
97 sigaddset(&set, SIGQUIT);
98 if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) {
99 err(1, "sigprocmask");
100 }
101 }
103 static int
104 run_input_loop(struct pwm_ctx *ctx)
105 {
106 int retval = -1;
107 char prompt[8 + 2 + 1];
108 GetLine *gl = NULL;
109 char buf[PWM_LINE_MAX + 1];
110 int io_retval;
111 int argc = 0;
112 char **argv = NULL;
113 struct cmd *cmd;
114 int i;
116 snprintf(prompt, sizeof (prompt), "%.*s> ", 8, getprogname());
118 pwm_block_signals();
120 /* initialize libtecla */
121 gl = new_GetLine(PWM_LINE_MAX, PWM_HISTORY_MAX);
122 if (gl == NULL) {
123 err(1, "new_GetLine");
124 }
125 gl_catch_blocked(gl);
126 gl_limit_history(gl, PWM_HISTORY_LINES_MAX);
127 /* disable default filename completion */
128 gl_customize_completion(gl, NULL, io_gl_complete_nothing);
130 for (;;) {
131 /* read next line */
132 cmd = NULL;
133 buf[0] = '\0';
134 io_retval = io_get_line(gl, prompt, 1, NULL, 0,
135 sizeof (buf), buf);
136 switch (io_retval) {
137 case IO_OK:
138 break;
139 case IO_TRUNCATED:
140 /* line was truncated in non-interactive mode */
141 fprintf(stderr, "line too long\n");
142 goto out;
143 case IO_EOF:
144 if (ctx->is_interactive) {
145 /* treat as "q" command */
146 strcpy(buf, "q\n");
147 io_retval = IO_OK;
148 break;
149 }
150 /* FALLTHORUGH */
151 case IO_SIGNAL:
152 if (ctx->unsaved_changes) {
153 pwfile_write_autosave_file(ctx);
154 }
155 goto quit;
156 default:
157 fprintf(stderr, "unknown error\n");
158 goto quit;
159 }
161 /* tokenize line */
162 switch (tok_tokenize(buf, &argc, &argv)) {
163 case TOK_ERR_SYSTEM_ERROR:
164 err(1, "tok_tokenize");
165 case TOK_ERR_UNTERMINATED_QUOTE:
166 fprintf(stderr, "unterminated quote\n");
167 if (!ctx->is_interactive) {
168 goto out;
169 }
170 goto next;
171 case TOK_ERR_TRAILING_BACKSLASH:
172 fprintf(stderr, "trailing backslash\n");
173 if (!ctx->is_interactive) {
174 goto out;
175 }
176 goto next;
177 }
179 if (argc == 0) {
180 /* empty line */
181 goto next;
182 }
184 /* find and execute the command */
185 cmd = cmd_match(argv[0]);
186 if (cmd == NULL) {
187 pwm_err(ctx, "unknown command: %s", argv[0]);
188 if (ctx->is_interactive) {
189 goto next;
190 } else {
191 goto out;
192 }
193 }
194 switch (cmd->cmd_func(ctx, argc, argv)) {
195 case CMD_OK:
196 pwm_err(ctx, NULL); /* clear error */
197 break;
198 case CMD_USAGE:
199 fprintf(stderr, "usage: %s\n", cmd->usage);
200 case CMD_ERR: /* FALLTHROUGH */
201 if (!ctx->is_interactive) {
202 goto out;
203 }
204 break;
205 case CMD_SIGNAL:
206 fprintf(stderr, "received signal, quitting\n");
207 case CMD_QUIT: /* FALLTHROUGH */
208 goto quit;
209 }
210 ctx->prev_cmd = cmd->full_cmd;
212 next:
213 for (i = 0; i < argc; i++) {
214 free(argv[i]);
215 }
216 free(argv);
217 argc = 0;
218 argv = NULL;
219 }
221 quit:
222 retval = 0;
224 out:
225 for (i = 0; i < argc; i++) {
226 free(argv[i]);
227 }
228 free(argv);
229 del_GetLine(gl);
231 return (retval);
232 }
234 int
235 pwm_read_password(struct pwm_ctx *ctx, int is_new_password)
236 {
237 switch (io_get_password(is_new_password ? "New Password:" :
238 "Password:", is_new_password ? "Confirm Password:" : NULL,
239 sizeof (ctx->password), ctx->password)) {
240 case IO_OK:
241 return (0);
242 case IO_SIGNAL:
243 return (-2);
244 case IO_PASSWORD_EMPTY:
245 pwm_err(ctx, "password must not be empty");
246 return (-1);
247 case IO_PASSWORD_MISMATCH:
248 pwm_err(ctx, "passwords do not match");
249 return (-1);
250 default:
251 return (-1);
252 }
253 }
255 static int
256 read_password_from_file(const char *filename, char *password,
257 size_t password_size)
258 {
259 int retval = -1;
260 char *buf = NULL;
261 FILE *fp = NULL;
262 size_t password_len = 0;
264 buf = xmalloc(password_size);
266 fp = fopen(filename, "r");
267 if (fp == NULL) {
268 warn("failed to open master password file \"%s\"", filename);
269 goto out;
270 }
271 if (fgets(buf, password_size, fp) == NULL) {
272 /* read error or empty file */
273 if (ferror(fp)) {
274 /* read error */
275 warn("failed to read master password from \"%s\"",
276 filename);
277 }
278 goto out;
279 }
280 password_len = strlen(buf);
281 if (buf[password_len - 1] == '\n') {
282 /* strip trailing newline */
283 password_len--;
284 if (password_len == 0) {
285 /* first line is empty */
286 fprintf(stderr, "malformed password file\n");
287 goto out;
288 }
289 } else if (!feof(fp)) {
290 /* the first line was truncated, password is too long */
291 fprintf(stderr, "malformed password file\n");
292 goto out;
293 }
294 memcpy(password, buf, password_size);
295 retval = 0;
297 out:
298 password[password_len] = '\0';
300 if (fp != NULL) {
301 fclose(fp);
302 }
303 free(buf);
305 return (retval);
306 }
308 int
309 main(int argc, char *argv[])
310 {
311 int status = EXIT_FAILURE;
312 char *locale;
313 int errflag = 0;
314 int c;
315 const char *master_password_filename = NULL;
316 struct pwm_ctx ctx = { 0 };
317 struct passwd *passwd;
318 FILE *fp = NULL;
320 setprogname(argv[0]);
322 /* set up locale and check for UTF-8 codeset */
323 locale = setlocale(LC_ALL, "");
324 if (locale == NULL) {
325 errx(1, "invalid locale");
326 }
327 if ((strcasecmp(nl_langinfo(CODESET), "UTF-8") != 0) &&
328 (strcasecmp(nl_langinfo(CODESET), "UTF8") != 0)) {
329 fprintf(stderr, "pwm requires a locale with UTF-8 character "
330 "encoding.\n");
331 goto out;
332 }
334 /* timestamps are processed as UTC */
335 if (setenv("TZ", "", 1) != 0) {
336 goto out;
337 }
338 tzset();
340 /* initialize libpws */
341 if (pws_init() != 0) {
342 goto out;
343 }
345 ctx.is_interactive = isatty(STDIN_FILENO);
347 while (!errflag && (c = getopt(argc, argv, "P:Rh")) != -1) {
348 switch (c) {
349 case 'P':
350 master_password_filename = optarg;
351 break;
352 case 'R':
353 ctx.is_readonly = 1;
354 break;
355 case 'h':
356 usage();
357 status = EXIT_SUCCESS;
358 goto out;
359 default:
360 errflag = 1;
361 }
362 }
363 if (errflag || (optind + 1 < argc)) {
364 usage();
365 status = EXIT_USAGE;
366 goto out;
367 }
369 passwd = getpwuid(getuid());
370 if (passwd == NULL) {
371 err(1, "getpwuid");
372 }
373 xasprintf(&ctx.dirname, "%s/.pwm", passwd->pw_dir);
375 /* create ~/.pwm directory if necessary */
376 if ((mkdir(ctx.dirname, S_IRWXU) != 0) && (errno != EEXIST)) {
377 warn("failed to create directory \"%s\"", ctx.dirname);
378 goto out;
379 }
381 if (optind == argc) {
382 xasprintf(&ctx.filename, "%s/pwm.psafe3", ctx.dirname);
383 } else if (optind + 1 == argc) {
384 ctx.filename = xstrdup(argv[optind++]);
385 } else {
386 usage();
387 status = EXIT_USAGE;
388 goto out;
389 }
391 if (ctx.is_interactive) {
392 printf("pwm version %s\n", VERSION);
393 } else if (master_password_filename == NULL) {
394 fprintf(stderr, "master password file must be specified when "
395 "running non-interactively\n");
396 goto out;
397 }
399 pwfile_init(&ctx);
401 fp = fopen(ctx.filename, "r");
402 if ((fp == NULL) && (errno != ENOENT)) {
403 warn("failed to open password database");
404 goto out;
405 }
406 /* obtain master password */
407 if (master_password_filename != NULL) {
408 if (read_password_from_file(master_password_filename,
409 ctx.password, sizeof (ctx.password)) != 0) {
410 goto out;
411 }
412 } else if (pwm_read_password(&ctx, (fp == NULL)) != 0) {
413 goto out;
414 }
415 if (fp != NULL) {
416 if (pwfile_read_file(&ctx, fp) != 0) {
417 goto out;
418 }
419 fclose(fp);
420 fp = NULL;
421 }
423 /* run main input loop */
424 status = (run_input_loop(&ctx) != 0);
426 out:
427 pwfile_destroy(&ctx);
428 if (fp != NULL) {
429 fclose(fp);
430 }
431 free(ctx.filename);
432 free(ctx.dirname);
433 free(ctx.errmsg);
435 exit(status);
436 }