1 /* proto-common.c - common IRC protocol parsing/sending support
2 * Copyright 2000-2004 srvx Development Team
4 * This file is part of srvx.
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.
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.
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.
27 #ifdef HAVE_SYS_SOCKET_H
28 #include <sys/socket.h>
30 #ifdef HAVE_NETINET_IN_H
31 #include <netinet/in.h>
33 #ifdef HAVE_ARPA_INET_H
34 #include <arpa/inet.h>
37 unsigned int lines_processed;
39 struct io_fd *socket_io_fd;
41 const char *hidden_host_suffix;
43 static char replay_line[MAXLEN+80];
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;
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;
62 void received_ping(void);
64 static int replay_read(void);
65 static dict_t irc_func_dict;
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);
74 uplink_readable(struct io_fd *fd) {
75 static char buffer[MAXLEN];
79 pos = ioset_line_read(fd, buffer, sizeof(buffer));
84 if ((eol = strpbrk(buffer, "\r\n"))) *eol = 0;
85 log_replay(MAIN_LOG, false, buffer);
86 if (cManager.uplink->state != DISCONNECTED)
87 parse_line(buffer, 0);
92 socket_destroyed(struct io_fd *fd)
95 log_module(MAIN_LOG, LOG_ERROR, "Connection to server lost.");
97 cManager.uplink->state = DISCONNECTED;
98 if (self->uplink) DelServer(self->uplink, 0, NULL);
101 void replay_event_loop(void)
103 while (!quit_services) {
104 if (!replay_connected) {
105 /* this time fudging is to get some of the logging right */
106 self->link = self->boot = now;
107 cManager.uplink->state = AUTHENTICATING;
108 irc_introduce(cManager.uplink->password);
109 replay_connected = 1;
110 } else if (!replay_read()) {
111 log_module(MAIN_LOG, LOG_ERROR, "Connection to server lost.");
119 create_socket_client(struct uplinkNode *target)
121 int port = target->port;
122 const char *addr = target->host;
125 return feof(replay_file) ? 0 : 1;
128 /* Leave the existing socket open, say we failed. */
129 log_module(MAIN_LOG, LOG_ERROR, "Refusing to create second connection to %s:%d.", addr, port);
133 log_module(MAIN_LOG, LOG_INFO, "Connecting to %s:%i...", addr, port);
135 socket_io_fd = ioset_connect((struct sockaddr*)cManager.uplink->bind_addr, sizeof(struct sockaddr), addr, port, 1, 0, NULL);
137 log_module(MAIN_LOG, LOG_ERROR, "Connection to uplink failed: %s (%d)", strerror(errno), errno);
138 target->state = DISCONNECTED;
142 socket_io_fd->readable_cb = uplink_readable;
143 socket_io_fd->destroy_cb = socket_destroyed;
144 socket_io_fd->line_reads = 1;
145 socket_io_fd->wants_reads = 1;
146 log_module(MAIN_LOG, LOG_INFO, "Connection to server established.");
147 cManager.uplink = target;
148 target->state = AUTHENTICATING;
154 replay_read_line(void)
159 if (replay_line[0]) return;
161 if (!fgets(replay_line, sizeof(replay_line), replay_file)) {
162 if (feof(replay_file)) {
164 memset(replay_line, 0, sizeof(replay_line));
168 if ((replay_line[0] != '[')
169 || (replay_line[3] != ':')
170 || (replay_line[6] != ':')
171 || (replay_line[9] != ' ')
172 || (replay_line[12] != '/')
173 || (replay_line[15] != '/')
174 || (replay_line[20] != ']')
175 || (replay_line[21] != ' ')) {
176 log_module(MAIN_LOG, LOG_ERROR, "Unrecognized timestamp in replay file: %s", replay_line);
179 timestamp.tm_hour = strtoul(replay_line+1, NULL, 10);
180 timestamp.tm_min = strtoul(replay_line+4, NULL, 10);
181 timestamp.tm_sec = strtoul(replay_line+7, NULL, 10);
182 timestamp.tm_mon = strtoul(replay_line+10, NULL, 10) - 1;
183 timestamp.tm_mday = strtoul(replay_line+13, NULL, 10);
184 timestamp.tm_year = strtoul(replay_line+16, NULL, 10) - 1900;
185 timestamp.tm_isdst = 0;
186 new_time = mktime(×tamp);
187 if (new_time == -1) {
188 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);
193 if (strncmp(replay_line+22, "(info) ", 7))
202 char read_line[MAXLEN];
205 /* if it's a sent line, break out to handle it */
206 if (!strncmp(replay_line+29, " ", 3))
208 if (!strncmp(replay_line+29, "W: ", 3)) {
209 log_module(MAIN_LOG, LOG_ERROR, "Expected response from services: %s", replay_line+32);
215 log_replay(MAIN_LOG, false, replay_line+32);
216 safestrncpy(read_line, replay_line+32, sizeof(read_line));
217 len = strlen(read_line);
218 if (read_line[len-1] == '\n')
219 read_line[--len] = 0;
221 parse_line(read_line, 0);
227 replay_write(char *text)
230 if (strncmp(replay_line+29, "W: ", 3)) {
231 log_module(MAIN_LOG, LOG_ERROR, "Unexpected output during replay: %s", text);
234 if (strcmp(replay_line+32, text)) {
235 log_module(MAIN_LOG, LOG_ERROR, "Incorrect output during replay:\nReceived: %sExpected: %s", text, replay_line+32);
237 log_replay(MAIN_LOG, true, text);
243 void putsock(const char *text, ...) PRINTF_LIKE(1, 2);
246 putsock(const char *text, ...)
252 if (!cManager.uplink || cManager.uplink->state == DISCONNECTED) return;
254 va_start(arg_list, text);
255 pos = vsnprintf(buffer, MAXLEN - 2, text, arg_list);
257 if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
260 log_replay(MAIN_LOG, true, buffer);
261 buffer[pos++] = '\n';
263 ioset_write(socket_io_fd, buffer, pos);
265 replay_write(buffer);
273 replay_connected = 0;
274 socket_destroyed(socket_io_fd);
276 ioset_close(socket_io_fd->fd, 1);
280 #define CMD_FUNC(NAME) int NAME(UNUSED_ARG(const char *origin), UNUSED_ARG(unsigned int argc), UNUSED_ARG(char **argv))
281 typedef CMD_FUNC(cmd_func_t);
283 static void timed_ping_timeout(void *data);
285 /* Ping state is kept in the timeq (only one of these two can be in
286 * the queue at any given time). */
288 timed_send_ping(UNUSED_ARG(void *data))
290 irc_ping(self->name);
291 timeq_add(now + ping_timeout, timed_ping_timeout, 0);
295 timed_ping_timeout(UNUSED_ARG(void *data))
297 /* Uplink "health" tracking could be accomplished by counting the
298 number of ping timeouts that happen for each uplink. After the
299 timeouts per time period exceeds some amount, the uplink could
300 be marked as unavalable.*/
301 irc_squit(self, "Ping timeout.", NULL);
304 static CMD_FUNC(cmd_pass)
306 const char *true_pass;
310 true_pass = cManager.uplink->their_password;
311 if (true_pass && strcmp(true_pass, argv[1])) {
312 /* It might be good to mark the uplink as unavailable when
313 this happens, though there should be a way of resetting
315 irc_squit(self, "Incorrect password received.", NULL);
319 cManager.uplink->state = BURSTING;
323 static CMD_FUNC(cmd_dummy)
325 /* we don't care about these messages */
329 static CMD_FUNC(cmd_error)
331 if (argv[1]) log_module(MAIN_LOG, LOG_ERROR, "Error: %s", argv[1]);
332 log_module(MAIN_LOG, LOG_ERROR, "Error received from uplink, squitting.");
334 if (cManager.uplink->state != CONNECTED) {
335 /* Error messages while connected should be fine. */
336 cManager.uplink->flags |= UPLINK_UNAVAILABLE;
337 log_module(MAIN_LOG, LOG_ERROR, "Disabling uplink.");
344 static CMD_FUNC(cmd_stats)
350 if (!(un = GetUserH(origin)))
352 switch (argv[1][0]) {
354 unsigned int uptime = now - boot_time;
355 irc_numeric(un, RPL_STATSUPTIME, ":Server Up %d days %d:%02d:%02d",
356 uptime/(24*60*60), (uptime/(60*60))%24, (uptime/60)%60, uptime%60);
357 irc_numeric(un, RPL_MAXCONNECTIONS, ":Highest connection count: %d (%d clients)",
358 self->max_clients+1, self->max_clients);
361 default: /* unrecognized/unhandled stats output */ break;
363 irc_numeric(un, 219, "%s :End of /STATS report", argv[1]);
367 static CMD_FUNC(cmd_whois)
369 struct userNode *from;
370 struct userNode *who;
374 if (!(from = GetUserH(origin))) {
375 log_module(MAIN_LOG, LOG_ERROR, "Could not find WHOIS origin user %s", origin);
378 if(!(who = GetUserH(argv[2]))) {
379 irc_numeric(from, ERR_NOSUCHNICK, "%s@%s :No such nick", argv[2], self->name);
382 if (IsHiddenHost(who) && !IsOper(from)) {
383 /* Just stay quiet. */
386 irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->ident, who->hostname, who->info);
387 irc_numeric(from, RPL_WHOISSERVER, "%s %s :%s", who->nick, who->uplink->name, who->uplink->description);
389 irc_numeric(from, RPL_WHOISOPERATOR, "%s :is a megalomaniacal power hungry tyrant", who->nick);
391 irc_numeric(from, RPL_ENDOFWHOIS, "%s :End of /WHOIS list", who->nick);
395 static CMD_FUNC(cmd_version)
397 struct userNode *user;
398 if (!(user = GetUserH(origin))) {
399 log_module(MAIN_LOG, LOG_ERROR, "Could not find VERSION origin user %s", origin);
402 irc_numeric(user, 351, "%s.%s %s :%s", PACKAGE_TARNAME, PACKAGE_VERSION, self->name, CODENAME);
406 static CMD_FUNC(cmd_admin)
408 struct userNode *user;
409 struct string_list *slist;
411 if (!(user = GetUserH(origin))) {
412 log_module(MAIN_LOG, LOG_ERROR, "Could not find ADMIN origin user %s", origin);
415 if ((slist = conf_get_data("server/admin", RECDB_STRING_LIST)) && slist->used) {
418 irc_numeric(user, 256, ":Administrative info about %s", self->name);
419 for (i = 0; i < slist->used && i < 3; i++)
420 irc_numeric(user, 257 + i, ":%s", slist->list[i]);
422 irc_numeric(user, 423, ":No administrative info available");
428 recalc_bursts(struct server *eob_server)
431 eob_server->burst = eob_server->self_burst;
432 if (eob_server->uplink != self)
433 eob_server->burst = eob_server->burst || eob_server->uplink->burst;
434 for (nn=0; nn < eob_server->children.used; nn++)
435 recalc_bursts(eob_server->children.list[nn]);
438 static struct chanmsg_func {
440 struct userNode *service;
441 } chanmsg_funcs[256]; /* indexed by trigger character */
443 struct privmsg_desc {
444 struct userNode *user;
446 unsigned int is_notice : 1;
447 unsigned int is_qualified : 1;
451 privmsg_chan_helper(struct chanNode *cn, void *data)
453 struct privmsg_desc *pd = data;
455 struct chanmsg_func *cf = &chanmsg_funcs[(unsigned char)pd->text[0]];
457 /* Don't complain if it can't find the modeNode because the channel might
459 if ((mn = GetUserMode(cn, pd->user)))
460 mn->idle_since = now;
462 /* Never send a NOTICE to a channel to one of the services */
463 if (!pd->is_notice && cf->func && GetUserMode(cn, cf->service))
464 cf->func(pd->user, cn, pd->text+1, cf->service);
468 privmsg_invalid(char *name, void *data)
470 struct privmsg_desc *pd = data;
474 irc_numeric(pd->user, ERR_NOSUCHNICK, "%s@%s :No such nick", name, self->name);
478 part_helper(struct chanNode *cn, void *data)
480 DelChannelUser(data, cn, false, 0);
484 reg_chanmsg_func(unsigned char prefix, struct userNode *service, chanmsg_func_t handler)
486 if (chanmsg_funcs[prefix].func)
487 log_module(MAIN_LOG, LOG_WARNING, "Re-registering new chanmsg handler for character `%c'.", prefix);
488 chanmsg_funcs[prefix].func = handler;
489 chanmsg_funcs[prefix].service = service;
493 get_chanmsg_bot(unsigned char prefix)
495 return chanmsg_funcs[prefix].service;
498 static mode_change_func_t *mcf_list;
499 static unsigned int mcf_size = 0, mcf_used = 0;
502 reg_mode_change_func(mode_change_func_t handler)
504 if (mcf_used == mcf_size) {
507 mcf_list = realloc(mcf_list, mcf_size*sizeof(mode_change_func_t));
510 mcf_list = malloc(mcf_size*sizeof(mode_change_func_t));
513 mcf_list[mcf_used++] = handler;
516 struct mod_chanmode *
517 mod_chanmode_alloc(unsigned int argc)
519 struct mod_chanmode *res;
521 res = calloc(1, sizeof(*res) + (argc-1)*sizeof(res->args[0]));
523 res = calloc(1, sizeof(*res));
526 res->alloc_argc = argc;
533 struct mod_chanmode *
534 mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra)
536 struct mod_chanmode *res;
537 res = mod_chanmode_alloc(orig->argc + extra);
539 res->modes_set = orig->modes_set;
540 res->modes_clear = orig->modes_clear;
541 res->new_limit = orig->new_limit;
542 memcpy(res->new_key, orig->new_key, sizeof(res->new_key));
543 res->argc = orig->argc;
544 memcpy(res->args, orig->args, orig->argc*sizeof(orig->args[0]));
550 mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change)
555 assert(change->argc <= change->alloc_argc);
556 channel->modes = (channel->modes & ~change->modes_clear) | change->modes_set;
557 if (change->modes_set & MODE_LIMIT)
558 channel->limit = change->new_limit;
559 if (change->modes_set & MODE_KEY)
560 strcpy(channel->key, change->new_key);
561 for (ii = 0; ii < change->argc; ++ii) {
562 switch (change->args[ii].mode) {
564 /* If any existing ban is a subset of the new ban,
565 * silently remove it. The new ban is not allowed
566 * to be more specific than an existing ban.
568 for (jj=0; jj<channel->banlist.used; ++jj) {
569 if (match_ircglobs(change->args[ii].hostmask, channel->banlist.list[jj]->ban)) {
570 banList_remove(&channel->banlist, channel->banlist.list[jj]);
571 free(channel->banlist.list[jj]);
575 bn = calloc(1, sizeof(*bn));
576 safestrncpy(bn->ban, change->args[ii].hostmask, sizeof(bn->ban));
577 safestrncpy(bn->who, who->nick, sizeof(bn->who));
579 banList_append(&channel->banlist, bn);
581 case MODE_REMOVE|MODE_BAN:
582 for (jj=0; jj<channel->banlist.used; ++jj) {
583 if (strcmp(channel->banlist.list[jj]->ban, change->args[ii].hostmask))
585 free(channel->banlist.list[jj]);
586 banList_remove(&channel->banlist, channel->banlist.list[jj]);
592 case MODE_VOICE|MODE_CHANOP:
593 case MODE_REMOVE|MODE_CHANOP:
594 case MODE_REMOVE|MODE_VOICE:
595 case MODE_REMOVE|MODE_VOICE|MODE_CHANOP:
596 if (change->args[ii].mode & MODE_REMOVE)
597 change->args[ii].member->modes &= ~change->args[ii].mode;
599 change->args[ii].member->modes |= change->args[ii].mode;
602 assert(0 && "Invalid mode argument");
609 mod_chanmode_free(struct mod_chanmode *change)
615 mod_chanmode(struct userNode *who, struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags)
617 struct mod_chanmode *change;
620 if (!modes || !modes[0])
622 if (!(change = mod_chanmode_parse(channel, modes, argc, flags)))
624 if (flags & MC_ANNOUNCE)
625 mod_chanmode_announce(who, channel, change);
627 mod_chanmode_apply(who, channel, change);
628 if (flags & MC_NOTIFY)
629 for (ii = 0; ii < mcf_used; ++ii)
630 mcf_list[ii](channel, who, change);
631 mod_chanmode_free(change);
636 irc_make_chanmode(struct chanNode *chan, char *out) {
637 struct mod_chanmode change;
638 change.modes_set = chan->modes;
639 change.modes_clear = change.argc = 0;
640 change.new_limit = chan->limit;
641 safestrncpy(change.new_key, chan->key, sizeof(change.new_key));
642 return strlen(mod_chanmode_format(&change, out));
646 generate_hostmask(struct userNode *user, int options)
648 char *nickname, *ident, *hostname;
652 /* figure out string parts */
653 if (options & GENMASK_OMITNICK)
655 else if (options & GENMASK_USENICK)
656 nickname = user->nick;
659 if (options & GENMASK_STRICT_IDENT)
661 else if (options & GENMASK_ANY_IDENT)
664 ident = alloca(strlen(user->ident)+2);
666 strcpy(ident+1, user->ident + ((*user->ident == '~')?1:0));
668 hostname = user->hostname;
669 if (IsHiddenHost(user) && user->handle_info && hidden_host_suffix && !(options & GENMASK_NO_HIDING)) {
670 hostname = alloca(strlen(user->handle_info->handle) + strlen(hidden_host_suffix) + 2);
671 sprintf(hostname, "%s.%s", user->handle_info->handle, hidden_host_suffix);
672 } else if (options & GENMASK_STRICT_HOST) {
673 if (options & GENMASK_BYIP)
674 hostname = inet_ntoa(user->ip);
675 } else if ((options & GENMASK_BYIP) || !hostname[strspn(hostname, "0123456789.")]) {
676 /* Should generate an IP-based hostmask. By popular acclaim, a /16
677 * hostmask is used by default. */
678 unsigned masked_ip, mask, masklen;
680 mask = ~0 << masklen;
681 masked_ip = ntohl(user->ip.s_addr) & mask;
682 hostname = alloca(32);
683 if (options & GENMASK_SRVXMASK) {
684 sprintf(hostname, "%d.%d.%d.%d/%d", (masked_ip>>24)&0xFF, (masked_ip>>16)&0xFF, (masked_ip>>8)&0xFF, masked_ip&0xFF, masklen);
687 for (ii=0; ii<4; ii++) {
689 ofs += sprintf(hostname+ofs, "%d.", (masked_ip>>24)&0xFF);
693 ofs += sprintf(hostname+ofs, "*.");
696 /* Truncate the last . */
701 /* This heuristic could be made smarter. Is it worth the effort? */
702 for (ii=cnt=0; hostname[ii]; ii++)
703 if (hostname[ii] == '.')
706 /* only a two-level domain name; leave hostname */
707 } else if (cnt == 2) {
708 for (ii=0; user->hostname[ii] != '.'; ii++) ;
709 /* Add 3 to account for the *. and \0. */
710 hostname = alloca(strlen(user->hostname+ii)+3);
711 sprintf(hostname, "*.%s", user->hostname+ii+1);
713 for (cnt=3, ii--; cnt; ii--)
714 if (user->hostname[ii] == '.')
716 /* The loop above will overshoot the dot one character;
717 we skip forward two (the one character and the dot)
718 when printing, so we only add one for the \0. */
719 hostname = alloca(strlen(user->hostname+ii)+1);
720 sprintf(hostname, "*.%s", user->hostname+ii+2);
724 len = strlen(ident) + strlen(hostname) + 2;
726 len += strlen(nickname) + 1;
728 sprintf(mask, "%s!%s@%s", nickname, ident, hostname);
731 sprintf(mask, "%s@%s", ident, hostname);
737 IsChannelName(const char *name) {
742 for (ii=1; name[ii]; ++ii) {
743 if ((name[ii] > 0) && (name[ii] <= 32))
747 if (name[ii] == '\xa0')