Do not run editor in batch mode
[shigofumi.git] / src / shigofumi.c
blob17aa50d50215e316eed12f7921b6816604167831
1 #define _XOPEN_SOURCE 600
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <isds.h>
5 #include <string.h>
6 #include <readline/readline.h>
7 #include <readline/history.h>
8 #include <locale.h>
9 #include <unistd.h>
10 #include <string.h>
11 #include <stdint.h>
12 #include <confuse.h>
13 #include <errno.h>
14 #include <libgen.h>
15 #include <limits.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <sys/wait.h>
19 #include <strings.h>
20 #include <time.h>
22 #include "shigofumi.h"
23 #include "completition.h"
24 #include "data.h"
25 #include "io.h"
26 #include "ui.h"
27 #include "utils.h"
29 #define CONFIG_FILE ".shigofumirc"
30 #define CONFIG_SERVER "base_url"
31 #define CONFIG_USERNAME "username"
32 #define CONFIG_PASSWORD "password"
33 #define CONFIG_CERT_FORMAT "certificate_format"
34 #define CONFIG_CERT_PATH "certificate_path"
35 #define CONFIG_KEY_ENGINE "key_engine"
36 #define CONFIG_KEY_FORMAT "key_format"
37 #define CONFIG_KEY_PATH "key_path"
38 #define CONFIG_KEY_PASSWORD "key_password"
39 #define CONFIG_OTP_METHOD "otp_method"
40 #define CONFIG_OTP_CODE "otp_code"
41 #define CONFIG_DEBUGLEVEL "debug_level"
42 #define CONFIG_VERIFYSERVER "verify_server"
43 #define CONFIG_CAFILE "ca_file"
44 #define CONFIG_CADIRECTORY "ca_directory"
45 #define CONFIG_CLEAN_TEMPORARY_FILES "clean_temporary_files"
46 #define CONFIG_CRLFILE "crl_file"
47 #define CONFIG_TIMEOUT "timeout"
48 #define CONFIG_LOGFACILITIES "log_facilities"
49 #define CONFIG_LOGFILE "log_file"
50 #define CONFIG_LOGLEVEL "log_level"
51 #define CONFIG_MARKMESSAGEREAD "mark_message_read"
52 #define CONFIG_NORMALIZEMIMETYPE "normalize_mime_type"
53 #define CONFIG_OPENCOMMAND "open_command"
54 #define CONFIG_OVERWRITEFILES "overwrite_files"
55 #define CZPDEPOSIT_URL "https://www.czechpoint.cz/uschovna/"
57 #define TIMEOUT 10000
58 #define LOG_LEVEL 20
60 /* Configuration */
61 cfg_opt_t configuration_syntax[] = {
62 CFG_STR(CONFIG_SERVER, NULL, CFGF_NONE),
63 CFG_STR(CONFIG_USERNAME, NULL, CFGF_NODEFAULT),
64 CFG_STR(CONFIG_PASSWORD, NULL, CFGF_NODEFAULT),
65 CFG_STR(CONFIG_CERT_FORMAT, NULL, CFGF_NODEFAULT),
66 CFG_STR(CONFIG_CERT_PATH, NULL, CFGF_NODEFAULT),
67 CFG_STR(CONFIG_KEY_ENGINE, NULL, CFGF_NODEFAULT),
68 CFG_STR(CONFIG_KEY_FORMAT, NULL, CFGF_NODEFAULT),
69 CFG_STR(CONFIG_KEY_PATH, NULL, CFGF_NODEFAULT),
70 CFG_STR(CONFIG_KEY_PASSWORD, NULL, CFGF_NODEFAULT),
71 CFG_STR(CONFIG_OTP_METHOD, NULL, CFGF_NODEFAULT),
72 CFG_STR(CONFIG_OTP_CODE, NULL, CFGF_NODEFAULT),
73 /*CFG_STR(CONFIG_DEBUGLEVEL, NULL, CFGF_NODEFAULT),*/
74 CFG_BOOL(CONFIG_VERIFYSERVER, cfg_true, CFGF_NONE),
75 CFG_STR(CONFIG_CAFILE, NULL, CFGF_NODEFAULT),
76 CFG_STR(CONFIG_CADIRECTORY, NULL, CFGF_NODEFAULT),
77 CFG_BOOL(CONFIG_CLEAN_TEMPORARY_FILES, cfg_true, CFGF_NONE),
78 CFG_STR(CONFIG_CRLFILE, NULL, CFGF_NODEFAULT),
79 CFG_INT(CONFIG_TIMEOUT, TIMEOUT, CFGF_NONE),
80 CFG_STR_LIST(CONFIG_LOGFACILITIES, "{none}", CFGF_NONE),
81 CFG_STR(CONFIG_LOGFILE, NULL, CFGF_NODEFAULT),
82 CFG_INT(CONFIG_LOGLEVEL, LOG_LEVEL, CFGF_NONE),
83 CFG_BOOL(CONFIG_MARKMESSAGEREAD, cfg_false, CFGF_NONE),
84 CFG_BOOL(CONFIG_NORMALIZEMIMETYPE, cfg_true, CFGF_NONE),
85 CFG_STR_LIST(CONFIG_OPENCOMMAND, "{xdg-open, %f}", CFGF_NONE),
86 CFG_BOOL(CONFIG_OVERWRITEFILES, cfg_true, CFGF_NONE),
87 CFG_END()
89 cfg_t *configuration;
91 /* Logger */
92 int logger_fd = -1;
94 /* UI */
95 _Bool batch_mode = 0;
96 char *prompt = NULL;
97 struct command (*commands)[] = NULL;
98 FILE *output = NULL;
100 /* Data */
101 struct isds_ctx *cisds = NULL;
102 struct isds_list *boxes = NULL;
103 struct isds_message *message = NULL;
104 _Bool messages_are_outgoing = 0;
105 struct isds_list *messages = NULL;
106 unsigned long int total_messages = 0;
107 struct isds_ctx *czechpoint = NULL;
108 struct isds_list *temporary_files = NULL;
110 /* Temporary log-in settings */
111 char *server = NULL;
112 char *username = NULL;
113 char *password = NULL;
114 char *key_password = NULL;
115 char *otp_method = NULL;
116 char *otp_code = NULL;
117 char *pki_engine = NULL;
118 char *pki_certificate_path = NULL;
119 char *pki_certificate_format = NULL;
120 char *pki_key_path = NULL;
121 char *pki_key_format = NULL;
123 static void discard_credentials(void) {
124 zfree(server);
125 zfree(username);
126 zfree(password);
127 zfree(key_password);
128 zfree(otp_method);
129 zfree(otp_code);
130 zfree(pki_engine);
131 zfree(pki_certificate_path);
132 zfree(pki_certificate_format);
133 zfree(pki_key_path);
134 zfree(pki_key_format);
138 /* Remove temporary file */
139 void shi_unlink_temporary_file(void **data) {
140 if (!data || !*data) return;
142 const char *file = (const char *)*data;
143 unlink_file(file);
144 zfree(*data);
148 /* Finish ISDS operation, report error, if the operation returned different
149 * code than @positive_code. */
150 static void finish_isds_operation_with_code(struct isds_ctx *ctx,
151 isds_error err, isds_error positive_code) {
152 shi_progressbar_finish();
153 if (err != positive_code) {
154 if (isds_long_message(ctx))
155 fprintf(stderr, _("Error occurred: %s: %s\n"), isds_strerror(err),
156 isds_long_message(ctx));
157 else
158 fprintf(stderr, _("Error occurred: %s\n"), isds_strerror(err));
163 /* Finish ISDS operation, report error, if the operation did not returned
164 * IE_SUCCESS. */
165 static void finish_isds_operation(struct isds_ctx *ctx, isds_error err) {
166 finish_isds_operation_with_code(ctx, err, IE_SUCCESS);
170 /* Do the cleanup and exit */
171 static void shi_exit(int exit_code) {
172 /* Data */
173 discard_credentials();
174 isds_list_free(&boxes);
175 isds_message_free(&message);
176 isds_list_free(&messages);
177 if (temporary_files) {
178 oprintf(_("Removing temporary files...\n"));
179 isds_list_free(&temporary_files);
182 if (cisds) {
183 isds_error err;
184 oprintf(_("Logging out...\n"));
185 err = isds_logout(cisds);
186 finish_isds_operation(cisds, err);
187 if (err) exit_code = EXIT_FAILURE;
188 isds_ctx_free(&cisds);
190 isds_ctx_free(&czechpoint);
191 isds_cleanup();
193 /* Configuration */
194 cfg_free(configuration);
196 /* UI */
197 free(prompt);
198 free(commands);
200 exit(exit_code);
203 /* Set prompt. if @format is NULL, switch to default prompt */
204 static void set_prompt(const char *format, ...) {
205 char *buffer = NULL;
206 va_list ap;
207 if (format) {
208 va_start(ap, format);
209 shi_vasprintf(&buffer, format, ap);
210 va_end(ap);
212 if (buffer) {
213 shi_asprintf(&prompt, _("%s> "), buffer);
214 if (prompt) {
215 free(buffer);
216 return;
220 free(buffer);
221 free(prompt);
222 prompt = strdup(_("> "));
223 return;
226 zfree(prompt);
230 static int shi_load_configuration(const char *config_file) {
231 char *config_name = NULL;
232 int ret;
234 /* Get config file */
235 if (config_file) {
236 config_name = (char *) config_file;
237 } else {
238 if (-1 == shi_asprintf(&config_name, "%s/%s", getenv("HOME"),
239 CONFIG_FILE)) {
240 fprintf(stderr, _("Could not build configuration file name\n"));
241 return -1;
245 /* Parse configuration */
246 configuration = cfg_init(configuration_syntax, CFGF_NONE);
247 ret = cfg_parse(configuration, config_name);
248 if (ret) {
249 if (ret == CFG_FILE_ERROR) {
250 fprintf(stderr,
251 _("Error while opening configuration file `%s': %s\n"),
252 config_name, strerror(errno));
253 } else {
254 fprintf(stderr, _("Error while parsing configuration file `%s'\n"),
255 config_name);
257 oprintf(_("Using default configuration\n"));
260 if (config_name != config_file) free(config_name);
261 return 0;
265 void logger(isds_log_facility facility, isds_log_level level,
266 const char *message, int length, void *data) {
267 int fd;
268 ssize_t written, left = length;
270 if (!data) return;
271 fd = *((int *) data);
272 /*printf("\033[32mLOG(%02d,%02d): ", facility, level);
273 printf("%.*s", length, message);
274 printf("\033[m");*/
276 while (left) {
277 written = write(fd, message + length - left, left);
278 if (written == -1) {
279 fprintf(stderr,
280 _("Could not save log message into log file: %s\n"
281 "Log message discarded!\n"),
282 strerror(errno));
283 /*close(fd);
284 fd = -1;*/
285 return;
287 left-=written;
292 /* Redirect ISDS log to file if @file is not NULL. */
293 static int do_log_to_file(const char *file) {
294 if (file && *file) {
295 logger_fd = open_file_for_writing(file, 0, 1);
296 if (logger_fd == -1) {
297 fprintf(stderr, _("Could not redirect ISDS log to file `%s'\n"),
298 file);
299 return -1;
301 isds_set_log_callback(logger, &logger_fd);
303 return 0;
307 /* Add log facility based on its name. */
308 static int add_log_facility(isds_log_facility *facilities, const char *name) {
309 if (!facilities) return -1;
311 if (!strcmp(name, "none")) *facilities |= ILF_NONE;
312 else if (!strcmp(name, "http")) *facilities |= ILF_HTTP;
313 else if (!strcmp(name, "soap")) *facilities |= ILF_SOAP;
314 else if (!strcmp(name, "isds")) *facilities |= ILF_ISDS;
315 else if (!strcmp(name, "file")) *facilities |= ILF_FILE;
316 else if (!strcmp(name, "sec")) *facilities |= ILF_SEC;
317 else if (!strcmp(name, "xml")) *facilities |= ILF_XML;
318 else if (!strcmp(name, "all")) *facilities |= ILF_ALL;
319 else {
320 fprintf(stderr, _("%s: Unknown log facility\n"), name);
321 return -1;
324 return 0;
328 /* Save log facility into confuse configuration */
329 static void save_log_facility(int level) {
330 cfg_setlist(configuration, CONFIG_LOGFACILITIES, 0);
332 if (level == ILF_ALL) {
333 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "all");
334 return;
336 if (level == ILF_NONE) {
337 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "none");
338 return;
340 if (level & ILF_HTTP)
341 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "http");
342 if (level & ILF_SOAP)
343 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "soap");
344 if (level & ILF_ISDS)
345 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "isds");
346 if (level & ILF_FILE)
347 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "file");
348 if (level & ILF_SEC)
349 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "sec");
350 if (level & ILF_XML)
351 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "xml");
355 /* Clamp long int to unsigned int */
356 static unsigned int normalize_timeout(long int raw) {
357 if (raw < 0) {
358 oprintf(_("Configured network timeout is less then 0. "
359 "Clamped to 0.\n"));
360 return 0;
362 if (raw > UINT_MAX ) {
363 oprintf(_("Configured network timeout is greater then %1$u. "
364 "Clamped to %1$u.\n"), UINT_MAX);
365 return UINT_MAX;
367 return (unsigned int) raw;
371 /* Clamp long int to <0;100> */
372 static unsigned int normalize_log_level(long int raw) {
373 if (raw < 0) {
374 oprintf(_("Configured log level is less then 0. Clamped to 0.\n"));
375 return 0;
377 if (raw > ILL_ALL) {
378 oprintf(_("Configured log level is greater then %1$u. "
379 "Clamped to %1$u.\n"), ILL_ALL);
380 return ILL_ALL;
382 if (raw > UINT_MAX ) {
383 oprintf(_("Configured log level is greater then %1$u. "
384 "Clamped to %1$u.\n"), UINT_MAX);
385 return UINT_MAX;
387 return (unsigned int) raw;
391 static int shi_init(const char *config_file) {
392 isds_error err;
393 char *value;
394 unsigned int timeout, log_level;
395 isds_log_facility log_facility = ILF_NONE;
397 oprintf(_("This is Shigofumi, an ISDS client. "
398 "Have a nice e-government.\n"));
400 /* Do not permute arguments in getopt() */
401 if (setenv("POSIXLY_CORRECT", "", 1)) {
402 fprintf(stderr,
403 _("Could not set POSIXLY_CORRECT environment variable\n"));
404 return -1;
407 /* Load configuration */
408 if (shi_load_configuration(config_file))
409 return -1;
410 timeout = normalize_timeout(cfg_getint(configuration, CONFIG_TIMEOUT));
411 log_level = normalize_log_level(cfg_getint(configuration, CONFIG_LOGLEVEL));
413 /* Init readline */
414 rl_readline_name = "shigofumi";
415 rl_filename_quote_characters = "\\ >";
416 rl_filename_quoting_function = shi_quote_filename;
417 rl_filename_dequoting_function = shi_dequote_filename;
418 rl_char_is_quoted_p = shi_char_is_quoted;
420 /* Initialize ISDS */
421 err = isds_init();
422 if (err) {
423 fprintf(stderr, _("Could not initialize libisds library: %s\n"),
424 isds_strerror(err));
425 return -1;
428 /* Set ISDS logging */
429 value = cfg_getstr(configuration, CONFIG_LOGFILE);
430 if (do_log_to_file(value))
431 return -1;
432 for (int i = 0; i < cfg_size(configuration, CONFIG_LOGFACILITIES); i++) {
433 if (add_log_facility(&log_facility,
434 cfg_getnstr(configuration, CONFIG_LOGFACILITIES, i)))
435 return -1;
438 isds_set_logging(log_facility, log_level);
440 /* Set ISDS context up */
441 cisds = isds_ctx_create();
442 if (!cisds) {
443 fprintf(stderr, _("Could not create ISDS context\n"));
444 return -1;
446 err = isds_set_timeout(cisds, timeout);
447 if (err) {
448 fprintf(stderr, _("Could not set ISDS network timeout: %s\n"),
449 isds_strerror(err));
451 err = isds_set_progress_callback(cisds, shi_progressbar, NULL);
452 if (err) {
453 fprintf(stderr, _("Could not register network progress bar: %s: %s\n"),
454 isds_strerror(err), isds_long_message(cisds));
456 err = isds_set_opt(cisds, IOPT_NORMALIZE_MIME_TYPE,
457 cfg_getbool(configuration, CONFIG_NORMALIZEMIMETYPE));
458 if (err) {
459 fprintf(stderr,
460 cfg_getbool(configuration, CONFIG_NORMALIZEMIMETYPE) ?
461 _("Could not enable MIME type normalization: %s: %s\n") :
462 _("Could not disable MIME type normalization: %s: %s\n"),
463 isds_strerror(err), isds_long_message(cisds));
465 if (!cfg_getbool(configuration, CONFIG_VERIFYSERVER)) {
466 oprintf(_("Warning: Shigofumi disabled server identity verification "
467 "on user request!\n"));
468 err = isds_set_opt(cisds, IOPT_TLS_VERIFY_SERVER, 0);
469 if (err) {
470 fprintf(stderr,
471 _("Could not disable server identity verification: "
472 "%s: %s\n"),
473 isds_strerror(err), isds_long_message(cisds));
476 if ((value = cfg_getstr(configuration, CONFIG_CAFILE))) {
477 err = isds_set_opt(cisds, IOPT_TLS_CA_FILE, value);
478 if (err) {
479 fprintf(stderr,
480 _("Could not set file with CA certificates: %s: %s: %s\n"),
481 value, isds_strerror(err), isds_long_message(cisds));
484 if ((value = cfg_getstr(configuration, CONFIG_CADIRECTORY))) {
485 err = isds_set_opt(cisds, IOPT_TLS_CA_DIRECTORY, value);
486 if (err) {
487 fprintf(stderr,
488 _("Could not set directory with CA certificates: "
489 "%s: %s: %s\n"),
490 value, isds_strerror(err), isds_long_message(cisds));
493 if ((value = cfg_getstr(configuration, CONFIG_CRLFILE))) {
494 err = isds_set_opt(cisds, IOPT_TLS_CRL_FILE, value);
495 if (err) {
496 fprintf(stderr, _("Could not set file with CRL: %s: %s: %s\n"),
497 value, isds_strerror(err), isds_long_message(cisds));
502 /* Set Czech POINT context up */
503 czechpoint = isds_ctx_create();
504 if (!czechpoint) {
505 fprintf(stderr, _("Could not create Czech POINT context\n"));
506 return -1;
508 err = isds_set_timeout(czechpoint, timeout);
509 if (err) {
510 fprintf(stderr, _("Could not set Czech POINT network timeout: %s\n"),
511 isds_strerror(err));
513 err = isds_set_progress_callback(czechpoint, shi_progressbar, NULL);
514 if (err) {
515 fprintf(stderr, "Could not register network progress bar: %s: %s\n",
516 isds_strerror(err), isds_long_message(cisds));
519 return 0;
523 static int shi_quit(int argc, const char **argv) {
524 shi_exit(EXIT_SUCCESS);
525 return 0;
530 static void shi_help_usage(const char *command) {
531 oprintf(_(
532 "Usage: %s [COMMAND]\n"
533 "Show COMMAND manual or list of currently available commands.\n"
535 command);
539 static int shi_help(int argc, const char **argv) {
540 size_t command_width = 14;
541 int i;
543 if (!commands) {
544 fprintf(stderr, _("No command is available\n"));
545 return -1;
548 if (argc == 2 && argv[1] && *argv[1]) {
549 /* Show usage for given command */
550 for (i = 0; (*commands)[i].name; i++) {
551 if (!strcmp((*commands)[i].name, argv[1])) {
552 if ((*commands)[i].usage)
553 (*commands)[i].usage((*commands)[i].name);
554 else if ((*commands)[i].description) {
555 ohprint((*commands)[i].name, command_width);
556 oprintf(" %s\n", _((*commands)[i].description));
558 else
559 fprintf(stderr,
560 _("%s: %s: Command description not defined\n"),
561 argv[0], argv[1]);
562 return 0;
565 fprintf(stderr, _("%s: %s: No such command exists\n"), argv[0], argv[1]);
566 return -1;
569 /* Or list all commands */
570 oprintf(_("Following commands are available:\n"));
571 for (i = 0; (*commands)[i].name; i++) {
572 ohprint((*commands)[i].name, command_width);
573 oprintf(" %s\n", _((*commands)[i].description));
576 return 0;
580 static void show_version(void) {
581 char *libisds_version = isds_version();
583 oprintf(_("This is Shigofumi version %s.\n"), PACKAGE_VERSION);
584 oprintf(_("\n"
585 "Used libraries\n"
586 "Readline: %s\n"
587 "libisds: %s\n"
589 rl_library_version, libisds_version);
590 free(libisds_version);
594 static int shi_version(int argc, const char **argv) {
595 show_version();
596 oprintf(_(
597 "\n"
598 "-----\n"
599 "It's a shigofumi. A letter delivered from the afterlife. (Fumika)\n"
600 "A message can not be delivered to dead person. (ISDS specification)\n"
601 "Virtual and real world. They can be compatible. (Program author)\n"
603 return 0;
607 static int shi_copying(int argc, const char **argv) {
608 oprintf(_(
609 "This is Shigofumi, an ISDS client.\n"
610 "Copyright (C) 2010, 2011, 2012 Petr Pisar\n"
611 "\n"
612 "This program is free software: you can redistribute it and/or modify\n"
613 "it under the terms of the GNU General Public License as published by\n"
614 "the Free Software Foundation, either version 3 of the License, or\n"
615 "(at your option) any later version.\n"
616 "\n"
617 "This program is distributed in the hope that it will be useful,\n"
618 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
619 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
620 "GNU General Public License for more details.\n"
621 "\n"
622 "You should have received a copy of the GNU General Public License\n"
623 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n"
625 return 0;
629 static int shi_cache(int argc, const char **argv) {
630 const struct isds_list *item;
631 size_t i;
633 if (boxes) {
634 for (item = boxes, i = 0; item; item = item->next, i++);
635 oprintf(_(
636 "Cached box list: %zu\n"),
640 if (messages) {
641 oprintf(_(
642 "Cached message list:\n"
643 "\tDirection: %s\n"
644 "\tMessages: %'lu\n"),
645 (messages_are_outgoing) ? _("Outgoing") : _("Incoming"),
646 total_messages);
649 if (message) {
650 oprintf(_("Cached message: %s\n"),
651 (message->envelope && message->envelope->dmID) ?
652 message->envelope->dmID : _("<Unknown ID>"));
655 return 0;
659 static void shi_chdir_usage(const char *command) {
660 oprintf(_(
661 "Usage: %s [DIRECTORY]\n"
662 "Change working directory to DIRECTORY.\n"
663 "If no DIRECTORY is supplied, HOME directory will be used.\n"),
664 command);
668 static int shi_chdir(int argc, const char **argv) {
669 const char *directory = NULL;
671 if (!argv || argc > 2) {
672 shi_chdir_usage((argv) ? argv[0] : NULL);
673 return -1;
676 if (argc == 2 && argv[1] && *argv[1])
677 directory = argv[1];
678 else {
679 directory = getenv("HOME");
680 if (!directory) {
681 oprintf("Environment variable HOME does not exist\n");
682 return -1;
685 if (chdir(directory)) {
686 oprintf(_("Could not change working directory: %s: %s\n"), directory,
687 strerror(errno));
688 return -1;
691 return 0;
695 static int shi_pwd(int argc, const char **argv) {
696 char *buffer = NULL, *newbuffer;
697 size_t length = 0;
699 while (length += 1024) {
700 newbuffer = realloc(buffer, length);
701 if (!newbuffer) {
702 fprintf(stderr, _("Error: Not enough memory\n"));
703 free(buffer);
704 return -1;
706 buffer = newbuffer;
708 if (getcwd(buffer, length)) {
709 oprintf("%s\n", buffer);
710 free(buffer);
711 return 0;
715 fprintf(stderr, _("Error: Current directory string is too long\n"));
716 free(buffer);
717 return -1;
721 /* Deallocate *@destination and duplicate @new_value if non-NULL. In case of
722 * error, it prints error message and returns -1. Otherwise it returns 0. */
723 static int replace_string(char **destination, const char *new_value) {
724 if (destination == NULL) return -1;
725 zfree(*destination);
726 if (new_value != NULL) {
727 *destination = strdup(new_value);
728 if (*destination == NULL) {
729 fprintf(stderr, _("Not enough memory\n"));
730 return -1;
733 return 0;
737 /* Convert name of PKI format into ISDS format type.
738 * Return -1 in case of invalid name */
739 static int string2pki_format(const char *name, isds_pki_format *format) {
740 if (!name || !format) { return -1; }
741 if (!strcasecmp(name, "PEM")) {
742 *format = PKI_FORMAT_PEM;
743 } else if (!strcasecmp(name, "DER")) {
744 *format = PKI_FORMAT_DER;
745 } else if (!strcasecmp(name, "ENG")) {
746 *format = PKI_FORMAT_ENG;
747 } else {
748 return -1;
750 return 0;
754 /* Convert name of OTP authentication method into ISDS method type.
755 * Return -1 in case of invalid name */
756 static int string2otp_method(const char *name, isds_otp_method *method) {
757 if (!name || !method) { return -1; }
758 if (!strcasecmp(name, "HOTP")) {
759 *method = OTP_HMAC;
760 } else if (!strcasecmp(name, "TOTP")) {
761 *method = OTP_TIME;
762 } else {
763 return -1;
765 return 0;
769 /* Log-in to ISDS.
770 * Return -1 in case of failure, 0 in case of success, +1 in case of partial
771 * success (e.g. TOTP preauthentication to obtain new code succeeded, but user
772 * is still not logged in because second phase is necessary). */
773 static int do_login(void) {
774 isds_error err;
775 struct isds_pki_credentials pki;
776 struct isds_otp otp;
778 /* Build OTP structure */
779 if (NULL != otp_method) {
780 if (string2otp_method(otp_method, &otp.method)) {
781 fprintf(stderr, _("Error: Invalid one-time password "
782 "authentication method `%s'\n"), otp_method);
783 return -1;
787 /* Announce base URL */
788 oprintf(_("ISDS base URL: %s\n"),
789 (server == NULL) ? _("<default>") : server);
791 if (batch_mode) {
792 oprintf(_("Unattended mode detected. "
793 "Make sure credentials have been preset.\n"));
794 } else {
795 oprintf(_("You are going to insert credentials for your account.\n"
796 "Leave blank line to choose default value.\n"));
798 select_completition(COMPL_NONE);
800 /* Ask for user name if not predefined */
801 if (NULL == username) {
802 shi_ask_for_string(&username, _("Input ISDS user name: "),
803 cfg_getstr(configuration, CONFIG_USERNAME), batch_mode);
806 /* Ask for password */
807 shi_ask_for_password(&password, _("Input ISDS password: "),
808 cfg_getstr(configuration, CONFIG_PASSWORD), batch_mode);
810 /* Ask for key password if PKI authentication requested */
811 if (pki_certificate_path) {
812 shi_ask_for_password(&key_password, _("Input private key password: "),
813 cfg_getstr(configuration, CONFIG_KEY_PASSWORD), batch_mode);
816 /* Ask for OTP code if OTP authentication requested */
817 if (NULL != otp_method) {
818 shi_ask_for_password(&otp_code,
819 (otp.method == OTP_TIME) ?
820 _("Input one-time code (empty to send new one): ") :
821 _("Input one-time code: "),
822 cfg_getstr(configuration, CONFIG_OTP_CODE), batch_mode);
823 otp.otp_code = otp_code;
826 select_completition(COMPL_COMMAND);
827 set_prompt(NULL);
829 /* Build PKI structure */
830 if (pki_certificate_path) {
831 pki.engine = pki_engine;
832 if (NULL == pki_certificate_format) {
833 fprintf(stderr, _("Error: No certficate format supplied\n"));
834 return -1;
836 if (string2pki_format(pki_certificate_format, &pki.certificate_format)) {
837 fprintf(stderr, _("Error: Invalid certificate format `%s'\n"),
838 pki_certificate_format);
839 return -1;
841 if (NULL == pki_key_format) {
842 fprintf(stderr, _("Error: No private key format supplied\n"));
843 return -1;
845 if (string2pki_format(pki_key_format, &pki.key_format)) {
846 fprintf(stderr, _("Error: Invalid private key format `%s'\n"),
847 pki_key_format);
848 return -1;
850 pki.certificate = pki_certificate_path;
851 pki.key = pki_key_path;
852 pki.passphrase = key_password;
855 if (NULL != otp_method && OTP_TIME == otp.method && NULL == otp_code)
856 printf(_("Requesting one-time code from server for "
857 "a login...\n"));
858 else
859 printf(_("Logging in...\n"));
860 err = isds_login(cisds, server, username, password,
861 (NULL != pki_certificate_path) ? &pki : NULL,
862 (NULL != otp_method) ? &otp : NULL);
863 if (NULL != otp_method && OTP_TIME == otp.method && NULL == otp_code)
864 finish_isds_operation_with_code(cisds, err, IE_PARTIAL_SUCCESS);
865 else
866 finish_isds_operation(cisds, err);
868 if (IE_PARTIAL_SUCCESS == err) {
869 printf(_("OTP code has been sent by ISDS successfully.\n"
870 "Once you receive the code, retry log-in with "
871 "the code.\n"));
872 return 1;
873 } else if (err) {
874 printf(_("Log-in failed\n"));
875 return -1;
878 oprintf(_("Logged in.\n"));
879 return 0;
883 static struct isds_DbOwnerInfo *do_box(void) {
884 isds_error err;
885 struct isds_DbOwnerInfo *box = NULL;
887 printf(_("Getting box details you are logged in...\n"));
888 err = isds_GetOwnerInfoFromLogin(cisds, &box);
889 finish_isds_operation(cisds, err);
891 return box;
895 static int shi_box(int argc, const char **argv) {
896 struct isds_DbOwnerInfo *box = NULL;
898 box = do_box();
899 if (!box) return -1;
901 format_DbOwnerInfo(box);
903 isds_DbOwnerInfo_free(&box);
904 return 0;
908 /* Get info about box with @id.
909 * @id is UTF-8 encoded
910 * Return NULL in case of error, otherwise box description that caller must
911 * free. */
912 static struct isds_DbOwnerInfo *stat_box(const char *id) {
913 isds_error err;
914 struct isds_DbOwnerInfo criteria;
915 struct isds_list *boxes = NULL, *item;
916 struct isds_DbOwnerInfo *box = NULL;
917 char *id_locale = NULL;
919 if (!id || !*id) return NULL;
921 id_locale = utf82locale(id);
922 memset(&criteria, 0, sizeof(criteria));
923 criteria.dbID = (char *) id;
925 printf(_("Getting details about box with ID `%s'...\n"), id_locale);
926 err = isds_FindDataBox(cisds, &criteria, &boxes);
927 finish_isds_operation(cisds, err);
928 if (err) goto leave;
930 for(item = boxes; item; item = item->next) {
931 if (!item->data) continue;
933 if (item->next) {
934 fprintf(stderr, _("Error: More boxes match ID `%s'\n"), id_locale);
935 goto leave;
938 box = (struct isds_DbOwnerInfo *) item->data;
939 item->data = NULL;
940 break;
943 leave:
944 free(id_locale);
945 isds_list_free(&boxes);
947 return box;
951 static void shi_commercialreceiving_usage(const char *command) {
952 oprintf(_(
953 "Usage: %s [-0|-1] [BOX_ID]\n"
954 "Manipulate commercial receiving box status.\n"
955 " -O switch off receiving of commercial messages\n"
956 " -1 switch on receiving of commercial messages\n"
957 " BOX_ID affects box with ID BOX_ID; default is box you are logged in\n"
958 "If no option is given, show current commercial receiving status.\n"),
959 command);
963 /* Manipulate commercial receiving box status */
964 static int shi_commercialreceiving(int argc, const char **argv) {
965 isds_error err;
966 struct isds_DbOwnerInfo *box = NULL;
967 int opt;
968 int action = -1;
969 char *box_id = NULL, *box_id_locale = NULL;
970 _Bool static_box_id;
971 int retval = 0;
973 optind = 0;
974 while ((opt = getopt(argc, (char * const *)argv, "01")) != -1) {
975 switch (opt) {
976 case '0':
977 action = 0;
978 break;
979 case '1':
980 action = 1;
981 break;
982 default:
983 shi_commercialreceiving_usage((argv)?argv[0]:NULL);
984 return -1;
987 if (optind + 1 < argc) {
988 fprintf(stderr, _("Bad invocation\n"));
989 shi_commercialreceiving_usage((argv)?argv[0]:NULL);
990 return -1;
993 if (!argv[optind] || !*argv[optind]) {
994 /* Get current box ID */
995 box = do_box();
996 if (!box || !box->dbID || !*box->dbID) {
997 isds_DbOwnerInfo_free(&box);
998 fprintf(stderr, _("Could not get current box ID\n"));
999 return -1;
1001 box_id = box->dbID; static_box_id = 1;
1002 box_id_locale = utf82locale(box_id);
1003 } else {
1004 /* Box ID supplied as argument */
1005 box_id_locale = (char *) argv[optind];
1006 box_id = locale2utf8(box_id_locale); static_box_id = 0;
1007 if (!box_id) {
1008 fprintf(stderr, _("Could not convert box ID `%s' to UTF-8\n"),
1009 box_id_locale);
1010 return -1;
1014 if (action == -1) {
1015 if (!box) box = stat_box(box_id);
1016 if (!box) {
1017 fprintf(stderr, _("Could not get details about box ID `%s'\n"),
1018 box_id_locale);
1019 retval = -1;
1020 goto leave;
1023 oprintf(_("Commercial receiving status of box `%s': "), box_id_locale);
1024 if (!box->dbOpenAddressing)
1025 oprintf(_("Unknown\n"));
1026 else if (*box->dbOpenAddressing)
1027 oprintf(_("Positive\n"));
1028 else
1029 oprintf(_("Negative\n"));
1030 } else {
1031 char *refnumber = NULL;
1032 printf((action) ?
1033 _("Switching `%s' box commercial receiving on...\n"):
1034 _("Switching `%s' box commercial receiving off...\n"),
1035 box_id_locale);
1036 err = isds_switch_commercial_receiving(cisds, box_id, action,
1037 NULL, &refnumber);
1038 finish_isds_operation(cisds, err);
1040 if (!err) {
1041 char *refnumber_locale = utf82locale(refnumber);
1042 oprintf(_("Commercial receiving status successfully changed. "
1043 "Assigned reference number: %s\n"),
1044 refnumber_locale);
1045 free(refnumber_locale);
1046 } else {
1047 oprintf(_("Commercial receiving status has not been changed.\n"));
1048 retval = -1;
1050 free(refnumber);
1053 leave:
1054 if (!static_box_id) free(box_id);
1055 else free(box_id_locale);
1056 isds_DbOwnerInfo_free(&box);
1057 return retval;
1061 static void shi_commercialsending_usage(const char *command) {
1062 oprintf(_(
1063 "Usage: %s [BOX_ID]\n"
1064 "Retrieve permissions to send commercial messages from a box.\n"
1065 " BOX_ID query permissions for box with ID BOX_ID; default is box you\n"
1066 " are logged in\n"),
1067 command);
1071 /* Retrieve permissions to send commercial messages */
1072 static int shi_commercialsending(int argc, const char **argv) {
1073 isds_error err;
1074 struct isds_DbOwnerInfo *box = NULL;
1075 struct isds_list *permissions = NULL, *item;
1076 int opt;
1077 char *box_id = NULL, *box_id_locale = NULL;
1078 _Bool static_box_id;
1079 int ordinar;
1080 int retval = 0;
1082 optind = 0;
1083 while ((opt = getopt(argc, (char * const *)argv, "h")) != -1) {
1084 switch (opt) {
1085 case 'h':
1086 shi_commercialsending_usage((argv)?argv[0]:NULL);
1087 return 0;
1088 default:
1089 shi_commercialsending_usage((argv)?argv[0]:NULL);
1090 return -1;
1093 if (optind + 1 < argc) {
1094 fprintf(stderr, _("Bad invocation\n"));
1095 shi_commercialsending_usage((argv)?argv[0]:NULL);
1096 return -1;
1099 if (!argv[optind] || !*argv[optind]) {
1100 /* Get current box ID */
1101 box = do_box();
1102 if (!box || !box->dbID || !*box->dbID) {
1103 isds_DbOwnerInfo_free(&box);
1104 fprintf(stderr, _("Could not get current box ID\n"));
1105 return -1;
1107 box_id = box->dbID; static_box_id = 1;
1108 box_id_locale = utf82locale(box_id);
1109 } else {
1110 /* Box ID supplied as argument */
1111 box_id_locale = (char *) argv[optind];
1112 box_id = locale2utf8(box_id_locale); static_box_id = 0;
1113 if (!box_id) {
1114 fprintf(stderr, _("Could not convert box ID `%s' to UTF-8\n"),
1115 box_id_locale);
1116 return -1;
1120 printf(_("Querying `%s' box commercial sending permissions...\n"),
1121 box_id_locale);
1122 err = isds_get_commercial_permissions(cisds, box_id, &permissions);
1123 finish_isds_operation(cisds, err);
1125 if (!err) {
1126 oprintf(_("Permissions to send commercial messages from box `%s':\n"),
1127 box_id_locale);
1128 for (item = permissions, ordinar = 0; item; item=item->next) {
1129 if (!item->data) continue;
1130 ordinar++;
1131 oprintf(_("\n* Permission #%d:\n"), ordinar);
1132 format_commercial_permission(item->data);
1134 if (ordinar == 0)
1135 oprintf(_("No permission exists.\n"));
1136 } else {
1137 oprintf(_("Could not list permissions to send commercial messages "
1138 "from box `%s'.\n"), box_id_locale);
1139 retval = -1;
1142 isds_list_free(&permissions);
1143 if (!static_box_id) free(box_id);
1144 else free(box_id_locale);
1145 isds_DbOwnerInfo_free(&box);
1146 return retval;
1150 static int shi_user(int argc, const char **argv) {
1151 isds_error err;
1152 struct isds_DbUserInfo *user = NULL;
1154 printf(_("Getting user details you are logged as...\n"));
1155 err = isds_GetUserInfoFromLogin(cisds, &user);
1156 finish_isds_operation(cisds, err);
1157 if (err) return -1;
1159 format_DbUserInfo(user);
1161 isds_DbUserInfo_free(&user);
1162 return 0;
1166 static void shi_users_usage(const char *command) {
1167 oprintf(_(
1168 "Usage: %s BOX_ID\n"
1169 "Get list of users having access to box with BOX_ID.\n"),
1170 command);
1174 static int shi_users(int argc, const char **argv) {
1175 isds_error err;
1176 struct isds_list *users = NULL, *item;
1177 int ordinar;
1179 if (!argv || !argv[1] || !*argv[1]) {
1180 shi_users_usage((argv)?argv[0]:NULL);
1181 return -1;
1184 printf(_("Getting users of box with ID `%s'...\n"), argv[1]);
1185 err = isds_GetDataBoxUsers(cisds, argv[1], &users);
1186 finish_isds_operation(cisds, err);
1187 if (err) return -1;
1189 for (item = users, ordinar = 0; item; item=item->next) {
1190 if (!item->data) continue;
1191 ordinar++;
1192 oprintf(_("\n* User #%d:\n"), ordinar);
1193 format_DbUserInfo(item->data);
1195 if (ordinar == 0)
1196 oprintf(_("Empty list of users returned.\n"));
1198 isds_list_free(&users);
1199 return 0;
1203 static int show_password_expiration(void) {
1204 isds_error err;
1205 struct timeval *expiration = NULL;
1207 err = isds_get_password_expiration(cisds, &expiration);
1208 finish_isds_operation(cisds, err);
1209 if (err) {
1210 fprintf(stderr, "Could not get password expiration time\n");
1211 return -1;
1214 print_header_timeval(_("Your password expires at"), expiration);
1215 free(expiration);
1216 return 0;
1220 /* Change password in ISDS */
1221 static int do_passwd(void) {
1222 char *old_password = NULL;
1223 char *new_password = NULL;
1224 char *new_password2 = NULL;
1225 struct isds_otp otp;
1226 char *refnumber = NULL;
1227 isds_error err = IE_ERROR;
1228 int retval = 0;
1230 if (replace_string(&otp_method, cfg_getstr(configuration, CONFIG_OTP_METHOD)))
1231 return -1;
1232 /* Build OTP structure */
1233 if (NULL != otp_method) {
1234 if (string2otp_method(otp_method, &otp.method)) {
1235 fprintf(stderr, _("Error: Invalid one-time password "
1236 "authentication method `%s'\n"), otp_method);
1237 return -1;
1241 select_completition(COMPL_NONE);
1243 oprintf(_(
1244 "You are going to change your password. If you don't want to change your\n"
1245 "password, insert empty string or EOF.\n"
1246 "\n"
1247 "You will be asked for your current (old) password and then for new password.\n"
1248 "ISDS forces some criteria new password must fulfill. Current rules are:\n"
1249 "\tLength: minimal 8, maximal 32 characters\n"
1250 "\tMust contain at least: 1 upper case letter, 1 lower case letter, 1 digit\n"
1251 "\tAllowed alphabet: [a-z][A-Z][0-9][!#$%%&()*+,-.:=?@[]_{}|~]\n"
1252 "\tMust differ from last 255 passwords\n"
1253 "\tMust not contain user ID\n"
1254 "\tMust not contain sequence of three or more same characters\n"
1255 "\tMust not start with `qwert', `asdgf', or `12345'\n"
1256 "Finally, you must repeat your new password to avoid mistakes.\n"
1257 "After password change will be confirmed, you must log in again as password\n"
1258 "is transmitted to server on each request.\n"
1259 "\n"));
1261 old_password = ask_for_password(_("Old password: "));
1262 if (!old_password || *old_password == '\0') {
1263 fprintf(stderr, _("No password supplied\n"));
1264 goto error;
1267 /* Ask for OTP code if OTP authentication requested */
1268 zfree(otp_code);
1269 if (NULL != otp_method) {
1270 shi_ask_for_password(&otp_code,
1271 (otp.method == OTP_TIME) ?
1272 _("One-time code (empty to send new one): ") :
1273 _("One-time code: "),
1274 cfg_getstr(configuration, CONFIG_OTP_CODE), batch_mode);
1275 otp.otp_code = otp_code;
1279 if (NULL == otp_method || NULL != otp_code) {
1280 new_password = ask_for_password(_("New password: "));
1281 if (!new_password || *new_password == '\0') {
1282 fprintf(stderr, _("No password supplied\n"));
1283 goto error;
1286 new_password2 = ask_for_password(_("Repeat new password: "));
1287 if (!new_password2 || new_password2 == '\0') {
1288 fprintf(stderr, _("No password supplied\n"));
1289 goto error;
1292 if (strcmp(new_password, new_password2)) {
1293 fprintf(stderr, _("New passwords differ\n"));
1294 goto error;
1298 if (NULL != otp_method && OTP_TIME == otp.method && NULL == otp_code)
1299 printf(_("Requesting one-time code from server for "
1300 "a password change...\n"));
1301 else
1302 printf(_("Changing password...\n"));
1303 err = isds_change_password(cisds, old_password, new_password,
1304 (NULL != otp_method) ? &otp : NULL, &refnumber);
1305 if (NULL != otp_method && OTP_TIME == otp.method && NULL == otp_code)
1306 finish_isds_operation_with_code(cisds, err, IE_PARTIAL_SUCCESS);
1307 else
1308 finish_isds_operation(cisds, err);
1310 if (NULL != refnumber) {
1311 char *refnumber_locale = utf82locale(refnumber);
1312 free(refnumber);
1313 oprintf(_("Assigned reference number: %s\n"), refnumber_locale);
1314 free(refnumber_locale);
1316 if (IE_PARTIAL_SUCCESS == err) {
1317 oprintf(_("OTP code has been sent by ISDS successfully.\n"
1318 "Once you receive the code, retry changing password with "
1319 "the code.\n"));
1320 goto leave;
1321 } else if (err) {
1322 printf(_("Password change failed\n"));
1323 goto error;
1324 } else {
1325 oprintf(_("Password HAS been successfully changed.\n"));
1326 goto leave;
1329 error:
1330 retval = -1;
1331 oprintf(_("Password has NOT been changed!\n"));
1333 leave:
1334 free(old_password);
1335 free(new_password);
1336 free(new_password2);
1337 zfree(otp_code);
1339 set_prompt(NULL);
1340 select_completition(COMPL_COMMAND);
1342 oprintf(_("\n"
1343 "Remember, ISDS password has limited life time.\n"));
1344 return retval;
1348 static void shi_passwd_usage(const char *command) {
1349 oprintf(_(
1350 "Usage: %s [-S]\n"
1351 "Manipulate user password or change it if no option given.\n"
1352 "\n"
1353 "Options:\n"
1354 " -S show password expiration time\n"
1355 ), command);
1359 static int shi_passwd(int argc, const char **argv) {
1360 int opt;
1362 optind = 0;
1363 while ((opt = getopt(argc, (char * const *)argv, "S")) != -1) {
1364 switch (opt) {
1365 case 'S':
1366 return show_password_expiration();
1367 default:
1368 shi_passwd_usage(argv[0]);
1369 return -1;
1372 if (optind != argc || argc > 1) {
1373 fprintf(stderr, _("Bad invocation\n"));
1374 shi_passwd_usage((argv)?argv[0]:NULL);
1375 return -1;
1378 return do_passwd();
1382 static void shi_login_usage(const char *command) {
1383 oprintf(_(
1384 "Usage: %s [OPTIONS] [USER_NAME]\n"
1385 "Attemp to log into ISDS server.\n"
1386 "\n"
1387 "Options:\n"
1388 " -b URL ISDS server base URL\n"
1389 " -c IDENTIFIER user certificate\n"
1390 " -C FORMAT user certificate format\n"
1391 " -k IDENTIFIER user private key\n"
1392 " -K FORMAT user private key format\n"
1393 " -e IDENTIFIER cryptographic engine\n"
1394 " -o METHOD use one-time password authentication method\n"
1395 "\n"
1396 "Recognized certificate and key FORMATS are:\n"
1397 " PEM Base64 encoded serialization in local file\n"
1398 " DER binary serialization in local file\n"
1399 " ENG material is stored in cryptographic engine\n"
1400 "Identifiers of cryptographic engine, certificate, and private key are\n"
1401 "specific for underlying cryptographic library.\n"
1402 "\n"
1403 "Recognized one-time password methods are:\n"
1404 " HOTP HMAC-based OTP method\n"
1405 " TOTP time-based OTP method\n"
1406 "\n"
1407 "Values of omitted options are taken from configuration file.\n"
1408 ), command);
1412 static int shi_login(int argc, const char **argv) {
1413 int opt;
1414 int status;
1416 discard_credentials();
1418 /* Load stored configuration */
1419 if (replace_string(&server, cfg_getstr(configuration, CONFIG_SERVER)))
1420 return -1;
1421 if (replace_string(&otp_method, cfg_getstr(configuration, CONFIG_OTP_METHOD)))
1422 return -1;
1423 if (replace_string(&pki_engine,
1424 cfg_getstr(configuration, CONFIG_KEY_ENGINE)))
1425 return -1;
1426 if (replace_string(&pki_certificate_path,
1427 cfg_getstr(configuration, CONFIG_CERT_PATH)))
1428 return -1;
1429 if (replace_string(&pki_certificate_format,
1430 cfg_getstr(configuration, CONFIG_CERT_FORMAT)))
1431 return -1;
1432 if (replace_string(&pki_key_path,
1433 cfg_getstr(configuration, CONFIG_KEY_PATH)))
1434 return -1;
1435 if (replace_string(&pki_key_format,
1436 cfg_getstr(configuration, CONFIG_KEY_FORMAT)))
1437 return -1;
1439 /* Override configuration with positional arguments */
1440 optind = 0;
1441 while ((opt = getopt(argc, (char * const *)argv, "b:c:C:k:K:e:o:")) != -1) {
1442 switch (opt) {
1443 case 'b':
1444 if (replace_string(&server, optarg)) return -1;
1445 break;
1446 case 'c':
1447 if (replace_string(&pki_certificate_path, optarg)) return -1;
1448 break;
1449 case 'C':
1450 if (replace_string(&pki_certificate_format, optarg)) return -1;
1451 break;
1452 case 'k':
1453 if (replace_string(&pki_key_path, optarg)) return -1;
1454 break;
1455 case 'K':
1456 if (replace_string(&pki_key_format, optarg)) return -1;
1457 break;
1458 case 'e':
1459 if (replace_string(&pki_engine, optarg)) return -1;
1460 break;
1461 case 'o':
1462 if (replace_string(&otp_method, optarg)) return -1;
1463 break;
1464 default:
1465 shi_login_usage(argv[0]);
1466 return -1;
1469 if (optind < argc - 1) {
1470 fprintf(stderr, _("Bad invocation\n"));
1471 shi_login_usage(argv[0]);
1472 return -1;
1474 if (optind == argc - 1) {
1475 username = strdup(argv[optind]);
1478 /* Proceed log-in */
1479 status = do_login();
1480 if (status < 0) return -1;
1482 /* If log-in passed, store configuration */
1483 cfg_setstr(configuration, CONFIG_SERVER, server);
1484 cfg_setstr(configuration, CONFIG_USERNAME, username);
1485 cfg_setstr(configuration, CONFIG_PASSWORD, password);
1486 cfg_setstr(configuration, CONFIG_KEY_PASSWORD, key_password);
1487 cfg_setstr(configuration, CONFIG_OTP_METHOD, otp_method);
1488 cfg_setstr(configuration, CONFIG_KEY_ENGINE, pki_engine);
1489 cfg_setstr(configuration, CONFIG_CERT_PATH, pki_certificate_path);
1490 cfg_setstr(configuration, CONFIG_CERT_FORMAT, pki_certificate_format);
1491 cfg_setstr(configuration, CONFIG_KEY_PATH, pki_key_path);
1492 cfg_setstr(configuration, CONFIG_KEY_FORMAT, pki_key_format);
1494 /* Get some details only if fully logged in */
1495 if (0 == status) {
1496 show_password_expiration();
1498 return 0;
1502 static void shi_debug_usage(const char *command) {
1503 oprintf(_(
1504 "Usage: %s -l LEVEL [-f FACILITY...] [{-e | -o FILE}]\n"
1505 "Debug FACILITIES on LEVEL.\n"
1506 "\n"
1507 "-l LEVEL set log level, valid interval <%d,%d>, default is %d\n"
1508 " %d is no logging, %d critical, %d errors,\n"
1509 " %d warnings, %d info, %d debug, %d all\n"
1510 "-f FACILITY debug only given facility, repeat this option to debug\n"
1511 " more facilities; valid values: none, http, soap, isds,\n"
1512 " file, sec, xml, all; default is none\n"
1513 "-e write debug log into stderr\n"
1514 "-o FILE append debug log to FILE\n"
1516 command,
1517 ILL_NONE, ILL_ALL, ILL_NONE,
1518 ILL_NONE, ILL_CRIT, ILL_ERR, ILL_WARNING,
1519 ILL_INFO, ILL_DEBUG, ILL_ALL);
1522 static int shi_debug(int argc, const char **argv) {
1523 int opt;
1524 int log_level = ILL_NONE;
1525 isds_log_facility log_facility = ILF_NONE;
1526 char *file = NULL;
1527 _Bool close_log = 0;
1529 optind = 0;
1530 while ((opt = getopt(argc, (char * const *)argv, "l:f:eo:")) != -1) {
1531 switch (opt) {
1532 case 'l':
1533 log_level = normalize_log_level(atoi(optarg));
1534 break;
1535 case 'f':
1536 if (add_log_facility(&log_facility, optarg)) return -1;
1537 break;
1538 case 'e':
1539 close_log = 1;
1540 case 'o':
1541 file = optarg;
1542 break;
1543 default:
1544 shi_debug_usage(argv[0]);
1545 return -1;
1548 if (optind == 1 || optind != argc) {
1549 fprintf(stderr, _("Bad invocation\n"));
1550 shi_debug_usage(argv[0]);
1551 return -1;
1554 /* Redirect log */
1555 if (close_log) {
1556 isds_set_log_callback(NULL, NULL);
1558 if (logger_fd != -1) {
1559 if (-1 == close(logger_fd)) {
1560 fprintf(stderr, _("Closing log file failed: %s\n"),
1561 strerror(errno));
1562 return -1;
1565 cfg_setstr(configuration, CONFIG_LOGFILE, NULL);
1567 if (do_log_to_file(file))
1568 return -1;
1569 if (file) cfg_setstr(configuration, CONFIG_LOGFILE, file);
1571 /* Set log levels */
1572 isds_set_logging(log_facility, log_level);
1573 cfg_setint(configuration, CONFIG_LOGLEVEL, log_level);
1574 save_log_facility(log_facility);
1576 return 0;
1580 static void show_setting_str(const char *variable, _Bool cenzore) {
1581 if (!variable) return;
1583 const char *value = cfg_getstr(configuration, variable);
1585 if (value) {
1586 if (cenzore)
1587 oprintf(_("%s = <set>\n"), variable);
1588 else
1589 oprintf(_("%s = `%s'\n"), variable, value);
1590 } else {
1591 oprintf(_("%s = <unset>\n"), variable);
1596 static void show_setting_boolean(const char *variable) {
1597 if (!variable) return;
1599 _Bool value = cfg_getbool(configuration, variable);
1601 if (value) {
1602 oprintf(_("%s = <true>\n"), variable);
1603 } else {
1604 oprintf(_("%s = <false>\n"), variable);
1609 static void show_setting_int(const char *variable) {
1610 if (!variable) return;
1612 long int value = cfg_getint(configuration, variable);
1614 oprintf(_("%s = %ld\n"), variable, value);
1618 static void show_setting_strlist(const char *variable) {
1619 if (!variable) return;
1621 int length = cfg_size(configuration, variable);
1623 if (length <= 0) {
1624 oprintf(_("%s = <unset>\n"), variable);
1625 } else {
1626 oprintf(_("%s = {"), variable);
1627 for (int i = 0; i < length; i++) {
1628 const char *value = cfg_getnstr(configuration, variable, i);
1629 if (i < length - 1)
1630 oprintf(_("`%s', "), value);
1631 else
1632 oprintf(_("`%s'}\n"), value);
1638 static int shi_settings(int argc, const char **argv) {
1639 /*int opt;
1640 int log_level = ILL_NONE;
1641 isds_log_facility log_facility = ILF_NONE;
1642 char *file = NULL;
1643 _Bool close_log = 0;*/
1646 optind = 0;
1647 while ((opt = getopt(argc, (char * const *)argv, "l:f:eo:")) != -1) {
1648 switch (opt) {
1649 case 'l':
1650 log_level = normalize_log_level(atoi(optarg));
1651 break;
1652 case 'f':
1653 if (add_log_facility(&log_facility, optarg)) return -1;
1654 break;
1655 case 'e':
1656 close_log = 1;
1657 case 'o':
1658 file = optarg;
1659 break;
1660 default:
1661 shi_debug_usage(argv[0]);
1662 return -1;
1665 if (optind == 1 || optind != argc) {
1666 printf(_("Bad invocation\n"));
1667 shi_debug_usage(argv[0]);
1668 return -1;
1672 oprintf(_("Current settings:\n"));
1674 show_setting_str(CONFIG_SERVER, 0);
1675 show_setting_str(CONFIG_USERNAME, 0);
1676 show_setting_str(CONFIG_PASSWORD, 1),
1677 show_setting_str(CONFIG_CERT_FORMAT, 0),
1678 show_setting_str(CONFIG_CERT_PATH, 0),
1679 show_setting_str(CONFIG_KEY_ENGINE, 0),
1680 show_setting_str(CONFIG_KEY_FORMAT, 0),
1681 show_setting_str(CONFIG_KEY_PATH, 0),
1682 show_setting_str(CONFIG_KEY_PASSWORD, 1),
1683 show_setting_str(CONFIG_OTP_METHOD, 0),
1684 show_setting_str(CONFIG_OTP_CODE, 1),
1685 show_setting_boolean(CONFIG_VERIFYSERVER);
1686 show_setting_str(CONFIG_CAFILE, 0);
1687 show_setting_str(CONFIG_CADIRECTORY, 0);
1688 show_setting_boolean(CONFIG_CLEAN_TEMPORARY_FILES);
1689 show_setting_str(CONFIG_CRLFILE, 0);
1690 show_setting_int(CONFIG_TIMEOUT);
1691 show_setting_strlist(CONFIG_LOGFACILITIES);
1692 show_setting_str(CONFIG_LOGFILE, 0);
1693 show_setting_int(CONFIG_LOGLEVEL);
1694 show_setting_boolean(CONFIG_MARKMESSAGEREAD);
1695 show_setting_boolean(CONFIG_NORMALIZEMIMETYPE);
1696 show_setting_strlist(CONFIG_OPENCOMMAND);
1697 show_setting_boolean(CONFIG_OVERWRITEFILES);
1699 return 0;
1703 static void shi_find_box_usage(const char *command) {
1704 oprintf(_(
1705 "Usage: %s {OPTION... | BOX_ID}\n"
1706 "Get information about box with BOX_ID or boxes meeting other criteria.\n"
1707 "Each search option requires an argument:\n"
1708 " -t box type; accepted values:\n"
1709 " FO Private individual\n"
1710 " PFO Self-employed individual\n"
1711 " PFO_ADVOK Lawyer\n"
1712 " PFO_DANPOR Tax advisor\n"
1713 " PFO_INSSPR Insolvency administrator\n"
1714 " PO Organisation\n"
1715 " PO_ZAK Organization based by law\n"
1716 " PO_REQ Organization based on request\n"
1717 " OVM Public authority\n"
1718 " OVM_NOTAR Notary\n"
1719 " OVM_EXEKUT Executor\n"
1720 " OVM_REQ Public authority based on request\n"
1721 " -j identity number\n"
1722 "\n"
1723 "Person name options:\n"
1724 " -f first name\n"
1725 " -m middle name\n"
1726 " -l last name\n"
1727 " -b last name at birth\n"
1728 " -s subject name\n"
1729 "\n"
1730 "Birth options:\n"
1731 " -d birth date (locale or full ISO 8601 date)\n"
1732 " -w birth city\n"
1733 " -y birth county\n"
1734 " -c birth state\n"
1735 "\n"
1736 "Address:\n"
1737 " -W city\n"
1738 " -S street\n"
1739 " -z number in street\n"
1740 " -Z number in municipality\n"
1741 " -P ZIP code\n"
1742 " -C state\n"
1743 "\n"
1744 "Other options:\n"
1745 " -n nationality\n"
1746 " -e e-mail\n"
1747 " -p phone number\n"
1748 " -i identifier\n"
1749 " -r registry code\n"
1750 " -a box status; accepted values:\n"
1751 " ACCESSIBLE Accessible\n"
1752 " TEMP_INACCESSIBLE Temporary inaccessible\n"
1753 " NOT_YET_ACCESSIBLE Not yet accessible\n"
1754 " PERM_INACCESSIBLE Permanently inaccessible\n"
1755 " REMOVED Deleted\n"
1756 " -o act as public authority; boolean values: 0 is false, 1 is true\n"
1757 " -k receive commercial messages; boolean values\n"
1758 "\n"
1759 "Not all option combinations are meaningful or allowed. For example box\n"
1760 "type is always required (except direct box ID query).\n"
1761 "ISDS can refuse to answer to much broad query. Not all boxes are searchable\n"
1762 "by every user.\n"
1764 command);
1768 /* Allow reassignment */
1769 #define FILL_OR_LEAVE(variable, locale) { \
1770 zfree(variable); \
1771 (variable) = locale2utf8(locale); \
1772 if (!(variable)) { \
1773 fprintf(stderr, _("Error: Not enough memory\n")); \
1774 retval = -1; \
1775 goto leave; \
1779 #define CALLOC_OR_LEAVE(structure) { \
1780 if (!(structure)) { \
1781 (structure) = calloc(1, sizeof(*(structure))); \
1782 if (!(structure)) { \
1783 fprintf(stderr, _("Error: Not enough memory\n")); \
1784 retval = -1; \
1785 goto leave; \
1790 #define FILL_BOOLEAN_OR_LEAVE(variable, locale) { \
1791 zfree(variable); \
1792 (variable) = malloc(sizeof(*(variable))); \
1793 if (!(variable)) { \
1794 fprintf(stderr, _("Error: Not enough memory\n")); \
1795 retval = -1; \
1796 goto leave; \
1798 if (!strcmp((locale), "0")) *(variable) = 0; \
1799 else if (!strcmp((locale), "1")) *(variable) = 1; \
1800 else { \
1801 fprintf(stderr, _("%s: %s: Unknown boolean value\n"), \
1802 argv[0], (locale)); \
1803 retval = -1; \
1804 goto leave; \
1808 #define FILL_LONGINT_OR_LEAVE(variable, locale) { \
1809 if (!(locale) || !*(locale)) { \
1810 fprintf(stderr, _("%s: Empty integer value\n"), argv[0]); \
1811 retval = -1; \
1812 goto leave; \
1814 char *endptr; \
1815 zfree(variable); \
1816 (variable) = malloc(sizeof(*(variable))); \
1817 if (!(variable)) { \
1818 fprintf(stderr, _("Error: Not enough memory\n")); \
1819 retval = -1; \
1820 goto leave; \
1822 (*variable) = strtol((locale), &endptr, 0); \
1823 if (*endptr) { \
1824 fprintf(stderr, _("%s: %s: Invalid integer value\n"), \
1825 argv[0], (locale)); \
1826 retval = -1; \
1827 goto leave; \
1831 static int shi_find_box(int argc, const char **argv) {
1832 int opt;
1833 isds_error err;
1834 struct isds_DbOwnerInfo *criteria = NULL;
1835 struct isds_list *item;
1836 int order = 0;
1837 int retval = 0;
1839 if (!argv || !argv[1] || !*argv[1]) {
1840 fprintf(stderr, _("Error: No argument supplied\n"));
1841 shi_find_box_usage((argv)?argv[0]:NULL);
1842 return -1;
1845 criteria = calloc(1, sizeof(*criteria));
1846 if (!criteria) {
1847 fprintf(stderr, _("Error: Not enough memory\n"));
1848 retval = -1;
1849 goto leave;
1852 /* Parse options */
1853 optind = 0;
1854 while ((opt = getopt(argc, (char * const *)argv, "t:j:s:"
1855 "f:m:l:b:s:" "d:w:y:c:" "W:S:z:Z:P:C:"
1856 "n:e:p:i:r:a:o:k:")) != -1) {
1857 switch (opt) {
1858 case 't':
1859 criteria->dbType = malloc(sizeof(*criteria->dbType));
1860 if (!criteria->dbType) {
1861 fprintf(stderr, _("Error: Not enough memory\n"));
1862 retval = -1;
1863 goto leave;
1865 if (!strcmp(optarg, "FO"))
1866 *criteria->dbType = DBTYPE_FO;
1867 else if (!strcmp(optarg, "PFO"))
1868 *criteria->dbType = DBTYPE_PFO;
1869 else if (!strcmp(optarg, "PFO_ADVOK"))
1870 *criteria->dbType = DBTYPE_PFO_ADVOK;
1871 else if (!strcmp(optarg, "PFO_DANPOR"))
1872 *criteria->dbType = DBTYPE_PFO_DANPOR;
1873 else if (!strcmp(optarg, "PFO_INSSPR"))
1874 *criteria->dbType = DBTYPE_PFO_INSSPR;
1875 else if (!strcmp(optarg, "PO"))
1876 *criteria->dbType = DBTYPE_PO;
1877 else if (!strcmp(optarg, "PO_ZAK"))
1878 *criteria->dbType = DBTYPE_PO_ZAK;
1879 else if (!strcmp(optarg, "PO_REQ"))
1880 *criteria->dbType = DBTYPE_PO_REQ;
1881 else if (!strcmp(optarg, "OVM"))
1882 *criteria->dbType = DBTYPE_OVM;
1883 else if (!strcmp(optarg, "OVM_NOTAR"))
1884 *criteria->dbType = DBTYPE_OVM_NOTAR;
1885 else if (!strcmp(optarg, "OVM_EXEKUT"))
1886 *criteria->dbType = DBTYPE_OVM_EXEKUT;
1887 else if (!strcmp(optarg, "OVM_REQ"))
1888 *criteria->dbType = DBTYPE_OVM_REQ;
1889 else {
1890 fprintf(stderr, _("%s: %s: Unknown box type\n"),
1891 argv[0], optarg);
1892 retval = -1;
1893 goto leave;
1895 break;
1897 case 'j':
1898 FILL_OR_LEAVE(criteria->ic, optarg);
1899 break;
1901 /* Person name */
1902 case 'f':
1903 CALLOC_OR_LEAVE(criteria->personName);
1904 FILL_OR_LEAVE(criteria->personName->pnFirstName, optarg);
1905 break;
1906 case 'm':
1907 CALLOC_OR_LEAVE(criteria->personName);
1908 FILL_OR_LEAVE(criteria->personName->pnMiddleName, optarg);
1909 break;
1910 case 'l':
1911 CALLOC_OR_LEAVE(criteria->personName);
1912 FILL_OR_LEAVE(criteria->personName->pnLastName, optarg);
1913 break;
1914 case 'b':
1915 CALLOC_OR_LEAVE(criteria->personName);
1916 FILL_OR_LEAVE(criteria->personName->pnLastNameAtBirth, optarg);
1917 break;
1918 case 's':
1919 FILL_OR_LEAVE(criteria->firmName, optarg);
1920 break;
1922 /* Birth */
1923 case 'd':
1924 CALLOC_OR_LEAVE(criteria->birthInfo);
1925 criteria->birthInfo->biDate = datestring2tm(optarg);
1926 if (!criteria->birthInfo->biDate) {
1927 fprintf(stderr, _("Error: Could not parse date: %s\n"),
1928 optarg);
1929 retval = -1;
1930 goto leave;
1932 break;
1933 case 'w':
1934 CALLOC_OR_LEAVE(criteria->birthInfo);
1935 FILL_OR_LEAVE(criteria->birthInfo->biCity, optarg);
1936 break;
1937 case 'y':
1938 CALLOC_OR_LEAVE(criteria->birthInfo);
1939 FILL_OR_LEAVE(criteria->birthInfo->biCounty, optarg);
1940 break;
1941 case 'c':
1942 CALLOC_OR_LEAVE(criteria->birthInfo);
1943 FILL_OR_LEAVE(criteria->birthInfo->biState, optarg);
1944 break;
1946 /* Address */
1947 case 'W':
1948 CALLOC_OR_LEAVE(criteria->address);
1949 FILL_OR_LEAVE(criteria->address->adCity, optarg);
1950 break;
1951 case 'S':
1952 CALLOC_OR_LEAVE(criteria->address);
1953 FILL_OR_LEAVE(criteria->address->adStreet, optarg);
1954 break;
1955 case 'z':
1956 CALLOC_OR_LEAVE(criteria->address);
1957 FILL_OR_LEAVE(criteria->address->adNumberInStreet, optarg);
1958 break;
1959 case 'Z':
1960 CALLOC_OR_LEAVE(criteria->address);
1961 FILL_OR_LEAVE(criteria->address->adNumberInMunicipality,
1962 optarg);
1963 break;
1964 case 'P':
1965 CALLOC_OR_LEAVE(criteria->address);
1966 FILL_OR_LEAVE(criteria->address->adZipCode, optarg);
1967 break;
1968 case 'C':
1969 CALLOC_OR_LEAVE(criteria->address);
1970 FILL_OR_LEAVE(criteria->address->adState, optarg);
1971 break;
1973 /* Other options */
1974 case 'n':
1975 FILL_OR_LEAVE(criteria->nationality, optarg);
1976 break;
1977 case 'e':
1978 FILL_OR_LEAVE(criteria->email, optarg);
1979 break;
1980 case 'p':
1981 FILL_OR_LEAVE(criteria->telNumber, optarg);
1982 break;
1983 case 'i':
1984 FILL_OR_LEAVE(criteria->identifier, optarg);
1985 break;
1986 case 'r':
1987 FILL_OR_LEAVE(criteria->registryCode, optarg);
1988 break;
1989 case 'a':
1990 criteria->dbState = malloc(sizeof(*criteria->dbState));
1991 if (!criteria->dbState) {
1992 fprintf(stderr, _("Error: Not enough memory\n"));
1993 retval = -1;
1994 goto leave;
1996 if (!strcmp(optarg, "ACCESSIBLE"))
1997 *criteria->dbState = DBSTATE_ACCESSIBLE;
1998 else if (!strcmp(optarg, "TEMP_INACCESSIBLE"))
1999 *criteria->dbState = DBSTATE_TEMP_UNACCESSIBLE;
2000 else if (!strcmp(optarg, "NOT_YET_ACCESSIBLE"))
2001 *criteria->dbState = DBSTATE_NOT_YET_ACCESSIBLE;
2002 else if (!strcmp(optarg, "PERM_INACCESSIBLE"))
2003 *criteria->dbState = DBSTATE_PERM_UNACCESSIBLE;
2004 else if (!strcmp(optarg, "REMOVED"))
2005 *criteria->dbState = DBSTATE_REMOVED;
2006 else {
2007 fprintf(stderr, _("%s: %s: Unknown box status\n"),
2008 argv[0], optarg);
2009 retval = -1;
2010 goto leave;
2012 break;
2013 case 'o':
2014 FILL_BOOLEAN_OR_LEAVE(criteria->dbEffectiveOVM, optarg);
2015 break;
2016 case 'k':
2017 FILL_BOOLEAN_OR_LEAVE(criteria->dbOpenAddressing, optarg);
2018 break;
2020 default:
2021 shi_find_box_usage(argv[0]);
2022 retval = -1;
2023 goto leave;
2027 /* There must be an option and all of them must be recognized, if not only
2028 * BOX_ID supplied */
2029 if (argc > 2 && optind != argc) {
2030 fprintf(stderr, _("Error: Superfluous argument\n"));
2031 shi_find_box_usage(argv[0]);
2032 retval = -1;
2033 goto leave;
2036 /* If only box ID is supplied use it */
2037 if (argc == 2 && argv[1] && *argv[1]) {
2038 criteria->dbID = locale2utf8(argv[1]);
2039 if (!criteria->dbID) {
2040 fprintf(stderr, _("Error: Not enough memory\n"));
2041 retval = -1;
2042 goto leave;
2046 printf(_("Searching boxes...\n"));
2047 err = isds_FindDataBox(cisds, criteria, &boxes);
2048 finish_isds_operation(cisds, err);
2049 if (err) return -1;
2051 for(item = boxes; item; item = item->next) {
2052 if (!item->data) continue;
2053 order++;
2055 oprintf(_("\n* Result #%d:\n"), order);
2056 format_DbOwnerInfo(item->data);
2059 leave:
2060 isds_DbOwnerInfo_free(&criteria);
2061 return retval;
2065 static void shi_stat_box_usage(const char *command) {
2066 oprintf(_(
2067 "Usage: %s BOX_ID...\n"
2068 "Get status of box with BOX_ID. More boxes can be specified.\n"),
2069 command);
2073 /* Get boxes status */
2074 static int shi_stat_box(int argc, const char **argv) {
2075 isds_error err;
2076 char *id = NULL;
2077 long int status;
2079 if (!argv || !*argv || argc < 2 || !argv[1]) {
2080 fprintf(stderr, _("Missing box ID\n"));
2081 shi_stat_box_usage((argv[0])?argv[0]:NULL);
2082 return -1;
2085 for (int i = 1; i < argc; i++) {
2086 if (!argv[i] || !*argv[i]) continue;
2088 free(id);
2089 id = locale2utf8(argv[i]);
2090 if (!id) {
2091 fprintf(stderr, _("%s: Could not covert box ID to UTF-8\n"),
2092 argv[i]);
2093 return -1;
2096 printf(_("Getting status of box `%s'...\n"), argv[i]);
2097 err = isds_CheckDataBox(cisds, id, &status);
2098 finish_isds_operation(cisds, err);
2099 if (err) return -1;
2101 oprintf(_("Status of box `%s': %s\n"),
2102 argv[i], DbState2string(&status));
2105 return 0;
2109 static void shi_boxlist_usage(const char *command) {
2110 oprintf(_(
2111 "Usage: %s LIST_TYPE FILE\n"
2112 "Save latest snapshot of list of boxes of type LIST_TYPE into FILE.\n"
2113 "\n"
2114 "Currently recognized LIST_TYPES are:\n"
2115 " ALL All boxes\n"
2116 " UPG Effectively OVM boxes\n"
2117 " OVM OVM gross type boxes\n"
2118 " OPN Boxes allowing receiving commercial messages\n"
2119 "\n"
2120 "Not all types are available to all users. E.g. only `UPG' is available\n"
2121 "to regular users.\n"
2122 "\n"
2123 "The format of the list is comma separate list that is packed into\n"
2124 "ZIP archive. Name of the list file denotes time of snapshoting\n"
2125 "the list. The snapshot is created by ISDS once a day.\n"),
2126 command);
2130 /* Download list of boxes */
2131 static int shi_boxlist(int argc, const char **argv) {
2132 isds_error err;
2133 const char *type_locale;
2134 char *type = NULL;
2135 void *buffer = NULL;
2136 size_t buffer_length;
2137 int retval;
2139 if (!argv || !*argv || argc < 3 || !argv[1] || !argv[2]) {
2140 fprintf(stderr, _("Bad number of arguments\n"));
2141 shi_boxlist_usage((argv)?argv[0]:NULL);
2142 return -1;
2144 type_locale = argv[1];
2146 type = locale2utf8(type_locale);
2147 if (!type) {
2148 fprintf(stderr, _("%s: Could not covert list type to UTF-8\n"),
2149 type_locale);
2150 return -1;
2153 printf(_("Getting `%s' list of boxes...\n"), type_locale);
2154 err = isds_get_box_list_archive(cisds, type, &buffer, &buffer_length);
2155 finish_isds_operation(cisds, err);
2156 free(type);
2157 if (err) {
2158 return -1;
2161 retval = save_data_to_file(argv[2], -1, buffer, buffer_length,
2162 "application/zip",
2163 cfg_getbool(configuration, CONFIG_OVERWRITEFILES));
2165 free(buffer);
2166 return retval;
2170 static void shi_delivery_usage(const char *command) {
2171 oprintf(_(
2172 "Usage: %s MESSAGE_ID\n"
2173 "Get delivery data about message with MESSAGE_ID.\n"),
2174 command);
2178 static int shi_delivery(int argc, const char **argv) {
2179 isds_error err;
2180 const char *id;
2182 if (!argv || !argv[1] || !*argv[1]) {
2183 shi_delivery_usage(argv[0]);
2184 return -1;
2186 id = argv[1];
2188 printf(_("Getting delivery info...\n"));
2189 err = isds_get_signed_delivery_info(cisds, id, &message);
2190 finish_isds_operation(cisds, err);
2191 if (err) {
2192 set_prompt(NULL);
2193 select_completition(COMPL_COMMAND);
2194 return -1;
2197 format_message(message);
2199 if (message->envelope && message->envelope->dmID)
2200 set_prompt(_("%s %s"), argv[0], message->envelope->dmID);
2201 else
2202 set_prompt("%s", argv[0]);
2203 select_completition(COMPL_MSG);
2204 return 0;
2208 static int shi_dump_message(int argc, const char **argv) {
2209 if (!message) {
2210 fprintf(stderr, _("No message loaded\n"));
2211 return -1;
2214 print_message(message);
2215 return 0;
2219 static void shi_hash_usage(const char *command) {
2220 oprintf(_(
2221 "Usage: %s [MESSAGE_ID]\n"
2222 "Retrieve message hash stored in ISDS.\n"
2223 "If MESSAGE_ID is defined, query for that message.\n"
2224 "Otherwise use current message.\n"),
2225 command);
2229 static int shi_hash(int argc, const char **argv) {
2230 isds_error err;
2231 const char *id = NULL;
2232 struct isds_hash *hash = NULL;
2233 char *hash_string = NULL;
2235 if (!argv || argc > 2) {
2236 shi_hash_usage((argv)?argv[0]:NULL);
2237 return -1;
2239 if (argc == 2 && argv[1] && *argv[1])
2240 id = argv[1];
2241 else {
2242 if (!message) {
2243 fprintf(stderr, _("No message loaded\n"));
2244 return -1;
2246 if (!message->envelope || !message->envelope->dmID) {
2247 fprintf(stderr, _("Current message is missing ID\n"));
2248 return -1;
2250 id = message->envelope->dmID;
2253 printf(_("Getting message hash...\n"));
2254 err = isds_download_message_hash(cisds, id, &hash);
2255 finish_isds_operation(cisds, err);
2256 if (err) return -1;
2258 hash_string = hash2string(hash);
2259 oprintf(_("ISDS states message with `%s' ID has following hash:\n%s\n"),
2260 id, hash_string);
2262 free(hash_string);
2263 isds_hash_free(&hash);
2264 return 0;
2268 static int shi_verify(int argc, const char **argv) {
2269 isds_error err;
2270 int retval = 0;
2271 struct isds_hash *retrieved_hash = NULL, *stored_hash = NULL;
2272 char *hash_string = NULL;
2273 size_t width = 4;
2275 if (!message) {
2276 fprintf(stderr, _("No message loaded\n"));
2277 return -1;
2280 if (!message->envelope) {
2281 fprintf(stderr, _("Current message is missing envelope\n"));
2282 return -1;
2284 stored_hash = message->envelope->hash;
2285 message->envelope->hash = NULL;
2287 if (message->envelope->dmID) {
2288 /* Verify remote hash */
2289 oprintf(_("Remote hash check:\n"));
2291 printf(_("Getting message hash...\n"));
2292 err = isds_download_message_hash(cisds, message->envelope->dmID,
2293 &retrieved_hash);
2294 finish_isds_operation(cisds, err);
2296 if (retrieved_hash) {
2297 hash_string = hash2string(retrieved_hash);
2298 ohprint(_("Retrieved:"), width);
2299 oprintf("%s\n", hash_string);
2300 zfree(hash_string);
2303 if (retrieved_hash && message->raw) {
2304 err = isds_compute_message_hash(cisds, message,
2305 retrieved_hash->algorithm);
2306 finish_isds_operation(cisds, err);
2308 if (!err) {
2309 hash_string = hash2string(message->envelope->hash);
2310 ohprint(_("Computed:"), width);
2311 oprintf("%s\n", hash_string);
2312 zfree(hash_string);
2316 err = isds_hash_cmp(retrieved_hash, message->envelope->hash);
2317 switch (err) {
2318 case IE_SUCCESS:
2319 oprintf(_("Hashes match.\n")); break;
2320 case IE_NOTUNIQ:
2321 oprintf(_("Hashes do not match.\n"));
2322 retval = -1;
2323 break;
2324 default:
2325 oprintf(_("Hashes could not be compared.\n"));
2326 retval = -1;
2327 break;
2330 free(retrieved_hash);
2334 if (stored_hash) {
2335 /* Verify stored hash */
2336 oprintf(_("Stored hash check:\n"));
2338 hash_string = hash2string(stored_hash);
2339 ohprint(_("Stored:"), width);
2340 oprintf("%s\n", hash_string);
2341 zfree(hash_string);
2343 if (message->raw) {
2344 err = isds_compute_message_hash(cisds, message,
2345 stored_hash->algorithm);
2346 finish_isds_operation(cisds, err);
2348 if (!err) {
2349 hash_string = hash2string(message->envelope->hash);
2350 ohprint(_("Computed:"), width);
2351 oprintf("%s\n", hash_string);
2352 zfree(hash_string);
2356 err = isds_hash_cmp(stored_hash, message->envelope->hash);
2357 switch (err) {
2358 case IE_SUCCESS:
2359 oprintf(_("Hashes match.\n")); break;
2360 case IE_NOTUNIQ:
2361 oprintf(_("Hashes do not match.\n"));
2362 retval = -1;
2363 break;
2364 default:
2365 oprintf(_("Hashes could not be compared.\n"));
2366 retval = -1;
2367 break;
2370 isds_hash_free(&message->envelope->hash);
2373 message->envelope->hash = stored_hash;
2374 return retval;
2378 static int shi_authenticate(int argc, const char **argv) {
2379 isds_error err;
2380 int retval = 0;
2382 if (!message) {
2383 fprintf(stderr, _("No message loaded\n"));
2384 return -1;
2386 if (!message->raw || message->raw_length == 0) {
2387 fprintf(stderr, _("Current message is missing raw representation\n"));
2388 return -1;
2391 printf(_("Submitting message to authenticity check...\n"));
2392 err = isds_authenticate_message(cisds, message->raw, message->raw_length);
2393 finish_isds_operation(cisds, (err == IE_NOTUNIQ) ? IE_SUCCESS : err);
2395 switch (err) {
2396 case IE_SUCCESS:
2397 oprintf(_("Message originates in ISDS.\n")); break;
2398 case IE_NOTUNIQ:
2399 oprintf(_("Message is unknown to ISDS or has been tampered.\n"));
2400 retval = -1;
2401 break;
2402 default:
2403 retval = -1;
2404 break;
2407 return retval;
2411 static void shi_accept_message_usage(const char *command) {
2412 oprintf(_(
2413 "Usage: %s [MESSAGE_ID...]\n"
2414 "Accept commercial message moving its state to received.\n"
2415 "If MESSAGE_ID is defined, accept that message. More messages can be specified.\n"
2416 "Otherwise accept all commercial incoming messages.\n"),
2417 command);
2421 static int shi_accept_message(int argc, const char **argv) {
2422 isds_error err;
2423 char *id = NULL;
2425 /* Process messages named in argv */
2426 for (int i = 1; i < argc; i++) {
2427 if (!argv[i] || !*argv[i]) continue;
2429 id = locale2utf8(argv[i]);
2430 if (!id) {
2431 fprintf(stderr,
2432 _("Error: Could not convert message ID to UTF-8: %s\n"),
2433 argv[i]);
2434 return -1;
2437 printf(_("Accepting message `%s'...\n"), argv[i]);
2438 err = isds_mark_message_received(cisds, id);
2439 finish_isds_operation(cisds, err);
2440 if (err) {
2441 free(id);
2442 return -1;
2445 oprintf(_("Message `%s' accepted\n"), argv[i]);
2446 free(id);
2449 if (argc < 2) {
2450 /* TODO: list commercial not received messages and accept all of them
2451 * */
2452 fprintf(stderr,
2453 _("Error: No message ID supplied. Accepting all commercial "
2454 "messages not implemented yet.\n"));
2455 return -1;
2458 return 0;
2462 static void shi_delete_message_usage(const char *command) {
2463 oprintf(_(
2464 "Usage: %s {-i|-o} MESSAGE_ID...\n"
2465 "Remove message from long term storage.\n"
2466 "Options:\n"
2467 " -i Messages are incoming\n"
2468 " -o Messages are outoging\n"),
2469 command);
2473 static int shi_delete_message(int argc, const char **argv) {
2474 isds_error err;
2475 char *id = NULL;
2476 _Bool incoming = 0;
2477 _Bool direction_specified = 0;
2478 int opt;
2480 optind = 0;
2481 while ((opt = getopt(argc, (char * const *)argv, "io")) != -1) {
2482 switch (opt) {
2483 case 'i':
2484 incoming = 1;
2485 direction_specified = 1;
2486 break;
2487 case 'o':
2488 incoming = 0;
2489 direction_specified = 1;
2490 break;
2491 default:
2492 shi_delete_message_usage((argv)?argv[0]:NULL);
2493 return -1;
2496 if (optind >= argc || !argv || !argv[optind] || !*argv[optind]) {
2497 fprintf(stderr, _("Bad invocation\n"));
2498 shi_delete_message_usage((argv)?argv[0]:NULL);
2499 return -1;
2501 if (!direction_specified) {
2502 fprintf(stderr, _("Message direction has not been specified\n"));
2503 shi_delete_message_usage((argv)?argv[0]:NULL);
2504 return -1;
2507 /* Process messages named in argv */
2508 for (int i = optind; i < argc; i++) {
2509 if (!argv[i] || !*argv[i]) continue;
2511 id = locale2utf8(argv[i]);
2512 if (!id) {
2513 fprintf(stderr,
2514 _("Error: Could not convert message ID to UTF-8: %s\n"),
2515 argv[i]);
2516 return -1;
2519 printf(_("Deleting message `%s'...\n"), argv[i]);
2520 err = isds_delete_message_from_storage(cisds, id, incoming);
2521 finish_isds_operation(cisds, err);
2522 if (err) {
2523 free(id);
2524 return -1;
2527 oprintf(_("Message `%s' deleted\n"), argv[i]);
2528 free(id);
2531 return 0;
2535 /* Convert message ID form locale to UTF-8 or in other direction. If both
2536 * strings are provided, UTF-8 will take precedence. The missing string is
2537 * automatically allocated (but not freed before). If UTF-8 version has been
2538 * provided, @stastic_utf8 will become 1, otherwise 0. You can pass the
2539 * strings and the flags to free_message_id() to free memory properly.*/
2540 static int convert_message_id(char **id_utf8, char **id_locale, _Bool *static_utf8) {
2541 if (!id_utf8 || !id_locale || !static_utf8) return -1;
2542 if (!*id_utf8 && !*id_locale) return -1;
2544 if (*id_utf8) {
2545 *static_utf8 = 1;
2546 *id_locale = utf82locale(*id_utf8);
2547 } else {
2548 *static_utf8 = 0;
2549 *id_utf8 = locale2utf8(*id_locale);
2550 if (!*id_utf8) {
2551 fprintf(stderr,
2552 _("Error: Could not convert message ID to UTF-8: %s\n"),
2553 *id_locale);
2554 return -1;
2558 return 0;
2562 /* Free message ID strings as were allocated by convert_message_id() */
2563 static int free_message_id(char **id_utf8, char **id_locale, _Bool static_utf8) {
2564 if (!id_utf8 || !id_locale) return -1;
2565 if (static_utf8) zfree(*id_locale);
2566 else zfree(*id_utf8);
2567 return 0;
2571 /* Return static UTF-8 encoded ID of current message. In case of error NULL. */
2572 static const char *get_current_message_id(void) {
2573 if (!message) {
2574 fprintf(stderr, _("No message loaded\n"));
2575 return NULL;
2577 if (!message->envelope) {
2578 fprintf(stderr, _("Loaded message is missing envelope\n"));
2579 return NULL;
2581 if (!message->envelope->dmID || !*message->envelope->dmID) {
2582 fprintf(stderr, _("Loaded message is missing ID\n"));
2583 return NULL;
2585 return message->envelope->dmID;
2589 static void shi_message_sender_usage(const char *command) {
2590 oprintf(_(
2591 "Usage: %s [MESSAGE_ID...]\n"
2592 "Get details about sender of a message.\n"
2593 "If MESSAGE_ID is defined, get sender of that message. More messages can be specified.\n"
2594 "Otherwise will get sender of current message, if any is loaded.\n"),
2595 command);
2599 /* Get details about sender of message with given ID. At least one form must
2600 * be specified.
2601 * @message_id is UTF-8 string
2602 * @message_id_locale is string in locale encoding
2603 * @return 0 on success, -1 on failure */
2604 static int do_message_sender(const char *message_id, const char *message_id_locale) {
2605 isds_sender_type *type = NULL;
2606 char *raw_type = NULL;
2607 char *name = NULL;
2608 isds_error err;
2609 _Bool static_id;
2611 if (convert_message_id((char **)&message_id, (char **)&message_id_locale,
2612 &static_id))
2613 return -1;
2615 printf(_("Getting sender of message `%s'...\n"), message_id_locale);
2616 err = isds_get_message_sender(cisds, message_id, &type, &raw_type, &name);
2617 finish_isds_operation(cisds, err);
2618 if (err) {
2619 free_message_id((char **)&message_id, (char **)&message_id_locale,
2620 static_id);
2621 return -1;
2624 format_sender_info(message_id, type, raw_type, name);
2626 free_message_id((char **)&message_id, (char **)&message_id_locale,
2627 static_id);
2628 zfree(type);
2629 zfree(raw_type);
2630 zfree(name);
2631 return 0;
2635 static int shi_message_sender(int argc, const char **argv) {
2636 if (argc < 2) {
2637 return do_message_sender(get_current_message_id(), NULL);
2640 for (int i = 1; i < argc; i++) {
2641 if (!argv[i] || !*argv[i]) continue;
2642 if (do_message_sender(NULL, argv[i]))
2643 return -1;
2646 return 0;
2650 /* Mark message as read. At least one form of ID must be provided.
2651 * @id is UTF-8 encoded message ID
2652 * @id_locale is locale encoded message ID. @id takes preference. */
2653 static int do_read_message(const char *id, const char *id_locale) {
2654 _Bool static_id;
2655 isds_error err;
2657 if ((!id || !*id) && (!id_locale || !*id_locale)) return -1;
2659 if (convert_message_id((char **)&id, (char **)&id_locale, &static_id)) return -1;
2661 printf(_("Marking message `%s' as read...\n"), id_locale);
2662 err = isds_mark_message_read(cisds, id);
2663 finish_isds_operation(cisds, err);
2665 if (!err)
2666 oprintf(_("Message `%s' marked as read\n"), id_locale);
2668 free_message_id((char **)&id, (char **)&id_locale, static_id);
2670 return (err) ? -1 : 0;
2674 static void shi_read_message_usage(const char *command) {
2675 oprintf(_(
2676 "Usage: %s [MESSAGE_ID...]\n"
2677 "Mark message as read moving its state to read.\n"
2678 "\n"
2679 "When new incoming message is download, its state is not changed on server.\n"
2680 "Client must mark such message as read explicitly. You can use this command\n"
2681 "to do so, if not done automatically at download time by your client.\n"
2682 "\n"
2683 "If MESSAGE_ID is defined, mark that message. More messages can be specified.\n"
2684 "Otherwise marks currently loaded message.\n"),
2685 command);
2689 static int shi_read_message(int argc, const char **argv) {
2690 if (argc < 2) {
2691 return do_read_message(get_current_message_id(), NULL);
2694 for (int i = 1; i < argc; i++) {
2695 if (!argv[i] || !*argv[i]) continue;
2696 if (do_read_message(NULL, argv[i]))
2697 return -1;
2700 return 0;
2704 static void shi_cat_message_usage(const char *command) {
2705 oprintf(_(
2706 "Usage: %s\n"
2707 "Print unformated raw representation of current message.\n"
2708 "\n"
2709 "This is the same content you would get into file by `save' command.\n"
2710 "\n"
2711 "Be ware the binary stream can screw your terminal. No new line character\n"
2712 "will be appended to the end of the output.\n"),
2713 command);
2717 static int shi_cat_message(int argc, const char **argv) {
2718 if (!message) {
2719 fprintf(stderr, _("No message loaded\n"));
2720 return -1;
2723 if (!message->raw || !message->raw_length) {
2724 fprintf(stderr, _("Current message is missing raw representation\n"));
2725 return -1;
2728 if (owrite(message->raw, message->raw_length) != message->raw_length) {
2729 fprintf(stderr, _("Error while printing message content\n"));
2730 return -1;
2733 return 0;
2737 static int shi_show_message(int argc, const char **argv) {
2738 if (!message) {
2739 fprintf(stderr, _("No message loaded\n"));
2740 return -1;
2743 format_message(message);
2744 return 0;
2748 static void shi_incoming_message_usage(const char *command) {
2749 oprintf(_(
2750 "Usage: %s [-r] MESSAGE_ID\n"
2751 "Get incoming message with MESSAGE_ID.\n"
2752 "Options:\n"
2753 " -r Mark mesage as read\n"),
2754 command);
2758 static int shi_incoming_message(int argc, const char **argv) {
2759 isds_error err;
2760 const char *id;
2761 int opt;
2762 _Bool mark_as_read = 0;
2764 optind = 0;
2765 while ((opt = getopt(argc, (char * const *)argv, "r")) != -1) {
2766 switch (opt) {
2767 case 'r':
2768 mark_as_read = 1;
2769 break;
2770 default:
2771 shi_incoming_message_usage((argv)?argv[0]:NULL);
2772 return -1;
2775 if (optind + 1 != argc || !argv || !argv[optind] || !*argv[optind]) {
2776 fprintf(stderr, _("Bad invocation\n"));
2777 shi_incoming_message_usage((argv)?argv[0]:NULL);
2778 return -1;
2780 id = argv[optind];
2782 printf(_("Getting incoming message...\n"));
2783 err = isds_get_signed_received_message(cisds, id, &message);
2784 finish_isds_operation(cisds, err);
2785 if (err) {
2786 set_prompt(NULL);
2787 select_completition(COMPL_COMMAND);
2788 return -1;
2791 format_message(message);
2793 if (message->envelope && message->envelope->dmID)
2794 set_prompt(_("%s %s"), argv[0], message->envelope->dmID);
2795 else
2796 set_prompt("%s", argv[0]);
2797 select_completition(COMPL_MSG);
2799 if (mark_as_read || cfg_getbool(configuration, CONFIG_MARKMESSAGEREAD)) {
2800 if (message->envelope && message->envelope->dmMessageStatus &&
2801 ! (*message->envelope->dmMessageStatus & MESSAGESTATE_READ))
2802 return do_read_message(id, NULL);
2804 return 0;
2808 static void shi_outgoing_message_usage(const char *command) {
2809 oprintf(_(
2810 "Usage: %s MESSAGE_ID\n"
2811 "Get outgoing message with MESSAGE_ID.\n"),
2812 command);
2816 static int shi_outgoing_message(int argc, const char **argv) {
2817 isds_error err;
2818 const char *id;
2820 if (!argv || !argv[1] || !*argv[1]) {
2821 shi_outgoing_message_usage(argv[0]);
2822 return -1;
2824 id = argv[1];
2826 printf(_("Getting outgoing message...\n"));
2827 err = isds_get_signed_sent_message(cisds, id, &message);
2828 finish_isds_operation(cisds, err);
2829 if (err) {
2830 set_prompt(NULL);
2831 select_completition(COMPL_COMMAND);
2832 return -1;
2835 format_message(message);
2836 if (message->envelope && message->envelope->dmID)
2837 set_prompt(_("%s %s"), argv[0], message->envelope->dmID);
2838 else
2839 set_prompt("%s", argv[0]);
2840 select_completition(COMPL_MSG);
2841 return 0;
2845 /* Detect type (message or delivery data) and load it. And change completion
2846 * and show the data.
2847 * @buffer is memory with message or delivery data
2848 * @length is size of @buffer in bytes
2849 * @strategy defines how to fill global message variable
2850 * @return 0 for success, otherwise non-zero. */
2851 static int do_load_anything(const void *buffer, size_t length,
2852 isds_buffer_strategy strategy) {
2853 isds_raw_type raw_type;
2854 isds_error err;
2855 char *type_name = NULL;
2857 if (NULL == buffer || 0 == length) {
2858 return -1;
2861 printf(_("Detecting format...\n"));
2862 err = isds_guess_raw_type(cisds, &raw_type, buffer, length);
2863 finish_isds_operation(cisds, err);
2865 if (err) {
2866 if (err == IE_NOTSUP)
2867 fprintf(stderr, _("Unknown format.\n"));
2868 else
2869 fprintf(stderr, _("Error while detecting format.\n"));
2870 } else {
2871 switch (raw_type) {
2872 case RAWTYPE_INCOMING_MESSAGE:
2873 case RAWTYPE_PLAIN_SIGNED_INCOMING_MESSAGE:
2874 case RAWTYPE_CMS_SIGNED_INCOMING_MESSAGE:
2875 case RAWTYPE_PLAIN_SIGNED_OUTGOING_MESSAGE:
2876 case RAWTYPE_CMS_SIGNED_OUTGOING_MESSAGE:
2877 err = isds_load_message(cisds, raw_type,
2878 buffer, length, &message, strategy);
2879 finish_isds_operation(cisds, err);
2880 type_name = N_("message");
2881 break;
2883 case RAWTYPE_DELIVERYINFO:
2884 case RAWTYPE_PLAIN_SIGNED_DELIVERYINFO:
2885 case RAWTYPE_CMS_SIGNED_DELIVERYINFO:
2886 err = isds_load_delivery_info(cisds, raw_type,
2887 buffer, length, &message, strategy);
2888 finish_isds_operation(cisds, err);
2889 type_name = N_("delivery");
2890 break;
2892 default:
2893 fprintf(stderr,
2894 _("Unsupported format.\n"));
2895 err = IE_NOTSUP;
2899 if (err) {
2900 set_prompt(NULL);
2901 select_completition(COMPL_COMMAND);
2902 return -1;
2905 format_message(message);
2907 if (message->envelope && message->envelope->dmID)
2908 set_prompt(_("%s %s"), _(type_name), message->envelope->dmID);
2909 else
2910 set_prompt("%s", _(type_name));
2911 select_completition(COMPL_MSG);
2912 return 0;
2916 static void shi_load_anything_usage(const char *command) {
2917 oprintf(_(
2918 "Usage: %s FILE\n"
2919 "Load message or message delivery details from local FILE.\n"),
2920 command);
2924 static int shi_load_anything(int argc, const char **argv) {
2925 int fd;
2926 void *buffer = NULL;
2927 size_t length;
2928 int error;
2930 if (!argv || !argv[1] || !*argv[1]) {
2931 shi_load_anything_usage((argv)?argv[0]:NULL);
2932 return -1;
2935 printf(_("Loading file `%s'...\n"), argv[1]);
2937 if (mmap_file(argv[1], &fd, &buffer, &length)) return -1;
2939 error = do_load_anything(buffer, length, BUFFER_COPY);
2941 munmap_file(fd, buffer, length);
2943 return error;
2947 static void shi_save_message_usage(const char *command) {
2948 oprintf(_(
2949 "Usage: %s FILE\n"
2950 "Save message into local FILE.\n"),
2951 command);
2955 static const char *raw_type2mime(isds_raw_type raw_type) {
2956 switch (raw_type) {
2957 case RAWTYPE_INCOMING_MESSAGE:
2958 case RAWTYPE_PLAIN_SIGNED_INCOMING_MESSAGE:
2959 case RAWTYPE_PLAIN_SIGNED_OUTGOING_MESSAGE:
2960 case RAWTYPE_DELIVERYINFO:
2961 case RAWTYPE_PLAIN_SIGNED_DELIVERYINFO:
2962 return "text/xml";
2964 case RAWTYPE_CMS_SIGNED_INCOMING_MESSAGE:
2965 case RAWTYPE_CMS_SIGNED_OUTGOING_MESSAGE:
2966 case RAWTYPE_CMS_SIGNED_DELIVERYINFO:
2967 return "application/pkcs7-mime";
2969 default:
2970 return NULL;
2975 static int shi_save_message(int argc, const char **argv) {
2976 _Bool overwrite = cfg_getbool(configuration, CONFIG_OVERWRITEFILES);
2978 if (!argv || !argv[1] || !*argv[1]) {
2979 shi_save_message_usage(argv[0]);
2980 return -1;
2983 if (!message) {
2984 fprintf(stderr, _("No message loaded\n"));
2985 return -1;
2987 if (!message->raw || message->raw_length == 0) {
2988 fprintf(stderr, _("Loaded message is missing raw representation\n"));
2989 return -1;
2992 return save_data_to_file(argv[1], -1, message->raw, message->raw_length,
2993 raw_type2mime(message->raw_type), overwrite);
2997 /* Return document of current message identified by ordinal number expressed
2998 * as string. In case of error return NULL. */
2999 static const struct isds_document *locate_document_by_ordinal_string(
3000 const char *number) {
3001 const struct isds_list *item;
3002 const struct isds_document *document = NULL;
3003 int ordinar, i;
3005 if (!number) return NULL;
3007 ordinar = atoi(number);
3008 if (ordinar <= 0) {
3009 fprintf(stderr, _("%s: Document number must be positive number\n"),
3010 number);
3011 return NULL;
3014 if (!message) {
3015 fprintf(stderr, _("No message loaded\n"));
3016 return NULL;
3019 /* Find document */
3020 for (item = message->documents, i = 0; item; item = item->next) {
3021 if (!item->data) continue;
3022 if (++i == ordinar) {
3023 document = (const struct isds_document *) item->data;
3024 break;
3027 if (i != ordinar) {
3028 fprintf(stderr, _("Message does not contain document #%d\n"), ordinar);
3029 return NULL;
3032 return document;
3036 static void shi_cat_document_usage(const char *command) {
3037 oprintf(_(
3038 "Usage: %s NUMBER\n"
3039 "Print document selected with ordinal NUMBER.\n"),
3040 command);
3043 static int shi_cat_document(int argc, const char **argv) {
3044 const struct isds_document *document;
3046 if (!argv || !argv[1] || !*argv[1] || argc > 3) {
3047 shi_cat_document_usage(argv[0]);
3048 return -1;
3051 document = locate_document_by_ordinal_string(argv[1]);
3052 if (!document) return -1;
3054 if (document->is_xml) {
3055 xmlBufferPtr buffer = NULL;
3056 size_t written;
3058 if (serialize_xml_to_buffer(&buffer, document->xml_node_list))
3059 return -1;
3061 written = owrite(buffer->content, buffer->use);
3062 xmlBufferFree(buffer);
3063 if (written != buffer->use) {
3064 fprintf(stderr, _("Error while printing document content\n"));
3065 return -1;
3067 } else {
3068 if (!document->data || !document->data_length) {
3069 fprintf(stderr, _("Document is missing raw representation\n"));
3070 return -1;
3073 if (owrite(document->data, document->data_length) != document->data_length) {
3074 fprintf(stderr, _("Error while printing document content\n"));
3075 return -1;
3079 return 0;
3083 static void shi_save_document_usage(const char *command) {
3084 oprintf(_(
3085 "Usage: %s NUMBER [DESTINATION]\n"
3086 "Save document having ordinal NUMBER within current message into local file.\n"
3087 "If DESTINATION is file (or does not exist yet), document will be saved into\n"
3088 "this file.\n"
3089 "If DESTINATION is existing directory, file name equaled to document name\n"
3090 "will be saved into DESTINATION.\n"
3091 "If DESTINATION is missing, document name will be used as file name and\n"
3092 "saved into working directory.\n"
3093 "Be aware that document name does not embed malicious characters (slashes).\n"
3094 "\n"
3095 "If the document is a binary stream, image of the document will be copied\n"
3096 "into a file. If the document is a XML document, the XML tree will be serialized\n"
3097 "into a file. If XML document stands for one element or one text node, the node\n"
3098 "(and its children recursively) will be serialized. If XML document compounds\n"
3099 "more nodes or a comment or a processing instruction, parent node from ISDS name\n"
3100 "space will be used to ensure output serialized XML well-formness.\n"
3102 command);
3106 static int shi_save_document(int argc, const char **argv) {
3107 const struct isds_document *document;
3108 const char *dirname = NULL;
3109 char *filename = NULL, *path = NULL;
3110 int retval = 0;
3111 _Bool overwrite = cfg_getbool(configuration, CONFIG_OVERWRITEFILES);
3113 if (!argv || !argv[1] || !*argv[1] || argc > 3) {
3114 shi_save_document_usage(argv[0]);
3115 return -1;
3118 document = locate_document_by_ordinal_string(argv[1]);
3119 if (!document) return -1;
3121 /* Select directory and file name */
3122 if (argv[2] && *argv[2]) {
3123 if (!is_directory(argv[2])) {
3124 dirname = argv[2];
3125 } else {
3126 filename = strdup(argv[2]);
3127 if (!filename) {
3128 fprintf(stderr, _("Not enough memory\n"));
3129 return -1;
3133 if (!filename && document->dmFileDescr && &document->dmFileDescr) {
3134 filename = utf82locale(document->dmFileDescr);
3135 if (!filename) {
3136 fprintf(stderr, _("Not enough memory\n"));
3137 return -1;
3140 if (!filename) {
3141 fprintf(stderr,
3142 _("File name neither supplied, nor document name exists\n"
3143 "Please, supply one.\n"));
3144 return -1;
3147 /* Build path */
3148 if (dirname) {
3149 path = astrcat3(dirname, "/", filename);
3150 zfree(filename);
3151 } else {
3152 path = filename;
3153 filename = NULL;
3155 if (!path) {
3156 fprintf(stderr, _("Not enough memory\n"));
3157 return -1;
3160 /* Save document */
3161 if (document->is_xml)
3162 retval = save_xml_to_file(path, -1, document->xml_node_list,
3163 document->dmMimeType, overwrite);
3164 else
3165 retval = save_data_to_file(path, -1, document->data,
3166 document->data_length, document->dmMimeType, overwrite);
3167 free(path);
3168 return retval;
3172 /* Execute program specified as NULL terminated array of arguments. argv[0] is
3173 * subject of PATH search variable look-up. The program is executed directly,
3174 * it's not a shell command. */
3175 static int execute_system_command(char *const argv[]) {
3176 pid_t pid;
3178 if (!argv || !argv[0]) return -1;
3180 pid = fork();
3181 if (pid == -1) {
3182 /* Could not fork */
3183 fprintf(stderr, _("Could not fork\n"));
3184 return -1;
3185 } else if (pid == 0) {
3186 /* Child */
3187 execvp(argv[0], argv);
3188 fprintf(stderr, _("Could not execute:"));
3189 for (char *const *arg = argv; *arg; arg++)
3190 fprintf(stderr, " %s", *arg);
3191 fprintf(stderr, _(": %s\n"), strerror(errno));
3192 exit(EXIT_FAILURE);
3193 } else {
3194 /* Wait for the command */
3195 int retval;
3197 if (-1 == waitpid(pid, &retval, 0)) {
3198 fprintf(stderr, _("Could not wait for executed command\n"));
3199 return -1;
3202 if (retval == -1)
3203 fprintf(stderr, _("Exit code of command could not "
3204 "be determined\n"));
3205 else if (WIFEXITED(retval) && WEXITSTATUS(retval))
3206 printf(_("Command exited with code %d\n"),
3207 WEXITSTATUS(retval));
3208 else if (WIFSIGNALED(retval))
3209 printf(_("Command terminated by signal "
3210 "#%d\n"), WTERMSIG(retval));
3211 return retval;
3216 /* Run editor to create new text document */
3217 static int edit_new_textual_document(struct isds_document *document) {
3218 char filename[14] = "shiXXXXXX.txt";
3219 int fd;
3220 char *command[] = { getenv("VISUAL"), filename, NULL };
3221 int retval = 0;
3222 struct stat file_before, file_after;
3224 if (batch_mode) {
3225 fprintf(stderr, _("Editing is forbidden in batch mode.\n"));
3226 return -1;
3229 if (NULL == document) return -1;
3230 if (NULL == command[0]) command[0] = getenv("EDITOR");
3231 if (NULL == command[0]) {
3232 fprintf(stderr,
3233 _("Neither environment variable VISUAL nor EDITOR are set.\n"));
3234 return -1;
3237 /* Create temporary file for the document */
3238 fd = create_new_file(filename, 4);
3239 if (fd == -1) {
3240 return -1;
3242 if (fstat(fd, &file_before)) {
3243 fprintf(stderr,
3244 _("Could not retrieve modification time for `%s': %s\n"),
3245 filename, strerror(errno));
3246 retval = -1;
3247 goto leave;
3250 /* Open the file with $EDITOR */
3251 if ((retval = execute_system_command(command))) {
3252 fprintf(stderr, _("Editor failed.\n"));
3253 retval = -1;
3254 goto leave;
3257 /* Compare modification times */
3258 /* XXX: fstat(2) does return updated st_mtime. Bug in Linux 3.7.1? */
3259 if (stat(filename, &file_after)) {
3260 fprintf(stderr,
3261 _("Could not retrieve modification time for `%s': %s\n"),
3262 filename, strerror(errno));
3263 retval = -1;
3264 goto leave;
3266 if (file_before.st_mtime == file_after.st_mtime) {
3267 fprintf(stderr, _("Edited document has not been changed.\n"));
3268 retval = -1;
3269 goto leave;
3272 /* Load document */
3273 if (load_data_from_file(filename, &document->data,
3274 &document->data_length, NULL)) {
3275 retval = -1;
3276 goto leave;
3279 /* Set meta-data */
3280 if (NULL == document->dmMimeType)
3281 FILL_OR_LEAVE(document->dmMimeType, "text/plain");
3282 /* XXX: POSIX basename() modifies argument */
3283 if (NULL == document->dmFileDescr)
3284 FILL_OR_LEAVE(document->dmFileDescr, basename(filename));
3286 leave:
3287 /* Remove the file */
3288 unlink_file(filename);
3289 close(fd);
3290 return retval;
3294 /* Append @suffix into @buffer with @size bytes prealocated at position @at.
3295 * Trailing '\0' of @suffix is not carried.
3296 * @buffer can be reallocated if @size is not suffient to fill @suffix
3297 * @size is original @buffer size, can change if @buffer would be reallocated
3298 * @at position where append @suffic to. Outputs new end after appending
3299 * @suffix is NULL rerminated string to append
3300 * @return 0 if success, -1 otherwise. Caller is resposible for freeing
3301 * @buffer.*/
3302 static int append_string_at(char **buffer, size_t *size, char **at,
3303 const char *suffix) {
3305 if (!buffer || !*buffer || !size || !at || !*at) return -1;
3306 if (!suffix) return 0;
3308 while (*suffix) {
3309 if (*at - *buffer + 1 >= *size) {
3310 /* End of buffer, grow it */
3311 if (*size < 8) *size = 8;
3312 *size *= 2;
3313 char *new_buffer = realloc(*buffer, *size);
3314 if (!new_buffer) return -1;
3315 *at = *at - *buffer + new_buffer;
3316 *buffer = new_buffer;
3319 /* Copy a character */
3320 *((*at)++) = *(suffix++);
3323 return 0;
3327 static char *expand_command_arg(const char *format, const char *file,
3328 const char *type) {
3329 char *buffer = NULL;
3330 size_t size = 0;
3331 const char *format_cursor;
3332 char *buffer_cursor;
3334 if (!format) return NULL;
3336 for (format_cursor = format, buffer_cursor = buffer; ; format_cursor++) {
3337 if (buffer_cursor - buffer + 1 >= size) {
3338 /* End of buffer, grow it */
3339 if (size < 8) size = 8;
3340 size *= 2;
3341 char *new_buffer = realloc(buffer, size);
3342 if (!new_buffer) goto error;
3343 buffer_cursor = buffer_cursor - buffer + new_buffer;
3344 buffer = new_buffer;
3347 if (*format_cursor == '%') {
3348 /* Escape */
3349 switch (*(format_cursor+1)) {
3350 case 'f':
3351 if (!file) {
3352 fprintf(stderr, _("Could not expand `%%f' because "
3353 "file name did not exist.\n"));
3354 free(buffer);
3355 return NULL;
3357 if (append_string_at(&buffer, &size, &buffer_cursor, file))
3358 goto error;
3360 format_cursor++;
3361 continue;
3362 case 't':
3363 if (!file) {
3364 fprintf(stderr, _("Could not expand `%%t' because "
3365 "file type did not exist.\n"));
3366 free(buffer);
3367 return NULL;
3369 if (append_string_at(&buffer, &size, &buffer_cursor, type))
3370 goto error;
3372 format_cursor++;
3373 continue;
3374 case '%':
3375 format_cursor++;
3379 /* Copy plain character */
3380 *(buffer_cursor++) = *format_cursor;
3382 if (!*format_cursor) break;
3385 return buffer;
3387 error:
3388 fprintf(stderr, _("Error: Not enough memory\n"));
3389 free(buffer);
3390 return NULL;
3394 /* Expand open_command configuration option by substiting %f and %t with file
3395 * name and file type.
3396 * @file is locale encoded file name
3397 * @type is UTF-8 encoded MIME type
3398 * @return heap allocated arrary of arguments or NULL if error occurs.
3399 * Arguments are coded in locale. */
3400 static char **expand_open_command(const char *file, const char *type) {
3401 char **command = NULL;
3402 int length;
3403 char *type_locale = NULL;
3405 length = cfg_size(configuration, CONFIG_OPENCOMMAND);
3406 if (length <= 0) {
3407 fprintf(stderr, _("%s not set\n"), CONFIG_OPENCOMMAND);
3408 return NULL;
3411 command = malloc((length + 1) * sizeof(*command));
3412 if (!command) {
3413 fprintf(stderr, _("Error: Not enough memory\n"));
3414 return NULL;
3417 if (type) {
3418 type_locale = utf82locale(type);
3419 if (!type_locale) {
3420 printf(_("Could not convert document MIME type to locale "
3421 "encoding\n"));
3422 free(command);
3423 return NULL;
3427 for (int i = 0; i < length; i++) {
3428 const char *value = cfg_getnstr(configuration, CONFIG_OPENCOMMAND, i);
3429 command[i] = expand_command_arg(value, file, type_locale);
3430 if (!command[i]) {
3431 fprintf(stderr, _("Error: Not enough memory\n"));
3432 for (int j = 0; j < i; j++) free(command[j]);
3433 free(command);
3434 free(type_locale);
3435 return NULL;
3438 command[length] = NULL;
3440 free(type_locale);
3441 return command;
3445 /* Register temporary @file for removal at shigofumi exit.
3446 * This is done by destructor call-back in isds_list_free(). */
3447 static int register_temporary_file(const char *file) {
3448 struct isds_list *temporary_file = NULL;
3450 if (!file) return 0;
3452 temporary_file = malloc(sizeof(*temporary_file));
3453 if (!temporary_file) {
3454 fprintf(stderr, _("Error: Not enough memory\n"));
3455 return -1;
3458 temporary_file->data = (void *)strdup(file);
3459 if (!temporary_file) {
3460 fprintf(stderr, _("Error: Not enough memory\n"));
3461 free(temporary_file);
3462 return -1;
3465 temporary_file->destructor = shi_unlink_temporary_file;
3467 if (temporary_files)
3468 temporary_file->next = temporary_files;
3469 else
3470 temporary_file->next = NULL;
3471 temporary_files = temporary_file;
3473 return 0;
3477 static void shi_open_document_usage(const char *command) {
3478 oprintf(_(
3479 "Usage: %s NUMBER\n"
3480 "Save document having ordinal NUMBER within current message into temporal\n"
3481 "local file, open the file by xdg-open utility and then remove the file.\n"
3483 command);
3487 static int shi_open_document(int argc, const char **argv) {
3488 const struct isds_document *document;
3489 char filename[10] = "shiXXXXXX";
3490 int fd;
3491 char **command = NULL;
3492 int retval = 0;
3494 if (!argv || !argv[1] || !*argv[1] || argc > 3) {
3495 shi_open_document_usage(argv[0]);
3496 return -1;
3499 document = locate_document_by_ordinal_string(argv[1]);
3500 if (!document) return -1;
3502 /* Create temporary file for the document */
3503 fd = create_new_file(filename, 0);
3504 if (fd == -1) {
3505 return -1;
3508 /* Save document */
3509 if (document->is_xml)
3510 retval = save_xml_to_file(filename, fd, document->xml_node_list,
3511 document->dmMimeType, 0);
3512 else
3513 retval = save_data_to_file(filename, fd, document->data,
3514 document->data_length, document->dmMimeType, 0);
3516 /* Open the file with external utility */
3517 if (!retval) {
3518 /* Construct command arguments to execute */
3519 command = expand_open_command(filename, document->dmMimeType);
3521 if (!command)
3522 retval = -1;
3523 else {
3524 /* XXX: Do not use system(3) as we cannot escape uknown shell */
3525 retval = execute_system_command(command);
3526 for (char **arg = command; *arg; arg++) free(*arg);
3527 free(command);
3531 /* Remove the file */
3532 /* XXX: We do not know when external program opens the file. We cannot
3533 * remove it immediately. Register the filename and remove all temporary
3534 * files at exit of shigofumi if requested by configuration. */
3535 if (cfg_getbool(configuration, CONFIG_CLEAN_TEMPORARY_FILES)) {
3536 if (register_temporary_file(filename))
3537 fprintf(stderr, _("Warning: Temporary file `%s' could not been "
3538 "registered for later removal. Remove the file by "
3539 "hand, please.\n"), filename);
3541 return retval;
3545 static void shi_compose_usage(const char *command) {
3546 oprintf(_(
3547 "Usage: %s OPTION...\n"
3548 "Compose and send a message to recipient defined by his box ID.\n"
3549 "Each option requires an argument (if not stated otherwise):\n"
3550 " -s * message subject\n"
3551 "\n"
3552 "Recipient options:\n"
3553 " -b * recipient box ID\n"
3554 " -U organisation unit name\n"
3555 " -N organisation unit number\n"
3556 " -P to hands of given person\n"
3557 "\n"
3558 "Sender organisation structure options:\n"
3559 " -I publish user's identity (NO argument allowed)\n"
3560 " -u unit name\n"
3561 " -n unit number\n"
3562 "\n"
3563 "Message identifier options:\n"
3564 " -r sender reference number\n"
3565 " -f sender file ID\n"
3566 " -R recipient reference number\n"
3567 " -F recipient file ID\n"
3568 "\n"
3569 "Legal title options:\n"
3570 " -y year act has been issued\n"
3571 " -a ordinal number of act in a year\n"
3572 " -e section of the act\n"
3573 " -o paragraph of the act\n"
3574 " -i point of the paragraph of the act\n"
3575 "\n"
3576 "Delivery options:\n"
3577 " -p personal delivery required\n"
3578 " -t allow substitutable delivery\n"
3579 " -A non-OVM sender acts as public authority\n"
3580 " -C commercial type; accepted values:\n"
3581 " K Commercial message paid by sender or sponsor\n"
3582 " I Initiatory commercial message offering to pay response\n"
3583 " O Commercial response paid by recipient\n"
3584 " V Public message paid by government\n"
3585 " Missing option defaults to K or O (see `commercialsending' command)\n"
3586 " if sending to non-OVM recipient with enabled commercial receiving,\n"
3587 " otherwise it defaults to V.\n"
3588 "\n"
3589 "Document options:\n"
3590 " -d * read document from local file. If `-' is specified,\n"
3591 " run text editor.\n"
3592 " -D document name (defaults to base local file name)\n"
3593 " -x transport subset of the document as a XML.\n"
3594 " Argument is XPath expression specifying desired node set\n"
3595 " -m override MIME type (guessed on -d)\n"
3596 " -g document ID (must be unique per message)\n"
3597 " -G reference to other document using its ID\n"
3598 " -c document is digital signature of other document (NO argument\n"
3599 " allowed)\n"
3600 "\n"
3601 "Options marked with asterisk are mandatory, other are optional. Another soft\n"
3602 "dependencies can emerge upon using specific option. They are not mandated by\n"
3603 "ISDS currently, but client library or this program can force them to assure\n"
3604 "semantically complete message. Following soft dependencies are recommended:\n"
3605 " -y <=> -a act number and year must be used at the same time\n"
3606 " -i => -o act point requires act paragraph\n"
3607 " -o => -e act paragraph requires act section\n"
3608 " -e => -a act section requires act number\n"
3609 " -G => -g document with referenced ID must exist\n"
3610 " -c => -G signature must refer to signed document\n"
3611 " -c first document cannot be signature\n"
3612 " -C I => -r sender reference number allows responder to reply to this message\n"
3613 " -C O -> -R recipient reference number must match sender reference number of\n"
3614 " initiatory message\n"
3615 "\n"
3616 "More documents can be attached to a message by repeating `-d' option.\n"
3617 "Document order will be preserved. Other document options affect immediately\n"
3618 "preceding `-d' document only. E.g. `-d /tmp/foo.pdf -m application/pdf\n"
3619 "-d /tmp/bar.txt -m text/plain' attaches first PDF file, then textual file.\n"
3620 "\n"
3621 "The same applies to recipient options that must start with box ID (-b).\n"
3622 "If more recipients specified, each of them will get a copy of composed\n"
3623 "message. ISDS will assign message identifier to each copy in turn.\n"
3625 command);
3629 static int shi_compose(int argc, const char **argv) {
3630 int opt;
3631 isds_error err;
3632 int retval = 0;
3633 unsigned int i;
3634 struct isds_message *message = NULL;
3635 struct isds_envelope *envelope = NULL;
3636 struct isds_list *documents = NULL;
3637 struct isds_document *document = NULL;
3638 struct isds_list *copies = NULL, *copy_item = NULL;
3639 struct isds_message_copy *copy = NULL;
3640 char *message_id_locale = NULL, *recipient_id_locale = NULL,
3641 *dmStatus_locale = NULL;
3643 if (!argv || !argv[1] || !*argv[1]) {
3644 fprintf(stderr, _("Error: No argument supplied\n"));
3645 shi_compose_usage((argv)?argv[0]:NULL);
3646 return -1;
3649 message = calloc(1, sizeof(*message));
3650 if (!message) {
3651 fprintf(stderr, _("Error: Not enough memory\n"));
3652 retval = -1;
3653 goto leave;
3655 envelope = calloc(1, sizeof(*envelope));
3656 if (!envelope) {
3657 fprintf(stderr, _("Error: Not enough memory\n"));
3658 retval = -1;
3659 goto leave;
3661 message->envelope = envelope;
3663 /* Parse options */
3664 optind = 0;
3665 while ((opt = getopt(argc, (char * const *)argv, "s:" "b:U:N:P:" "Iu:n:"
3666 "r:f:R:F:" "y:a:e:o:i:" "p:t:A:C:" "d:D:x:m:g:G:c"
3667 )) != -1) {
3668 switch (opt) {
3669 case 's':
3670 FILL_OR_LEAVE(envelope->dmAnnotation, optarg);
3671 break;
3673 /* Recipient options */
3674 case 'b':
3675 copy = NULL;
3676 if (!copies) {
3677 /* First recipient */
3678 CALLOC_OR_LEAVE(copies);
3679 copies->destructor =
3680 (void(*)(void **)) isds_message_copy_free;
3681 copy_item = copies;
3682 } else {
3683 /* Next recipient */
3684 CALLOC_OR_LEAVE(copy_item->next);
3685 copy_item->next->destructor =
3686 (void(*)(void **)) isds_message_copy_free;
3687 copy_item = copy_item->next;
3689 CALLOC_OR_LEAVE(copy);
3690 copy_item->data = copy;
3692 /* Copy recipient box ID */
3693 FILL_OR_LEAVE(copy->dbIDRecipient, optarg);
3694 break;
3695 case 'U':
3696 if (!copy) {
3697 fprintf(stderr,
3698 _("Error: %s: Recipient box ID (-b) must precede "
3699 "recipient organisation unit name (-%c)\n"),
3700 optarg, opt);
3701 retval = -1;
3702 goto leave;
3704 FILL_OR_LEAVE(copy->dmRecipientOrgUnit, optarg);
3705 break;
3706 case 'N':
3707 if (!copy) {
3708 fprintf(stderr,
3709 _("Error: %s: Recipient box ID (-b) must precede "
3710 "recipient organisation unit number (-%c)\n"),
3711 optarg, opt);
3712 retval = -1;
3713 goto leave;
3715 FILL_LONGINT_OR_LEAVE(copy->dmRecipientOrgUnitNum, optarg);
3716 break;
3717 case 'P':
3718 if (!copy) {
3719 fprintf(stderr,
3720 _("Error: %s: Recipient box ID (-b) must precede "
3721 "to-hands option (-%c)\n"), optarg, opt);
3722 retval = -1;
3723 goto leave;
3725 FILL_OR_LEAVE(copy->dmToHands, optarg);
3726 break;
3728 /* Sender organisation structure options */
3729 case 'I':
3730 FILL_BOOLEAN_OR_LEAVE(envelope->dmPublishOwnID, optarg);
3731 case 'u':
3732 FILL_OR_LEAVE(envelope->dmSenderOrgUnit, optarg);
3733 break;
3734 case 'n':
3735 FILL_LONGINT_OR_LEAVE(envelope->dmSenderOrgUnitNum, optarg);
3736 break;
3738 /* Message identifier options */
3739 case 'r':
3740 FILL_OR_LEAVE(envelope->dmSenderRefNumber, optarg);
3741 break;
3742 case 'f':
3743 FILL_OR_LEAVE(envelope->dmSenderIdent, optarg);
3744 break;
3745 case 'R':
3746 FILL_OR_LEAVE(envelope->dmRecipientRefNumber, optarg);
3747 break;
3748 case 'F':
3749 FILL_OR_LEAVE(envelope->dmRecipientIdent, optarg);
3750 break;
3752 /* Legal title options */
3753 case 'y':
3754 FILL_LONGINT_OR_LEAVE(envelope->dmLegalTitleYear, optarg);
3755 break;
3756 case 'a':
3757 FILL_LONGINT_OR_LEAVE(envelope->dmLegalTitleLaw, optarg);
3758 break;
3759 case 'e':
3760 FILL_OR_LEAVE(envelope->dmLegalTitleSect, optarg);
3761 break;
3762 case 'o':
3763 FILL_OR_LEAVE(envelope->dmLegalTitlePar, optarg);
3764 break;
3765 case 'i':
3766 FILL_OR_LEAVE(envelope->dmLegalTitlePoint, optarg);
3767 break;
3769 /* Delivery options */
3770 case 'p':
3771 FILL_BOOLEAN_OR_LEAVE(envelope->dmPersonalDelivery, optarg);
3772 break;
3773 case 't':
3774 FILL_BOOLEAN_OR_LEAVE(envelope->dmAllowSubstDelivery, optarg);
3775 break;
3776 case 'A':
3777 FILL_BOOLEAN_OR_LEAVE(envelope->dmOVM, optarg);
3778 break;
3779 case 'C':
3780 FILL_OR_LEAVE(envelope->dmType, optarg);
3781 break;
3783 /* Document options */
3784 case 'd':
3785 document = NULL;
3786 if (!documents) {
3787 /* First document */
3788 CALLOC_OR_LEAVE(message->documents);
3789 message->documents->destructor =
3790 (void(*)(void **)) isds_document_free;
3791 documents = message->documents;
3792 CALLOC_OR_LEAVE(document);
3793 documents->data = document;
3794 document->dmFileMetaType = FILEMETATYPE_MAIN;
3795 } else {
3796 /* Next document */
3797 CALLOC_OR_LEAVE(documents->next);
3798 documents->next->destructor =
3799 (void(*)(void **)) isds_document_free;
3800 documents = documents->next;
3801 CALLOC_OR_LEAVE(document);
3802 documents->data = document;
3803 document->dmFileMetaType = FILEMETATYPE_ENCLOSURE;
3806 if (strcmp(optarg, "-")) {
3807 /* Load file if specified. Keep editing new file after
3808 * processing all arguments. */
3809 if (load_data_from_file(optarg, &document->data,
3810 &document->data_length, &document->dmMimeType)) {
3811 retval = -1;
3812 goto leave;
3814 /* XXX: POSIX basename() modifies argument */
3815 FILL_OR_LEAVE(document->dmFileDescr, basename(optarg));
3817 break;
3818 case 'D':
3819 if (!document) {
3820 fprintf(stderr,
3821 _("Error: %s: Document file (-d) must precede "
3822 "document name (-%c)\n"), optarg, opt);
3823 retval = -1;
3824 goto leave;
3826 FILL_OR_LEAVE(document->dmFileDescr, optarg);
3827 break;
3828 case 'x':
3829 if (!document) {
3830 fprintf(stderr,
3831 _("Error: %s: Document file (-d) must precede "
3832 "XPath expression (-%c)\n"), optarg, opt);
3833 retval = -1;
3834 goto leave;
3836 /* Load XML node list */
3837 char *xpath_expr = NULL;
3838 FILL_OR_LEAVE(xpath_expr, optarg);
3839 retval = load_xml_subtree_from_memory(
3840 document->data, document->data_length,
3841 &document->xml_node_list, xpath_expr);
3842 if (retval) {
3843 free(xpath_expr);
3844 goto leave;
3846 /* Switch document type to XML */
3847 document->is_xml = 1;
3848 zfree(document->data);
3849 document->data_length = 0;
3850 documents->destructor =
3851 (void(*)(void **)) free_document_with_xml_node_list;
3852 break;
3853 case 'm':
3854 if (!document) {
3855 fprintf(stderr,
3856 _("Error: %s: Document file (-d) must precede "
3857 "MIME type (-%c)\n"), optarg, opt);
3858 retval = -1;
3859 goto leave;
3861 FILL_OR_LEAVE(document->dmMimeType, optarg);
3862 break;
3863 case 'g':
3864 if (!document) {
3865 fprintf(stderr,
3866 _("Error: %s: Document file (-d) must precede "
3867 "document ID (-%c)\n"), optarg, opt);
3868 retval = -1;
3869 goto leave;
3871 FILL_OR_LEAVE(document->dmFileGuid, optarg);
3872 break;
3873 case 'G':
3874 if (!document) {
3875 fprintf(stderr,
3876 _("Error: %s: Document file (-d) must precede "
3877 "document reference (-%c)\n"), optarg, opt);
3878 retval = -1;
3879 goto leave;
3881 FILL_OR_LEAVE(document->dmUpFileGuid, optarg);
3882 break;
3883 case 'c':
3884 if (!document) {
3885 fprintf(stderr,
3886 _("Error: Document file (-d) must precede "
3887 "document signature type (-%c)\n"), opt);
3888 retval = -1;
3889 goto leave;
3891 document->dmFileMetaType = FILEMETATYPE_SIGNATURE;
3892 break;
3894 default:
3895 shi_compose_usage(argv[0]);
3896 retval = -1;
3897 goto leave;
3901 /* All options must be recognized */
3902 if (optind != argc) {
3903 fprintf(stderr, _("Error: Superfluous argument\n"));
3904 shi_compose_usage(argv[0]);
3905 retval = -1;
3906 goto leave;
3909 if (!copies) {
3910 fprintf(stderr, _("Error: No recipient box ID specified\n"));
3911 shi_compose_usage(argv[0]);
3912 retval = -1;
3913 goto leave;
3916 /* TODO: Check Legal Title soft dependencies */
3918 /* Compose missing documents */
3919 for (i = 1, documents = message->documents; NULL != documents;
3920 documents = documents->next, i++) {
3921 document = documents->data;
3922 if (NULL == document->data) {
3923 printf(_("Editing document #%u...\n"), i);
3924 if (edit_new_textual_document(document)) {
3925 fprintf(stderr, _("Composition aborted.\n"));
3926 retval = -1;
3927 goto leave;
3932 /* Preview */
3933 oprintf(_("Following message has been composed:\n"));
3934 format_message(message);
3935 oprintf(_("Following recipients have been specified:\n"));
3936 format_copies(copies);
3937 /* TODO: Confirmation, correction */
3939 /* Send a message */
3940 printf(_("Sending message...\n"));
3941 err = isds_send_message_to_multiple_recipients(cisds, message, copies);
3942 finish_isds_operation(cisds, err);
3943 if (err && err != IE_PARTIAL_SUCCESS) {
3944 retval = -1;
3945 goto leave;
3948 /* Show results for each copy */
3949 for (copy_item = copies; copy_item; copy_item = copy_item->next) {
3950 if (!copy_item->data) continue;
3951 copy = (struct isds_message_copy *) copy_item->data;
3952 recipient_id_locale = utf82locale(copy->dbIDRecipient);
3954 if (copy->error) {
3955 retval = -1;
3956 if (copy->dmStatus) dmStatus_locale = utf82locale(copy->dmStatus);
3957 if (dmStatus_locale)
3958 oprintf(_("%s: Failed: %s: %s\n"),
3959 recipient_id_locale,
3960 isds_strerror(copy->error), dmStatus_locale);
3961 else
3962 oprintf(_("%s: Failed: %s\n"), recipient_id_locale,
3963 isds_strerror(copy->error));
3964 zfree(dmStatus_locale);
3965 } else {
3966 message_id_locale = utf82locale(copy->dmID);
3967 oprintf(_("%s: Succeeded. Assigned message ID: %s\n"),
3968 recipient_id_locale, message_id_locale);
3969 free(message_id_locale);
3972 free(recipient_id_locale);
3975 leave:
3976 isds_message_free(&message);
3977 isds_list_free(&copies);
3978 return retval;
3982 #undef FILL_LONGINT_OR_LEAVE
3983 #undef FILL_BOOLEAN_OR_LEAVE
3984 #undef CALLOC_OR_LEAVE
3985 #undef FILL_OR_LEAVE
3988 static void shi_save_stamp_usage(const char *command) {
3989 oprintf(_(
3990 "Usage: %s FILE\n"
3991 "Save message time stamp into local FILE.\n"),
3992 command);
3996 static int shi_save_stamp(int argc, const char **argv) {
3997 _Bool overwrite = cfg_getbool(configuration, CONFIG_OVERWRITEFILES);
3999 if (!argv || !argv[1] || !*argv[1]) {
4000 shi_save_message_usage(argv[0]);
4001 return -1;
4004 if (!message) {
4005 fprintf(stderr, _("No message loaded\n"));
4006 return -1;
4008 if (!message->envelope || !message->envelope->timestamp||
4009 message->envelope->timestamp_length == 0) {
4010 fprintf(stderr, _("Loaded message is missing time stamp\n"));
4011 return -1;
4014 return save_data_to_file(argv[1], -1,
4015 message->envelope->timestamp, message->envelope->timestamp_length,
4016 "application/timestamp-reply", overwrite);
4020 static int shi_show_list(int argc, const char **argv) {
4021 if (!messages) {
4022 fprintf(stderr, _("No message list loaded\n"));
4023 return -1;
4026 oprintf((messages_are_outgoing) ?
4027 ngettext("You have %'lu outgoing message\n",
4028 "You have %'lu outgoing messages\n", total_messages) :
4029 ngettext("You have %'lu incoming message\n",
4030 "You have %'lu incoming messages\n", total_messages),
4031 total_messages);
4032 print_message_list(messages, messages_are_outgoing);
4033 return 0;
4037 static int shi_list_incoming(int argc, const char **argv) {
4038 isds_error err;
4040 printf(_("Listing incoming messages...\n"));
4041 err = isds_get_list_of_received_messages(cisds, NULL, NULL, NULL,
4042 MESSAGESTATE_ANY, 0, &total_messages, &messages);
4043 finish_isds_operation(cisds, err);
4044 if (err) {
4045 set_prompt(NULL);
4046 select_completition(COMPL_COMMAND);
4047 return -1;
4049 messages_are_outgoing = 0;
4051 shi_show_list(0, NULL);
4053 set_prompt(_("%s %'lu"), argv[0], total_messages);
4054 select_completition(COMPL_LIST);
4055 return 0;
4059 static int shi_list_outgoing(int argc, const char **argv) {
4060 isds_error err;
4062 printf(_("Listing outgoing messages...\n"));
4063 err = isds_get_list_of_sent_messages(cisds, NULL, NULL, NULL,
4064 MESSAGESTATE_ANY, 0, &total_messages, &messages);
4065 finish_isds_operation(cisds, err);
4066 if (err) {
4067 set_prompt(NULL);
4068 select_completition(COMPL_COMMAND);
4069 return -1;
4071 messages_are_outgoing = 1;
4073 shi_show_list(0, NULL);
4075 set_prompt(_("%s %'lu"), argv[0], total_messages);
4076 select_completition(COMPL_LIST);
4077 return 0;
4081 /* Submit document for conversion and print assigned identifier */
4082 static int do_convert(const struct isds_document *document) {
4083 isds_error err;
4084 char *id = NULL;
4085 struct tm *date = NULL;
4087 if (!document) return -1;
4089 printf(_("Submitting document for authorized conversion...\n"));
4091 err = czp_convert_document(czechpoint, document, &id, &date);
4092 finish_isds_operation(czechpoint, err);
4094 if (!err) {
4095 char *name_locale = utf82locale(document->dmFileDescr);
4096 char *date_string = tm2string(date);
4097 char *id_locale = utf82locale(id);
4098 oprintf(_(
4099 "Document submitted for authorized conversion successfully under name\n"
4100 "`%s' on %s.\n"
4101 "Submit identifier assigned by Czech POINT deposit is `%s'.\n"),
4102 name_locale, date_string, id_locale);
4103 free(name_locale);
4104 free(date_string);
4105 free(id_locale);
4106 oprintf(_("Be ware that submitted document has restricted lifetime "
4107 "(30 days).\n"));
4108 oprintf(_("See <%s> for more details.\n"), CZPDEPOSIT_URL);
4111 free(id); free(date);
4112 return (err) ? -1 : 0;
4116 static void shi_convert_file_or_message_usage(const char *command) {
4117 oprintf(_(
4118 "Usage: %s [FILE [NAME]]\n"
4119 "Submit local FILE to authorized conversion under NAME. If NAME is missing,\n"
4120 "it will use FILE name. If FILE is missing, it will submit current message.\n"),
4121 command);
4122 oprintf(_(
4123 "\n"
4124 "If Czech POINT deposit accepts document, it will return document identifier\n"
4125 "that user is supposed to provide to officer at Czech POINT contact place.\n"
4126 "Currently only PDF 1.3 and higher version files and signed messages are\n"
4127 "accepted.\n"));
4128 oprintf(_("See <%s> for more details.\n"), CZPDEPOSIT_URL);
4132 static int shi_convert_file_or_message(int argc, const char **argv) {
4133 int fd = -1;
4134 struct isds_document document;
4135 int retval = 0;
4137 if (!argv || argc > 3) {
4138 shi_convert_file_or_message_usage((argv)?argv[0]:NULL);
4139 return -1;
4142 memset(&document, 0, sizeof(document));
4144 if (NULL == argv[1] || !*argv[1]) {
4145 /* Convert current message */
4146 if (!message) {
4147 fprintf(stderr, _("No message loaded\n"));
4148 return -1;
4150 if (!message->raw || !message->raw_length) {
4151 fprintf(stderr,
4152 _("Current message is missing raw representation\n"));
4153 return -1;
4155 document.dmFileDescr = astrcat(
4156 (NULL != message->envelope && NULL != message->envelope->dmID) ?
4157 message->envelope->dmID :
4158 "unknown",
4159 ".zfo");
4160 if (NULL == document.dmFileDescr) {
4161 printf(_("Could not build document name from message ID\n"));
4162 return -1;
4164 document.data = message->raw;
4165 document.data_length = message->raw_length;
4166 } else {
4167 /* Convert local file */
4168 if (argc == 3 && argv[2] && *argv[2])
4169 document.dmFileDescr = locale2utf8(argv[2]);
4170 else
4171 document.dmFileDescr = locale2utf8(argv[1]);
4172 if (!document.dmFileDescr) {
4173 printf(_("Could not convert document name to UTF-8\n"));
4174 return -1;
4177 printf(_("Loading document from file `%s'...\n"), argv[1]);
4178 if (mmap_file(argv[1], &fd, &document.data, &document.data_length)) {
4179 free(document.dmFileDescr);
4180 return -1;
4184 retval = do_convert(&document);
4186 if (0 <= fd) {
4187 munmap_file(fd, document.data, document.data_length);
4189 free(document.dmFileDescr);
4190 return retval;
4194 static void shi_convert_document_usage(const char *command) {
4195 oprintf(_(
4196 "Usage: %s NUMBER\n"
4197 "Submit message document with ordinal NUMBER to authorized conversion.\n"),
4198 command);
4199 oprintf(_(
4200 "\n"
4201 "If Czech POINT deposit accepts document, it will return document identifier\n"
4202 "that user is supposed to provide to officer at Czech POINT contact place.\n"
4203 "Currently only PDF 1.3 and higher version files and signed messages are\n"
4204 "accepted.\n"));
4205 oprintf(_("See <%s> for more details.\n"), CZPDEPOSIT_URL);
4209 static int shi_convert_document(int argc, const char **argv) {
4210 const struct isds_document *document;
4212 if (!argv || !argv[1] || !*argv[1]) {
4213 shi_convert_document_usage((argv)?argv[0]:NULL);
4214 return -1;
4217 document = locate_document_by_ordinal_string(argv[1]);
4218 if (!document) return -1;
4220 return do_convert(document);
4224 static void shi_resign_usage(const char *command) {
4225 oprintf(_(
4226 "Usage: %s [FILE]\n"
4227 "Send message or delivery data to re-sign it and to add current time stamp.\n"
4228 "If FILE is specified, message will be loaded from local file. Otherwise\n"
4229 "current message will be sent.\n"),
4230 command);
4231 oprintf(_(
4232 "\n"
4233 "Only signed messages or delivery data without time stamp are accepted by\n"
4234 "ISDS. Output re-signed message or delivery data will be loaded.\n"));
4238 static int shi_resign(int argc, const char **argv) {
4239 int fd = -1;
4240 void *data = NULL; /* Static */
4241 void *resigned_data = NULL; /* Dynamic, stored into message */
4242 size_t data_length = 0, resigned_data_length = 0;
4243 struct tm *valid_to = NULL; /* Dynamic */
4244 isds_error err;
4245 int error;
4247 if (!argv || argc > 3) {
4248 shi_resign_usage((argv)?argv[0]:NULL);
4249 return -1;
4252 if (NULL == argv[1] || !*argv[1]) {
4253 /* Use current message */
4254 if (!message) {
4255 fprintf(stderr, _("No message or delivery data loaded\n"));
4256 return -1;
4258 if (!message->raw || !message->raw_length) {
4259 fprintf(stderr, _("Current message or delivery data "
4260 "is missing raw representation\n"));
4261 return -1;
4263 data = message->raw;
4264 data_length = message->raw_length;
4265 } else {
4266 /* Use local file */
4267 printf(_("Loading message or delivery data from file `%s'...\n"),
4268 argv[1]);
4269 if (mmap_file(argv[1], &fd, &data, &data_length)) {
4270 return -1;
4274 printf(_("Re-signing...\n"));
4275 err = isds_resign_message(cisds, data, data_length,
4276 &resigned_data, &resigned_data_length, &valid_to);
4277 finish_isds_operation(cisds, err);
4279 if (0 <= fd) {
4280 munmap_file(fd, data, data_length);
4283 if (err) {
4284 return -1;
4287 print_header_tm(_("New time stamp expires"), valid_to);
4288 free(valid_to);
4290 error = do_load_anything(resigned_data, resigned_data_length, BUFFER_MOVE);
4291 if (error) free(resigned_data);
4293 return error;
4297 #if ENABLE_DEBUG
4298 static void shi_print_usage(const char *command) {
4299 oprintf(_(
4300 "Usage: %s STRING LENGTH\n"
4301 "Prints STRING into LENGTH columns. Negative LENGTH means not to cut\n"
4302 "overflowing string.\n"
4303 "This should be locale and terminal agnostic.\n"),
4304 command);
4308 static int shi_print(int argc, const char **argv) {
4309 long int width;
4311 if (!argv || !argv[1] || !argv[2]) {
4312 shi_print_usage((argv)?argv[0]:NULL);
4313 return -1;
4316 width = strtol(argv[2], NULL, 10);
4317 if (width < INT_MIN) {
4318 fprintf(stderr,
4319 _("Length argument must not lesser than %d.\n"), INT_MIN);
4320 return -1;
4322 if (width > INT_MAX) {
4323 fprintf(stderr,
4324 _("Length argument must not be greater than %d.\n"), INT_MAX);
4325 return -1;
4328 oprintf(_(">"));
4329 onprint(argv[1], width);
4330 oprintf(_("<\n"));
4332 return 0;
4336 static int shi_tokenize(int argc, const char **argv) {
4338 if (!argv) return 0;
4340 for (int i = 0; i < argc; i++) {
4341 oprintf(_(">%s<\n"), argv[i]);
4343 return 0;
4347 static int shi_quote(int argc, const char **argv) {
4348 char *escaped, *unescaped;
4350 if (!argv) return 0;
4352 oprintf(_("Original\tQuoted\tDequoted\n"));
4353 for (int i = 0; i < argc; i++) {
4354 escaped = shi_quote_filename((char *) argv[i], 0, NULL);
4355 unescaped = shi_dequote_filename((char *) argv[i], 0);
4356 oprintf(_(">%s<\t>%s<\t>%s<\n"), argv[i], escaped, unescaped);
4357 free(escaped);
4358 free(unescaped);
4360 return 0;
4362 #endif
4365 /* pclose(pipe), restore ouput to stdout, show error return code */
4366 int wait_for_shell(FILE **pipe) {
4367 int retval = 0;
4369 if (pipe && *pipe) {
4370 retval = pclose(*pipe);
4371 *pipe = NULL;
4372 output = stdout;
4374 if (retval == -1)
4375 fprintf(stderr, _("Exit code of shell command could not "
4376 "be determined\n"));
4377 else if (WIFEXITED(retval) && WEXITSTATUS(retval))
4378 printf(_("Shell command exited with code %d\n"),
4379 WEXITSTATUS(retval));
4380 else if (WIFSIGNALED(retval))
4381 printf(_("Shell command terminated by signal "
4382 "#%d\n"), WTERMSIG(retval));
4385 return retval;
4389 /* Interactive loop */
4390 void shi_loop(void) {
4391 char *command_line = NULL;
4392 char **command_argv = NULL;
4393 int command_argc;
4394 char *shell = NULL;
4395 struct command *command = NULL;
4396 FILE *pipe = NULL;
4397 int failed = 0;
4399 oprintf(_("Use `help' command to get list of available commands.\n"));
4401 select_completition(COMPL_COMMAND);
4402 set_prompt(NULL);
4404 while (1) {
4405 command_line = readline((prompt) ? prompt : _("shigofumi> "));
4406 /* Remember not parsable commands too to user be able to get back to
4407 * fix command */
4408 if (command_line && *command_line) {
4409 /* TODO: Omit blank lines */
4410 add_history(command_line);
4413 command_argv = tokenize(command_line, &command_argc, &shell);
4415 if (command_argv && command_argv[0]) {
4416 command = find_command(command_argv[0]);
4418 if (!command) {
4419 fprintf(stderr, _("Command not understood\n"));
4420 } else {
4421 failed = 0;
4422 if (shell) {
4423 fflush(stdout);
4424 pipe = popen(shell, "w");
4425 if (!pipe) {
4426 fprintf(stderr, _("Could not run shell command `%s':"
4427 " %s\n"), shell, strerror(errno));
4428 failed = 1;
4430 output = pipe;
4432 if (!failed) {
4433 command->function(command_argc,
4434 (const char **) command_argv);
4435 wait_for_shell(&pipe);
4440 argv_free(command_argv);
4441 zfree(shell);
4442 zfree(command_line);
4447 /* Non-interactive mode. Commands from @lines are processed until any command
4448 * lines remains or no error occurred. First failure terminates processing.
4449 * @lines is sequence of commands separated by '\n' or '\r'. The content is
4450 * modified during this call.
4451 * @return 0 if all command succeed, otherwise non-zero value
4453 int shi_batch(char *lines) {
4454 char *command_line;
4455 char **command_argv = NULL;
4456 int command_argc;
4457 char *shell = NULL;
4458 struct command *command = NULL;
4459 int retval = 0;
4460 FILE *pipe = NULL;
4461 int failed = 0;
4463 oprintf(_("Batch mode started.\n"));
4464 batch_mode = 1;
4465 select_completition(COMPL_COMMAND);
4467 while (!retval && (command_line = strtok(lines, "\n\r"))) {
4468 lines = NULL; /* strtok(3) requires it for subsequent calls */
4470 printf(_("Processing command: %s\n"), command_line);
4472 command_argv = tokenize(command_line, &command_argc, &shell);
4474 if (command_argv && command_argv[0]) {
4475 command = find_command(command_argv[0]);
4477 if (!command) {
4478 fprintf(stderr, _("Command not understood\n"));
4479 retval = -1;
4480 } else {
4481 failed = 0;
4482 if (shell) {
4483 fflush(stdout);
4484 pipe = popen(shell, "w");
4485 if (!pipe) {
4486 fprintf(stderr, _("Could not run shell command `%s':"
4487 " %s\n"), shell, strerror(errno));
4488 failed = 1;
4490 output = pipe;
4492 if (!failed) {
4493 retval = command->function(command_argc,
4494 (const char **) command_argv);
4495 if (wait_for_shell(&pipe)) retval = -1;
4500 argv_free(command_argv);
4501 zfree(shell);
4504 if (retval)
4505 fprintf(stderr, _("Command failed!\n"));
4506 return retval;
4510 #define COMMON_COMMANDS \
4511 { "accept", shi_accept_message, N_("accept commercial message"), \
4512 shi_accept_message_usage, ARGTYPE_MSGID }, \
4513 { "box", shi_box, N_("show current box details"), NULL, \
4514 ARGTYPE_NONE }, \
4515 { "boxlist", shi_boxlist, N_("get list of all boxes"), shi_boxlist_usage, \
4516 ARGTYPE_FILE }, \
4517 { "cache", shi_cache, N_("show cache details"), NULL, \
4518 ARGTYPE_NONE }, \
4519 { "cd", shi_chdir, N_("change working directory"), shi_chdir_usage, \
4520 ARGTYPE_FILE }, \
4521 { "commercialreceiving", shi_commercialreceiving, \
4522 N_("manipulate commercial receiving box status"), \
4523 shi_commercialreceiving_usage, ARGTYPE_BOXID }, \
4524 { "commercialsending", shi_commercialsending, \
4525 N_("manipulate commercial sending box status"), \
4526 shi_commercialsending_usage, ARGTYPE_BOXID }, \
4527 { "compose", shi_compose, N_("compose a message"), shi_compose_usage, \
4528 ARGTYPE_FILE }, \
4529 { "convert", shi_convert_file_or_message, \
4530 N_("submit local document for authorized conversion"), \
4531 shi_convert_file_or_message_usage, ARGTYPE_FILE }, \
4532 { "copying", shi_copying, N_("show this program licence excerpt"), NULL, \
4533 ARGTYPE_NONE }, \
4534 { "debug", shi_debug, N_("set debugging"), shi_debug_usage, \
4535 ARGTYPE_FILE }, \
4536 { "delete", shi_delete_message, N_("delete message from storage"), \
4537 shi_delete_message_usage, ARGTYPE_MSGID }, \
4538 { "delivery", shi_delivery, N_("get message delivery details"), \
4539 shi_delivery_usage, ARGTYPE_MSGID }, \
4540 { "findbox", shi_find_box, N_("search for a box"), shi_find_box_usage, \
4541 ARGTYPE_BOXID }, \
4542 { "hash", shi_hash, N_("query ISDS for message hash"), \
4543 shi_hash_usage, ARGTYPE_MSGID }, \
4544 { "help", shi_help, N_("describe commands"), shi_help_usage, \
4545 ARGTYPE_COMMAND }, \
4546 { "load", shi_load_anything, \
4547 N_("load message or message delivery details from local file"), \
4548 shi_load_anything_usage, ARGTYPE_FILE }, \
4549 { "login", shi_login, N_("log into ISDS"), shi_login_usage, \
4550 ARGTYPE_FILE }, \
4551 { "lsi", shi_list_incoming, N_("list received messages"), NULL, \
4552 ARGTYPE_NONE }, \
4553 { "lso", shi_list_outgoing, N_("list sent messages"), NULL, \
4554 ARGTYPE_NONE }, \
4555 { "msgi", shi_incoming_message, N_("get incoming message"), \
4556 shi_incoming_message_usage, ARGTYPE_MSGID }, \
4557 { "msgo", shi_outgoing_message, N_("get outgoing message"), \
4558 shi_outgoing_message_usage, ARGTYPE_MSGID }, \
4559 { "passwd", shi_passwd, N_("manipulate user password"), shi_passwd_usage, \
4560 ARGTYPE_NONE }, \
4561 { "pwd", shi_pwd, N_("print working directory"), NULL, ARGTYPE_NONE }, \
4562 { "quit", shi_quit, N_("exit shigofumi"), NULL, ARGTYPE_NONE }, \
4563 { "read", shi_read_message, N_("mark message as read"), \
4564 shi_read_message_usage, ARGTYPE_MSGID }, \
4565 { "resign", shi_resign, N_("re-sign message or delivery data"), \
4566 shi_resign_usage, ARGTYPE_FILE }, \
4567 { "set", shi_settings, N_("show settings"), NULL, ARGTYPE_NONE }, \
4568 { "sender", shi_message_sender, N_("get message sender"), \
4569 shi_message_sender_usage, ARGTYPE_MSGID }, \
4570 { "statbox", shi_stat_box, N_("get status of a box"), shi_stat_box_usage, \
4571 ARGTYPE_BOXID }, \
4572 { "user", shi_user, N_("show current user details"), NULL, \
4573 ARGTYPE_NONE }, \
4574 { "users", shi_users, N_("show box users"), shi_users_usage, \
4575 ARGTYPE_NONE }, \
4576 { "version", shi_version, N_("show version of this program"), NULL, \
4577 ARGTYPE_NONE}, \
4578 { NULL, NULL, NULL, NULL, ARGTYPE_NONE }
4580 struct command base_commands[] = {
4581 #if ENABLE_DEBUG
4582 { "quote", shi_quote, N_("demonstrate argument escaping"), NULL,
4583 ARGTYPE_FILE },
4584 { "print", shi_print, N_("print string into given width"),
4585 shi_print_usage, ARGTYPE_NONE },
4586 { "tokenize", shi_tokenize, N_("demonstrate arguments tokenization"), NULL,
4587 ARGTYPE_FILE },
4588 #endif
4589 COMMON_COMMANDS
4592 struct command message_commands[] = {
4593 { "authenticate", shi_authenticate, N_("check message authenticity"),
4594 NULL, ARGTYPE_NONE },
4595 { "cat", shi_cat_message, N_("show raw current message"),
4596 shi_cat_message_usage, ARGTYPE_NONE },
4597 { "catdoc", shi_cat_document, N_("show raw document"),
4598 shi_cat_document_usage, ARGTYPE_DOCID },
4599 { "convertdoc", shi_convert_document,
4600 N_("submit document of current message for authorized conversion"),
4601 shi_convert_document_usage, ARGTYPE_DOCID },
4602 { "dump", shi_dump_message, N_("dump current message structure"),
4603 NULL, ARGTYPE_NONE },
4604 { "opendoc", shi_open_document, N_("open document using external utility"),
4605 shi_open_document_usage, ARGTYPE_DOCID },
4606 { "savestamp", shi_save_stamp,
4607 N_("save time stamp of current message into local file"),
4608 shi_save_stamp_usage, ARGTYPE_FILE },
4609 { "savedoc", shi_save_document,
4610 N_("save document of current message into local file"),
4611 shi_save_document_usage, ARGTYPE_FILE },
4612 { "save", shi_save_message, N_("save current message into local file"),
4613 shi_save_message_usage, ARGTYPE_FILE },
4614 { "show", shi_show_message, N_("show current message"), NULL,
4615 ARGTYPE_NONE },
4616 { "verify", shi_verify, N_("verify current message hash"), NULL,
4617 ARGTYPE_NONE },
4618 COMMON_COMMANDS
4621 struct command list_commands[] = {
4622 { "show", shi_show_list, N_("show current message list"), NULL,
4623 ARGTYPE_NONE },
4624 COMMON_COMMANDS
4627 #undef COMMON_COMMANDS
4630 static void main_version(void) {
4631 isds_init();
4632 show_version();
4633 isds_cleanup();
4634 printf("\n");
4635 shi_copying(0, NULL);
4639 static void main_usage(const char *command) {
4640 oprintf(_(
4641 "Usage: %s [OPTION...]\n"
4642 "Access ISDS, process local data box messages or delivery details, submit\n"
4643 "document to authorized conversion.\n"
4644 "\n"
4645 "Options:\n"
4646 " -c FILE use the FILE as configuration file instead of ~/%s\n"
4647 " -e COMMANDS execute COMMANDS (new line separated) and exit\n"
4648 " -V show version info and exit\n"
4650 (command) ? command : "shigofumi",
4651 CONFIG_FILE);
4655 int main(int argc, char **argv) {
4656 int opt;
4657 char *config_file = NULL;
4658 char *batch_commands = NULL;
4659 int retval = EXIT_SUCCESS;
4661 setlocale(LC_ALL, "");
4662 #if ENABLE_NLS
4663 /* Initialize gettext */
4664 bindtextdomain(PACKAGE, LOCALEDIR);
4665 textdomain(PACKAGE);
4666 #endif
4668 /* Default output */
4669 output = stdout;
4671 /* Parse arguments */
4672 optind = 0;
4673 while ((opt = getopt(argc, (char * const *)argv, "c:e:V")) != -1) {
4674 switch (opt) {
4675 case 'c':
4676 config_file = optarg;
4677 break;
4678 case 'e':
4679 batch_commands = optarg;
4680 break;
4681 case 'V':
4682 main_version();
4683 shi_exit(EXIT_SUCCESS);
4684 break;
4685 default:
4686 main_usage((argv[0]) ? basename(argv[0]): NULL);
4687 shi_exit(EXIT_FAILURE);
4692 if (shi_init(config_file)) {
4693 shi_exit(EXIT_FAILURE);
4696 /*shi_login(NULL);*/
4698 if (batch_commands) {
4699 if (shi_batch(batch_commands))
4700 retval = EXIT_FAILURE;
4701 } else {
4702 shi_loop();
4705 shi_exit(retval);