irctk

libircclient binding for scripts
git clone https://a3nm.net/git/irctk/
Log | Files | Refs | README

irctk.c (59879B)


      1 /* irctk -- an IRC toolkit
      2  * Copyright (C) Antoine Amarilli 2010-2018
      3  * Licence: GPLv3, see README and COPYING */
      4 
      5 #include <argp.h>
      6 #include <assert.h>
      7 #include <errno.h>
      8 #include <pthread.h>
      9 #include <stdarg.h>
     10 #include <stdio.h>
     11 #include <stdlib.h>
     12 #include <string.h>
     13 #include <sys/time.h>
     14 #include <unistd.h>
     15 
     16 #include <libircclient.h>
     17 #include <libirc_rfcnumeric.h>
     18 
     19 #define E_SESSION 1
     20 #define E_CONNECT 2
     21 #define E_THREAD 3
     22 #define E_PASSWORD 4
     23 #define E_SERVER_ERROR 5
     24 #define E_BADNAME 6
     25 #define E_BADLINE 7
     26 #define E_LIBIRCCLIENT_VERSION 7
     27 
     28 enum do_say_type {DO_SAY_MSG, DO_SAY_NOTICE};
     29 
     30 enum default_destinations {DEFAULT_FIRST, DEFAULT_LAST_IN, DEFAULT_LAST_OUT, DEFAULT_ALL};
     31 enum event_tos {NOTHING, COMMAND, MESSAGE};
     32 enum track {NO, YES, UNIQUE};
     33 
     34 #define MAX_LINE_LEN 4096
     35 #define MAX_CHAN_LEN 4096
     36 #define MAX_CHANS_LEN 4096
     37 #define MAX_NICK_LEN 4096
     38 #define STDIN_BUFFER_SIZE 4096
     39 
     40 #define INITIAL_CHAN_LIST_SIZE 2
     41 
     42 // non-standard error for the hybrid ircd
     43 #define ERR_BADCHANNAME 479
     44 
     45 const char *argp_program_bug_address = "<a3nm@a3nm.net>";
     46 
     47 static char doc[] = "irctk -- an IRC toolkit";
     48 static char args_doc[] = "[NICK[:PASS]@]SERVER[:PORT] [CHANNEL[:PASS]]...";
     49 
     50 enum arg_types {STANDARD, SSL, TIMING, MISC, CHANNEL_SELECTION, BOT_OPTIONS,
     51   TRACKING, PARSING, DISPLAY_SELECTION, NAME};
     52 
     53 static struct argp_option options[] = {
     54   
     55  {"verbose", 'v', 0, 0,
     56    "Produce debug output on stderr",
     57    STANDARD},
     58  {"quiet", 'q', 0, 0,
     59    "Don't display errors on stderr, fail silently",
     60    STANDARD},
     61 
     62  {"ssl", 's', 0, 0,
     63   "Use SSL",
     64    SSL},
     65  {"no-ssl", 'S', 0, 0,
     66   "Do not use SSL (default)",
     67    SSL},
     68  {"check-certificate", 'k', 0, 0,
     69   "Require a valid SSL certificate (default)",
     70    SSL},
     71  {"no-check-certificate", 'K', 0, 0,
     72   "Accept self-signed, invalid (and man-in-the-middle) certificates",
     73    SSL},
     74 
     75  {"interval", 'i', "secs", 0,
     76    "Wait delay between posted messages (default: 1)",
     77    TIMING},
     78  {"interval-end", 'I', "secs", 0,
     79    "Wait delay between sending the last message and disconnecting"
     80     " (default: 2)",
     81    TIMING},
     82  {"retry-after", 'y', "secs", 0,
     83    "Wait delay before trying to reconnect to the server (default: 20)",
     84    TIMING},
     85  {"retry-factor", 'Y', "secs", 0,
     86    "Retry delay multiplication factor after each failed attempt (default: 2)",
     87    TIMING},
     88 
     89  {"join", 'j', 0, 0,
     90    "Automatically join and re-join channels as needed (default)", MISC },
     91  {"no-join", 'J', 0, 0,
     92    "Never join other channels than those specified on the command-line, don't"
     93     " re-join", MISC },
     94 
     95  {"default-always-first", '0', 0, 0,
     96    "Post messages to the first specified channel by default",
     97    CHANNEL_SELECTION},
     98  {"default-last-active", 'a', 0, 0,
     99    "Post messages to the last active channel by default (default)",
    100    CHANNEL_SELECTION},
    101  {"default-last-posted", 'p', 0, 0,
    102    "Post messages to the last posted channel by default",
    103    CHANNEL_SELECTION},
    104  {"default-all", 'l', 0, 0,
    105    "Post messages to all channels specified on the command line by default",
    106    CHANNEL_SELECTION},
    107  {"show-inferred", 'P', 0, 0,
    108    "When inferring a next chan with --default-last-posted, display it on"
    109    " stderr",
    110    CHANNEL_SELECTION},
    111 
    112  {"reply", 'r', 0, 0,
    113    "When inferring a destination channel with --default-last-active*,"
    114     " also infer a destination nick, and prefix lines with this nick",
    115    BOT_OPTIONS},
    116  {"filter", 'f', 0, 0,
    117    "Only keep messages directly addressed to us (in private channel or with"
    118     " lines prefixed by our nick)",
    119    BOT_OPTIONS },
    120  {"filter-prune", 'F', 0, 0,
    121    "Only give the message body without the channel, nick or address (implies -f, useful"
    122     " with -r)",
    123    BOT_OPTIONS },
    124 
    125  {"no-track-renames", 'T', 0, 0,
    126    "When sending to a user, do not track them if they changes nick (default)",
    127    TRACKING },
    128  {"track-renames", 't', 0, 0,
    129    "When sending to a user, track them if they changes nick",
    130    TRACKING },
    131  {"unique-names", 'u', 0, 0,
    132    "Track nick changes and expose unique names for users",
    133    TRACKING },
    134 
    135  {"event-to-nothing", 'n', 0, 0,
    136    "Ignore server events (default).",
    137    PARSING},
    138  {"event-to-command", 'c', 0, 0,
    139    "Translate server events to messages with \"/quit\", \"/me\"...",
    140    PARSING},
    141  {"event-to-message", 'm', 0, 0,
    142    "Translate server events to a human-readable description",
    143    PARSING},
    144 
    145  {"command-to-event", 'e', 0, 0,
    146    "Interpret messages starting with \"/quit\", \"/me\", etc. as commands"
    147     " unless escaped (default)",
    148     DISPLAY_SELECTION, },
    149  {"no-command-to-event", 'E', 0, 0,
    150    "Don't generate events from messages with \"/quit\", \"/me\"...",
    151     DISPLAY_SELECTION, },
    152  {"with-host", 'w', 0, 0,
    153    "Keep the host info in pseudos",
    154    DISPLAY_SELECTION},
    155  {"own", 'o', 0, 0,
    156    "Include own messages in output",
    157     DISPLAY_SELECTION},
    158 
    159  {"username", 'U', "name", 0,
    160    "Unix username (default: same as nick, only keeping lowercase letters)",
    161    NAME},
    162  {"realname", 'R', "name", 0,
    163    "Full name (default: same as nick)",
    164    NAME},
    165  {"force-nick", 'N', 0, 0,
    166    "Abort when the requested nick is unavailable or invalid; don't pick"
    167    " another one automatically",
    168    NAME},
    169 
    170  { 0 }
    171 };
    172 
    173 
    174 /* The structure to store program settings */
    175 struct arguments
    176 {
    177   pthread_mutex_t mutex;
    178 
    179   char nick[MAX_NICK_LEN];
    180   char hostname[MAX_NICK_LEN];
    181   char *raw_server; // before parsing SSL
    182   char *server;
    183   int port;
    184   char *password;
    185   char **channels;
    186   int n_channels; // TODO2 refactor this with fifos
    187 
    188   int ssl;
    189   int ssl_check;
    190 
    191   int verbosity;
    192 
    193   int interval;
    194   int interval_after;
    195   int retry_after;
    196   int retry_factor;
    197 
    198   enum default_destinations default_destination;
    199   int show_inferred;
    200 
    201   int with_host;
    202   int own;
    203 
    204   enum event_tos event_to;
    205   int command_to_event;
    206   enum track track;
    207   int join;
    208   
    209   char username[MAX_NICK_LEN];
    210   char realname[MAX_NICK_LEN];
    211   char username_set;
    212   char realname_set;
    213   int force_nick;
    214 
    215   int command;
    216   char * raw_dest;
    217   char ready;
    218   char last_chan_in[MAX_CHAN_LEN];
    219   char last_nick_in[MAX_NICK_LEN];
    220   char last_chans_out[MAX_CHANS_LEN];
    221   int show_nick_prefix;
    222   int filter;
    223   int prune;
    224 };
    225 
    226 typedef struct arguments irc_ctx_t;
    227 
    228 struct arguments args;
    229 
    230 /* The initial argument values */
    231 void initialize_args()
    232 {
    233   assert(!pthread_mutex_init(&(args.mutex), NULL));
    234 
    235   strncpy(args.nick, "irctk", MAX_NICK_LEN-1);
    236   strncpy(args.hostname, "!~UNKNOWN@UNKNOWN", MAX_NICK_LEN-1);
    237   args.raw_server = NULL;
    238   args.server = NULL;
    239   args.password = NULL;
    240   args.port = 6667;
    241   args.channels = (char**) NULL;
    242   args.n_channels = 0;
    243 
    244   args.ssl = 0;
    245   args.ssl_check = 1;
    246 
    247   args.verbosity = 0;
    248 
    249   args.interval = 1000000;
    250   args.interval_after = 2000000;
    251   args.retry_after = 20;
    252   args.retry_factor = 2;
    253 
    254   args.default_destination = DEFAULT_LAST_IN;
    255   args.show_inferred = 0;
    256 
    257   args.with_host = 0;
    258   args.own = 0;
    259 
    260   args.event_to = NOTHING;
    261   args.command_to_event = 1;
    262   args.track = NO;
    263   args.join = 1;
    264   
    265   strncpy(args.username, "irctk", MAX_NICK_LEN-1);
    266   strncpy(args.realname, "irctk", MAX_NICK_LEN-1);
    267   args.username_set = 0;
    268   args.realname_set = 0;
    269   args.force_nick = 0;
    270 
    271   args.filter = 0;
    272   args.prune = 0;
    273   args.last_chan_in[0] = 0;
    274   args.last_nick_in[0] = 0;
    275   args.last_chans_out[0] = 0;
    276 }
    277 
    278 /* set the username, removing bad characters */
    279 void set_username(struct arguments *arguments, char *name) {
    280   int opos = 0, ipos = 0;
    281   while (name[ipos]) {
    282     // section 2.3.1 of RFC 2812, definition of "user"
    283     if (name[ipos] != 0x0A && name[ipos] != 0x0D
    284           && name[ipos] != 0x20 && name[ipos] != 0x40)
    285       arguments->username[opos++] = name[ipos];
    286     ipos++;
    287   }
    288   arguments->username[opos] = 0;
    289   if (!opos) {
    290     // there were no valid characters to copy, revert to default
    291     strncpy(arguments->username, "irctk", MAX_NICK_LEN-1);
    292   }
    293 }
    294 
    295 /* set our nick, and names if not already set */
    296 void set_nick_and_names(struct arguments *arguments)
    297 {
    298   strncpy(arguments->nick, arguments->raw_dest, MAX_NICK_LEN-1);
    299   if (!args.username_set)
    300     set_username(arguments, arguments->raw_dest);
    301   if (!args.realname_set)
    302     strncpy(arguments->realname, arguments->raw_dest, MAX_NICK_LEN-1);
    303 }
    304 
    305 
    306 /* The command-line argument parsing function for argp */
    307 static error_t parse_opt (int key, char *arg, struct argp_state *state)
    308 {
    309   struct arguments *arguments = state->input;
    310   int i;
    311 
    312   if (key != ARGP_KEY_ARG && key != ARGP_KEY_END && (! arguments->channels)) {
    313     switch (key) {
    314       case 'v':
    315         arguments->verbosity = 1;
    316         break;
    317       case 'q':
    318         arguments->verbosity = -1;
    319         break;
    320       case 'S':
    321         arguments->ssl = 0;
    322         break;
    323       case 's':
    324         arguments->ssl = 1;
    325         break;
    326       case 'K':
    327         arguments->ssl_check = 0;
    328         break;
    329       case 'k':
    330         arguments->ssl_check = 1;
    331         break;
    332       case 'n':
    333         arguments->event_to = NOTHING;
    334         break;
    335       case 'c':
    336         arguments->event_to = COMMAND;
    337         break;
    338       case 'm':
    339         arguments->event_to = MESSAGE;
    340         break;
    341       case 'E':
    342         arguments->command_to_event = 0;
    343         break;
    344       case 'e':
    345         arguments->command_to_event = 1;
    346         break;
    347       case 'i':
    348         arguments->interval = (int) (atof(arg) * 1000000.);
    349         break;
    350       case 'I':
    351         arguments->interval_after = (int) (atof(arg) * 1000000.);
    352         break;
    353       case 'y':
    354         arguments->retry_after = (int) (atof(arg));
    355         break;
    356       case 'Y':
    357         arguments->retry_factor = (int) (atof(arg));
    358         break;
    359       case 'U':
    360         arguments->username_set = 1;
    361         set_username(arguments, arg);
    362         break;
    363       case 'R':
    364         arguments->realname_set = 1;
    365         strncpy(arguments->realname, arg, MAX_NICK_LEN-1);
    366         break;
    367       case 'o':
    368         arguments->own = 1;
    369         break;
    370       case 'w':
    371         arguments->with_host = 1;
    372         break;
    373       case 'r':
    374         arguments->show_nick_prefix = 1;
    375         break;
    376       case '0':
    377         arguments->default_destination = DEFAULT_FIRST;
    378         break;
    379       case 'f':
    380         arguments->filter = 1;
    381         break;
    382       case 'F':
    383         arguments->filter = 1;
    384         arguments->prune = 1;
    385         break;
    386       case 'J':
    387         arguments->join = 0;
    388         break;
    389       case 'j':
    390         arguments->join = 1;
    391         break;
    392       case 'T':
    393         arguments->track = NO;
    394         break;
    395       case 't':
    396         arguments->track = YES;
    397         break;
    398       case 'u':
    399         arguments->track = UNIQUE;
    400         break;
    401       case 'a':
    402         arguments->default_destination = DEFAULT_LAST_IN;
    403         break;
    404       case 'p':
    405         arguments->default_destination = DEFAULT_LAST_OUT;
    406         break;
    407       case 'l':
    408         arguments->default_destination = DEFAULT_ALL;
    409         break;
    410       case 'P':
    411         arguments->show_inferred = 1;
    412         break;
    413       case 'N':
    414         arguments->force_nick = 1;
    415         break;
    416 
    417       case ARGP_KEY_NO_ARGS:
    418         argp_usage (state);
    419 
    420       default:
    421         return ARGP_ERR_UNKNOWN;
    422     }
    423   } else {
    424     switch(key) {
    425       case ARGP_KEY_ARG:
    426         if (state->arg_num == 0) {
    427           i = -1;
    428           arguments->raw_dest = arg;
    429           arguments->raw_server = arg;
    430 
    431           int saw_colon = 0;
    432           int saw_at = 0;
    433 
    434           while (arguments->raw_dest[++i]) {
    435             if (arguments->raw_dest[i] == ':' && !saw_colon) {
    436               arguments->raw_dest[i] = 0;
    437               saw_colon = i;
    438             }
    439             if (arguments->raw_dest[i] == '@') {
    440               arguments->raw_dest[i] = 0;
    441               saw_at = i;
    442               arguments->raw_server = arguments->raw_dest + saw_at + 1;
    443               set_nick_and_names(arguments);
    444               if (saw_colon) {
    445                 arguments->password = arguments->raw_dest + saw_colon + 1;
    446                 saw_colon = 0;
    447               }
    448             }
    449           }
    450           if (saw_colon) {
    451             // we must have a server, so the last thing seen behind an initial
    452             // colon must be the port
    453             arguments->port = atoi(arguments->raw_dest + saw_colon + 1);
    454           }
    455         }
    456         else {
    457           if (arguments->channels == NULL) {
    458             arguments->channels = state->argv + (state->next - 1);
    459           }
    460           arguments->n_channels++;
    461         }
    462         break;
    463 
    464       case ARGP_KEY_END:
    465         if (state->arg_num < 1 || strlen(arguments->raw_server) == 0
    466             || strlen(arguments->nick) == 0 || arguments->port == 0)
    467           argp_usage (state);
    468         break;
    469     }
    470   }
    471   return 0;
    472 }
    473 
    474 /* argp structure */
    475 static struct argp argp = { options, parse_opt, args_doc, doc };
    476 
    477 
    478 /* Emit a message with a verbosity level, and optionally terminate.
    479  * Verbosity is >=1 for debug, 0 for info, -n for error n-1 */
    480 void message(int verbosity, const char *err, va_list ap)
    481 {
    482   if (args.verbosity >= verbosity) {
    483     fprintf(stderr, "%s ", verbosity > 0 ? "irctk: [debug]" :
    484         verbosity >= 0 ? "irctk:" : "irctk: [error]");
    485     vfprintf(stderr, err, ap);
    486   }
    487   if (verbosity < 0)
    488     exit(1-verbosity);
    489 }
    490 
    491 void info(const char *err, ...)
    492 {
    493   va_list ap;
    494   va_start(ap, err);
    495   return message(0, err, ap);
    496 }
    497 
    498 void debug(const char *err, ...)
    499 {
    500   va_list ap;
    501   va_start(ap, err);
    502   return message(1, err, ap);
    503 }
    504 
    505 void die(int val, const char *err, ...)
    506 {
    507   va_list ap;
    508   va_start(ap, err);
    509   return message(-val - 1, err, ap);
    510 }
    511 
    512 int mprintf(const char *fmt, ...)
    513 {
    514   va_list ap;
    515   va_start(ap, fmt);
    516   int ret;
    517   ret = vprintf(fmt, ap);
    518   fflush(stdout);
    519   return ret;
    520 }
    521 
    522 
    523 typedef struct line {
    524   char *line;
    525   char *full_line;
    526   int next_dep_fifoidx;
    527   int next_dep_lineidx;
    528   char is_head;
    529   char is_sendable;
    530 } line;
    531 
    532 typedef struct action {
    533   char *line;
    534   char *full_line;
    535   char *destination;
    536 } action;
    537 
    538 typedef struct fifo {
    539   int hd;
    540   int tl;
    541   line queue[STDIN_BUFFER_SIZE];
    542 } fifo;
    543 
    544 typedef struct fifo_ctrl {
    545   pthread_cond_t empty;
    546   pthread_cond_t full;
    547   pthread_mutex_t mutex;
    548 } fifo_ctrl;
    549 
    550 typedef struct fifo_set {
    551   int n;
    552   int allocated;
    553   fifo_ctrl ctrl;
    554   fifo *fifos;
    555   char **chans;
    556   char **surface_name;
    557   char *was_pushed;
    558 } fifo_set;
    559 
    560 void init_fifo_ctrl(fifo_ctrl *c) {
    561   assert(!pthread_cond_init(&(c->empty), NULL));
    562   assert(!pthread_cond_init(&(c->full), NULL));
    563   assert(!pthread_mutex_init(&(c->mutex), NULL));
    564 }
    565 
    566 void init_fifo_set (fifo_set *s) {
    567   s->n = 0;
    568   s->allocated = INITIAL_CHAN_LIST_SIZE;
    569   s->chans = malloc(s->allocated * sizeof(char*));
    570   assert(s->chans);
    571   s->fifos = malloc(s->allocated * sizeof(fifo));
    572   assert(s->fifos);
    573   s->was_pushed = malloc(s->allocated * sizeof(char));
    574   assert(s->was_pushed);
    575   s->surface_name = malloc(s->allocated * sizeof(char*));
    576   assert(s->surface_name);
    577   init_fifo_ctrl(&(s->ctrl));
    578 }
    579 
    580 void destroy_fifo_ctrl(fifo_ctrl *f) {
    581   pthread_cond_destroy(&f->empty);
    582   pthread_cond_destroy(&f->full);
    583   pthread_mutex_destroy(&f->mutex);
    584 }
    585 
    586 void destroy_fifo_set (fifo_set *s) {
    587   int i;
    588   free(s->fifos);
    589   for (i = 0; i < s->n ; i++)
    590     free(s->chans[i]);
    591   free(s->chans);
    592   free(s->was_pushed);
    593   for (i = 0; i < s->n ; i++)
    594     free(s->surface_name[i]);
    595   free(s->surface_name);
    596   destroy_fifo_ctrl(&(s->ctrl));
    597 }
    598 
    599 void init_fifo(fifo *f) {
    600   f->hd = 0;
    601   f->tl = 0;
    602 }
    603 
    604 int size(fifo *f) {
    605   return (f->tl - f->hd + STDIN_BUFFER_SIZE) % STDIN_BUFFER_SIZE;
    606 }
    607 int full(fifo *f) {
    608   return size(f) == STDIN_BUFFER_SIZE - 1;
    609 }
    610 int full_fifo_set(fifo_set *s) {
    611   // fifo_set is full if one fifo is full
    612   int i;
    613   for (i=0; i<s->n; i++)
    614     if (full(&(s->fifos[i])))
    615       return 1;
    616   return 0;
    617 }
    618 int empty(fifo *f) {
    619   return size(f) == 0;
    620 }
    621 int empty_fifo_set(fifo_set *s) {
    622   // fifo_set is empty if all fifos are empty
    623   int i;
    624   for (i=0; i<s->n; i++)
    625     if (!empty(&(s->fifos[i])))
    626       return 0;
    627   return 1;
    628 }
    629 
    630 void realloc_chans(fifo_set *s) {
    631   debug("realloc_chans()\n");
    632   s->allocated *= 2;
    633   s->chans = realloc(s->chans, s->allocated*sizeof(char*));
    634   assert(s->chans);
    635   s->was_pushed = realloc(s->was_pushed, s->allocated*sizeof(char*));
    636   assert(s->was_pushed);
    637   s->fifos = realloc(s->fifos, s->allocated*sizeof(fifo));
    638   assert(s->fifos);
    639   s->surface_name = realloc(s->surface_name, s->allocated*sizeof(char*));
    640   assert(s->surface_name);
    641 }
    642 
    643 int push_chan_alias(fifo_set *s, const char *chan, const char *alias) {
    644   if (s->n == s->allocated)
    645     realloc_chans(s);
    646   s->chans[s->n] = malloc(MAX_CHANS_LEN * sizeof(chan));
    647   assert(s->chans[s->n]);
    648   strncpy(s->chans[s->n], chan, MAX_CHAN_LEN-1);
    649   init_fifo(&s->fifos[s->n]);
    650   s->was_pushed[s->n] = 0;
    651   s->surface_name[s->n] = malloc(MAX_NICK_LEN * sizeof(chan));
    652   assert(s->surface_name[s->n]);
    653   strncpy(s->surface_name[s->n], alias, MAX_NICK_LEN-1);
    654   return (s->n)++;
    655 }
    656 
    657 int push_chan(fifo_set *s, const char *chan) {
    658   return push_chan_alias(s, chan, chan);
    659 }
    660 
    661 int scan_collection(fifo_set *s, const char **collection, const char *chan) {
    662   int i;
    663   for (i=0; i<s->n; i++) {
    664     if (!strcmp(collection[i], chan)) {
    665       return i;
    666     }
    667   }
    668   return -1;
    669 }
    670 
    671 int index_of_chan_get(fifo_set *s, const char *chan) {
    672   return scan_collection(s, (const char **) s->chans, chan);
    673 }
    674 
    675 int index_of_chan_surface(fifo_set *s, const char *chan) {
    676   int result = scan_collection(s, (const char **) s->surface_name, chan);
    677   if (result >= 0) {
    678     return result;
    679   } else {
    680     return push_chan(s, chan);
    681   }
    682 }
    683 
    684 int index_of_chan(fifo_set *s, const char *chan) {
    685   int result = index_of_chan_get(s, chan);
    686   if (result >= 0) {
    687     return result;
    688   } else {
    689     return push_chan(s, chan);
    690   }
    691 }
    692 
    693 int rename_user(fifo_set *s, const char *from, const char *to) {
    694   int pos;
    695   debug("rename_user %s -> %s\n", from, to);
    696   pthread_mutex_lock(&s->ctrl.mutex);
    697   pos = scan_collection(s, (const char **) s->surface_name, from);
    698   assert(pos >= 0);
    699   strncpy(s->surface_name[pos], to, MAX_NICK_LEN-1);
    700   pthread_mutex_unlock(&s->ctrl.mutex);
    701   return pos;
    702 }
    703 
    704 int surface_nick_exists(fifo_set *s, const char *nick) {
    705   int result = 0;
    706   pthread_mutex_lock(&s->ctrl.mutex);
    707   if (scan_collection(s, (const char **) s->surface_name, nick) >= 0)
    708     result = 1;
    709   pthread_mutex_unlock(&s->ctrl.mutex);
    710   return result;
    711 }
    712 
    713 int nick_exists(fifo_set *s, const char *nick) {
    714   int result = 0;
    715   if (scan_collection(s, (const char **) s->chans, nick) >= 0 ||
    716       scan_collection(s, (const char **) s->surface_name, nick) >= 0)
    717     result = 1;
    718   return result;
    719 }
    720 
    721 int register_nick(fifo_set *s, const char *nick) {
    722   int result;
    723   pthread_mutex_lock(&s->ctrl.mutex);
    724   // we assume that nick is not a current surface nick
    725   if (scan_collection(s, (const char **) s->chans, nick) < 0) {
    726     result = push_chan(s, nick);
    727   } else {
    728     // nick is already taken, generate a fresh id
    729     int suffix = 2;
    730     char new[MAX_NICK_LEN], num[MAX_NICK_LEN];
    731     while (1) {
    732       strncpy(new, nick, MAX_NICK_LEN/2);
    733       sprintf(num, "%d", suffix++);
    734       strncat(new, num, MAX_NICK_LEN/2-1);
    735       debug("Trying to register under %s\n", new);
    736       if (!nick_exists(s, new)) {
    737         break;
    738       }
    739     }
    740     debug("Register under %s %s\n", new, nick);
    741     result = push_chan_alias(s, new, nick);
    742   }
    743   pthread_mutex_unlock(&s->ctrl.mutex);
    744   return result;
    745 }
    746 
    747 void push_one(fifo_set *s, char *fl, char *dest, char *l,
    748     int next_dep_fifoidx, int next_dep_lineidx,
    749     int *fifoidx, int *lineidx) {
    750   int c;
    751   if (args.track != UNIQUE)
    752     c = index_of_chan_surface(s, dest);
    753   else
    754     c = index_of_chan(s, dest);
    755   fifo *f = &(s->fifos[c]);
    756   if (s->was_pushed[c])
    757     return; // already pushed
    758   debug("PUSH_ONE \"%s\" (%p), nextdep %d %d ioc %d position %d\n",
    759       l, l, next_dep_fifoidx, next_dep_lineidx, c, f->tl);
    760   s->was_pushed[c] = 1;
    761   f->queue[f->tl].line = l;
    762   f->queue[f->tl].full_line = fl;
    763   f->queue[f->tl].next_dep_fifoidx = next_dep_fifoidx;
    764   f->queue[f->tl].next_dep_lineidx = next_dep_lineidx;
    765   f->queue[f->tl].is_head = 0;
    766   f->queue[f->tl].is_sendable = 0;
    767   *fifoidx = c;
    768   *lineidx = f->tl;
    769   debug("pushed one, returning next dep %d %d\n", *fifoidx, *lineidx);
    770   f->tl++;
    771   f->tl %= STDIN_BUFFER_SIZE;
    772 }
    773 
    774 void set_last_idxes(fifo_set *s, char *chan, int fifoidx, int lineidx) {
    775   int c = index_of_chan(s, chan);
    776   fifo *f = &(s->fifos[c]);
    777   f->queue[(f->tl - 1 + STDIN_BUFFER_SIZE) % STDIN_BUFFER_SIZE].next_dep_fifoidx = fifoidx;
    778   f->queue[(f->tl - 1 + STDIN_BUFFER_SIZE) % STDIN_BUFFER_SIZE].next_dep_lineidx = lineidx;
    779 }
    780 
    781 void reset_chan_marks(fifo_set *s) {
    782   int i;
    783   for (i=0; i<s->n; i++)
    784     s->was_pushed[i] = 0;
    785 }
    786 
    787 void pre_push(fifo_set *s) {
    788   pthread_mutex_lock(&s->ctrl.mutex);
    789   while (full_fifo_set(s))
    790     pthread_cond_wait(&s->ctrl.full, &s->ctrl.mutex);
    791   // now fifo_set isn't full
    792 }
    793 
    794 void post_push(fifo_set *s, int was_empty) {
    795   if (was_empty) {
    796     debug("signal for empty\n");
    797     pthread_cond_signal(&s->ctrl.empty);
    798   }
    799   pthread_mutex_unlock(&s->ctrl.mutex);
    800 }
    801 
    802 void push_fifo_set(fifo_set *s, char *fl, char *dests, char *l) {
    803   debug("push_fifo_set full_line \"%s\" (%p) line \"%s\" (%p) dests %s\n",
    804       fl, fl, l, l, dests);
    805   int was_empty;
    806 
    807   pre_push(s);
    808   int i = 0;
    809   int cont = 1;
    810   char *last_dest, *first_dest = 0;
    811   int first_fifoidx = -1, first_lineidx = -1;
    812   int fifoidx = 0, lineidx = 0, nfifoidx = 0, nlineidx = 0;
    813 
    814   last_dest = dests;
    815 
    816   reset_chan_marks(s);
    817   was_empty = empty_fifo_set(s);
    818   while (cont) {
    819     if (dests[i] == ',' || !dests[i]) {
    820       if (!dests[i])
    821         cont = 0;
    822       if (i)
    823         dests[i] = 0;
    824       debug("will push one \"%s\"\n", l);
    825       // only put the full line for *one* occurrence
    826       push_one(s, first_fifoidx < 0?fl:NULL, last_dest, l, fifoidx, lineidx, &nfifoidx, &nlineidx);
    827       fifoidx = nfifoidx;
    828       lineidx = nlineidx;
    829       if (first_fifoidx < 0) {
    830         first_fifoidx = fifoidx;
    831         first_lineidx = lineidx;
    832         first_dest = last_dest;
    833       }
    834       last_dest = dests + i + 1;
    835     }
    836     i++;
    837   }
    838 
    839   set_last_idxes(s, first_dest, fifoidx, lineidx);
    840 
    841   post_push(s, was_empty);
    842 }
    843 
    844 /* TODO2 refactor this */
    845 void push_fifo_set_all(fifo_set *s, char *fl, char **dests, int ndests, char *l) {
    846   pre_push(s);
    847   int was_empty;
    848   int i = 0;
    849   int fifoidx = 0, lineidx = 0, nfifoidx = 0, nlineidx = 0;
    850 
    851   assert(ndests > 0);
    852   reset_chan_marks(s);
    853   was_empty = empty_fifo_set(s);
    854   for (i=0; i<ndests; i++) {
    855     push_one(s, i?NULL:fl, dests[i], l, fifoidx, lineidx, &nfifoidx, &nlineidx);
    856     fifoidx = nfifoidx;
    857     lineidx = nlineidx;
    858   }
    859   
    860   set_last_idxes(s, dests[0], fifoidx, lineidx);
    861 
    862   post_push(s, was_empty);
    863 }
    864 
    865 void mark_head(fifo *f) {
    866   f->queue[f->hd].is_head = 1;
    867   f->queue[f->hd].is_sendable = 0;
    868 }
    869 
    870 void mark_heads(fifo_set *s) {
    871   int i;
    872   for (i=0; i<s->n; i++)
    873     mark_head(&s->fifos[i]);
    874 }
    875 
    876 int mark_sendable_line(fifo_set *s, line *l) {
    877   if (!l->is_head)
    878     return 0;
    879   if (l->is_sendable)
    880     return 1;
    881   l->is_sendable = 1;
    882   line* ptr = &(s->fifos[l->next_dep_fifoidx].queue[l->next_dep_lineidx]);
    883   return mark_sendable_line(s, ptr);
    884 }
    885 int mark_sendable(fifo_set *s, fifo *f) {
    886   if (empty(f))
    887     return 1;
    888   return mark_sendable_line(s, &f->queue[f->hd]);
    889 }
    890 void unmark_sendable_line(fifo_set *s, line *l) {
    891   if (!l->is_sendable)
    892     return;
    893   l->is_sendable = 0;
    894   line* ptr = &(s->fifos[l->next_dep_fifoidx].queue[l->next_dep_lineidx]);
    895   unmark_sendable_line(s, ptr);
    896 }
    897 void unmark_sendable(fifo_set *s, fifo *f) {
    898   unmark_sendable_line(s, &f->queue[f->hd]);
    899 }
    900 void pop_if_sendable(fifo_set *s, int c, int *n_result, action *result) {
    901   fifo *f = &s->fifos[c];
    902   if (f->queue[f->hd].is_sendable && f->queue[f->hd].line) {
    903     result[*n_result].line = f->queue[f->hd].line;
    904     result[*n_result].full_line = f->queue[f->hd].full_line;
    905     result[*n_result].destination = s->surface_name[c];
    906     (*n_result)++;
    907     f->hd++;
    908     f->hd %= STDIN_BUFFER_SIZE;
    909   } else {
    910     // don't pop sentinel
    911     debug("don't pop channel %d\n", c);
    912   }
    913 }
    914 
    915 int pop_fifo_set(fifo_set *s, action **result) {
    916   int n_result = 0;
    917   int was_full;
    918   int i;
    919   pthread_mutex_lock(&s->ctrl.mutex);
    920   while (empty_fifo_set(s)) {
    921     debug("wait because empty\n");
    922     pthread_cond_wait(&s->ctrl.empty, &s->ctrl.mutex);
    923   }
    924   // now fifo isn't empty
    925   debug("now fifo isn't empty\n");
    926   was_full = full_fifo_set(s);
    927   mark_heads(s);
    928   for (i=0; i<s->n; i++) {
    929     debug("doing fifo for chan %s\n", s->chans[i]);
    930     debug("fifo hd %d tl %d\n", s->fifos[i].hd, s->fifos[i].tl);
    931     if (!mark_sendable(s, &s->fifos[i]))
    932       unmark_sendable(s, &s->fifos[i]);
    933   }
    934   // freeing result is responsibility of callee
    935   *result = malloc(s->n * sizeof(line));
    936   assert(*result);
    937   for (i=0; i<s->n; i++)
    938     pop_if_sendable(s, i, &n_result, *result);
    939   if (!n_result) {
    940     // only thing left is sentinel, return sentinel
    941     debug("only thing to return is sentinel\n");
    942     n_result = 1;
    943     result[0]->line = NULL;
    944     result[0]->full_line = NULL;
    945   }
    946   debug("prepared %d results to return\n", n_result);
    947   if (was_full)
    948     pthread_cond_signal(&s->ctrl.full);
    949   pthread_mutex_unlock(&s->ctrl.mutex);
    950   return n_result;
    951 }
    952 
    953 // fifo for input
    954 // we want to read lines at the time we get them even if we are delaying writes
    955 // on the irc channel
    956 fifo_set fifos;
    957 
    958 
    959 /* Our main chan is either the first CLI chan or our own chan if there are no
    960  * CLI chans */
    961 char* first_chan()
    962 {
    963   if (args.n_channels)
    964     return args.channels[0];
    965   else return args.nick;
    966 }
    967 
    968 const char *input_nick(const char *nick) {
    969   int c;
    970   const char *result;
    971   if (args.track == NO)
    972     return nick;
    973   pthread_mutex_lock(&fifos.ctrl.mutex);
    974   c = scan_collection(&fifos, (const char **) fifos.surface_name, nick);
    975   if (c < 0)
    976     result = nick; // dest changed nick, but we're not supposed to track that
    977   else
    978     result = fifos.chans[c];
    979   pthread_mutex_unlock(&fifos.ctrl.mutex);
    980   return result;
    981 }
    982 
    983 const char *output_nick(const char *nick) {
    984   int c;
    985   char *result;
    986   if (args.track != UNIQUE)
    987     return nick;
    988   pthread_mutex_lock(&fifos.ctrl.mutex);
    989   c = scan_collection(&fifos, (const char **) fifos.surface_name, nick);
    990   assert(c >= 0);
    991   result = fifos.chans[c];
    992   pthread_mutex_unlock(&fifos.ctrl.mutex);
    993   return result;
    994 }
    995 
    996 #define MATCH_CMD(str, cmd) \
    997   ( strncmp((str) + 1, (cmd " "), sizeof(cmd) / sizeof(char) ) \
    998   ? NULL : (str) + sizeof(cmd) / sizeof(char) + 1 )
    999 
   1000 #define MATCH_CMD0(str, cmd) \
   1001   ( strncmp((str) + 1, (cmd), sizeof(cmd) / sizeof(char) - 1) \
   1002   ? NULL : (str) + sizeof(cmd) / sizeof(char) )
   1003 
   1004 #define IS_END(ptr) ( !(ptr) || (ptr) == '\n' )
   1005 
   1006 /* return value should be freed by callee */
   1007 char* consume_word(char **from)
   1008 {
   1009   const char *start;
   1010   char *result;
   1011   int size = 0;
   1012   while (**from == ' ')
   1013     ++*from;
   1014   start = *from;
   1015   while (**from != ' ' && !IS_END(**from)) {
   1016     ++*from;
   1017     ++size;
   1018   }
   1019   while (**from == ' ')
   1020     ++*from;
   1021   result = malloc((size + 1) * sizeof(char));
   1022   assert(result);
   1023   strncpy(result, start, size);
   1024   result[size] = 0;
   1025   return result;
   1026 }
   1027 
   1028 int do_me(irc_session_t *s, char *chan, char *password, char *line) {
   1029   /* TODO2 only join channels we haven't joined yet */
   1030   int ret;
   1031   int oldend;
   1032   char oldchar;
   1033   if (args.join && chan[0] == '#')
   1034     irc_cmd_join(s, chan, password);
   1035   if (args.own) {
   1036     if (args.event_to == COMMAND)
   1037       mprintf("[%s] <%s%s> /me %s", chan,
   1038           output_nick(args.nick),
   1039           args.with_host ? args.hostname : "",
   1040           line);
   1041     if (args.event_to == MESSAGE)
   1042       mprintf("[%s] <%s%s> * %s%s %s", chan,
   1043           output_nick(args.nick),
   1044           args.with_host ? args.hostname : "",
   1045           output_nick(args.nick),
   1046           args.with_host ? args.hostname : "",
   1047           line);
   1048   }
   1049 
   1050   /* kludge: replace trailing '\n's by '\0's temporarily to avoid problem with
   1051    * the '\01' of ACTION falling on a different line */
   1052   oldend = strlen(line);
   1053   while (oldend >= 0 && (line[oldend] == '\n' || line[oldend] == 0))
   1054     oldend--;
   1055   oldend++;
   1056   // oldend is now the last index that is >= 0 and line[oldend] is '\0' or '\n'
   1057   oldchar = line[oldend];
   1058   line[oldend] = 0;
   1059   ret = irc_cmd_me(s, chan, line);
   1060   line[oldend] = oldchar;
   1061   return ret;
   1062 }
   1063 
   1064 int do_say(irc_session_t *s, char *chan, char *password, char *line, enum do_say_type type) {
   1065   /* TODO2 only join channels we haven't joined yet */
   1066   if (args.join && chan[0] == '#')
   1067     irc_cmd_join(s, chan, password);
   1068   if (args.own) {
   1069     mprintf("[%s] <%s%s> %s", chan,
   1070         output_nick(args.nick),
   1071         args.with_host ? args.hostname : "",
   1072         line);
   1073   }
   1074 
   1075   switch (type) {
   1076     case DO_SAY_MSG:
   1077       return irc_cmd_msg(s, chan, line);
   1078 
   1079     case DO_SAY_NOTICE:
   1080       return irc_cmd_notice(s, chan, line);
   1081   }
   1082 
   1083   abort(); // unreachable
   1084 }
   1085 
   1086 /* If chan is suffixed by a password, modify it and return the modified offset */
   1087 int get_password(char *chan) {
   1088   int pos = 0;
   1089   while (chan[pos] != ':' && chan[pos])
   1090     pos++;
   1091   if (chan[pos]) {
   1092     chan[pos] = 0;
   1093     return pos;
   1094   }
   1095   return -1;
   1096 }
   1097 
   1098 void revert_password(char *chan, int pos) {
   1099   if (pos < 0) return;
   1100   chan[pos] = ':';
   1101 }
   1102 
   1103 
   1104 int my_irc_cmd_oper(irc_session_t *s, char *user, char *pass)
   1105 {
   1106   return irc_send_raw(s, "OPER %s %s\n", user, pass);
   1107 }
   1108 
   1109 
   1110 /* Send a message or do a command */
   1111 /* Return value is 0 if OK, >0 if error, -1 if correctly exited */
   1112 int do_cmd_msg(irc_session_t *s, char *chan, char *line)
   1113 {
   1114   int rsl = 0;
   1115   int pw_pos = get_password(chan);
   1116   char *password = chan + pw_pos + 1;
   1117 
   1118   debug("do_cmd_msg %s %s\n", chan, line);
   1119 
   1120   /* TODO2 ask the server to notify, if possible */
   1121   /* TODO2 find a way to get real host name (part after !) */
   1122   
   1123   if (line[0] == '/' && args.command_to_event) {
   1124     char *arg;
   1125     if ((arg = MATCH_CMD(line, "nick"))) {
   1126       rsl = irc_cmd_nick (s, arg);
   1127     } else if ((arg = MATCH_CMD(line, "mode"))) {
   1128       rsl = irc_cmd_channel_mode (s, chan, arg);
   1129     } else if ((arg = MATCH_CMD0(line, "part"))) {
   1130       if (!*arg || *arg == '\n') {
   1131         rsl = irc_cmd_part(s, chan);
   1132       } else if (*arg == ' ') {
   1133         rsl = irc_cmd_part(s, arg + 1);
   1134       } else {
   1135         info("Unrecognized command: %s", line);
   1136       }
   1137     } else if ((arg = MATCH_CMD0(line, "join"))) {
   1138       char *to_join = NULL;
   1139       // we must revert temporarily so that we can join
   1140       revert_password(chan, pw_pos);
   1141       if (!*arg || *arg == '\n') {
   1142         to_join = chan;
   1143       } else if (*arg == ' ') {
   1144         to_join = arg + 1;
   1145       }
   1146       if (to_join && to_join[0] == '#') {
   1147         if (args.join) {
   1148           // now, get the right index
   1149           int pw_pos2 = get_password(to_join);
   1150           rsl = irc_cmd_join(s, to_join, pw_pos2 >= 0 ? to_join + pw_pos2 + 1 : NULL);
   1151           // and revert
   1152           revert_password(to_join, pw_pos);
   1153         } else {
   1154           info("Will not join because --no-auto-join was set\n");
   1155         }
   1156       } else {
   1157         info("Unrecognized command: %s", line);
   1158       }
   1159       // and we go back to the previous state
   1160       pw_pos = get_password(chan);
   1161     } else if ((arg = MATCH_CMD(line, "topic"))) {
   1162       rsl = irc_cmd_topic(s, chan, arg);
   1163     } else if ((arg = MATCH_CMD(line, "quote"))) {
   1164       rsl = irc_send_raw(s, "%s", arg);
   1165     } else if ((arg = MATCH_CMD0(line, "quit"))) {
   1166       int ok = 1;
   1167       if (*arg == ' ') {
   1168         rsl = irc_cmd_quit(s, arg + 1);
   1169       } else if (!*arg || *arg == '\n') {
   1170         rsl = irc_cmd_quit(s, NULL);
   1171       } else {
   1172         info("Unrecognized command: %s", line);
   1173         ok = 0;
   1174       }
   1175       if (!rsl && ok)
   1176         rsl = -1;
   1177     } else if ((arg = MATCH_CMD(line, "invite"))) {
   1178       char *inick = consume_word(&arg);
   1179       if (!IS_END(*arg)) {
   1180         rsl = irc_cmd_invite(s, inick, arg);
   1181       } else {
   1182         rsl = irc_cmd_invite(s, inick, chan);
   1183       }
   1184       free(inick);
   1185     } else if ((arg = MATCH_CMD(line, "oper"))) {
   1186       char *ouser = consume_word(&arg);
   1187       if (!IS_END(*arg)) {
   1188         rsl = my_irc_cmd_oper(s, ouser, arg);
   1189       } else {
   1190         rsl = my_irc_cmd_oper(s, ouser, "");
   1191       }
   1192       free(ouser);
   1193     } else if ((arg = MATCH_CMD(line, "kick"))) {
   1194       char *knick = consume_word(&arg);
   1195       rsl = irc_cmd_kick(s, knick, chan, arg);
   1196       free(knick);
   1197     } else if ((arg = MATCH_CMD0(line, "me"))) {
   1198       if (*arg == ' ') {
   1199         rsl = do_me(s, chan, password, arg + 1);
   1200       } else if (!*arg || *arg == '\n') {
   1201         rsl = do_me(s, chan, password, arg);
   1202       } else {
   1203         info("Unrecognized command: %s", line);
   1204       }
   1205     } else if ((arg = MATCH_CMD0(line, "say"))) {
   1206       // just a way to escape messages starting by '/'
   1207       if (*arg == ' ') {
   1208         rsl = do_say(s, chan, password, arg + 1, DO_SAY_MSG);
   1209       } else if (!*arg || *arg == '\n') {
   1210         rsl = do_say(s, chan, password, arg, DO_SAY_MSG);
   1211       } else {
   1212         info("Unrecognized command: %s", line);
   1213       }
   1214     } else if ((arg = MATCH_CMD0(line, "notice"))) {
   1215       // same, but for notices
   1216       if (*arg == ' ') {
   1217         rsl = do_say(s, chan, password, arg + 1, DO_SAY_NOTICE);
   1218       } else if (!*arg || *arg == '\n') {
   1219         rsl = do_say(s, chan, password, arg, DO_SAY_NOTICE);
   1220       } else {
   1221         info("Unrecognized command: %s", line);
   1222       }
   1223     } else if (line[1] == ' ') {
   1224       // another way, compatible with irssi: "/ /message"
   1225       rsl = do_say(s, chan, password, line + 2, DO_SAY_MSG);
   1226     } else {
   1227       info("Unrecognized or malformed command: %s", line);
   1228     }
   1229     // TODO2: names, list, topic, usermode, whois, raw...
   1230     // with correct user tracking!
   1231   } else {
   1232     /* TODO2 only join channels we haven't joined yet */
   1233     if (args.join && chan[0] == '#') {
   1234       rsl = irc_cmd_join(s, chan, password);
   1235     }
   1236 
   1237     if (args.track != NO) {
   1238       char lline[MAX_LINE_LEN + MAX_NICK_LEN + 2];
   1239       int seppos = -1;
   1240       int i;
   1241       for (i=0; i<strlen(line); i++) {
   1242         if (line[i] == ',' || line[i] == ':') {
   1243           seppos = i;
   1244           break;
   1245         }
   1246         if (line[i] == ' ')
   1247           break;
   1248       }
   1249       if (seppos == -1) {
   1250         rsl = do_say(s, chan, password, line, DO_SAY_MSG);
   1251       } else {
   1252         char sep;
   1253         sep = line[seppos];
   1254         line[seppos] = 0;
   1255         pthread_mutex_lock(&fifos.ctrl.mutex);
   1256         int c = scan_collection(&fifos, (const char **) fifos.chans, line);
   1257         if (c < 0)
   1258           strncpy(lline, line, MAX_NICK_LEN-1);
   1259         else
   1260           strncpy(lline, fifos.surface_name[c], MAX_NICK_LEN-1);
   1261         pthread_mutex_unlock(&fifos.ctrl.mutex);
   1262         line[seppos] = sep;
   1263         strncat(lline, line + seppos, MAX_LINE_LEN-1);
   1264         rsl = do_say(s, chan, password, lline, DO_SAY_MSG);
   1265       }
   1266     } else {
   1267       rsl = do_say(s, chan, password, line, DO_SAY_MSG);
   1268     }
   1269   }
   1270 
   1271   revert_password(chan, pw_pos);
   1272 
   1273   if (rsl)
   1274     return rsl;
   1275 
   1276   return 0;
   1277 }
   1278 
   1279 // Handle a command request which might not specify a destination
   1280 void cmd_msg(char *full_line, char *target, char *line)
   1281 {
   1282   char *msg;
   1283 
   1284   debug("cmd_msg %s \"%s\" (%p), last_in %s\n", target, line, line, args.last_chan_in);
   1285   
   1286   if (args.track != NO) {
   1287     char real_nick[MAX_NICK_LEN];
   1288     int seppos = -1;
   1289     int i;
   1290     for (i=0; i<strlen(line); i++) {
   1291       if (line[i] == ',' || line[i] == ':') {
   1292         seppos = i;
   1293         break;
   1294       }
   1295       if (line[i] == ' ')
   1296         break;
   1297     }
   1298     if (seppos >= 0) {
   1299       char sep;
   1300       sep = line[seppos];
   1301       line[seppos] = 0;
   1302       pthread_mutex_lock(&fifos.ctrl.mutex);
   1303       int c = scan_collection(&fifos, (const char **) fifos.surface_name, line);
   1304       if (c >= 0)
   1305         strncpy(real_nick, fifos.chans[c], MAX_NICK_LEN);
   1306       pthread_mutex_unlock(&fifos.ctrl.mutex);
   1307       line[seppos] = sep;
   1308       if (c >= 0) {
   1309         // replace surface name by real name
   1310         // we must move temporarily the line out of the way
   1311         debug("i will allocate %d bytes and copy %s\n", (strlen(line) + 1) * sizeof(char), line);
   1312         char* old_line = malloc((strlen(line) + 1) * sizeof(char));
   1313         assert(old_line);
   1314         strcpy(old_line, line);
   1315 
   1316         // the size of the line needs to change
   1317         // first, count the part before line starts
   1318         // then count the length of the line plus \0 byte
   1319         // then adjust: the size of the real_nick
   1320         //   minus seppos, the size of the old nick
   1321         int new_size = (line-full_line) + strlen(line) + 1 + (strlen(real_nick) - seppos);
   1322         int target_offset = target ? (target - full_line) : (-1);
   1323         int line_offset = line - full_line;
   1324         debug("-1 my full line is: %s\n", full_line);
   1325         char *nfull_line = realloc(full_line, new_size*sizeof(char));
   1326         assert(nfull_line);
   1327         full_line = nfull_line;
   1328         debug("resized to: %d\n", new_size*sizeof(char));
   1329         debug("0 my full line is: %s\n", full_line);
   1330         if (target)
   1331           target = full_line + target_offset;
   1332         line = full_line + line_offset;
   1333 
   1334         // now do the copy
   1335         debug("1 my full line is: %s\n", full_line);
   1336         strcpy(line, real_nick);
   1337         debug("2 my full line is: %s\n", full_line);
   1338         strcat(line, old_line + seppos);
   1339         debug("3 my full line is: %s\n", full_line);
   1340 
   1341         free(old_line);
   1342 
   1343         debug("now my full line is: %s\n", full_line);
   1344         debug("now my target is: %s\n", target);
   1345         debug("now my line is: %s\n", line);
   1346       }
   1347     }
   1348   }
   1349 
   1350   /* Manage the fact that target may be "" */
   1351   if (!target) {
   1352     debug("have to infer destination\n");
   1353     switch(args.default_destination) {
   1354       case DEFAULT_FIRST:
   1355         push_fifo_set(&fifos, full_line, first_chan(), line);
   1356         return;
   1357 
   1358       case DEFAULT_LAST_IN:
   1359         if (args.show_nick_prefix && args.last_nick_in[0]
   1360             && args.last_chan_in[0] == '#') {
   1361           if (line[0] != '\n') {
   1362             msg = malloc((2*MAX_LINE_LEN + 2)* sizeof(char));
   1363             assert(msg);
   1364             msg[0] = 0;
   1365             if (args.track == YES) {
   1366               strncat((char*) msg, input_nick(args.last_nick_in),
   1367                   MAX_LINE_LEN-1);
   1368             } else {
   1369               strncat((char*) msg, args.last_nick_in, MAX_LINE_LEN-1);
   1370             }
   1371             strcat((char*) msg, ": ");
   1372             strncat((char*) msg, line, MAX_LINE_LEN-1);
   1373             free(full_line);
   1374             full_line = msg;
   1375             line = msg;
   1376           } else {
   1377             args.last_nick_in[0] = 0;
   1378           }
   1379         }
   1380         debug("last_chan_in is %s\n", args.last_chan_in);
   1381         return push_fifo_set(&fifos, full_line,
   1382             args.last_chan_in[0] ? args.last_chan_in : first_chan(), line);
   1383 
   1384       case DEFAULT_LAST_OUT:
   1385         return push_fifo_set(&fifos, full_line,
   1386            args.last_chans_out[0] ? args.last_chans_out : first_chan(), line);
   1387 
   1388       case DEFAULT_ALL:
   1389         //TODO2: ugly, could have generated the comma-separated string
   1390         //like for last_chans_out!
   1391         if (args.n_channels)
   1392           return push_fifo_set_all(&fifos, full_line, args.channels, args.n_channels, line);
   1393         else
   1394           return push_fifo_set(&fifos, full_line, args.nick, line); // fallback
   1395       
   1396       default:
   1397         return; // won't happen
   1398     }
   1399   } else {
   1400     push_fifo_set(&fifos, full_line, target, line);
   1401   }
   1402 }
   1403 
   1404 void saw_user(const char *nick) {
   1405   if (!nick) return;
   1406   if (!surface_nick_exists(&fifos, nick)) {
   1407     // unknown user, register them
   1408     debug("Register this new user\n");
   1409     register_nick(&fifos, nick);
   1410   }
   1411 }
   1412  
   1413 // Manage an event
   1414 void manage_event (irc_session_t *session, const char *event, const char *origin,
   1415     const char **params, unsigned int count)
   1416 {
   1417   int rsl = 0;
   1418   char temp[MAX_NICK_LEN+1];
   1419 
   1420   saw_user(origin);
   1421   if (origin) {
   1422     debug("irc_target_get_nick(%s, %d)\n", origin, MAX_NICK_LEN);
   1423     irc_target_get_nick(origin, temp, MAX_NICK_LEN);
   1424   } else {
   1425     temp[0] = 0;
   1426   }
   1427   if (!strcmp(temp, args.nick)) {
   1428     // it is us, store our hostname
   1429     irc_target_get_host(origin, args.hostname, MAX_NICK_LEN);
   1430   }
   1431 
   1432   if (!strcmp(event, "ERROR")) {
   1433     if (count == 1) {
   1434       die(E_SERVER_ERROR, "Error from server: %s\n", params[0]);
   1435     } else {
   1436       die(E_SERVER_ERROR, "Unspecified error from server\n");
   1437     }
   1438   }
   1439 
   1440   irc_ctx_t * ctx;
   1441   switch(atoi(event))  {
   1442     case LIBIRC_RFC_ERR_ERRONEUSNICKNAME:
   1443       if (args.force_nick) {
   1444         die(E_BADNAME, "Could not set nick\n");
   1445       } else {
   1446         if (!((irc_ctx_t *) irc_get_ctx(session))->ready) {
   1447           info("Could not set nick, reverting to \"irctk\"\n");
   1448           strncpy(args.nick, "irctk", MAX_NICK_LEN-1);
   1449           rsl = irc_cmd_nick(session, "irctk");
   1450         } else {
   1451           info("Could not set nick, ignoring\n");
   1452           rsl = 0;
   1453         }
   1454       }
   1455       break;
   1456     case LIBIRC_RFC_ERR_NICKCOLLISION:
   1457     case LIBIRC_RFC_ERR_NICKNAMEINUSE:
   1458       if (args.force_nick) {
   1459         die(E_BADNAME, "Nickname already in use\n");
   1460       } else {
   1461         int len = strlen(args.nick);
   1462         if (len >= MAX_NICK_LEN-1) {
   1463           die(E_BADNAME, "No available nickname found\n");
   1464         } else {
   1465           args.nick[len] = '_';
   1466           args.nick[len+1] = 0;
   1467           // no need to rename self because the surface_name of ourself points
   1468           // to args.nick
   1469           rsl = irc_cmd_nick(session, args.nick);
   1470           info("Requested nick is taken, try %s\n", args.nick);
   1471         }
   1472       }
   1473       break ;
   1474     case LIBIRC_RFC_ERR_PASSWDMISMATCH:
   1475       ctx = (irc_ctx_t *) irc_get_ctx (session);
   1476       if (ctx->ready) {
   1477         // this could be about a wrong password for /oper, so make it non-fatal
   1478         info("Invalid or missing password.\n");
   1479       } else {
   1480         // assuming this is about trying to connect to the server
   1481         die(E_PASSWORD, "Invalid or missing password when connecting to server.\n");
   1482       }
   1483       break;
   1484     case LIBIRC_RFC_ERR_BADCHANNELKEY:
   1485       info("Cannot join %s: bad password.\n", params[1]);
   1486       break;
   1487     case LIBIRC_RFC_ERR_NOSUCHCHANNEL:
   1488       info("Cannot join %s: no such channel.\n", params[1]);
   1489       break ;
   1490     case LIBIRC_RFC_ERR_TOOMANYCHANNELS:
   1491       info("Cannot join %s: you have joined too many channels.\n", params[1]);
   1492       break ;
   1493     case LIBIRC_RFC_ERR_INVITEONLYCHAN:
   1494       info("Cannot join %s: channel is invite-only.\n", params[1]);
   1495       break ;
   1496     case LIBIRC_RFC_ERR_NOTONCHANNEL:
   1497       info("Cannot perform action in %s: not on channel.\n", params[1]);
   1498       break ;
   1499     case LIBIRC_RFC_ERR_CHANOPRIVSNEEDED:
   1500       info("Cannot perform action in %s: you're not channel operator.\n", params[1]);
   1501       break ;
   1502     case LIBIRC_RFC_ERR_CANNOTSENDTOCHAN:
   1503       info("Cannot send to %s.\n", params[1]);
   1504       break ;
   1505     case ERR_BADCHANNAME:
   1506       info("Could not join %s.\n", params[1]);
   1507       break ;
   1508     case LIBIRC_RFC_ERR_USERONCHANNEL:
   1509       info("Cannot invite %s to %s: %s is already on %s.\n", params[1], params[2], params[1], params[2]);
   1510       break ;
   1511     case LIBIRC_RFC_ERR_NOSUCHNICK:
   1512       info("No such nick/channel: %s\n", params[1]);
   1513       break ;
   1514     default:
   1515       if (!strcmp(event, "INVITE")) {
   1516         if (args.event_to == COMMAND)
   1517           mprintf("[%s] <%s> /invite %s %s\n", params[1], output_nick(origin), params[0], params[1]);
   1518         if (args.event_to == MESSAGE)
   1519           mprintf("[%s] -!- %s invited %s to join %s\n", params[1], output_nick(origin), params[0], params[1]);
   1520       }
   1521     }
   1522   
   1523   switch (count ) {
   1524   case 2:
   1525     debug("%s %s: %s %s\n", origin, event, params[0], params[1]);
   1526     break;
   1527   case 1:
   1528     debug("%s %s: %s\n", origin, event, params[0]);
   1529     break;
   1530   default:
   1531     debug("Event \"%s\", origin: \"%s\", params: %d\n", event,
   1532           origin ? origin : "NULL", count);
   1533   }
   1534   if (rsl > 0) {
   1535     int error = irc_errno(session);
   1536     info("(%d) %s when managing event: %s\n",
   1537          error, irc_strerror(error), event);
   1538   }
   1539 }
   1540 
   1541 // Manage a mode event
   1542 void manage_mode_event (irc_session_t *session, const char *event, const char *origin,
   1543     const char **params, unsigned int count)
   1544 {
   1545   manage_event (session, event, origin, params, count);
   1546 
   1547   // now, specific processing
   1548   if (args.event_to == COMMAND)
   1549     mprintf("[%s] <%s> /mode %s %s\n", params[0], output_nick(origin), params[1], params[0]);
   1550   if (args.event_to == MESSAGE)
   1551     mprintf("[%s] -!- %s mode %s on %s\n", params[0], output_nick(origin), params[1], params[0]);
   1552 }
   1553 
   1554 // Manage a umode event
   1555 void manage_umode_event (irc_session_t *session, const char *event, const char *origin,
   1556     const char **params, unsigned int count)
   1557 {
   1558   manage_event (session, event, origin, params, count);
   1559 
   1560   // now, specific processing
   1561   if (args.event_to == COMMAND)
   1562     mprintf("[%s] <%s> /mode %s %s\n", args.nick, output_nick(origin), params[0], args.nick);
   1563   if (args.event_to == MESSAGE)
   1564     mprintf("[%s] -!- %s mode %s on %s\n", args.nick, output_nick(origin), params[0], args.nick);
   1565 }
   1566 
   1567 // Handle a nick event
   1568 void event_nick (irc_session_t *session, const char *event, const char *origin,
   1569     const char **params, unsigned int count)
   1570 {
   1571   debug("[] <%s> /nick %s\n", origin, params[0]);
   1572   saw_user(origin);
   1573   rename_user(&fifos, origin, params[0]);
   1574   if (!strcmp(origin, args.nick))
   1575     strncpy(args.nick, params[0], MAX_NICK_LEN-1);
   1576   if (args.event_to == COMMAND)
   1577     mprintf("[] <%s> /nick %s\n", output_nick(origin), params[0]);
   1578   if (args.event_to == MESSAGE)
   1579     mprintf("[] -!- %s is now known as %s\n", output_nick(origin), params[0]);
   1580 }
   1581 
   1582 // Handle a join event
   1583 void event_join (irc_session_t * session, const char *event, const char *origin,
   1584     const char ** params, unsigned int count)
   1585 {
   1586   manage_event(session, event, origin, params, count);
   1587   debug("[%s] <%s> /join %s\n", params[0], origin, params[0]);
   1588   saw_user(params[0]);
   1589 
   1590   if (args.event_to == COMMAND)
   1591     mprintf("[%s] <%s> /join %s\n", params[0], output_nick(origin), params[0]);
   1592   if (args.event_to == MESSAGE)
   1593     mprintf("[%s] -!- %s has joined %s\n", params[0], output_nick(origin), params[0]);
   1594 }
   1595 
   1596 // Handle a part event
   1597 void event_part (irc_session_t *session, const char *event, const char *origin,
   1598     const char **params, unsigned int count)
   1599 {
   1600   saw_user(origin);
   1601   if (args.event_to == COMMAND)
   1602     mprintf("[%s] <%s> /part %s\n", params[0], output_nick(origin), params[0]);
   1603   if (args.event_to == MESSAGE)
   1604     mprintf("[%s] -!- %s has left %s (%s)\n", params[0], output_nick(origin), params[0],
   1605         params[1]);
   1606 }
   1607 
   1608 // Handle a topic event
   1609 void event_topic (irc_session_t *session, const char *event, const char *origin,
   1610     const char **params, unsigned int count) 
   1611 {
   1612   saw_user(origin);
   1613   if (args.event_to == COMMAND)
   1614     mprintf("[%s] <%s> /topic %s\n", params[0], output_nick(origin), params[1]);
   1615   if (args.event_to == MESSAGE)
   1616     mprintf("[%s] -!- %s changed the topic of %s to %s\n", params[0], output_nick(origin),
   1617         params[0], params[1]);
   1618 }
   1619 
   1620 // Handle a quit event
   1621 void event_quit (irc_session_t *session, const char *event, const char *origin,
   1622     const char **params, unsigned int count) 
   1623 {
   1624   saw_user(origin);
   1625   if (args.event_to == COMMAND)
   1626     mprintf("[] <%s> /quit %s\n", output_nick(origin), params[0]);
   1627   if (args.event_to == MESSAGE)
   1628     mprintf("[] -!- %s has quit (%s)\n", output_nick(origin), params[0]);
   1629 }
   1630 
   1631 // Handle an action
   1632 void event_ctcp_action (irc_session_t *session, const char *event,
   1633     const char *origin, const char **params, unsigned int count) 
   1634 {
   1635   saw_user(origin);
   1636   if (args.event_to == COMMAND)
   1637     mprintf("[%s] <%s> /me %s\n", params[0], output_nick(origin), params[1]);
   1638   if (args.event_to == MESSAGE)
   1639     mprintf("[%s] *** %s %s\n", params[0], output_nick(origin), params[1]);
   1640 }
   1641 
   1642 // Handle a kick event
   1643 void event_kick (irc_session_t *session, const char *event, const char *origin,
   1644     const char **params, unsigned int count) 
   1645 {
   1646   saw_user(origin);
   1647   saw_user(params[1]);
   1648   if (args.event_to == COMMAND)
   1649     mprintf("[%s] <%s> /kick %s %s\n", params[0], output_nick(origin), params[1], params[2]);
   1650   if (args.event_to == MESSAGE)
   1651     mprintf("[%s] -!- %s was kicked from %s by %s (%s)\n", params[0], output_nick(params[1]),
   1652         params[0], output_nick(origin), params[2]);
   1653 }
   1654 
   1655 // Handle a connect event
   1656 void event_connect (irc_session_t *session, const char *event, const char *origin,
   1657     const char **params, unsigned int count) 
   1658 {
   1659   saw_user(origin);
   1660   irc_ctx_t * ctx = (irc_ctx_t *) irc_get_ctx (session);
   1661   int i;
   1662 
   1663   manage_event(session, event, origin, params, count);
   1664 
   1665   pthread_mutex_lock(&args.mutex);
   1666   ctx->ready = 1;
   1667   pthread_mutex_unlock(&args.mutex);
   1668   debug("Connected!\n");
   1669 
   1670   for (i = 0; i < ctx->n_channels; i++) {
   1671     if (ctx->channels[i][0] != '#')
   1672       continue; // not a regular channel
   1673     debug("Attempt to join %s\n", ctx->channels[i]);
   1674     int pw_pos = get_password(ctx->channels[i]);
   1675     irc_cmd_join (session, ctx->channels[i], ctx->channels[i] + pw_pos + 1);
   1676     revert_password(ctx->channels[i], pw_pos);
   1677   }
   1678 }
   1679 
   1680 // Handle a channel event
   1681 void event_channel (irc_session_t *session, const char *event,
   1682     const char *origin, const char **params, unsigned int count) 
   1683 {
   1684   saw_user(origin);
   1685   int i = 0, ok = 1, ok2;
   1686   const char* pruned;
   1687 
   1688   if (count != 2) return;
   1689 
   1690   if (args.filter)
   1691     if (strcmp(params[0], args.nick)) {
   1692       int len = strlen(args.nick);
   1693       int offset = 0;
   1694       while (params[1][offset] == ' ')
   1695         offset++;
   1696       for (i=0; i<len; i++)
   1697         if (args.nick[i] != params[1][offset + i] || !params[1][offset + i]) {
   1698           ok = 0;
   1699           break;
   1700         }
   1701       if (ok) {
   1702         // check the nick ends here
   1703         char next = params[1][offset + len];
   1704         if (next != ':' && next != ' ' && next != ',')
   1705           ok = 0; // nick continues
   1706       }
   1707     }
   1708 
   1709   if (ok) {
   1710     if (!args.prune) {
   1711       mprintf ("[%s] <", params[0]);
   1712       if (!origin)
   1713         mprintf("someone");
   1714       else
   1715         /* not robust, and useless since we use STRIPNICKS */
   1716         //while(origin[i++] != '!') putchar(origin[i-1]);
   1717         mprintf("%s", output_nick(origin));
   1718       mprintf("> ");
   1719       if (params[1][0] == '/' && args.event_to == COMMAND)
   1720         mprintf("/ "); // escape slashes
   1721       mprintf("%s\n", params[1] );
   1722     } else {
   1723       pruned = params[1];
   1724       while (pruned[0] == ' ') pruned++;
   1725       ok2 = 1;
   1726       while(pruned[0] != ':' && pruned[0] != ',' && pruned[0] != ' ') {
   1727         if (!pruned[0] || pruned[0] == ' ') {
   1728           ok2 = 0;
   1729           break;
   1730         }
   1731         pruned++;
   1732       } 
   1733       if (params[0][0] != '#') {
   1734         // must be a private message
   1735         ok2 = 0;
   1736       }
   1737 
   1738       if (ok2) {
   1739         pruned++; // skip ':'
   1740         while(pruned[0] == ' ')
   1741           pruned++; // skip spaces
   1742       }
   1743       else pruned = params[1]; // no address
   1744 
   1745       mprintf("%s\n", pruned);
   1746     }
   1747     // if addressed in our private chan, reply on the sender's priv chan
   1748     if (strcmp(params[0], args.nick)) 
   1749       strncpy(args.last_chan_in, params[0], MAX_CHAN_LEN-1);
   1750     else strncpy(args.last_chan_in, origin?origin:"someone", MAX_CHAN_LEN-1);
   1751     // if UNIQUE, store the uid, else store the surface
   1752     if (args.track == UNIQUE) {
   1753       strncpy(args.last_nick_in, origin?output_nick(origin):"someone",
   1754           MAX_NICK_LEN-1);
   1755     } else {
   1756       strncpy(args.last_nick_in, origin?origin:"someone", MAX_NICK_LEN-1);
   1757     }
   1758   }
   1759 }
   1760 
   1761 // Handle a privmsg
   1762 void event_privmsg (irc_session_t *session, const char *event,
   1763     const char *origin, const char **params, unsigned int count)
   1764 {
   1765   saw_user(origin);
   1766   manage_event(session, event, origin, params, count);
   1767 
   1768   event_channel (session, event, origin, params, count);
   1769 }
   1770 
   1771 // Handle a numeric event
   1772 void event_numeric (irc_session_t *session, unsigned int event,
   1773     const char *origin, const char **params, unsigned int count) 
   1774 {
   1775   saw_user(origin);
   1776   char buf[24];
   1777   sprintf (buf, "%d", event);
   1778 
   1779   manage_event(session, buf, origin, params, count);
   1780 }
   1781 
   1782 
   1783 irc_session_t* do_connect()
   1784 {
   1785   irc_session_t *s;
   1786   irc_callbacks_t callbacks;
   1787   memset (&callbacks, 0, sizeof(callbacks));
   1788   args.ready = 0 ;
   1789   callbacks.event_connect = event_connect;
   1790   callbacks.event_nick = event_nick;
   1791   callbacks.event_quit = event_quit;
   1792   callbacks.event_join = event_join;
   1793   callbacks.event_part = event_part;
   1794   callbacks.event_mode = manage_mode_event;
   1795   callbacks.event_umode = manage_umode_event;
   1796   callbacks.event_topic = event_topic;
   1797   callbacks.event_kick = event_kick;
   1798   callbacks.event_channel = event_channel;
   1799   callbacks.event_privmsg = event_privmsg;
   1800   callbacks.event_notice = manage_event;
   1801   callbacks.event_invite = manage_event;
   1802   callbacks.event_ctcp_rep = manage_event;
   1803   callbacks.event_ctcp_action = event_ctcp_action;
   1804   callbacks.event_unknown = manage_event;
   1805   callbacks.event_numeric = event_numeric;
   1806   callbacks.event_dcc_chat_req = NULL;
   1807   callbacks.event_dcc_send_req = NULL;
   1808 
   1809   s = irc_create_session (&callbacks);
   1810 
   1811   if (!s)
   1812     die(E_SESSION, "Could not create session\n");
   1813   
   1814   if (!args.with_host)
   1815     irc_option_set (s, LIBIRC_OPTION_STRIPNICKS);
   1816   if(!args.ssl_check)
   1817     irc_option_set (s, LIBIRC_OPTION_SSL_NO_VERIFY);
   1818   irc_set_ctx (s, &args);
   1819   
   1820   debug("Connecting to %s port %d with nick %s and password %s...\n",
   1821         args.server, args.port, args.nick, args.password);
   1822 
   1823   if (irc_connect(s, args.server, args.port, args.password, args.nick,
   1824         args.username, args.realname)) {
   1825     info("Could not connect: %s\n", irc_strerror (irc_errno(s)));
   1826     if (irc_errno(s) == LIBIRC_ERR_SSL_NOT_SUPPORTED)
   1827       info("(Did you ./configure --enable-ssl when building libircclient?)\n");
   1828     exit(E_CONNECT);
   1829   }
   1830 
   1831   return s;
   1832 }
   1833 
   1834 // The IRC thread entry point
   1835 static void* irc_thread (void *arg)
   1836 {
   1837   irc_session_t * sp = (irc_session_t *) arg;
   1838   int ret;
   1839   irc_run(sp);
   1840   ret = irc_errno(sp);
   1841   pthread_mutex_lock(&args.mutex);
   1842   if (args.ready == 0) {
   1843     // we hadn't even connected yet
   1844     // TODO2 maybe not all errors are fatal?
   1845     if (ret)
   1846       info("Connection failed: %s\n", irc_strerror(ret));
   1847     else
   1848       // TODO2 this seems to happen when a port is blocked
   1849       // can we notice the error elsewhere?
   1850       info("Connection failed (no reason specified)\n");
   1851     if (ret == LIBIRC_ERR_CONNECT_SSL_FAILED)
   1852       info("(Maybe try without --ssl?)\n");
   1853     if (ret == LIBIRC_ERR_SSL_CERT_VERIFY_FAILED)
   1854       info("(Try with --no-check-certificate if security is not an issue?)\n");
   1855   } else {
   1856     info("Connection interrupted: %s\n", irc_strerror(ret));
   1857   }
   1858   args.ready = -ret;
   1859   pthread_mutex_unlock(&args.mutex);
   1860   return 0;
   1861 }
   1862 
   1863 // The fifo_in thread entry point
   1864 static void* fifo_in_thread (void *arg) {
   1865   char *line = NULL;
   1866   int res;
   1867   size_t size;
   1868 
   1869   while ((res = getline(&line, (size_t*) &size, stdin)) != -1) {
   1870     debug("[thread_in] READS %s\n", line);
   1871     debug("someone will have to free %p\n", line);
   1872     if (line[0] == '[') {
   1873       int i=0;
   1874       char *msg;
   1875       char *target = line+1;
   1876       while (line[i] != ']' && line[i])
   1877         i++;
   1878       if (!line[i])
   1879         die(E_BADLINE, "Malformed address prefix\n");
   1880       msg = line + i + 1;
   1881       if (msg[0] == ' ')
   1882         msg++;
   1883       if (i)
   1884         line[i] = 0;
   1885       debug("[thread_in] calling with line target msg %p %p %p\n", line, target, msg);
   1886       strncpy(args.last_chans_out, target, MAX_CHANS_LEN-1);
   1887       cmd_msg(line, target, msg);
   1888     } else {
   1889       /* No target specified, we attempt the default */
   1890       debug("[thread_in] calling with line target msg %p %p %p\n", line, NULL, line);
   1891       cmd_msg(line, NULL, line);
   1892     }
   1893 
   1894     // the popper will free the line
   1895     line = NULL;
   1896   }
   1897   // sentinel
   1898   debug("[thread_in] pushed sentinel\n");
   1899   push_fifo_set(&fifos, NULL, "", NULL);
   1900   free(line); // which wasn't used
   1901   return 0;
   1902 }
   1903 
   1904 
   1905 // Start the IRC thread and monitor stdin
   1906 int start (int max_wait)
   1907 {
   1908   irc_session_t * s;
   1909   pthread_t tid_irc;
   1910   int i;
   1911   int waiting = 0;
   1912   int cont = 1;
   1913   action *results;
   1914   int n_results;
   1915   int rsl, error;
   1916 
   1917   s = do_connect();
   1918 
   1919   debug("Connection request successful!\n");
   1920 
   1921   debug("Starting threads...\n");
   1922   if (pthread_create (&tid_irc, 0, irc_thread, (void*) s) != 0)
   1923     die(E_THREAD, "Could not create thread: %s\n", strerror(errno));
   1924   debug("Threads started!\n");
   1925 
   1926   if (args.show_inferred && args.default_destination == DEFAULT_LAST_OUT)
   1927     fprintf(stderr, "[%s] ", args.last_chans_out);
   1928 
   1929   //gettimeofday(&tp1, NULL);
   1930 
   1931   // TODO do things nicely with a cond raised by a connection error, connection
   1932   // success, or timeout
   1933   waiting = 0;
   1934   usleep(200000);
   1935   while (1) {
   1936     int val;
   1937     pthread_mutex_lock(&args.mutex);
   1938     val = args.ready;
   1939     pthread_mutex_unlock(&args.mutex);
   1940     if (val)
   1941       break;
   1942     debug("Waiting for the connection to be ready...\n"); 
   1943     debug("waiting (%d)\n", waiting);
   1944     sleep(1);
   1945     waiting++;
   1946     if (waiting >= max_wait) {
   1947       int new_max_wait = max_wait * args.retry_factor;
   1948       info("Connection timed out after %d seconds, retrying for %d seconds...\n",
   1949           max_wait, new_max_wait);
   1950       irc_disconnect(s);
   1951       args.ready = 0;
   1952       sleep(1);
   1953       return start(new_max_wait);
   1954     }
   1955   }
   1956 
   1957   if (args.ready > 0) {
   1958 
   1959     debug("[main] Starting loop\n");
   1960 
   1961     irc_ctx_t * ctx = (irc_ctx_t *) irc_get_ctx (s);
   1962     for (i = 0; i < (args.join ? fifos.n : ctx->n_channels); i++) {
   1963       char *chan = args.join ? fifos.chans[i] : ctx->channels[i];
   1964       if (chan[0] != '#')
   1965         continue; // not a regular channel
   1966       debug("[reboot] rejoining %s\n",chan);
   1967       int pw_pos = get_password(chan);
   1968       irc_cmd_join (s, chan, chan + pw_pos + 1);
   1969       rsl = irc_cmd_join(s, chan, pw_pos >= 0 ? chan + pw_pos + 1 : NULL);
   1970       revert_password(chan, pw_pos);
   1971     }
   1972     
   1973     while (cont) {
   1974       n_results = pop_fifo_set(&fifos, &results);
   1975       debug("[main] I popped %d results\n", n_results);
   1976       for (i = 0; i < n_results; i++) {
   1977         if (!results[i].line) {
   1978           debug("[main] Sentinel seen, we will exit\n");
   1979           cont = 0; // sentinel
   1980         } else {
   1981           debug("[main] manage line %s %s, pointer %p\n",
   1982              results[i].destination, results[i].line, results[i].full_line );
   1983           rsl = do_cmd_msg(s, results[i].destination, results[i].line);
   1984           if (rsl > 0) {
   1985             error = irc_errno(s);
   1986             info("(%d) %s when interpreting: %s",
   1987                 error, irc_strerror(error), results[i].line);
   1988           }
   1989           if (rsl < 0) {
   1990             // successful exit
   1991             cont = 0;
   1992           }
   1993         }
   1994       }
   1995       // now, we free stuff
   1996       for (i = 0; i < n_results; i++)
   1997         if (results[i].full_line)
   1998           free(results[i].full_line);
   1999       free(results);
   2000       debug("[main] I managed my lines\n");
   2001       int val;
   2002       pthread_mutex_lock(&args.mutex);
   2003       val = args.ready;
   2004       pthread_mutex_unlock(&args.mutex);
   2005       if (cont && val <= 0) {
   2006         info("Connection lost, reconnecting...\n");
   2007         irc_disconnect(s);
   2008         sleep(1);
   2009         return start(max_wait);
   2010       }
   2011 
   2012       if (args.show_inferred && args.default_destination == DEFAULT_LAST_OUT)
   2013         fprintf(stderr, "[%s] ", args.last_chans_out);
   2014 
   2015       if (cont)
   2016         usleep(args.interval);
   2017 
   2018       debug("[main] endloop\n");
   2019     }
   2020     debug("[main] exiting\n");
   2021 
   2022     usleep(args.interval_after);
   2023   }
   2024 
   2025   irc_disconnect(s);
   2026 
   2027   free(args.server);
   2028   pthread_mutex_destroy(&args.mutex);
   2029 
   2030   return 0;
   2031 }
   2032 
   2033 void ssl_rename() {
   2034   args.server = malloc((strlen(args.raw_server)+2) * sizeof(char*));
   2035   assert(args.server);
   2036   if (args.ssl) {
   2037     // this is libircclient's convention, I don't really like it
   2038     args.server[0] = '#';
   2039     args.server[1] = 0;
   2040   } else {
   2041     args.server[0] = 0;
   2042   }
   2043   strcat(args.server, args.raw_server);
   2044 }
   2045 
   2046 
   2047 int main (int argc, char **argv)
   2048 {
   2049   // check libircclient version
   2050   unsigned int high, low;
   2051   irc_get_version(&high, &low);
   2052   if (high < 1 || (high == 1 && low < 8)) {
   2053     die(E_LIBIRCCLIENT_VERSION,
   2054       "Need libircclient >= 1.8 but library version is %d.%d\n",
   2055       high, low);
   2056   }
   2057 
   2058   // initialize the default option values
   2059   initialize_args();
   2060 
   2061   // parse command line arguments to set option values
   2062   argp_parse (&argp, argc, argv, 0, 0, &args);
   2063 
   2064   // setup SSL
   2065   ssl_rename();
   2066 
   2067   // initialize the fifo
   2068   init_fifo_set(&fifos);
   2069   saw_user(args.nick);
   2070 
   2071   // start stdin thread
   2072   pthread_t tid_in ;
   2073   if (pthread_create (&tid_in, 0, fifo_in_thread, (void*) NULL) != 0)
   2074     die(E_THREAD, "Could not create thread: %s\n", strerror(errno));
   2075   
   2076 
   2077   
   2078   //return test_fifo_set();
   2079 
   2080   // start trying to connect with the initial retry interval
   2081   return start(args.retry_after);
   2082 }
   2083