1/*!
2 * @file main.c
3 *
4 * vim: expandtab:ts=2:sts=2:sw=2
5 *
6 * @authors
7 * Copyright (C) 2020 Anoxinon e.V.
8 *
9 * @copyright
10 * This file is part of xmppc.
11 *
12 * xmppc is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation, either version 3 of the License, or
15 * (at your option) any later version.
16 *
17 * xmppc is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with Foobar. If not, see <http://www.gnu.org/licenses/>.
24 *
25 * German
26 *
27 * Diese Datei ist Teil von xmppc.
28 *
29 * xmppc ist Freie Software: Sie können es unter den Bedingungen
30 * der GNU General Public License, wie von der Free Software Foundation,
31 * Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren
32 * veröffentlichten Version, weiter verteilen und/oder modifizieren.
33 *
34 * xmppc wird in der Hoffnung, dass es nützlich sein wird, aber
35 * OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
36 * Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
37 * Siehe die GNU General Public License für weitere Details.
38 *
39 * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
40 * Programm erhalten haben. Wenn nicht, siehe <https://www.gnu.org/licenses/>.
41 */
42
43/*!
44 * @mainpage xmppc - XMPP command line
45 *
46 * @section whatIsXmppc What is xmppc
47 *
48 * xmppc is a command line program for XMPP (https://xmpp.org/).
49 * The program has been written for the Debian GNU/Linux system (https://www.debian.org/).
50 *
51 * @section build Build
52 *
53 * @subsection dependencies Dependencies
54 *
55 * - The GNU C Library (glibc) - 2.28-10
56 * - GLib - 2.58.3
57 * - XMPP library libstrophe - 0.9.2-2
58 * - GPGME (GnuPG Made Easy) - 1.12.0-6
59 *
60 * @subsection compile Compile
61 *
62 * @code{.bash}
63 * ./bootstrap.sh
64 * ./configure
65 * make
66 * @endcode
67 * @section usage Usage
68 *
69 * @code{.bash}
70 * xmppc --jid <jabberid> --pwd <secret> --mode <mode> <command> <command args>
71 * @endcode
72 *
73 * @code{.bash}
74 * xmppc --jid user@domain.tld --pwd "my secret" --mode pgp chat friend@domain.tld "Hello!"
75 * xmppc --jid user@domain.tld --pwd $(pass XMPP/domain/user) --mode omemo list
76 * @endcode
77 *
78 *
79 * @section development Development
80 *
81 * - \subpage module
82 *
83 */
84
85/*!
86 * \page module Modules
87 *
88 * \section account Account
89 * \section roster Roster
90 * \section message Message
91 * \section muc Multi-User-Chat
92 * \section omemo OMEMO
93 * XEP-0384: OMEMO Encryption (Version 0.3.0 (2018-07-31)). xmppc can use used
94 * to request the users OMEMO Device ID List and displays the Fingerprints of
95 * the keys.
96 *
97 * @code{.bash}
98 * xmppc --jid user@domain.tld --pwd $(pass domain.tld/user) --mode omemo
99 * @endcode
100 *
101 * @code{.bash}
102 * xmppc --jid user@domain.tld --pwd $(pass domain.tld/user) --mode omemo| gpg --clear-sign --sign-with 123ABC_MY_OPENPGP_KEY_1234 > omemo.asc
103 * @endcode
104 *
105 *
106 * \section pgp PGP
107 * XEP-0027: Current Jabber OpenPGP Usage
108 * (https://xmpp.org/extensions/xep-0027.html) is obsolete, but there are still
109 * clients which supports XEP-0027 instead of XEP-0373 / XEP-0374. This module
110 * supports sending of OpenPGP encrypted messages via XMPP's XEP-0027.
111 *
112 * @code{.bash}
113 * xmppc --jid user@domain.tld --pwd "my secret" --mode pgp friend@domain.tld "Hello!"
114 * @endcode
115 *
116 * xmppc use GnuPG's GPGME API use lookup the own key and the key of the
117 * recipient. Those two keys will be used to encrypt the message.
118 *
119 * Details \subpage module-pgp
120 *
121 */
122
123#include "config.h"
124#include <assert.h>
125#include <getopt.h>
126#include <stdarg.h>
127#include <stdio.h>
128#include <stdlib.h>
129#include <string.h>
130#include <glib.h>
131#include <glib/gstdio.h>
132#include <strophe.h>
133#include <errno.h>
134#include <termios.h>
135#include <unistd.h>
136
137#include "xmppc.h"
138#include "mode/account.h"
139#include "mode/message.h"
140#include "mode/muc.h"
141#include "mode/omemo.h"
142#include "mode/roster.h"
143#include "mode/pgp.h"
144#include "mode/openpgp.h"
145#include "mode/monitor.h"
146#include "mode/mam.h"
147#include "mode/discovery.h"
148#include "mode/bookmark.h"
149
150/*!
151 * @brief The callback structure.
152 *
153 * This struct is used to call the "execution"-function of the mode module (e.g
154 * account_execute_command, roster_execute_command, ...).
155 *
156 * @authors DebxWoody
157 * @since 0.0.2
158 * @version 1
159 *
160 */
161
162typedef struct {
163 /*! Size of arguments */
164 int argc;
165 /*! Array of C-Strings (arguments) with size of argc */
166 char **argv;
167 /*! Pointer to Execute-Handler */
168 ExecuteHandler callback;
169 /*! XMPPC context object */
170 xmppc_t *xmppc;
171} callback_t;
172
173/*!
174 * @brief Mode Mapping.
175 *
176 * This struct is used to provider the mapping of the mode name (name via
177 * command line interface of the user (e.g. -m account). The technical ID of the
178 * mode which is defined via enum xmppc_mode_t and the pointer of function which
179 * should be used to handle those mode.
180 *
181 * @authors DebxWoody
182 * @since 0.0.2
183 * @version 1
184 */
185
186struct mode_mapping {
187 const char *name;
188 xmppc_mode_t mode;
189 ExecuteHandler callback;
190};
191
192/*!
193 * @brief Mode mapping table.
194 *
195 * Mapping of mode's string, enum and function.
196 *
197 * @authors DebxWoody
198 * @since 0.0.2
199 * @version 1
200 *
201 */
202
203static struct mode_mapping map[] = {
204 /*!
205 * Account Mode Mapping
206 * @since 0.0.2
207 * @version 1
208 */
209 {"account", ACCOUNT, account_execute_command},
210 /*!
211 * roster Mode Mapping
212 * @since 0.0.2
213 * @version 1
214 */
215 {"roster", ROSTER, roster_execute_command},
216 /*!
217 * Message Mode Mapping
218 * @since 0.0.2
219 * @version 1
220 */
221 {"message", MESSAGE, message_execute_command},
222 /*!
223 * MUC Mode Mapping
224 * @since 0.0.2
225 * @version 1
226 */
227 {"muc", MUC, muc_execute_command},
228 /*!
229 * OMEMO Mode Mapping
230 * @since 0.0.2
231 * @version 1
232 */
233 {"omemo", OMEMO, omemo_execute_command},
234 /*!
235 * PGP Mode Mapping
236 * @since 0.0.2
237 * @version 1
238 */
239 {"pgp", PGP, pgp_execute_command},
240 /*!
241 * XEP-0373: OpenPGP for XMPP
242 */
243 {"openpgp", OPENPGP, openpgp_execute_command},
244 /*!
245 * Monitor
246 */
247 {"monitor", MONITOR, monitor_execute_command},
248 //
249 {"discovery", DISCOVERY, discovery_execute_command},
250 //
251 {"bookmark", BOOKMARK, bookmark_execute_command},
252 //
253 {"mam", MAM, mam_execute_command},
254 // End of Map
255 {NULL, 0}
256};
257
258/*!
259 * \brief Connection Handler Callback of libstrophe.
260 *
261 * This function will be called by libstrophe.
262 *
263 * \param conn See libstrophe documentation
264 * \param status See libstrophe documentation
265 * \param error See libstrophe documentation
266 * \param stream_error See libstrophe documentation
267 *
268 * \param userdata callback_t object for the mode request by user.
269 *
270 * @authors DebxWoody
271 * @since 0.0.2
272 * @version 1
273 *
274 */
275
276void conn_handler(xmpp_conn_t *const conn, const xmpp_conn_event_t status,
277 const int error, xmpp_stream_error_t *const stream_error,
278 void *const userdata) {
279 callback_t *callback = (callback_t *)userdata;
280
281 if( error != 0 ) {
282 logError(callback->xmppc,"Connection failed. %s\n", strerror(error));
283 xmpp_stop(xmpp_conn_get_context(conn));
284 return;
285 }
286
287 if( stream_error != NULL ) {
288 logError(callback->xmppc,"Connection failed. %s\n", stream_error->text);
289 xmpp_stop(xmpp_conn_get_context(conn));
290 return;
291 }
292
293
294 switch (status) {
295 case XMPP_CONN_CONNECT:
296 logInfo(callback->xmppc, "Connected\n");
297 if( xmpp_conn_is_secured(conn) ) {
298 logInfo(callback->xmppc, "Secure connection!\n");
299 } else {
300 logWarn(callback->xmppc, "Connection not secure!\n");
301 }
302 callback->callback(callback->xmppc, callback->argc, callback->argv);
303 break;
304 case XMPP_CONN_RAW_CONNECT:
305 case XMPP_CONN_DISCONNECT:
306 case XMPP_CONN_FAIL:
307 logInfo(callback->xmppc, "Stopping XMPP!\n");
308 xmpp_stop(xmpp_conn_get_context(conn));
309 }
310}
311
312static void _show_help();
313static int _agent_skill_command(int argc, char *argv[]);
314static char *_read_password_from_stdin(void);
315static char *_read_password_command(xmppc_t *xmppc, const char *command);
316
317/*!
318 * \brief C-Main function
319 *
320 * - Parse argv arguments of the command line.
321 * - Checks jid and password for xmpp logging
322 * - Checks mode requested by user
323 * - All other arguments will be used as mode parameters
324 *
325 * \param argc Arguments counter
326 * \param argv Arguments vector
327 * \returns Exit Code
328 *
329 * @authors DebxWoody
330 * @since 0.0.2
331 * @version 2
332 */
333
334int main(int argc, char *argv[]) {
335 if(argc < 2) {
336 _show_help();
337 return EXIT_FAILURE;
338 }
339
340 if (strcmp(argv[1], "agent-skill") == 0) {
341 return _agent_skill_command(argc, argv);
342 }
343
344 INIT_XMPPC(xmppc);
345
346 static int verbose_flag = 0;
347 int c = 0;
348 xmppc_mode_t mode = UNKOWN;
349 char *jid = NULL;
350 char *pwd = NULL;
351 char *account = NULL;
352 GKeyFile *config_file = NULL;
353 GString *configfile = NULL;
354 GError *error = NULL;
355 int paramc = 0;
356 char **paramv = NULL;
357 int exit_code = EXIT_FAILURE;
358
359 static struct option long_options[] = {
360 /* These options set a flag. */
361 {"verbose", no_argument, &verbose_flag, 1},
362 {"help", no_argument, 0, 'h'},
363 {"config", required_argument, 0, 'c'},
364 {"account", required_argument, 0, 'a'},
365 {"jid", required_argument, 0, 'j'},
366 {"pwd", required_argument, 0, 'p'},
367 {"mode", required_argument, 0, 'm'},
368 {"file", required_argument, 0, 'f'},
369 {0, 0, 0, 0}};
370
371 // No arguments provided
372 if (argc == 1) {
373 _show_help();
374 return EXIT_SUCCESS;
375 }
376
377 while (c > -1) {
378 int option_index = 0;
379
380 c = getopt_long(argc, argv, "hva:j:p:m:", long_options, &option_index);
381 if (c > -1) {
382 switch (c) {
383 case 'h':
384 _show_help();
385 exit_code = EXIT_SUCCESS;
386 goto cleanup;
387
388 case 'c':
389 printf("option -c with value `%s'\n", optarg);
390 break;
391
392 case 'f':
393 printf("option -f with value `%s'\n", optarg);
394 break;
395
396 case 'a':
397 g_free(account);
398 account = g_strdup(optarg);
399 break;
400
401 case 'j':
402 g_free(jid);
403 jid = g_strdup(optarg);
404 break;
405
406 case 'p':
407 if (strcmp(optarg, "-") == 0) {
408 pwd = _read_password_from_stdin();
409 } else {
410 pwd = g_strdup(optarg);
411 }
412 break;
413
414 case 'm':
415 for(int i = 0; map[i].name;i++ ) {
416 if (strcmp(optarg, map[i].name) == 0) {
417 mode = map[i].mode;
418 break;
419 }
420 }
421 break;
422
423 case 'v':
424 verbose_flag++;
425 break;
426
427 case '?':
428 break;
429
430 default:
431 abort();
432 }
433 }
434 }
435
436 // Loading config file
437 config_file = g_key_file_new();
438 configfile = g_string_new( g_get_home_dir());
439 g_string_append(configfile,"/.config/xmppc.conf");
440 gboolean configfilefound = g_key_file_load_from_file(
441 config_file,
442 configfile->str,
443 G_KEY_FILE_NONE,
444 &error);
445
446 if (!configfilefound) {
447 if(error && !g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
448 logError(&xmppc, "Error loading key file: %s\n", error->message);
449 goto cleanup;
450 }
451 if(jid == NULL && account == NULL) {
452 printf("You need either --jid or --account parameter or a default account\n");
453 _show_help();
454 goto cleanup;
455 }
456 }
457 g_clear_error(&error);
458
459 if(account != NULL || jid == NULL) {
460 logInfo(&xmppc,"Loading default account\n");
461 if( account == NULL ) {
462 account = g_strdup("default");
463 }
464 if (jid == NULL) {
465 jid = g_key_file_get_value (config_file, account, "jid" ,&error);
466 if( error ) {
467 logError(&xmppc, "No jid found in configuration file. %s\n", error->message);
468 goto cleanup;
469 }
470 g_clear_error(&error);
471 }
472
473 if (pwd == NULL) {
474 pwd = g_key_file_get_value (config_file, account, "pwd", &error);
475 if (!pwd) {
476 g_clear_error(&error);
477 char *pwd_cmd = g_key_file_get_value(config_file, account, "pwd-cmd", &error);
478 if (pwd_cmd) {
479 pwd = _read_password_command(&xmppc, pwd_cmd);
480 g_free(pwd_cmd);
481 if (!pwd) {
482 goto cleanup;
483 }
484 }
485 g_clear_error(&error);
486 }
487 g_clear_error(&error);
488 }
489 }
490
491 if ( !pwd ) {
492 static struct termios current_terminal;
493 static struct termios pwd_terminal;
494
495 tcgetattr(STDIN_FILENO, ¤t_terminal);
496
497 pwd_terminal = current_terminal;
498 pwd_terminal.c_lflag &= ~(ECHO);
499 tcsetattr(STDIN_FILENO, TCSANOW, &pwd_terminal);
500 printf("Password for %s:", jid);
501 pwd = malloc(sizeof(char) * BUFSIZ);
502 if (fgets(pwd, BUFSIZ, stdin) == NULL) {
503 pwd[0] = '\0';
504 } else {
505 size_t pwd_len = strlen(pwd);
506 if (pwd_len > 0 && pwd[pwd_len - 1] == '\n') {
507 pwd[pwd_len - 1] = '\0';
508 }
509 }
510 tcsetattr(STDIN_FILENO, TCSANOW, ¤t_terminal);
511 printf("\n");
512 }
513
514 paramc = argc- optind;
515 paramv = g_new0(char *, paramc);
516
517 for (int i = optind; i < argc; i++) {
518 char* x= g_strdup(argv[i]);
519 paramv[i-optind] = x;
520 }
521 xmppc_context(&xmppc, verbose_flag);
522
523 logInfo(&xmppc, "Connecting %s ... ", jid);
524 xmppc_connect(&xmppc, jid, pwd);
525
526 ExecuteHandler handler = NULL;
527 for(int i = 0; map[i].name;i++ ) {
528 if (mode == map[i].mode) {
529 handler = map[i].callback;
530 break;
531 }
532 }
533
534 if( handler == NULL ) {
535 logError(&xmppc, "Unknown mode\n");
536 xmpp_conn_release(xmppc.conn);
537 xmpp_ctx_free(xmppc.ctx);
538 xmpp_shutdown();
539 goto cleanup;
540 }
541
542 callback_t callback = {paramc, paramv, handler, &xmppc};
543
544#if XMPPC_DEVELOPMENT
545 xmpp_conn_set_flags(xmppc.conn, XMPP_CONN_FLAG_TRUST_TLS);
546#else
547 xmpp_conn_set_flags(xmppc.conn, XMPP_CONN_FLAG_MANDATORY_TLS);
548#endif
549
550 int e = xmpp_connect_client(xmppc.conn, NULL, 0, conn_handler, &callback);
551 if(XMPP_EOK != e ) {
552 printf("xmpp_connect_client failed");
553 }
554
555 xmpp_run(xmppc.ctx);
556 xmpp_conn_release(xmppc.conn);
557 xmpp_ctx_free(xmppc.ctx);
558 xmpp_shutdown();
559
560 exit_code = EXIT_SUCCESS;
561
562cleanup:
563 if (paramv) {
564 for (int i = 0; i < paramc; i++) {
565 g_free(paramv[i]);
566 }
567 g_free(paramv);
568 }
569 if (config_file) {
570 g_key_file_free(config_file);
571 }
572 if (configfile) {
573 g_string_free(configfile, TRUE);
574 }
575 g_clear_error(&error);
576 g_free(account);
577 g_free(jid);
578 g_free(pwd);
579
580 return exit_code;
581}
582
583static int _agent_skill_command(int argc, char *argv[]) {
584 const char *skill_text =
585 "Before sending or receiving, read ~/.config/xmppc/AGENTS.md if it exists; it\n"
586 "contains the user's allowed accounts, preferred contacts, and local policy.\n"
587 "Never read, print, or expose ~/.config/xmppc.conf.\n"
588 "\n"
589 "Commands:\n"
590 " xmppc -a <account> -m message chat <jid> <body>\n"
591 " xmppc -a <account> -m mam list <jid> [MAM_FIELD=VALUE ...]\n"
592 " xmppc -a <account> -m mam receive <jid> after-id=<mam-id> [timeout=300s] [max=5]\n"
593 "\n"
594 "Sending prints one compact XML record after the sent stanza appears in MAM:\n"
595 " <sent archive-id=\"mam-id\" message-id=\"stanza-id\" to=\"jid\"/>\n"
596 "Use archive-id as the receive after-id cursor when waiting for a reply. If\n"
597 "archive-id is empty, the message was sent but the archive cursor was not\n"
598 "resolved before timeout.\n"
599 "\n"
600 "Send-then-wait-for-reply workflow:\n"
601 " 1. Capture the <sent .../> line from message chat.\n"
602 " 2. Extract its archive-id attribute. Do not use message-id as after-id;\n"
603 " message-id is only the client stanza id.\n"
604 " 3. Run mam receive for the same JID with after-id=<archive-id>. This skips\n"
605 " your outgoing message and waits for newer archived messages.\n"
606 " 4. If archive-id is empty, the send likely succeeded but the cursor was not\n"
607 " resolved. Fall back to mam list over a recent time window, find the line\n"
608 " whose message-id matches the sent message-id, then use that line's id as\n"
609 " the receive cursor.\n"
610 "\n"
611 "MAM fields for list/receive are unordered key=value terms: with=, start=,\n"
612 "end=, after-id=, before-id=, ids=, max=. A bare JID is shorthand for with=.\n"
613 "A second bare argument is an error. start= accepts XMPP timestamps or relative\n"
614 "values like -5m; max defaults to 5 and is capped at 50.\n"
615 "\n"
616 "Message archive output is one XML record per line:\n"
617 " <message id=\"mam-id\" message-id=\"stanza-id\" stamp=\"...\" from=\"...\" to=\"...\"><body>escaped body</body></message>\n"
618 "The id attribute is the MAM archive cursor. message-id is the stanza id.\n"
619 "Use the MAM id, not the stanza id, as receive after-id=.\n"
620 "\n"
621 "Receive behavior:\n"
622 " xmppc -a <account> -m mam receive <jid> after-id=<mam-id>\n"
623 "blocks until at least one newer archived message is available or timeout is\n"
624 "reached. It polls with real MAM/RSM after-id queries and prints at most max\n"
625 "messages. If no message arrives before timeout, it exits without output.\n";
626
627 const char *pointer_skill =
628 "---\n"
629 "name: communicating-through-xmppc\n"
630 "description: \"Communicates with the user and other contacts over XMPP/Jabber using xmppc. Use when asked to send, list, or receive Jabber/XMPP messages.\"\n"
631 "user-invocable: true\n"
632 "---\n"
633 "\n"
634 "Run `xmppc agent-skill` and follow the instructions it prints.\n"
635 "\n"
636 "For installing or updating xmppc itself, see `references/installation.md`.\n";
637
638 const char *installation_doc =
639 "Do not print secrets, password commands, or `~/.config/xmppc.conf` while\n"
640 "installing or testing. Runtime account policy belongs in\n"
641 "`~/.config/xmppc/AGENTS.md`.\n"
642 "\n"
643 "## Build dependencies\n"
644 "\n"
645 "On Debian/Ubuntu-like systems:\n"
646 "\n"
647 "```sh\n"
648 "sudo apt-get update\n"
649 "sudo apt-get install -y asciidoc autoconf automake gcc libglib2.0-dev libgpgme-dev libstrophe-dev libtool make pkg-config\n"
650 "```\n"
651 "\n"
652 "## Build from source\n"
653 "\n"
654 "```sh\n"
655 "git clone https://git.secluded.site/xmppc.git\n"
656 "cd xmppc\n"
657 "./bootstrap.sh\n"
658 "./configure\n"
659 "make -j$(nproc)\n"
660 "```\n"
661 "\n"
662 "## Install the binary\n"
663 "\n"
664 "Prefer the user's local binary directory when possible:\n"
665 "\n"
666 "```sh\n"
667 "mkdir -p ~/.local/bin\n"
668 "mv xmppc ~/.local/bin/xmppc\n"
669 "```\n"
670 "\n"
671 "If the environment expects a system-wide executable instead, use a privileged\n"
672 "location such as `/usr/local/bin/xmppc`:\n"
673 "\n"
674 "```sh\n"
675 "sudo mv xmppc /usr/local/bin/xmppc\n"
676 "```\n"
677 "\n"
678 "After installation, verify that the installed binary prints the agent contract:\n"
679 "\n"
680 "```sh\n"
681 "xmppc agent-skill\n"
682 "```\n";
683
684 if (argc == 2) {
685 printf("%s", skill_text);
686 return EXIT_SUCCESS;
687 }
688
689 if (argc == 3 && strcmp(argv[2], "install") == 0) {
690 const char *home = g_get_home_dir();
691 if (!home) {
692 fprintf(stderr, "Could not determine home directory\n");
693 return EXIT_FAILURE;
694 }
695
696 char *skill_dir = g_build_filename(home, ".agents", "skills", "communicating-through-xmppc", NULL);
697 char *skill_file = g_build_filename(skill_dir, "SKILL.md", NULL);
698 char *reference_dir = g_build_filename(skill_dir, "references", NULL);
699 char *installation_file = g_build_filename(reference_dir, "installation.md", NULL);
700
701 if (g_mkdir_with_parents(reference_dir, 0700) != 0) {
702 fprintf(stderr, "Could not create skill directory: %s\n", skill_dir);
703 g_free(installation_file);
704 g_free(reference_dir);
705 g_free(skill_file);
706 g_free(skill_dir);
707 return EXIT_FAILURE;
708 }
709
710 GError *error = NULL;
711 if (!g_file_set_contents(skill_file, pointer_skill, -1, &error)) {
712 fprintf(stderr, "Could not write skill file: %s\n", error ? error->message : "unknown error");
713 g_clear_error(&error);
714 g_free(installation_file);
715 g_free(reference_dir);
716 g_free(skill_file);
717 g_free(skill_dir);
718 return EXIT_FAILURE;
719 }
720
721 if (!g_file_set_contents(installation_file, installation_doc, -1, &error)) {
722 fprintf(stderr, "Could not write installation reference: %s\n", error ? error->message : "unknown error");
723 g_clear_error(&error);
724 g_free(installation_file);
725 g_free(reference_dir);
726 g_free(skill_file);
727 g_free(skill_dir);
728 return EXIT_FAILURE;
729 }
730
731 printf("Installed communicating-through-xmppc skill at %s\n", skill_file);
732 printf("Installed installation reference at %s\n", installation_file);
733 g_free(installation_file);
734 g_free(reference_dir);
735 g_free(skill_file);
736 g_free(skill_dir);
737 return EXIT_SUCCESS;
738 }
739
740 fprintf(stderr, "Usage: xmppc agent-skill [install]\n");
741 return EXIT_FAILURE;
742}
743
744static char *_read_password_from_stdin(void) {
745 char buffer[BUFSIZ];
746 if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
747 return g_strdup("");
748 }
749 g_strchomp(buffer);
750 return g_strdup(buffer);
751}
752
753static char *_read_password_command(xmppc_t *xmppc, const char *command) {
754 char *stdout_buf = NULL;
755 char *stderr_buf = NULL;
756 int exit_status = 0;
757 GError *error = NULL;
758
759 gboolean ok = g_spawn_command_line_sync(command, &stdout_buf, &stderr_buf, &exit_status, &error);
760 g_free(stderr_buf);
761
762 if (!ok) {
763 logError(xmppc, "pwd-cmd failed: %s\n", error ? error->message : "unknown error");
764 g_clear_error(&error);
765 g_free(stdout_buf);
766 return NULL;
767 }
768
769 if (exit_status != 0) {
770 logError(xmppc, "pwd-cmd failed\n");
771 g_free(stdout_buf);
772 return NULL;
773 }
774
775 if (!stdout_buf) {
776 return g_strdup("");
777 }
778
779 g_strchomp(stdout_buf);
780 return stdout_buf;
781}
782
783static void _show_help() {
784#ifdef XMPPC_DEVELOPMENT
785 printf("%s - Development\n", PACKAGE_STRING);
786#else
787 printf("%s\n", PACKAGE_STRING);
788#endif
789 printf("Usage: xmppc [--account <account>] [ --jid <jid> --pwd <pwd>] --mode <mode> <command> [<parameters> ...]\n");
790 printf("Options:\n");
791 printf(" -h / --help Display this information.\n");
792 printf(" -j / --jid <jid> Jabber ID\n");
793 printf(" -p / --pwd <password> Passwort\n");
794 printf(" Use '-' to read password from stdin\n");
795 printf(" -a / --account <account> Account\n");
796 printf(" -m / --mode <mode> xmppc mode\n");
797 printf(" agent-skill [install] Print or install the agent-facing xmppc skill\n");
798 printf("\n");
799 printf("Modes:\n");
800 printf(" -m --mode roster xmppc roster mode\n");
801 printf(" list List all contacts\n");
802 printf(" export Exports all contacts\n");
803 printf("\n");
804 printf(" -m --mode message xmppc message mode\n");
805 printf(" chat <jid> <message> Sending unencrypted message to jid\n");
806 printf("\n");
807 printf(" -m --mode pgp xmppc pgp mode (XEP-0027) \n");
808 printf(" chat <jid> <message> Sending pgp encrypted message to jid\n");
809 printf("\n");
810 printf(" -m --mode omemo xmppc omemo mode (XEP-0384)\n");
811 printf(" list List the device IDs and fingerprints\n");
812 printf(" delete-device-list Delete OMEMO device list\n");
813 printf("\n");
814 printf(" -m --mode openpgp xmppc openpgp mode (XEP-0373)\n");
815 printf(" signcrypt <jid> <message> Sending pgp signed and encrypted message to jid\n");
816 printf("\n");
817 printf(" -m --mode monitor Monitot mode\n");
818 printf(" stanza Stanza Monitor\n");
819 printf(" monitor microblog Monitor microblog (XEP-0277)\n");
820 printf("\n");
821 printf(" -m --mode bookmark Bookmark mode (XEP-0048)\n");
822 printf(" list List bookmarks\n");
823 printf("\n");
824 printf(" -m --mode mam Message Archive Management (XEP-0313)\n");
825 printf(" list <jid> [field=value] List archived messages from <jid>\n");
826 printf(" receive <jid> after-id=<id> [timeout=300s] [max=5]\n");
827 printf("\n");
828 printf(" -m --mode discovery Service Discovery (XEP-0030)\n");
829 printf(" info <jid> info request for <jid>\n");
830 printf(" item <jid> item request for <jid>\n");
831 printf("\n");
832 printf("\n");
833 printf("Examples:\n");
834 printf(" Usage: xmppc --jid user@domain.tld --pwd \"secret\" --mode roster list\n");
835 printf(" Usage: xmppc --jid user@domain.tld --pwd \"secret\" --mode pgp chat friend@domain.tld \"Hello\"\n");
836 printf(" Usage: xmppc -a account1 --mode discovery item conference@domain.tld\n");
837 printf(" Usage: xmppc --mode bookmark list\n");
838}