web: Release announcement for 2.2.11
[gnupg-doc.git] / tools / txxmpp.c
1 /* txxmpp.c - Transmit a message to an XMPP account
2  * Copyright (C) 2017  g10 Code GmbH
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, see <https://www.gnu.org/licenses/>.
16  * SPDX-License-Identifier: GPL-3.0+
17  */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <stdarg.h>
23 #include <errno.h>
24 #include <ctype.h>
25 #include <strophe.h>  /* The low-level xmpp library.  */
26
27 #define PGMNAME "txxmpp"
28 #define VERSION "0.9"
29
30 #define DIM(v)               (sizeof(v)/sizeof((v)[0]))
31 #define DIMof(type,member)   DIM(((type *)0)->member)
32 #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)
33 #define ATTR_PRINTF(a,b)    __attribute__ ((format (printf,a,b)))
34 #define ATTR_NR_PRINTF(a,b) __attribute__ ((noreturn,format (printf,a,b)))
35 #else
36 #define ATTR_PRINTF(a,b)
37 #define ATTR_NR_PRINTF(a,b)
38 #endif
39 #if __GNUC__ >= 4
40 # define ATTR_SENTINEL(a) __attribute__ ((sentinel(a)))
41 #else
42 # define ATTR_SENTINEL(a)
43 #endif
44
45
46 static int opt_verbose;
47 static int opt_debug;
48 static const char *opt_user;
49 static const char *opt_pass;
50 static const char *opt_resource;
51 static const char *opt_subject;
52 static int opt_chat;
53 static char **opt_recipients; /* NULL terminated array with recipients.  */
54 static char *the_message;
55
56 static void die (const char *format, ...) ATTR_NR_PRINTF(1,2);
57 static void err (const char *format, ...) ATTR_PRINTF(1,2);
58 static void inf (const char *format, ...) ATTR_PRINTF(1,2);
59 static void dbg (const char *format, ...) ATTR_PRINTF(1,2);
60 static char *xstrconcat (const char *s1, ...) ATTR_SENTINEL(0);
61
62
63 /*
64  * Utility functions
65  */
66
67 static void
68 die (const char *fmt, ...)
69 {
70   va_list arg_ptr;
71
72   va_start (arg_ptr, fmt);
73   fputs (PGMNAME": fatal: ", stderr);
74   vfprintf (stderr, fmt, arg_ptr);
75   va_end (arg_ptr);
76   exit (1);
77 }
78
79
80 static void
81 err (const char *fmt, ...)
82 {
83   va_list arg_ptr;
84
85   va_start (arg_ptr, fmt);
86   fputs (PGMNAME": ", stderr);
87   vfprintf (stderr, fmt, arg_ptr);
88   va_end (arg_ptr);
89 }
90
91
92 static void
93 inf (const char *fmt, ...)
94 {
95   va_list arg_ptr;
96
97   if (!opt_verbose && !opt_debug)
98     return;
99
100   va_start (arg_ptr, fmt);
101   fputs (PGMNAME": ", stderr);
102   vfprintf (stderr, fmt, arg_ptr);
103   va_end (arg_ptr);
104 }
105
106
107 static void
108 dbg (const char *fmt, ...)
109 {
110   va_list arg_ptr;
111
112   if (!opt_debug)
113     return;
114
115   va_start (arg_ptr, fmt);
116   fputs (PGMNAME": DBG: ", stderr);
117   vfprintf (stderr, fmt, arg_ptr);
118   va_end (arg_ptr);
119 }
120
121
122 static void *
123 xmalloc (size_t n)
124 {
125   void *p = malloc (n);
126   if (!p)
127     die ("out of core\n");
128   return p;
129 }
130
131
132 static void *
133 xrealloc (void *a, size_t newsize)
134 {
135   void *p = realloc (a, newsize);
136   if (!p && newsize)
137     die ("out of core\n");
138   return p;
139 }
140
141
142 static char *
143 xstrdup (const char *string)
144 {
145   char *buf = xmalloc (strlen (string) + 1);
146   strcpy (buf, string);
147   return buf;
148 }
149
150
151 static inline char *
152 my_stpcpy (char *a, const char *b)
153 {
154   while (*b)
155     *a++ = *b++;
156   *a = 0;
157
158   return (char*)a;
159 }
160
161
162 /* Helper for xstrconcat.  */
163 static char *
164 do_strconcat (const char *s1, va_list arg_ptr)
165 {
166   const char *argv[48];
167   size_t argc;
168   size_t needed;
169   char *buffer, *p;
170
171   argc = 0;
172   argv[argc++] = s1;
173   needed = strlen (s1);
174   while (((argv[argc] = va_arg (arg_ptr, const char *))))
175     {
176       needed += strlen (argv[argc]);
177       if (argc >= DIM (argv)-1)
178         die ("too may args for strconcat\n");
179       argc++;
180     }
181   needed++;
182   buffer = xmalloc (needed);
183   for (p = buffer, argc=0; argv[argc]; argc++)
184     p = my_stpcpy (p, argv[argc]);
185
186   return buffer;
187 }
188
189
190 /* Concatenate the string S1 with all the following strings up to a
191    NULL.  Returns a malloced buffer with the new string or NULL on a
192    malloc error or if too many arguments are given.  */
193 static char *
194 xstrconcat (const char *s1, ...)
195 {
196   va_list arg_ptr;
197   char *result;
198
199   if (!s1)
200     result = xstrdup ("");
201   else
202     {
203       va_start (arg_ptr, s1);
204       result = do_strconcat (s1, arg_ptr);
205       va_end (arg_ptr);
206     }
207   return result;
208 }
209
210
211 /* Remove leading and trailing white space from STR.  */
212 static char *
213 trim_spaces (char *string)
214 {
215   unsigned char *s, *p, *mark;
216
217   p = s = (unsigned char *)string;
218   for (; *p && isspace (*p); p++)
219     ;
220   for (mark=NULL; (*s = *p); s++, p++)
221     {
222       if (isspace (*p))
223         {
224           if (!mark)
225             mark = s;
226         }
227       else
228         mark = NULL;
229     }
230   if (mark)
231     *mark = 0;
232
233   return string;
234 }
235
236
237 /* Read up to MAXLENGTH bytes from FP into a buffer and return that
238  * buffer.  Die on error.  */
239 static char *
240 xreadfile (FILE *fp, size_t maxlength)
241 {
242 #define NCHUNK 8192
243   char *buf = NULL;
244   size_t buflen = 0;
245   size_t nread;
246   size_t bufsize = 0;
247
248   do
249     {
250       bufsize += NCHUNK;
251       buf = xrealloc (buf, bufsize + 1);
252
253       nread = fread (buf+buflen, 1, NCHUNK, fp);
254       if (nread < NCHUNK && ferror (fp))
255         {
256           err ("error reading input: %s\n", strerror (errno));
257           free (buf);
258           exit (1);
259         }
260       buflen += nread;
261       if (maxlength && buflen >= maxlength)
262         {
263           buflen = maxlength;
264           break;
265         }
266     }
267   while (nread == NCHUNK);
268
269   buf[buflen] = 0;
270   if (strlen (buf) != buflen)
271     err ("warning: Nul characters detected in the input\n");
272   return buf;
273 #undef NCHUNK
274 }
275
276
277 \f
278 /*
279  * txxmpp proper
280  */
281
282 static xmpp_stanza_t *
283 new_name_stanza (xmpp_ctx_t *ctx, const char *name)
284 {
285   xmpp_stanza_t *stanza;
286   int rc;
287
288   stanza = xmpp_stanza_new (ctx);
289   if (!stanza)
290     die ("xmpp_stanza_new failed\n");
291   rc = xmpp_stanza_set_name (stanza, name);
292   if (rc)
293     die ("xmpp_stanza_set_name failed: rc=%d\n", rc);
294   return stanza;
295 }
296
297 static xmpp_stanza_t *
298 new_text_stanza (xmpp_ctx_t *ctx, const char *text)
299 {
300   xmpp_stanza_t *stanza;
301   int rc;
302
303   stanza = xmpp_stanza_new (ctx);
304   if (!stanza)
305     die ("xmpp_stanza_new failed\n");
306   rc = xmpp_stanza_set_text (stanza, text);
307   if (rc)
308     die ("xmpp_stanza_set_text failed: rc=%d\n", rc);
309   return stanza;
310 }
311
312
313 const char *
314 get_bound_jid (const xmpp_conn_t * const conn)
315 {
316   const char *s = xmpp_conn_get_bound_jid (conn);
317   if (!s)
318     die ("xmpp_conn_get_bound_jid failed\n");
319   return s;
320 }
321
322
323 /* Send a standard message to RECIPIENT which already has any desired
324  * resource attached.  */
325 static void
326 send_message (xmpp_ctx_t *ctx, xmpp_conn_t * const conn,
327               const char *recipient)
328 {
329   int rc;
330   xmpp_stanza_t *stanza;
331
332   inf ("sending message to '%s'\n", recipient);
333   stanza = xmpp_message_new (ctx, opt_chat? "chat":"normal", recipient, NULL);
334   if (!stanza)
335     err ("xmpp_message_new failed for '%s'\n", recipient);
336   else
337     {
338       rc = xmpp_message_set_body (stanza, the_message);
339       if (rc)
340         err ("xmpp_message_set_body failed: rc=%d\n", rc);
341       else
342         {
343           xmpp_send (conn, stanza);
344         }
345       xmpp_stanza_release (stanza);
346     }
347 }
348
349
350 /* Send a MUC message to RECIPIENT using NICK.  A resource has already
351  * been stripped from RECIPIENT, NICK may be the empty string to
352  * indicate the use of a default.  */
353 static void
354 send_muc_message (xmpp_ctx_t *ctx, xmpp_conn_t * const conn,
355                   const char *recipient, const char *nick)
356 {
357   int rc;
358   xmpp_stanza_t *stanza, *stanza2, *stanza3;
359   char *p;
360   const char *recp;
361   char *nickbuf = NULL;
362   char *recpbuf = NULL;
363
364   /* Make sure we have a NICK.  FIXME: We should first ask the server
365    * whether it already has a reserved nick. */
366   if (!*nick)
367     {
368       nickbuf = xstrdup (get_bound_jid (conn));
369       p = strchr (nickbuf, '@');
370       if (!p)
371         die ("internal error at %d\n", __LINE__);
372       *p = 0;
373       nick = nickbuf;
374     }
375
376   inf ("sending MUC message to '%s' nick '%s'\n", recipient, nick);
377
378   recp = recpbuf = xstrconcat (recipient, "/", nick, NULL);
379
380   dbg ("sending presence to the room\n");
381   stanza = xmpp_presence_new (ctx);
382   if (!stanza)
383     die ("xmpp_presence_new failed\n");
384   rc = xmpp_stanza_set_from (stanza, get_bound_jid (conn));
385   if (rc)
386     die ("xmpp_stanza_set_from failed: rc=%d\n", rc);
387   rc = xmpp_stanza_set_to (stanza, recp);
388   if (rc)
389     die ("xmpp_stanza_set_from failed: rc=%d\n", rc);
390   rc = xmpp_stanza_set_id (stanza, "pres1");
391   if (rc)
392     die ("xmpp_stanza_set_id failed: rc=%d\n", rc);
393
394   /* Tell server that we support the Basic MUC protocol and that we
395    * don't want any history.  */
396   stanza2 = new_name_stanza (ctx, "x");
397   rc = xmpp_stanza_set_ns (stanza2, "http://jabber.org/protocol/muc");
398   if (rc)
399     die ("xmpp_stanza_set_ns failed: rc=%d\n", rc);
400   stanza3 = new_name_stanza (ctx, "history");
401   rc = xmpp_stanza_set_attribute (stanza3, "maxchars", "0");
402   if (rc)
403     die ("xmpp_stanza_set_attribute failed: rc=%d\n", rc);
404   rc = xmpp_stanza_add_child (stanza2, stanza3);
405   if (rc)
406     die ("xmpp_stanza_add_child failed: rc=%d\n", rc);
407   xmpp_stanza_release (stanza3);
408   rc = xmpp_stanza_add_child (stanza, stanza2);
409   if (rc)
410     die ("xmpp_stanza_add_child failed: rc=%d\n", rc);
411   xmpp_stanza_release (stanza2);
412
413   xmpp_send (conn, stanza);
414   xmpp_stanza_release (stanza);
415
416   stanza = xmpp_message_new (ctx, "groupchat", recipient, "chat1");
417   if (!stanza)
418     err ("xmpp_message_new failed for '%s'\n", recipient);
419   else
420     {
421       rc = xmpp_message_set_body (stanza, the_message);
422       if (rc)
423         err ("xmpp_message_set_body failed: rc=%d\n", rc);
424       else
425         {
426           xmpp_send (conn, stanza);
427         }
428       xmpp_stanza_release (stanza);
429     }
430
431   free (nickbuf);
432   free (recpbuf);
433 }
434
435
436 /* Handle iq:version stanzas.  */
437 static int
438 version_handler (xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
439                  void * const opaque)
440 {
441   xmpp_ctx_t *ctx = opaque;
442   int rc;
443   xmpp_stanza_t *reply, *query, *name, *version, *value;
444   const char *s;
445
446   inf ("received version request from %s\n", xmpp_stanza_get_from (stanza));
447
448   reply = xmpp_stanza_reply (stanza);
449   if (!reply)
450     die ("xmpp_stanza_reply failed\n");
451   xmpp_stanza_set_type (reply, "result");
452
453   query = new_name_stanza (ctx, "query");
454   s = xmpp_stanza_get_ns (xmpp_stanza_get_children (stanza));
455   if (s)
456     xmpp_stanza_set_ns (query, s);
457
458   name = new_name_stanza (ctx, "name");
459   rc = xmpp_stanza_add_child (query, name);
460   if (rc)
461     die ("xmpp_stanza_add_child failed: rc=%d\n", rc);
462   xmpp_stanza_release (name);
463   value = new_text_stanza (ctx, PGMNAME);
464   rc = xmpp_stanza_add_child (name, value);
465   if (rc)
466     die ("xmpp_stanza_add_child failed: rc=%d\n", rc);
467   xmpp_stanza_release (value);
468
469   version = new_name_stanza (ctx, "version");
470   rc = xmpp_stanza_add_child (query, version);
471   if (rc)
472     die ("xmpp_stanza_add_child failed: rc=%d\n", rc);
473   xmpp_stanza_release (version);
474   value = new_text_stanza (ctx, VERSION);
475   rc = xmpp_stanza_add_child (version, value);
476   if (rc)
477     die ("xmpp_stanza_add_child failed: rc=%d\n", rc);
478   xmpp_stanza_release (value);
479
480   rc = xmpp_stanza_add_child (reply, query);
481   if (rc)
482     die ("xmpp_stanza_add_child failed: rc=%d\n", rc);
483   xmpp_stanza_release (query);
484
485   xmpp_send (conn, reply);
486   xmpp_stanza_release (reply);
487
488   return 1;  /* Keep this handler.  */
489 }
490
491
492 /* Handle message stanzas.  */
493 static int
494 message_handler (xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
495                  void * const opaque)
496 {
497   xmpp_ctx_t *ctx = opaque;
498   const char *type;
499   xmpp_stanza_t *child, *achild;
500   char *subject, *body;
501   const char *code, *errtype;
502
503   type = xmpp_stanza_get_type (stanza);
504   if (type && !strcmp (type, "error"))
505     {
506       child = xmpp_stanza_get_child_by_name (stanza, "error");
507       errtype = child? xmpp_stanza_get_attribute (child, "type") : NULL;
508       code = child? xmpp_stanza_get_attribute (child, "code") : NULL;
509       err ("received error from <%s>: %s=%s\n",
510            xmpp_stanza_get_from (stanza),
511            code? "code":"type",
512            code?  code : errtype);
513       achild = xmpp_stanza_get_child_by_name (child, "text");
514       body = achild? xmpp_stanza_get_text (achild) : NULL;
515       if (body)
516         inf ("->%s<-\n", body);
517       xmpp_free (ctx, body);
518     }
519   else if (xmpp_stanza_get_child_by_name (stanza, "body"))
520     {
521       /* No type but has a body.  */
522       child = xmpp_stanza_get_child_by_name (stanza, "subject");
523       subject = child? xmpp_stanza_get_text (child) : NULL;
524
525       child = xmpp_stanza_get_child_by_name (stanza, "body");
526       body = child? xmpp_stanza_get_text (child) : NULL;
527
528       inf ("received message from <%s> %s%s%s\n", xmpp_stanza_get_from (stanza),
529            subject? "(subject: ":"",
530            subject? subject:"",
531            subject? ")":"");
532       if (body)
533         inf ("->%s<-\n", body);
534
535       xmpp_free (ctx, body);
536       xmpp_free (ctx, subject);
537     }
538
539   return 1; /* Keep this handler.  */
540 }
541
542
543 /* Handle connection events.  */
544 static void
545 conn_handler (xmpp_conn_t * const conn, const xmpp_conn_event_t status,
546               const int error, xmpp_stream_error_t * const stream_error,
547               void * const userdata)
548 {
549   xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata;
550   int rcpidx;
551   char *recpbuffer;
552   const char *recp, *nick, *s;
553
554   if (status == XMPP_CONN_CONNECT)
555     {
556       inf ("connected\n");
557
558       xmpp_handler_add (conn, version_handler,
559                         "jabber:iq:version", "iq", NULL, ctx);
560
561       xmpp_handler_add (conn, message_handler,
562                         NULL, "message", NULL, ctx);
563
564       /* Send the messages.  */
565       for (rcpidx=0; (recp = opt_recipients[rcpidx]); rcpidx++)
566         {
567           s = strchr (recp, '/');
568           if (s && s[1] == '/')  /* MUC  */
569             {
570               nick = s + 2;
571               recp = recpbuffer = xstrdup (recp);
572               *strchr (recpbuffer, '/') = 0;
573               send_muc_message (ctx, conn, recp, nick);
574               free (recpbuffer);
575             }
576           else
577             send_message (ctx, conn, recp);
578         }
579
580       inf ("requesting disconnect\n");
581       xmpp_disconnect (conn);
582     }
583   else
584     {
585       inf ("disconnected\n");
586       xmpp_stop(ctx);
587     }
588 }
589
590
591 /* Read our config file.  */
592 static void
593 read_config (void)
594 {
595   char *fname;
596   const char *s;
597   FILE *fp;
598   char line[512];
599   int c;
600   char *user, *pass;
601
602   s = getenv ("HOME");
603   if (!s)
604     s = "";
605   fname = xstrconcat (s, "/." PGMNAME "rc", NULL);
606   fp = fopen (fname, "r");
607   if (!fp)
608     {
609       free (fname);
610       return;
611     }
612
613   user = pass = NULL;
614   while (fgets (line, sizeof line, fp))
615     {
616       if (line[strlen (line)-1] != '\n')
617         {
618           while ((c = getc (fp)) != EOF && c != '\n')
619             ;
620           err ("warning: ignoring rest of overlong line in '%s'\n", fname);
621         }
622       if (*line == '#')
623         continue;
624       trim_spaces (line);
625       if (!*line)
626         continue;
627       user = strtok (line, " \t");
628       if (user)
629         pass = strtok (NULL, " \t");
630       else
631         pass = NULL;
632
633       if (!opt_user) /* Take the first line and we are done.  */
634         {
635           opt_user = xstrdup (user);
636           if (!opt_pass && pass)
637             opt_pass = xstrdup (pass);
638           break;
639         }
640
641       if (!strcmp (opt_user, user) && !opt_pass)  /* Password found.  */
642         {
643           opt_pass = xstrdup (pass);
644           break;
645         }
646     }
647
648   fclose (fp);
649   free (fname);
650 }
651
652
653 int
654 main (int argc, char **argv)
655 {
656   int last_argc = -1;
657   int rc, idx, anyerr;
658   int opt_me = 0;
659   unsigned long opt_limit = 0;
660   const char *recp;
661   xmpp_ctx_t *ctx;
662   xmpp_conn_t *conn;
663
664   if (argc < 1)
665     die ("Hey, read up on how to use exec(2)\n");
666   argv++; argc--;
667
668   while (argc && last_argc != argc )
669     {
670       last_argc = argc;
671       if (!strcmp (*argv, "--"))
672         {
673           argc--; argv++;
674           break;
675         }
676       else if (!strcmp (*argv, "--version"))
677         {
678           fputs (PGMNAME " " VERSION "\n"
679                  "Copyright (C) 2017 g10 Code GmbH\n"
680                  "License GPLv3+: GNU GPL version 3 or later"
681                  " <https://gnu.org/licenses/gpl.html>\n"
682                  "This is free software: you are free to change"
683                  " and redistribute it.\n"
684                  "There is NO WARRANTY, to the extent permitted by law.\n",
685                  stdout);
686           exit (0);
687         }
688       else if (!strcmp (*argv, "--help"))
689         {
690           fputs ("usage: " PGMNAME " [options] recipients\n"
691                  "Send XMPP message taken from stdin to the recipients.\n\n"
692                  "Options:\n"
693                  "  --version         print program version\n"
694                  "  --verbose         verbose diagnostics\n"
695                  "  --debug           flyswatter\n"
696                  "  --subject STRING  use STRING as subject\n"
697                  "  --chat            Use \"chat\" as message type\n"
698                  "  --me              Prepend \"/me \" to the message\n"
699                  "  --user JID        connect as JID\n"
700                  "  --pass PASS       override password with PASS\n"
701                  "  --resource RES    override default resource with RES\n"
702                  "  --limit N         read not more than N bytes\n"
703                  "\n"
704                  "The password is taken from the ~/.txmpprc file where the\n"
705                  "first non-comment line specifies the default user:\n"
706                  "  ----- 8< ----- 8< ----- 8< -----\n"
707                  "  # Example config for txxmppp\n"
708                  "  foo@jabber.example.org PASSWORD\n"
709                  "  bar@example.net PASSWORD\n"
710                  "  ----- >8 ----- >8 ----- >8 -----\n"
711                  "To send to a MUC use resource with a leading slash followed\n"
712                  "by the nick (e.g. \"juliet@capulet.lit.org//Giulietta\").\n"
713                  "\n", stdout);
714           exit (0);
715         }
716       else if (!strcmp (*argv, "--verbose"))
717         {
718           opt_verbose++;
719           argc--; argv++;
720         }
721       else if (!strcmp (*argv, "--debug"))
722         {
723           opt_debug++;
724           argc--; argv++;
725         }
726       else if (!strcmp (*argv, "--user"))
727         {
728           argc--; argv++;
729           if (!argc || !**argv || !strcmp (*argv, "--"))
730             die ("argument missing for option '%s'\n", argv[-1]);
731           opt_user = *argv;
732           argc--; argv++;
733         }
734       else if (!strcmp (*argv, "--pass"))
735         {
736           argc--; argv++;
737           if (!argc || !**argv || !strcmp (*argv, "--"))
738             die ("argument missing for option '%s'\n", argv[-1]);
739           opt_pass = *argv;
740           argc--; argv++;
741         }
742       else if (!strcmp (*argv, "--resource"))
743         {
744           argc--; argv++;
745           if (!argc || !**argv || !strcmp (*argv, "--"))
746             die ("argument missing for option '%s'\n", argv[-1]);
747           opt_resource = *argv;
748           argc--; argv++;
749         }
750       else if (!strcmp (*argv, "--subject"))
751         {
752           argc--; argv++;
753           if (!argc || !**argv || !strcmp (*argv, "--"))
754             die ("argument missing for option '%s'\n", argv[-1]);
755           opt_subject = *argv;
756           argc--; argv++;
757         }
758       else if (!strcmp (*argv, "--chat"))
759         {
760           opt_chat = 1;
761           argc--; argv++;
762         }
763       else if (!strcmp (*argv, "--me"))
764         {
765           opt_me = 1;
766           argc--; argv++;
767         }
768       else if (!strcmp (*argv, "--limit"))
769         {
770           argc--; argv++;
771           if (!argc || !**argv || !strcmp (*argv, "--"))
772             die ("argument missing for option '%s'\n", argv[-1]);
773           opt_limit = strtoul (*argv, NULL, 0);
774           argc--; argv++;
775         }
776       else if (!strncmp (*argv, "--", 2))
777         die ("unknown option '%s' (use --help)\n", *argv);
778     }
779
780   if (!argc)
781     die ("usage: " PGMNAME " [options] recipients  (try --help)\n");
782
783   opt_recipients = argv;
784
785   read_config ();
786
787   anyerr = 0;
788   if (!opt_user || !*opt_user || !opt_pass || !*opt_pass)
789     {
790       if (!opt_user || !*opt_user)
791         err ("error: no user given\n");
792       if (!opt_pass || !*opt_pass)
793         err ("error: no password given\n");
794       inf ("hint: use config file \"~/.txxmpprc\" or option \"--user\"\n");
795       anyerr = 1;
796     }
797
798   for (idx = 0; (recp = opt_recipients[idx]); idx++)
799     {
800       const char *at, *slash;
801       at = strchr (recp, '@');
802       slash = strchr (recp, '/');
803       if (!at || at == recp || !at[1]
804           || (slash && (slash < at || at + 1 == slash)))
805         {
806           err ("error: invalid recipient '%s'\n", recp);
807           anyerr = 1;;
808         }
809     }
810
811   if (anyerr)
812     exit (1);
813
814   the_message = xreadfile (stdin, opt_limit);
815   if (opt_me)
816     {
817       char *newbuf = xstrconcat ("/me ", the_message, NULL);
818       free (the_message);
819       the_message = newbuf;
820     }
821
822   xmpp_initialize ();
823
824   ctx = xmpp_ctx_new (NULL,
825                       (opt_debug? xmpp_get_default_logger (XMPP_LEVEL_DEBUG)
826                        /* */    : NULL));
827   if (!ctx)
828     die ("xmpp_ctx_new failed\n");
829
830   conn = xmpp_conn_new (ctx);
831   if (!conn)
832     die ("xmpp_conn_new failed\n");
833
834   xmpp_conn_set_jid (conn, opt_user);
835   xmpp_conn_set_pass (conn, opt_pass);
836
837   rc = xmpp_connect_client (conn, NULL, 0, conn_handler, ctx);
838   if (rc)
839     err ("xmpp_connect_client failed: rc=%d\n", rc);
840   else
841     {
842       xmpp_run (ctx);
843     }
844
845   xmpp_conn_release (conn);
846   xmpp_ctx_free (ctx);
847
848   xmpp_shutdown ();
849
850   free (the_message);
851   the_message = NULL;
852
853   return 0;
854 }
855
856
857 /*
858 Local Variables:
859 compile-command: "gcc -Wall -g -lstrophe -o txxmpp txxmpp.c"
860 End:
861 */