Mercurial > projects > libpws
comparison pwsdump.c @ 2:97097b4b6bfb
Add pwsdump utility
The pwsdum utility can dump PasswordSafe database files to a plaintext format
and convert this format back into a PasswordSafe database.
author | Guido Berhoerster <guido+libpws@berhoerster.name> |
---|---|
date | Wed, 01 Apr 2015 14:57:57 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
1:e1309515d111 | 2:97097b4b6bfb |
---|---|
1 /* | |
2 * Copyright (C) 2015 Guido Berhoerster <guido+libpws@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 #include <ctype.h> | |
27 #include <fcntl.h> | |
28 #include <unistd.h> | |
29 #include <stdlib.h> | |
30 #include <string.h> | |
31 #include <strings.h> | |
32 #include <stdio.h> | |
33 #include <inttypes.h> | |
34 #include <limits.h> | |
35 #include <time.h> | |
36 #include <errno.h> | |
37 #include <assert.h> | |
38 #include <libgen.h> | |
39 #include <sys/stat.h> | |
40 #ifdef HAVE_READPASSPHRASE_H | |
41 #include <readpassphrase.h> | |
42 #endif /* HAVE_READPASSPHRASE_H */ | |
43 #ifdef HAVE_VIS_H | |
44 #include <vis.h> | |
45 #endif /* HAVE_VIS_H */ | |
46 #ifdef HAVE_SYS_ENDIAN_H | |
47 #include <sys/endian.h> | |
48 #else | |
49 #ifdef HAVE_ENDIAN_H | |
50 #include <endian.h> | |
51 #endif /* HAVE_ENDIAN_H */ | |
52 #endif /* HAVE_SYS_ENDIAN_H */ | |
53 #ifdef HAVE_ERR_H | |
54 #include <err.h> | |
55 #endif /* HAVE_ERR_H */ | |
56 #include <pws.h> | |
57 | |
58 #define EXIT_USAGE 2 | |
59 #define TIME_FORMAT "%Y-%m-%dT%TZ" | |
60 #define TIME_SIZE (4 + 1 + 2 + 1 + 2 + 1 + 8 + 1 + 1) | |
61 | |
62 enum { | |
63 FORMAT_PWS3, | |
64 FORMAT_DUMP | |
65 }; | |
66 | |
67 enum { | |
68 INITIAL, | |
69 IN_HEADER, | |
70 IN_RECORD | |
71 }; | |
72 | |
73 static void | |
74 header_field_dump_write(struct pws3_field *field, FILE *fp) { | |
75 size_t i; | |
76 const char *text; | |
77 char *vis_text; | |
78 const unsigned char *bytes; | |
79 time_t time; | |
80 struct tm *tm; | |
81 char time_buf[TIME_SIZE]; | |
82 size_t len; | |
83 | |
84 fprintf(fp, "%02x:", pws3_field_get_type(field)); | |
85 switch (pws3_field_get_data_type(field)) { | |
86 case PWS_DATA_TYPE_UUID: | |
87 bytes = pws3_field_get_uuid(field); | |
88 for (i = 0; i < 16; i++) { | |
89 fprintf(fp, "%02x", bytes[i]); | |
90 } | |
91 fprintf(fp, "\n"); | |
92 break; | |
93 case PWS_DATA_TYPE_TEXT: | |
94 text = pws3_field_get_text(field); | |
95 text = (text != NULL) ? text : ""; | |
96 vis_text = malloc(4 * strlen(text) + 1); | |
97 if (vis_text == NULL) { | |
98 err(1, NULL); | |
99 } | |
100 strvis(vis_text, text, VIS_TAB | VIS_NL | VIS_CSTYLE); | |
101 fprintf(fp, "%s\n", vis_text); | |
102 free(vis_text); | |
103 break; | |
104 case PWS_DATA_TYPE_TIME: | |
105 time = pws3_field_get_time(field); | |
106 tm = gmtime(&time); | |
107 strftime(time_buf, sizeof (time_buf), TIME_FORMAT, tm); | |
108 fprintf(fp, "%s\n", time_buf); | |
109 break; | |
110 case PWS_DATA_TYPE_UINT8: | |
111 fprintf(fp, "%02" PRIx8 "\n", | |
112 pws3_field_get_uint8(field)); | |
113 break; | |
114 case PWS_DATA_TYPE_UINT16: | |
115 fprintf(fp, "%04" PRIx16 "\n", | |
116 pws3_field_get_uint16(field)); | |
117 break; | |
118 case PWS_DATA_TYPE_UINT32: | |
119 fprintf(fp, "%08" PRIx32 "\n", | |
120 pws3_field_get_uint32(field)); | |
121 break; | |
122 case PWS_DATA_TYPE_BYTES: | |
123 pws3_field_get_bytes(field, &bytes, &len); | |
124 for (i = 0; i < len; i++) { | |
125 fprintf(fp, "%s%02x", (i > 0) ? " " : "", bytes[i]); | |
126 } | |
127 fprintf(fp, "\n"); | |
128 } | |
129 } | |
130 | |
131 static void | |
132 record_field_dump_write(struct pws3_field *field, FILE *fp) { | |
133 uint8_t field_type; | |
134 size_t i; | |
135 const char *text; | |
136 char *vis_text; | |
137 const unsigned char *bytes; | |
138 time_t time; | |
139 struct tm *tm; | |
140 char time_buf[TIME_SIZE]; | |
141 size_t len; | |
142 | |
143 field_type = pws3_field_get_type(field); | |
144 fprintf(fp, "%02x:", field_type); | |
145 switch (pws3_field_get_data_type(field)) { | |
146 case PWS_DATA_TYPE_UUID: | |
147 bytes = pws3_field_get_uuid(field); | |
148 for (i = 0; i < 16; i++) { | |
149 fprintf(fp, "%02x", bytes[i]); | |
150 } | |
151 fprintf(fp, "\n"); | |
152 break; | |
153 case PWS_DATA_TYPE_TEXT: | |
154 text = pws3_field_get_text(field); | |
155 text = (text != NULL) ? text : ""; | |
156 | |
157 if (field_type == PWS3_RECORD_FIELD_PASSWORD) { | |
158 vis_text = malloc(4 * strlen(text) + 1); | |
159 if (vis_text == NULL) { | |
160 err(1, NULL); | |
161 } | |
162 } else { | |
163 vis_text = malloc(4 * strlen(text) + 1); | |
164 if (vis_text == NULL) { | |
165 err(1, NULL); | |
166 } | |
167 } | |
168 strvis(vis_text, text, VIS_TAB | VIS_NL | VIS_CSTYLE); | |
169 fprintf(fp, "%s\n", vis_text); | |
170 if (field_type == PWS3_RECORD_FIELD_PASSWORD) { | |
171 free(vis_text); | |
172 } else { | |
173 free(vis_text); | |
174 } | |
175 break; | |
176 case PWS_DATA_TYPE_TIME: | |
177 time = pws3_field_get_time(field); | |
178 tm = gmtime(&time); | |
179 strftime(time_buf, sizeof (time_buf), TIME_FORMAT, tm); | |
180 fprintf(fp, "%s\n", time_buf); | |
181 break; | |
182 case PWS_DATA_TYPE_UINT8: | |
183 fprintf(fp, "%02" PRIx8 "\n", | |
184 pws3_field_get_uint8(field)); | |
185 break; | |
186 case PWS_DATA_TYPE_UINT16: | |
187 fprintf(fp, "%04" PRIx16 "\n", | |
188 pws3_field_get_uint16(field)); | |
189 break; | |
190 case PWS_DATA_TYPE_UINT32: | |
191 fprintf(fp, "%08" PRIx32 "\n", | |
192 pws3_field_get_uint32(field)); | |
193 break; | |
194 case PWS_DATA_TYPE_BYTES: | |
195 pws3_field_get_bytes(field, &bytes, &len); | |
196 for (i = 0; i < len; i++) { | |
197 fprintf(fp, "%s%02x", (i > 0) ? " " : "", bytes[i]); | |
198 } | |
199 fprintf(fp, "\n"); | |
200 } | |
201 } | |
202 | |
203 static int | |
204 parse_hex_byte(const char *str, uint8_t *u8p) | |
205 { | |
206 size_t i; | |
207 uint8_t value; | |
208 uint8_t u8 = 0; | |
209 | |
210 if (!(isascii(str[0]) && isxdigit(str[0])) || | |
211 !(isascii(str[1]) && isxdigit(str[1]))) { | |
212 return (-1); | |
213 } | |
214 | |
215 for (i = 0; i < 2; i++) { | |
216 if (str[i] >= '0' && str[i] <= '9') { | |
217 value = (str[i] - '0'); | |
218 } else if (str[i] >= 'A' && str[i] <= 'F') { | |
219 value = (10 + (str[i] - 'A')); | |
220 } else { | |
221 value = (10 + (str[i] - 'a')); | |
222 } | |
223 u8 += value << ((2 - 1 - i) * 4); | |
224 } | |
225 | |
226 *u8p = u8; | |
227 | |
228 return (0); | |
229 } | |
230 | |
231 static struct pws3_field * | |
232 header_field_dump_parse(const char *line) | |
233 { | |
234 const char *p = line; | |
235 uint8_t field_type; | |
236 struct pws3_field *field = NULL; | |
237 size_t len; | |
238 size_t i; | |
239 unsigned char uuid[32]; | |
240 char *text = NULL; | |
241 struct tm tm; | |
242 uint8_t u8; | |
243 uint16_t u16; | |
244 uint32_t u32; | |
245 unsigned char *bytes = NULL; | |
246 | |
247 if (strlen(line) < 3) { | |
248 goto err; | |
249 } | |
250 | |
251 if (parse_hex_byte(p, &field_type) != 0) { | |
252 goto err; | |
253 } | |
254 p += 2; | |
255 field = pws3_field_create(1, field_type); | |
256 if (field == NULL) { | |
257 err(1, NULL); | |
258 } | |
259 | |
260 if (*p++ != ':') { | |
261 goto err; | |
262 } | |
263 | |
264 len = strlen(p); | |
265 | |
266 switch (pws3_field_get_data_type(field)) { | |
267 case PWS_DATA_TYPE_UUID: | |
268 for (i = 0; i < 16; i++) { | |
269 if (parse_hex_byte(p, &uuid[i]) != 0) { | |
270 goto err; | |
271 } | |
272 p += 2; | |
273 | |
274 while (*p == ' ') { p++; } | |
275 } | |
276 | |
277 if (*p != '\0') { | |
278 goto err; | |
279 } | |
280 | |
281 if (pws3_field_set_uuid(field, uuid) != 0) { | |
282 goto err; | |
283 } | |
284 break; | |
285 case PWS_DATA_TYPE_TEXT: | |
286 if (len > PWS3_MAX_FIELD_SIZE) { | |
287 goto err; | |
288 } | |
289 text = malloc(len + 1); | |
290 if (text == NULL) { | |
291 err(1, NULL); | |
292 } | |
293 if (strunvis(text, p) == -1) { | |
294 goto err; | |
295 } | |
296 if (pws3_field_set_text(field, text) != 0) { | |
297 goto err; | |
298 } | |
299 break; | |
300 case PWS_DATA_TYPE_TIME: | |
301 p = strptime(p, TIME_FORMAT, &tm); | |
302 if ((p == NULL) || (*p != '\0')) { | |
303 goto err; | |
304 } | |
305 tm.tm_isdst = -1; | |
306 pws3_field_set_time(field, mktime(&tm)); | |
307 break; | |
308 case PWS_DATA_TYPE_UINT8: | |
309 if (len != 2) { | |
310 goto err; | |
311 } | |
312 if (parse_hex_byte(p, &u8) != 0) { | |
313 goto err; | |
314 } | |
315 pws3_field_set_uint8(field, u8); | |
316 break; | |
317 case PWS_DATA_TYPE_UINT16: | |
318 if (len != 4) { | |
319 goto err; | |
320 } | |
321 for (i = 0; i < 2; i++) { | |
322 if (parse_hex_byte(p, &((unsigned char *)&u16)[i]) != | |
323 0) { | |
324 goto err; | |
325 } | |
326 p += 2; | |
327 } | |
328 pws3_field_set_uint16(field, be16toh(u16)); | |
329 break; | |
330 case PWS_DATA_TYPE_UINT32: | |
331 if (len != 8) { | |
332 goto err; | |
333 } | |
334 for (i = 0; i < 4; i++) { | |
335 if (parse_hex_byte(p, | |
336 &((unsigned char *)&u32)[i]) != 0) { | |
337 goto err; | |
338 } | |
339 p += 2; | |
340 } | |
341 pws3_field_set_uint16(field, be32toh(u32)); | |
342 break; | |
343 case PWS_DATA_TYPE_BYTES: | |
344 bytes = malloc(len / 2); | |
345 if (bytes == NULL) { | |
346 err(1, NULL); | |
347 } | |
348 for (i = 0; (*p != '\0') && (i < PWS3_MAX_FIELD_SIZE); i++) { | |
349 if (parse_hex_byte(p, &bytes[i]) != 0) { | |
350 goto err; | |
351 } | |
352 p += 2; | |
353 | |
354 while (*p == ' ') { p++; } | |
355 } | |
356 | |
357 if (*p != '\0') { | |
358 goto err; | |
359 } | |
360 | |
361 if (pws3_field_set_bytes(field, bytes, i) != 0) { | |
362 goto err; | |
363 } | |
364 } | |
365 | |
366 free(bytes); | |
367 free(text); | |
368 | |
369 return (field); | |
370 err: | |
371 free(bytes); | |
372 free(text); | |
373 pws3_field_destroy(field); | |
374 | |
375 return (NULL); | |
376 } | |
377 | |
378 static struct pws3_field * | |
379 record_field_dump_parse(const char *line) | |
380 { | |
381 const char *p = line; | |
382 uint8_t field_type = 0xff; | |
383 struct pws3_field *field = NULL; | |
384 size_t len; | |
385 size_t i; | |
386 unsigned char uuid[32]; | |
387 char *text = NULL; | |
388 struct tm tm; | |
389 uint8_t u8; | |
390 uint16_t u16; | |
391 uint32_t u32; | |
392 unsigned char *bytes = NULL; | |
393 | |
394 if (strlen(line) < 3) { | |
395 goto err; | |
396 } | |
397 | |
398 if (parse_hex_byte(p, &field_type) != 0) { | |
399 goto err; | |
400 } | |
401 p += 2; | |
402 field = pws3_field_create(0, field_type); | |
403 if (field == NULL) { | |
404 err(1, NULL); | |
405 } | |
406 | |
407 if (*p++ != ':') { | |
408 goto err; | |
409 } | |
410 | |
411 len = strlen(p); | |
412 | |
413 switch (pws3_field_get_data_type(field)) { | |
414 case PWS_DATA_TYPE_UUID: | |
415 for (i = 0; i < 16; i++) { | |
416 if (parse_hex_byte(p, &uuid[i]) != 0) { | |
417 goto err; | |
418 } | |
419 p += 2; | |
420 | |
421 while (*p == ' ') { p++; } | |
422 } | |
423 | |
424 if (*p != '\0') { | |
425 goto err; | |
426 } | |
427 | |
428 if (pws3_field_set_uuid(field, uuid) != 0) { | |
429 goto err; | |
430 } | |
431 break; | |
432 case PWS_DATA_TYPE_TEXT: | |
433 if (((field_type == PWS3_RECORD_FIELD_PASSWORD) && | |
434 (len > PWS3_MAX_PASSWORD_LEN)) || | |
435 (len > PWS3_MAX_FIELD_SIZE)) { | |
436 goto err; | |
437 } | |
438 if (field_type == PWS3_RECORD_FIELD_PASSWORD) { | |
439 text = malloc(len + 1); | |
440 if (text == NULL) { | |
441 err(1, NULL); | |
442 } | |
443 } else { | |
444 text = malloc(len + 1); | |
445 } | |
446 if (strunvis(text, p) == -1) { | |
447 goto err; | |
448 } | |
449 if (pws3_field_set_text(field, text) != 0) { | |
450 goto err; | |
451 } | |
452 break; | |
453 case PWS_DATA_TYPE_TIME: | |
454 p = strptime(p, TIME_FORMAT, &tm); | |
455 if ((p == NULL) || (*p != '\0')) { | |
456 goto err; | |
457 } | |
458 tm.tm_isdst = -1; | |
459 pws3_field_set_time(field, mktime(&tm)); | |
460 break; | |
461 case PWS_DATA_TYPE_UINT8: | |
462 if (len != 2) { | |
463 goto err; | |
464 } | |
465 if (parse_hex_byte(p, &u8) != 0) { | |
466 goto err; | |
467 } | |
468 pws3_field_set_uint8(field, u8); | |
469 break; | |
470 case PWS_DATA_TYPE_UINT16: | |
471 if (len != 4) { | |
472 goto err; | |
473 } | |
474 for (i = 0; i < 2; i++) { | |
475 if (parse_hex_byte(p, &((unsigned char *)&u16)[i]) != | |
476 0) { | |
477 goto err; | |
478 } | |
479 p += 2; | |
480 } | |
481 pws3_field_set_uint16(field, be16toh(u16)); | |
482 break; | |
483 case PWS_DATA_TYPE_UINT32: | |
484 if (len != 8) { | |
485 goto err; | |
486 } | |
487 for (i = 0; i < 4; i++) { | |
488 if (parse_hex_byte(p, &((unsigned char *)&u32)[i]) != | |
489 0) { | |
490 goto err; | |
491 } | |
492 p += 2; | |
493 } | |
494 pws3_field_set_uint16(field, be32toh(u32)); | |
495 break; | |
496 case PWS_DATA_TYPE_BYTES: | |
497 bytes = malloc(len / 2); | |
498 if (bytes == NULL) { | |
499 err(1, NULL); | |
500 } | |
501 for (i = 0; (*p != '\0') && (i < PWS3_MAX_FIELD_SIZE); i++) { | |
502 if (parse_hex_byte(p, &bytes[i]) != 0) { | |
503 goto err; | |
504 } | |
505 p += 2; | |
506 | |
507 while (*p == ' ') { p++; } | |
508 } | |
509 | |
510 if (*p != '\0') { | |
511 goto err; | |
512 } | |
513 | |
514 if (pws3_field_set_bytes(field, bytes, i) != 0) { | |
515 goto err; | |
516 } | |
517 } | |
518 | |
519 free(bytes); | |
520 if (field_type == PWS3_RECORD_FIELD_PASSWORD) { | |
521 free(text); | |
522 } else { | |
523 free(text); | |
524 } | |
525 | |
526 return (field); | |
527 err: | |
528 free(bytes); | |
529 if (field_type == PWS3_RECORD_FIELD_PASSWORD) { | |
530 free(text); | |
531 } else { | |
532 free(text); | |
533 } | |
534 pws3_field_destroy(field); | |
535 | |
536 return (NULL); | |
537 } | |
538 | |
539 static int | |
540 dump_read(struct pws3_file *file, FILE *fp) | |
541 { | |
542 int retval = -1; | |
543 ssize_t line_len; | |
544 char *line = NULL; | |
545 size_t line_size = 0; | |
546 size_t line_no = 0; | |
547 int state = INITIAL; | |
548 struct pws3_field *header_field = NULL; | |
549 struct pws3_record *record = NULL; | |
550 struct pws3_field *record_field = NULL; | |
551 struct pws3_field *field_uuid; | |
552 | |
553 errno = 0; | |
554 while ((line_len = getline(&line, &line_size, fp)) != -1) { | |
555 line_no++; | |
556 | |
557 /* skip empty lines and comments */ | |
558 if (line_len <= 1 || (line[0] == '#')) { | |
559 continue; | |
560 } | |
561 | |
562 /* remove trailing newline */ | |
563 if (line[line_len - 1] == '\n') { | |
564 line[line_len - 1] = '\0'; | |
565 } | |
566 | |
567 switch (state) { | |
568 case INITIAL: | |
569 if (strcasecmp(line, "HEADER") == 0) { | |
570 state = IN_HEADER; | |
571 } else { | |
572 warnx("syntax error in line %zu", line_no); | |
573 goto out; | |
574 } | |
575 break; | |
576 case IN_HEADER: | |
577 if (strncasecmp(line, "RECORD", | |
578 strlen("RECORD")) == 0) { | |
579 state = IN_RECORD; | |
580 } else { | |
581 header_field = header_field_dump_parse(line); | |
582 if (header_field == NULL) { | |
583 warnx("syntax error in line %zu", | |
584 line_no); | |
585 goto out; | |
586 } | |
587 pws3_file_set_header_field(file, header_field); | |
588 header_field = NULL; | |
589 } | |
590 break; | |
591 case IN_RECORD: | |
592 if (strncasecmp(line, "RECORD", | |
593 strlen("RECORD")) == 0) { | |
594 if (record == NULL) { | |
595 warnx("syntax error in line %zu", | |
596 line_no); | |
597 goto out; | |
598 } | |
599 | |
600 /* check for mandatory UUID field */ | |
601 if (((field_uuid = pws3_record_get_field(record, | |
602 PWS3_RECORD_FIELD_UUID)) == NULL) || | |
603 (pws3_field_get_uuid(field_uuid) == | |
604 NULL)) { | |
605 warnx("record ending on line %zu is " | |
606 "missing UUID field", line_no); | |
607 goto out; | |
608 } | |
609 pws3_file_insert_record(file, record); | |
610 record = NULL; | |
611 } else { | |
612 if (record == NULL) { | |
613 record = pws3_record_create(); | |
614 if (record == NULL) { | |
615 err(1, NULL); | |
616 } | |
617 } | |
618 | |
619 record_field = record_field_dump_parse(line); | |
620 if (record_field == NULL) { | |
621 warnx("syntax error in line %zu", | |
622 line_no); | |
623 goto out; | |
624 } | |
625 pws3_record_set_field(record, record_field); | |
626 record_field = NULL; | |
627 } | |
628 } | |
629 errno = 0; | |
630 } | |
631 if (errno != 0) { | |
632 warn("failed to read from input file"); | |
633 goto out; | |
634 } | |
635 if (record != NULL) { | |
636 /* check for mandatory UUID field */ | |
637 if (((field_uuid = pws3_record_get_field(record, | |
638 PWS3_RECORD_FIELD_UUID)) == NULL) || | |
639 (pws3_field_get_uuid(field_uuid) == NULL)) { | |
640 warnx("record ending on line %zu is missing UUID " | |
641 "field", line_no); | |
642 goto out; | |
643 } | |
644 pws3_file_insert_record(file, record); | |
645 record = NULL; | |
646 } | |
647 | |
648 retval = 0; | |
649 | |
650 out: | |
651 pws3_field_destroy(header_field); | |
652 pws3_field_destroy(record_field); | |
653 pws3_record_destroy(record); | |
654 free(line); | |
655 | |
656 return (retval); | |
657 } | |
658 | |
659 static int | |
660 dump_write(struct pws3_file *file, FILE *fp) | |
661 { | |
662 size_t i; | |
663 struct pws3_field *header_field; | |
664 struct pws3_record *record; | |
665 size_t n; | |
666 struct pws3_field *record_field; | |
667 | |
668 if (fprintf(fp, "# Passwordsafe v3 database dump\nHeader\n") < 0) { | |
669 warn("failed to write to output file"); | |
670 return (-1); | |
671 } | |
672 for (i = 0x00; i < 0xff; i++) { | |
673 header_field = pws3_file_get_header_field(file, i); | |
674 if (header_field != NULL) { | |
675 header_field_dump_write(header_field, fp); | |
676 } | |
677 | |
678 while ((i == PWS3_HEADER_FIELD_EMPTY_GROUPS) && | |
679 (header_field != NULL) && | |
680 (header_field = pws3_file_next_empty_group(file, | |
681 header_field)) != NULL) { | |
682 header_field_dump_write(header_field, fp); | |
683 } | |
684 } | |
685 | |
686 for (record = pws3_file_first_record(file), n = 1; record != NULL; | |
687 record = pws3_file_next_record(file, record), n++) { | |
688 if (fprintf(fp, "Record %zu\n", n) < 0) { | |
689 warn("failed to write to output file"); | |
690 return (-1); | |
691 } | |
692 | |
693 for (i = 0x00; i <= 0xff; i++) { | |
694 record_field = pws3_record_get_field(record, i); | |
695 if (record_field != NULL) { | |
696 record_field_dump_write(record_field, fp); | |
697 } | |
698 } | |
699 } | |
700 | |
701 return (0); | |
702 } | |
703 | |
704 void | |
705 usage(void) | |
706 { | |
707 fprintf(stderr, "usage: pwsdump [-f pws3 | dump] [-o filename] " | |
708 "[-p password_file] [-P password_fd] [-t pws3 | dump] " | |
709 "input_file\n" | |
710 " pwsdump -f dump [-o filename] [-t pws3 | dump]\n"); | |
711 } | |
712 | |
713 int | |
714 main(int argc, char *argv[]) | |
715 { | |
716 int status = EXIT_FAILURE; | |
717 int c; | |
718 int errflag = 0; | |
719 int from_format = FORMAT_PWS3; | |
720 int to_format = FORMAT_DUMP; | |
721 const char *in_filename = NULL; | |
722 const char *out_filename = NULL; | |
723 const char *new_password_filename = NULL; | |
724 const char *password_filename = NULL; | |
725 int fd_value = -1; | |
726 char *p; | |
727 int fd_new_password = -1; | |
728 FILE * fp_new_password = NULL; | |
729 int fd_password = -1; | |
730 FILE * fp_password = NULL; | |
731 struct pws3_file *file = NULL; | |
732 FILE *fp_in = stdin; | |
733 char *password = NULL; | |
734 ssize_t password_len; | |
735 size_t password_size = PWS3_MAX_PASSWORD_LEN + 1; | |
736 char *new_password = NULL; | |
737 ssize_t new_password_len; | |
738 size_t new_password_size = PWS3_MAX_PASSWORD_LEN + 1; | |
739 char *confirm_password = NULL; | |
740 FILE *fp_out = stdout; | |
741 struct stat statbuf_in; | |
742 struct stat statbuf_out; | |
743 int need_tmpfile = 0; | |
744 int fd_tmp = -1; | |
745 char *out_filename_tmp = NULL; | |
746 char *out_dir = NULL; | |
747 char *tmp_filename = NULL; | |
748 int len; | |
749 mode_t old_mode; | |
750 | |
751 setprogname(argv[0]); | |
752 | |
753 if (pws_init() != 0) { | |
754 goto out; | |
755 } | |
756 | |
757 /* timestamps are processed as UTC */ | |
758 if (setenv("TZ", "", 1) != 0) { | |
759 goto out; | |
760 } | |
761 tzset(); | |
762 | |
763 while (!errflag && ((c = getopt(argc, argv, "f:n:N:o:p:P:t:")) != -1)) { | |
764 switch (c) { | |
765 case 'f': | |
766 if (strcmp(optarg, "pws3") == 0) { | |
767 from_format = FORMAT_PWS3; | |
768 } else if (strcmp(optarg, "dump") == 0) { | |
769 from_format = FORMAT_DUMP; | |
770 } else { | |
771 errflag = 1; | |
772 } | |
773 break; | |
774 case 'n': | |
775 fd_new_password = -1; | |
776 new_password_filename = optarg; | |
777 break; | |
778 case 'N': | |
779 new_password_filename = NULL; | |
780 errno = 0; | |
781 fd_value = strtol(optarg, &p, 10); | |
782 if ((errno == 0) && (p != optarg) && (*p == '\0') && | |
783 (fd_value >= 0) && (fd_value < INT_MAX)) { | |
784 fd_new_password = (int)fd_value; | |
785 } else { | |
786 errflag = 1; | |
787 } | |
788 break; | |
789 case 'o': | |
790 out_filename = optarg; | |
791 break; | |
792 case 'p': | |
793 fd_password = -1; | |
794 password_filename = optarg; | |
795 break; | |
796 case 'P': | |
797 password_filename = NULL; | |
798 errno = 0; | |
799 fd_value = strtol(optarg, &p, 10); | |
800 if ((errno == 0) && (p != optarg) && (*p == '\0') && | |
801 (fd_value >= 0) && | |
802 (fd_value < INT_MAX)) { | |
803 fd_password = (int)fd_value; | |
804 } else { | |
805 errflag = 1; | |
806 } | |
807 break; | |
808 case 't': | |
809 if (strcmp(optarg, "pws3") == 0) { | |
810 to_format = FORMAT_PWS3; | |
811 } else if (strcmp(optarg, "dump") == 0) { | |
812 to_format = FORMAT_DUMP; | |
813 } else { | |
814 errflag = 1; | |
815 } | |
816 break; | |
817 default: | |
818 errflag = 1; | |
819 } | |
820 } | |
821 | |
822 if (errflag || ((from_format == FORMAT_PWS3) && (argc != optind + 1)) || | |
823 (argc > optind + 1)) { | |
824 usage(); | |
825 status = EXIT_USAGE; | |
826 goto out; | |
827 } | |
828 | |
829 if (optind == argc - 1) { | |
830 in_filename = argv[optind]; | |
831 } | |
832 | |
833 if (fd_password != -1) { | |
834 fp_password = fdopen(fd_password, "r"); | |
835 if (fp_password == NULL) { | |
836 warn("invalid password fd %d", fd_password); | |
837 goto out; | |
838 } | |
839 fd_password = -1; | |
840 } else if (password_filename != NULL) { | |
841 fp_password = fopen(password_filename, "r"); | |
842 if (fp_password == NULL) { | |
843 warn("could not open password file"); | |
844 goto out; | |
845 } | |
846 } | |
847 | |
848 if (fd_new_password != -1) { | |
849 fp_new_password = fdopen(fd_new_password, "r"); | |
850 if (fp_new_password == NULL) { | |
851 warn("invalid password fd %d", fd_new_password); | |
852 goto out; | |
853 } | |
854 fd_new_password = -1; | |
855 } else if (new_password_filename != NULL) { | |
856 fp_new_password = fopen(new_password_filename, "r"); | |
857 if (fp_new_password == NULL) { | |
858 warn("could not open password file"); | |
859 goto out; | |
860 } | |
861 } | |
862 | |
863 if (in_filename != NULL) { | |
864 fp_in = fopen(in_filename, "r"); | |
865 if (fp_in == NULL) { | |
866 warn("could not open input file"); | |
867 goto out; | |
868 } | |
869 } | |
870 | |
871 if (out_filename != NULL) { | |
872 if (in_filename != NULL) { | |
873 if (fstat(fileno(fp_in), &statbuf_in) == -1) { | |
874 warn("could not stat input file"); | |
875 status = EXIT_FAILURE; | |
876 goto out; | |
877 } | |
878 if (stat(out_filename, &statbuf_out) == -1) { | |
879 if (errno != ENOENT) { | |
880 warn("could not stat output file"); | |
881 status = 1; | |
882 goto out; | |
883 } | |
884 } else if ((statbuf_in.st_ino == statbuf_out.st_ino) && | |
885 (statbuf_in.st_dev == statbuf_out.st_dev)) { | |
886 need_tmpfile = 1; | |
887 } | |
888 } | |
889 | |
890 if (need_tmpfile) { | |
891 out_filename_tmp = strdup(out_filename); | |
892 if (out_filename_tmp == NULL) { | |
893 err(1, NULL); | |
894 } | |
895 out_dir = dirname(out_filename_tmp); | |
896 len = snprintf(NULL, 0, "%s/pwsdumpXXXXXX", out_dir); | |
897 if (len < 0) { | |
898 warn(NULL); | |
899 goto out; | |
900 } | |
901 tmp_filename = malloc((size_t)len + 1); | |
902 if (tmp_filename == NULL) { | |
903 err(1, NULL); | |
904 } | |
905 if (snprintf(tmp_filename, (size_t)len + 1, | |
906 "%s/pwsdumpXXXXXX", out_dir) != len) { | |
907 warn(NULL); | |
908 goto out; | |
909 } | |
910 old_mode = umask(077); | |
911 fd_tmp = mkstemp(tmp_filename); | |
912 umask(old_mode); | |
913 if (fd_tmp == -1) { | |
914 warn("could not create temporary file"); | |
915 goto out; | |
916 } | |
917 fp_out = fdopen(fd_tmp, "w"); | |
918 if (fp_out == NULL) { | |
919 warn("could not open temporary file"); | |
920 goto out; | |
921 } | |
922 fd_tmp = -1; | |
923 } else { | |
924 old_mode = umask(077); | |
925 fp_out = fopen(out_filename, "w"); | |
926 umask(old_mode); | |
927 if (fp_out == NULL) { | |
928 warn("could not open output file"); | |
929 goto out; | |
930 } | |
931 } | |
932 } | |
933 | |
934 file = pws3_file_create(); | |
935 if (file == NULL) { | |
936 err(1, NULL); | |
937 } | |
938 | |
939 if (from_format == FORMAT_PWS3) { | |
940 password = malloc(password_size); | |
941 if (password == NULL) { | |
942 err(1, NULL); | |
943 } | |
944 if (fp_password != NULL) { | |
945 errno = 0; | |
946 if (getline(&password, &password_size, | |
947 fp_password) == -1) { | |
948 if (errno != 0) { | |
949 warn("failed to read password"); | |
950 } else { | |
951 warnx("failed to read password"); | |
952 } | |
953 goto out; | |
954 } | |
955 password_len = strlen(password); | |
956 /* strip trailing newline */ | |
957 if ((password_len > 0) && | |
958 (password[password_len - 1] == '\n')) { | |
959 password[password_len - 1] = '\0'; | |
960 password_len--; | |
961 } | |
962 if (password_len == 0) { | |
963 warnx("invalid password"); | |
964 goto out; | |
965 } else if (password_len > PWS3_MAX_PASSWORD_LEN) { | |
966 warnx("password too long"); | |
967 goto out; | |
968 } | |
969 } else { | |
970 if (readpassphrase("Enter password: ", password, | |
971 password_size, RPP_ECHO_OFF | | |
972 RPP_REQUIRE_TTY) == NULL) { | |
973 err(1, NULL); | |
974 } | |
975 password_len = strlen(password); | |
976 } | |
977 if (password_len == 0) { | |
978 warnx("invalid password"); | |
979 goto out; | |
980 } | |
981 | |
982 if (pws3_file_read_stream(file, password, fp_in) != 0) { | |
983 warnx("%s", pws3_file_get_error_message(file)); | |
984 goto out; | |
985 } | |
986 } else { | |
987 if (dump_read(file, fp_in) != 0) { | |
988 goto out; | |
989 } | |
990 } | |
991 | |
992 if (to_format == FORMAT_PWS3) { | |
993 new_password = malloc(new_password_size); | |
994 if (new_password == NULL) { | |
995 err(1, NULL); | |
996 } | |
997 if (fp_new_password != NULL) { | |
998 errno = 0; | |
999 if (getline(&new_password, &new_password_size, | |
1000 fp_new_password) == -1) { | |
1001 if (errno != 0) { | |
1002 warn("failed to read password"); | |
1003 } else { | |
1004 warnx("failed to read password"); | |
1005 } | |
1006 goto out; | |
1007 } | |
1008 new_password_len = strlen(new_password); | |
1009 /* strip trailing newline */ | |
1010 if ((new_password_len > 0) && | |
1011 (new_password[new_password_len - 1] == '\n')) { | |
1012 new_password[new_password_len - 1] = '\0'; | |
1013 new_password_len--; | |
1014 } | |
1015 if (new_password_len == 0) { | |
1016 warnx("invalid password"); | |
1017 goto out; | |
1018 } else if (new_password_len > PWS3_MAX_PASSWORD_LEN) { | |
1019 warnx("password too long"); | |
1020 goto out; | |
1021 } | |
1022 } else { | |
1023 if (readpassphrase("Enter new password: ", new_password, | |
1024 PWS3_MAX_PASSWORD_LEN + 1, RPP_ECHO_OFF | | |
1025 RPP_REQUIRE_TTY) == NULL) { | |
1026 err(1, NULL); | |
1027 } | |
1028 if (strlen(new_password) == 0) { | |
1029 warnx("invalid password"); | |
1030 goto out; | |
1031 } | |
1032 | |
1033 confirm_password = malloc(PWS3_MAX_PASSWORD_LEN + 1); | |
1034 if (confirm_password == NULL) { | |
1035 err(1, NULL); | |
1036 } | |
1037 if (readpassphrase("Confirm new password: ", | |
1038 confirm_password, PWS3_MAX_PASSWORD_LEN + 1, | |
1039 RPP_ECHO_OFF | RPP_REQUIRE_TTY) == NULL) { | |
1040 err(1, NULL); | |
1041 } | |
1042 if (strcmp(new_password, confirm_password) != 0) { | |
1043 warnx("password mismatch"); | |
1044 goto out; | |
1045 } | |
1046 } | |
1047 | |
1048 if (pws3_file_write_stream(file, new_password, 10000, fp_out) != | |
1049 0) { | |
1050 goto out; | |
1051 } | |
1052 } else { | |
1053 if (dump_write(file, fp_out) != 0) { | |
1054 goto out; | |
1055 } | |
1056 if (fflush(fp_out) != 0) { | |
1057 warn("failed to flush output file"); | |
1058 goto out; | |
1059 } | |
1060 } | |
1061 | |
1062 status = EXIT_SUCCESS; | |
1063 | |
1064 out: | |
1065 if (fd_new_password != -1) { | |
1066 close(fd_new_password); | |
1067 } | |
1068 | |
1069 if (fp_new_password != NULL) { | |
1070 fclose(fp_new_password); | |
1071 } | |
1072 | |
1073 if (fd_password != -1) { | |
1074 close(fd_password); | |
1075 } | |
1076 | |
1077 if (fp_password != NULL) { | |
1078 fclose(fp_password); | |
1079 } | |
1080 | |
1081 if ((fp_in != NULL) && (fp_in != stdin)) { | |
1082 fclose(fp_in); | |
1083 } | |
1084 | |
1085 if (fd_tmp != -1) { | |
1086 close(fd_tmp); | |
1087 } | |
1088 | |
1089 if ((fp_out != NULL) && (fp_out != stdout)) { | |
1090 fclose(fp_out); | |
1091 if (status == EXIT_SUCCESS) { | |
1092 if (need_tmpfile) { | |
1093 if (rename(tmp_filename, out_filename) == -1) { | |
1094 warn("could not create output file"); | |
1095 status = EXIT_FAILURE; | |
1096 unlink(tmp_filename); | |
1097 } | |
1098 } | |
1099 } else { | |
1100 if (need_tmpfile) { | |
1101 unlink(tmp_filename); | |
1102 } else { | |
1103 unlink(out_filename); | |
1104 } | |
1105 } | |
1106 } | |
1107 | |
1108 pws3_file_destroy(file); | |
1109 free(out_filename_tmp); | |
1110 free(tmp_filename); | |
1111 free(confirm_password); | |
1112 free(new_password); | |
1113 free(password); | |
1114 | |
1115 pws_finalize(); | |
1116 | |
1117 exit(status); | |
1118 } |