changes to !uset autoinvite behavior, introduction of allchanmsg_func hooks
[srvx.git] / src / proto-common.c
1 /* proto-common.c - common IRC protocol parsing/sending support
2  * Copyright 2000-2004 srvx Development Team
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with srvx; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
19  */
20
21 #include "conf.h"
22 #include "gline.h"
23 #include "ioset.h"
24 #include "log.h"
25 #include "nickserv.h"
26 #include "timeq.h"
27 #ifdef HAVE_SYS_SOCKET_H
28 #include <sys/socket.h>
29 #endif
30 #ifdef HAVE_NETINET_IN_H
31 #include <netinet/in.h>
32 #endif
33 #ifdef HAVE_ARPA_INET_H
34 #include <arpa/inet.h>
35 #endif
36
37 unsigned int lines_processed;
38 FILE *replay_file;
39 struct io_fd *socket_io_fd;
40 int force_n2k;
41 const char *hidden_host_suffix;
42
43 static char replay_line[MAXLEN+80];
44 static int ping_freq;
45 static int ping_timeout;
46 static int replay_connected;
47 static unsigned int nicklen = NICKLEN; /* how long do we think servers allow nicks to be? */
48 static struct userList dead_users;
49
50 extern struct cManagerNode cManager;
51 extern unsigned long burst_length;
52 extern struct cManagerNode cManager;
53 extern struct policer_params *oper_policer_params, *luser_policer_params;
54 extern server_link_func_t *slf_list;
55 extern unsigned int slf_size, slf_used;
56 extern new_user_func_t *nuf_list;
57 extern unsigned int nuf_size, nuf_used;
58 extern del_user_func_t *duf_list;
59 extern unsigned int duf_size, duf_used;
60 extern time_t boot_time;
61
62 void received_ping(void);
63
64 static int replay_read(void);
65 static dict_t irc_func_dict;
66
67 typedef void (*foreach_chanfunc) (struct chanNode *chan, void *data);
68 typedef void (*foreach_nonchan) (char *name, void *data);
69 typedef void (*foreach_userfunc) (struct userNode *user, void *data);
70 typedef void (*foreach_nonuser) (char *name, void *data);
71 static void parse_foreach(char *target_list, foreach_chanfunc cf, foreach_nonchan nc, foreach_userfunc uf, foreach_nonuser nu, void *data);
72
73 static void
74 uplink_readable(struct io_fd *fd) {
75     static char buffer[MAXLEN];
76     char *eol;
77     int pos;
78
79     pos = ioset_line_read(fd, buffer, sizeof(buffer));
80     if (pos <= 0) {
81         close_socket();
82         return;
83     }
84     if ((eol = strpbrk(buffer, "\r\n")))
85         *eol = 0;
86     log_replay(MAIN_LOG, false, buffer);
87     if (cManager.uplink->state != DISCONNECTED)
88         parse_line(buffer, 0);
89     lines_processed++;
90 }
91
92 void
93 socket_destroyed(struct io_fd *fd)
94 {
95     if (fd && fd->eof)
96         log_module(MAIN_LOG, LOG_ERROR, "Connection to server lost.");
97     socket_io_fd = NULL;
98     cManager.uplink->state = DISCONNECTED;
99     if (self->uplink)
100         DelServer(self->uplink, 0, NULL);
101 }
102
103 void replay_event_loop(void)
104 {
105     while (!quit_services) {
106         if (!replay_connected) {
107             /* this time fudging is to get some of the logging right */
108             self->link = self->boot = now;
109             cManager.uplink->state = AUTHENTICATING;
110             irc_introduce(cManager.uplink->password);
111             replay_connected = 1;
112         } else if (!replay_read()) {
113             log_module(MAIN_LOG, LOG_ERROR, "Connection to server lost.");
114             close_socket();
115         }
116         timeq_run();
117     }
118 }
119
120 int
121 create_socket_client(struct uplinkNode *target)
122 {
123     int port = target->port;
124     const char *addr = target->host;
125
126     if (replay_file)
127         return feof(replay_file) ? 0 : 1;
128
129     if (socket_io_fd) {
130         /* Leave the existing socket open, say we failed. */
131         log_module(MAIN_LOG, LOG_ERROR, "Refusing to create second connection to %s:%d.", addr, port);
132         return 0;
133     }
134
135     log_module(MAIN_LOG, LOG_INFO, "Connecting to %s:%i...", addr, port);
136
137     socket_io_fd = ioset_connect((struct sockaddr*)cManager.uplink->bind_addr, sizeof(struct sockaddr), addr, port, 1, 0, NULL);
138     if (!socket_io_fd) {
139         log_module(MAIN_LOG, LOG_ERROR, "Connection to uplink failed: %s (%d)", strerror(errno), errno);
140         target->state = DISCONNECTED;
141         target->tries++;
142         return 0;
143     }
144     socket_io_fd->readable_cb = uplink_readable;
145     socket_io_fd->destroy_cb = socket_destroyed;
146     socket_io_fd->line_reads = 1;
147     socket_io_fd->wants_reads = 1;
148     log_module(MAIN_LOG, LOG_INFO, "Connection to server established.");
149     cManager.uplink = target;
150     target->state = AUTHENTICATING;
151     target->tries = 0;
152     return 1;
153 }
154
155 void
156 replay_read_line(void)
157 {
158     struct tm timestamp;
159     time_t new_time;
160
161     if (replay_line[0]) return;
162   read_line:
163     if (!fgets(replay_line, sizeof(replay_line), replay_file)) {
164         if (feof(replay_file)) {
165             quit_services = 1;
166             memset(replay_line, 0, sizeof(replay_line));
167             return;
168         }
169     }
170     if ((replay_line[0] != '[')
171         || (replay_line[3] != ':')
172         || (replay_line[6] != ':')
173         || (replay_line[9] != ' ')
174         || (replay_line[12] != '/')
175         || (replay_line[15] != '/')
176         || (replay_line[20] != ']')
177         || (replay_line[21] != ' ')) {
178         log_module(MAIN_LOG, LOG_ERROR, "Unrecognized timestamp in replay file: %s", replay_line);
179         goto read_line;
180     }
181     timestamp.tm_hour = strtoul(replay_line+1, NULL, 10);
182     timestamp.tm_min = strtoul(replay_line+4, NULL, 10);
183     timestamp.tm_sec = strtoul(replay_line+7, NULL, 10);
184     timestamp.tm_mon = strtoul(replay_line+10, NULL, 10) - 1;
185     timestamp.tm_mday = strtoul(replay_line+13, NULL, 10);
186     timestamp.tm_year = strtoul(replay_line+16, NULL, 10) - 1900;
187     timestamp.tm_isdst = 0;
188     new_time = mktime(&timestamp);
189     if (new_time == -1) {
190         log_module(MAIN_LOG, LOG_ERROR, "Unable to parse time struct tm_sec=%d tm_min=%d tm_hour=%d tm_mday=%d tm_mon=%d tm_year=%d", timestamp.tm_sec, timestamp.tm_min, timestamp.tm_hour, timestamp.tm_mday, timestamp.tm_mon, timestamp.tm_year);
191     } else {
192         now = new_time;
193     }
194
195     if (strncmp(replay_line+22, "(info) ", 7))
196         goto read_line;
197     return;
198 }
199
200 static int
201 replay_read(void)
202 {
203     size_t len;
204     char read_line[MAXLEN];
205     while (1) {
206         replay_read_line();
207         /* if it's a sent line, break out to handle it */
208         if (!strncmp(replay_line+29, "   ", 3))
209             break;
210         if (!strncmp(replay_line+29, "W: ", 3)) {
211             log_module(MAIN_LOG, LOG_ERROR, "Expected response from services: %s", replay_line+32);
212             replay_line[0] = 0;
213         } else {
214             return 0;
215         }
216     }
217     log_replay(MAIN_LOG, false, replay_line+32);
218     safestrncpy(read_line, replay_line+32, sizeof(read_line));
219     len = strlen(read_line);
220     if (read_line[len-1] == '\n')
221         read_line[--len] = 0;
222     replay_line[0] = 0;
223     parse_line(read_line, 0);
224     lines_processed++;
225     return 1;
226 }
227
228 static void
229 replay_write(char *text)
230 {
231     replay_read_line();
232     if (strncmp(replay_line+29, "W: ", 3)) {
233         log_module(MAIN_LOG, LOG_ERROR, "Unexpected output during replay: %s", text);
234         return;
235     } else {
236         if (strcmp(replay_line+32, text)) {
237             log_module(MAIN_LOG, LOG_ERROR, "Incorrect output during replay:\nReceived: %sExpected: %s", text, replay_line+32);
238         } else {
239             log_replay(MAIN_LOG, true, text);
240         }
241         replay_line[0] = 0;
242     }
243 }
244
245 void putsock(const char *text, ...) PRINTF_LIKE(1, 2);
246
247 void
248 putsock(const char *text, ...)
249 {
250     va_list arg_list;
251     char buffer[MAXLEN];
252     int pos;
253
254     if (!cManager.uplink || cManager.uplink->state == DISCONNECTED) return;
255     buffer[0] = '\0';
256     va_start(arg_list, text);
257     pos = vsnprintf(buffer, MAXLEN - 2, text, arg_list);
258     va_end(arg_list);
259     if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
260     buffer[pos] = 0;
261     if (!replay_file) {
262         log_replay(MAIN_LOG, true, buffer);
263         buffer[pos++] = '\n';
264         buffer[pos] = 0;
265         ioset_write(socket_io_fd, buffer, pos);
266     } else {
267         replay_write(buffer);
268     }
269 }
270
271 void
272 close_socket(void)
273 {
274     if (replay_file) {
275         replay_connected = 0;
276         socket_destroyed(socket_io_fd);
277     } else {
278         ioset_close(socket_io_fd->fd, 1);
279     }
280 }
281
282 #define CMD_FUNC(NAME) int NAME(UNUSED_ARG(const char *origin), UNUSED_ARG(unsigned int argc), UNUSED_ARG(char **argv))
283 typedef CMD_FUNC(cmd_func_t);
284
285 static void timed_ping_timeout(void *data);
286
287 /* Ping state is kept in the timeq (only one of these two can be in
288  * the queue at any given time). */
289 void
290 timed_send_ping(UNUSED_ARG(void *data))
291 {
292     irc_ping(self->name);
293     timeq_add(now + ping_timeout, timed_ping_timeout, 0);
294 }
295
296 static void
297 timed_ping_timeout(UNUSED_ARG(void *data))
298 {
299     /* Uplink "health" tracking could be accomplished by counting the
300        number of ping timeouts that happen for each uplink. After the
301        timeouts per time period exceeds some amount, the uplink could
302        be marked as unavalable.*/
303     irc_squit(self, "Ping timeout.", NULL);
304 }
305
306 static CMD_FUNC(cmd_pass)
307 {
308     const char *true_pass;
309
310     if (argc < 2)
311         return 0;
312     true_pass = cManager.uplink->their_password;
313     if (true_pass && strcmp(true_pass, argv[1])) {
314         /* It might be good to mark the uplink as unavailable when
315            this happens, though there should be a way of resetting
316            the flag. */
317         irc_squit(self, "Incorrect password received.", NULL);
318         return 1;
319     }
320
321     cManager.uplink->state = BURSTING;
322     return 1;
323 }
324
325 static CMD_FUNC(cmd_dummy)
326 {
327     /* we don't care about these messages */
328     return 1;
329 }
330
331 static CMD_FUNC(cmd_error)
332 {
333     if (argv[1]) log_module(MAIN_LOG, LOG_ERROR, "Error: %s", argv[1]);
334     log_module(MAIN_LOG, LOG_ERROR, "Error received from uplink, squitting.");
335
336     if (cManager.uplink->state != CONNECTED) {
337         /* Error messages while connected should be fine. */
338         cManager.uplink->flags |= UPLINK_UNAVAILABLE;
339         log_module(MAIN_LOG, LOG_ERROR, "Disabling uplink.");
340     }
341
342     close_socket();
343     return 1;
344 }
345
346 static CMD_FUNC(cmd_stats)
347 {
348     struct userNode *un;
349
350     if (argc < 2)
351         return 0;
352     if (!(un = GetUserH(origin)))
353         return 0;
354     switch (argv[1][0]) {
355     case 'u': {
356         unsigned int uptime = now - boot_time;
357         irc_numeric(un, RPL_STATSUPTIME, ":Server Up %d days %d:%02d:%02d",
358                     uptime/(24*60*60), (uptime/(60*60))%24, (uptime/60)%60, uptime%60);
359         irc_numeric(un, RPL_MAXCONNECTIONS, ":Highest connection count: %d (%d clients)",
360                     self->max_clients+1, self->max_clients);
361         break;
362     }
363     default: /* unrecognized/unhandled stats output */ break;
364     }
365     irc_numeric(un, 219, "%s :End of /STATS report", argv[1]);
366     return 1;
367 }
368
369 static CMD_FUNC(cmd_whois)
370 {
371     struct userNode *from;
372     struct userNode *who;
373
374     if (argc < 3)
375         return 0;
376     if (!(from = GetUserH(origin))) {
377         log_module(MAIN_LOG, LOG_ERROR, "Could not find WHOIS origin user %s", origin);
378         return 0;
379     }
380     if(!(who = GetUserH(argv[2]))) {
381         irc_numeric(from, ERR_NOSUCHNICK, "%s@%s :No such nick", argv[2], self->name);
382         return 1;
383     }
384     if (IsHiddenHost(who) && !IsOper(from)) {
385         /* Just stay quiet. */
386         return 1;
387     }
388     irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->ident, who->hostname, who->info);
389     irc_numeric(from, RPL_WHOISSERVER, "%s %s :%s", who->nick, who->uplink->name, who->uplink->description);
390     if (IsOper(who)) {
391         irc_numeric(from, RPL_WHOISOPERATOR, "%s :is a megalomaniacal power hungry tyrant", who->nick);
392     }
393     irc_numeric(from, RPL_ENDOFWHOIS, "%s :End of /WHOIS list", who->nick);
394     return 1;
395 }
396
397 static CMD_FUNC(cmd_version)
398 {
399     struct userNode *user;
400     if (!(user = GetUserH(origin))) {
401         log_module(MAIN_LOG, LOG_ERROR, "Could not find VERSION origin user %s", origin);
402         return 0;
403     }
404     irc_numeric(user, 351, "%s.%s %s :%s", PACKAGE_TARNAME, PACKAGE_VERSION, self->name, CODENAME);
405     return 1;
406 }
407
408 static CMD_FUNC(cmd_admin)
409 {
410     struct userNode *user;
411     struct string_list *slist;
412
413     if (!(user = GetUserH(origin))) {
414         log_module(MAIN_LOG, LOG_ERROR, "Could not find ADMIN origin user %s", origin);
415         return 0;
416     }
417     if ((slist = conf_get_data("server/admin", RECDB_STRING_LIST)) && slist->used) {
418         unsigned int i;
419
420         irc_numeric(user, 256, ":Administrative info about %s", self->name);
421         for (i = 0; i < slist->used && i < 3; i++)
422             irc_numeric(user, 257 + i, ":%s", slist->list[i]);
423     } else {
424         irc_numeric(user, 423, ":No administrative info available");
425     }
426     return 1;
427 }
428
429 static void
430 recalc_bursts(struct server *eob_server)
431 {
432     unsigned int nn;
433     eob_server->burst = eob_server->self_burst;
434     if (eob_server->uplink != self)
435         eob_server->burst = eob_server->burst || eob_server->uplink->burst;
436     for (nn=0; nn < eob_server->children.used; nn++)
437         recalc_bursts(eob_server->children.list[nn]);
438 }
439
440 static struct chanmsg_func {
441     chanmsg_func_t func;
442     struct userNode *service;
443 } chanmsg_funcs[256]; /* indexed by trigger character */
444
445 static struct allchanmsg_func {
446     chanmsg_func_t func;
447     struct userNode *service;
448 } allchanmsg_funcs[ALLCHANMSG_FUNCS_MAX];
449
450 struct privmsg_desc {
451     struct userNode *user;
452     char *text;
453     unsigned int is_notice : 1;
454     unsigned int is_qualified : 1;
455 };
456
457 static void
458 privmsg_chan_helper(struct chanNode *cn, void *data)
459 {
460     struct privmsg_desc *pd = data;
461     struct modeNode *mn;
462     struct chanmsg_func *cf = &chanmsg_funcs[(unsigned char)pd->text[0]];
463     int x;
464
465     /* Don't complain if it can't find the modeNode because the channel might
466      * be -n */
467     if ((mn = GetUserMode(cn, pd->user)))
468         mn->idle_since = now;
469
470     /* Never send a NOTICE to a channel to one of the services */
471     if (!pd->is_notice && cf->func && GetUserMode(cn, cf->service))
472         cf->func(pd->user, cn, pd->text+1, cf->service);
473
474     /* This catches *all* text sent to the channel that the services server sees */
475     for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
476        cf = (struct chanmsg_func *)&allchanmsg_funcs[x];
477        if (!cf->func)
478          break; /* end of list */
479        else
480        cf->func(pd->user, cn, pd->text, cf->service);
481     }
482 }
483
484 static void
485 privmsg_invalid(char *name, void *data)
486 {
487     struct privmsg_desc *pd = data;
488
489     if (*name == '$')
490         return;
491     irc_numeric(pd->user, ERR_NOSUCHNICK, "%s@%s :No such nick", name, self->name);
492 }
493
494 static void
495 part_helper(struct chanNode *cn, void *data)
496 {
497     DelChannelUser(data, cn, false, 0);
498 }
499
500 void
501 reg_chanmsg_func(unsigned char prefix, struct userNode *service, chanmsg_func_t handler)
502 {
503     if (chanmsg_funcs[prefix].func)
504         log_module(MAIN_LOG, LOG_WARNING, "Re-registering new chanmsg handler for character `%c'.", prefix);
505     chanmsg_funcs[prefix].func = handler;
506     chanmsg_funcs[prefix].service = service;
507 }
508
509 void
510 reg_allchanmsg_func(struct userNode *service, chanmsg_func_t handler)
511 {
512     int x;
513     for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
514        if (allchanmsg_funcs[x].func)
515          continue;
516        allchanmsg_funcs[x].func = handler;
517        allchanmsg_funcs[x].service = service;
518        break;
519     }
520 }
521
522 struct userNode *
523 get_chanmsg_bot(unsigned char prefix)
524 {
525     return chanmsg_funcs[prefix].service;
526 }
527
528 static mode_change_func_t *mcf_list;
529 static unsigned int mcf_size = 0, mcf_used = 0;
530
531 void
532 reg_mode_change_func(mode_change_func_t handler)
533 {
534     if (mcf_used == mcf_size) {
535         if (mcf_size) {
536             mcf_size <<= 1;
537             mcf_list = realloc(mcf_list, mcf_size*sizeof(mode_change_func_t));
538         } else {
539             mcf_size = 8;
540             mcf_list = malloc(mcf_size*sizeof(mode_change_func_t));
541         }
542     }
543     mcf_list[mcf_used++] = handler;
544 }
545
546 struct mod_chanmode *
547 mod_chanmode_alloc(unsigned int argc)
548 {
549     struct mod_chanmode *res;
550     if (argc > 1)
551         res = calloc(1, sizeof(*res) + (argc-1)*sizeof(res->args[0]));
552     else
553         res = calloc(1, sizeof(*res));
554     if (res) {
555 #if !defined(NDEBUG)
556         res->alloc_argc = argc;
557 #endif
558         res->argc = argc;
559     }
560     return res;
561 }
562
563 struct mod_chanmode *
564 mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra)
565 {
566     struct mod_chanmode *res;
567     res = mod_chanmode_alloc(orig->argc + extra);
568     if (res) {
569         res->modes_set = orig->modes_set;
570         res->modes_clear = orig->modes_clear;
571         res->new_limit = orig->new_limit;
572         memcpy(res->new_key, orig->new_key, sizeof(res->new_key));
573         res->argc = orig->argc;
574         memcpy(res->args, orig->args, orig->argc*sizeof(orig->args[0]));
575     }
576     return res;
577 }
578
579 void
580 mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change)
581 {
582     struct banNode *bn;
583     unsigned int ii, jj;
584
585     assert(change->argc <= change->alloc_argc);
586     channel->modes = (channel->modes & ~change->modes_clear) | change->modes_set;
587     if (change->modes_set & MODE_LIMIT)
588         channel->limit = change->new_limit;
589     if (change->modes_set & MODE_KEY)
590         strcpy(channel->key, change->new_key);
591     for (ii = 0; ii < change->argc; ++ii) {
592         switch (change->args[ii].mode) {
593         case MODE_BAN:
594             /* If any existing ban is a subset of the new ban,
595              * silently remove it.  The new ban is not allowed
596              * to be more specific than an existing ban.
597              */
598             for (jj=0; jj<channel->banlist.used; ++jj) {
599                 if (match_ircglobs(change->args[ii].hostmask, channel->banlist.list[jj]->ban)) {
600                     banList_remove(&channel->banlist, channel->banlist.list[jj]);
601                     free(channel->banlist.list[jj]);
602                     jj--;
603                 }
604             }
605             bn = calloc(1, sizeof(*bn));
606             safestrncpy(bn->ban, change->args[ii].hostmask, sizeof(bn->ban));
607             safestrncpy(bn->who, who->nick, sizeof(bn->who));
608             bn->set = now;
609             banList_append(&channel->banlist, bn);
610             break;
611         case MODE_REMOVE|MODE_BAN:
612             for (jj=0; jj<channel->banlist.used; ++jj) {
613                 if (strcmp(channel->banlist.list[jj]->ban, change->args[ii].hostmask))
614                     continue;
615                 free(channel->banlist.list[jj]);
616                 banList_remove(&channel->banlist, channel->banlist.list[jj]);
617                 break;
618             }
619             break;
620         case MODE_CHANOP:
621         case MODE_VOICE:
622         case MODE_VOICE|MODE_CHANOP:
623         case MODE_REMOVE|MODE_CHANOP:
624         case MODE_REMOVE|MODE_VOICE:
625         case MODE_REMOVE|MODE_VOICE|MODE_CHANOP:
626             if (change->args[ii].mode & MODE_REMOVE)
627                 change->args[ii].member->modes &= ~change->args[ii].mode;
628             else
629                 change->args[ii].member->modes |= change->args[ii].mode;
630             break;
631         default:
632             assert(0 && "Invalid mode argument");
633             continue;
634         }
635     }
636 }
637
638 void
639 mod_chanmode_free(struct mod_chanmode *change)
640 {
641     free(change);
642 }
643
644 int
645 mod_chanmode(struct userNode *who, struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags)
646 {
647     struct mod_chanmode *change;
648     unsigned int ii;
649
650     if (!modes || !modes[0])
651         return 0;
652     if (!(change = mod_chanmode_parse(channel, modes, argc, flags)))
653         return 0;
654     if (flags & MC_ANNOUNCE)
655         mod_chanmode_announce(who, channel, change);
656     else
657         mod_chanmode_apply(who, channel, change);
658     if (flags & MC_NOTIFY)
659         for (ii = 0; ii < mcf_used; ++ii)
660             mcf_list[ii](channel, who, change);
661     mod_chanmode_free(change);
662     return 1;
663 }
664
665 int
666 irc_make_chanmode(struct chanNode *chan, char *out) {
667     struct mod_chanmode change;
668     mod_chanmode_init(&change);
669     change.modes_set = chan->modes;
670     change.new_limit = chan->limit;
671     safestrncpy(change.new_key, chan->key, sizeof(change.new_key));
672     return strlen(mod_chanmode_format(&change, out));
673 }
674
675 char *
676 generate_hostmask(struct userNode *user, int options)
677 {
678     char *nickname, *ident, *hostname;
679     char *mask;
680     int len, ii;
681
682     /* figure out string parts */
683     if (options & GENMASK_OMITNICK)
684         nickname = NULL;
685     else if (options & GENMASK_USENICK)
686         nickname = user->nick;
687     else
688         nickname = "*";
689     if (options & GENMASK_STRICT_IDENT)
690         ident = user->ident;
691     else if (options & GENMASK_ANY_IDENT)
692         ident = "*";
693     else {
694         ident = alloca(strlen(user->ident)+2);
695         ident[0] = '*';
696         strcpy(ident+1, user->ident + ((*user->ident == '~')?1:0));
697     }
698     hostname = user->hostname;
699     if (IsHiddenHost(user) && user->handle_info && hidden_host_suffix && !(options & GENMASK_NO_HIDING)) {
700         hostname = alloca(strlen(user->handle_info->handle) + strlen(hidden_host_suffix) + 2);
701         sprintf(hostname, "%s.%s", user->handle_info->handle, hidden_host_suffix);
702     } else if (options & GENMASK_STRICT_HOST) {
703         if (options & GENMASK_BYIP)
704             hostname = inet_ntoa(user->ip);
705     } else if ((options & GENMASK_BYIP) || !hostname[strspn(hostname, "0123456789.")]) {
706         /* Should generate an IP-based hostmask.  By popular acclaim, a /16
707          * hostmask is used by default. */
708         unsigned masked_ip, mask, masklen;
709         masklen = 16;
710         mask = ~0 << masklen;
711         masked_ip = ntohl(user->ip.s_addr) & mask;
712         hostname = alloca(32);
713         if (options & GENMASK_SRVXMASK) {
714             sprintf(hostname, "%d.%d.%d.%d/%d", (masked_ip>>24)&0xFF, (masked_ip>>16)&0xFF, (masked_ip>>8)&0xFF, masked_ip&0xFF, masklen);
715         } else {
716             int ofs = 0;
717             for (ii=0; ii<4; ii++) {
718                 if (masklen) {
719                     ofs += sprintf(hostname+ofs, "%d.", (masked_ip>>24)&0xFF);
720                     masklen -= 8;
721                     masked_ip <<= 8;
722                 } else {
723                     ofs += sprintf(hostname+ofs, "*.");
724                 }
725             }
726             /* Truncate the last . */
727             hostname[ofs-1] = 0;
728         }
729     } else {
730         int cnt;
731         /* This heuristic could be made smarter.  Is it worth the effort? */
732         for (ii=cnt=0; hostname[ii]; ii++)
733             if (hostname[ii] == '.')
734                 cnt++;
735         if (cnt == 1) {
736             /* only a two-level domain name; leave hostname */
737         } else if (cnt == 2) {
738             for (ii=0; user->hostname[ii] != '.'; ii++) ;
739             /* Add 3 to account for the *. and \0. */
740             hostname = alloca(strlen(user->hostname+ii)+3);
741             sprintf(hostname, "*.%s", user->hostname+ii+1);
742         } else {
743             for (cnt=3, ii--; cnt; ii--)
744                 if (user->hostname[ii] == '.')
745                     cnt--;
746             /* The loop above will overshoot the dot one character;
747                we skip forward two (the one character and the dot)
748                when printing, so we only add one for the \0. */
749             hostname = alloca(strlen(user->hostname+ii)+1);
750             sprintf(hostname, "*.%s", user->hostname+ii+2);
751         }
752     }
753     /* Emit hostmask */
754     len = strlen(ident) + strlen(hostname) + 2;
755     if (nickname) {
756         len += strlen(nickname) + 1;
757         mask = malloc(len);
758         sprintf(mask, "%s!%s@%s", nickname, ident, hostname);
759     } else {
760         mask = malloc(len);
761         sprintf(mask, "%s@%s", ident, hostname);
762     }
763     return mask;
764 }
765
766 int
767 IsChannelName(const char *name) {
768     unsigned int ii;
769
770     if (*name !='#')
771         return 0;
772     for (ii=1; name[ii]; ++ii) {
773         if ((name[ii] > 0) && (name[ii] <= 32))
774             return 0;
775         if (name[ii] == ',')
776             return 0;
777         if (name[ii] == '\xa0')
778             return 0;
779     }
780     return 1;
781 }