Mercurial > projects > pwm
comparison cmd.c @ 0:a7e41e1a79c8
Initial revision
author | Guido Berhoerster <guido+pwm@berhoerster.name> |
---|---|
date | Thu, 19 Jan 2017 22:39:51 +0100 |
parents | |
children | b5c4267a7182 |
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 <limits.h> | |
31 #ifdef HAVE_READPASSPHRASE_H | |
32 #include <readpassphrase.h> | |
33 #endif /* READPASSPHRASE_H */ | |
34 #include <stdlib.h> | |
35 #include <string.h> | |
36 #include <unistd.h> | |
37 | |
38 #include "cmd.h" | |
39 #include "pwfile.h" | |
40 #include "util.h" | |
41 | |
42 enum field_type { | |
43 FIELD_UNKNOWN = -1, | |
44 FIELD_GROUP, | |
45 FIELD_TITLE, | |
46 FIELD_USERNAME, | |
47 FIELD_PASSWORD, | |
48 FIELD_NOTES, | |
49 FIELD_URL | |
50 }; | |
51 | |
52 static enum cmd_return cmd_list(struct pwm_ctx *, int, char *[]); | |
53 static enum cmd_return cmd_create(struct pwm_ctx *, int, char *[]); | |
54 static enum cmd_return cmd_modify(struct pwm_ctx *, int, char *[]); | |
55 static enum cmd_return cmd_remove(struct pwm_ctx *, int, char *[]); | |
56 static enum cmd_return cmd_show(struct pwm_ctx *, int, char *[]); | |
57 static enum cmd_return cmd_pipe(struct pwm_ctx *, int, char *[]); | |
58 static enum cmd_return cmd_creategroup(struct pwm_ctx *, int, char *[]); | |
59 static enum cmd_return cmd_removegroup(struct pwm_ctx *, int, char *[]); | |
60 static enum cmd_return cmd_changepassword(struct pwm_ctx *, int, char *[]); | |
61 static enum cmd_return cmd_help(struct pwm_ctx *, int, char *[]); | |
62 static enum cmd_return cmd_write(struct pwm_ctx *, int, char *[]); | |
63 static enum cmd_return cmd_quit(struct pwm_ctx *, int, char *[]); | |
64 | |
65 static const char *field_names[] = { | |
66 "group", | |
67 "title", | |
68 "username", | |
69 "password", | |
70 "notes", | |
71 "url" | |
72 }; | |
73 | |
74 static const char *field_labels[] = { | |
75 "Group: ", | |
76 "Title: ", | |
77 "Username: ", | |
78 "Password: ", | |
79 "Notes: ", | |
80 "URL: " | |
81 }; | |
82 | |
83 static struct cmd cmds[] = { | |
84 { "ls", "list", "list", "List entries", cmd_list }, | |
85 { "c", "create", "create field=value ...", "Create entry", cmd_create }, | |
86 { "m", "modify", "modify id field=value ...", "Modify entry", cmd_modify }, | |
87 { "rm", "remove", "remove id", "Delete entry", cmd_remove }, | |
88 { "s", "show", "show id field", "Show entry", cmd_show }, | |
89 { "p", "pipe", "pipe id field command", "Pipe entry to external command", | |
90 cmd_pipe }, | |
91 { "cg", "creategroup", "creategroup name", "Create empty group", | |
92 cmd_creategroup }, | |
93 { "rg", "removegroup", "removegroup name", "Delete empty group", | |
94 cmd_removegroup }, | |
95 { "ch", "changepassword", "changepassword", "Change password", | |
96 cmd_changepassword }, | |
97 { "h", "help", "help", "Show help text", cmd_help }, | |
98 { "w", "write", "write", "Write the database", cmd_write }, | |
99 { "q", "quit", "quit", "Quit", cmd_quit }, | |
100 { 0 } | |
101 }; | |
102 | |
103 static enum field_type | |
104 parse_field_name(const char *name) | |
105 { | |
106 int i; | |
107 | |
108 for (i = 0; i < (int)COUNTOF(field_names); i++) { | |
109 if (strcmp(field_names[i], name) == 0) { | |
110 return (i); | |
111 } | |
112 } | |
113 | |
114 return (FIELD_UNKNOWN); | |
115 } | |
116 | |
117 static enum field_type | |
118 parse_field_assignment(char *arg, struct record *record) | |
119 { | |
120 int i; | |
121 size_t field_name_len; | |
122 char *value; | |
123 | |
124 for (i = 0; i < (int)COUNTOF(field_names); i++) { | |
125 field_name_len = strlen(field_names[i]); | |
126 if ((strncmp(field_names[i], arg, field_name_len) == 0) && | |
127 (arg[field_name_len] == '=')){ | |
128 value = arg + field_name_len + 1; | |
129 if (*value == '\0') { | |
130 /* skip empty assignments */ | |
131 return (i); | |
132 } | |
133 switch (i) { | |
134 case FIELD_GROUP: | |
135 record->group = value; | |
136 break; | |
137 case FIELD_TITLE: | |
138 record->title = value; | |
139 break; | |
140 case FIELD_USERNAME: | |
141 record->username = value; | |
142 break; | |
143 case FIELD_PASSWORD: | |
144 record->password = value; | |
145 break; | |
146 case FIELD_NOTES: | |
147 record->notes = value; | |
148 break; | |
149 case FIELD_URL: | |
150 record->url = value; | |
151 break; | |
152 default: | |
153 break; | |
154 } | |
155 return (i); | |
156 } | |
157 } | |
158 | |
159 return (FIELD_UNKNOWN); | |
160 } | |
161 | |
162 static int | |
163 parse_id(const char *arg, unsigned int *idp) | |
164 { | |
165 long x; | |
166 char *p; | |
167 | |
168 errno = 0; | |
169 x = strtol(arg, &p, 10); | |
170 if ((errno != 0) || (*arg == '\0') || (*p != '\0') || (x > UINT_MAX) || | |
171 (x <= 0)) { | |
172 return (-1); | |
173 } | |
174 *idp = (unsigned int)x; | |
175 | |
176 return (0); | |
177 } | |
178 | |
179 static enum cmd_return | |
180 cmd_list(struct pwm_ctx *ctx, int argc, char *argv[]) | |
181 { | |
182 union list_item **list; | |
183 size_t i; | |
184 | |
185 if (argc != 1) { | |
186 return (CMD_USAGE); | |
187 } | |
188 | |
189 list = pwfile_create_list(ctx); | |
190 for (i = 0; list[i] != NULL; i++) { | |
191 if (list[i]->any.type == ITEM_TYPE_GROUP) { | |
192 printf("[%s]\n", list[i]->group.group); | |
193 } else { | |
194 printf("%4u %s\n", list[i]->record.id, | |
195 (list[i]->record.title != NULL) ? | |
196 list[i]->record.title : ""); | |
197 } | |
198 } | |
199 pwfile_destroy_list(list); | |
200 | |
201 return (CMD_OK); | |
202 } | |
203 | |
204 static enum cmd_return | |
205 cmd_create(struct pwm_ctx *ctx, int argc, char *argv[]) | |
206 { | |
207 int i; | |
208 struct record record = { 0 }; | |
209 | |
210 if (argc < 2) { | |
211 return (CMD_USAGE); | |
212 } | |
213 | |
214 for (i = 1; i < argc; i++) { | |
215 if (parse_field_assignment(argv[i], &record) == FIELD_UNKNOWN) { | |
216 fprintf(stderr, "bad field assignment \"%s\"\n", | |
217 argv[i]); | |
218 return (CMD_ERR); | |
219 } | |
220 } | |
221 | |
222 pwfile_create_record(ctx, &record); | |
223 | |
224 return (CMD_OK); | |
225 } | |
226 | |
227 static enum cmd_return | |
228 cmd_modify(struct pwm_ctx *ctx, int argc, char *argv[]) | |
229 { | |
230 unsigned int id; | |
231 int i; | |
232 struct record record = { 0 }; | |
233 | |
234 if (argc < 2) { | |
235 return (CMD_USAGE); | |
236 } | |
237 | |
238 if (parse_id(argv[1], &id) != 0) { | |
239 fprintf(stderr, "invalid id %s\n", argv[1]); | |
240 return (CMD_ERR); | |
241 } | |
242 | |
243 for (i = 2; i < argc; i++) { | |
244 if (parse_field_assignment(argv[i], &record) == FIELD_UNKNOWN) { | |
245 fprintf(stderr, "bad field assignment \"%s\"\n", | |
246 argv[i]); | |
247 return (CMD_ERR); | |
248 } | |
249 } | |
250 | |
251 pwfile_modify_record(ctx, id, &record); | |
252 | |
253 return (CMD_OK); | |
254 } | |
255 | |
256 static enum cmd_return | |
257 cmd_remove(struct pwm_ctx *ctx, int argc, char *argv[]) | |
258 { | |
259 unsigned int id; | |
260 | |
261 if (argc != 2) { | |
262 return (CMD_USAGE); | |
263 } | |
264 | |
265 if (parse_id(argv[1], &id) != 0) { | |
266 fprintf(stderr, "invalid id %s\n", argv[1]); | |
267 return (CMD_ERR); | |
268 } | |
269 | |
270 if (pwfile_remove_record(ctx, id) != 0) { | |
271 fprintf(stderr, "failed to remove record %u\n", id); | |
272 return (CMD_ERR); | |
273 } | |
274 | |
275 return (CMD_OK); | |
276 } | |
277 | |
278 static int | |
279 print_field(const char *label, const char *value, int show_label, FILE *fp) | |
280 { | |
281 fprintf(fp, "%s%s\n", show_label ? label : "", (value != NULL) ? | |
282 value : ""); | |
283 if (ferror(fp)) { | |
284 warn("fprintf"); | |
285 return (-1); | |
286 } | |
287 return (0); | |
288 } | |
289 | |
290 static void | |
291 print_record(struct record *record, int fields[], int show_labels, FILE *fp) | |
292 { | |
293 if (fields[FIELD_TITLE]) { | |
294 if (print_field(field_labels[FIELD_TITLE], record->title, | |
295 show_labels, fp) != 0) { | |
296 return; | |
297 } | |
298 } | |
299 if (fields[FIELD_GROUP]) { | |
300 if (print_field(field_labels[FIELD_GROUP], record->group, | |
301 show_labels, fp)) { | |
302 return; | |
303 } | |
304 } | |
305 if (fields[FIELD_USERNAME]) { | |
306 if (print_field(field_labels[FIELD_USERNAME], record->username, | |
307 show_labels, fp)) { | |
308 return; | |
309 } | |
310 } | |
311 if (fields[FIELD_PASSWORD]) { | |
312 if (print_field(field_labels[FIELD_PASSWORD], record->password, | |
313 show_labels, fp)) { | |
314 return; | |
315 } | |
316 } | |
317 if (fields[FIELD_NOTES]) { | |
318 if (print_field(field_labels[FIELD_NOTES], record->notes, | |
319 show_labels, fp)) { | |
320 return; | |
321 } | |
322 } | |
323 if (fields[FIELD_URL]) { | |
324 if (print_field(field_labels[FIELD_URL], record->url, | |
325 show_labels, fp)) { | |
326 return; | |
327 } | |
328 } | |
329 } | |
330 | |
331 static enum cmd_return | |
332 cmd_show(struct pwm_ctx *ctx, int argc, char *argv[]) | |
333 { | |
334 unsigned int id; | |
335 struct record *record; | |
336 int i; | |
337 enum field_type type; | |
338 int fields[COUNTOF(field_names)] = { 0 }; | |
339 | |
340 if (argc < 2) { | |
341 return (CMD_USAGE); | |
342 } | |
343 | |
344 if (parse_id(argv[1], &id) != 0) { | |
345 fprintf(stderr, "invalid id %s\n", argv[1]); | |
346 return (CMD_ERR); | |
347 } | |
348 | |
349 for (i = 2; i < argc; i++) { | |
350 type = parse_field_name(argv[i]); | |
351 if (type < 0) { | |
352 fprintf(stderr, "bad field name \"%s\"\n", argv[i]); | |
353 return (CMD_ERR); | |
354 } | |
355 fields[type] = 1; | |
356 } | |
357 | |
358 record = pwfile_get_record(ctx, id); | |
359 if (record == NULL) { | |
360 fprintf(stderr, "record %u does not exist\n", id); | |
361 return (CMD_ERR); | |
362 } | |
363 print_record(record, fields, 1, stdout); | |
364 pwfile_destroy_record(record); | |
365 | |
366 return (CMD_OK); | |
367 } | |
368 | |
369 static enum cmd_return | |
370 cmd_pipe(struct pwm_ctx *ctx, int argc, char *argv[]) | |
371 { | |
372 enum cmd_return retval = CMD_ERR; | |
373 unsigned int id; | |
374 struct record *record = NULL; | |
375 enum field_type type; | |
376 int fields[COUNTOF(field_names)] = { 0 }; | |
377 FILE *fp = NULL; | |
378 | |
379 if (argc != 4) { | |
380 return (CMD_USAGE); | |
381 } | |
382 | |
383 if (parse_id(argv[1], &id) != 0) { | |
384 fprintf(stderr, "invalid id %s\n", argv[1]); | |
385 return (CMD_ERR); | |
386 } | |
387 | |
388 type = parse_field_name(argv[2]); | |
389 if (type < 0) { | |
390 fprintf(stderr, "bad field name \"%s\"\n", argv[2]); | |
391 return (CMD_ERR); | |
392 } | |
393 fields[type] = 1; | |
394 | |
395 fp = popen(argv[3], "w"); | |
396 if (fp == NULL) { | |
397 warn("popen"); | |
398 goto out; | |
399 } | |
400 | |
401 record = pwfile_get_record(ctx, id); | |
402 if (record == NULL) { | |
403 fprintf(stderr, "record %u does not exist\n", id); | |
404 goto out; | |
405 } | |
406 | |
407 print_record(record, fields, 0, fp); | |
408 | |
409 retval = CMD_OK; | |
410 | |
411 out: | |
412 pwfile_destroy_record(record); | |
413 if (fp != NULL) { | |
414 pclose(fp); | |
415 } | |
416 | |
417 return (retval); | |
418 } | |
419 | |
420 static enum cmd_return | |
421 cmd_creategroup(struct pwm_ctx *ctx, int argc, char *argv[]) | |
422 { | |
423 if (argc != 2) { | |
424 return (CMD_USAGE); | |
425 } | |
426 | |
427 if (pwfile_create_group(ctx, argv[1]) != 0) { | |
428 fprintf(stderr, "group \"%s\" already exists\n", argv[1]); | |
429 return (CMD_ERR); | |
430 } | |
431 | |
432 return (CMD_OK); | |
433 } | |
434 | |
435 static enum cmd_return | |
436 cmd_removegroup(struct pwm_ctx *ctx, int argc, char *argv[]) | |
437 { | |
438 if (argc != 2) { | |
439 return (CMD_USAGE); | |
440 } | |
441 | |
442 if (pwfile_remove_group(ctx, argv[1]) != 0) { | |
443 fprintf(stderr, "group \"%s\" does not exist\n", argv[1]); | |
444 return (CMD_ERR); | |
445 } | |
446 | |
447 return (CMD_OK); | |
448 } | |
449 | |
450 static enum cmd_return | |
451 cmd_changepassword(struct pwm_ctx *ctx, int argc, char *argv[]) | |
452 { | |
453 size_t password_len; | |
454 char password_buf[PWS3_MAX_PASSWORD_LEN + 1]; | |
455 char confirm_buf[PWS3_MAX_PASSWORD_LEN + 1]; | |
456 | |
457 if (argc > 2) { | |
458 return (CMD_USAGE); | |
459 } else if (argc == 2) { | |
460 password_len = strlen(argv[1]); | |
461 if (password_len == 0) { | |
462 fprintf(stderr, "password must not be empty\n"); | |
463 return (CMD_ERR); | |
464 } else if (password_len + 1 > sizeof (ctx->password)) { | |
465 fprintf(stderr, "password too long\n"); | |
466 return (CMD_ERR); | |
467 } | |
468 memcpy(ctx->password, argv[1], password_len + 1); | |
469 } else { | |
470 if (readpassphrase("Enter password: ", password_buf, | |
471 sizeof (password_buf), RPP_ECHO_OFF | RPP_REQUIRE_TTY) == | |
472 NULL) { | |
473 err(1, "readpassphrase"); | |
474 } | |
475 password_len = strlen(password_buf); | |
476 if (password_len == 0) { | |
477 fprintf(stderr, "password must not be empty\n"); | |
478 return (CMD_ERR); | |
479 } | |
480 if (readpassphrase("Confirm password: ", confirm_buf, | |
481 sizeof (confirm_buf), | |
482 RPP_ECHO_OFF | RPP_REQUIRE_TTY) == NULL) { | |
483 err(1, "readpassphrase"); | |
484 } | |
485 if (strcmp(password_buf, confirm_buf) != 0) { | |
486 fprintf(stderr, "passwords do not match\n"); | |
487 return (CMD_ERR); | |
488 } | |
489 memcpy(ctx->password, password_buf, password_len + 1); | |
490 } | |
491 | |
492 return (CMD_OK); | |
493 } | |
494 | |
495 static enum cmd_return | |
496 cmd_help(struct pwm_ctx *ctx, int argc, char *argv[]) | |
497 { | |
498 struct cmd *cmd; | |
499 | |
500 if (argc != 1) { | |
501 return (CMD_USAGE); | |
502 } | |
503 | |
504 printf("Commands:\n"); | |
505 for (cmd = cmds; cmd->cmd_func != NULL; cmd++) { | |
506 printf("%-2s %-16s %s\n", cmd->abbrev_cmd, cmd->full_cmd, | |
507 cmd->description); | |
508 } | |
509 | |
510 return (CMD_OK); | |
511 } | |
512 | |
513 static enum cmd_return | |
514 cmd_write(struct pwm_ctx *ctx, int argc, char *argv[]) | |
515 { | |
516 if (argc != 1) { | |
517 return (CMD_USAGE); | |
518 } | |
519 | |
520 return ((pwfile_write_file(ctx) == 0) ? CMD_OK : CMD_ERR); | |
521 } | |
522 | |
523 static enum cmd_return | |
524 cmd_quit(struct pwm_ctx *ctx, int argc, char *argv[]) | |
525 { | |
526 if (argc != 1) { | |
527 return (CMD_USAGE); | |
528 } | |
529 | |
530 return (CMD_QUIT); | |
531 } | |
532 | |
533 struct cmd * | |
534 cmd_match(const char *cmd_name) | |
535 { | |
536 size_t i; | |
537 | |
538 for (i = 0; cmds[i].cmd_func != NULL; i++) { | |
539 if ((strcmp(cmds[i].abbrev_cmd, cmd_name) == 0) || | |
540 (strcmp(cmds[i].full_cmd, cmd_name) == 0)) { | |
541 return (&cmds[i]); | |
542 } | |
543 } | |
544 | |
545 return (NULL); | |
546 } |