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.
28 #ifdef HAVE_SYS_SOCKET_H
29 #include <sys/socket.h>
31 #ifdef HAVE_NETINET_IN_H
32 #include <netinet/in.h>
34 #ifdef HAVE_ARPA_INET_H
35 #include <arpa/inet.h>
38 unsigned int lines_processed;
40 struct io_fd *socket_io_fd;
42 const char *hidden_host_suffix;
44 static char replay_line[MAXLEN+80];
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;
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 unsigned long boot_time;
63 void received_ping(void);
65 static int replay_read(void);
66 static dict_t irc_func_dict;
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);
75 uplink_readable(struct io_fd *fd) {
76 static char buffer[MAXLEN];
80 pos = ioset_line_read(fd, buffer, sizeof(buffer));
85 if ((eol = strpbrk(buffer, "\r\n")))
87 log_replay(MAIN_LOG, false, buffer);
88 if (cManager.uplink->state != DISCONNECTED)
89 parse_line(buffer, 0);
94 socket_destroyed(struct io_fd *fd)
96 if (fd && fd->state != IO_CONNECTED)
97 log_module(MAIN_LOG, LOG_ERROR, "Connection to server lost.");
99 cManager.uplink->state = DISCONNECTED;
101 DelServer(self->uplink, 0, NULL);
104 void replay_event_loop(void)
106 while (!quit_services) {
107 if (!replay_connected) {
108 /* this time fudging is to get some of the logging right */
109 self->link_time = 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.");
122 create_socket_client(struct uplinkNode *target)
124 int port = target->port;
125 const char *addr = target->host;
128 return feof(replay_file) ? 0 : 1;
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);
136 log_module(MAIN_LOG, LOG_INFO, "Connecting to %s:%i...", addr, port);
138 socket_io_fd = ioset_connect(cManager.uplink->bind_addr, cManager.uplink->bind_addr_len, addr, port, 1, 0, NULL);
140 log_module(MAIN_LOG, LOG_ERROR, "Connection to uplink failed: %s (%d)", strerror(errno), errno);
141 target->state = DISCONNECTED;
145 socket_io_fd->readable_cb = uplink_readable;
146 socket_io_fd->destroy_cb = socket_destroyed;
147 socket_io_fd->line_reads = 1;
148 log_module(MAIN_LOG, LOG_INFO, "Connection to server established.");
149 cManager.uplink = target;
150 target->state = AUTHENTICATING;
156 replay_read_line(void)
159 unsigned long new_time;
161 if (replay_line[0]) return;
163 if (!fgets(replay_line, sizeof(replay_line), replay_file)) {
164 if (feof(replay_file)) {
166 memset(replay_line, 0, sizeof(replay_line));
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);
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(×tamp);
189 if (new_time == (unsigned long)-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);
195 if (strncmp(replay_line+22, "(info) ", 7))
204 char read_line[MAXLEN];
207 /* if it's a sent line, break out to handle it */
208 if (!strncmp(replay_line+29, " ", 3))
210 if (!strncmp(replay_line+29, "W: ", 3)) {
211 log_module(MAIN_LOG, LOG_ERROR, "Expected response from services: %s", replay_line+32);
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;
223 parse_line(read_line, 0);
229 replay_write(char *text)
232 if (strncmp(replay_line+29, "W: ", 3)) {
233 log_module(MAIN_LOG, LOG_ERROR, "Unexpected output during replay: %s", text);
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);
239 log_replay(MAIN_LOG, true, text);
245 void putsock(const char *text, ...) PRINTF_LIKE(1, 2);
248 putsock(const char *text, ...)
254 if (!cManager.uplink || cManager.uplink->state == DISCONNECTED) return;
256 va_start(arg_list, text);
257 pos = vsnprintf(buffer, MAXLEN - 2, text, arg_list);
259 if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
262 log_replay(MAIN_LOG, true, buffer);
263 buffer[pos++] = '\n';
265 ioset_write(socket_io_fd, buffer, pos);
267 replay_write(buffer);
275 replay_connected = 0;
276 socket_destroyed(socket_io_fd);
278 ioset_close(socket_io_fd, 3);
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);
286 static void timed_ping_timeout(void *data);
288 /* Ping state is kept in the timeq (only one of these two can be in
289 * the queue at any given time). */
291 timed_send_ping(UNUSED_ARG(void *data))
293 irc_ping(self->name);
294 timeq_add(now + ping_timeout, timed_ping_timeout, 0);
298 timed_ping_timeout(UNUSED_ARG(void *data))
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);
307 static CMD_FUNC(cmd_pass)
309 const char *true_pass;
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
318 irc_squit(self, "Incorrect password received.", NULL);
322 cManager.uplink->state = BURSTING;
326 static CMD_FUNC(cmd_dummy)
328 /* we don't care about these messages */
332 static CMD_FUNC(cmd_error)
334 if (argv[1]) log_module(MAIN_LOG, LOG_ERROR, "Error from ircd: %s", argv[1]);
335 log_module(MAIN_LOG, LOG_ERROR, "Error received from uplink, squitting.");
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.");
347 static CMD_FUNC(cmd_stats)
353 if (!(un = GetUserH(origin)))
355 switch (argv[1][0]) {
357 unsigned long uptime;
358 uptime = now - boot_time;
359 irc_numeric(un, RPL_STATSUPTIME, ":Server Up %d days %d:%02d:%02d",
360 uptime/(24*60*60), (uptime/(60*60))%24, (uptime/60)%60, uptime%60);
361 irc_numeric(un, RPL_MAXCONNECTIONS, ":Highest connection count: %d (%d clients)",
362 self->max_clients+1, self->max_clients);
365 default: /* unrecognized/unhandled stats output */ break;
367 irc_numeric(un, 219, "%s :End of /STATS report", argv[1]);
371 static CMD_FUNC(cmd_version)
373 struct userNode *user;
374 if (!(user = GetUserH(origin))) {
375 log_module(MAIN_LOG, LOG_ERROR, "Could not find VERSION origin user %s", origin);
378 irc_numeric(user, 351, "%s.%s %s :%s", PACKAGE_TARNAME, PACKAGE_VERSION, self->name, CODENAME);
382 static CMD_FUNC(cmd_admin)
384 struct userNode *user;
385 struct string_list *slist;
387 if (!(user = GetUserH(origin))) {
388 log_module(MAIN_LOG, LOG_ERROR, "Could not find ADMIN origin user %s", origin);
391 if ((slist = conf_get_data("server/admin", RECDB_STRING_LIST)) && slist->used) {
394 irc_numeric(user, 256, ":Administrative info about %s", self->name);
395 for (i = 0; i < slist->used && i < 3; i++)
396 irc_numeric(user, 257 + i, ":%s", slist->list[i]);
398 irc_numeric(user, 423, ":No administrative info available");
404 recalc_bursts(struct server *eob_server)
407 eob_server->burst = eob_server->self_burst;
408 if (eob_server->uplink != self)
409 eob_server->burst = eob_server->burst || eob_server->uplink->burst;
410 for (nn=0; nn < eob_server->children.used; nn++)
411 recalc_bursts(eob_server->children.list[nn]);
414 static struct chanmsg_func {
416 struct userNode *service;
417 } chanmsg_funcs[256]; /* indexed by trigger character */
419 static struct allchanmsg_func {
421 struct userNode *service;
422 } allchanmsg_funcs[ALLCHANMSG_FUNCS_MAX];
424 struct privmsg_desc {
425 struct userNode *user;
427 unsigned int is_notice : 1;
428 unsigned int is_qualified : 1;
432 privmsg_chan_helper(struct chanNode *cn, void *data)
434 extern unsigned short offchannel_allowed[256];
435 struct privmsg_desc *pd = data;
437 struct chanmsg_func *cf;
440 /* Don't complain if it can't find the modeNode because the channel might
442 if ((mn = GetUserMode(cn, pd->user)))
443 mn->idle_since = now;
445 /* Never send a NOTICE to a channel to one of the services */
446 cf = &chanmsg_funcs[(unsigned char)pd->text[0]];
447 if (cf->func && !pd->is_notice
448 && (offchannel_allowed[(unsigned char)pd->text[0]]
449 || (GetUserMode(cn, cf->service) && !IsDeaf(cf->service))))
450 cf->func(pd->user, cn, pd->text+1, cf->service, pd->is_notice);
452 spamserv_channel_message(cn, pd->user, pd->text);
454 /* This catches *all* text sent to the channel that the services server sees */
455 for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
456 cf = (struct chanmsg_func *)&allchanmsg_funcs[x];
458 break; /* end of list */
460 cf->func(pd->user, cn, pd->text, cf->service, pd->is_notice);
465 privmsg_invalid(char *name, void *data)
467 struct privmsg_desc *pd = data;
471 irc_numeric(pd->user, ERR_NOSUCHNICK, "%s@%s :No such nick", name, self->name);
475 struct userNode *user;
480 part_helper(struct chanNode *cn, void *data)
482 struct part_desc *desc = data;
483 DelChannelUser(desc->user, cn, desc->text, false);
486 static CMD_FUNC(cmd_part)
488 struct part_desc desc;
492 desc.user = GetUserH(origin);
495 desc.text = (argc > 2) ? argv[argc - 1] : NULL;
496 parse_foreach(argv[1], part_helper, NULL, NULL, NULL, &desc);
501 reg_chanmsg_func(unsigned char prefix, struct userNode *service, chanmsg_func_t handler)
503 if (chanmsg_funcs[prefix].func)
504 log_module(MAIN_LOG, LOG_WARNING, "Re-registering new chanmsg handler for character `%c'.", prefix);
505 chanmsg_funcs[prefix].func = handler;
506 chanmsg_funcs[prefix].service = service;
510 reg_allchanmsg_func(struct userNode *service, chanmsg_func_t handler)
513 for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
514 if (allchanmsg_funcs[x].func)
516 allchanmsg_funcs[x].func = handler;
517 allchanmsg_funcs[x].service = service;
523 get_chanmsg_bot(unsigned char prefix)
525 return chanmsg_funcs[prefix].service;
528 static mode_change_func_t *mcf_list;
529 static unsigned int mcf_size = 0, mcf_used = 0;
532 reg_mode_change_func(mode_change_func_t handler)
534 if (mcf_used == mcf_size) {
537 mcf_list = realloc(mcf_list, mcf_size*sizeof(mode_change_func_t));
540 mcf_list = malloc(mcf_size*sizeof(mode_change_func_t));
543 mcf_list[mcf_used++] = handler;
546 static oper_func_t *of_list;
547 static unsigned int of_size = 0, of_used = 0;
550 reg_oper_func(oper_func_t handler)
552 if (of_used == of_size) {
555 of_list = realloc(of_list, of_size*sizeof(oper_func_t));
558 of_list = malloc(of_size*sizeof(oper_func_t));
561 of_list[of_used++] = handler;
565 call_oper_funcs(struct userNode *user)
570 for (n=0; (n<of_used) && !user->dead; n++)
576 struct mod_chanmode *
577 mod_chanmode_alloc(unsigned int argc)
579 struct mod_chanmode *res;
581 res = calloc(1, sizeof(*res) + (argc-1)*sizeof(res->args[0]));
583 res = calloc(1, sizeof(*res));
586 res->alloc_argc = argc;
593 struct mod_chanmode *
594 mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra)
596 struct mod_chanmode *res;
597 res = mod_chanmode_alloc(orig->argc + extra);
599 res->modes_set = orig->modes_set;
600 res->modes_clear = orig->modes_clear;
601 res->new_limit = orig->new_limit;
602 res->new_access = orig->new_access;
603 memcpy(res->new_altchan, orig->new_altchan, sizeof(res->new_altchan));
604 memcpy(res->new_key, orig->new_key, sizeof(res->new_key));
605 memcpy(res->new_upass, orig->new_upass, sizeof(res->new_upass));
606 memcpy(res->new_apass, orig->new_apass, sizeof(res->new_apass));
607 res->argc = orig->argc;
608 memcpy(res->args, orig->args, orig->argc*sizeof(orig->args[0]));
614 mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change)
619 assert(change->argc <= change->alloc_argc);
620 channel->modes = (channel->modes & ~change->modes_clear) | change->modes_set;
621 if (change->modes_set & MODE_LIMIT)
622 channel->limit = change->new_limit;
623 if (change->modes_set & MODE_ACCESS)
624 channel->access = change->new_access;
625 if (change->modes_set & MODE_KEY)
626 strcpy(channel->key, change->new_key);
627 if (change->modes_set & MODE_ALTCHAN)
628 strcpy(channel->altchan, change->new_altchan);
629 if (change->modes_set & MODE_UPASS)
630 strcpy(channel->upass, change->new_upass);
631 if (change->modes_set & MODE_APASS)
632 strcpy(channel->apass, change->new_apass);
633 for (ii = 0; ii < change->argc; ++ii) {
634 switch (change->args[ii].mode) {
636 /* If any existing ban is a subset of the new ban,
637 * silently remove it. The new ban is not allowed
638 * to be more specific than an existing ban.
640 for (jj=0; jj<channel->banlist.used; ++jj) {
641 bn = channel->banlist.list[jj];
642 if (match_ircglobs(change->args[ii].u.hostmask, bn->ban)) {
643 banList_remove(&channel->banlist, bn);
648 bn = calloc(1, sizeof(*bn));
649 safestrncpy(bn->ban, change->args[ii].u.hostmask, sizeof(bn->ban));
651 safestrncpy(bn->who, who->nick, sizeof(bn->who));
653 safestrncpy(bn->who, "<unknown>", sizeof(bn->who));
655 banList_append(&channel->banlist, bn);
657 case MODE_REMOVE|MODE_BAN:
658 for (jj=0; jj<channel->banlist.used; ++jj) {
659 bn = channel->banlist.list[jj];
660 if (strcmp(bn->ban, change->args[ii].u.hostmask))
663 banList_remove(&channel->banlist, bn);
669 case MODE_VOICE|MODE_CHANOP:
670 case MODE_REMOVE|MODE_CHANOP:
671 case MODE_REMOVE|MODE_VOICE:
672 case MODE_REMOVE|MODE_VOICE|MODE_CHANOP:
673 if (change->args[ii].mode & MODE_REMOVE)
674 change->args[ii].u.member->modes &= ~change->args[ii].mode;
676 change->args[ii].u.member->modes |= change->args[ii].mode;
679 assert(0 && "Invalid mode argument");
686 mod_chanmode_free(struct mod_chanmode *change)
692 mod_chanmode(struct userNode *who, struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags)
694 struct modeNode *member;
695 struct mod_chanmode *change;
699 if (!modes || !modes[0])
701 if (who && (member = GetUserMode(channel, who)))
702 base_oplevel = member->oplevel;
704 base_oplevel = MAXOPLEVEL;
705 if (!(change = mod_chanmode_parse(channel, modes, argc, flags, base_oplevel)))
707 if (flags & MC_ANNOUNCE)
708 mod_chanmode_announce(who, channel, change);
710 mod_chanmode_apply(who, channel, change);
711 if (flags & MC_NOTIFY)
712 for (ii = 0; ii < mcf_used; ++ii)
713 mcf_list[ii](channel, who, change);
714 mod_chanmode_free(change);
719 irc_make_chanmode(struct chanNode *chan, char *out)
721 struct mod_chanmode change;
722 mod_chanmode_init(&change);
723 change.modes_set = chan->modes;
724 change.new_limit = chan->limit;
725 change.new_access = chan->access;
726 safestrncpy(change.new_altchan, chan->altchan, sizeof(change.new_altchan));
727 safestrncpy(change.new_key, chan->key, sizeof(change.new_key));
728 safestrncpy(change.new_upass, chan->upass, sizeof(change.new_upass));
729 safestrncpy(change.new_apass, chan->apass, sizeof(change.new_apass));
730 return strlen(mod_chanmode_format(&change, out));
734 generate_hostmask(struct userNode *user, int options)
737 char *nickname, *ident, *hostname, *mask;
740 /* figure out string parts */
741 if (options & GENMASK_OMITNICK)
743 else if (options & GENMASK_USENICK)
744 nickname = user->nick;
747 if (options & GENMASK_STRICT_IDENT)
749 else if (options & GENMASK_ANY_IDENT)
751 else if (IsFakeIdent(user) && IsHiddenHost(user) && !(options & GENMASK_NO_HIDING))
752 ident = user->fakeident;
754 ident = alloca(strlen(user->ident)+2);
756 strcpy(ident+1, user->ident + ((*user->ident == '~')?1:0));
758 hostname = user->hostname;
759 if (IsFakeHost(user) && IsHiddenHost(user) && !(options & GENMASK_NO_HIDING)) {
760 hostname = user->fakehost;
761 } else if (IsHiddenHost(user) && user->handle_info && hidden_host_suffix && !(options & GENMASK_NO_HIDING)) {
762 hostname = alloca(strlen(user->handle_info->handle) + strlen(hidden_host_suffix) + 2);
763 sprintf(hostname, "%s.%s", user->handle_info->handle, hidden_host_suffix);
764 } else if (options & GENMASK_STRICT_HOST) {
765 if (options & GENMASK_BYIP)
766 hostname = (char*)irc_ntoa(&user->ip);
767 } else if ((options & GENMASK_BYIP) || irc_pton(&ip, NULL, hostname)) {
768 /* Should generate an IP-based hostmask. */
769 hostname = alloca(IRC_NTOP_MAX_SIZE);
770 hostname[IRC_NTOP_MAX_SIZE-1] = '\0';
771 if (irc_in_addr_is_ipv4(user->ip)) {
772 /* By popular acclaim, a /16 hostmask is used. */
773 sprintf(hostname, "%d.%d.*", user->ip.in6_8[12], user->ip.in6_8[13]);
774 } else if (irc_in_addr_is_ipv6(user->ip)) {
775 /* Who knows what the default mask should be? Use a /48 to start with. */
776 sprintf(hostname, "%x:%x:%x:*", ntohs(user->ip.in6[0]), ntohs(user->ip.in6[1]), ntohs(user->ip.in6[2]));
778 /* Unknown type; just copy IP directly. */
779 irc_ntop(hostname, IRC_NTOP_MAX_SIZE, &user->ip);
783 /* This heuristic could be made smarter. Is it worth the effort? */
784 for (ii=cnt=0; hostname[ii]; ii++)
785 if (hostname[ii] == '.')
787 if (cnt == 0 || cnt == 1) {
788 /* only a one- or two-level domain name; leave hostname */
789 } else if (cnt == 2) {
790 for (ii=0; user->hostname[ii] != '.'; ii++) ;
791 /* Add 3 to account for the *. and \0. */
792 hostname = alloca(strlen(user->hostname+ii)+3);
793 sprintf(hostname, "*.%s", user->hostname+ii+1);
795 for (cnt=3, ii--; cnt; ii--)
796 if (user->hostname[ii] == '.')
798 /* The loop above will overshoot the dot one character;
799 we skip forward two (the one character and the dot)
800 when printing, so we only add one for the \0. */
801 hostname = alloca(strlen(user->hostname+ii)+1);
802 sprintf(hostname, "*.%s", user->hostname+ii+2);
806 len = strlen(ident) + strlen(hostname) + 2;
808 len += strlen(nickname) + 1;
810 sprintf(mask, "%s!%s@%s", nickname, ident, hostname);
813 sprintf(mask, "%s@%s", ident, hostname);
819 IsChannelName(const char *name) {
824 for (ii=1; name[ii]; ++ii) {
825 if ((name[ii] > 0) && (name[ii] <= 32))
829 if (name[ii] == '\xa0')
836 irc_user_modes(const struct userNode *user, char modes[], size_t length)
840 for (ii = jj = 0; (jj < length) && (irc_user_mode_chars[ii] != '\0'); ++ii) {
841 if ((user->modes & (1 << ii)) && (irc_user_mode_chars[ii] != ' '))
842 modes[jj++] = irc_user_mode_chars[ii];