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