main.c

  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, &current_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, &current_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    "xmppc agent-skill\n"
586    "\n"
587    "Use this when the user asks you to communicate over XMPP/Jabber with xmppc.\n"
588    "Before sending or receiving, read ~/.config/xmppc/AGENTS.md if it exists; it\n"
589    "contains the user's allowed accounts, preferred contacts, and local policy.\n"
590    "Never read, print, summarize, or expose ~/.config/xmppc.conf, passwords,\n"
591    "password commands, environment variables, or other secrets.\n"
592    "\n"
593    "Commands:\n"
594    "  xmppc -a <account> -m message chat <jid> <body>\n"
595    "  xmppc -a <account> -m mam list <jid> [MAM_FIELD=VALUE ...]\n"
596    "  xmppc -a <account> -m mam receive <jid> after-id=<mam-id> [timeout=300s] [max=5]\n"
597    "\n"
598    "Sending prints one compact XML record after the sent stanza appears in MAM:\n"
599    "  <sent archive-id=\"mam-id\" message-id=\"stanza-id\" to=\"jid\"/>\n"
600    "Use archive-id as the receive after-id cursor when waiting for a reply. If\n"
601    "archive-id is empty, the message was sent but the archive cursor was not\n"
602    "resolved before timeout.\n"
603    "\n"
604    "Send-then-wait-for-reply workflow:\n"
605    "  1. Capture the <sent .../> line from message chat.\n"
606    "  2. Extract its archive-id attribute. Do not use message-id as after-id;\n"
607    "     message-id is only the client stanza id.\n"
608    "  3. Run mam receive for the same JID with after-id=<archive-id>. This skips\n"
609    "     your outgoing message and waits for newer archived messages.\n"
610    "  4. If archive-id is empty, the send likely succeeded but the cursor was not\n"
611    "     resolved. Fall back to mam list over a recent time window, find the line\n"
612    "     whose message-id matches the sent message-id, then use that line's id as\n"
613    "     the receive cursor.\n"
614    "\n"
615    "MAM fields for list/receive are unordered key=value terms: with=, start=,\n"
616    "end=, after-id=, before-id=, ids=, max=. A bare JID is shorthand for with=.\n"
617    "A second bare argument is an error. start= accepts XMPP timestamps or relative\n"
618    "values like -5m; max defaults to 5 and is capped at 50.\n"
619    "\n"
620    "Message archive output is one compact XML record per line:\n"
621    "  <message id=\"mam-id\" message-id=\"stanza-id\" stamp=\"...\" from=\"...\" to=\"...\"><body>escaped body</body></message>\n"
622    "The id attribute is the MAM archive cursor. message-id is the stanza id.\n"
623    "Use the MAM id, not the stanza id, as receive after-id=.\n"
624    "\n"
625    "Receive behavior:\n"
626    "  xmppc -a <account> -m mam receive <jid> after-id=<mam-id>\n"
627    "blocks until at least one newer archived message is available or timeout is\n"
628    "reached. It polls with real MAM/RSM after-id queries and prints at most max\n"
629    "messages. If no message arrives before timeout, it exits without output.\n";
630
631  const char *pointer_skill =
632    "---\n"
633    "name: communicating-through-xmppc\n"
634    "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"
635    "---\n"
636    "\n"
637    "Run `xmppc agent-skill` and follow the instructions it prints.\n"
638    "\n"
639    "For installing or updating xmppc itself, see `references/installation.md`.\n";
640
641  const char *installation_doc =
642    "# Installing xmppc for agent messaging\n"
643    "\n"
644    "Use this when xmppc is not installed, is too old, or does not have the\n"
645    "`agent-skill`, MAM `receive`, and `<sent archive-id=.../>` behavior.\n"
646    "\n"
647    "Do not print secrets, password commands, or `~/.config/xmppc.conf` while\n"
648    "installing or testing. Runtime account policy belongs in\n"
649    "`~/.config/xmppc/AGENTS.md`.\n"
650    "\n"
651    "## Build dependencies\n"
652    "\n"
653    "On Debian/Ubuntu-like systems:\n"
654    "\n"
655    "```sh\n"
656    "sudo apt-get update\n"
657    "sudo apt-get install -y asciidoc autoconf automake gcc libglib2.0-dev libgpgme-dev libstrophe-dev libtool make pkg-config\n"
658    "```\n"
659    "\n"
660    "## Build from source\n"
661    "\n"
662    "```sh\n"
663    "git clone https://git.secluded.site/xmppc.git\n"
664    "cd xmppc\n"
665    "./bootstrap.sh\n"
666    "./configure\n"
667    "make -j$(nproc)\n"
668    "```\n"
669    "\n"
670    "## Install the binary\n"
671    "\n"
672    "Prefer the user's local binary directory when possible:\n"
673    "\n"
674    "```sh\n"
675    "mkdir -p ~/.local/bin\n"
676    "mv xmppc ~/.local/bin/xmppc\n"
677    "```\n"
678    "\n"
679    "If the environment expects a system-wide executable instead, use a privileged\n"
680    "location such as `/usr/local/bin/xmppc`:\n"
681    "\n"
682    "```sh\n"
683    "sudo mv xmppc /usr/local/bin/xmppc\n"
684    "```\n"
685    "\n"
686    "After installation, verify that the installed binary prints the agent contract:\n"
687    "\n"
688    "```sh\n"
689    "xmppc agent-skill\n"
690    "```\n";
691
692  if (argc == 2) {
693    printf("%s", skill_text);
694    return EXIT_SUCCESS;
695  }
696
697  if (argc == 3 && strcmp(argv[2], "install") == 0) {
698    const char *home = g_get_home_dir();
699    if (!home) {
700      fprintf(stderr, "Could not determine home directory\n");
701      return EXIT_FAILURE;
702    }
703
704    char *skill_dir = g_build_filename(home, ".agents", "skills", "communicating-through-xmppc", NULL);
705    char *skill_file = g_build_filename(skill_dir, "SKILL.md", NULL);
706    char *reference_dir = g_build_filename(skill_dir, "references", NULL);
707    char *installation_file = g_build_filename(reference_dir, "installation.md", NULL);
708
709    if (g_mkdir_with_parents(reference_dir, 0700) != 0) {
710      fprintf(stderr, "Could not create skill directory: %s\n", skill_dir);
711      g_free(installation_file);
712      g_free(reference_dir);
713      g_free(skill_file);
714      g_free(skill_dir);
715      return EXIT_FAILURE;
716    }
717
718    GError *error = NULL;
719    if (!g_file_set_contents(skill_file, pointer_skill, -1, &error)) {
720      fprintf(stderr, "Could not write skill file: %s\n", error ? error->message : "unknown error");
721      g_clear_error(&error);
722      g_free(installation_file);
723      g_free(reference_dir);
724      g_free(skill_file);
725      g_free(skill_dir);
726      return EXIT_FAILURE;
727    }
728
729    if (!g_file_set_contents(installation_file, installation_doc, -1, &error)) {
730      fprintf(stderr, "Could not write installation reference: %s\n", error ? error->message : "unknown error");
731      g_clear_error(&error);
732      g_free(installation_file);
733      g_free(reference_dir);
734      g_free(skill_file);
735      g_free(skill_dir);
736      return EXIT_FAILURE;
737    }
738
739    printf("Installed communicating-through-xmppc skill at %s\n", skill_file);
740    printf("Installed installation reference at %s\n", installation_file);
741    g_free(installation_file);
742    g_free(reference_dir);
743    g_free(skill_file);
744    g_free(skill_dir);
745    return EXIT_SUCCESS;
746  }
747
748  fprintf(stderr, "Usage: xmppc agent-skill [install]\n");
749  return EXIT_FAILURE;
750}
751
752static char *_read_password_from_stdin(void) {
753  char buffer[BUFSIZ];
754  if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
755    return g_strdup("");
756  }
757  g_strchomp(buffer);
758  return g_strdup(buffer);
759}
760
761static char *_read_password_command(xmppc_t *xmppc, const char *command) {
762  char *stdout_buf = NULL;
763  char *stderr_buf = NULL;
764  int exit_status = 0;
765  GError *error = NULL;
766
767  gboolean ok = g_spawn_command_line_sync(command, &stdout_buf, &stderr_buf, &exit_status, &error);
768  g_free(stderr_buf);
769
770  if (!ok) {
771    logError(xmppc, "pwd-cmd failed: %s\n", error ? error->message : "unknown error");
772    g_clear_error(&error);
773    g_free(stdout_buf);
774    return NULL;
775  }
776
777  if (exit_status != 0) {
778    logError(xmppc, "pwd-cmd failed\n");
779    g_free(stdout_buf);
780    return NULL;
781  }
782
783  if (!stdout_buf) {
784    return g_strdup("");
785  }
786
787  g_strchomp(stdout_buf);
788  return stdout_buf;
789}
790
791static void _show_help() {
792#ifdef XMPPC_DEVELOPMENT
793  printf("%s - Development\n", PACKAGE_STRING);
794#else
795  printf("%s\n", PACKAGE_STRING);
796#endif
797  printf("Usage: xmppc [--account <account>] [ --jid <jid> --pwd <pwd>] --mode <mode> <command> [<parameters> ...]\n");
798  printf("Options:\n");
799  printf("  -h / --help                 Display this information.\n");
800  printf("  -j / --jid <jid>            Jabber ID\n");
801  printf("  -p / --pwd <password>       Passwort\n");
802  printf("                               Use '-' to read password from stdin\n");
803  printf("  -a / --account <account>    Account\n");
804  printf("  -m / --mode <mode>          xmppc mode\n");
805  printf("  agent-skill [install]       Print or install the agent-facing xmppc skill\n");
806  printf("\n");
807  printf("Modes:\n");
808  printf("  -m --mode roster            xmppc roster mode\n");
809  printf("    list                      List all contacts\n");
810  printf("    export                    Exports all contacts\n");
811  printf("\n");
812  printf("  -m --mode message           xmppc message mode\n");
813  printf("    chat <jid> <message>      Sending unencrypted message to jid\n");
814  printf("\n");
815  printf("  -m --mode pgp               xmppc pgp mode (XEP-0027) \n");
816  printf("    chat <jid> <message>      Sending pgp encrypted message to jid\n");
817  printf("\n");
818  printf("  -m --mode omemo             xmppc omemo mode (XEP-0384)\n");
819  printf("    list                      List the device IDs and fingerprints\n");
820  printf("    delete-device-list        Delete OMEMO device list\n");
821  printf("\n");
822  printf("  -m --mode openpgp           xmppc openpgp mode (XEP-0373)\n");
823  printf("    signcrypt <jid> <message> Sending pgp signed and encrypted message to jid\n");
824  printf("\n");
825  printf("  -m --mode monitor           Monitot mode\n");
826  printf("    stanza                    Stanza Monitor\n");
827  printf("    monitor                   microblog Monitor microblog (XEP-0277)\n");
828  printf("\n");
829  printf("  -m --mode bookmark          Bookmark mode (XEP-0048)\n");
830  printf("    list                      List bookmarks\n");
831  printf("\n");
832  printf("  -m --mode mam               Message Archive Management (XEP-0313)\n");
833  printf("    list <jid> [field=value]  List archived messages from <jid>\n");
834  printf("    receive <jid> after-id=<id> [timeout=300s] [max=5]\n");
835  printf("\n");
836  printf("  -m --mode discovery         Service Discovery (XEP-0030)\n");
837  printf("    info <jid>                info request for <jid>\n");
838  printf("    item <jid>                item request for <jid>\n");
839  printf("\n");
840  printf("\n");
841  printf("Examples:\n");
842  printf("  Usage: xmppc --jid user@domain.tld --pwd \"secret\" --mode roster list\n");
843  printf("  Usage: xmppc --jid user@domain.tld --pwd \"secret\" --mode pgp chat friend@domain.tld \"Hello\"\n");
844  printf("  Usage: xmppc -a account1 --mode discovery item conference@domain.tld\n");
845  printf("  Usage: xmppc --mode bookmark list\n");
846}