1 /* proto-common.c - common IRC protocol parsing/sending support
2 * Copyright 2000-2006 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.
29 #ifdef HAVE_SYS_SOCKET_H
30 #include <sys/socket.h>
32 #ifdef HAVE_NETINET_IN_H
33 #include <netinet/in.h>
35 #ifdef HAVE_ARPA_INET_H
36 #include <arpa/inet.h>
39 unsigned int lines_processed;
41 struct io_fd *socket_io_fd;
43 const char *hidden_host_suffix;
45 static char replay_line[MAXLEN+80];
47 static int ping_timeout;
48 static int replay_connected;
49 static unsigned int nicklen = NICKLEN; /* how long do we think servers allow nicks to be? */
50 static struct userList dead_users;
52 extern struct cManagerNode cManager;
53 extern unsigned long burst_length;
54 extern struct cManagerNode cManager;
55 extern struct policer_params *oper_policer_params, *luser_policer_params;
56 extern server_link_func_t *slf_list;
57 extern unsigned int slf_size, slf_used;
58 extern new_user_func_t *nuf_list;
59 extern unsigned int nuf_size, nuf_used;
60 extern del_user_func_t *duf_list;
61 extern unsigned int duf_size, duf_used;
62 extern unsigned long boot_time;
64 void received_ping(void);
66 static int replay_read(void);
67 static dict_t irc_func_dict;
69 typedef void (*foreach_chanfunc) (struct chanNode *chan, void *data);
70 typedef void (*foreach_nonchan) (char *name, void *data);
71 typedef void (*foreach_userfunc) (struct userNode *user, void *data);
72 typedef void (*foreach_nonuser) (char *name, void *data);
73 static void parse_foreach(char *target_list, foreach_chanfunc cf, foreach_nonchan nc, foreach_userfunc uf, foreach_nonuser nu, void *data);
76 uplink_readable(struct io_fd *fd) {
77 static char buffer[MAXLEN];
81 pos = ioset_line_read(fd, buffer, sizeof(buffer));
86 if ((eol = strpbrk(buffer, "\r\n")))
88 log_replay(MAIN_LOG, false, buffer);
89 if (cManager.uplink->state != DISCONNECTED)
90 parse_line(buffer, 0);
95 socket_destroyed(struct io_fd *fd)
97 if (fd && fd->state != IO_CONNECTED)
98 log_module(MAIN_LOG, LOG_ERROR, "Connection to server lost.");
100 cManager.uplink->state = DISCONNECTED;
102 DelServer(self->uplink, 0, NULL);
105 void replay_event_loop(void)
107 while (!quit_services) {
108 if (!replay_connected) {
109 /* this time fudging is to get some of the logging right */
110 self->link_time = self->boot = now;
111 cManager.uplink->state = AUTHENTICATING;
112 irc_introduce(cManager.uplink->password);
113 replay_connected = 1;
114 } else if (!replay_read()) {
115 log_module(MAIN_LOG, LOG_ERROR, "Connection to server lost.");
123 create_socket_client(struct uplinkNode *target)
125 int port = target->port;
126 const char *addr = target->host;
129 return feof(replay_file) ? 0 : 1;
132 /* Leave the existing socket open, say we failed. */
133 log_module(MAIN_LOG, LOG_ERROR, "Refusing to create second connection to %s:%d.", addr, port);
137 log_module(MAIN_LOG, LOG_INFO, "Connecting to %s:%i...", addr, port);
139 socket_io_fd = ioset_connect(cManager.uplink->bind_addr, cManager.uplink->bind_addr_len, addr, port, 1, 0, NULL);
141 log_module(MAIN_LOG, LOG_ERROR, "Connection to uplink failed: %s (%d)", strerror(errno), errno);
142 target->state = DISCONNECTED;
146 socket_io_fd->readable_cb = uplink_readable;
147 socket_io_fd->destroy_cb = socket_destroyed;
148 socket_io_fd->line_reads = 1;
149 log_module(MAIN_LOG, LOG_INFO, "Connection to server established.");
150 cManager.uplink = target;
151 target->state = AUTHENTICATING;
157 replay_read_line(void)
160 unsigned long new_time;
162 if (replay_line[0]) return;
164 if (!fgets(replay_line, sizeof(replay_line), replay_file)) {
165 if (feof(replay_file)) {
167 memset(replay_line, 0, sizeof(replay_line));
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);
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(×tamp);
190 if (new_time == (unsigned long)-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);
196 if (strncmp(replay_line+22, "(info) ", 7))
205 char read_line[MAXLEN];
208 /* if it's a sent line, break out to handle it */
209 if (!strncmp(replay_line+29, " ", 3))
211 if (!strncmp(replay_line+29, "W: ", 3)) {
212 log_module(MAIN_LOG, LOG_ERROR, "Expected response from services: %s", replay_line+32);
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;
224 parse_line(read_line, 0);
230 replay_write(char *text)
233 if (strncmp(replay_line+29, "W: ", 3)) {
234 log_module(MAIN_LOG, LOG_ERROR, "Unexpected output during replay: %s", text);
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);
240 log_replay(MAIN_LOG, true, text);
246 void putsock(const char *text, ...) PRINTF_LIKE(1, 2);
249 putsock(const char *text, ...)
255 if (!cManager.uplink || cManager.uplink->state == DISCONNECTED) return;
257 va_start(arg_list, text);
258 pos = vsnprintf(buffer, MAXLEN - 2, text, arg_list);
260 if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
263 log_replay(MAIN_LOG, true, buffer);
264 buffer[pos++] = '\n';
266 ioset_write(socket_io_fd, buffer, pos);
268 replay_write(buffer);
276 replay_connected = 0;
277 socket_destroyed(socket_io_fd);
279 ioset_close(socket_io_fd, 3);
284 #define CMD_FUNC(NAME) int NAME(UNUSED_ARG(const char *origin), UNUSED_ARG(unsigned int argc), UNUSED_ARG(char **argv))
285 typedef CMD_FUNC(cmd_func_t);
287 static void timed_ping_timeout(void *data);
289 /* Ping state is kept in the timeq (only one of these two can be in
290 * the queue at any given time). */
292 timed_send_ping(UNUSED_ARG(void *data))
294 irc_ping(self->name);
295 timeq_add(now + ping_timeout, timed_ping_timeout, 0);
299 timed_ping_timeout(UNUSED_ARG(void *data))
301 /* Uplink "health" tracking could be accomplished by counting the
302 number of ping timeouts that happen for each uplink. After the
303 timeouts per time period exceeds some amount, the uplink could
304 be marked as unavalable.*/
305 irc_squit(self, "Ping timeout.", NULL);
308 static CMD_FUNC(cmd_pass)
310 const char *true_pass;
314 true_pass = cManager.uplink->their_password;
315 if (true_pass && strcmp(true_pass, argv[1])) {
316 /* It might be good to mark the uplink as unavailable when
317 this happens, though there should be a way of resetting
319 irc_squit(self, "Incorrect password received.", NULL);
323 cManager.uplink->state = BURSTING;
327 static CMD_FUNC(cmd_dummy)
329 /* we don't care about these messages */
333 static CMD_FUNC(cmd_error)
335 if (argv[1]) log_module(MAIN_LOG, LOG_ERROR, "Error from ircd: %s", argv[1]);
336 log_module(MAIN_LOG, LOG_ERROR, "Error received from uplink, squitting.");
338 if (cManager.uplink->state != CONNECTED) {
339 /* Error messages while connected should be fine. */
340 cManager.uplink->flags |= UPLINK_UNAVAILABLE;
341 log_module(MAIN_LOG, LOG_ERROR, "Disabling uplink.");
348 static CMD_FUNC(cmd_stats)
354 if (!(un = GetUserH(origin)))
356 switch (argv[1][0]) {
358 unsigned long uptime;
359 uptime = now - boot_time;
360 irc_numeric(un, RPL_STATSUPTIME, ":Server Up %d days %d:%02d:%02d",
361 uptime/(24*60*60), (uptime/(60*60))%24, (uptime/60)%60, uptime%60);
362 irc_numeric(un, RPL_MAXCONNECTIONS, ":Highest connection count: %d (%d clients)",
363 self->max_clients+1, self->max_clients);
366 default: /* unrecognized/unhandled stats output */ break;
368 irc_numeric(un, 219, "%s :End of /STATS report", argv[1]);
372 static CMD_FUNC(cmd_version)
374 struct userNode *user;
375 if (!(user = GetUserH(origin))) {
376 log_module(MAIN_LOG, LOG_ERROR, "Could not find VERSION origin user %s", origin);
379 irc_numeric(user, 351, "%s.%s %s :%s", PACKAGE_TARNAME, PACKAGE_VERSION, self->name, CODENAME);
383 static CMD_FUNC(cmd_admin)
385 struct userNode *user;
386 struct string_list *slist;
388 if (!(user = GetUserH(origin))) {
389 log_module(MAIN_LOG, LOG_ERROR, "Could not find ADMIN origin user %s", origin);
392 if ((slist = conf_get_data("server/admin", RECDB_STRING_LIST)) && slist->used) {
395 irc_numeric(user, 256, ":Administrative info about %s", self->name);
396 for (i = 0; i < slist->used && i < 3; i++)
397 irc_numeric(user, 257 + i, ":%s", slist->list[i]);
399 irc_numeric(user, 423, ":No administrative info available");
405 recalc_bursts(struct server *eob_server)
408 eob_server->burst = eob_server->self_burst;
409 if (eob_server->uplink != self)
410 eob_server->burst = eob_server->burst || eob_server->uplink->burst;
411 for (nn=0; nn < eob_server->children.used; nn++)
412 recalc_bursts(eob_server->children.list[nn]);
415 static struct chanmsg_func {
417 struct userNode *service;
418 } chanmsg_funcs[256]; /* indexed by trigger character */
420 static struct allchanmsg_func {
422 struct userNode *service;
423 } allchanmsg_funcs[ALLCHANMSG_FUNCS_MAX];
425 struct privmsg_desc {
426 struct userNode *user;
428 unsigned int is_notice : 1;
429 unsigned int is_qualified : 1;
433 privmsg_chan_helper(struct chanNode *cn, void *data)
435 extern unsigned short offchannel_allowed[256];
436 struct privmsg_desc *pd = data;
438 struct chanmsg_func *cf;
441 /* Don't complain if it can't find the modeNode because the channel might
443 if ((mn = GetUserMode(cn, pd->user)))
444 mn->idle_since = now;
446 /* Never send a NOTICE to a channel to one of the services */
447 cf = &chanmsg_funcs[(unsigned char)pd->text[0]];
448 if (cf->func && !pd->is_notice
449 && (offchannel_allowed[(unsigned char)pd->text[0]]
450 || (GetUserMode(cn, cf->service) && !IsDeaf(cf->service))))
451 cf->func(pd->user, cn, pd->text+1, cf->service, pd->is_notice);
453 spamserv_channel_message(cn, pd->user, pd->text);
455 /* This catches *all* text sent to the channel that the services server sees */
456 for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
457 cf = (struct chanmsg_func *)&allchanmsg_funcs[x];
459 break; /* end of list */
461 cf->func(pd->user, cn, pd->text, cf->service, pd->is_notice);
466 privmsg_invalid(char *name, void *data)
468 struct privmsg_desc *pd = data;
472 irc_numeric(pd->user, ERR_NOSUCHNICK, "%s@%s :No such nick", name, self->name);
476 struct userNode *user;
481 part_helper(struct chanNode *cn, void *data)
483 struct part_desc *desc = data;
484 DelChannelUser(desc->user, cn, desc->text, false);
485 if (IsOper(desc->user))
486 operpart(cn, desc->user);
489 static CMD_FUNC(cmd_part)
491 struct part_desc desc;
495 desc.user = GetUserH(origin);
498 desc.text = (argc > 2) ? argv[argc - 1] : NULL;
499 parse_foreach(argv[1], part_helper, NULL, NULL, NULL, &desc);
504 reg_chanmsg_func(unsigned char prefix, struct userNode *service, chanmsg_func_t handler)
506 if (chanmsg_funcs[prefix].func)
507 log_module(MAIN_LOG, LOG_WARNING, "Re-registering new chanmsg handler for character `%c'.", prefix);
508 chanmsg_funcs[prefix].func = handler;
509 chanmsg_funcs[prefix].service = service;
513 reg_allchanmsg_func(struct userNode *service, chanmsg_func_t handler)
516 for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
517 if (allchanmsg_funcs[x].func)
519 allchanmsg_funcs[x].func = handler;
520 allchanmsg_funcs[x].service = service;
526 get_chanmsg_bot(unsigned char prefix)
528 return chanmsg_funcs[prefix].service;
531 static mode_change_func_t *mcf_list;
532 static unsigned int mcf_size = 0, mcf_used = 0;
535 reg_mode_change_func(mode_change_func_t handler)
537 if (mcf_used == mcf_size) {
540 mcf_list = realloc(mcf_list, mcf_size*sizeof(mode_change_func_t));
543 mcf_list = malloc(mcf_size*sizeof(mode_change_func_t));
546 mcf_list[mcf_used++] = handler;
549 static oper_func_t *of_list;
550 static unsigned int of_size = 0, of_used = 0;
553 reg_oper_func(oper_func_t handler)
555 if (of_used == of_size) {
558 of_list = realloc(of_list, of_size*sizeof(oper_func_t));
561 of_list = malloc(of_size*sizeof(oper_func_t));
564 of_list[of_used++] = handler;
568 call_oper_funcs(struct userNode *user)
573 for (n=0; (n<of_used) && !user->dead; n++)
579 struct mod_chanmode *
580 mod_chanmode_alloc(unsigned int argc)
582 struct mod_chanmode *res;
584 res = calloc(1, sizeof(*res) + (argc-1)*sizeof(res->args[0]));
586 res = calloc(1, sizeof(*res));
589 res->alloc_argc = argc;
596 struct mod_chanmode *
597 mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra)
599 struct mod_chanmode *res;
600 res = mod_chanmode_alloc(orig->argc + extra);
602 res->modes_set = orig->modes_set;
603 res->modes_clear = orig->modes_clear;
604 res->new_limit = orig->new_limit;
605 res->new_access = orig->new_access;
606 memcpy(res->new_altchan, orig->new_altchan, sizeof(res->new_altchan));
607 memcpy(res->new_key, orig->new_key, sizeof(res->new_key));
608 memcpy(res->new_upass, orig->new_upass, sizeof(res->new_upass));
609 memcpy(res->new_apass, orig->new_apass, sizeof(res->new_apass));
610 res->argc = orig->argc;
611 memcpy(res->args, orig->args, orig->argc*sizeof(orig->args[0]));
617 mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change)
622 assert(change->argc <= change->alloc_argc);
623 channel->modes = (channel->modes & ~change->modes_clear) | change->modes_set;
624 if (change->modes_set & MODE_LIMIT)
625 channel->limit = change->new_limit;
626 if (change->modes_set & MODE_ACCESS)
627 channel->access = change->new_access;
628 if (change->modes_set & MODE_KEY)
629 strcpy(channel->key, change->new_key);
630 if (change->modes_set & MODE_ALTCHAN)
631 strcpy(channel->altchan, change->new_altchan);
632 if (change->modes_set & MODE_UPASS)
633 strcpy(channel->upass, change->new_upass);
634 if (change->modes_set & MODE_APASS)
635 strcpy(channel->apass, change->new_apass);
636 for (ii = 0; ii < change->argc; ++ii) {
637 switch (change->args[ii].mode) {
639 /* If any existing ban is a subset of the new ban,
640 * silently remove it. The new ban is not allowed
641 * to be more specific than an existing ban.
643 for (jj=0; jj<channel->banlist.used; ++jj) {
644 bn = channel->banlist.list[jj];
645 if (match_ircglobs(change->args[ii].u.hostmask, bn->ban)) {
646 banList_remove(&channel->banlist, bn);
651 bn = calloc(1, sizeof(*bn));
652 safestrncpy(bn->ban, change->args[ii].u.hostmask, sizeof(bn->ban));
654 safestrncpy(bn->who, who->nick, sizeof(bn->who));
656 safestrncpy(bn->who, "<unknown>", sizeof(bn->who));
658 banList_append(&channel->banlist, bn);
660 case MODE_REMOVE|MODE_BAN:
661 for (jj=0; jj<channel->banlist.used; ++jj) {
662 bn = channel->banlist.list[jj];
663 if (strcmp(bn->ban, change->args[ii].u.hostmask))
666 banList_remove(&channel->banlist, bn);
672 case MODE_VOICE|MODE_CHANOP:
673 case MODE_REMOVE|MODE_CHANOP:
674 case MODE_REMOVE|MODE_VOICE:
675 case MODE_REMOVE|MODE_VOICE|MODE_CHANOP:
676 if (change->args[ii].mode & MODE_REMOVE)
677 change->args[ii].u.member->modes &= ~change->args[ii].mode;
679 change->args[ii].u.member->modes |= change->args[ii].mode;
682 assert(0 && "Invalid mode argument");
689 mod_chanmode_free(struct mod_chanmode *change)
695 mod_chanmode(struct userNode *who, struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags)
697 struct modeNode *member;
698 struct mod_chanmode *change;
702 if (!modes || !modes[0])
704 if (who && (member = GetUserMode(channel, who)))
705 base_oplevel = member->oplevel;
707 base_oplevel = MAXOPLEVEL;
708 if (!(change = mod_chanmode_parse(channel, modes, argc, flags, base_oplevel)))
710 if (flags & MC_ANNOUNCE)
711 mod_chanmode_announce(who, channel, change);
713 mod_chanmode_apply(who, channel, change);
714 if (flags & MC_NOTIFY)
715 for (ii = 0; ii < mcf_used; ++ii)
716 mcf_list[ii](channel, who, change);
717 mod_chanmode_free(change);
722 irc_make_chanmode(struct chanNode *chan, char *out)
724 struct mod_chanmode change;
725 mod_chanmode_init(&change);
726 change.modes_set = chan->modes;
727 change.new_limit = chan->limit;
728 change.new_access = chan->access;
729 safestrncpy(change.new_altchan, chan->altchan, sizeof(change.new_altchan));
730 safestrncpy(change.new_key, chan->key, sizeof(change.new_key));
731 safestrncpy(change.new_upass, chan->upass, sizeof(change.new_upass));
732 safestrncpy(change.new_apass, chan->apass, sizeof(change.new_apass));
733 return strlen(mod_chanmode_format(&change, out));
737 generate_hostmask(struct userNode *user, int options)
740 char *nickname, *ident, *hostname, *mask;
743 /* figure out string parts */
744 if (options & GENMASK_OMITNICK)
746 else if (options & GENMASK_USENICK)
747 nickname = user->nick;
750 if (options & GENMASK_STRICT_IDENT)
752 else if (options & GENMASK_ANY_IDENT)
754 else if (IsFakeIdent(user) && IsHiddenHost(user) && !(options & GENMASK_NO_HIDING))
755 ident = user->fakeident;
757 ident = alloca(strlen(user->ident)+2);
759 strcpy(ident+1, user->ident + ((*user->ident == '~')?1:0));
761 hostname = user->hostname;
762 if (IsFakeHost(user) && IsHiddenHost(user) && !(options & GENMASK_NO_HIDING)) {
763 if(user->fakehost && user->fakehost[0] == '$') {
764 hostname = alloca(strlen(user->handle_info->handle) + strlen(user->fakehost));
765 sprintf(hostname, "%s%s", user->handle_info->handle, user->fakehost+1);
767 hostname = user->fakehost;
769 } else if (IsHiddenHost(user) && user->handle_info && hidden_host_suffix && !(options & GENMASK_NO_HIDING)) {
770 hostname = alloca(strlen(user->handle_info->handle) + strlen(hidden_host_suffix) + 2);
771 sprintf(hostname, "%s.%s", user->handle_info->handle, hidden_host_suffix);
772 } else if (options & GENMASK_STRICT_HOST) {
773 if (options & GENMASK_BYIP)
774 hostname = (char*)irc_ntoa(&user->ip);
775 } else if ((options & GENMASK_BYIP) || irc_pton(&ip, NULL, hostname)) {
776 /* Should generate an IP-based hostmask. */
777 hostname = alloca(IRC_NTOP_MAX_SIZE);
778 hostname[IRC_NTOP_MAX_SIZE-1] = '\0';
779 if (irc_in_addr_is_ipv4(user->ip)) {
780 /* By popular acclaim, a /16 hostmask is used. */
781 sprintf(hostname, "%d.%d.*", user->ip.in6_8[12], user->ip.in6_8[13]);
782 } else if (irc_in_addr_is_ipv6(user->ip)) {
783 /* Who knows what the default mask should be? Use a /48 to start with. */
784 sprintf(hostname, "%x:%x:%x:*", ntohs(user->ip.in6[0]), ntohs(user->ip.in6[1]), ntohs(user->ip.in6[2]));
786 /* Unknown type; just copy IP directly. */
787 irc_ntop(hostname, IRC_NTOP_MAX_SIZE, &user->ip);
791 /* This heuristic could be made smarter. Is it worth the effort? */
792 for (ii=cnt=0; hostname[ii]; ii++)
793 if (hostname[ii] == '.')
795 if (cnt == 0 || cnt == 1) {
796 /* only a one- or two-level domain name; leave hostname */
797 } else if (cnt == 2) {
798 for (ii=0; user->hostname[ii] != '.'; ii++) ;
799 /* Add 3 to account for the *. and \0. */
800 hostname = alloca(strlen(user->hostname+ii)+3);
801 sprintf(hostname, "*.%s", user->hostname+ii+1);
803 for (cnt=3, ii--; cnt; ii--)
804 if (user->hostname[ii] == '.')
806 /* The loop above will overshoot the dot one character;
807 we skip forward two (the one character and the dot)
808 when printing, so we only add one for the \0. */
809 hostname = alloca(strlen(user->hostname+ii)+1);
810 sprintf(hostname, "*.%s", user->hostname+ii+2);
814 len = strlen(ident) + strlen(hostname) + 2;
816 len += strlen(nickname) + 1;
818 sprintf(mask, "%s!%s@%s", nickname, ident, hostname);
821 sprintf(mask, "%s@%s", ident, hostname);
827 IsChannelName(const char *name) {
832 for (ii=1; name[ii]; ++ii) {
833 if ((name[ii] > 0) && (name[ii] <= 32))
837 if (name[ii] == '\xa0')
844 irc_user_modes(const struct userNode *user, char modes[], size_t length)
848 for (ii = jj = 0; (jj < length) && (irc_user_mode_chars[ii] != '\0'); ++ii) {
849 if ((user->modes & (1 << ii)) && (irc_user_mode_chars[ii] != ' '))
850 modes[jj++] = irc_user_mode_chars[ii];