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.
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 unsigned long 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")))
86 log_replay(MAIN_LOG, false, buffer);
87 if (cManager.uplink->state != DISCONNECTED)
88 parse_line(buffer, 0);
93 socket_destroyed(struct io_fd *fd)
95 if (fd && fd->state != IO_CONNECTED)
96 log_module(MAIN_LOG, LOG_ERROR, "Connection to server lost.");
98 cManager.uplink->state = DISCONNECTED;
100 DelServer(self->uplink, 0, NULL);
103 void replay_event_loop(void)
105 while (!quit_services) {
106 if (!replay_connected) {
107 /* this time fudging is to get some of the logging right */
108 self->link_time = 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.");
121 create_socket_client(struct uplinkNode *target)
123 int port = target->port;
124 const char *addr = target->host;
127 return feof(replay_file) ? 0 : 1;
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);
135 log_module(MAIN_LOG, LOG_INFO, "Connecting to %s:%i...", addr, port);
137 socket_io_fd = ioset_connect(cManager.uplink->bind_addr, cManager.uplink->bind_addr_len, addr, port, 1, 0, NULL);
139 log_module(MAIN_LOG, LOG_ERROR, "Connection to uplink failed: %s (%d)", strerror(errno), errno);
140 target->state = DISCONNECTED;
144 socket_io_fd->readable_cb = uplink_readable;
145 socket_io_fd->destroy_cb = socket_destroyed;
146 socket_io_fd->line_reads = 1;
147 log_module(MAIN_LOG, LOG_INFO, "Connection to server established.");
148 cManager.uplink = target;
149 target->state = AUTHENTICATING;
155 replay_read_line(void)
158 unsigned long new_time;
160 if (replay_line[0]) return;
162 if (!fgets(replay_line, sizeof(replay_line), replay_file)) {
163 if (feof(replay_file)) {
165 memset(replay_line, 0, sizeof(replay_line));
169 if ((replay_line[0] != '[')
170 || (replay_line[3] != ':')
171 || (replay_line[6] != ':')
172 || (replay_line[9] != ' ')
173 || (replay_line[12] != '/')
174 || (replay_line[15] != '/')
175 || (replay_line[20] != ']')
176 || (replay_line[21] != ' ')) {
177 log_module(MAIN_LOG, LOG_ERROR, "Unrecognized timestamp in replay file: %s", replay_line);
180 timestamp.tm_hour = strtoul(replay_line+1, NULL, 10);
181 timestamp.tm_min = strtoul(replay_line+4, NULL, 10);
182 timestamp.tm_sec = strtoul(replay_line+7, NULL, 10);
183 timestamp.tm_mon = strtoul(replay_line+10, NULL, 10) - 1;
184 timestamp.tm_mday = strtoul(replay_line+13, NULL, 10);
185 timestamp.tm_year = strtoul(replay_line+16, NULL, 10) - 1900;
186 timestamp.tm_isdst = 0;
187 new_time = mktime(×tamp);
188 if (new_time == (unsigned long)-1) {
189 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);
194 if (strncmp(replay_line+22, "(info) ", 7))
203 char read_line[MAXLEN];
206 /* if it's a sent line, break out to handle it */
207 if (!strncmp(replay_line+29, " ", 3))
209 if (!strncmp(replay_line+29, "W: ", 3)) {
210 log_module(MAIN_LOG, LOG_ERROR, "Expected response from services: %s", replay_line+32);
216 log_replay(MAIN_LOG, false, replay_line+32);
217 safestrncpy(read_line, replay_line+32, sizeof(read_line));
218 len = strlen(read_line);
219 if (read_line[len-1] == '\n')
220 read_line[--len] = 0;
222 parse_line(read_line, 0);
228 replay_write(char *text)
231 if (strncmp(replay_line+29, "W: ", 3)) {
232 log_module(MAIN_LOG, LOG_ERROR, "Unexpected output during replay: %s", text);
235 if (strcmp(replay_line+32, text)) {
236 log_module(MAIN_LOG, LOG_ERROR, "Incorrect output during replay:\nReceived: %sExpected: %s", text, replay_line+32);
238 log_replay(MAIN_LOG, true, text);
244 void putsock(const char *text, ...) PRINTF_LIKE(1, 2);
247 putsock(const char *text, ...)
253 if (!cManager.uplink || cManager.uplink->state == DISCONNECTED) return;
255 va_start(arg_list, text);
256 pos = vsnprintf(buffer, MAXLEN - 2, text, arg_list);
258 if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
261 log_replay(MAIN_LOG, true, buffer);
262 buffer[pos++] = '\n';
264 ioset_write(socket_io_fd, buffer, pos);
266 replay_write(buffer);
274 replay_connected = 0;
275 socket_destroyed(socket_io_fd);
277 ioset_close(socket_io_fd, 3);
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);
285 static void timed_ping_timeout(void *data);
287 /* Ping state is kept in the timeq (only one of these two can be in
288 * the queue at any given time). */
290 timed_send_ping(UNUSED_ARG(void *data))
292 irc_ping(self->name);
293 timeq_add(now + ping_timeout, timed_ping_timeout, 0);
297 timed_ping_timeout(UNUSED_ARG(void *data))
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);
306 static CMD_FUNC(cmd_pass)
308 const char *true_pass;
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
317 irc_squit(self, "Incorrect password received.", NULL);
321 cManager.uplink->state = BURSTING;
325 static CMD_FUNC(cmd_dummy)
327 /* we don't care about these messages */
331 static CMD_FUNC(cmd_error)
333 if (argv[1]) log_module(MAIN_LOG, LOG_ERROR, "Error from ircd: %s", argv[1]);
334 log_module(MAIN_LOG, LOG_ERROR, "Error received from uplink, squitting.");
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.");
346 static CMD_FUNC(cmd_stats)
352 if (!(un = GetUserH(origin)))
354 switch (argv[1][0]) {
356 unsigned long uptime;
357 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);
364 default: /* unrecognized/unhandled stats output */ break;
366 irc_numeric(un, 219, "%s :End of /STATS report", argv[1]);
370 static CMD_FUNC(cmd_version)
372 struct userNode *user;
373 if (!(user = GetUserH(origin))) {
374 log_module(MAIN_LOG, LOG_ERROR, "Could not find VERSION origin user %s", origin);
377 irc_numeric(user, 351, "%s.%s %s :%s", PACKAGE_TARNAME, PACKAGE_VERSION, self->name, CODENAME);
381 static CMD_FUNC(cmd_admin)
383 struct userNode *user;
384 struct string_list *slist;
386 if (!(user = GetUserH(origin))) {
387 log_module(MAIN_LOG, LOG_ERROR, "Could not find ADMIN origin user %s", origin);
390 if ((slist = conf_get_data("server/admin", RECDB_STRING_LIST)) && slist->used) {
393 irc_numeric(user, 256, ":Administrative info about %s", self->name);
394 for (i = 0; i < slist->used && i < 3; i++)
395 irc_numeric(user, 257 + i, ":%s", slist->list[i]);
397 irc_numeric(user, 423, ":No administrative info available");
403 recalc_bursts(struct server *eob_server)
406 eob_server->burst = eob_server->self_burst;
407 if (eob_server->uplink != self)
408 eob_server->burst = eob_server->burst || eob_server->uplink->burst;
409 for (nn=0; nn < eob_server->children.used; nn++)
410 recalc_bursts(eob_server->children.list[nn]);
413 static struct chanmsg_func {
415 struct userNode *service;
416 } chanmsg_funcs[256]; /* indexed by trigger character */
418 static struct allchanmsg_func {
420 struct userNode *service;
421 } allchanmsg_funcs[ALLCHANMSG_FUNCS_MAX];
423 struct privmsg_desc {
424 struct userNode *user;
426 unsigned int is_notice : 1;
427 unsigned int is_qualified : 1;
431 privmsg_chan_helper(struct chanNode *cn, void *data)
433 extern unsigned short offchannel_allowed[256];
434 struct privmsg_desc *pd = data;
436 struct chanmsg_func *cf;
439 /* Don't complain if it can't find the modeNode because the channel might
441 if ((mn = GetUserMode(cn, pd->user)))
442 mn->idle_since = now;
444 /* Never send a NOTICE to a channel to one of the services */
445 cf = &chanmsg_funcs[(unsigned char)pd->text[0]];
446 if (cf->func && !pd->is_notice
447 && (offchannel_allowed[(unsigned char)pd->text[0]]
448 || (GetUserMode(cn, cf->service) && !IsDeaf(cf->service))))
449 cf->func(pd->user, cn, pd->text+1, cf->service, pd->is_notice);
451 /* This catches *all* text sent to the channel that the services server sees */
452 for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
453 cf = (struct chanmsg_func *)&allchanmsg_funcs[x];
455 break; /* end of list */
457 cf->func(pd->user, cn, pd->text, cf->service, pd->is_notice);
462 privmsg_invalid(char *name, void *data)
464 struct privmsg_desc *pd = data;
468 irc_numeric(pd->user, ERR_NOSUCHNICK, "%s@%s :No such nick", name, self->name);
472 struct userNode *user;
477 part_helper(struct chanNode *cn, void *data)
479 struct part_desc *desc = data;
480 DelChannelUser(desc->user, cn, desc->text, false);
483 static CMD_FUNC(cmd_part)
485 struct part_desc desc;
489 desc.user = GetUserH(origin);
492 desc.text = (argc > 2) ? argv[argc - 1] : NULL;
493 parse_foreach(argv[1], part_helper, NULL, NULL, NULL, &desc);
498 reg_chanmsg_func(unsigned char prefix, struct userNode *service, chanmsg_func_t handler)
500 if (chanmsg_funcs[prefix].func)
501 log_module(MAIN_LOG, LOG_WARNING, "Re-registering new chanmsg handler for character `%c'.", prefix);
502 chanmsg_funcs[prefix].func = handler;
503 chanmsg_funcs[prefix].service = service;
507 reg_allchanmsg_func(struct userNode *service, chanmsg_func_t handler)
510 for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
511 if (allchanmsg_funcs[x].func)
513 allchanmsg_funcs[x].func = handler;
514 allchanmsg_funcs[x].service = service;
520 get_chanmsg_bot(unsigned char prefix)
522 return chanmsg_funcs[prefix].service;
525 static mode_change_func_t *mcf_list;
526 static unsigned int mcf_size = 0, mcf_used = 0;
529 reg_mode_change_func(mode_change_func_t handler)
531 if (mcf_used == mcf_size) {
534 mcf_list = realloc(mcf_list, mcf_size*sizeof(mode_change_func_t));
537 mcf_list = malloc(mcf_size*sizeof(mode_change_func_t));
540 mcf_list[mcf_used++] = handler;
543 static oper_func_t *of_list;
544 static unsigned int of_size = 0, of_used = 0;
547 reg_oper_func(oper_func_t handler)
549 if (of_used == of_size) {
552 of_list = realloc(of_list, of_size*sizeof(oper_func_t));
555 of_list = malloc(of_size*sizeof(oper_func_t));
558 of_list[of_used++] = handler;
562 call_oper_funcs(struct userNode *user)
567 for (n=0; (n<of_used) && !user->dead; n++)
573 static xquery_func_t *xqf_list;
574 static unsigned int xqf_size = 0, xqf_used = 0;
577 reg_xquery_func(xquery_func_t handler)
579 if (xqf_used == xqf_size) {
582 xqf_list = realloc(xqf_list, xqf_size*sizeof(xquery_func_t));
585 xqf_list = malloc(xqf_size*sizeof(xquery_func_t));
588 xqf_list[xqf_used++] = handler;
592 call_xquery_funcs(struct server *source, const char routing[], const char query[])
595 for (n=0; n < xqf_used; n++)
597 xqf_list[n](source, routing, query);
601 struct mod_chanmode *
602 mod_chanmode_alloc(unsigned int argc)
604 struct mod_chanmode *res;
606 res = calloc(1, sizeof(*res) + (argc-1)*sizeof(res->args[0]));
608 res = calloc(1, sizeof(*res));
611 res->alloc_argc = argc;
618 struct mod_chanmode *
619 mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra)
621 struct mod_chanmode *res;
622 res = mod_chanmode_alloc(orig->argc + extra);
624 res->modes_set = orig->modes_set;
625 res->modes_clear = orig->modes_clear;
626 res->new_limit = orig->new_limit;
627 memcpy(res->new_key, orig->new_key, sizeof(res->new_key));
628 memcpy(res->new_upass, orig->new_upass, sizeof(res->new_upass));
629 memcpy(res->new_apass, orig->new_apass, sizeof(res->new_apass));
630 res->argc = orig->argc;
631 memcpy(res->args, orig->args, orig->argc*sizeof(orig->args[0]));
637 mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change)
642 assert(change->argc <= change->alloc_argc);
643 channel->modes = (channel->modes & ~change->modes_clear) | change->modes_set;
644 if (change->modes_set & MODE_LIMIT)
645 channel->limit = change->new_limit;
646 if (change->modes_set & MODE_KEY)
647 strcpy(channel->key, change->new_key);
648 if (change->modes_set & MODE_UPASS)
649 strcpy(channel->upass, change->new_upass);
650 if (change->modes_set & MODE_APASS)
651 strcpy(channel->apass, change->new_apass);
652 for (ii = 0; ii < change->argc; ++ii) {
653 switch (change->args[ii].mode) {
655 /* If any existing ban is a subset of the new ban,
656 * silently remove it. The new ban is not allowed
657 * to be more specific than an existing ban.
659 for (jj=0; jj<channel->banlist.used; ++jj) {
660 bn = channel->banlist.list[jj];
661 if (match_ircglobs(change->args[ii].u.hostmask, bn->ban)) {
662 banList_remove(&channel->banlist, bn);
667 bn = calloc(1, sizeof(*bn));
668 safestrncpy(bn->ban, change->args[ii].u.hostmask, sizeof(bn->ban));
670 safestrncpy(bn->who, who->nick, sizeof(bn->who));
672 safestrncpy(bn->who, "<unknown>", sizeof(bn->who));
674 banList_append(&channel->banlist, bn);
676 case MODE_REMOVE|MODE_BAN:
677 for (jj=0; jj<channel->banlist.used; ++jj) {
678 bn = channel->banlist.list[jj];
679 if (strcmp(bn->ban, change->args[ii].u.hostmask))
682 banList_remove(&channel->banlist, bn);
688 case MODE_VOICE|MODE_CHANOP:
689 case MODE_REMOVE|MODE_CHANOP:
690 case MODE_REMOVE|MODE_VOICE:
691 case MODE_REMOVE|MODE_VOICE|MODE_CHANOP:
692 if (change->args[ii].mode & MODE_REMOVE)
693 change->args[ii].u.member->modes &= ~change->args[ii].mode;
695 change->args[ii].u.member->modes |= change->args[ii].mode;
698 assert(0 && "Invalid mode argument");
705 mod_chanmode_free(struct mod_chanmode *change)
711 mod_chanmode(struct userNode *who, struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags)
713 struct modeNode *member;
714 struct mod_chanmode *change;
718 if (!modes || !modes[0])
720 if (who && (member = GetUserMode(channel, who)))
721 base_oplevel = member->oplevel;
723 base_oplevel = MAXOPLEVEL;
724 if (!(change = mod_chanmode_parse(channel, modes, argc, flags, base_oplevel)))
726 if (flags & MC_ANNOUNCE)
727 mod_chanmode_announce(who, channel, change);
729 mod_chanmode_apply(who, channel, change);
730 if (flags & MC_NOTIFY)
731 for (ii = 0; ii < mcf_used; ++ii)
732 mcf_list[ii](channel, who, change);
733 mod_chanmode_free(change);
738 irc_make_chanmode(struct chanNode *chan, char *out)
740 struct mod_chanmode change;
741 mod_chanmode_init(&change);
742 change.modes_set = chan->modes;
743 change.new_limit = chan->limit;
744 safestrncpy(change.new_key, chan->key, sizeof(change.new_key));
745 safestrncpy(change.new_upass, chan->upass, sizeof(change.new_upass));
746 safestrncpy(change.new_apass, chan->apass, sizeof(change.new_apass));
747 return strlen(mod_chanmode_format(&change, out));
751 generate_hostmask(struct userNode *user, int options)
754 char *nickname, *ident, *hostname, *mask;
757 /* figure out string parts */
758 if (options & GENMASK_OMITNICK)
760 else if (options & GENMASK_USENICK)
761 nickname = user->nick;
764 if (options & GENMASK_STRICT_IDENT)
766 else if (options & GENMASK_ANY_IDENT)
768 else if (IsFakeIdent(user) && IsHiddenHost(user) && !(options & GENMASK_NO_HIDING))
769 ident = user->fakeident;
771 ident = alloca(strlen(user->ident)+2);
773 strcpy(ident+1, user->ident + ((*user->ident == '~')?1:0));
775 hostname = user->hostname;
776 if (IsFakeHost(user) && IsHiddenHost(user) && !(options & GENMASK_NO_HIDING)) {
777 hostname = user->fakehost;
778 } else if (IsHiddenHost(user) && user->handle_info && hidden_host_suffix && !(options & GENMASK_NO_HIDING)) {
779 hostname = alloca(strlen(user->handle_info->handle) + strlen(hidden_host_suffix) + 2);
780 sprintf(hostname, "%s.%s", user->handle_info->handle, hidden_host_suffix);
781 } else if (options & GENMASK_STRICT_HOST) {
782 if (options & GENMASK_BYIP)
783 hostname = (char*)irc_ntoa(&user->ip);
784 } else if ((options & GENMASK_BYIP) || irc_pton(&ip, NULL, hostname)) {
785 /* Should generate an IP-based hostmask. */
786 hostname = alloca(IRC_NTOP_MAX_SIZE);
787 hostname[IRC_NTOP_MAX_SIZE-1] = '\0';
788 if (irc_in_addr_is_ipv4(user->ip)) {
789 /* By popular acclaim, a /16 hostmask is used. */
790 sprintf(hostname, "%d.%d.*", user->ip.in6_8[12], user->ip.in6_8[13]);
791 } else if (irc_in_addr_is_ipv6(user->ip)) {
792 /* Who knows what the default mask should be? Use a /48 to start with. */
793 sprintf(hostname, "%x:%x:%x:*", ntohs(user->ip.in6[0]), ntohs(user->ip.in6[1]), ntohs(user->ip.in6[2]));
795 /* Unknown type; just copy IP directly. */
796 irc_ntop(hostname, IRC_NTOP_MAX_SIZE, &user->ip);
800 /* This heuristic could be made smarter. Is it worth the effort? */
801 for (ii=cnt=0; hostname[ii]; ii++)
802 if (hostname[ii] == '.')
804 if (cnt == 0 || cnt == 1) {
805 /* only a one- or two-level domain name; leave hostname */
806 } else if (cnt == 2) {
807 for (ii=0; user->hostname[ii] != '.'; ii++) ;
808 /* Add 3 to account for the *. and \0. */
809 hostname = alloca(strlen(user->hostname+ii)+3);
810 sprintf(hostname, "*.%s", user->hostname+ii+1);
812 for (cnt=3, ii--; cnt; ii--)
813 if (user->hostname[ii] == '.')
815 /* The loop above will overshoot the dot one character;
816 we skip forward two (the one character and the dot)
817 when printing, so we only add one for the \0. */
818 hostname = alloca(strlen(user->hostname+ii)+1);
819 sprintf(hostname, "*.%s", user->hostname+ii+2);
823 len = strlen(ident) + strlen(hostname) + 2;
825 len += strlen(nickname) + 1;
827 sprintf(mask, "%s!%s@%s", nickname, ident, hostname);
830 sprintf(mask, "%s@%s", ident, hostname);
836 IsChannelName(const char *name) {
841 for (ii=1; name[ii]; ++ii) {
842 if ((name[ii] > 0) && (name[ii] <= 32))
846 if (name[ii] == '\xa0')
853 irc_user_modes(const struct userNode *user, char modes[], size_t length)
857 for (ii = jj = 0; (jj < length) && (irc_user_mode_chars[ii] != '\0'); ++ii) {
858 if ((user->modes & (1 << ii)) && (irc_user_mode_chars[ii] != ' '))
859 modes[jj++] = irc_user_mode_chars[ii];