Recgnize new box types by searchbox and findbox commands
[shigofumi.git] / src / shigofumi.c
blobab6fed6b74417cd6cfd8673386391247c78b2d4a
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>
21 #include <magic.h>
23 #include "shigofumi.h"
24 #include "completion.h"
25 #include "data.h"
26 #include "io.h"
27 #include "ui.h"
28 #include "utils.h"
30 #define CONFIG_FILE ".shigofumirc"
31 #define CONFIG_SERVER "base_url"
32 #define CONFIG_USERNAME "username"
33 #define CONFIG_PASSWORD "password"
34 #define CONFIG_CERT_FORMAT "certificate_format"
35 #define CONFIG_CERT_PATH "certificate_path"
36 #define CONFIG_KEY_ENGINE "key_engine"
37 #define CONFIG_KEY_FORMAT "key_format"
38 #define CONFIG_KEY_PATH "key_path"
39 #define CONFIG_KEY_PASSWORD "key_password"
40 #define CONFIG_OTP_METHOD "otp_method"
41 #define CONFIG_OTP_CODE "otp_code"
42 #define CONFIG_DEBUGLEVEL "debug_level"
43 #define CONFIG_VERIFYSERVER "verify_server"
44 #define CONFIG_CAFILE "ca_file"
45 #define CONFIG_CADIRECTORY "ca_directory"
46 #define CONFIG_CLEAN_TEMPORARY_FILES "clean_temporary_files"
47 #define CONFIG_CONFIRM_SEND "confirm_send"
48 #define CONFIG_CRLFILE "crl_file"
49 #define CONFIG_TIMEOUT "timeout"
50 #define CONFIG_LOGFACILITIES "log_facilities"
51 #define CONFIG_LOGFILE "log_file"
52 #define CONFIG_LOGLEVEL "log_level"
53 #define CONFIG_MARKMESSAGEREAD "mark_message_read"
54 #define CONFIG_NORMALIZEMIMETYPE "normalize_mime_type"
55 #define CONFIG_OPENCOMMAND "open_command"
56 #define CONFIG_OVERWRITEFILES "overwrite_files"
57 #define CZPDEPOSIT_URL "https://www.czechpoint.cz/uschovna/"
59 #define TIMEOUT 10000
60 #define LOG_LEVEL 20
62 /* Configuration */
63 cfg_opt_t configuration_syntax[] = {
64 CFG_STR(CONFIG_SERVER, NULL, CFGF_NONE),
65 CFG_STR(CONFIG_USERNAME, NULL, CFGF_NODEFAULT),
66 CFG_STR(CONFIG_PASSWORD, NULL, CFGF_NODEFAULT),
67 CFG_STR(CONFIG_CERT_FORMAT, NULL, CFGF_NODEFAULT),
68 CFG_STR(CONFIG_CERT_PATH, NULL, CFGF_NODEFAULT),
69 CFG_STR(CONFIG_KEY_ENGINE, NULL, CFGF_NODEFAULT),
70 CFG_STR(CONFIG_KEY_FORMAT, NULL, CFGF_NODEFAULT),
71 CFG_STR(CONFIG_KEY_PATH, NULL, CFGF_NODEFAULT),
72 CFG_STR(CONFIG_KEY_PASSWORD, NULL, CFGF_NODEFAULT),
73 CFG_STR(CONFIG_OTP_METHOD, NULL, CFGF_NODEFAULT),
74 CFG_STR(CONFIG_OTP_CODE, NULL, CFGF_NODEFAULT),
75 /*CFG_STR(CONFIG_DEBUGLEVEL, NULL, CFGF_NODEFAULT),*/
76 CFG_BOOL(CONFIG_VERIFYSERVER, cfg_true, CFGF_NONE),
77 CFG_STR(CONFIG_CAFILE, NULL, CFGF_NODEFAULT),
78 CFG_STR(CONFIG_CADIRECTORY, NULL, CFGF_NODEFAULT),
79 CFG_BOOL(CONFIG_CLEAN_TEMPORARY_FILES, cfg_true, CFGF_NONE),
80 CFG_STR(CONFIG_CRLFILE, NULL, CFGF_NODEFAULT),
81 CFG_INT(CONFIG_TIMEOUT, TIMEOUT, CFGF_NONE),
82 CFG_STR_LIST(CONFIG_LOGFACILITIES, "{none}", CFGF_NONE),
83 CFG_STR(CONFIG_LOGFILE, NULL, CFGF_NODEFAULT),
84 CFG_INT(CONFIG_LOGLEVEL, LOG_LEVEL, CFGF_NONE),
85 CFG_BOOL(CONFIG_CONFIRM_SEND, cfg_true, CFGF_NONE),
86 CFG_BOOL(CONFIG_MARKMESSAGEREAD, cfg_false, CFGF_NONE),
87 CFG_BOOL(CONFIG_NORMALIZEMIMETYPE, cfg_true, CFGF_NONE),
88 CFG_STR_LIST(CONFIG_OPENCOMMAND, "{xdg-open, %f}", CFGF_NONE),
89 CFG_BOOL(CONFIG_OVERWRITEFILES, cfg_true, CFGF_NONE),
90 CFG_END()
92 cfg_t *configuration;
94 /* Logger */
95 int logger_fd = -1;
97 /* UI */
98 _Bool batch_mode = 0;
99 char *prompt = NULL;
100 struct command (*commands)[] = NULL;
101 FILE *output = NULL;
103 /* Data */
104 struct isds_ctx *cisds = NULL;
105 struct isds_list *boxes = NULL;
106 struct isds_message *message = NULL;
107 _Bool messages_are_outgoing = 0;
108 struct isds_list *messages = NULL;
109 unsigned long int total_messages = 0;
110 struct isds_ctx *czechpoint = NULL;
111 struct isds_list *temporary_files = NULL;
113 /* Temporary log-in settings */
114 char *server = NULL;
115 char *username = NULL;
116 char *password = NULL;
117 char *key_password = NULL;
118 char *otp_method = NULL;
119 char *otp_code = NULL;
120 char *pki_engine = NULL;
121 char *pki_certificate_path = NULL;
122 char *pki_certificate_format = NULL;
123 char *pki_key_path = NULL;
124 char *pki_key_format = NULL;
126 static void discard_credentials(void) {
127 zfree(server);
128 zfree(username);
129 zfree(password);
130 zfree(key_password);
131 zfree(otp_method);
132 zfree(otp_code);
133 zfree(pki_engine);
134 zfree(pki_certificate_path);
135 zfree(pki_certificate_format);
136 zfree(pki_key_path);
137 zfree(pki_key_format);
141 /* Remove temporary file */
142 void shi_unlink_temporary_file(void **data) {
143 if (!data || !*data) return;
145 const char *file = (const char *)*data;
146 unlink_file(file);
147 zfree(*data);
151 /* Finish ISDS operation, report error, if the operation returned different
152 * code than @positive_code. */
153 static void finish_isds_operation_with_code(struct isds_ctx *ctx,
154 isds_error err, isds_error positive_code) {
155 shi_progressbar_finish();
156 if (err != positive_code) {
157 if (isds_long_message(ctx))
158 fprintf(stderr, _("Error occurred: %s: %s\n"), isds_strerror(err),
159 isds_long_message(ctx));
160 else
161 fprintf(stderr, _("Error occurred: %s\n"), isds_strerror(err));
166 /* Finish ISDS operation, report error, if the operation did not returned
167 * IE_SUCCESS. */
168 static void finish_isds_operation(struct isds_ctx *ctx, isds_error err) {
169 finish_isds_operation_with_code(ctx, err, IE_SUCCESS);
173 /* Do the cleanup and exit */
174 static void shi_exit(int exit_code) {
175 /* Data */
176 discard_credentials();
177 isds_list_free(&boxes);
178 isds_message_free(&message);
179 isds_list_free(&messages);
180 if (temporary_files) {
181 oprintf(_("Removing temporary files...\n"));
182 isds_list_free(&temporary_files);
185 if (cisds) {
186 isds_error err;
187 oprintf(_("Logging out...\n"));
188 err = isds_logout(cisds);
189 finish_isds_operation(cisds, err);
190 if (err) exit_code = EXIT_FAILURE;
191 isds_ctx_free(&cisds);
193 isds_ctx_free(&czechpoint);
194 isds_cleanup();
196 /* Configuration */
197 cfg_free(configuration);
199 /* UI */
200 free(prompt);
201 free(commands);
203 exit(exit_code);
206 /* Set prompt. if @format is NULL, switch to default prompt */
207 static void set_prompt(const char *format, ...) {
208 char *buffer = NULL;
209 va_list ap;
210 if (format) {
211 va_start(ap, format);
212 shi_vasprintf(&buffer, format, ap);
213 va_end(ap);
215 if (buffer) {
216 shi_asprintf(&prompt, _("%s> "), buffer);
217 if (prompt) {
218 free(buffer);
219 return;
223 free(buffer);
224 free(prompt);
225 prompt = strdup(_("> "));
226 return;
229 zfree(prompt);
233 /* Convert name of PKI format into ISDS format type.
234 * Return -1 in case of invalid name */
235 static int string2pki_format(const char *name, isds_pki_format *format) {
236 if (!name || !format) { return -1; }
237 if (!strcasecmp(name, "PEM")) {
238 *format = PKI_FORMAT_PEM;
239 } else if (!strcasecmp(name, "DER")) {
240 *format = PKI_FORMAT_DER;
241 } else if (!strcasecmp(name, "ENG")) {
242 *format = PKI_FORMAT_ENG;
243 } else {
244 return -1;
246 return 0;
250 /* Check for configutation option CONFIG_CERT_FORMAT.
251 * Return 0 in case of valid or undefined value. */
252 static int shi_validate_certificate_format(cfg_t *configuration, cfg_opt_t *option) {
253 isds_pki_format format;
254 const char *value = cfg_opt_getnstr(option, 0);
256 if (NULL != value && string2pki_format(value, &format)) {
257 cfg_error(configuration, _("Invalid certificate format: %s"), value);
258 return -1;
261 return 0;
264 /* Check for configutation option CONFIG_KEY_FORMAT.
265 * Return 0 in case of valid or undefined value. */
266 static int shi_validate_key_format(cfg_t *configuration, cfg_opt_t *option) {
267 isds_pki_format format;
268 const char *value = cfg_opt_getnstr(option, 0);
270 if (NULL != value && string2pki_format(value, &format)) {
271 cfg_error(configuration, _("Invalid private key format: %s"), value);
272 return -1;
275 return 0;
278 static int shi_load_configuration(const char *config_file) {
279 char *config_name = NULL;
280 int ret;
281 int error = 0;
283 /* Get config file */
284 if (config_file) {
285 config_name = (char *) config_file;
286 } else {
287 if (-1 == shi_asprintf(&config_name, "%s/%s", getenv("HOME"),
288 CONFIG_FILE)) {
289 fprintf(stderr, _("Could not build configuration file name\n"));
290 return -1;
294 /* Parse configuration */
295 configuration = cfg_init(configuration_syntax, CFGF_NONE);
296 cfg_set_validate_func(configuration,
297 CONFIG_CERT_FORMAT, shi_validate_certificate_format);
298 cfg_set_validate_func(configuration,
299 CONFIG_KEY_FORMAT, shi_validate_key_format);
301 ret = cfg_parse(configuration, config_name);
302 if (ret) {
303 error = -1;
304 if (ret == CFG_FILE_ERROR) {
305 fprintf(stderr,
306 _("Error while opening configuration file `%s': %s\n"),
307 config_name, strerror(errno));
308 if (NULL == config_file) {
309 /* Consider missing default configuration file as non-fatal. */
310 oprintf(_("Using default configuration\n"));
311 error = 0;
313 } else {
314 fprintf(stderr, _("Error while parsing configuration file `%s'\n"),
315 config_name);
319 if (config_name != config_file) free(config_name);
320 return error;
324 void logger(isds_log_facility facility, isds_log_level level,
325 const char *message, int length, void *data) {
326 int fd;
327 ssize_t written, left = length;
329 if (!data) return;
330 fd = *((int *) data);
331 /*printf("\033[32mLOG(%02d,%02d): ", facility, level);
332 printf("%.*s", length, message);
333 printf("\033[m");*/
335 while (left) {
336 written = write(fd, message + length - left, left);
337 if (written == -1) {
338 fprintf(stderr,
339 _("Could not save log message into log file: %s\n"
340 "Log message discarded!\n"),
341 strerror(errno));
342 /*close(fd);
343 fd = -1;*/
344 return;
346 left-=written;
351 /* Redirect ISDS log to file if @file is not NULL. */
352 static int do_log_to_file(const char *file) {
353 if (file && *file) {
354 logger_fd = open_file_for_writing(file, 0, 1);
355 if (logger_fd == -1) {
356 fprintf(stderr, _("Could not redirect ISDS log to file `%s'\n"),
357 file);
358 return -1;
360 isds_set_log_callback(logger, &logger_fd);
362 return 0;
366 /* Add log facility based on its name. */
367 static int add_log_facility(isds_log_facility *facilities, const char *name) {
368 if (!facilities) return -1;
370 if (!strcmp(name, "none")) *facilities |= ILF_NONE;
371 else if (!strcmp(name, "http")) *facilities |= ILF_HTTP;
372 else if (!strcmp(name, "soap")) *facilities |= ILF_SOAP;
373 else if (!strcmp(name, "isds")) *facilities |= ILF_ISDS;
374 else if (!strcmp(name, "file")) *facilities |= ILF_FILE;
375 else if (!strcmp(name, "sec")) *facilities |= ILF_SEC;
376 else if (!strcmp(name, "xml")) *facilities |= ILF_XML;
377 else if (!strcmp(name, "all")) *facilities |= ILF_ALL;
378 else {
379 fprintf(stderr, _("%s: Unknown log facility\n"), name);
380 return -1;
383 return 0;
387 /* Save log facility into confuse configuration */
388 static void save_log_facility(int level) {
389 cfg_setlist(configuration, CONFIG_LOGFACILITIES, 0);
391 if (level == ILF_ALL) {
392 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "all");
393 return;
395 if (level == ILF_NONE) {
396 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "none");
397 return;
399 if (level & ILF_HTTP)
400 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "http");
401 if (level & ILF_SOAP)
402 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "soap");
403 if (level & ILF_ISDS)
404 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "isds");
405 if (level & ILF_FILE)
406 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "file");
407 if (level & ILF_SEC)
408 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "sec");
409 if (level & ILF_XML)
410 cfg_addlist(configuration, CONFIG_LOGFACILITIES, 1, "xml");
414 /* Clamp long int to unsigned int */
415 static unsigned int normalize_timeout(long int raw) {
416 if (raw < 0) {
417 oprintf(_("Configured network timeout is less then 0. "
418 "Clamped to 0.\n"));
419 return 0;
421 if (raw > UINT_MAX ) {
422 oprintf(_("Configured network timeout is greater then %1$u. "
423 "Clamped to %1$u.\n"), UINT_MAX);
424 return UINT_MAX;
426 return (unsigned int) raw;
430 /* Clamp long int to <0;100> */
431 static unsigned int normalize_log_level(long int raw) {
432 if (raw < 0) {
433 oprintf(_("Configured log level is less then 0. Clamped to 0.\n"));
434 return 0;
436 if (raw > ILL_ALL) {
437 oprintf(_("Configured log level is greater then %1$u. "
438 "Clamped to %1$u.\n"), ILL_ALL);
439 return ILL_ALL;
441 if (raw > UINT_MAX ) {
442 oprintf(_("Configured log level is greater then %1$u. "
443 "Clamped to %1$u.\n"), UINT_MAX);
444 return UINT_MAX;
446 return (unsigned int) raw;
450 static int shi_init(const char *config_file) {
451 isds_error err;
452 char *value;
453 unsigned int timeout, log_level;
454 isds_log_facility log_facility = ILF_NONE;
456 oprintf(_("This is Shigofumi, an ISDS client. "
457 "Have a nice e-government.\n"));
459 /* Do not permute arguments in getopt() */
460 if (setenv("POSIXLY_CORRECT", "", 1)) {
461 fprintf(stderr,
462 _("Could not set POSIXLY_CORRECT environment variable\n"));
463 return -1;
466 /* Load configuration */
467 if (shi_load_configuration(config_file))
468 return -1;
469 timeout = normalize_timeout(cfg_getint(configuration, CONFIG_TIMEOUT));
470 log_level = normalize_log_level(cfg_getint(configuration, CONFIG_LOGLEVEL));
472 /* Init readline */
473 rl_readline_name = "shigofumi";
474 rl_filename_quote_characters = "\\ >";
475 rl_filename_quoting_function = shi_quote_filename;
476 rl_filename_dequoting_function = shi_dequote_filename;
477 rl_char_is_quoted_p = shi_char_is_quoted;
479 /* Initialize ISDS */
480 err = isds_init();
481 if (err) {
482 fprintf(stderr, _("Could not initialize libisds library: %s\n"),
483 isds_strerror(err));
484 return -1;
487 /* Set ISDS logging */
488 value = cfg_getstr(configuration, CONFIG_LOGFILE);
489 if (do_log_to_file(value))
490 return -1;
491 for (int i = 0; i < cfg_size(configuration, CONFIG_LOGFACILITIES); i++) {
492 if (add_log_facility(&log_facility,
493 cfg_getnstr(configuration, CONFIG_LOGFACILITIES, i)))
494 return -1;
497 isds_set_logging(log_facility, log_level);
499 /* Set ISDS context up */
500 cisds = isds_ctx_create();
501 if (!cisds) {
502 fprintf(stderr, _("Could not create ISDS context\n"));
503 return -1;
505 err = isds_set_timeout(cisds, timeout);
506 if (err) {
507 fprintf(stderr, _("Could not set ISDS network timeout: %s\n"),
508 isds_strerror(err));
510 err = isds_set_progress_callback(cisds, shi_progressbar, NULL);
511 if (err) {
512 fprintf(stderr, _("Could not register network progress bar: %s: %s\n"),
513 isds_strerror(err), isds_long_message(cisds));
515 err = isds_set_opt(cisds, IOPT_NORMALIZE_MIME_TYPE,
516 cfg_getbool(configuration, CONFIG_NORMALIZEMIMETYPE));
517 if (err) {
518 fprintf(stderr,
519 cfg_getbool(configuration, CONFIG_NORMALIZEMIMETYPE) ?
520 _("Could not enable MIME type normalization: %s: %s\n") :
521 _("Could not disable MIME type normalization: %s: %s\n"),
522 isds_strerror(err), isds_long_message(cisds));
524 if (!cfg_getbool(configuration, CONFIG_VERIFYSERVER)) {
525 oprintf(_("Warning: Shigofumi disabled server identity verification "
526 "on user request!\n"));
527 err = isds_set_opt(cisds, IOPT_TLS_VERIFY_SERVER, 0);
528 if (err) {
529 fprintf(stderr,
530 _("Could not disable server identity verification: "
531 "%s: %s\n"),
532 isds_strerror(err), isds_long_message(cisds));
535 if ((value = cfg_getstr(configuration, CONFIG_CAFILE))) {
536 err = isds_set_opt(cisds, IOPT_TLS_CA_FILE, value);
537 if (err) {
538 fprintf(stderr,
539 _("Could not set file with CA certificates: %s: %s: %s\n"),
540 value, isds_strerror(err), isds_long_message(cisds));
543 if ((value = cfg_getstr(configuration, CONFIG_CADIRECTORY))) {
544 err = isds_set_opt(cisds, IOPT_TLS_CA_DIRECTORY, value);
545 if (err) {
546 fprintf(stderr,
547 _("Could not set directory with CA certificates: "
548 "%s: %s: %s\n"),
549 value, isds_strerror(err), isds_long_message(cisds));
552 if ((value = cfg_getstr(configuration, CONFIG_CRLFILE))) {
553 err = isds_set_opt(cisds, IOPT_TLS_CRL_FILE, value);
554 if (err) {
555 fprintf(stderr, _("Could not set file with CRL: %s: %s: %s\n"),
556 value, isds_strerror(err), isds_long_message(cisds));
561 /* Set Czech POINT context up */
562 czechpoint = isds_ctx_create();
563 if (!czechpoint) {
564 fprintf(stderr, _("Could not create Czech POINT context\n"));
565 return -1;
567 err = isds_set_timeout(czechpoint, timeout);
568 if (err) {
569 fprintf(stderr, _("Could not set Czech POINT network timeout: %s\n"),
570 isds_strerror(err));
572 err = isds_set_progress_callback(czechpoint, shi_progressbar, NULL);
573 if (err) {
574 fprintf(stderr, "Could not register network progress bar: %s: %s\n",
575 isds_strerror(err), isds_long_message(cisds));
578 return 0;
582 static int shi_quit(int argc, const char **argv) {
583 shi_exit(EXIT_SUCCESS);
584 return 0;
589 static void shi_help_usage(const char *command) {
590 oprintf(_(
591 "Usage: %s [COMMAND]\n"
592 "Show COMMAND manual or list of currently available commands.\n"
594 command);
598 static int shi_help(int argc, const char **argv) {
599 size_t command_width = 14;
600 int i;
602 if (!commands) {
603 fprintf(stderr, _("No command is available\n"));
604 return -1;
607 if (argc == 2 && argv[1] && *argv[1]) {
608 /* Show usage for given command */
609 for (i = 0; (*commands)[i].name; i++) {
610 if (!strcmp((*commands)[i].name, argv[1])) {
611 if ((*commands)[i].usage)
612 (*commands)[i].usage((*commands)[i].name);
613 else if ((*commands)[i].description) {
614 ohprint((*commands)[i].name, command_width);
615 oprintf(" %s\n", _((*commands)[i].description));
617 else
618 fprintf(stderr,
619 _("%s: %s: Command description not defined\n"),
620 argv[0], argv[1]);
621 return 0;
624 fprintf(stderr, _("%s: %s: No such command exists\n"), argv[0], argv[1]);
625 return -1;
628 /* Or list all commands */
629 oprintf(_("Following commands are available:\n"));
630 for (i = 0; (*commands)[i].name; i++) {
631 ohprint((*commands)[i].name, command_width);
632 oprintf(" %s\n", _((*commands)[i].description));
635 return 0;
639 static void show_version(void) {
640 char *libisds_version = isds_version();
642 oprintf(_("This is Shigofumi version %s.\n"), PACKAGE_VERSION);
643 oprintf(_("\n"
644 "Used libraries\n"
645 "confuse: %s\n"
646 "libisds: %s\n"
648 confuse_version, libisds_version);
649 #if HAVE_DECL_MAGIC_VERSION
650 oprintf(_("libmagic: %d\n"), magic_version());
651 #endif
652 oprintf(_("libxml2: %s\n"
653 "Readline: %s\n"
655 xmlParserVersion, rl_library_version);
656 free(libisds_version);
660 static int shi_version(int argc, const char **argv) {
661 show_version();
662 oprintf(_(
663 "\n"
664 "-----\n"
665 "It's a shigofumi. A letter delivered from the afterlife. (Fumika)\n"
666 "A message can not be delivered to dead person. (ISDS specification)\n"
667 "Virtual and real world. They can be compatible. (Program author)\n"
669 return 0;
673 static int shi_copying(int argc, const char **argv) {
674 oprintf(_(
675 "This is Shigofumi, an ISDS client.\n"
676 "Copyright (C) 2010, 2011, 2012, 2013 Petr Pisar\n"
677 "\n"
678 "This program is free software: you can redistribute it and/or modify\n"
679 "it under the terms of the GNU General Public License as published by\n"
680 "the Free Software Foundation, either version 3 of the License, or\n"
681 "(at your option) any later version.\n"
682 "\n"
683 "This program is distributed in the hope that it will be useful,\n"
684 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
685 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
686 "GNU General Public License for more details.\n"
687 "\n"
688 "You should have received a copy of the GNU General Public License\n"
689 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n"
691 return 0;
695 static int shi_cache(int argc, const char **argv) {
696 const struct isds_list *item;
697 size_t i;
699 if (boxes) {
700 for (item = boxes, i = 0; item; item = item->next, i++);
701 oprintf(_(
702 "Cached box list: %zu\n"),
706 if (messages) {
707 oprintf(_(
708 "Cached message list:\n"
709 "\tDirection: %s\n"
710 "\tMessages: %'lu\n"),
711 (messages_are_outgoing) ? _("Outgoing") : _("Incoming"),
712 total_messages);
715 if (message) {
716 oprintf(_("Cached message: %s\n"),
717 (message->envelope && message->envelope->dmID) ?
718 message->envelope->dmID : _("<Unknown ID>"));
721 return 0;
725 static void shi_chdir_usage(const char *command) {
726 oprintf(_(
727 "Usage: %s [DIRECTORY]\n"
728 "Change working directory to DIRECTORY.\n"
729 "If no DIRECTORY is supplied, HOME directory will be used.\n"),
730 command);
734 static int shi_chdir(int argc, const char **argv) {
735 const char *directory = NULL;
737 if (!argv || argc > 2) {
738 shi_chdir_usage((argv) ? argv[0] : NULL);
739 return -1;
742 if (argc == 2 && argv[1] && *argv[1])
743 directory = argv[1];
744 else {
745 directory = getenv("HOME");
746 if (!directory) {
747 oprintf("Environment variable HOME does not exist\n");
748 return -1;
751 if (chdir(directory)) {
752 oprintf(_("Could not change working directory: %s: %s\n"), directory,
753 strerror(errno));
754 return -1;
757 return 0;
761 static int shi_pwd(int argc, const char **argv) {
762 char *buffer = NULL, *newbuffer;
763 size_t length = 0;
765 while (length += 1024) {
766 newbuffer = realloc(buffer, length);
767 if (!newbuffer) {
768 fprintf(stderr, _("Error: Not enough memory\n"));
769 free(buffer);
770 return -1;
772 buffer = newbuffer;
774 if (getcwd(buffer, length)) {
775 oprintf("%s\n", buffer);
776 free(buffer);
777 return 0;
781 fprintf(stderr, _("Error: Current directory string is too long\n"));
782 free(buffer);
783 return -1;
787 /* Deallocate *@destination and duplicate @new_value if non-NULL. In case of
788 * error, it prints error message and returns -1. Otherwise it returns 0. */
789 static int replace_string(char **destination, const char *new_value) {
790 if (destination == NULL) return -1;
791 zfree(*destination);
792 if (new_value != NULL) {
793 *destination = strdup(new_value);
794 if (*destination == NULL) {
795 fprintf(stderr, _("Not enough memory\n"));
796 return -1;
799 return 0;
803 /* Convert name of OTP authentication method into ISDS method type.
804 * Return -1 in case of invalid name */
805 static int string2otp_method(const char *name, isds_otp_method *method) {
806 if (!name || !method) { return -1; }
807 if (!strcasecmp(name, "HOTP")) {
808 *method = OTP_HMAC;
809 } else if (!strcasecmp(name, "TOTP")) {
810 *method = OTP_TIME;
811 } else {
812 return -1;
814 return 0;
818 /* Log-in to ISDS.
819 * Return -1 in case of failure, 0 in case of success, +1 in case of partial
820 * success (e.g. TOTP preauthentication to obtain new code succeeded, but user
821 * is still not logged in because second phase is necessary). */
822 static int do_login(void) {
823 isds_error err;
824 struct isds_pki_credentials pki;
825 struct isds_otp otp;
827 /* Build OTP structure */
828 if (NULL != otp_method) {
829 if (string2otp_method(otp_method, &otp.method)) {
830 fprintf(stderr, _("Error: Invalid one-time password "
831 "authentication method `%s'\n"), otp_method);
832 return -1;
836 /* Announce base URL */
837 oprintf(_("ISDS base URL: %s\n"),
838 (server == NULL) ? _("<default>") : server);
840 if (batch_mode) {
841 oprintf(_("Unattended mode detected. "
842 "Make sure credentials have been preset.\n"));
843 } else {
844 oprintf(_("You are going to insert credentials for your account.\n"
845 "Leave blank line to choose default value.\n"));
847 select_completion(COMPL_NONE);
849 /* Ask for user name if not predefined */
850 if (NULL == username) {
851 shi_ask_for_string(&username, _("Input ISDS user name: "),
852 cfg_getstr(configuration, CONFIG_USERNAME), batch_mode);
855 /* Ask for password */
856 shi_ask_for_password(&password, _("Input ISDS password: "),
857 cfg_getstr(configuration, CONFIG_PASSWORD), batch_mode);
859 /* Ask for key password if PKI authentication requested */
860 if (NULL != pki_key_path) {
861 shi_ask_for_password(&key_password, _("Input private key password: "),
862 cfg_getstr(configuration, CONFIG_KEY_PASSWORD), batch_mode);
865 /* Ask for OTP code if OTP authentication requested */
866 if (NULL != otp_method) {
867 shi_ask_for_password(&otp_code,
868 (otp.method == OTP_TIME) ?
869 _("Input one-time code (empty to send new one): ") :
870 _("Input one-time code: "),
871 cfg_getstr(configuration, CONFIG_OTP_CODE), batch_mode);
872 otp.otp_code = otp_code;
875 select_completion(COMPL_COMMAND);
876 set_prompt(NULL);
878 /* Build PKI structure */
879 if (NULL != pki_certificate_path || NULL != pki_key_path) {
880 pki.engine = pki_engine;
881 if (NULL == pki_certificate_format) {
882 fprintf(stderr, _("Error: No certficate format supplied\n"));
883 return -1;
885 if (string2pki_format(pki_certificate_format, &pki.certificate_format)) {
886 fprintf(stderr, _("Error: Invalid certificate format `%s'\n"),
887 pki_certificate_format);
888 return -1;
890 if (NULL == pki_key_format) {
891 fprintf(stderr, _("Error: No private key format supplied\n"));
892 return -1;
894 if (string2pki_format(pki_key_format, &pki.key_format)) {
895 fprintf(stderr, _("Error: Invalid private key format `%s'\n"),
896 pki_key_format);
897 return -1;
899 pki.certificate = pki_certificate_path;
900 pki.key = pki_key_path;
901 pki.passphrase = key_password;
904 if (NULL != otp_method && OTP_TIME == otp.method && NULL == otp_code)
905 printf(_("Requesting one-time code from server for "
906 "a login...\n"));
907 else
908 printf(_("Logging in...\n"));
909 err = isds_login(cisds, server, username, password,
910 (NULL != pki_certificate_path || NULL != pki_key_path) ?
911 &pki : NULL,
912 (NULL != otp_method) ? &otp : NULL);
913 if (NULL != otp_method && OTP_TIME == otp.method && NULL == otp_code)
914 finish_isds_operation_with_code(cisds, err, IE_PARTIAL_SUCCESS);
915 else
916 finish_isds_operation(cisds, err);
918 if (IE_PARTIAL_SUCCESS == err) {
919 printf(_("OTP code has been sent by ISDS successfully.\n"
920 "Once you receive the code, retry log-in with "
921 "the code.\n"));
922 return 1;
923 } else if (err) {
924 printf(_("Log-in failed\n"));
925 return -1;
928 oprintf(_("Logged in.\n"));
929 return 0;
933 static struct isds_DbOwnerInfo *do_box(void) {
934 isds_error err;
935 struct isds_DbOwnerInfo *box = NULL;
937 printf(_("Getting box details you are logged in...\n"));
938 err = isds_GetOwnerInfoFromLogin(cisds, &box);
939 finish_isds_operation(cisds, err);
941 return box;
945 static int shi_box(int argc, const char **argv) {
946 struct isds_DbOwnerInfo *box = NULL;
948 box = do_box();
949 if (!box) return -1;
951 format_DbOwnerInfo(box);
953 isds_DbOwnerInfo_free(&box);
954 return 0;
958 /* Get info about box with @id.
959 * @id is UTF-8 encoded
960 * Return NULL in case of error, otherwise box description that caller must
961 * free. */
962 static struct isds_DbOwnerInfo *stat_box(const char *id) {
963 isds_error err;
964 struct isds_DbOwnerInfo criteria;
965 struct isds_list *boxes = NULL, *item;
966 struct isds_DbOwnerInfo *box = NULL;
967 char *id_locale = NULL;
969 if (!id || !*id) return NULL;
971 id_locale = utf82locale(id);
972 memset(&criteria, 0, sizeof(criteria));
973 criteria.dbID = (char *) id;
975 printf(_("Getting details about box with ID `%s'...\n"), id_locale);
976 err = isds_FindDataBox(cisds, &criteria, &boxes);
977 finish_isds_operation(cisds, err);
978 if (err) goto leave;
980 for(item = boxes; item; item = item->next) {
981 if (!item->data) continue;
983 if (item->next) {
984 fprintf(stderr, _("Error: More boxes match ID `%s'\n"), id_locale);
985 goto leave;
988 box = (struct isds_DbOwnerInfo *) item->data;
989 item->data = NULL;
990 break;
993 leave:
994 free(id_locale);
995 isds_list_free(&boxes);
997 return box;
1001 /* Obtain box ID value either from locale-encoded argument or from current box
1002 * the context is logged in.
1003 * @arg is locale-encoded box ID or NULL
1004 * @box_id outputs pointer to reallocated UTF-8 encoded box ID. Pass NULL if
1005 * you are not interreted in it. Will be NULLed on error.
1006 * @box_id_locale outputs pointer to reallocated locale-encoded box ID. Pass
1007 * NULL if you are not interrested in it. Will be NULLed on error.
1008 * @return 0 on sucess, -1 on error. */
1009 static int get_current_box_id(const char *arg,
1010 char **box_id, char **box_id_locale) {
1011 struct isds_DbOwnerInfo *box = NULL;
1013 if (NULL != box_id) zfree(*box_id);
1014 if (NULL != box_id_locale) zfree(*box_id_locale);
1016 if (NULL == arg || '\0' == *arg) {
1017 /* Get current box ID */
1018 box = do_box();
1019 if (NULL == box || NULL == box->dbID || !*box->dbID) {
1020 isds_DbOwnerInfo_free(&box);
1021 fprintf(stderr, _("Could not get current box ID\n"));
1022 goto error;
1024 if (NULL != box_id) {
1025 *box_id = strdup(box->dbID);
1026 if (NULL == *box_id) {
1027 fprintf(stderr, _("Not enough memory\n"));
1028 goto error;
1031 if (NULL != box_id_locale) {
1032 *box_id_locale = utf82locale(box->dbID);
1033 if (NULL == *box_id_locale) {
1034 fprintf(stderr, _("Could not convert box ID to locale\n"));
1035 goto error;
1038 } else {
1039 /* Box ID supplied as argument */
1040 if (NULL != box_id_locale) {
1041 *box_id_locale = strdup(arg);
1042 if (NULL == *box_id_locale) {
1043 fprintf(stderr, _("Not enough memory\n"));
1044 goto error;
1047 if (NULL != box_id) {
1048 *box_id = locale2utf8(arg);
1049 if (NULL == *box_id) {
1050 fprintf(stderr, _("Could not convert box ID `%s' to UTF-8\n"),
1051 arg);
1052 goto error;
1057 return 0;
1059 error:
1060 if (NULL != box_id) zfree(*box_id);
1061 if (NULL != box_id_locale) zfree(*box_id_locale);
1062 return -1;
1066 static void shi_commercialcredit_usage(const char *command) {
1067 oprintf(_(
1068 "Usage: %s [OPTION...] [BOX_ID]\n"
1069 "Retrieve details and history of a credit available for sending commercial\n"
1070 "messages from a box with ID BOX_ID. Default is box you are logged in.\n"
1071 "Options require a date argument:\n"
1072 " -f list history from date inclusive (locale or full ISO 8601 date)\n"
1073 " -t list history to date inclusive (locale or full ISO 8601 date)\n"
1074 ), command);
1078 /* Retrieve details about credit for sending commercial messages */
1079 static int shi_commercialcredit(int argc, const char **argv) {
1080 isds_error err;
1081 struct tm *from_date = NULL, *to_date = NULL;
1082 long int current_credit;
1083 char *notification_email = NULL;
1084 struct isds_list *history = NULL, *item;
1085 int opt;
1086 char *box_id = NULL, *box_id_locale = NULL;
1087 int ordinar;
1088 int retval = 0;
1090 optind = 0;
1091 while ((opt = getopt(argc, (char * const *)argv, "f:ht:")) != -1) {
1092 switch (opt) {
1093 case 'f':
1094 free(from_date);
1095 from_date = datestring2tm(optarg);
1096 if (NULL == from_date) {
1097 fprintf(stderr, _("Error: Could not parse date: %s\n"),
1098 optarg);
1099 retval = -1;
1100 goto leave;
1102 break;
1103 case 'h':
1104 shi_commercialcredit_usage((argv)?argv[0]:NULL);
1105 goto leave;
1106 case 't':
1107 free(to_date);
1108 to_date = datestring2tm(optarg);
1109 if (NULL == to_date) {
1110 fprintf(stderr, _("Error: Could not parse date: %s\n"),
1111 optarg);
1112 retval = -1;
1113 goto leave;
1115 break;
1116 default:
1117 shi_commercialcredit_usage((argv)?argv[0]:NULL);
1118 retval = -1;
1119 goto leave;
1122 if (optind + 1 < argc) {
1123 fprintf(stderr, _("Bad invocation\n"));
1124 shi_commercialcredit_usage((argv)?argv[0]:NULL);
1125 retval = -1;
1126 goto leave;
1129 if ((retval = get_current_box_id(argv[optind], &box_id, &box_id_locale))) {
1130 goto leave;
1133 printf(_("Querying `%s' box credit details...\n"),
1134 box_id_locale);
1135 err = isds_get_commercial_credit(cisds, box_id, from_date, to_date,
1136 &current_credit, &notification_email, &history);
1137 finish_isds_operation(cisds, err);
1139 if (!err) {
1140 oprintf(_("Details of credit for sending commercial messages "
1141 "from box `%s':\n"), box_id_locale);
1142 print_header_currency(_("Current credit"), &current_credit);
1143 print_header_utf8(_("Notification e-mail"), notification_email);
1145 if (NULL != history) {
1146 oprintf(_("History of credit change events:\n"));
1147 print_header_tm(_("From"), from_date);
1148 print_header_tm(_("To"), to_date);
1149 for (item = history, ordinar = 0; item; item=item->next) {
1150 if (!item->data) continue;
1151 ordinar++;
1152 oprintf(_("\n* Event #%d:\n"), ordinar);
1153 format_credit_event(item->data);
1155 if (ordinar == 0)
1156 oprintf(_("No event exists.\n"));
1158 } else {
1159 oprintf(_("Could not get details about a credit available for sending "
1160 "commercial messages\n"
1161 "from box `%s'.\n"),
1162 box_id_locale);
1163 retval = -1;
1166 leave:
1167 isds_list_free(&history);
1168 free(notification_email);
1169 free(from_date);
1170 free(to_date);
1171 free(box_id);
1172 free(box_id_locale);
1173 return retval;
1177 static void shi_commercialreceiving_usage(const char *command) {
1178 oprintf(_(
1179 "Usage: %s [-0|-1] [BOX_ID]\n"
1180 "Manipulate commercial receiving box status.\n"
1181 " -O switch off receiving of commercial messages\n"
1182 " -1 switch on receiving of commercial messages\n"
1183 " BOX_ID affects box with ID BOX_ID; default is box you are logged in\n"
1184 "If no option is given, show current commercial receiving status.\n"),
1185 command);
1189 /* Manipulate commercial receiving box status */
1190 static int shi_commercialreceiving(int argc, const char **argv) {
1191 isds_error err;
1192 int opt;
1193 int action = -1;
1194 char *box_id = NULL, *box_id_locale = NULL;
1195 int retval = 0;
1197 optind = 0;
1198 while ((opt = getopt(argc, (char * const *)argv, "01")) != -1) {
1199 switch (opt) {
1200 case '0':
1201 action = 0;
1202 break;
1203 case '1':
1204 action = 1;
1205 break;
1206 default:
1207 shi_commercialreceiving_usage((argv)?argv[0]:NULL);
1208 return -1;
1211 if (optind + 1 < argc) {
1212 fprintf(stderr, _("Bad invocation\n"));
1213 shi_commercialreceiving_usage((argv)?argv[0]:NULL);
1214 return -1;
1217 if ((retval = get_current_box_id(argv[optind], &box_id, &box_id_locale))) {
1218 goto leave;
1221 if (action == -1) {
1222 struct isds_DbOwnerInfo *box = stat_box(box_id);
1223 if (!box) {
1224 fprintf(stderr, _("Could not get details about box ID `%s'\n"),
1225 box_id_locale);
1226 retval = -1;
1227 goto leave;
1230 oprintf(_("Commercial receiving status of box `%s': "), box_id_locale);
1231 if (!box->dbOpenAddressing)
1232 oprintf(_("Unknown\n"));
1233 else if (*box->dbOpenAddressing)
1234 oprintf(_("Positive\n"));
1235 else
1236 oprintf(_("Negative\n"));
1237 isds_DbOwnerInfo_free(&box);
1238 } else {
1239 char *refnumber = NULL;
1240 printf((action) ?
1241 _("Switching `%s' box commercial receiving on...\n"):
1242 _("Switching `%s' box commercial receiving off...\n"),
1243 box_id_locale);
1244 err = isds_switch_commercial_receiving(cisds, box_id, action,
1245 NULL, &refnumber);
1246 finish_isds_operation(cisds, err);
1248 if (!err) {
1249 char *refnumber_locale = utf82locale(refnumber);
1250 oprintf(_("Commercial receiving status successfully changed. "
1251 "Assigned reference number: %s\n"),
1252 refnumber_locale);
1253 free(refnumber_locale);
1254 } else {
1255 oprintf(_("Commercial receiving status has not been changed.\n"));
1256 retval = -1;
1258 free(refnumber);
1261 leave:
1262 free(box_id);
1263 free(box_id_locale);
1264 return retval;
1268 static void shi_commercialsending_usage(const char *command) {
1269 oprintf(_(
1270 "Usage: %s [BOX_ID]\n"
1271 "Retrieve permissions to send commercial messages from a box.\n"
1272 " BOX_ID query permissions for box with ID BOX_ID; default is box you\n"
1273 " are logged in\n"),
1274 command);
1278 /* Retrieve permissions to send commercial messages */
1279 static int shi_commercialsending(int argc, const char **argv) {
1280 isds_error err;
1281 struct isds_DbOwnerInfo *box = NULL;
1282 struct isds_list *permissions = NULL, *item;
1283 int opt;
1284 char *box_id = NULL, *box_id_locale = NULL;
1285 int ordinar;
1286 int retval = 0;
1288 optind = 0;
1289 while ((opt = getopt(argc, (char * const *)argv, "h")) != -1) {
1290 switch (opt) {
1291 case 'h':
1292 shi_commercialsending_usage((argv)?argv[0]:NULL);
1293 return 0;
1294 default:
1295 shi_commercialsending_usage((argv)?argv[0]:NULL);
1296 return -1;
1299 if (optind + 1 < argc) {
1300 fprintf(stderr, _("Bad invocation\n"));
1301 shi_commercialsending_usage((argv)?argv[0]:NULL);
1302 return -1;
1305 if ((retval = get_current_box_id(argv[optind], &box_id, &box_id_locale))) {
1306 return -1;
1309 printf(_("Querying `%s' box commercial sending permissions...\n"),
1310 box_id_locale);
1311 err = isds_get_commercial_permissions(cisds, box_id, &permissions);
1312 finish_isds_operation(cisds, err);
1314 if (!err) {
1315 oprintf(_("Permissions to send commercial messages from box `%s':\n"),
1316 box_id_locale);
1317 for (item = permissions, ordinar = 0; item; item=item->next) {
1318 if (!item->data) continue;
1319 ordinar++;
1320 oprintf(_("\n* Permission #%d:\n"), ordinar);
1321 format_commercial_permission(item->data);
1323 if (ordinar == 0)
1324 oprintf(_("No permission exists.\n"));
1325 } else {
1326 oprintf(_("Could not list permissions to send commercial messages "
1327 "from box `%s'.\n"), box_id_locale);
1328 retval = -1;
1331 isds_list_free(&permissions);
1332 free(box_id);
1333 free(box_id_locale);
1334 isds_DbOwnerInfo_free(&box);
1335 return retval;
1339 static int shi_user(int argc, const char **argv) {
1340 isds_error err;
1341 struct isds_DbUserInfo *user = NULL;
1343 printf(_("Getting user details you are logged as...\n"));
1344 err = isds_GetUserInfoFromLogin(cisds, &user);
1345 finish_isds_operation(cisds, err);
1346 if (err) return -1;
1348 format_DbUserInfo(user);
1350 isds_DbUserInfo_free(&user);
1351 return 0;
1355 static void shi_users_usage(const char *command) {
1356 oprintf(_(
1357 "Usage: %s BOX_ID\n"
1358 "Get list of users having access to box with BOX_ID.\n"),
1359 command);
1363 static int shi_users(int argc, const char **argv) {
1364 isds_error err;
1365 struct isds_list *users = NULL, *item;
1366 int ordinar;
1368 if (!argv || !argv[1] || !*argv[1]) {
1369 shi_users_usage((argv)?argv[0]:NULL);
1370 return -1;
1373 printf(_("Getting users of box with ID `%s'...\n"), argv[1]);
1374 err = isds_GetDataBoxUsers(cisds, argv[1], &users);
1375 finish_isds_operation(cisds, err);
1376 if (err) return -1;
1378 for (item = users, ordinar = 0; item; item=item->next) {
1379 if (!item->data) continue;
1380 ordinar++;
1381 oprintf(_("\n* User #%d:\n"), ordinar);
1382 format_DbUserInfo(item->data);
1384 if (ordinar == 0)
1385 oprintf(_("Empty list of users returned.\n"));
1387 isds_list_free(&users);
1388 return 0;
1392 static int show_password_expiration(void) {
1393 isds_error err;
1394 struct timeval *expiration = NULL;
1396 err = isds_get_password_expiration(cisds, &expiration);
1397 finish_isds_operation(cisds, err);
1398 if (err) {
1399 fprintf(stderr, "Could not get password expiration time\n");
1400 return -1;
1403 print_header_timeval(_("Your password expires at"), expiration);
1404 free(expiration);
1405 return 0;
1409 /* Change password in ISDS */
1410 static int do_passwd(void) {
1411 char *old_password = NULL;
1412 char *new_password = NULL;
1413 char *new_password2 = NULL;
1414 struct isds_otp otp;
1415 char *refnumber = NULL;
1416 isds_error err = IE_ERROR;
1417 int retval = 0;
1419 if (replace_string(&otp_method, cfg_getstr(configuration, CONFIG_OTP_METHOD)))
1420 return -1;
1421 /* Build OTP structure */
1422 if (NULL != otp_method) {
1423 if (string2otp_method(otp_method, &otp.method)) {
1424 fprintf(stderr, _("Error: Invalid one-time password "
1425 "authentication method `%s'\n"), otp_method);
1426 return -1;
1430 select_completion(COMPL_NONE);
1432 oprintf(_(
1433 "You are going to change your password. If you don't want to change your\n"
1434 "password, insert empty string or EOF.\n"
1435 "\n"
1436 "You will be asked for your current (old) password and then for new password.\n"
1437 "ISDS forces some criteria new password must fulfill. Current rules are:\n"
1438 "\tLength: minimal 8, maximal 32 characters\n"
1439 "\tMust contain at least: 1 upper case letter, 1 lower case letter, 1 digit\n"
1440 "\tAllowed alphabet: [a-z][A-Z][0-9][!#$%%&()*+,-.:=?@[]_{}|~]\n"
1441 "\tMust differ from last 255 passwords\n"
1442 "\tMust not contain user ID\n"
1443 "\tMust not contain sequence of three or more same characters\n"
1444 "\tMust not start with `qwert', `asdgf', or `12345'\n"
1445 "Finally, you must repeat your new password to avoid mistakes.\n"
1446 "After password change will be confirmed, you must log in again as password\n"
1447 "is transmitted to server on each request.\n"
1448 "\n"));
1450 old_password = ask_for_password(_("Old password: "));
1451 if (!old_password || *old_password == '\0') {
1452 fprintf(stderr, _("No password supplied\n"));
1453 goto error;
1456 /* Ask for OTP code if OTP authentication requested */
1457 zfree(otp_code);
1458 if (NULL != otp_method) {
1459 shi_ask_for_password(&otp_code,
1460 (otp.method == OTP_TIME) ?
1461 _("One-time code (empty to send new one): ") :
1462 _("One-time code: "),
1463 cfg_getstr(configuration, CONFIG_OTP_CODE), batch_mode);
1464 otp.otp_code = otp_code;
1468 if (NULL == otp_method || NULL != otp_code) {
1469 new_password = ask_for_password(_("New password: "));
1470 if (!new_password || *new_password == '\0') {
1471 fprintf(stderr, _("No password supplied\n"));
1472 goto error;
1475 new_password2 = ask_for_password(_("Repeat new password: "));
1476 if (!new_password2 || *new_password2 == '\0') {
1477 fprintf(stderr, _("No password supplied\n"));
1478 goto error;
1481 if (strcmp(new_password, new_password2)) {
1482 fprintf(stderr, _("New passwords differ\n"));
1483 goto error;
1487 if (NULL != otp_method && OTP_TIME == otp.method && NULL == otp_code)
1488 printf(_("Requesting one-time code from server for "
1489 "a password change...\n"));
1490 else
1491 printf(_("Changing password...\n"));
1492 err = isds_change_password(cisds, old_password, new_password,
1493 (NULL != otp_method) ? &otp : NULL, &refnumber);
1494 if (NULL != otp_method && OTP_TIME == otp.method && NULL == otp_code)
1495 finish_isds_operation_with_code(cisds, err, IE_PARTIAL_SUCCESS);
1496 else
1497 finish_isds_operation(cisds, err);
1499 if (NULL != refnumber) {
1500 char *refnumber_locale = utf82locale(refnumber);
1501 free(refnumber);
1502 oprintf(_("Assigned reference number: %s\n"), refnumber_locale);
1503 free(refnumber_locale);
1505 if (IE_PARTIAL_SUCCESS == err) {
1506 oprintf(_("OTP code has been sent by ISDS successfully.\n"
1507 "Once you receive the code, retry changing password with "
1508 "the code.\n"));
1509 goto leave;
1510 } else if (err) {
1511 printf(_("Password change failed\n"));
1512 goto error;
1513 } else {
1514 oprintf(_("Password HAS been successfully changed.\n"));
1515 goto leave;
1518 error:
1519 retval = -1;
1520 oprintf(_("Password has NOT been changed!\n"));
1522 leave:
1523 free(old_password);
1524 free(new_password);
1525 free(new_password2);
1526 zfree(otp_code);
1528 set_prompt(NULL);
1529 select_completion(COMPL_COMMAND);
1531 oprintf(_("\n"
1532 "Remember, ISDS password has limited life time.\n"));
1533 return retval;
1537 static void shi_passwd_usage(const char *command) {
1538 oprintf(_(
1539 "Usage: %s [-S]\n"
1540 "Manipulate user password or change it if no option given.\n"
1541 "\n"
1542 "Options:\n"
1543 " -S show password expiration time\n"
1544 ), command);
1548 static int shi_passwd(int argc, const char **argv) {
1549 int opt;
1551 optind = 0;
1552 while ((opt = getopt(argc, (char * const *)argv, "S")) != -1) {
1553 switch (opt) {
1554 case 'S':
1555 return show_password_expiration();
1556 default:
1557 shi_passwd_usage(argv[0]);
1558 return -1;
1561 if (optind != argc || argc > 1) {
1562 fprintf(stderr, _("Bad invocation\n"));
1563 shi_passwd_usage((argv)?argv[0]:NULL);
1564 return -1;
1567 return do_passwd();
1571 static void shi_login_usage(const char *command) {
1572 oprintf(_(
1573 "Usage: %s [OPTIONS] [USER_NAME]\n"
1574 "Attemp to log into ISDS server.\n"
1575 "\n"
1576 "Options:\n"
1577 " -b URL ISDS server base URL\n"
1578 " -c IDENTIFIER user certificate\n"
1579 " -C FORMAT user certificate format\n"
1580 " -k IDENTIFIER user private key\n"
1581 " -K FORMAT user private key format\n"
1582 " -e IDENTIFIER cryptographic engine\n"
1583 " -o METHOD use one-time password authentication method\n"
1584 "\n"
1585 "Recognized certificate and key FORMATS are:\n"
1586 " PEM Base64 encoded serialization in local file\n"
1587 " DER binary serialization in local file\n"
1588 " ENG material is stored in cryptographic engine\n"
1589 "Identifiers of cryptographic engine, certificate, and private key are\n"
1590 "specific for underlying cryptographic library.\n"
1591 "\n"
1592 "Recognized one-time password methods are:\n"
1593 " HOTP HMAC-based OTP method\n"
1594 " TOTP time-based OTP method\n"
1595 "\n"
1596 "Values of omitted options are taken from configuration file.\n"
1597 ), command);
1601 static int shi_login(int argc, const char **argv) {
1602 int opt;
1603 int status;
1605 discard_credentials();
1607 /* Load stored configuration */
1608 if (replace_string(&server, cfg_getstr(configuration, CONFIG_SERVER)))
1609 return -1;
1610 if (replace_string(&otp_method, cfg_getstr(configuration, CONFIG_OTP_METHOD)))
1611 return -1;
1612 if (replace_string(&pki_engine,
1613 cfg_getstr(configuration, CONFIG_KEY_ENGINE)))
1614 return -1;
1615 if (replace_string(&pki_certificate_path,
1616 cfg_getstr(configuration, CONFIG_CERT_PATH)))
1617 return -1;
1618 if (replace_string(&pki_certificate_format,
1619 cfg_getstr(configuration, CONFIG_CERT_FORMAT)))
1620 return -1;
1621 if (replace_string(&pki_key_path,
1622 cfg_getstr(configuration, CONFIG_KEY_PATH)))
1623 return -1;
1624 if (replace_string(&pki_key_format,
1625 cfg_getstr(configuration, CONFIG_KEY_FORMAT)))
1626 return -1;
1628 /* Override configuration with positional arguments */
1629 optind = 0;
1630 while ((opt = getopt(argc, (char * const *)argv, "b:c:C:k:K:e:o:")) != -1) {
1631 switch (opt) {
1632 case 'b':
1633 if (replace_string(&server, optarg)) return -1;
1634 break;
1635 case 'c':
1636 if (replace_string(&pki_certificate_path, optarg)) return -1;
1637 break;
1638 case 'C':
1639 if (replace_string(&pki_certificate_format, optarg)) return -1;
1640 break;
1641 case 'k':
1642 if (replace_string(&pki_key_path, optarg)) return -1;
1643 break;
1644 case 'K':
1645 if (replace_string(&pki_key_format, optarg)) return -1;
1646 break;
1647 case 'e':
1648 if (replace_string(&pki_engine, optarg)) return -1;
1649 break;
1650 case 'o':
1651 if (replace_string(&otp_method, optarg)) return -1;
1652 break;
1653 default:
1654 shi_login_usage(argv[0]);
1655 return -1;
1658 if (optind < argc - 1) {
1659 fprintf(stderr, _("Bad invocation\n"));
1660 shi_login_usage(argv[0]);
1661 return -1;
1663 if (optind == argc - 1) {
1664 username = strdup(argv[optind]);
1667 /* Proceed log-in */
1668 status = do_login();
1669 if (status < 0) return -1;
1671 /* If log-in passed, store configuration */
1672 cfg_setstr(configuration, CONFIG_SERVER, server);
1673 cfg_setstr(configuration, CONFIG_USERNAME, username);
1674 cfg_setstr(configuration, CONFIG_PASSWORD, password);
1675 cfg_setstr(configuration, CONFIG_KEY_PASSWORD, key_password);
1676 cfg_setstr(configuration, CONFIG_OTP_METHOD, otp_method);
1677 cfg_setstr(configuration, CONFIG_KEY_ENGINE, pki_engine);
1678 cfg_setstr(configuration, CONFIG_CERT_PATH, pki_certificate_path);
1679 cfg_setstr(configuration, CONFIG_CERT_FORMAT, pki_certificate_format);
1680 cfg_setstr(configuration, CONFIG_KEY_PATH, pki_key_path);
1681 cfg_setstr(configuration, CONFIG_KEY_FORMAT, pki_key_format);
1683 /* Get some details only if fully logged in */
1684 if (0 == status) {
1685 show_password_expiration();
1687 return 0;
1691 static void shi_debug_usage(const char *command) {
1692 oprintf(_(
1693 "Usage: %s -l LEVEL [-f FACILITY...] [{-e | -o FILE}]\n"
1694 "Debug FACILITIES on LEVEL.\n"
1695 "\n"
1696 "-l LEVEL set log level, valid interval <%d,%d>, default is %d\n"
1697 " %d is no logging, %d critical, %d errors,\n"
1698 " %d warnings, %d info, %d debug, %d all\n"
1699 "-f FACILITY debug only given facility, repeat this option to debug\n"
1700 " more facilities; valid values: none, http, soap, isds,\n"
1701 " file, sec, xml, all; default is none\n"
1702 "-e write debug log into stderr\n"
1703 "-o FILE append debug log to FILE\n"
1705 command,
1706 ILL_NONE, ILL_ALL, ILL_NONE,
1707 ILL_NONE, ILL_CRIT, ILL_ERR, ILL_WARNING,
1708 ILL_INFO, ILL_DEBUG, ILL_ALL);
1711 static int shi_debug(int argc, const char **argv) {
1712 int opt;
1713 int log_level = ILL_NONE;
1714 isds_log_facility log_facility = ILF_NONE;
1715 char *file = NULL;
1716 _Bool close_log = 0;
1718 optind = 0;
1719 while ((opt = getopt(argc, (char * const *)argv, "l:f:eo:")) != -1) {
1720 switch (opt) {
1721 case 'l':
1722 log_level = normalize_log_level(atoi(optarg));
1723 break;
1724 case 'f':
1725 if (add_log_facility(&log_facility, optarg)) return -1;
1726 break;
1727 case 'e':
1728 close_log = 1;
1729 case 'o':
1730 file = optarg;
1731 break;
1732 default:
1733 shi_debug_usage(argv[0]);
1734 return -1;
1737 if (optind == 1 || optind != argc) {
1738 fprintf(stderr, _("Bad invocation\n"));
1739 shi_debug_usage(argv[0]);
1740 return -1;
1743 /* Redirect log */
1744 if (close_log) {
1745 isds_set_log_callback(NULL, NULL);
1747 if (logger_fd != -1) {
1748 if (-1 == close(logger_fd)) {
1749 fprintf(stderr, _("Closing log file failed: %s\n"),
1750 strerror(errno));
1751 return -1;
1754 cfg_setstr(configuration, CONFIG_LOGFILE, NULL);
1756 if (do_log_to_file(file))
1757 return -1;
1758 if (file) cfg_setstr(configuration, CONFIG_LOGFILE, file);
1760 /* Set log levels */
1761 isds_set_logging(log_facility, log_level);
1762 cfg_setint(configuration, CONFIG_LOGLEVEL, log_level);
1763 save_log_facility(log_facility);
1765 return 0;
1769 static void show_setting_str(const char *variable, _Bool cenzore) {
1770 if (!variable) return;
1772 const char *value = cfg_getstr(configuration, variable);
1774 if (value) {
1775 if (cenzore)
1776 oprintf(_("%s = <set>\n"), variable);
1777 else
1778 oprintf(_("%s = `%s'\n"), variable, value);
1779 } else {
1780 oprintf(_("%s = <unset>\n"), variable);
1785 static void show_setting_boolean(const char *variable) {
1786 if (!variable) return;
1788 _Bool value = cfg_getbool(configuration, variable);
1790 if (value) {
1791 oprintf(_("%s = <true>\n"), variable);
1792 } else {
1793 oprintf(_("%s = <false>\n"), variable);
1798 static void show_setting_int(const char *variable) {
1799 if (!variable) return;
1801 long int value = cfg_getint(configuration, variable);
1803 oprintf(_("%s = %ld\n"), variable, value);
1807 static void show_setting_strlist(const char *variable) {
1808 if (!variable) return;
1810 int length = cfg_size(configuration, variable);
1812 if (length <= 0) {
1813 oprintf(_("%s = <unset>\n"), variable);
1814 } else {
1815 oprintf(_("%s = {"), variable);
1816 for (int i = 0; i < length; i++) {
1817 const char *value = cfg_getnstr(configuration, variable, i);
1818 if (i < length - 1)
1819 oprintf(_("`%s', "), value);
1820 else
1821 oprintf(_("`%s'}\n"), value);
1827 static int shi_settings(int argc, const char **argv) {
1828 /*int opt;
1829 int log_level = ILL_NONE;
1830 isds_log_facility log_facility = ILF_NONE;
1831 char *file = NULL;
1832 _Bool close_log = 0;*/
1835 optind = 0;
1836 while ((opt = getopt(argc, (char * const *)argv, "l:f:eo:")) != -1) {
1837 switch (opt) {
1838 case 'l':
1839 log_level = normalize_log_level(atoi(optarg));
1840 break;
1841 case 'f':
1842 if (add_log_facility(&log_facility, optarg)) return -1;
1843 break;
1844 case 'e':
1845 close_log = 1;
1846 case 'o':
1847 file = optarg;
1848 break;
1849 default:
1850 shi_debug_usage(argv[0]);
1851 return -1;
1854 if (optind == 1 || optind != argc) {
1855 printf(_("Bad invocation\n"));
1856 shi_debug_usage(argv[0]);
1857 return -1;
1861 oprintf(_("Current settings:\n"));
1863 show_setting_str(CONFIG_SERVER, 0);
1864 show_setting_str(CONFIG_USERNAME, 0);
1865 show_setting_str(CONFIG_PASSWORD, 1),
1866 show_setting_str(CONFIG_CERT_FORMAT, 0),
1867 show_setting_str(CONFIG_CERT_PATH, 0),
1868 show_setting_str(CONFIG_KEY_ENGINE, 0),
1869 show_setting_str(CONFIG_KEY_FORMAT, 0),
1870 show_setting_str(CONFIG_KEY_PATH, 0),
1871 show_setting_str(CONFIG_KEY_PASSWORD, 1),
1872 show_setting_str(CONFIG_OTP_METHOD, 0),
1873 show_setting_str(CONFIG_OTP_CODE, 1),
1874 show_setting_boolean(CONFIG_VERIFYSERVER);
1875 show_setting_str(CONFIG_CAFILE, 0);
1876 show_setting_str(CONFIG_CADIRECTORY, 0);
1877 show_setting_boolean(CONFIG_CLEAN_TEMPORARY_FILES);
1878 show_setting_str(CONFIG_CRLFILE, 0);
1879 show_setting_int(CONFIG_TIMEOUT);
1880 show_setting_strlist(CONFIG_LOGFACILITIES);
1881 show_setting_str(CONFIG_LOGFILE, 0);
1882 show_setting_int(CONFIG_LOGLEVEL);
1883 show_setting_boolean(CONFIG_CONFIRM_SEND);
1884 show_setting_boolean(CONFIG_MARKMESSAGEREAD);
1885 show_setting_boolean(CONFIG_NORMALIZEMIMETYPE);
1886 show_setting_strlist(CONFIG_OPENCOMMAND);
1887 show_setting_boolean(CONFIG_OVERWRITEFILES);
1889 return 0;
1893 static void shi_find_box_usage(const char *command) {
1894 oprintf(_(
1895 "Usage: %s {OPTION... | BOX_ID}\n"
1896 "Get information about box with BOX_ID or boxes meeting other criteria.\n"
1897 "Each search option requires an argument:\n"
1898 " -t box type; accepted values:\n"
1899 " FO Private individual\n"
1900 " PFO Self-employed individual\n"
1901 " PFO_ADVOK Lawyer\n"
1902 " PFO_AUDITOR Statutory auditor\n"
1903 " PFO_DANPOR Tax advisor\n"
1904 " PFO_INSSPR Insolvency administrator\n"
1905 " PO Organisation\n"
1906 " PO_ZAK Organization based by law\n"
1907 " PO_REQ Organization based on request\n"
1908 " OVM Public authority\n"
1909 " OVM_EXEKUT Executor\n"
1910 " OVM_NOTAR Notary\n"
1911 " OVM_REQ Public authority based on request\n"
1912 " OVM_FO Private individual listed in the public authority\n"
1913 " index\n"
1914 " OVM_PFO Self-employed individual listed in the public\n"
1915 " authority index\n"
1916 " OVM_PO Organisation listed in the public authority index\n"
1917 " -j identity number\n"
1918 "\n"
1919 "Person name options:\n"
1920 " -f first name\n"
1921 " -m middle name\n"
1922 " -l last name\n"
1923 " -b last name at birth\n"
1924 " -s subject name\n"
1925 "\n"
1926 "Birth options:\n"
1927 " -d birth date (locale or full ISO 8601 date)\n"
1928 " -w birth city\n"
1929 " -y birth county\n"
1930 " -c birth state\n"
1931 "\n"
1932 "Address:\n"
1933 " -W city\n"
1934 " -S street\n"
1935 " -z number in street\n"
1936 " -Z number in municipality\n"
1937 " -P ZIP code\n"
1938 " -C state\n"
1939 "\n"
1940 "Other options:\n"
1941 " -n nationality\n"
1942 " -e e-mail\n"
1943 " -p phone number\n"
1944 " -i identifier\n"
1945 " -r registry code\n"
1946 " -a box status; accepted values:\n"
1947 " ACCESSIBLE Accessible\n"
1948 " TEMP_INACCESSIBLE Temporary inaccessible\n"
1949 " NOT_YET_ACCESSIBLE Not yet accessible\n"
1950 " PERM_INACCESSIBLE Permanently inaccessible\n"
1951 " REMOVED Deleted\n"
1952 " -o act as public authority; boolean values: 0 is false, 1 is true\n"
1953 " -k receive commercial messages; boolean values\n"
1954 "\n"
1955 "Not all option combinations are meaningful or allowed. For example box\n"
1956 "type is always required (except direct box ID query).\n"
1957 "ISDS can refuse to answer to much broad query. Not all boxes are searchable\n"
1958 "by every user.\n"
1960 command);
1964 /* Allow reassignment */
1965 #define FILL_OR_LEAVE(variable, locale) { \
1966 zfree(variable); \
1967 (variable) = locale2utf8(locale); \
1968 if (!(variable)) { \
1969 fprintf(stderr, _("Error: Not enough memory\n")); \
1970 retval = -1; \
1971 goto leave; \
1975 #define CALLOC_OR_LEAVE(structure) { \
1976 if (!(structure)) { \
1977 (structure) = calloc(1, sizeof(*(structure))); \
1978 if (!(structure)) { \
1979 fprintf(stderr, _("Error: Not enough memory\n")); \
1980 retval = -1; \
1981 goto leave; \
1986 #define FILL_BOOLEAN_OR_LEAVE(variable, locale) { \
1987 zfree(variable); \
1988 (variable) = malloc(sizeof(*(variable))); \
1989 if (!(variable)) { \
1990 fprintf(stderr, _("Error: Not enough memory\n")); \
1991 retval = -1; \
1992 goto leave; \
1994 if (!strcmp((locale), "0")) *(variable) = 0; \
1995 else if (!strcmp((locale), "1")) *(variable) = 1; \
1996 else { \
1997 fprintf(stderr, _("%s: %s: Unknown boolean value\n"), \
1998 argv[0], (locale)); \
1999 retval = -1; \
2000 goto leave; \
2004 #define FILL_LONGINT_OR_LEAVE(variable, locale) { \
2005 if (!(locale) || !*(locale)) { \
2006 fprintf(stderr, _("%s: Empty integer value\n"), argv[0]); \
2007 retval = -1; \
2008 goto leave; \
2010 char *endptr; \
2011 zfree(variable); \
2012 (variable) = malloc(sizeof(*(variable))); \
2013 if (!(variable)) { \
2014 fprintf(stderr, _("Error: Not enough memory\n")); \
2015 retval = -1; \
2016 goto leave; \
2018 (*variable) = strtol((locale), &endptr, 0); \
2019 if (*endptr) { \
2020 fprintf(stderr, _("%s: %s: Invalid integer value\n"), \
2021 argv[0], (locale)); \
2022 retval = -1; \
2023 goto leave; \
2027 #define FILL_ULONGINT_OR_LEAVE(variable, locale) { \
2028 long int value; \
2029 if (!(locale) || !*(locale)) { \
2030 fprintf(stderr, _("%s: Empty integer value\n"), argv[0]); \
2031 retval = -1; \
2032 goto leave; \
2034 char *endptr; \
2035 zfree(variable); \
2036 (variable) = malloc(sizeof(*(variable))); \
2037 if (!(variable)) { \
2038 fprintf(stderr, _("Error: Not enough memory\n")); \
2039 retval = -1; \
2040 goto leave; \
2042 value = strtol((locale), &endptr, 0); \
2043 if (*endptr) { \
2044 fprintf(stderr, _("%s: %s: Invalid integer value\n"), \
2045 argv[0], (locale)); \
2046 retval = -1; \
2047 goto leave; \
2049 if (value < 0) { \
2050 fprintf(stderr, _("%s: %s: Negative integer value\n"), \
2051 argv[0], (locale)); \
2052 retval = -1; \
2053 goto leave; \
2055 (*variable) = value; \
2058 static int shi_find_box(int argc, const char **argv) {
2059 int opt;
2060 isds_error err;
2061 struct isds_DbOwnerInfo *criteria = NULL;
2062 struct isds_list *item;
2063 int order = 0;
2064 int retval = 0;
2066 if (!argv || !argv[1] || !*argv[1]) {
2067 fprintf(stderr, _("Error: No argument supplied\n"));
2068 shi_find_box_usage((argv)?argv[0]:NULL);
2069 return -1;
2072 criteria = calloc(1, sizeof(*criteria));
2073 if (!criteria) {
2074 fprintf(stderr, _("Error: Not enough memory\n"));
2075 retval = -1;
2076 goto leave;
2079 /* Parse options */
2080 optind = 0;
2081 while ((opt = getopt(argc, (char * const *)argv, "t:j:s:"
2082 "f:m:l:b:s:" "d:w:y:c:" "W:S:z:Z:P:C:"
2083 "n:e:p:i:r:a:o:k:")) != -1) {
2084 switch (opt) {
2085 case 't':
2086 criteria->dbType = malloc(sizeof(*criteria->dbType));
2087 if (!criteria->dbType) {
2088 fprintf(stderr, _("Error: Not enough memory\n"));
2089 retval = -1;
2090 goto leave;
2092 if (!string2isds_DbType(criteria->dbType, optarg)) {
2093 fprintf(stderr, _("%s: %s: Unknown box type\n"),
2094 argv[0], optarg);
2095 retval = -1;
2096 goto leave;
2098 break;
2100 case 'j':
2101 FILL_OR_LEAVE(criteria->ic, optarg);
2102 break;
2104 /* Person name */
2105 case 'f':
2106 CALLOC_OR_LEAVE(criteria->personName);
2107 FILL_OR_LEAVE(criteria->personName->pnFirstName, optarg);
2108 break;
2109 case 'm':
2110 CALLOC_OR_LEAVE(criteria->personName);
2111 FILL_OR_LEAVE(criteria->personName->pnMiddleName, optarg);
2112 break;
2113 case 'l':
2114 CALLOC_OR_LEAVE(criteria->personName);
2115 FILL_OR_LEAVE(criteria->personName->pnLastName, optarg);
2116 break;
2117 case 'b':
2118 CALLOC_OR_LEAVE(criteria->personName);
2119 FILL_OR_LEAVE(criteria->personName->pnLastNameAtBirth, optarg);
2120 break;
2121 case 's':
2122 FILL_OR_LEAVE(criteria->firmName, optarg);
2123 break;
2125 /* Birth */
2126 case 'd':
2127 CALLOC_OR_LEAVE(criteria->birthInfo);
2128 criteria->birthInfo->biDate = datestring2tm(optarg);
2129 if (!criteria->birthInfo->biDate) {
2130 fprintf(stderr, _("Error: Could not parse date: %s\n"),
2131 optarg);
2132 retval = -1;
2133 goto leave;
2135 break;
2136 case 'w':
2137 CALLOC_OR_LEAVE(criteria->birthInfo);
2138 FILL_OR_LEAVE(criteria->birthInfo->biCity, optarg);
2139 break;
2140 case 'y':
2141 CALLOC_OR_LEAVE(criteria->birthInfo);
2142 FILL_OR_LEAVE(criteria->birthInfo->biCounty, optarg);
2143 break;
2144 case 'c':
2145 CALLOC_OR_LEAVE(criteria->birthInfo);
2146 FILL_OR_LEAVE(criteria->birthInfo->biState, optarg);
2147 break;
2149 /* Address */
2150 case 'W':
2151 CALLOC_OR_LEAVE(criteria->address);
2152 FILL_OR_LEAVE(criteria->address->adCity, optarg);
2153 break;
2154 case 'S':
2155 CALLOC_OR_LEAVE(criteria->address);
2156 FILL_OR_LEAVE(criteria->address->adStreet, optarg);
2157 break;
2158 case 'z':
2159 CALLOC_OR_LEAVE(criteria->address);
2160 FILL_OR_LEAVE(criteria->address->adNumberInStreet, optarg);
2161 break;
2162 case 'Z':
2163 CALLOC_OR_LEAVE(criteria->address);
2164 FILL_OR_LEAVE(criteria->address->adNumberInMunicipality,
2165 optarg);
2166 break;
2167 case 'P':
2168 CALLOC_OR_LEAVE(criteria->address);
2169 FILL_OR_LEAVE(criteria->address->adZipCode, optarg);
2170 break;
2171 case 'C':
2172 CALLOC_OR_LEAVE(criteria->address);
2173 FILL_OR_LEAVE(criteria->address->adState, optarg);
2174 break;
2176 /* Other options */
2177 case 'n':
2178 FILL_OR_LEAVE(criteria->nationality, optarg);
2179 break;
2180 case 'e':
2181 FILL_OR_LEAVE(criteria->email, optarg);
2182 break;
2183 case 'p':
2184 FILL_OR_LEAVE(criteria->telNumber, optarg);
2185 break;
2186 case 'i':
2187 FILL_OR_LEAVE(criteria->identifier, optarg);
2188 break;
2189 case 'r':
2190 FILL_OR_LEAVE(criteria->registryCode, optarg);
2191 break;
2192 case 'a':
2193 criteria->dbState = malloc(sizeof(*criteria->dbState));
2194 if (!criteria->dbState) {
2195 fprintf(stderr, _("Error: Not enough memory\n"));
2196 retval = -1;
2197 goto leave;
2199 if (!strcmp(optarg, "ACCESSIBLE"))
2200 *criteria->dbState = DBSTATE_ACCESSIBLE;
2201 else if (!strcmp(optarg, "TEMP_INACCESSIBLE"))
2202 *criteria->dbState = DBSTATE_TEMP_UNACCESSIBLE;
2203 else if (!strcmp(optarg, "NOT_YET_ACCESSIBLE"))
2204 *criteria->dbState = DBSTATE_NOT_YET_ACCESSIBLE;
2205 else if (!strcmp(optarg, "PERM_INACCESSIBLE"))
2206 *criteria->dbState = DBSTATE_PERM_UNACCESSIBLE;
2207 else if (!strcmp(optarg, "REMOVED"))
2208 *criteria->dbState = DBSTATE_REMOVED;
2209 else {
2210 fprintf(stderr, _("%s: %s: Unknown box status\n"),
2211 argv[0], optarg);
2212 retval = -1;
2213 goto leave;
2215 break;
2216 case 'o':
2217 FILL_BOOLEAN_OR_LEAVE(criteria->dbEffectiveOVM, optarg);
2218 break;
2219 case 'k':
2220 FILL_BOOLEAN_OR_LEAVE(criteria->dbOpenAddressing, optarg);
2221 break;
2223 default:
2224 shi_find_box_usage(argv[0]);
2225 retval = -1;
2226 goto leave;
2230 /* There must be an option and all of them must be recognized, if not only
2231 * BOX_ID supplied */
2232 if (argc > 2 && optind != argc) {
2233 fprintf(stderr, _("Error: Superfluous argument\n"));
2234 shi_find_box_usage(argv[0]);
2235 retval = -1;
2236 goto leave;
2239 /* If only box ID is supplied use it */
2240 if (argc == 2 && argv[1] && *argv[1]) {
2241 criteria->dbID = locale2utf8(argv[1]);
2242 if (!criteria->dbID) {
2243 fprintf(stderr, _("Error: Not enough memory\n"));
2244 retval = -1;
2245 goto leave;
2249 printf(_("Searching boxes...\n"));
2250 err = isds_FindDataBox(cisds, criteria, &boxes);
2251 finish_isds_operation(cisds, err);
2252 if (err) return -1;
2254 for(item = boxes; item; item = item->next) {
2255 if (!item->data) continue;
2256 order++;
2258 oprintf(_("\n* Result #%d:\n"), order);
2259 format_DbOwnerInfo(item->data);
2262 leave:
2263 isds_DbOwnerInfo_free(&criteria);
2264 return retval;
2268 static void shi_search_box_usage(const char *command) {
2269 oprintf(_(
2270 "Usage: %s [OPTION...] QUERY...\n"
2271 "Get list of boxes matching a query string.\n"
2272 "Attribute options:\n"
2273 " -a search in addresses only\n"
2274 " -b search in box identifiers only\n"
2275 " -i search in organization identifiers only\n"
2276 "\n"
2277 "Box type options:\n"
2278 " -t TYPE restrict to a box type; accepted values:\n"
2279 " FO Private individual\n"
2280 " PFO Self-employed individual\n"
2281 " PFO_ADVOK Lawyer\n"
2282 " PFO_AUDITOR Statutory auditor\n"
2283 " PFO_DANPOR Tax advisor\n"
2284 " PFO_INSSPR Insolvency administrator\n"
2285 " PO Organization\n"
2286 " PO_ZAK Organization based by law\n"
2287 " PO_REQ Organization based on request\n"
2288 " OVM Public authority\n"
2289 " OVM_NOTAR Notary\n"
2290 " OVM_EXEKUT Executor\n"
2291 " OVM_REQ Public authority based on request\n"
2292 " OVM_FO Private individual listed in the public\n"
2293 " authority index\n"
2294 " OVM_PFO Self-employed individual listed in the public\n"
2295 " authority index\n"
2296 " OVM_PO Organisation listed in the public authority\n"
2297 " index\n"
2298 "\n"
2299 "Pagination options:\n"
2300 " -p NUMBER request a page with this number (defaults to the first one)\n"
2301 " -s NUMBER request a page with this size\n"
2302 "\n"
2303 "The query arguments are concatenated by a space into one query string.\n"
2304 "If no attribute option is specified, the query string will be searched in\n"
2305 "all of the attributes (address, box ID, organization ID). The server splits\n"
2306 "the query string into words and normalize them according to complex rules\n"
2307 "(see ISDS specification for more details) before the search. Each word\n"
2308 "matches independently, but all of them must exist in a box attributes to\n"
2309 "return the box.\n"
2311 command);
2315 static int shi_search_box(int argc, const char **argv) {
2316 int opt;
2317 isds_error err;
2318 isds_fulltext_target target = FULLTEXT_ALL;
2319 isds_DbType type = DBTYPE_SYSTEM;
2320 unsigned long int *page_size = NULL;
2321 unsigned long int *page_number = NULL;
2322 char *query_locale = NULL;
2323 char *query = NULL;
2324 unsigned long int *total_matching_boxes = NULL;
2325 unsigned long int *current_page_beginning = NULL;
2326 unsigned long int *current_page_size = NULL;
2327 _Bool *last_page = NULL;
2328 struct isds_list *boxes = NULL;
2329 struct isds_list *item;
2330 unsigned long int order = 0;
2331 int retval = 0;
2333 if (!argv || !argv[1] || !*argv[1]) {
2334 fprintf(stderr, _("Error: No argument supplied\n"));
2335 shi_search_box_usage((argv)?argv[0]:NULL);
2336 return -1;
2339 /* Parse options */
2340 optind = 0;
2341 while ((opt = getopt(argc, (char * const *)argv, "abi" "t:" "p:s:"))
2342 != -1) {
2343 switch (opt) {
2344 case 'a':
2345 if (target != FULLTEXT_ALL) {
2346 fprintf(stderr, _("%s: -%c: Another attribute option "
2347 "has already been specified\n"),
2348 argv[0], opt);
2349 retval = -1;
2350 goto leave;
2352 target = FULLTEXT_ADDRESS;
2353 break;
2354 case 'b':
2355 if (target != FULLTEXT_ALL) {
2356 fprintf(stderr, _("%s: -%c: Another attribute option "
2357 "has already been specified\n"),
2358 argv[0], opt);
2359 retval = -1;
2360 goto leave;
2362 target = FULLTEXT_BOX_ID;
2363 break;
2364 case 'i':
2365 if (target != FULLTEXT_ALL) {
2366 fprintf(stderr, _("%s: -%c: Another attribute option "
2367 "has already been specified\n"),
2368 argv[0], opt);
2369 retval = -1;
2370 goto leave;
2372 target = FULLTEXT_IC;
2373 break;
2374 case 't':
2375 if (!string2isds_DbType(&type, optarg)) {
2376 fprintf(stderr, _("%s: %s: Unknown box type\n"),
2377 argv[0], optarg);
2378 retval = -1;
2379 goto leave;
2381 break;
2383 case 'p':
2384 FILL_ULONGINT_OR_LEAVE(page_number, optarg);
2385 break;
2387 case 's':
2388 FILL_ULONGINT_OR_LEAVE(page_size, optarg);
2389 break;
2391 default:
2392 shi_search_box_usage(argv[0]);
2393 retval = -1;
2394 goto leave;
2398 /* All options are optional, there must one non-options argument, the
2399 * query */
2400 if (optind >= argc) {
2401 fprintf(stderr, _("Error: Missing a query argument\n"));
2402 retval = -1;
2403 goto leave;
2406 /* If only box ID is supplied use it */
2407 if (NULL == argv[optind] || '\0' == *argv[optind]) {
2408 fprintf(stderr, _("Error: The query string must be non-empty\n"));
2409 retval = -1;
2410 goto leave;
2413 /* Concatenate arguments into one query string */
2414 for (int i = optind; NULL != argv[i]; i++) {
2415 char *new_query_locale;
2416 new_query_locale = astrcat3(query_locale, " ", argv[i]);
2417 if (NULL == new_query_locale) {
2418 fprintf(stderr, _("Error: Not enough memory\n"));
2419 retval = -1;
2420 goto leave;
2422 free(query_locale);
2423 query_locale = new_query_locale;
2425 FILL_OR_LEAVE(query, query_locale);
2427 printf(_("Searching boxes...\n"));
2428 err = isds_find_box_by_fulltext(cisds,
2429 query, &target, &type, page_size, page_number, 0,
2430 &total_matching_boxes, &current_page_beginning, &current_page_size,
2431 &last_page, &boxes);
2432 finish_isds_operation(cisds, err);
2433 if (err) return -1;
2435 if (NULL != current_page_beginning) {
2436 order = *current_page_beginning;
2438 print_header_ulongint(_("Total matching boxes"), total_matching_boxes);
2439 print_header_ulongint(_("This page size"), current_page_size);
2441 for(item = boxes; item; item = item->next) {
2442 if (!item->data) continue;
2443 order++;
2445 oprintf(_("\n* Result #%lu:\n"), order);
2446 format_isds_fulltext_result(item->data);
2449 if (NULL != last_page) {
2450 if (NULL != boxes)
2451 oprintf("\n");
2452 if (*last_page)
2453 oprintf(_("This is the last page.\n"));
2454 else
2455 oprintf(_("Next pages exist.\n"));
2458 leave:
2459 free(page_size);
2460 free(page_number);
2461 free(query);
2462 free(query_locale);
2463 free(total_matching_boxes);
2464 free(current_page_beginning);
2465 free(current_page_size);
2466 free(last_page);
2467 isds_list_free(&boxes);
2468 return retval;
2472 static void shi_stat_box_usage(const char *command) {
2473 oprintf(_(
2474 "Usage: %s BOX_ID...\n"
2475 "Get status of box with BOX_ID. More boxes can be specified.\n"),
2476 command);
2480 /* Get boxes status */
2481 static int shi_stat_box(int argc, const char **argv) {
2482 isds_error err;
2483 char *id = NULL;
2484 long int status;
2486 if (!argv || !*argv || argc < 2 || !argv[1]) {
2487 fprintf(stderr, _("Missing box ID\n"));
2488 shi_stat_box_usage((argv[0])?argv[0]:NULL);
2489 return -1;
2492 for (int i = 1; i < argc; i++) {
2493 if (!argv[i] || !*argv[i]) continue;
2495 free(id);
2496 id = locale2utf8(argv[i]);
2497 if (!id) {
2498 fprintf(stderr, _("%s: Could not covert box ID to UTF-8\n"),
2499 argv[i]);
2500 return -1;
2503 printf(_("Getting status of box `%s'...\n"), argv[i]);
2504 err = isds_CheckDataBox(cisds, id, &status);
2505 finish_isds_operation(cisds, err);
2506 if (err) return -1;
2508 oprintf(_("Status of box `%s': %s\n"),
2509 argv[i], DbState2string(&status));
2512 return 0;
2516 static void shi_boxlist_usage(const char *command) {
2517 oprintf(_(
2518 "Usage: %s LIST_TYPE FILE\n"
2519 "Save latest snapshot of list of boxes of type LIST_TYPE into FILE.\n"
2520 "\n"
2521 "Currently recognized LIST_TYPES are:\n"
2522 " ALL All boxes\n"
2523 " UPG Effectively OVM boxes\n"
2524 " OVM OVM gross type boxes\n"
2525 " OPN Boxes allowing receiving commercial messages\n"
2526 "\n"
2527 "Not all types are available to all users. E.g. only `UPG' is available\n"
2528 "to regular users.\n"
2529 "\n"
2530 "The format of the list is comma separate list that is packed into\n"
2531 "ZIP archive. Name of the list file denotes time of snapshoting\n"
2532 "the list. The snapshot is created by ISDS once a day.\n"),
2533 command);
2537 /* Download list of boxes */
2538 static int shi_boxlist(int argc, const char **argv) {
2539 isds_error err;
2540 const char *type_locale;
2541 char *type = NULL;
2542 void *buffer = NULL;
2543 size_t buffer_length;
2544 int retval;
2546 if (!argv || !*argv || argc < 3 || !argv[1] || !argv[2]) {
2547 fprintf(stderr, _("Bad number of arguments\n"));
2548 shi_boxlist_usage((argv)?argv[0]:NULL);
2549 return -1;
2551 type_locale = argv[1];
2553 type = locale2utf8(type_locale);
2554 if (!type) {
2555 fprintf(stderr, _("%s: Could not covert list type to UTF-8\n"),
2556 type_locale);
2557 return -1;
2560 printf(_("Getting `%s' list of boxes...\n"), type_locale);
2561 err = isds_get_box_list_archive(cisds, type, &buffer, &buffer_length);
2562 finish_isds_operation(cisds, err);
2563 free(type);
2564 if (err) {
2565 return -1;
2568 retval = save_data_to_file(argv[2], -1, buffer, buffer_length,
2569 "application/zip",
2570 cfg_getbool(configuration, CONFIG_OVERWRITEFILES));
2572 free(buffer);
2573 return retval;
2577 static void shi_delivery_usage(const char *command) {
2578 oprintf(_(
2579 "Usage: %s [MESSAGE_ID]\n"
2580 "Get delivery data about a message.\n"
2581 "If MESSAGE_ID is defined, query for that message.\n"
2582 "Otherwise use current message.\n"),
2583 command);
2587 static int shi_delivery(int argc, const char **argv) {
2588 isds_error err;
2589 const char *id = NULL;
2590 struct isds_message *delivery_info = NULL;
2592 if (!argv || argc > 2) {
2593 shi_delivery_usage((argv)?argv[0]:NULL);
2594 return -1;
2596 if (argc == 2 && argv[1] && *argv[1]) {
2597 id = argv[1];
2598 } else {
2599 if (!message) {
2600 fprintf(stderr, _("No message loaded\n"));
2601 return -1;
2603 if (!message->envelope || !message->envelope->dmID) {
2604 fprintf(stderr, _("Current message is missing ID\n"));
2605 return -1;
2607 id = message->envelope->dmID;
2610 printf(_("Getting delivery info...\n"));
2611 err = isds_get_signed_delivery_info(cisds, id, &delivery_info);
2612 finish_isds_operation(cisds, err);
2613 if (err)
2614 return -1;
2616 isds_message_free(&message);
2617 message = delivery_info;
2619 format_message(message);
2621 if (message->envelope && message->envelope->dmID)
2622 set_prompt(_("%s %s"), argv[0], message->envelope->dmID);
2623 else
2624 set_prompt("%s", argv[0]);
2625 select_completion(COMPL_MSG);
2626 return 0;
2630 static int shi_dump_message(int argc, const char **argv) {
2631 if (!message) {
2632 fprintf(stderr, _("No message loaded\n"));
2633 return -1;
2636 print_message(message);
2637 return 0;
2641 static void shi_hash_usage(const char *command) {
2642 oprintf(_(
2643 "Usage: %s [MESSAGE_ID]\n"
2644 "Retrieve message hash stored in ISDS.\n"
2645 "If MESSAGE_ID is defined, query for that message.\n"
2646 "Otherwise use current message.\n"),
2647 command);
2651 static int shi_hash(int argc, const char **argv) {
2652 isds_error err;
2653 const char *id = NULL;
2654 struct isds_hash *hash = NULL;
2655 char *hash_string = NULL;
2657 if (!argv || argc > 2) {
2658 shi_hash_usage((argv)?argv[0]:NULL);
2659 return -1;
2661 if (argc == 2 && argv[1] && *argv[1])
2662 id = argv[1];
2663 else {
2664 if (!message) {
2665 fprintf(stderr, _("No message loaded\n"));
2666 return -1;
2668 if (!message->envelope || !message->envelope->dmID) {
2669 fprintf(stderr, _("Current message is missing ID\n"));
2670 return -1;
2672 id = message->envelope->dmID;
2675 printf(_("Getting message hash...\n"));
2676 err = isds_download_message_hash(cisds, id, &hash);
2677 finish_isds_operation(cisds, err);
2678 if (err) return -1;
2680 hash_string = hash2string(hash);
2681 oprintf(_("ISDS states message with `%s' ID has following hash:\n%s\n"),
2682 id, hash_string);
2684 free(hash_string);
2685 isds_hash_free(&hash);
2686 return 0;
2690 static int shi_verify(int argc, const char **argv) {
2691 isds_error err;
2692 int retval = 0;
2693 struct isds_hash *retrieved_hash = NULL, *stored_hash = NULL;
2694 char *hash_string = NULL;
2695 size_t width = 4;
2697 if (!message) {
2698 fprintf(stderr, _("No message loaded\n"));
2699 return -1;
2702 if (!message->envelope) {
2703 fprintf(stderr, _("Current message is missing envelope\n"));
2704 return -1;
2706 stored_hash = message->envelope->hash;
2707 message->envelope->hash = NULL;
2709 if (message->envelope->dmID) {
2710 /* Verify remote hash */
2711 oprintf(_("Remote hash check:\n"));
2713 printf(_("Getting message hash...\n"));
2714 err = isds_download_message_hash(cisds, message->envelope->dmID,
2715 &retrieved_hash);
2716 finish_isds_operation(cisds, err);
2718 if (retrieved_hash) {
2719 hash_string = hash2string(retrieved_hash);
2720 ohprint(_("Retrieved:"), width);
2721 oprintf("%s\n", hash_string);
2722 zfree(hash_string);
2725 if (retrieved_hash && message->raw) {
2726 err = isds_compute_message_hash(cisds, message,
2727 retrieved_hash->algorithm);
2728 finish_isds_operation(cisds, err);
2730 if (!err) {
2731 hash_string = hash2string(message->envelope->hash);
2732 ohprint(_("Computed:"), width);
2733 oprintf("%s\n", hash_string);
2734 zfree(hash_string);
2738 err = isds_hash_cmp(retrieved_hash, message->envelope->hash);
2739 switch (err) {
2740 case IE_SUCCESS:
2741 oprintf(_("Hashes match.\n")); break;
2742 case IE_NOTUNIQ:
2743 oprintf(_("Hashes do not match.\n"));
2744 retval = -1;
2745 break;
2746 default:
2747 oprintf(_("Hashes could not be compared.\n"));
2748 retval = -1;
2749 break;
2752 free(retrieved_hash);
2756 if (stored_hash) {
2757 /* Verify stored hash */
2758 oprintf(_("Stored hash check:\n"));
2760 hash_string = hash2string(stored_hash);
2761 ohprint(_("Stored:"), width);
2762 oprintf("%s\n", hash_string);
2763 zfree(hash_string);
2765 if (message->raw) {
2766 err = isds_compute_message_hash(cisds, message,
2767 stored_hash->algorithm);
2768 finish_isds_operation(cisds, err);
2770 if (!err) {
2771 hash_string = hash2string(message->envelope->hash);
2772 ohprint(_("Computed:"), width);
2773 oprintf("%s\n", hash_string);
2774 zfree(hash_string);
2778 err = isds_hash_cmp(stored_hash, message->envelope->hash);
2779 switch (err) {
2780 case IE_SUCCESS:
2781 oprintf(_("Hashes match.\n")); break;
2782 case IE_NOTUNIQ:
2783 oprintf(_("Hashes do not match.\n"));
2784 retval = -1;
2785 break;
2786 default:
2787 oprintf(_("Hashes could not be compared.\n"));
2788 retval = -1;
2789 break;
2792 isds_hash_free(&message->envelope->hash);
2795 message->envelope->hash = stored_hash;
2796 return retval;
2800 static int shi_authenticate(int argc, const char **argv) {
2801 isds_error err;
2802 int retval = 0;
2804 if (!message) {
2805 fprintf(stderr, _("No message loaded\n"));
2806 return -1;
2808 if (!message->raw || message->raw_length == 0) {
2809 fprintf(stderr, _("Current message is missing raw representation\n"));
2810 return -1;
2813 printf(_("Submitting message to authenticity check...\n"));
2814 err = isds_authenticate_message(cisds, message->raw, message->raw_length);
2815 finish_isds_operation(cisds, (err == IE_NOTUNIQ) ? IE_SUCCESS : err);
2817 switch (err) {
2818 case IE_SUCCESS:
2819 oprintf(_("Message originates in ISDS.\n")); break;
2820 case IE_NOTUNIQ:
2821 oprintf(_("Message is unknown to ISDS or has been tampered.\n"));
2822 retval = -1;
2823 break;
2824 default:
2825 retval = -1;
2826 break;
2829 return retval;
2833 static void shi_accept_message_usage(const char *command) {
2834 oprintf(_(
2835 "Usage: %s [MESSAGE_ID...]\n"
2836 "Accept commercial message moving its state to received.\n"
2837 "If MESSAGE_ID is defined, accept that message. More messages can be specified.\n"
2838 "Otherwise accept all commercial incoming messages.\n"),
2839 command);
2843 static int shi_accept_message(int argc, const char **argv) {
2844 isds_error err;
2845 char *id = NULL;
2847 /* Process messages named in argv */
2848 for (int i = 1; i < argc; i++) {
2849 if (!argv[i] || !*argv[i]) continue;
2851 id = locale2utf8(argv[i]);
2852 if (!id) {
2853 fprintf(stderr,
2854 _("Error: Could not convert message ID to UTF-8: %s\n"),
2855 argv[i]);
2856 return -1;
2859 printf(_("Accepting message `%s'...\n"), argv[i]);
2860 err = isds_mark_message_received(cisds, id);
2861 finish_isds_operation(cisds, err);
2862 if (err) {
2863 free(id);
2864 return -1;
2867 oprintf(_("Message `%s' accepted\n"), argv[i]);
2868 free(id);
2871 if (argc < 2) {
2872 /* TODO: list commercial not received messages and accept all of them
2873 * */
2874 fprintf(stderr,
2875 _("Error: No message ID supplied. Accepting all commercial "
2876 "messages not implemented yet.\n"));
2877 return -1;
2880 return 0;
2884 static void shi_delete_message_usage(const char *command) {
2885 oprintf(_(
2886 "Usage: %s {-i|-o} MESSAGE_ID...\n"
2887 "Remove message from long term storage.\n"
2888 "Options:\n"
2889 " -i Messages are incoming\n"
2890 " -o Messages are outoging\n"),
2891 command);
2895 static int shi_delete_message(int argc, const char **argv) {
2896 isds_error err;
2897 char *id = NULL;
2898 _Bool incoming = 0;
2899 _Bool direction_specified = 0;
2900 int opt;
2902 optind = 0;
2903 while ((opt = getopt(argc, (char * const *)argv, "io")) != -1) {
2904 switch (opt) {
2905 case 'i':
2906 incoming = 1;
2907 direction_specified = 1;
2908 break;
2909 case 'o':
2910 incoming = 0;
2911 direction_specified = 1;
2912 break;
2913 default:
2914 shi_delete_message_usage((argv)?argv[0]:NULL);
2915 return -1;
2918 if (optind >= argc || !argv || !argv[optind] || !*argv[optind]) {
2919 fprintf(stderr, _("Bad invocation\n"));
2920 shi_delete_message_usage((argv)?argv[0]:NULL);
2921 return -1;
2923 if (!direction_specified) {
2924 fprintf(stderr, _("Message direction has not been specified\n"));
2925 shi_delete_message_usage((argv)?argv[0]:NULL);
2926 return -1;
2929 /* Process messages named in argv */
2930 for (int i = optind; i < argc; i++) {
2931 if (!argv[i] || !*argv[i]) continue;
2933 id = locale2utf8(argv[i]);
2934 if (!id) {
2935 fprintf(stderr,
2936 _("Error: Could not convert message ID to UTF-8: %s\n"),
2937 argv[i]);
2938 return -1;
2941 printf(_("Deleting message `%s'...\n"), argv[i]);
2942 err = isds_delete_message_from_storage(cisds, id, incoming);
2943 finish_isds_operation(cisds, err);
2944 if (err) {
2945 free(id);
2946 return -1;
2949 oprintf(_("Message `%s' deleted\n"), argv[i]);
2950 free(id);
2953 return 0;
2957 /* Convert message ID form locale to UTF-8 or in other direction. If both
2958 * strings are provided, UTF-8 will take precedence. The missing string is
2959 * automatically allocated (but not freed before). If UTF-8 version has been
2960 * provided, @stastic_utf8 will become 1, otherwise 0. You can pass the
2961 * strings and the flags to free_message_id() to free memory properly.*/
2962 static int convert_message_id(char **id_utf8, char **id_locale, _Bool *static_utf8) {
2963 if (!id_utf8 || !id_locale || !static_utf8) return -1;
2964 if (!*id_utf8 && !*id_locale) return -1;
2966 if (*id_utf8) {
2967 *static_utf8 = 1;
2968 *id_locale = utf82locale(*id_utf8);
2969 } else {
2970 *static_utf8 = 0;
2971 *id_utf8 = locale2utf8(*id_locale);
2972 if (!*id_utf8) {
2973 fprintf(stderr,
2974 _("Error: Could not convert message ID to UTF-8: %s\n"),
2975 *id_locale);
2976 return -1;
2980 return 0;
2984 /* Free message ID strings as were allocated by convert_message_id() */
2985 static int free_message_id(char **id_utf8, char **id_locale, _Bool static_utf8) {
2986 if (!id_utf8 || !id_locale) return -1;
2987 if (static_utf8) zfree(*id_locale);
2988 else zfree(*id_utf8);
2989 return 0;
2993 /* Return static UTF-8 encoded ID of current message. In case of error NULL. */
2994 static const char *get_current_message_id(void) {
2995 if (!message) {
2996 fprintf(stderr, _("No message loaded\n"));
2997 return NULL;
2999 if (!message->envelope) {
3000 fprintf(stderr, _("Loaded message is missing envelope\n"));
3001 return NULL;
3003 if (!message->envelope->dmID || !*message->envelope->dmID) {
3004 fprintf(stderr, _("Loaded message is missing ID\n"));
3005 return NULL;
3007 return message->envelope->dmID;
3011 static void shi_message_sender_usage(const char *command) {
3012 oprintf(_(
3013 "Usage: %s [MESSAGE_ID...]\n"
3014 "Get details about sender of a message.\n"
3015 "If MESSAGE_ID is defined, get sender of that message. More messages can be specified.\n"
3016 "Otherwise will get sender of current message, if any is loaded.\n"),
3017 command);
3021 /* Get details about sender of message with given ID. At least one form must
3022 * be specified.
3023 * @message_id is UTF-8 string
3024 * @message_id_locale is string in locale encoding
3025 * @return 0 on success, -1 on failure */
3026 static int do_message_sender(const char *message_id, const char *message_id_locale) {
3027 isds_sender_type *type = NULL;
3028 char *raw_type = NULL;
3029 char *name = NULL;
3030 isds_error err;
3031 _Bool static_id;
3033 if (convert_message_id((char **)&message_id, (char **)&message_id_locale,
3034 &static_id))
3035 return -1;
3037 printf(_("Getting sender of message `%s'...\n"), message_id_locale);
3038 err = isds_get_message_sender(cisds, message_id, &type, &raw_type, &name);
3039 finish_isds_operation(cisds, err);
3040 if (err) {
3041 free_message_id((char **)&message_id, (char **)&message_id_locale,
3042 static_id);
3043 return -1;
3046 format_sender_info(message_id, type, raw_type, name);
3048 free_message_id((char **)&message_id, (char **)&message_id_locale,
3049 static_id);
3050 zfree(type);
3051 zfree(raw_type);
3052 zfree(name);
3053 return 0;
3057 static int shi_message_sender(int argc, const char **argv) {
3058 if (argc < 2) {
3059 return do_message_sender(get_current_message_id(), NULL);
3062 for (int i = 1; i < argc; i++) {
3063 if (!argv[i] || !*argv[i]) continue;
3064 if (do_message_sender(NULL, argv[i]))
3065 return -1;
3068 return 0;
3072 /* Mark message as read. At least one form of ID must be provided.
3073 * @id is UTF-8 encoded message ID
3074 * @id_locale is locale encoded message ID. @id takes preference. */
3075 static int do_read_message(const char *id, const char *id_locale) {
3076 _Bool static_id;
3077 isds_error err;
3079 if ((!id || !*id) && (!id_locale || !*id_locale)) return -1;
3081 if (convert_message_id((char **)&id, (char **)&id_locale, &static_id)) return -1;
3083 printf(_("Marking message `%s' as read...\n"), id_locale);
3084 err = isds_mark_message_read(cisds, id);
3085 finish_isds_operation(cisds, err);
3087 if (!err)
3088 oprintf(_("Message `%s' marked as read\n"), id_locale);
3090 free_message_id((char **)&id, (char **)&id_locale, static_id);
3092 return (err) ? -1 : 0;
3096 static void shi_read_message_usage(const char *command) {
3097 oprintf(_(
3098 "Usage: %s [MESSAGE_ID...]\n"
3099 "Mark message as read moving its state to read.\n"
3100 "\n"
3101 "When new incoming message is download, its state is not changed on server.\n"
3102 "Client must mark such message as read explicitly. You can use this command\n"
3103 "to do so, if not done automatically at download time by your client.\n"
3104 "\n"
3105 "If MESSAGE_ID is defined, mark that message. More messages can be specified.\n"
3106 "Otherwise marks currently loaded message.\n"),
3107 command);
3111 static int shi_read_message(int argc, const char **argv) {
3112 if (argc < 2) {
3113 return do_read_message(get_current_message_id(), NULL);
3116 for (int i = 1; i < argc; i++) {
3117 if (!argv[i] || !*argv[i]) continue;
3118 if (do_read_message(NULL, argv[i]))
3119 return -1;
3122 return 0;
3126 static void shi_cat_message_usage(const char *command) {
3127 oprintf(_(
3128 "Usage: %s\n"
3129 "Print unformated raw representation of current message.\n"
3130 "\n"
3131 "This is the same content you would get into file by `save' command.\n"
3132 "\n"
3133 "Be ware the binary stream can screw your terminal. No new line character\n"
3134 "will be appended to the end of the output.\n"),
3135 command);
3139 static int shi_cat_message(int argc, const char **argv) {
3140 if (!message) {
3141 fprintf(stderr, _("No message loaded\n"));
3142 return -1;
3145 if (!message->raw || !message->raw_length) {
3146 fprintf(stderr, _("Current message is missing raw representation\n"));
3147 return -1;
3150 if (owrite(message->raw, message->raw_length) != message->raw_length) {
3151 fprintf(stderr, _("Error while printing message content\n"));
3152 return -1;
3155 return 0;
3159 static int shi_show_message(int argc, const char **argv) {
3160 if (!message) {
3161 fprintf(stderr, _("No message loaded\n"));
3162 return -1;
3165 format_message(message);
3166 return 0;
3170 static void shi_incoming_message_usage(const char *command) {
3171 oprintf(_(
3172 "Usage: %s [-r] MESSAGE_ID\n"
3173 "Get incoming message with MESSAGE_ID.\n"
3174 "Options:\n"
3175 " -r Mark mesage as read\n"),
3176 command);
3180 static int shi_incoming_message(int argc, const char **argv) {
3181 isds_error err;
3182 const char *id;
3183 int opt;
3184 _Bool mark_as_read = 0;
3186 optind = 0;
3187 while ((opt = getopt(argc, (char * const *)argv, "r")) != -1) {
3188 switch (opt) {
3189 case 'r':
3190 mark_as_read = 1;
3191 break;
3192 default:
3193 shi_incoming_message_usage((argv)?argv[0]:NULL);
3194 return -1;
3197 if (optind + 1 != argc || !argv || !argv[optind] || !*argv[optind]) {
3198 fprintf(stderr, _("Bad invocation\n"));
3199 shi_incoming_message_usage((argv)?argv[0]:NULL);
3200 return -1;
3202 id = argv[optind];
3204 printf(_("Getting incoming message...\n"));
3205 err = isds_get_signed_received_message(cisds, id, &message);
3206 finish_isds_operation(cisds, err);
3207 if (err) {
3208 set_prompt(NULL);
3209 select_completion(COMPL_COMMAND);
3210 return -1;
3213 format_message(message);
3215 if (message->envelope && message->envelope->dmID)
3216 set_prompt(_("%s %s"), argv[0], message->envelope->dmID);
3217 else
3218 set_prompt("%s", argv[0]);
3219 select_completion(COMPL_MSG);
3221 if (mark_as_read || cfg_getbool(configuration, CONFIG_MARKMESSAGEREAD)) {
3222 if (message->envelope && message->envelope->dmMessageStatus &&
3223 ! (*message->envelope->dmMessageStatus & MESSAGESTATE_READ))
3224 return do_read_message(id, NULL);
3226 return 0;
3230 static void shi_outgoing_message_usage(const char *command) {
3231 oprintf(_(
3232 "Usage: %s MESSAGE_ID\n"
3233 "Get outgoing message with MESSAGE_ID.\n"),
3234 command);
3238 static int shi_outgoing_message(int argc, const char **argv) {
3239 isds_error err;
3240 const char *id;
3242 if (!argv || !argv[1] || !*argv[1]) {
3243 shi_outgoing_message_usage(argv[0]);
3244 return -1;
3246 id = argv[1];
3248 printf(_("Getting outgoing message...\n"));
3249 err = isds_get_signed_sent_message(cisds, id, &message);
3250 finish_isds_operation(cisds, err);
3251 if (err) {
3252 set_prompt(NULL);
3253 select_completion(COMPL_COMMAND);
3254 return -1;
3257 format_message(message);
3258 if (message->envelope && message->envelope->dmID)
3259 set_prompt(_("%s %s"), argv[0], message->envelope->dmID);
3260 else
3261 set_prompt("%s", argv[0]);
3262 select_completion(COMPL_MSG);
3263 return 0;
3267 /* Detect type (message or delivery data) and load it. And change completion
3268 * and show the data.
3269 * @buffer is memory with message or delivery data
3270 * @length is size of @buffer in bytes
3271 * @strategy defines how to fill global message variable
3272 * @return 0 for success, otherwise non-zero. */
3273 static int do_load_anything(const void *buffer, size_t length,
3274 isds_buffer_strategy strategy) {
3275 isds_raw_type raw_type;
3276 isds_error err;
3277 char *type_name = NULL;
3279 if (NULL == buffer || 0 == length) {
3280 return -1;
3283 printf(_("Detecting format...\n"));
3284 err = isds_guess_raw_type(cisds, &raw_type, buffer, length);
3285 finish_isds_operation(cisds, err);
3287 if (err) {
3288 if (err == IE_NOTSUP)
3289 fprintf(stderr, _("Unknown format.\n"));
3290 else
3291 fprintf(stderr, _("Error while detecting format.\n"));
3292 } else {
3293 switch (raw_type) {
3294 case RAWTYPE_INCOMING_MESSAGE:
3295 case RAWTYPE_PLAIN_SIGNED_INCOMING_MESSAGE:
3296 case RAWTYPE_CMS_SIGNED_INCOMING_MESSAGE:
3297 case RAWTYPE_PLAIN_SIGNED_OUTGOING_MESSAGE:
3298 case RAWTYPE_CMS_SIGNED_OUTGOING_MESSAGE:
3299 err = isds_load_message(cisds, raw_type,
3300 buffer, length, &message, strategy);
3301 finish_isds_operation(cisds, err);
3302 type_name = N_("message");
3303 break;
3305 case RAWTYPE_DELIVERYINFO:
3306 case RAWTYPE_PLAIN_SIGNED_DELIVERYINFO:
3307 case RAWTYPE_CMS_SIGNED_DELIVERYINFO:
3308 err = isds_load_delivery_info(cisds, raw_type,
3309 buffer, length, &message, strategy);
3310 finish_isds_operation(cisds, err);
3311 type_name = N_("delivery");
3312 break;
3314 default:
3315 fprintf(stderr,
3316 _("Unsupported format.\n"));
3317 err = IE_NOTSUP;
3321 if (err) {
3322 set_prompt(NULL);
3323 select_completion(COMPL_COMMAND);
3324 return -1;
3327 format_message(message);
3329 if (message->envelope && message->envelope->dmID)
3330 set_prompt(_("%s %s"), _(type_name), message->envelope->dmID);
3331 else
3332 set_prompt("%s", _(type_name));
3333 select_completion(COMPL_MSG);
3334 return 0;
3338 static void shi_load_anything_usage(const char *command) {
3339 oprintf(_(
3340 "Usage: %s FILE\n"
3341 "Load message or message delivery details from local FILE.\n"),
3342 command);
3346 static int shi_load_anything(int argc, const char **argv) {
3347 int fd;
3348 void *buffer = NULL;
3349 size_t length;
3350 int error;
3352 if (!argv || !argv[1] || !*argv[1]) {
3353 shi_load_anything_usage((argv)?argv[0]:NULL);
3354 return -1;
3357 printf(_("Loading file `%s'...\n"), argv[1]);
3359 if (mmap_file(argv[1], &fd, &buffer, &length)) return -1;
3361 error = do_load_anything(buffer, length, BUFFER_COPY);
3363 munmap_file(fd, buffer, length);
3365 return error;
3369 static void shi_save_message_usage(const char *command) {
3370 oprintf(_(
3371 "Usage: %s FILE\n"
3372 "Save message into local FILE.\n"),
3373 command);
3377 static const char *raw_type2mime(isds_raw_type raw_type) {
3378 switch (raw_type) {
3379 case RAWTYPE_INCOMING_MESSAGE:
3380 case RAWTYPE_PLAIN_SIGNED_INCOMING_MESSAGE:
3381 case RAWTYPE_PLAIN_SIGNED_OUTGOING_MESSAGE:
3382 case RAWTYPE_DELIVERYINFO:
3383 case RAWTYPE_PLAIN_SIGNED_DELIVERYINFO:
3384 return "text/xml";
3386 case RAWTYPE_CMS_SIGNED_INCOMING_MESSAGE:
3387 case RAWTYPE_CMS_SIGNED_OUTGOING_MESSAGE:
3388 case RAWTYPE_CMS_SIGNED_DELIVERYINFO:
3389 return "application/pkcs7-mime";
3391 default:
3392 return NULL;
3397 static int shi_save_message(int argc, const char **argv) {
3398 _Bool overwrite = cfg_getbool(configuration, CONFIG_OVERWRITEFILES);
3400 if (!argv || !argv[1] || !*argv[1]) {
3401 shi_save_message_usage(argv[0]);
3402 return -1;
3405 if (!message) {
3406 fprintf(stderr, _("No message loaded\n"));
3407 return -1;
3409 if (!message->raw || message->raw_length == 0) {
3410 fprintf(stderr, _("Loaded message is missing raw representation\n"));
3411 return -1;
3414 return save_data_to_file(argv[1], -1, message->raw, message->raw_length,
3415 raw_type2mime(message->raw_type), overwrite);
3419 /* Return document of current message identified by ordinal number expressed
3420 * as string. In case of error return NULL. */
3421 static const struct isds_document *locate_document_by_ordinal_string(
3422 const char *number) {
3423 const struct isds_list *item;
3424 const struct isds_document *document = NULL;
3425 int ordinar, i;
3427 if (!number) return NULL;
3429 ordinar = atoi(number);
3430 if (ordinar <= 0) {
3431 fprintf(stderr, _("%s: Document number must be positive number\n"),
3432 number);
3433 return NULL;
3436 if (!message) {
3437 fprintf(stderr, _("No message loaded\n"));
3438 return NULL;
3441 /* Find document */
3442 for (item = message->documents, i = 0; item; item = item->next) {
3443 if (!item->data) continue;
3444 if (++i == ordinar) {
3445 document = (const struct isds_document *) item->data;
3446 break;
3449 if (i != ordinar) {
3450 fprintf(stderr, _("Message does not contain document #%d\n"), ordinar);
3451 return NULL;
3454 return document;
3458 static void shi_cat_document_usage(const char *command) {
3459 oprintf(_(
3460 "Usage: %s NUMBER\n"
3461 "Print document selected with ordinal NUMBER.\n"),
3462 command);
3465 static int shi_cat_document(int argc, const char **argv) {
3466 const struct isds_document *document;
3468 if (!argv || !argv[1] || !*argv[1] || argc > 3) {
3469 shi_cat_document_usage(argv[0]);
3470 return -1;
3473 document = locate_document_by_ordinal_string(argv[1]);
3474 if (!document) return -1;
3476 if (document->is_xml) {
3477 xmlBufferPtr buffer = NULL;
3478 size_t written;
3480 if (serialize_xml_to_buffer(&buffer, document->xml_node_list))
3481 return -1;
3483 written = owrite(buffer->content, buffer->use);
3484 xmlBufferFree(buffer);
3485 if (written != buffer->use) {
3486 fprintf(stderr, _("Error while printing document content\n"));
3487 return -1;
3489 } else {
3490 if (!document->data || !document->data_length) {
3491 fprintf(stderr, _("Document is missing raw representation\n"));
3492 return -1;
3495 if (owrite(document->data, document->data_length) != document->data_length) {
3496 fprintf(stderr, _("Error while printing document content\n"));
3497 return -1;
3501 return 0;
3505 static void shi_save_document_usage(const char *command) {
3506 oprintf(_(
3507 "Usage: %s NUMBER [DESTINATION]\n"
3508 "Save document having ordinal NUMBER within current message into local file.\n"
3509 "If DESTINATION is file (or does not exist yet), document will be saved into\n"
3510 "this file.\n"
3511 "If DESTINATION is existing directory, file name equaled to document name\n"
3512 "will be saved into DESTINATION.\n"
3513 "If DESTINATION is missing, document name will be used as file name and\n"
3514 "saved into working directory.\n"
3515 "Be aware that document name does not embed malicious characters (slashes).\n"
3516 "\n"
3517 "If the document is a binary stream, image of the document will be copied\n"
3518 "into a file. If the document is a XML document, the XML tree will be serialized\n"
3519 "into a file. If XML document stands for one element or one text node, the node\n"
3520 "(and its children recursively) will be serialized. If XML document compounds\n"
3521 "more nodes or a comment or a processing instruction, parent node from ISDS name\n"
3522 "space will be used to ensure output serialized XML well-formness.\n"
3524 command);
3528 static int shi_save_document(int argc, const char **argv) {
3529 const struct isds_document *document;
3530 const char *dirname = NULL;
3531 char *filename = NULL, *path = NULL;
3532 int retval = 0;
3533 _Bool overwrite = cfg_getbool(configuration, CONFIG_OVERWRITEFILES);
3535 if (!argv || !argv[1] || !*argv[1] || argc > 3) {
3536 shi_save_document_usage(argv[0]);
3537 return -1;
3540 document = locate_document_by_ordinal_string(argv[1]);
3541 if (!document) return -1;
3543 /* Select directory and file name */
3544 if (argv[2] && *argv[2]) {
3545 if (!is_directory(argv[2])) {
3546 dirname = argv[2];
3547 } else {
3548 filename = strdup(argv[2]);
3549 if (!filename) {
3550 fprintf(stderr, _("Not enough memory\n"));
3551 return -1;
3555 if (!filename && document->dmFileDescr && &document->dmFileDescr) {
3556 filename = utf82locale(document->dmFileDescr);
3557 if (!filename) {
3558 fprintf(stderr, _("Not enough memory\n"));
3559 return -1;
3562 if (!filename) {
3563 fprintf(stderr,
3564 _("File name neither supplied, nor document name exists\n"
3565 "Please, supply one.\n"));
3566 return -1;
3569 /* Build path */
3570 if (dirname) {
3571 path = astrcat3(dirname, "/", filename);
3572 zfree(filename);
3573 } else {
3574 path = filename;
3575 filename = NULL;
3577 if (!path) {
3578 fprintf(stderr, _("Not enough memory\n"));
3579 return -1;
3582 /* Save document */
3583 if (document->is_xml)
3584 retval = save_xml_to_file(path, -1, document->xml_node_list,
3585 document->dmMimeType, overwrite);
3586 else
3587 retval = save_data_to_file(path, -1, document->data,
3588 document->data_length, document->dmMimeType, overwrite);
3589 free(path);
3590 return retval;
3594 /* Execute program specified as NULL terminated array of arguments. argv[0] is
3595 * subject of PATH search variable look-up. The program is executed directly,
3596 * it's not a shell command. */
3597 static int execute_system_command(char *const argv[]) {
3598 pid_t pid;
3600 if (!argv || !argv[0]) return -1;
3602 pid = fork();
3603 if (pid == -1) {
3604 /* Could not fork */
3605 fprintf(stderr, _("Could not fork\n"));
3606 return -1;
3607 } else if (pid == 0) {
3608 /* Child */
3609 execvp(argv[0], argv);
3610 fprintf(stderr, _("Could not execute:"));
3611 for (char *const *arg = argv; *arg; arg++)
3612 fprintf(stderr, " %s", *arg);
3613 fprintf(stderr, _(": %s\n"), strerror(errno));
3614 exit(EXIT_FAILURE);
3615 } else {
3616 /* Wait for the command */
3617 int retval;
3619 if (-1 == waitpid(pid, &retval, 0)) {
3620 fprintf(stderr, _("Could not wait for executed command\n"));
3621 return -1;
3624 if (retval == -1)
3625 fprintf(stderr, _("Exit code of command could not "
3626 "be determined\n"));
3627 else if (WIFEXITED(retval) && WEXITSTATUS(retval))
3628 printf(_("Command exited with code %d\n"),
3629 WEXITSTATUS(retval));
3630 else if (WIFSIGNALED(retval))
3631 printf(_("Command terminated by signal "
3632 "#%d\n"), WTERMSIG(retval));
3633 return retval;
3638 /* Run editor to create new text document */
3639 static int edit_new_textual_document(struct isds_document *document) {
3640 char filename[14] = "shiXXXXXX.txt";
3641 int fd;
3642 char *command[] = { getenv("VISUAL"), filename, NULL };
3643 int retval = 0;
3644 struct stat file_before, file_after;
3646 if (batch_mode) {
3647 fprintf(stderr, _("Editing is forbidden in batch mode.\n"));
3648 return -1;
3651 if (NULL == document) return -1;
3652 if (NULL == command[0]) command[0] = getenv("EDITOR");
3653 if (NULL == command[0]) {
3654 fprintf(stderr,
3655 _("Neither environment variable VISUAL nor EDITOR are set.\n"));
3656 return -1;
3659 /* Create temporary file for the document */
3660 fd = create_new_file(filename, 4);
3661 if (fd == -1) {
3662 return -1;
3664 if (fstat(fd, &file_before)) {
3665 fprintf(stderr,
3666 _("Could not retrieve modification time for `%s': %s\n"),
3667 filename, strerror(errno));
3668 retval = -1;
3669 goto leave;
3672 /* Open the file with $EDITOR */
3673 if ((retval = execute_system_command(command))) {
3674 fprintf(stderr, _("Editor failed.\n"));
3675 retval = -1;
3676 goto leave;
3679 /* Compare modification times */
3680 /* XXX: fstat(2) does return updated st_mtime. Bug in Linux 3.7.1? */
3681 if (stat(filename, &file_after)) {
3682 fprintf(stderr,
3683 _("Could not retrieve modification time for `%s': %s\n"),
3684 filename, strerror(errno));
3685 retval = -1;
3686 goto leave;
3688 if (file_before.st_mtime == file_after.st_mtime) {
3689 fprintf(stderr, _("Edited document has not been changed.\n"));
3690 retval = -1;
3691 goto leave;
3694 /* Load document */
3695 if (load_data_from_file(filename, &document->data,
3696 &document->data_length, NULL)) {
3697 retval = -1;
3698 goto leave;
3701 /* Set meta-data */
3702 if (NULL == document->dmMimeType)
3703 FILL_OR_LEAVE(document->dmMimeType, "text/plain");
3704 /* XXX: POSIX basename() modifies argument */
3705 if (NULL == document->dmFileDescr)
3706 FILL_OR_LEAVE(document->dmFileDescr, basename(filename));
3708 leave:
3709 /* Remove the file */
3710 unlink_file(filename);
3711 close(fd);
3712 return retval;
3716 /* Append @suffix into @buffer with @size bytes prealocated at position @at.
3717 * Trailing '\0' of @suffix is not carried.
3718 * @buffer can be reallocated if @size is not suffient to fill @suffix
3719 * @size is original @buffer size, can change if @buffer would be reallocated
3720 * @at position where append @suffic to. Outputs new end after appending
3721 * @suffix is NULL rerminated string to append
3722 * @return 0 if success, -1 otherwise. Caller is resposible for freeing
3723 * @buffer.*/
3724 static int append_string_at(char **buffer, size_t *size, char **at,
3725 const char *suffix) {
3727 if (!buffer || !*buffer || !size || !at || !*at) return -1;
3728 if (!suffix) return 0;
3730 while (*suffix) {
3731 if (*at - *buffer + 1 >= *size) {
3732 /* End of buffer, grow it */
3733 if (*size < 8) *size = 8;
3734 *size *= 2;
3735 char *new_buffer = realloc(*buffer, *size);
3736 if (!new_buffer) return -1;
3737 *at = *at - *buffer + new_buffer;
3738 *buffer = new_buffer;
3741 /* Copy a character */
3742 *((*at)++) = *(suffix++);
3745 return 0;
3749 static char *expand_command_arg(const char *format, const char *file,
3750 const char *type) {
3751 char *buffer = NULL;
3752 size_t size = 0;
3753 const char *format_cursor;
3754 char *buffer_cursor;
3756 if (!format) return NULL;
3758 for (format_cursor = format, buffer_cursor = buffer; ; format_cursor++) {
3759 if (buffer_cursor - buffer + 1 >= size) {
3760 /* End of buffer, grow it */
3761 if (size < 8) size = 8;
3762 size *= 2;
3763 char *new_buffer = realloc(buffer, size);
3764 if (!new_buffer) goto error;
3765 buffer_cursor = buffer_cursor - buffer + new_buffer;
3766 buffer = new_buffer;
3769 if (*format_cursor == '%') {
3770 /* Escape */
3771 switch (*(format_cursor+1)) {
3772 case 'f':
3773 if (!file) {
3774 fprintf(stderr, _("Could not expand `%%f' because "
3775 "file name did not exist.\n"));
3776 free(buffer);
3777 return NULL;
3779 if (append_string_at(&buffer, &size, &buffer_cursor, file))
3780 goto error;
3782 format_cursor++;
3783 continue;
3784 case 't':
3785 if (!file) {
3786 fprintf(stderr, _("Could not expand `%%t' because "
3787 "file type did not exist.\n"));
3788 free(buffer);
3789 return NULL;
3791 if (append_string_at(&buffer, &size, &buffer_cursor, type))
3792 goto error;
3794 format_cursor++;
3795 continue;
3796 case '%':
3797 format_cursor++;
3801 /* Copy plain character */
3802 *(buffer_cursor++) = *format_cursor;
3804 if (!*format_cursor) break;
3807 return buffer;
3809 error:
3810 fprintf(stderr, _("Error: Not enough memory\n"));
3811 free(buffer);
3812 return NULL;
3816 /* Expand open_command configuration option by substiting %f and %t with file
3817 * name and file type.
3818 * @file is locale encoded file name
3819 * @type is UTF-8 encoded MIME type
3820 * @return heap allocated arrary of arguments or NULL if error occurs.
3821 * Arguments are coded in locale. */
3822 static char **expand_open_command(const char *file, const char *type) {
3823 char **command = NULL;
3824 int length;
3825 char *type_locale = NULL;
3827 length = cfg_size(configuration, CONFIG_OPENCOMMAND);
3828 if (length <= 0) {
3829 fprintf(stderr, _("%s not set\n"), CONFIG_OPENCOMMAND);
3830 return NULL;
3833 command = malloc((length + 1) * sizeof(*command));
3834 if (!command) {
3835 fprintf(stderr, _("Error: Not enough memory\n"));
3836 return NULL;
3839 if (type) {
3840 type_locale = utf82locale(type);
3841 if (!type_locale) {
3842 printf(_("Could not convert document MIME type to locale "
3843 "encoding\n"));
3844 free(command);
3845 return NULL;
3849 for (int i = 0; i < length; i++) {
3850 const char *value = cfg_getnstr(configuration, CONFIG_OPENCOMMAND, i);
3851 command[i] = expand_command_arg(value, file, type_locale);
3852 if (!command[i]) {
3853 fprintf(stderr, _("Error: Not enough memory\n"));
3854 for (int j = 0; j < i; j++) free(command[j]);
3855 free(command);
3856 free(type_locale);
3857 return NULL;
3860 command[length] = NULL;
3862 free(type_locale);
3863 return command;
3867 /* Register temporary @file for removal at shigofumi exit.
3868 * This is done by destructor call-back in isds_list_free(). */
3869 static int register_temporary_file(const char *file) {
3870 struct isds_list *temporary_file = NULL;
3872 if (!file) return 0;
3874 temporary_file = malloc(sizeof(*temporary_file));
3875 if (!temporary_file) {
3876 fprintf(stderr, _("Error: Not enough memory\n"));
3877 return -1;
3880 temporary_file->data = (void *)strdup(file);
3881 if (!temporary_file) {
3882 fprintf(stderr, _("Error: Not enough memory\n"));
3883 free(temporary_file);
3884 return -1;
3887 temporary_file->destructor = shi_unlink_temporary_file;
3889 if (temporary_files)
3890 temporary_file->next = temporary_files;
3891 else
3892 temporary_file->next = NULL;
3893 temporary_files = temporary_file;
3895 return 0;
3899 static void shi_open_document_usage(const char *command) {
3900 oprintf(_(
3901 "Usage: %s NUMBER\n"
3902 "Save document having ordinal NUMBER within current message into temporal\n"
3903 "local file, open the file by xdg-open utility and then remove the file.\n"
3905 command);
3909 static int shi_open_document(int argc, const char **argv) {
3910 const struct isds_document *document;
3911 char filename[10] = "shiXXXXXX";
3912 int fd;
3913 char **command = NULL;
3914 int retval = 0;
3916 if (!argv || !argv[1] || !*argv[1] || argc > 3) {
3917 shi_open_document_usage(argv[0]);
3918 return -1;
3921 document = locate_document_by_ordinal_string(argv[1]);
3922 if (!document) return -1;
3924 /* Create temporary file for the document */
3925 fd = create_new_file(filename, 0);
3926 if (fd == -1) {
3927 return -1;
3930 /* Save document */
3931 if (document->is_xml)
3932 retval = save_xml_to_file(filename, fd, document->xml_node_list,
3933 document->dmMimeType, 0);
3934 else
3935 retval = save_data_to_file(filename, fd, document->data,
3936 document->data_length, document->dmMimeType, 0);
3938 /* Open the file with external utility */
3939 if (!retval) {
3940 /* Construct command arguments to execute */
3941 command = expand_open_command(filename, document->dmMimeType);
3943 if (!command)
3944 retval = -1;
3945 else {
3946 /* XXX: Do not use system(3) as we cannot escape uknown shell */
3947 retval = execute_system_command(command);
3948 for (char **arg = command; *arg; arg++) free(*arg);
3949 free(command);
3953 /* Remove the file */
3954 /* XXX: We do not know when external program opens the file. We cannot
3955 * remove it immediately. Register the filename and remove all temporary
3956 * files at exit of shigofumi if requested by configuration. */
3957 if (cfg_getbool(configuration, CONFIG_CLEAN_TEMPORARY_FILES)) {
3958 if (register_temporary_file(filename))
3959 fprintf(stderr, _("Warning: Temporary file `%s' could not been "
3960 "registered for later removal. Remove the file by "
3961 "hand, please.\n"), filename);
3963 return retval;
3967 static void shi_compose_usage(const char *command) {
3968 oprintf(_(
3969 "Usage: %s OPTION...\n"
3970 "Compose and send a message to recipient defined by his box ID.\n"
3971 "Each option requires an argument (if not stated otherwise):\n"
3972 " -s * message subject\n"
3973 "\n"
3974 "Recipient options:\n"
3975 " -b * recipient box ID\n"
3976 " -U organisation unit name\n"
3977 " -N organisation unit number\n"
3978 " -P to hands of given person\n"
3979 "\n"
3980 "Sender organisation structure options:\n"
3981 " -I publish user's identity\n"
3982 " -u unit name\n"
3983 " -n unit number\n"
3984 "\n"
3985 "Message identifier options:\n"
3986 " -r sender reference number\n"
3987 " -f sender file ID\n"
3988 " -R recipient reference number\n"
3989 " -F recipient file ID\n"
3990 "\n"
3991 "Legal title options:\n"
3992 " -y year act has been issued\n"
3993 " -a ordinal number of act in a year\n"
3994 " -e section of the act\n"
3995 " -o paragraph of the act\n"
3996 " -i point of the paragraph of the act\n"
3997 "\n"
3998 "Delivery options:\n"
3999 " -p personal delivery required\n"
4000 " -t allow substitutable delivery\n"
4001 " -A non-OVM sender acts as public authority\n"
4002 " -C commercial type; accepted values:\n"
4003 " K Commercial message paid by sender or sponsor\n"
4004 " I Initiatory commercial message offering to pay response\n"
4005 " O Commercial response paid by recipient\n"
4006 " V Public message paid by government\n"
4007 " Missing option defaults to K or O (see `commercialsending' command)\n"
4008 " if sending to non-OVM recipient with enabled commercial receiving,\n"
4009 " otherwise it defaults to V.\n"
4010 "\n"
4011 "Document options:\n"
4012 " -d * read document from local file. If `-' is specified,\n"
4013 " run text editor.\n"
4014 " -D document name (defaults to base local file name)\n"
4015 " -x transport subset of the document as a XML.\n"
4016 " Argument is XPath expression specifying desired node set\n"
4017 " -m override MIME type (guessed on -d)\n"
4018 " -g document ID (must be unique per message)\n"
4019 " -G reference to other document using its ID\n"
4020 " -c document is digital signature of other document (NO argument\n"
4021 " allowed)\n"
4022 "\n"
4023 "Options marked with asterisk are mandatory, other are optional. Another soft\n"
4024 "dependencies can emerge upon using specific option. They are not mandated by\n"
4025 "ISDS currently, but client library or this program can force them to assure\n"
4026 "semantically complete message. Following soft dependencies are recommended:\n"
4027 " -y <=> -a act number and year must be used at the same time\n"
4028 " -i => -o act point requires act paragraph\n"
4029 " -o => -e act paragraph requires act section\n"
4030 " -e => -a act section requires act number\n"
4031 " -G => -g document with referenced ID must exist\n"
4032 " -c => -G signature must refer to signed document\n"
4033 " -c first document cannot be signature\n"
4034 " -C I => -r sender reference number allows responder to reply to this message\n"
4035 " -C O -> -R recipient reference number must match sender reference number of\n"
4036 " initiatory message\n"
4037 "\n"
4038 "More documents can be attached to a message by repeating `-d' option.\n"
4039 "Document order will be preserved. Other document options affect immediately\n"
4040 "preceding `-d' document only. E.g. `-d /tmp/foo.pdf -m application/pdf\n"
4041 "-d /tmp/bar.txt -m text/plain' attaches first PDF file, then textual file.\n"
4042 "\n"
4043 "The same applies to recipient options that must start with box ID (-b).\n"
4044 "If more recipients specified, each of them will get a copy of composed\n"
4045 "message. ISDS will assign message identifier to each copy in turn.\n"
4047 command);
4051 static int shi_compose(int argc, const char **argv) {
4052 int opt;
4053 isds_error err;
4054 int retval = 0;
4055 unsigned int i;
4056 struct isds_message *message = NULL;
4057 struct isds_envelope *envelope = NULL;
4058 struct isds_list *documents = NULL;
4059 struct isds_document *document = NULL;
4060 struct isds_list *copies = NULL, *copy_item = NULL;
4061 struct isds_message_copy *copy = NULL;
4062 char *message_id_locale = NULL, *recipient_id_locale = NULL,
4063 *dmStatus_locale = NULL;
4065 if (!argv || !argv[1] || !*argv[1]) {
4066 fprintf(stderr, _("Error: No argument supplied\n"));
4067 shi_compose_usage((argv)?argv[0]:NULL);
4068 return -1;
4071 message = calloc(1, sizeof(*message));
4072 if (!message) {
4073 fprintf(stderr, _("Error: Not enough memory\n"));
4074 retval = -1;
4075 goto leave;
4077 envelope = calloc(1, sizeof(*envelope));
4078 if (!envelope) {
4079 fprintf(stderr, _("Error: Not enough memory\n"));
4080 retval = -1;
4081 goto leave;
4083 message->envelope = envelope;
4085 /* Parse options */
4086 optind = 0;
4087 while ((opt = getopt(argc, (char * const *)argv, "s:" "b:U:N:P:" "I:u:n:"
4088 "r:f:R:F:" "y:a:e:o:i:" "p:t:A:C:" "d:D:x:m:g:G:c"
4089 )) != -1) {
4090 switch (opt) {
4091 case 's':
4092 FILL_OR_LEAVE(envelope->dmAnnotation, optarg);
4093 break;
4095 /* Recipient options */
4096 case 'b':
4097 copy = NULL;
4098 if (!copies) {
4099 /* First recipient */
4100 CALLOC_OR_LEAVE(copies);
4101 copies->destructor =
4102 (void(*)(void **)) isds_message_copy_free;
4103 copy_item = copies;
4104 } else {
4105 /* Next recipient */
4106 CALLOC_OR_LEAVE(copy_item->next);
4107 copy_item->next->destructor =
4108 (void(*)(void **)) isds_message_copy_free;
4109 copy_item = copy_item->next;
4111 CALLOC_OR_LEAVE(copy);
4112 copy_item->data = copy;
4114 /* Copy recipient box ID */
4115 FILL_OR_LEAVE(copy->dbIDRecipient, optarg);
4116 break;
4117 case 'U':
4118 if (!copy) {
4119 fprintf(stderr,
4120 _("Error: %s: Recipient box ID (-b) must precede "
4121 "recipient organisation unit name (-%c)\n"),
4122 optarg, opt);
4123 retval = -1;
4124 goto leave;
4126 FILL_OR_LEAVE(copy->dmRecipientOrgUnit, optarg);
4127 break;
4128 case 'N':
4129 if (!copy) {
4130 fprintf(stderr,
4131 _("Error: %s: Recipient box ID (-b) must precede "
4132 "recipient organisation unit number (-%c)\n"),
4133 optarg, opt);
4134 retval = -1;
4135 goto leave;
4137 FILL_LONGINT_OR_LEAVE(copy->dmRecipientOrgUnitNum, optarg);
4138 break;
4139 case 'P':
4140 if (!copy) {
4141 fprintf(stderr,
4142 _("Error: %s: Recipient box ID (-b) must precede "
4143 "to-hands option (-%c)\n"), optarg, opt);
4144 retval = -1;
4145 goto leave;
4147 FILL_OR_LEAVE(copy->dmToHands, optarg);
4148 break;
4150 /* Sender organisation structure options */
4151 case 'I':
4152 FILL_BOOLEAN_OR_LEAVE(envelope->dmPublishOwnID, optarg);
4153 break;
4154 case 'u':
4155 FILL_OR_LEAVE(envelope->dmSenderOrgUnit, optarg);
4156 break;
4157 case 'n':
4158 FILL_LONGINT_OR_LEAVE(envelope->dmSenderOrgUnitNum, optarg);
4159 break;
4161 /* Message identifier options */
4162 case 'r':
4163 FILL_OR_LEAVE(envelope->dmSenderRefNumber, optarg);
4164 break;
4165 case 'f':
4166 FILL_OR_LEAVE(envelope->dmSenderIdent, optarg);
4167 break;
4168 case 'R':
4169 FILL_OR_LEAVE(envelope->dmRecipientRefNumber, optarg);
4170 break;
4171 case 'F':
4172 FILL_OR_LEAVE(envelope->dmRecipientIdent, optarg);
4173 break;
4175 /* Legal title options */
4176 case 'y':
4177 FILL_LONGINT_OR_LEAVE(envelope->dmLegalTitleYear, optarg);
4178 break;
4179 case 'a':
4180 FILL_LONGINT_OR_LEAVE(envelope->dmLegalTitleLaw, optarg);
4181 break;
4182 case 'e':
4183 FILL_OR_LEAVE(envelope->dmLegalTitleSect, optarg);
4184 break;
4185 case 'o':
4186 FILL_OR_LEAVE(envelope->dmLegalTitlePar, optarg);
4187 break;
4188 case 'i':
4189 FILL_OR_LEAVE(envelope->dmLegalTitlePoint, optarg);
4190 break;
4192 /* Delivery options */
4193 case 'p':
4194 FILL_BOOLEAN_OR_LEAVE(envelope->dmPersonalDelivery, optarg);
4195 break;
4196 case 't':
4197 FILL_BOOLEAN_OR_LEAVE(envelope->dmAllowSubstDelivery, optarg);
4198 break;
4199 case 'A':
4200 FILL_BOOLEAN_OR_LEAVE(envelope->dmOVM, optarg);
4201 break;
4202 case 'C':
4203 FILL_OR_LEAVE(envelope->dmType, optarg);
4204 break;
4206 /* Document options */
4207 case 'd':
4208 document = NULL;
4209 if (!documents) {
4210 /* First document */
4211 CALLOC_OR_LEAVE(message->documents);
4212 message->documents->destructor =
4213 (void(*)(void **)) isds_document_free;
4214 documents = message->documents;
4215 CALLOC_OR_LEAVE(document);
4216 documents->data = document;
4217 document->dmFileMetaType = FILEMETATYPE_MAIN;
4218 } else {
4219 /* Next document */
4220 CALLOC_OR_LEAVE(documents->next);
4221 documents->next->destructor =
4222 (void(*)(void **)) isds_document_free;
4223 documents = documents->next;
4224 CALLOC_OR_LEAVE(document);
4225 documents->data = document;
4226 document->dmFileMetaType = FILEMETATYPE_ENCLOSURE;
4229 if (strcmp(optarg, "-")) {
4230 /* Load file if specified. Keep editing new file after
4231 * processing all arguments. */
4232 if (load_data_from_file(optarg, &document->data,
4233 &document->data_length, &document->dmMimeType)) {
4234 retval = -1;
4235 goto leave;
4237 /* XXX: POSIX basename() modifies argument */
4238 FILL_OR_LEAVE(document->dmFileDescr, basename(optarg));
4240 break;
4241 case 'D':
4242 if (!document) {
4243 fprintf(stderr,
4244 _("Error: %s: Document file (-d) must precede "
4245 "document name (-%c)\n"), optarg, opt);
4246 retval = -1;
4247 goto leave;
4249 FILL_OR_LEAVE(document->dmFileDescr, optarg);
4250 break;
4251 case 'x':
4252 if (!document) {
4253 fprintf(stderr,
4254 _("Error: %s: Document file (-d) must precede "
4255 "XPath expression (-%c)\n"), optarg, opt);
4256 retval = -1;
4257 goto leave;
4259 /* Load XML node list */
4260 char *xpath_expr = NULL;
4261 FILL_OR_LEAVE(xpath_expr, optarg);
4262 retval = load_xml_subtree_from_memory(
4263 document->data, document->data_length,
4264 &document->xml_node_list, xpath_expr);
4265 if (retval) {
4266 free(xpath_expr);
4267 goto leave;
4269 /* Switch document type to XML */
4270 document->is_xml = 1;
4271 zfree(document->data);
4272 document->data_length = 0;
4273 documents->destructor =
4274 (void(*)(void **)) free_document_with_xml_node_list;
4275 break;
4276 case 'm':
4277 if (!document) {
4278 fprintf(stderr,
4279 _("Error: %s: Document file (-d) must precede "
4280 "MIME type (-%c)\n"), optarg, opt);
4281 retval = -1;
4282 goto leave;
4284 FILL_OR_LEAVE(document->dmMimeType, optarg);
4285 break;
4286 case 'g':
4287 if (!document) {
4288 fprintf(stderr,
4289 _("Error: %s: Document file (-d) must precede "
4290 "document ID (-%c)\n"), optarg, opt);
4291 retval = -1;
4292 goto leave;
4294 FILL_OR_LEAVE(document->dmFileGuid, optarg);
4295 break;
4296 case 'G':
4297 if (!document) {
4298 fprintf(stderr,
4299 _("Error: %s: Document file (-d) must precede "
4300 "document reference (-%c)\n"), optarg, opt);
4301 retval = -1;
4302 goto leave;
4304 FILL_OR_LEAVE(document->dmUpFileGuid, optarg);
4305 break;
4306 case 'c':
4307 if (!document) {
4308 fprintf(stderr,
4309 _("Error: Document file (-d) must precede "
4310 "document signature type (-%c)\n"), opt);
4311 retval = -1;
4312 goto leave;
4314 document->dmFileMetaType = FILEMETATYPE_SIGNATURE;
4315 break;
4317 default:
4318 shi_compose_usage(argv[0]);
4319 retval = -1;
4320 goto leave;
4324 /* All options must be recognized */
4325 if (optind != argc) {
4326 fprintf(stderr, _("Error: Superfluous argument\n"));
4327 shi_compose_usage(argv[0]);
4328 retval = -1;
4329 goto leave;
4332 if (!copies) {
4333 fprintf(stderr, _("Error: No recipient box ID specified\n"));
4334 shi_compose_usage(argv[0]);
4335 retval = -1;
4336 goto leave;
4339 /* TODO: Check Legal Title soft dependencies */
4341 /* Compose missing documents */
4342 for (i = 1, documents = message->documents; NULL != documents;
4343 documents = documents->next, i++) {
4344 document = documents->data;
4345 if (NULL == document->data) {
4346 printf(_("Editing document #%u...\n"), i);
4347 if (edit_new_textual_document(document)) {
4348 fprintf(stderr, _("Composition aborted.\n"));
4349 retval = -1;
4350 goto leave;
4355 /* Preview */
4356 oprintf(_("Following message has been composed:\n"));
4357 format_message(message);
4358 oprintf(_("Following recipients have been specified:\n"));
4359 format_copies(copies);
4360 /* TODO: correction */
4361 if (cfg_getbool(configuration, CONFIG_CONFIRM_SEND)) {
4362 if (!shi_ask_yes_no(_("Send composed message?"), 1, batch_mode)) {
4363 fprintf(stderr, _("Composition aborted.\n"));
4364 retval = -1;
4365 goto leave;
4369 /* Send a message */
4370 printf(_("Sending message...\n"));
4371 err = isds_send_message_to_multiple_recipients(cisds, message, copies);
4372 finish_isds_operation(cisds, err);
4373 if (err && err != IE_PARTIAL_SUCCESS) {
4374 retval = -1;
4375 goto leave;
4378 /* Show results for each copy */
4379 for (copy_item = copies; copy_item; copy_item = copy_item->next) {
4380 if (!copy_item->data) continue;
4381 copy = (struct isds_message_copy *) copy_item->data;
4382 recipient_id_locale = utf82locale(copy->dbIDRecipient);
4384 if (copy->error) {
4385 retval = -1;
4386 if (copy->dmStatus) dmStatus_locale = utf82locale(copy->dmStatus);
4387 if (dmStatus_locale)
4388 oprintf(_("%s: Failed: %s: %s\n"),
4389 recipient_id_locale,
4390 isds_strerror(copy->error), dmStatus_locale);
4391 else
4392 oprintf(_("%s: Failed: %s\n"), recipient_id_locale,
4393 isds_strerror(copy->error));
4394 zfree(dmStatus_locale);
4395 } else {
4396 message_id_locale = utf82locale(copy->dmID);
4397 oprintf(_("%s: Succeeded. Assigned message ID: %s\n"),
4398 recipient_id_locale, message_id_locale);
4399 free(message_id_locale);
4402 free(recipient_id_locale);
4405 leave:
4406 isds_message_free(&message);
4407 isds_list_free(&copies);
4408 return retval;
4412 #undef FILL_LONGINT_OR_LEAVE
4413 #undef FILL_BOOLEAN_OR_LEAVE
4414 #undef CALLOC_OR_LEAVE
4415 #undef FILL_OR_LEAVE
4418 static void shi_save_stamp_usage(const char *command) {
4419 oprintf(_(
4420 "Usage: %s FILE\n"
4421 "Save message time stamp into local FILE.\n"),
4422 command);
4426 static int shi_save_stamp(int argc, const char **argv) {
4427 _Bool overwrite = cfg_getbool(configuration, CONFIG_OVERWRITEFILES);
4429 if (!argv || !argv[1] || !*argv[1]) {
4430 shi_save_message_usage(argv[0]);
4431 return -1;
4434 if (!message) {
4435 fprintf(stderr, _("No message loaded\n"));
4436 return -1;
4438 if (!message->envelope || !message->envelope->timestamp||
4439 message->envelope->timestamp_length == 0) {
4440 fprintf(stderr, _("Loaded message is missing time stamp\n"));
4441 return -1;
4444 return save_data_to_file(argv[1], -1,
4445 message->envelope->timestamp, message->envelope->timestamp_length,
4446 "application/timestamp-reply", overwrite);
4450 /* Return message of current message list identified by message ID expressed
4451 * as string. In case of error return NULL. */
4452 static const struct isds_message *locate_message_by_id(const char *id_locale) {
4453 char *id = NULL;
4454 const struct isds_list *item;
4455 const struct isds_message *message = NULL;
4457 if (NULL == id_locale) return NULL;
4459 id = locale2utf8(id_locale);
4460 if (id == NULL) {
4461 fprintf(stderr, _("Could not convert message ID `%s' to UTF-8\n"),
4462 id_locale);
4463 return NULL;
4466 if (NULL == messages) {
4467 fprintf(stderr, _("No message list loaded\n"));
4468 return NULL;
4471 /* Find message */
4472 for (item = messages; NULL != item; item = item->next) {
4473 if (NULL == item->data) continue;
4474 message = (const struct isds_message *) item->data;
4475 if (NULL == message->envelope || NULL == message->envelope->dmID)
4476 continue;
4477 if (!strcmp(id, message->envelope->dmID)) {
4478 break;
4481 if (NULL == item) {
4482 fprintf(stderr, _("List does not contain message `%s'\n"), id_locale);
4483 return NULL;
4486 return message;
4490 static void shi_show_list_usage(const char *command) {
4491 oprintf(_(
4492 "Usage: %s [MESSAGE_ID]\n"
4493 "If no MESSAGE_ID is given, show current message list.\n"
4494 "If MESSAGE_ID is specified, show details about message with the MESSAGE_ID\n"
4495 "on the current message list.\n"),
4496 command);
4500 static int shi_show_list(int argc, const char **argv) {
4501 if (NULL == messages) {
4502 fprintf(stderr, _("No message list loaded\n"));
4503 return -1;
4506 if (argc > 2 || (2 == argc && NULL == argv[1])) {
4507 shi_show_list_usage((argv[0] == NULL) ? NULL : argv[0]);
4508 return -1;
4511 if (argc == 2) {
4512 /* Show details about one message */
4513 const struct isds_message *message = locate_message_by_id(argv[1]);
4514 if (NULL == message) return -1;
4515 format_message(message);
4516 return 0;
4519 /* Show all list */
4520 oprintf((messages_are_outgoing) ?
4521 ngettext("You have %'lu outgoing message\n",
4522 "You have %'lu outgoing messages\n", total_messages) :
4523 ngettext("You have %'lu incoming message\n",
4524 "You have %'lu incoming messages\n", total_messages),
4525 total_messages);
4526 print_message_list(messages, messages_are_outgoing);
4527 return 0;
4531 static void describe_message_listing_flags(void) {
4532 oprintf(_(
4533 "The third column displays flags describing status of a message:\n"
4534 " First character is a commercial type of the message:\n"
4535 " P public non-commercial message\n"
4536 " C commercial message\n"
4537 " I commercial message offering a paid response\n"
4538 " i like I, but the offer has expired\n"
4539 " R commerical message as a reply to I\n"
4540 " The second character is a delivery status of the message:\n"
4541 " > message has been sent into the system\n"
4542 " t message has been time-stamped by the system\n"
4543 " I message contained an infected document\n"
4544 " N message has been delivered ordinaly\n"
4545 " n message has been delivered substitutingly\n"
4546 " O message has been accepted by the recipient\n"
4547 " \" \" message has been read\n"
4548 " ! message could not been delivered\n"
4549 " D message content has been deleted\n"
4550 " S message has been stored in the long term storage\n"
4551 " ? unrecognized state\n"));
4555 static void shi_list_incoming_usage(const char *command) {
4556 oprintf(_(
4557 "Usage: %s\n"
4558 "List messages received into your box.\n"
4559 "\n"),
4560 command);
4561 describe_message_listing_flags();
4565 static int shi_list_incoming(int argc, const char **argv) {
4566 unsigned long int count = 0;
4567 isds_error err;
4569 printf(_("Listing incoming messages...\n"));
4570 err = isds_get_list_of_received_messages(cisds, NULL, NULL, NULL,
4571 MESSAGESTATE_ANY, 0, &count, &messages);
4572 finish_isds_operation(cisds, err);
4573 total_messages = count;
4574 if (err) {
4575 set_prompt(NULL);
4576 select_completion(COMPL_COMMAND);
4577 return -1;
4579 messages_are_outgoing = 0;
4581 shi_show_list(0, NULL);
4583 set_prompt(_("%s %'lu"), argv[0], total_messages);
4584 select_completion(COMPL_LIST);
4585 return 0;
4589 static void shi_list_outgoing_usage(const char *command) {
4590 oprintf(_(
4591 "Usage: %s\n"
4592 "List messages sent from your box.\n"
4593 "\n"),
4594 command);
4595 describe_message_listing_flags();
4599 static int shi_list_outgoing(int argc, const char **argv) {
4600 unsigned long int count = 0;
4601 isds_error err;
4603 printf(_("Listing outgoing messages...\n"));
4604 err = isds_get_list_of_sent_messages(cisds, NULL, NULL, NULL,
4605 MESSAGESTATE_ANY, 0, &count, &messages);
4606 finish_isds_operation(cisds, err);
4607 total_messages = count;
4608 if (err) {
4609 set_prompt(NULL);
4610 select_completion(COMPL_COMMAND);
4611 return -1;
4613 messages_are_outgoing = 1;
4615 shi_show_list(0, NULL);
4617 set_prompt(_("%s %'lu"), argv[0], total_messages);
4618 select_completion(COMPL_LIST);
4619 return 0;
4623 /* Submit document for conversion and print assigned identifier */
4624 static int do_convert(const struct isds_document *document) {
4625 isds_error err;
4626 char *id = NULL;
4627 struct tm *date = NULL;
4629 if (!document) return -1;
4631 printf(_("Submitting document for authorized conversion...\n"));
4633 err = czp_convert_document(czechpoint, document, &id, &date);
4634 finish_isds_operation(czechpoint, err);
4636 if (!err) {
4637 char *name_locale = utf82locale(document->dmFileDescr);
4638 char *date_string = tm2string(date);
4639 char *id_locale = utf82locale(id);
4640 oprintf(_(
4641 "Document submitted for authorized conversion successfully under name\n"
4642 "`%s' on %s.\n"
4643 "Submit identifier assigned by Czech POINT deposit is `%s'.\n"),
4644 name_locale, date_string, id_locale);
4645 free(name_locale);
4646 free(date_string);
4647 free(id_locale);
4648 oprintf(_("Be ware that submitted document has restricted lifetime "
4649 "(30 days).\n"));
4650 oprintf(_("See <%s> for more details.\n"), CZPDEPOSIT_URL);
4653 free(id); free(date);
4654 return (err) ? -1 : 0;
4658 static void shi_convert_file_or_message_usage(const char *command) {
4659 oprintf(_(
4660 "Usage: %s [FILE [NAME]]\n"
4661 "Submit local FILE to authorized conversion under NAME. If NAME is missing,\n"
4662 "it will use FILE name. If FILE is missing, it will submit current message.\n"),
4663 command);
4664 oprintf(_(
4665 "\n"
4666 "If Czech POINT deposit accepts document, it will return document identifier\n"
4667 "that user is supposed to provide to officer at Czech POINT contact place.\n"
4668 "Currently only PDF 1.3 and higher version files and signed messages are\n"
4669 "accepted.\n"));
4670 oprintf(_("See <%s> for more details.\n"), CZPDEPOSIT_URL);
4674 static int shi_convert_file_or_message(int argc, const char **argv) {
4675 int fd = -1;
4676 struct isds_document document;
4677 int retval = 0;
4679 if (!argv || argc > 3) {
4680 shi_convert_file_or_message_usage((argv)?argv[0]:NULL);
4681 return -1;
4684 memset(&document, 0, sizeof(document));
4686 if (NULL == argv[1] || !*argv[1]) {
4687 /* Convert current message */
4688 if (!message) {
4689 fprintf(stderr, _("No message loaded\n"));
4690 return -1;
4692 if (!message->raw || !message->raw_length) {
4693 fprintf(stderr,
4694 _("Current message is missing raw representation\n"));
4695 return -1;
4697 document.dmFileDescr = astrcat(
4698 (NULL != message->envelope && NULL != message->envelope->dmID) ?
4699 message->envelope->dmID :
4700 "unknown",
4701 ".zfo");
4702 if (NULL == document.dmFileDescr) {
4703 printf(_("Could not build document name from message ID\n"));
4704 return -1;
4706 document.data = message->raw;
4707 document.data_length = message->raw_length;
4708 } else {
4709 /* Convert local file */
4710 if (argc == 3 && argv[2] && *argv[2])
4711 document.dmFileDescr = locale2utf8(argv[2]);
4712 else
4713 document.dmFileDescr = locale2utf8(argv[1]);
4714 if (!document.dmFileDescr) {
4715 printf(_("Could not convert document name to UTF-8\n"));
4716 return -1;
4719 printf(_("Loading document from file `%s'...\n"), argv[1]);
4720 if (mmap_file(argv[1], &fd, &document.data, &document.data_length)) {
4721 free(document.dmFileDescr);
4722 return -1;
4726 retval = do_convert(&document);
4728 if (0 <= fd) {
4729 munmap_file(fd, document.data, document.data_length);
4731 free(document.dmFileDescr);
4732 return retval;
4736 static void shi_convert_document_usage(const char *command) {
4737 oprintf(_(
4738 "Usage: %s NUMBER\n"
4739 "Submit message document with ordinal NUMBER to authorized conversion.\n"),
4740 command);
4741 oprintf(_(
4742 "\n"
4743 "If Czech POINT deposit accepts document, it will return document identifier\n"
4744 "that user is supposed to provide to officer at Czech POINT contact place.\n"
4745 "Currently only PDF 1.3 and higher version files and signed messages are\n"
4746 "accepted.\n"));
4747 oprintf(_("See <%s> for more details.\n"), CZPDEPOSIT_URL);
4751 static int shi_convert_document(int argc, const char **argv) {
4752 const struct isds_document *document;
4754 if (!argv || !argv[1] || !*argv[1]) {
4755 shi_convert_document_usage((argv)?argv[0]:NULL);
4756 return -1;
4759 document = locate_document_by_ordinal_string(argv[1]);
4760 if (!document) return -1;
4762 return do_convert(document);
4766 static void shi_resign_usage(const char *command) {
4767 oprintf(_(
4768 "Usage: %s [FILE]\n"
4769 "Send message or delivery data to re-sign it and to add current time stamp.\n"
4770 "If FILE is specified, message will be loaded from local file. Otherwise\n"
4771 "current message will be sent.\n"),
4772 command);
4773 oprintf(_(
4774 "\n"
4775 "Only signed messages or delivery data without time stamp are accepted by\n"
4776 "ISDS. Output re-signed message or delivery data will be loaded.\n"));
4780 static int shi_resign(int argc, const char **argv) {
4781 int fd = -1;
4782 void *data = NULL; /* Static */
4783 void *resigned_data = NULL; /* Dynamic, stored into message */
4784 size_t data_length = 0, resigned_data_length = 0;
4785 struct tm *valid_to = NULL; /* Dynamic */
4786 isds_error err;
4787 int error;
4789 if (!argv || argc > 3) {
4790 shi_resign_usage((argv)?argv[0]:NULL);
4791 return -1;
4794 if (NULL == argv[1] || !*argv[1]) {
4795 /* Use current message */
4796 if (!message) {
4797 fprintf(stderr, _("No message or delivery data loaded\n"));
4798 return -1;
4800 if (!message->raw || !message->raw_length) {
4801 fprintf(stderr, _("Current message or delivery data "
4802 "is missing raw representation\n"));
4803 return -1;
4805 data = message->raw;
4806 data_length = message->raw_length;
4807 } else {
4808 /* Use local file */
4809 printf(_("Loading message or delivery data from file `%s'...\n"),
4810 argv[1]);
4811 if (mmap_file(argv[1], &fd, &data, &data_length)) {
4812 return -1;
4816 printf(_("Re-signing...\n"));
4817 err = isds_resign_message(cisds, data, data_length,
4818 &resigned_data, &resigned_data_length, &valid_to);
4819 finish_isds_operation(cisds, err);
4821 if (0 <= fd) {
4822 munmap_file(fd, data, data_length);
4825 if (err) {
4826 return -1;
4829 print_header_tm(_("New time stamp expires"), valid_to);
4830 free(valid_to);
4832 error = do_load_anything(resigned_data, resigned_data_length, BUFFER_MOVE);
4833 if (error) free(resigned_data);
4835 return error;
4839 #if ENABLE_DEBUG
4840 static void shi_print_usage(const char *command) {
4841 oprintf(_(
4842 "Usage: %s STRING LENGTH\n"
4843 "Prints STRING into LENGTH columns. Negative LENGTH means not to cut\n"
4844 "overflowing string.\n"
4845 "This should be locale and terminal agnostic.\n"),
4846 command);
4850 static int shi_print(int argc, const char **argv) {
4851 long int width;
4853 if (!argv || !argv[1] || !argv[2]) {
4854 shi_print_usage((argv)?argv[0]:NULL);
4855 return -1;
4858 width = strtol(argv[2], NULL, 10);
4859 if (width < INT_MIN) {
4860 fprintf(stderr,
4861 _("Length argument must not lesser than %d.\n"), INT_MIN);
4862 return -1;
4864 if (width > INT_MAX) {
4865 fprintf(stderr,
4866 _("Length argument must not be greater than %d.\n"), INT_MAX);
4867 return -1;
4870 oprintf(_(">"));
4871 onprint(argv[1], width);
4872 oprintf(_("<\n"));
4874 return 0;
4878 static int shi_tokenize(int argc, const char **argv) {
4880 if (!argv) return 0;
4882 for (int i = 0; i < argc; i++) {
4883 oprintf(_(">%s<\n"), argv[i]);
4885 return 0;
4889 static int shi_quote(int argc, const char **argv) {
4890 char *escaped, *unescaped;
4892 if (!argv) return 0;
4894 oprintf(_("Original\tQuoted\tDequoted\n"));
4895 for (int i = 0; i < argc; i++) {
4896 escaped = shi_quote_filename((char *) argv[i], 0, NULL);
4897 unescaped = shi_dequote_filename((char *) argv[i], 0);
4898 oprintf(_(">%s<\t>%s<\t>%s<\n"), argv[i], escaped, unescaped);
4899 free(escaped);
4900 free(unescaped);
4902 return 0;
4906 static int shi_ask_yesno(int argc, const char **argv) {
4907 _Bool answer;
4909 answer = shi_ask_yes_no(argv[1], 1, batch_mode);
4910 oprintf(_("Answer is %s.\n"), (answer) ? _("Yes") : _("No"));
4912 return 0;
4916 #endif
4919 /* pclose(pipe), restore ouput to stdout, show error return code */
4920 int wait_for_shell(FILE **pipe) {
4921 int retval = 0;
4923 if (pipe && *pipe) {
4924 retval = pclose(*pipe);
4925 *pipe = NULL;
4926 output = stdout;
4928 if (retval == -1)
4929 fprintf(stderr, _("Exit code of shell command could not "
4930 "be determined\n"));
4931 else if (WIFEXITED(retval) && WEXITSTATUS(retval))
4932 printf(_("Shell command exited with code %d\n"),
4933 WEXITSTATUS(retval));
4934 else if (WIFSIGNALED(retval))
4935 printf(_("Shell command terminated by signal "
4936 "#%d\n"), WTERMSIG(retval));
4939 return retval;
4943 /* Interactive loop */
4944 void shi_loop(void) {
4945 char *command_line = NULL;
4946 char **command_argv = NULL;
4947 int command_argc;
4948 char *shell = NULL;
4949 struct command *command = NULL;
4950 FILE *pipe = NULL;
4951 int failed = 0;
4953 oprintf(_("Use `help' command to get list of available commands.\n"));
4955 select_completion(COMPL_COMMAND);
4956 set_prompt(NULL);
4958 while (1) {
4959 command_line = readline((prompt) ? prompt : _("shigofumi> "));
4960 /* Remember not parsable commands too to user be able to get back to
4961 * fix command */
4962 if (command_line && *command_line) {
4963 /* TODO: Omit blank lines */
4964 add_history(command_line);
4967 command_argv = tokenize(command_line, &command_argc, &shell);
4969 if (command_argv && command_argv[0]) {
4970 command = find_command(command_argv[0]);
4972 if (!command) {
4973 fprintf(stderr, _("Command not understood\n"));
4974 } else {
4975 failed = 0;
4976 if (shell) {
4977 fflush(stdout);
4978 pipe = popen(shell, "w");
4979 if (!pipe) {
4980 fprintf(stderr, _("Could not run shell command `%s':"
4981 " %s\n"), shell, strerror(errno));
4982 failed = 1;
4984 output = pipe;
4986 if (!failed) {
4987 command->function(command_argc,
4988 (const char **) command_argv);
4989 wait_for_shell(&pipe);
4994 argv_free(command_argv);
4995 zfree(shell);
4996 zfree(command_line);
5001 /* Non-interactive mode. Commands from @lines are processed until any command
5002 * lines remains or no error occurred. First failure terminates processing.
5003 * @lines is sequence of commands separated by '\n' or '\r'. The content is
5004 * modified during this call.
5005 * @return 0 if all command succeed, otherwise non-zero value
5007 int shi_batch(char *lines) {
5008 char *command_line;
5009 char **command_argv = NULL;
5010 int command_argc;
5011 char *shell = NULL;
5012 struct command *command = NULL;
5013 int retval = 0;
5014 FILE *pipe = NULL;
5015 int failed = 0;
5017 oprintf(_("Batch mode started.\n"));
5018 batch_mode = 1;
5019 select_completion(COMPL_COMMAND);
5021 while (!retval && (command_line = strtok(lines, "\n\r"))) {
5022 lines = NULL; /* strtok(3) requires it for subsequent calls */
5024 printf(_("Processing command: %s\n"), command_line);
5026 command_argv = tokenize(command_line, &command_argc, &shell);
5028 if (command_argv && command_argv[0]) {
5029 command = find_command(command_argv[0]);
5031 if (!command) {
5032 fprintf(stderr, _("Command not understood\n"));
5033 retval = -1;
5034 } else {
5035 failed = 0;
5036 if (shell) {
5037 fflush(stdout);
5038 pipe = popen(shell, "w");
5039 if (!pipe) {
5040 fprintf(stderr, _("Could not run shell command `%s':"
5041 " %s\n"), shell, strerror(errno));
5042 failed = 1;
5044 output = pipe;
5046 if (!failed) {
5047 retval = command->function(command_argc,
5048 (const char **) command_argv);
5049 if (wait_for_shell(&pipe)) retval = -1;
5054 argv_free(command_argv);
5055 zfree(shell);
5058 if (retval)
5059 fprintf(stderr, _("Command failed!\n"));
5060 return retval;
5064 #define COMMON_COMMANDS \
5065 { "accept", shi_accept_message, N_("accept commercial message"), \
5066 shi_accept_message_usage, ARGTYPE_MSGID }, \
5067 { "box", shi_box, N_("show current box details"), NULL, \
5068 ARGTYPE_NONE }, \
5069 { "boxlist", shi_boxlist, N_("get list of all boxes"), shi_boxlist_usage, \
5070 ARGTYPE_FILE }, \
5071 { "cache", shi_cache, N_("show cache details"), NULL, \
5072 ARGTYPE_NONE }, \
5073 { "cd", shi_chdir, N_("change working directory"), shi_chdir_usage, \
5074 ARGTYPE_FILE }, \
5075 { "commercialcredit", shi_commercialcredit, N_("get credit details"), \
5076 shi_commercialcredit_usage, ARGTYPE_BOXID }, \
5077 { "commercialreceiving", shi_commercialreceiving, \
5078 N_("manipulate commercial receiving box status"), \
5079 shi_commercialreceiving_usage, ARGTYPE_BOXID }, \
5080 { "commercialsending", shi_commercialsending, \
5081 N_("manipulate commercial sending box status"), \
5082 shi_commercialsending_usage, ARGTYPE_BOXID }, \
5083 { "compose", shi_compose, N_("compose a message"), shi_compose_usage, \
5084 ARGTYPE_FILE }, \
5085 { "convert", shi_convert_file_or_message, \
5086 N_("submit local document for authorized conversion"), \
5087 shi_convert_file_or_message_usage, ARGTYPE_FILE }, \
5088 { "copying", shi_copying, N_("show this program licence excerpt"), NULL, \
5089 ARGTYPE_NONE }, \
5090 { "debug", shi_debug, N_("set debugging"), shi_debug_usage, \
5091 ARGTYPE_FILE }, \
5092 { "delete", shi_delete_message, N_("delete message from storage"), \
5093 shi_delete_message_usage, ARGTYPE_MSGID }, \
5094 { "delivery", shi_delivery, N_("get message delivery details"), \
5095 shi_delivery_usage, ARGTYPE_MSGID }, \
5096 { "findbox", shi_find_box, N_("search for a box by attributes"), \
5097 shi_find_box_usage, ARGTYPE_BOXID }, \
5098 { "hash", shi_hash, N_("query ISDS for message hash"), \
5099 shi_hash_usage, ARGTYPE_MSGID }, \
5100 { "help", shi_help, N_("describe commands"), shi_help_usage, \
5101 ARGTYPE_COMMAND }, \
5102 { "load", shi_load_anything, \
5103 N_("load message or message delivery details from local file"), \
5104 shi_load_anything_usage, ARGTYPE_FILE }, \
5105 { "login", shi_login, N_("log into ISDS"), shi_login_usage, \
5106 ARGTYPE_FILE }, \
5107 { "lsi", shi_list_incoming, N_("list received messages"), \
5108 shi_list_incoming_usage, ARGTYPE_NONE }, \
5109 { "lso", shi_list_outgoing, N_("list sent messages"), \
5110 shi_list_outgoing_usage, ARGTYPE_NONE }, \
5111 { "msgi", shi_incoming_message, N_("get incoming message"), \
5112 shi_incoming_message_usage, ARGTYPE_MSGID }, \
5113 { "msgo", shi_outgoing_message, N_("get outgoing message"), \
5114 shi_outgoing_message_usage, ARGTYPE_MSGID }, \
5115 { "passwd", shi_passwd, N_("manipulate user password"), shi_passwd_usage, \
5116 ARGTYPE_NONE }, \
5117 { "pwd", shi_pwd, N_("print working directory"), NULL, ARGTYPE_NONE }, \
5118 { "quit", shi_quit, N_("exit shigofumi"), NULL, ARGTYPE_NONE }, \
5119 { "read", shi_read_message, N_("mark message as read"), \
5120 shi_read_message_usage, ARGTYPE_MSGID }, \
5121 { "resign", shi_resign, N_("re-sign message or delivery data"), \
5122 shi_resign_usage, ARGTYPE_FILE }, \
5123 { "searchbox", shi_search_box, N_("search for a box by a full-text"), \
5124 shi_search_box_usage, ARGTYPE_BOXID }, \
5125 { "set", shi_settings, N_("show settings"), NULL, ARGTYPE_NONE }, \
5126 { "sender", shi_message_sender, N_("get message sender"), \
5127 shi_message_sender_usage, ARGTYPE_MSGID }, \
5128 { "statbox", shi_stat_box, N_("get status of a box"), shi_stat_box_usage, \
5129 ARGTYPE_BOXID }, \
5130 { "user", shi_user, N_("show current user details"), NULL, \
5131 ARGTYPE_NONE }, \
5132 { "users", shi_users, N_("show box users"), shi_users_usage, \
5133 ARGTYPE_NONE }, \
5134 { "version", shi_version, N_("show version of this program"), NULL, \
5135 ARGTYPE_NONE}, \
5136 { NULL, NULL, NULL, NULL, ARGTYPE_NONE }
5138 struct command base_commands[] = {
5139 #if ENABLE_DEBUG
5140 { "askyesno", shi_ask_yesno, N_("demonstrate yes-no question"), NULL,
5141 ARGTYPE_NONE },
5142 { "quote", shi_quote, N_("demonstrate argument escaping"), NULL,
5143 ARGTYPE_FILE },
5144 { "print", shi_print, N_("print string into given width"),
5145 shi_print_usage, ARGTYPE_NONE },
5146 { "tokenize", shi_tokenize, N_("demonstrate arguments tokenization"), NULL,
5147 ARGTYPE_FILE },
5148 #endif
5149 COMMON_COMMANDS
5152 struct command message_commands[] = {
5153 { "authenticate", shi_authenticate, N_("check message authenticity"),
5154 NULL, ARGTYPE_NONE },
5155 { "cat", shi_cat_message, N_("show raw current message"),
5156 shi_cat_message_usage, ARGTYPE_NONE },
5157 { "catdoc", shi_cat_document, N_("show raw document"),
5158 shi_cat_document_usage, ARGTYPE_DOCID },
5159 { "convertdoc", shi_convert_document,
5160 N_("submit document of current message for authorized conversion"),
5161 shi_convert_document_usage, ARGTYPE_DOCID },
5162 { "dump", shi_dump_message, N_("dump current message structure"),
5163 NULL, ARGTYPE_NONE },
5164 { "opendoc", shi_open_document, N_("open document using external utility"),
5165 shi_open_document_usage, ARGTYPE_DOCID },
5166 { "savestamp", shi_save_stamp,
5167 N_("save time stamp of current message into local file"),
5168 shi_save_stamp_usage, ARGTYPE_FILE },
5169 { "savedoc", shi_save_document,
5170 N_("save document of current message into local file"),
5171 shi_save_document_usage, ARGTYPE_FILE },
5172 { "save", shi_save_message, N_("save current message into local file"),
5173 shi_save_message_usage, ARGTYPE_FILE },
5174 { "show", shi_show_message, N_("show current message"), NULL,
5175 ARGTYPE_NONE },
5176 { "verify", shi_verify, N_("verify current message hash"), NULL,
5177 ARGTYPE_NONE },
5178 COMMON_COMMANDS
5181 struct command list_commands[] = {
5182 { "show", shi_show_list, N_("show current message list or list item details"),
5183 shi_show_list_usage, ARGTYPE_MSGID },
5184 COMMON_COMMANDS
5187 #undef COMMON_COMMANDS
5190 static void main_version(void) {
5191 isds_init();
5192 show_version();
5193 isds_cleanup();
5194 printf("\n");
5195 shi_copying(0, NULL);
5199 static void main_usage(const char *command) {
5200 oprintf(_(
5201 "Usage: %s [OPTION...]\n"
5202 "Access ISDS, process local data box messages or delivery details, submit\n"
5203 "document to authorized conversion.\n"
5204 "\n"
5205 "Options:\n"
5206 " -c FILE use the FILE as configuration file instead of ~/%s\n"
5207 " -e COMMANDS execute COMMANDS (new line separated) and exit\n"
5208 " -V show version info and exit\n"
5210 (command) ? command : "shigofumi",
5211 CONFIG_FILE);
5215 int main(int argc, char **argv) {
5216 int opt;
5217 char *config_file = NULL;
5218 char *batch_commands = NULL;
5219 int retval = EXIT_SUCCESS;
5221 setlocale(LC_ALL, "");
5222 #if ENABLE_NLS
5223 /* Initialize gettext */
5224 bindtextdomain(PACKAGE, LOCALEDIR);
5225 textdomain(PACKAGE);
5226 #endif
5228 /* Default output */
5229 output = stdout;
5231 /* Parse arguments */
5232 optind = 0;
5233 while ((opt = getopt(argc, (char * const *)argv, "c:e:V")) != -1) {
5234 switch (opt) {
5235 case 'c':
5236 config_file = optarg;
5237 break;
5238 case 'e':
5239 batch_commands = optarg;
5240 break;
5241 case 'V':
5242 main_version();
5243 shi_exit(EXIT_SUCCESS);
5244 break;
5245 default:
5246 main_usage((argv[0]) ? basename(argv[0]): NULL);
5247 shi_exit(EXIT_FAILURE);
5252 if (shi_init(config_file)) {
5253 shi_exit(EXIT_FAILURE);
5256 /*shi_login(NULL);*/
5258 if (batch_commands) {
5259 if (shi_batch(batch_commands))
5260 retval = EXIT_FAILURE;
5261 } else {
5262 shi_loop();
5265 shi_exit(retval);