Mercurial > irccd
view lib/irccd/server.c @ 981:e4fc051e2d94
irccd: add brand new Irccd.Hook API
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 09 Feb 2021 21:15:51 +0100 |
parents | 3afd375f308b |
children | 2e4b29ab8e9c |
line wrap: on
line source
/* * server.c -- an IRC server * * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <compat.h> #include <assert.h> #include <errno.h> #include <err.h> #include <poll.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "config.h" #if defined(IRCCD_WITH_SSL) # include <openssl/err.h> #endif #include "channel.h" #include "event.h" #include "log.h" #include "server.h" #include "util.h" #define DELAY 30 /* Seconds to wait before reconnecting. */ #define TIMEOUT 1800 /* Seconds before marking a server as dead. */ static inline const char * statename(enum irc_server_state st) { switch (st) { case IRC_SERVER_STATE_NONE: return "none"; case IRC_SERVER_STATE_DISCONNECTED: return "disconnected"; case IRC_SERVER_STATE_CONNECTING: return "connecting"; case IRC_SERVER_STATE_CONNECTED: return "connected"; case IRC_SERVER_STATE_WAITING: return "waiting"; default: return "unknown"; } } static inline void clear_channels(struct irc_server *s, int free) { struct irc_channel *c, *tmp; LIST_FOREACH_SAFE(c, &s->channels, link, tmp) { if (free) irc_channel_finish(c); else irc_channel_clear(c); } if (free) LIST_INIT(&s->channels); } static inline void clear_server(struct irc_server *s) { irc_conn_finish(&s->conn); free(s->bufwhois.nickname); free(s->bufwhois.username); free(s->bufwhois.realname); free(s->bufwhois.hostname); free(s->bufwhois.channels); memset(&s->params, 0, sizeof (s->params)); memset(&s->bufwhois, 0, sizeof (s->bufwhois)); } static inline int is_self(const struct irc_server *s, const char *nick) { return strncmp(s->ident.nickname, nick, strlen(s->ident.nickname)) == 0; } static void add_nick(const struct irc_server *s, struct irc_channel *ch, const char *nick) { char mode = 0, prefix = 0; irc_server_strip(s, &nick, &mode, &prefix); irc_channel_add(ch, nick, mode, prefix); } static struct irc_channel * add_channel(struct irc_server *s, const char *name, const char *password, int joined) { struct irc_channel *ch; if ((ch = irc_server_find(s, name))) { ch->joined = joined; return ch; } ch = irc_util_calloc(1, sizeof (*ch)); ch->joined = joined; strlcpy(ch->name, name, sizeof (ch->name)); if (password) strlcpy(ch->password, password, sizeof (ch->password)); LIST_INIT(&ch->users); LIST_INSERT_HEAD(&s->channels, ch, link); return ch; } static void remove_channel(struct irc_channel *ch) { LIST_REMOVE(ch, link); irc_channel_finish(ch); } static int is_ctcp(const char *line) { size_t length; if (!line) return 0; if ((length = strlen(line)) < 2) return 0; return line[0] == 0x1 && line[length - 1] == 0x1; } static char * ctcp(char *line) { /* Skip first \001. */ if (*line == '\001') line++; /* Remove last \001. */ line[strcspn(line, "\001")] = '\0'; if (strncmp(line, "ACTION ", 7) == 0) line += 7; return line; } static void read_support_prefix(struct irc_server *s, const char *value) { char modes[IRC_UTIL_SIZE(s->params.prefixes) + 1] = {0}; char tokens[IRC_UTIL_SIZE(s->params.prefixes) + 1] = {0}; char fmt[32] = {0}; snprintf(fmt, sizeof (fmt), "(%%%zu[^)])%%%zus", sizeof (modes) - 1, sizeof (tokens) - 1); if (sscanf(value, fmt, modes, tokens) == 2) { char *pm = modes; char *tk = tokens; for (size_t i = 0; i < IRC_UTIL_SIZE(s->params.prefixes) && *pm && *tk; ++i) { s->params.prefixes[i].mode = *pm++; s->params.prefixes[i].token = *tk++; } } } static void read_support_chantypes(struct irc_server *s, const char *value) { strlcpy(s->params.chantypes, value, sizeof (s->params.chantypes)); } static void fail(struct irc_server *s) { clear_channels(s, 0); clear_server(s); if (s->flags & IRC_SERVER_FLAGS_AUTO_RECO) { irc_log_debug("server %s: state %s -> %s", s->name, statename(s->state), statename(IRC_SERVER_STATE_WAITING)); irc_log_info("server %s: waiting %u seconds before reconnecting", s->name, DELAY); s->state = IRC_SERVER_STATE_WAITING; } else { irc_log_debug("server %s: state %s -> %s", s->name, statename(s->state), statename(IRC_SERVER_STATE_DISCONNECTED)); s->state = IRC_SERVER_STATE_DISCONNECTED; } /* Time point when we lose signal from the server. */ s->lost_tp = time(NULL); } static void handle_connect(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)msg; struct irc_channel *ch; /* Now join all channels that were requested. */ LIST_FOREACH(ch, &s->channels, link) irc_server_join(s, ch->name, ch->password); irc_log_debug("server %s: state %s -> %s", s->name, statename(s->state), statename(IRC_SERVER_STATE_CONNECTED)); s->state = IRC_SERVER_STATE_CONNECTED; ev->type = IRC_EVENT_CONNECT; irc_log_info("server %s: connection complete", s->name); } static void handle_disconnect(struct irc_server *s, struct irc_event *ev) { ev->type = IRC_EVENT_DISCONNECT; ev->server = s; fail(s); irc_log_info("server %s: connection lost", s->name); } static void handle_support(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)ev; char key[64]; char value[64]; for (size_t i = 0; i < IRC_UTIL_SIZE(msg->args) && msg->args[i]; ++i) { if (sscanf(msg->args[i], "%63[^=]=%63s", key, value) != 2) continue; if (strcmp(key, "PREFIX") == 0) { read_support_prefix(s, value); irc_log_info("server %s: prefixes: %s", s->name, value); } else if (strcmp(key, "CHANTYPES") == 0) { read_support_chantypes(s, value); irc_log_info("server %s: channel types: %s", s->name, value); } else if (strcmp(key, "CHANNELLEN") == 0) { s->params.chanlen = atoi(value); irc_log_info("server %s: channel name limit: %u", s->name, s->params.chanlen); } else if (strcmp(key, "NICKLEN") == 0) { s->params.nicklen = atoi(value); irc_log_info("server %s: nickname limit: %u", s->name, s->params.nicklen); } else if (strcmp(key, "TOPICLEN") == 0) { s->params.topiclen = atoi(value); irc_log_info("server %s: topic limit: %u", s->name, s->params.topiclen); } else if (strcmp(key, "AWAYLEN") == 0) { s->params.awaylen = atoi(value); irc_log_info("server %s: away message limit: %u", s->name, s->params.awaylen); } else if (strcmp(key, "KICKLEN") == 0) { s->params.kicklen = atoi(value); irc_log_info("server %s: kick reason limit: %u", s->name, s->params.kicklen); } else if (strcmp(key, "CHARSET") == 0) { strlcpy(s->params.charset, value, sizeof (s->params.charset)); irc_log_info("server %s: charset: %s", s->name, s->params.charset); } else if (strcmp(key, "CASEMAPPING") == 0) { strlcpy(s->params.casemapping, value, sizeof (s->params.casemapping)); irc_log_info("server %s: case mapping: %s", s->name, s->params.casemapping); } } } static void handle_invite(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { ev->type = IRC_EVENT_INVITE; ev->invite.origin = strdup(msg->args[0]); ev->invite.channel = strdup(msg->args[1]); if (s->flags & IRC_SERVER_FLAGS_JOIN_INVITE) { irc_server_join(s, ev->invite.channel, NULL); irc_log_info("server %s: joining %s on invite", s->name, ev->invite.channel); } } static void handle_join(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { ev->type = IRC_EVENT_JOIN; ev->join.origin = strdup(msg->prefix); ev->join.channel = strdup(msg->args[0]); add_channel(s, ev->join.channel, NULL, 1); if (is_self(s, ev->join.origin)) irc_log_info("server %s: joined channel %s", s->name, ev->join.channel); } static void handle_kick(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { ev->type = IRC_EVENT_KICK; ev->kick.origin = strdup(msg->prefix); ev->kick.channel = strdup(msg->args[0]); ev->kick.target = strdup(msg->args[1]); ev->kick.reason = msg->args[2] ? strdup(msg->args[2]) : NULL; struct irc_channel *ch = add_channel(s, ev->kick.channel, NULL, 1); /* * If the bot was kicked itself mark the channel as not joined and * rejoin it automatically if the option is set. */ if (is_self(s, ev->kick.target)) { ch->joined = 0; irc_channel_clear(ch); if (s->flags & IRC_SERVER_FLAGS_AUTO_REJOIN) { irc_server_join(s, ch->name, ch->password); irc_log_info("server %s: rejoining %s after kick", s->name, ch->name); } } else irc_channel_remove(ch, ev->kick.target); } static void handle_mode(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)s; (void)ev; (void)msg; ev->type = IRC_EVENT_MODE; ev->mode.origin = strdup(msg->prefix); ev->mode.channel = strdup(msg->args[0]); ev->mode.mode = strdup(msg->args[1]); ev->mode.limit = msg->args[2] ? strdup(msg->args[2]) : NULL; ev->mode.user = msg->args[3] ? strdup(msg->args[3]) : NULL; ev->mode.mask = msg->args[4] ? strdup(msg->args[4]) : NULL; /* TODO: update nickname modes. */ } static void handle_part(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { struct irc_channel *ch; ev->type = IRC_EVENT_PART; ev->part.origin = strdup(msg->prefix); ev->part.channel = strdup(msg->args[0]); ev->part.reason = msg->args[1] ? strdup(msg->args[1]) : NULL; ch = add_channel(s, ev->part.channel, NULL, 1); if (is_self(s, ev->part.origin)) { remove_channel(ch); irc_log_info("server %s: leaving channel %s", s->name, ev->part.channel); } else irc_channel_remove(ch, ev->part.origin); } static void handle_msg(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)s; ev->message.origin = strdup(msg->prefix); ev->message.channel = strdup(msg->args[0]); /* * Detect CTCP commands which are PRIVMSG with a special boundaries. * * Example: * PRIVMSG jean :\001ACTION I'm eating\001. */ if (is_ctcp(msg->args[1])) { ev->type = IRC_EVENT_ME; ev->message.message = strdup(ctcp(msg->args[1])); } else { ev->type = IRC_EVENT_MESSAGE; ev->message.message = strdup(msg->args[1]); } } static void handle_nick(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { ev->type = IRC_EVENT_NICK; ev->nick.origin = strdup(msg->prefix); ev->nick.nickname = strdup(msg->args[0]); /* Update nickname if it is myself. */ if (is_self(s, ev->nick.origin) == 0) { irc_log_info("server %s: nick change %s -> %s", s->name, s->ident.nickname, ev->nick.nickname); strlcpy(s->ident.nickname, ev->nick.nickname, sizeof (s->ident.nickname)); } } static void handle_notice(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)s; ev->type = IRC_EVENT_NOTICE; ev->notice.origin = strdup(msg->prefix); ev->notice.channel = strdup(msg->args[0]); ev->notice.notice = strdup(msg->args[1]); } static void handle_topic(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)s; ev->type = IRC_EVENT_TOPIC; ev->topic.origin = strdup(msg->prefix); ev->topic.channel = strdup(msg->args[0]); ev->topic.topic = strdup(msg->args[1]); } static void handle_ping(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)ev; (void)msg; irc_server_send(s, "PONG %s", msg->args[1]); } static void handle_names(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)s; (void)ev; (void)msg; struct irc_channel *ch; char *p, *token; ch = add_channel(s, msg->args[2], NULL, 1); /* Track existing nicknames into the given channel. */ for (p = msg->args[3]; (token = strtok_r(p, " ", &p)); ) if (strlen(token) > 0) add_nick(s, ch, token); } static void handle_endofnames(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)s; (void)ev; (void)msg; FILE *fp; size_t length; const struct irc_channel *ch; const struct irc_channel_user *u; ev->type = IRC_EVENT_NAMES; ev->names.channel = strdup(msg->args[1]); /* Construct a string list for every user in the channel. */ ch = irc_server_find(s, ev->names.channel); if (!(fp = open_memstream(&ev->names.names, &length))) err(1, "open_memstream"); LIST_FOREACH(u, &ch->users, link) { if (u->symbol) fprintf(fp, "%c", u->symbol); fprintf(fp, "%s", u->nickname); if (LIST_NEXT(u, link)) fputc(' ', fp); } fclose(fp); } static void handle_whoisuser(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)s; (void)ev; (void)msg; s->bufwhois.nickname = strdup(msg->args[1]); s->bufwhois.username = strdup(msg->args[2]); s->bufwhois.hostname = strdup(msg->args[3]); s->bufwhois.realname = strdup(msg->args[5]); } static void handle_whoischannels(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)ev; size_t curlen, reqlen; curlen = s->bufwhois.channels ? strlen(s->bufwhois.channels) : 0; reqlen = strlen(msg->args[2]); /* * If there is already something, add a space at the end of the current * buffer. */ if (curlen > 0) reqlen++; /* Now, don't forget */ s->bufwhois.channels = irc_util_realloc(s->bufwhois.channels, reqlen + 1); if (curlen > 0) { strcat(s->bufwhois.channels, " "); strcat(s->bufwhois.channels, msg->args[2]); } else strcpy(s->bufwhois.channels, msg->args[2]); } static void handle_endofwhois(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { (void)msg; ev->type = IRC_EVENT_WHOIS; ev->whois = s->bufwhois; memset(&s->bufwhois, 0, sizeof (s->bufwhois)); } static const struct handler { const char *command; void (*handle)(struct irc_server *, struct irc_event *, struct irc_conn_msg *); } handlers[] = { /* Must be kept ordered. */ { "001", handle_connect }, { "005", handle_support }, { "311", handle_whoisuser }, { "318", handle_endofwhois }, { "319", handle_whoischannels }, { "353", handle_names }, { "366", handle_endofnames }, { "INVITE", handle_invite }, { "JOIN", handle_join }, { "KICK", handle_kick }, { "MODE", handle_mode }, { "NICK", handle_nick }, { "NOTICE", handle_notice }, { "PART", handle_part }, { "PING", handle_ping }, { "PRIVMSG", handle_msg }, { "TOPIC", handle_topic } }; static int cmp_handler(const char *name, const struct handler *handler) { return strcmp(name, handler->command); } static inline struct handler * find_handler(const char *name) { return bsearch(name, handlers, IRC_UTIL_SIZE(handlers), sizeof (struct handler), (irc_cmp)(cmp_handler)); } static void handle(struct irc_server *s, struct irc_event *ev, struct irc_conn_msg *msg) { const struct handler *h; /* Update last message time to detect non-notified disconnection. */ s->last_tp = time(NULL); if (!(h = find_handler(msg->cmd))) return; memset(ev, 0, sizeof (*ev)); ev->server = s; h->handle(s, ev, msg); } static void auth(struct irc_server *s) { irc_log_debug("server %s: state %s -> %s", s->name, statename(s->state), statename(IRC_SERVER_STATE_CONNECTED)); s->state = IRC_SERVER_STATE_CONNECTED; if (s->ident.password[0]) irc_server_send(s, "PASS %s", s->ident.password); irc_server_send(s, "USER %s %s %s :%s", s->ident.username, s->ident.username, s->ident.username, s->ident.realname); irc_server_send(s, "NICK %s", s->ident.nickname); } struct irc_server * irc_server_new(const char *name, const char *nickname, const char *username, const char *realname, const char *hostname, unsigned int port) { assert(name); assert(nickname); assert(username); assert(realname); assert(hostname); struct irc_server *s; /* Connection. */ s = irc_util_calloc(1, sizeof (*s)); s->conn.port = port; strlcpy(s->conn.hostname, hostname, sizeof (s->conn.hostname)); /* Identity. */ strlcpy(s->ident.nickname, nickname, sizeof (s->ident.nickname)); strlcpy(s->ident.username, username, sizeof (s->ident.username)); strlcpy(s->ident.realname, realname, sizeof (s->ident.realname)); /* Server itself. */ strlcpy(s->name, name, sizeof (s->name)); LIST_INIT(&s->channels); /* Default options. */ strlcpy(s->commandchar, "!", sizeof (s->commandchar)); return s; } void irc_server_connect(struct irc_server *s) { assert(s); if (s->flags & IRC_SERVER_FLAGS_SSL) s->conn.flags |= IRC_CONN_SSL; if (irc_conn_connect(&s->conn) < 0) fail(s); else { irc_log_debug("server %s: state %s -> %s", s->name, statename(s->state), statename(IRC_SERVER_STATE_CONNECTING)); s->state = IRC_SERVER_STATE_CONNECTING; } /* * Assume the last time we received a message was now, so that * irc_server_flush don't think the server is already dead while we * didn't have any answer from it. */ s->last_tp = time(NULL); } void irc_server_disconnect(struct irc_server *s) { assert(s); irc_log_debug("server %s: state %s -> %s", s->name, statename(s->state), statename(IRC_SERVER_STATE_DISCONNECTED)); s->state = IRC_SERVER_STATE_DISCONNECTED; clear_channels(s, 0); clear_server(s); } void irc_server_prepare(const struct irc_server *s, struct pollfd *pfd) { assert(s); assert(pfd); irc_conn_prepare(&s->conn, pfd); } void irc_server_flush(struct irc_server *s, const struct pollfd *pfd) { assert(s); assert(pfd); switch (s->state) { case IRC_SERVER_STATE_WAITING: if (difftime(time(NULL), s->lost_tp) >= DELAY) irc_server_connect(s); break; case IRC_SERVER_STATE_CONNECTED: if (difftime(time(NULL), s->last_tp) >= TIMEOUT) { irc_log_warn("server %s: no message in more than %u seconds", s->name, TIMEOUT); fail(s); } else if (irc_conn_flush(&s->conn, pfd) < 0) { irc_log_warn("server %s: %s", s->name, strerror(errno)); return fail(s); } break; case IRC_SERVER_STATE_CONNECTING: /* * Now the conn object is ready which means the server has * to authenticate. */ auth(s); break; default: break; } } int irc_server_poll(struct irc_server *s, struct irc_event *ev) { assert(s); assert(ev); struct irc_conn_msg msg = {0}; /* * When the server gets disconnected, the state changes to * IRC_SERVER_STATE_DISCONNECTED which notifies the caller with the * appropriate event. Then to avoid returning this same event each time * this function is called again, we immediately change the state to * something else. */ if (s->state == IRC_SERVER_STATE_DISCONNECTED) { irc_log_debug("server %s: state %s -> %s", s->name, statename(s->state), statename(IRC_SERVER_STATE_NONE)); handle_disconnect(s, ev); s->state = IRC_SERVER_STATE_NONE; return 1; } if (irc_conn_poll(&s->conn, &msg)) return handle(s, ev, &msg), 1; return 0; } struct irc_channel * irc_server_find(struct irc_server *s, const char *name) { assert(s); assert(name); struct irc_channel *ch; LIST_FOREACH(ch, &s->channels, link) if (strcmp(ch->name, name) == 0) return ch; return NULL; } int irc_server_send(struct irc_server *s, const char *fmt, ...) { assert(s); assert(fmt); char buf[IRC_BUF_LEN]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof (buf), fmt, ap); va_end(ap); return irc_conn_send(&s->conn, buf); } int irc_server_invite(struct irc_server *s, const char *channel, const char *target) { assert(s); assert(channel); assert(target); return irc_server_send(s, "INVITE %s %s", target, channel); } int irc_server_join(struct irc_server *s, const char *name, const char *pass) { assert(s); assert(name); struct irc_channel *ch; int ret = 1; /* * Search if there is already a channel pending or joined. If the * server is connected we send a join command otherwise we put it there * and wait for connection. */ if (!(ch = irc_server_find(s, name))) ch = add_channel(s, name, pass, 0); if (!ch->joined && s->state == IRC_SERVER_STATE_CONNECTED) { if (pass) ret = irc_server_send(s, "JOIN %s %s", name, pass); else ret = irc_server_send(s, "JOIN %s", name); } return ret; } int irc_server_kick(struct irc_server *s, const char *channel, const char *target, const char *reason) { assert(s); assert(channel); assert(target); int ret; if (reason) ret = irc_server_send(s, "KICK %s %s :%s", channel, target, reason); else ret = irc_server_send(s, "KICK %s %s", channel, target); return ret; } int irc_server_part(struct irc_server *s, const char *channel, const char *reason) { assert(s); assert(channel); int ret; if (reason && strlen(reason) > 0) ret = irc_server_send(s, "PART %s :%s", channel, reason); else ret = irc_server_send(s, "PART %s", channel); return ret; } int irc_server_topic(struct irc_server *s, const char *channel, const char *topic) { assert(s); assert(channel); assert(topic); return irc_server_send(s, "TOPIC %s :%s", channel, topic); } int irc_server_message(struct irc_server *s, const char *chan, const char *msg) { assert(s); assert(chan); assert(msg); return irc_server_send(s, "PRIVMSG %s :%s", chan, msg); } int irc_server_me(struct irc_server *s, const char *chan, const char *message) { assert(s); assert(chan); assert(message); return irc_server_send(s, "PRIVMSG %s :\001ACTION %s\001", chan, message); } int irc_server_mode(struct irc_server *s, const char *channel, const char *mode, const char *limit, const char *user, const char *mask) { assert(s); assert(channel); assert(mode); return irc_server_send(s, "MODE %s %s %s %s %s", channel, mode, limit ? limit : "", user ? user : "", mask ? mask : ""); } int irc_server_names(struct irc_server *s, const char *channel) { return irc_server_send(s, "NAMES %s", channel); } int irc_server_nick(struct irc_server *s, const char *nick) { assert(s); assert(nick); if (s->state <= IRC_SERVER_STATE_DISCONNECTED) { strlcpy(s->ident.nickname, nick, sizeof (s->ident.nickname)); return 1; } return irc_server_send(s, "NICK %s", nick); } int irc_server_notice(struct irc_server *s, const char *channel, const char *message) { assert(s); assert(channel); assert(message); return irc_server_send(s, "NOTICE %s: %s", channel, message); } int irc_server_whois(struct irc_server *s, const char *target) { assert(s); assert(target); return irc_server_send(s, "WHOIS %s", target); } void irc_server_strip(const struct irc_server *s, const char **nick, char *mode, char *prefix) { assert(s); assert(*nick); if (mode) *mode = 0; if (prefix) *prefix = 0; for (size_t i = 0; i < IRC_UTIL_SIZE(s->params.prefixes); ++i) { if (**nick == s->params.prefixes[i].token) { *mode = s->params.prefixes[i].mode; *prefix = s->params.prefixes[i].token; *nick += 1; break; } } } void irc_server_split(const char *prefix, struct irc_server_user *user) { assert(prefix); assert(user); char fmt[128]; memset(user, 0, sizeof (*user)); snprintf(fmt, sizeof (fmt), "%%%zu[^!]!%%%zu[^@]@%%%zus", sizeof (user->nickname) - 1, sizeof (user->username) - 1, sizeof (user->host) - 1); sscanf(prefix, fmt, user->nickname, user->username, user->host); } void irc_server_incref(struct irc_server *s) { assert(s); s->refc++; } void irc_server_decref(struct irc_server *s) { assert(s); assert(s->refc >= 1); if (--s->refc == 0) { clear_channels(s, 1); free(s); } }