introduce unreg_privmsg_func, fix minor autoinvite issue, alter account-finding on...
[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 #ifdef WITH_PROTOCOL_P10
374     extern char *his_servername;
375     extern char *his_servercomment;
376 #endif
377
378     if (argc < 3)
379         return 0;
380     if (!(from = GetUserH(origin))) {
381         log_module(MAIN_LOG, LOG_ERROR, "Could not find WHOIS origin user %s", origin);
382         return 0;
383     }
384     if(!(who = GetUserH(argv[2]))) {
385         irc_numeric(from, ERR_NOSUCHNICK, "%s@%s :No such nick", argv[2], self->name);
386         return 1;
387     }
388     if (IsHiddenHost(who) && !IsOper(from)) {
389         /* Just stay quiet. */
390         return 1;
391     }
392     irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->ident, who->hostname, who->info);
393 #ifdef WITH_PROTOCOL_P10
394     if (his_servername && his_servercomment)
395       irc_numeric(from, RPL_WHOISSERVER, "%s %s :%s", who->nick, his_servername, his_servercomment);
396     else
397 #endif
398     irc_numeric(from, RPL_WHOISSERVER, "%s %s :%s", who->nick, who->uplink->name, who->uplink->description);
399
400     if (IsOper(who)) {
401         irc_numeric(from, RPL_WHOISOPERATOR, "%s :is a megalomaniacal power hungry tyrant", who->nick);
402     }
403     irc_numeric(from, RPL_ENDOFWHOIS, "%s :End of /WHOIS list", who->nick);
404     return 1;
405 }
406
407 static CMD_FUNC(cmd_version)
408 {
409     struct userNode *user;
410     if (!(user = GetUserH(origin))) {
411         log_module(MAIN_LOG, LOG_ERROR, "Could not find VERSION origin user %s", origin);
412         return 0;
413     }
414     irc_numeric(user, 351, "%s.%s %s :%s", PACKAGE_TARNAME, PACKAGE_VERSION, self->name, CODENAME);
415     return 1;
416 }
417
418 static CMD_FUNC(cmd_admin)
419 {
420     struct userNode *user;
421     struct string_list *slist;
422
423     if (!(user = GetUserH(origin))) {
424         log_module(MAIN_LOG, LOG_ERROR, "Could not find ADMIN origin user %s", origin);
425         return 0;
426     }
427     if ((slist = conf_get_data("server/admin", RECDB_STRING_LIST)) && slist->used) {
428         unsigned int i;
429
430         irc_numeric(user, 256, ":Administrative info about %s", self->name);
431         for (i = 0; i < slist->used && i < 3; i++)
432             irc_numeric(user, 257 + i, ":%s", slist->list[i]);
433     } else {
434         irc_numeric(user, 423, ":No administrative info available");
435     }
436     return 1;
437 }
438
439 static void
440 recalc_bursts(struct server *eob_server)
441 {
442     unsigned int nn;
443     eob_server->burst = eob_server->self_burst;
444     if (eob_server->uplink != self)
445         eob_server->burst = eob_server->burst || eob_server->uplink->burst;
446     for (nn=0; nn < eob_server->children.used; nn++)
447         recalc_bursts(eob_server->children.list[nn]);
448 }
449
450 static struct chanmsg_func {
451     chanmsg_func_t func;
452     struct userNode *service;
453 } chanmsg_funcs[256]; /* indexed by trigger character */
454
455 static struct allchanmsg_func {
456     chanmsg_func_t func;
457     struct userNode *service;
458 } allchanmsg_funcs[ALLCHANMSG_FUNCS_MAX];
459
460 struct privmsg_desc {
461     struct userNode *user;
462     char *text;
463     unsigned int is_notice : 1;
464     unsigned int is_qualified : 1;
465 };
466
467 static void
468 privmsg_chan_helper(struct chanNode *cn, void *data)
469 {
470     struct privmsg_desc *pd = data;
471     struct modeNode *mn;
472     struct chanmsg_func *cf = &chanmsg_funcs[(unsigned char)pd->text[0]];
473     int x;
474
475     /* Don't complain if it can't find the modeNode because the channel might
476      * be -n */
477     if ((mn = GetUserMode(cn, pd->user)))
478         mn->idle_since = now;
479
480     /* Never send a NOTICE to a channel to one of the services */
481     if (!pd->is_notice && cf->func && GetUserMode(cn, cf->service))
482         cf->func(pd->user, cn, pd->text+1, cf->service);
483
484     /* This catches *all* text sent to the channel that the services server sees */
485     for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
486        cf = (struct chanmsg_func *)&allchanmsg_funcs[x];
487        if (!cf->func)
488          break; /* end of list */
489        else
490        cf->func(pd->user, cn, pd->text, cf->service);
491     }
492 }
493
494 static void
495 privmsg_invalid(char *name, void *data)
496 {
497     struct privmsg_desc *pd = data;
498
499     if (*name == '$')
500         return;
501     irc_numeric(pd->user, ERR_NOSUCHNICK, "%s@%s :No such nick", name, self->name);
502 }
503
504 static void
505 part_helper(struct chanNode *cn, void *data)
506 {
507     DelChannelUser(data, cn, false, 0);
508 }
509
510 void
511 reg_chanmsg_func(unsigned char prefix, struct userNode *service, chanmsg_func_t handler)
512 {
513     if (chanmsg_funcs[prefix].func)
514         log_module(MAIN_LOG, LOG_WARNING, "Re-registering new chanmsg handler for character `%c'.", prefix);
515     chanmsg_funcs[prefix].func = handler;
516     chanmsg_funcs[prefix].service = service;
517 }
518
519 void
520 reg_allchanmsg_func(struct userNode *service, chanmsg_func_t handler)
521 {
522     int x;
523     for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
524        if (allchanmsg_funcs[x].func)
525          continue;
526        allchanmsg_funcs[x].func = handler;
527        allchanmsg_funcs[x].service = service;
528        break;
529     }
530 }
531
532 struct userNode *
533 get_chanmsg_bot(unsigned char prefix)
534 {
535     return chanmsg_funcs[prefix].service;
536 }
537
538 static mode_change_func_t *mcf_list;
539 static unsigned int mcf_size = 0, mcf_used = 0;
540
541 void
542 reg_mode_change_func(mode_change_func_t handler)
543 {
544     if (mcf_used == mcf_size) {
545         if (mcf_size) {
546             mcf_size <<= 1;
547             mcf_list = realloc(mcf_list, mcf_size*sizeof(mode_change_func_t));
548         } else {
549             mcf_size = 8;
550             mcf_list = malloc(mcf_size*sizeof(mode_change_func_t));
551         }
552     }
553     mcf_list[mcf_used++] = handler;
554 }
555
556 struct mod_chanmode *
557 mod_chanmode_alloc(unsigned int argc)
558 {
559     struct mod_chanmode *res;
560     if (argc > 1)
561         res = calloc(1, sizeof(*res) + (argc-1)*sizeof(res->args[0]));
562     else
563         res = calloc(1, sizeof(*res));
564     if (res) {
565 #if !defined(NDEBUG)
566         res->alloc_argc = argc;
567 #endif
568         res->argc = argc;
569     }
570     return res;
571 }
572
573 struct mod_chanmode *
574 mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra)
575 {
576     struct mod_chanmode *res;
577     res = mod_chanmode_alloc(orig->argc + extra);
578     if (res) {
579         res->modes_set = orig->modes_set;
580         res->modes_clear = orig->modes_clear;
581         res->new_limit = orig->new_limit;
582         memcpy(res->new_key, orig->new_key, sizeof(res->new_key));
583         res->argc = orig->argc;
584         memcpy(res->args, orig->args, orig->argc*sizeof(orig->args[0]));
585     }
586     return res;
587 }
588
589 void
590 mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change)
591 {
592     struct banNode *bn;
593     unsigned int ii, jj;
594
595     assert(change->argc <= change->alloc_argc);
596     channel->modes = (channel->modes & ~change->modes_clear) | change->modes_set;
597     if (change->modes_set & MODE_LIMIT)
598         channel->limit = change->new_limit;
599     if (change->modes_set & MODE_KEY)
600         strcpy(channel->key, change->new_key);
601     for (ii = 0; ii < change->argc; ++ii) {
602         switch (change->args[ii].mode) {
603         case MODE_BAN:
604             /* If any existing ban is a subset of the new ban,
605              * silently remove it.  The new ban is not allowed
606              * to be more specific than an existing ban.
607              */
608             for (jj=0; jj<channel->banlist.used; ++jj) {
609                 if (match_ircglobs(change->args[ii].hostmask, channel->banlist.list[jj]->ban)) {
610                     banList_remove(&channel->banlist, channel->banlist.list[jj]);
611                     free(channel->banlist.list[jj]);
612                     jj--;
613                 }
614             }
615             bn = calloc(1, sizeof(*bn));
616             safestrncpy(bn->ban, change->args[ii].hostmask, sizeof(bn->ban));
617             safestrncpy(bn->who, who->nick, sizeof(bn->who));
618             bn->set = now;
619             banList_append(&channel->banlist, bn);
620             break;
621         case MODE_REMOVE|MODE_BAN:
622             for (jj=0; jj<channel->banlist.used; ++jj) {
623                 if (strcmp(channel->banlist.list[jj]->ban, change->args[ii].hostmask))
624                     continue;
625                 free(channel->banlist.list[jj]);
626                 banList_remove(&channel->banlist, channel->banlist.list[jj]);
627                 break;
628             }
629             break;
630         case MODE_CHANOP:
631         case MODE_VOICE:
632         case MODE_VOICE|MODE_CHANOP:
633         case MODE_REMOVE|MODE_CHANOP:
634         case MODE_REMOVE|MODE_VOICE:
635         case MODE_REMOVE|MODE_VOICE|MODE_CHANOP:
636             if (change->args[ii].mode & MODE_REMOVE)
637                 change->args[ii].member->modes &= ~change->args[ii].mode;
638             else
639                 change->args[ii].member->modes |= change->args[ii].mode;
640             break;
641         default:
642             assert(0 && "Invalid mode argument");
643             continue;
644         }
645     }
646 }
647
648 void
649 mod_chanmode_free(struct mod_chanmode *change)
650 {
651     free(change);
652 }
653
654 int
655 mod_chanmode(struct userNode *who, struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags)
656 {
657     struct mod_chanmode *change;
658     unsigned int ii;
659
660     if (!modes || !modes[0])
661         return 0;
662     if (!(change = mod_chanmode_parse(channel, modes, argc, flags)))
663         return 0;
664     if (flags & MC_ANNOUNCE)
665         mod_chanmode_announce(who, channel, change);
666     else
667         mod_chanmode_apply(who, channel, change);
668     if (flags & MC_NOTIFY)
669         for (ii = 0; ii < mcf_used; ++ii)
670             mcf_list[ii](channel, who, change);
671     mod_chanmode_free(change);
672     return 1;
673 }
674
675 int
676 irc_make_chanmode(struct chanNode *chan, char *out) {
677     struct mod_chanmode change;
678     mod_chanmode_init(&change);
679     change.modes_set = chan->modes;
680     change.new_limit = chan->limit;
681     safestrncpy(change.new_key, chan->key, sizeof(change.new_key));
682     return strlen(mod_chanmode_format(&change, out));
683 }
684
685 char *
686 generate_hostmask(struct userNode *user, int options)
687 {
688     char *nickname, *ident, *hostname;
689     char *mask;
690     int len, ii;
691
692     /* figure out string parts */
693     if (options & GENMASK_OMITNICK)
694         nickname = NULL;
695     else if (options & GENMASK_USENICK)
696         nickname = user->nick;
697     else
698         nickname = "*";
699     if (options & GENMASK_STRICT_IDENT)
700         ident = user->ident;
701     else if (options & GENMASK_ANY_IDENT)
702         ident = "*";
703     else {
704         ident = alloca(strlen(user->ident)+2);
705         ident[0] = '*';
706         strcpy(ident+1, user->ident + ((*user->ident == '~')?1:0));
707     }
708     hostname = user->hostname;
709     if (IsHiddenHost(user) && user->handle_info && hidden_host_suffix && !(options & GENMASK_NO_HIDING)) {
710         hostname = alloca(strlen(user->handle_info->handle) + strlen(hidden_host_suffix) + 2);
711         sprintf(hostname, "%s.%s", user->handle_info->handle, hidden_host_suffix);
712     } else if (options & GENMASK_STRICT_HOST) {
713         if (options & GENMASK_BYIP)
714             hostname = inet_ntoa(user->ip);
715     } else if ((options & GENMASK_BYIP) || !hostname[strspn(hostname, "0123456789.")]) {
716         /* Should generate an IP-based hostmask.  By popular acclaim, a /16
717          * hostmask is used by default. */
718         unsigned masked_ip, mask, masklen;
719         masklen = 16;
720         mask = ~0 << masklen;
721         masked_ip = ntohl(user->ip.s_addr) & mask;
722         hostname = alloca(32);
723         if (options & GENMASK_SRVXMASK) {
724             sprintf(hostname, "%d.%d.%d.%d/%d", (masked_ip>>24)&0xFF, (masked_ip>>16)&0xFF, (masked_ip>>8)&0xFF, masked_ip&0xFF, masklen);
725         } else {
726             int ofs = 0;
727             for (ii=0; ii<4; ii++) {
728                 if (masklen) {
729                     ofs += sprintf(hostname+ofs, "%d.", (masked_ip>>24)&0xFF);
730                     masklen -= 8;
731                     masked_ip <<= 8;
732                 } else {
733                     ofs += sprintf(hostname+ofs, "*.");
734                 }
735             }
736             /* Truncate the last . */
737             hostname[ofs-1] = 0;
738         }
739     } else {
740         int cnt;
741         /* This heuristic could be made smarter.  Is it worth the effort? */
742         for (ii=cnt=0; hostname[ii]; ii++)
743             if (hostname[ii] == '.')
744                 cnt++;
745         if (cnt == 1) {
746             /* only a two-level domain name; leave hostname */
747         } else if (cnt == 2) {
748             for (ii=0; user->hostname[ii] != '.'; ii++) ;
749             /* Add 3 to account for the *. and \0. */
750             hostname = alloca(strlen(user->hostname+ii)+3);
751             sprintf(hostname, "*.%s", user->hostname+ii+1);
752         } else {
753             for (cnt=3, ii--; cnt; ii--)
754                 if (user->hostname[ii] == '.')
755                     cnt--;
756             /* The loop above will overshoot the dot one character;
757                we skip forward two (the one character and the dot)
758                when printing, so we only add one for the \0. */
759             hostname = alloca(strlen(user->hostname+ii)+1);
760             sprintf(hostname, "*.%s", user->hostname+ii+2);
761         }
762     }
763     /* Emit hostmask */
764     len = strlen(ident) + strlen(hostname) + 2;
765     if (nickname) {
766         len += strlen(nickname) + 1;
767         mask = malloc(len);
768         sprintf(mask, "%s!%s@%s", nickname, ident, hostname);
769     } else {
770         mask = malloc(len);
771         sprintf(mask, "%s@%s", ident, hostname);
772     }
773     return mask;
774 }
775
776 int
777 IsChannelName(const char *name) {
778     unsigned int ii;
779
780     if (*name !='#')
781         return 0;
782     for (ii=1; name[ii]; ++ii) {
783         if ((name[ii] > 0) && (name[ii] <= 32))
784             return 0;
785         if (name[ii] == ',')
786             return 0;
787         if (name[ii] == '\xa0')
788             return 0;
789     }
790     return 1;
791 }