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