Mercurial > projects > pwm
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:a7e41e1a79c8 |
---|---|
1 /* | |
2 * Copyright (C) 2016 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 */ | |
23 | |
24 #include "compat.h" | |
25 | |
26 #ifdef HAVE_ERR_H | |
27 #include <err.h> | |
28 #endif /* HAVE_ERR_H */ | |
29 #include <errno.h> | |
30 #include <langinfo.h> | |
31 #include <locale.h> | |
32 #include <pwd.h> | |
33 #ifdef HAVE_READPASSPHRASE_H | |
34 #include <readpassphrase.h> | |
35 #endif /* READPASSPHRASE_H */ | |
36 #include <stdlib.h> | |
37 #include <stdio.h> | |
38 #include <string.h> | |
39 #include <pwd.h> | |
40 #include <sys/stat.h> | |
41 #include <time.h> | |
42 #include <unistd.h> | |
43 | |
44 #include "pwm.h" | |
45 #include "cmd.h" | |
46 #include "pwfile.h" | |
47 #include "tok.h" | |
48 #include "util.h" | |
49 | |
50 #ifndef PWM_LINE_MAX | |
51 #define PWM_LINE_MAX 16384 | |
52 #endif /* !PWM_LINE_MAX */ | |
53 | |
54 static void | |
55 usage(void) | |
56 { | |
57 fprintf(stderr, "usage: %s [-P file] [filename]\n", getprogname()); | |
58 } | |
59 | |
60 static int | |
61 run_input_loop(struct pwm_ctx *ctx, int is_interactive) | |
62 { | |
63 int retval = -1; | |
64 char buf[PWM_LINE_MAX]; | |
65 int c; | |
66 int argc = 0; | |
67 char **argv = NULL; | |
68 struct cmd *cmd; | |
69 int i; | |
70 | |
71 for (;;) { | |
72 if (fgets(buf, (int)sizeof (buf), stdin) == NULL) { | |
73 if (ferror(stdin)) { | |
74 /* error */ | |
75 warn("failed to read command"); | |
76 goto out; | |
77 } else if (feof(stdin)) { | |
78 /* EOF */ | |
79 break; | |
80 } | |
81 } | |
82 if ((buf[strlen(buf) - 1] != '\n') && !feof(stdin)) { | |
83 /* line was truncated */ | |
84 fprintf(stderr, "line too long\n"); | |
85 if (is_interactive) { | |
86 /* skip input to next newline */ | |
87 do { | |
88 errno = 0; | |
89 c = fgetc(stdin); | |
90 if ((c == EOF) && (errno != 0)) { | |
91 warn("failed to read command"); | |
92 goto out; | |
93 } | |
94 } while ((c != '\n') && (c != EOF)); | |
95 } else { | |
96 /* fatal error in non-interactive mode */ | |
97 goto out; | |
98 } | |
99 } | |
100 | |
101 /* tokenize line */ | |
102 switch(tok_tokenize(buf, &argc, &argv)) { | |
103 case TOK_ERR_SYSTEM_ERROR: | |
104 err(1, "tok_tokenize"); | |
105 case TOK_ERR_UNTERMINATED_QUOTE: | |
106 fprintf(stderr, "unterminated quote\n"); | |
107 if (!is_interactive) { | |
108 goto out; | |
109 } | |
110 goto next; | |
111 case TOK_ERR_TRAILING_BACKSLASH: | |
112 fprintf(stderr, "trailing backslash\n"); | |
113 if (!is_interactive) { | |
114 goto out; | |
115 } | |
116 goto next; | |
117 } | |
118 | |
119 if (argc == 0) { | |
120 /* empty line */ | |
121 goto next; | |
122 } | |
123 | |
124 /* find and execute the command */ | |
125 cmd = cmd_match(argv[0]); | |
126 if (cmd == NULL) { | |
127 fprintf(stderr, "unknown command: %s\n", argv[0]); | |
128 if (is_interactive) { | |
129 goto next; | |
130 } else { | |
131 goto out; | |
132 } | |
133 } | |
134 switch (cmd->cmd_func(ctx, argc, argv)) { | |
135 case CMD_OK: | |
136 break; | |
137 case CMD_USAGE: | |
138 fprintf(stderr, "usage: %s\n", cmd->usage); | |
139 case CMD_ERR: /* FALLTHROUGH */ | |
140 if (!is_interactive) { | |
141 goto out; | |
142 } | |
143 break; | |
144 case CMD_QUIT: | |
145 goto quit; | |
146 } | |
147 | |
148 next: | |
149 for (i = 0; i < argc; i++) { | |
150 free(argv[i]); | |
151 } | |
152 free(argv); | |
153 argc = 0; | |
154 argv = NULL; | |
155 } | |
156 | |
157 quit: | |
158 retval = 0; | |
159 | |
160 out: | |
161 for (i = 0; i < argc; i++) { | |
162 free(argv[i]); | |
163 } | |
164 free(argv); | |
165 | |
166 return (retval); | |
167 } | |
168 | |
169 static int | |
170 read_password_from_file(const char *filename, char *password, | |
171 size_t password_size) | |
172 { | |
173 int retval = -1; | |
174 char *buf = NULL; | |
175 FILE *fp = NULL; | |
176 size_t password_len = 0; | |
177 | |
178 buf = xmalloc(password_size); | |
179 | |
180 fp = fopen(filename, "r"); | |
181 if (fp == NULL) { | |
182 warn("failed to open master password file \"%s\"", filename); | |
183 goto out; | |
184 } | |
185 if (fgets(buf, password_size, fp) == NULL) { | |
186 /* read error or empty file */ | |
187 if (ferror(fp)) { | |
188 /* read error */ | |
189 warn("failed to read master password from \"%s\"", | |
190 filename); | |
191 } | |
192 goto out; | |
193 } | |
194 password_len = strlen(buf); | |
195 if (buf[password_len - 1] == '\n') { | |
196 /* strip trailing newline */ | |
197 password_len--; | |
198 if (password_len == 0) { | |
199 /* first line is empty */ | |
200 goto out; | |
201 } | |
202 } else if (!feof(fp)) { | |
203 /* the first line was truncated, password is too long */ | |
204 goto out; | |
205 } | |
206 memcpy(password, buf, password_size); | |
207 retval = 0; | |
208 | |
209 out: | |
210 password[password_len] = '\0'; | |
211 | |
212 if (fp != NULL) { | |
213 fclose(fp); | |
214 } | |
215 free(buf); | |
216 | |
217 return (retval); | |
218 } | |
219 | |
220 int | |
221 main(int argc, char *argv[]) | |
222 { | |
223 int status = EXIT_FAILURE; | |
224 char *locale; | |
225 int is_interactive; | |
226 int errflag = 0; | |
227 int c; | |
228 const char *master_password_filename = NULL; | |
229 struct pwm_ctx ctx = { 0 }; | |
230 struct passwd *passwd; | |
231 char *pwm_dirname = NULL; | |
232 FILE *fp = NULL; | |
233 char password_buf[PWS3_MAX_PASSWORD_LEN + 1]; | |
234 | |
235 setprogname(argv[0]); | |
236 | |
237 /* set up locale and check for UTF-8 codeset */ | |
238 locale = setlocale(LC_ALL, ""); | |
239 if (locale == NULL) { | |
240 errx(1, "invalid locale"); | |
241 } | |
242 if ((strcasecmp(nl_langinfo(CODESET), "UTF-8") != 0) && | |
243 (strcasecmp(nl_langinfo(CODESET), "UTF8") != 0)) { | |
244 fprintf(stderr, "pwm requires a locale with UTF-8 character " | |
245 "encoding.\n"); | |
246 goto out; | |
247 } | |
248 | |
249 /* timestamps are processed as UTC */ | |
250 if (setenv("TZ", "", 1) != 0) { | |
251 goto out; | |
252 } | |
253 tzset(); | |
254 | |
255 /* initialize libpws */ | |
256 if (pws_init() != 0) { | |
257 goto out; | |
258 } | |
259 | |
260 is_interactive = isatty(STDIN_FILENO); | |
261 | |
262 while (!errflag && (c = getopt(argc, argv, "P:h")) != -1) { | |
263 switch (c) { | |
264 case 'P': | |
265 master_password_filename = optarg; | |
266 break; | |
267 case 'h': | |
268 usage(); | |
269 status = EXIT_SUCCESS; | |
270 goto out; | |
271 default: | |
272 errflag = 1; | |
273 } | |
274 } | |
275 if (errflag || (optind + 1 < argc)) { | |
276 usage(); | |
277 status = EXIT_USAGE; | |
278 goto out; | |
279 } | |
280 | |
281 if (optind == argc) { | |
282 passwd = getpwuid(getuid()); | |
283 if (passwd == NULL) { | |
284 err(1, "getpwuid"); | |
285 } | |
286 xasprintf(&pwm_dirname, "%s/.pwm", passwd->pw_dir); | |
287 xasprintf(&ctx.filename, "%s/pwm.psafe3", pwm_dirname); | |
288 | |
289 /* create ~/.pwm directory if necessary */ | |
290 if ((mkdir(pwm_dirname, S_IRWXU) != 0) && (errno != EEXIST)) { | |
291 warn("failed to create directory \"%s\"", pwm_dirname); | |
292 goto out; | |
293 } | |
294 } else if (optind + 1 == argc) { | |
295 ctx.filename = xstrdup(argv[optind++]); | |
296 } else { | |
297 usage(); | |
298 status = EXIT_USAGE; | |
299 goto out; | |
300 } | |
301 | |
302 if (is_interactive) { | |
303 printf("pwm version %s\n", VERSION); | |
304 } else if (master_password_filename == NULL) { | |
305 fprintf(stderr, "master password file must be specified when " | |
306 "running non-interactively\n"); | |
307 goto out; | |
308 } | |
309 | |
310 pwfile_init(&ctx); | |
311 | |
312 fp = fopen(ctx.filename, "r"); | |
313 if ((fp == NULL) && (errno != ENOENT)) { | |
314 warn("failed to open password database"); | |
315 goto out; | |
316 } | |
317 /* obtain master password */ | |
318 if (master_password_filename != NULL) { | |
319 if (read_password_from_file(master_password_filename, | |
320 ctx.password, sizeof (ctx.password)) != 0) { | |
321 fprintf(stderr, "malformed password database\n"); | |
322 goto out; | |
323 } | |
324 } else { | |
325 if (readpassphrase("Enter password: ", ctx.password, | |
326 sizeof (ctx.password), RPP_ECHO_OFF | RPP_REQUIRE_TTY) == | |
327 NULL) { | |
328 err(1, "readpassphrase"); | |
329 } | |
330 if (ctx.password[0] == '\0') { | |
331 fprintf(stderr, "password must not be empty\n"); | |
332 goto out; | |
333 } | |
334 if (fp == NULL) { | |
335 if (readpassphrase("Confirm password: ", password_buf, | |
336 sizeof (password_buf), | |
337 RPP_ECHO_OFF | RPP_REQUIRE_TTY) == NULL) { | |
338 err(1, "readpassphrase"); | |
339 } | |
340 if (strcmp(ctx.password, password_buf) != 0) { | |
341 fprintf(stderr, "passwords do not match\n"); | |
342 goto out; | |
343 } | |
344 } | |
345 } | |
346 if (fp != NULL) { | |
347 if (pwfile_read_file(&ctx, fp) != 0) { | |
348 goto out; | |
349 } | |
350 fclose(fp); | |
351 fp = NULL; | |
352 } | |
353 | |
354 /* run main input loop */ | |
355 status = (run_input_loop(&ctx, is_interactive) != 0); | |
356 | |
357 out: | |
358 pwfile_destroy(&ctx); | |
359 if (fp != NULL) { | |
360 fclose(fp); | |
361 } | |
362 free(ctx.filename); | |
363 free(pwm_dirname); | |
364 | |
365 exit(status); | |
366 } |