/* * Unreal Internet Relay Chat Daemon, src/send.c * Copyright (C) 1990 Jarkko Oikarinen and * University of Oulu, Computing Center * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* send.c 2.32 2/28/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */ /** @file * @brief The sending functions to users, channels, servers. */ #include "unrealircd.h" /* Some forward declarions are needed */ void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl); void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl) __attribute__((format(printf,4,0))); static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, const char *pattern, va_list vl) __attribute__((format(printf,4,0))); #define ADD_CRLF(buf, len) { if (len > 510) len = 510; \ buf[len++] = '\r'; buf[len++] = '\n'; buf[len] = '\0'; } while(0) /* These are two local (static) buffers used by the various send functions */ static char sendbuf[2048]; static char sendbuf2[MAXLINELENGTH]; /** This is used to ensure no duplicate messages are sent * to the same server uplink/direction. In send functions * that deliver to multiple users or servers the value is * increased by 1 and then for each delivery in the loop * it is checked if to->direction->local->serial == current_serial * and if so, sending is skipped. */ MODVAR int current_serial; /** Mark the socket as "dead". * This is used when exit_client() cannot be used from the * current code because doing so would be (too) unexpected. * The socket is closed later in the main loop. * NOTE: this function is becoming less important, now that * exit_client() will not actively free the client. * Still, sometimes we need to use dead_socket() * since we don't want to be doing IsDead() checks after * each and every sendto...(). * @param to Client to mark as dead * @param notice The quit reason to use */ int dead_socket(Client *to, const char *notice) { DBufClear(&to->local->recvQ); DBufClear(&to->local->sendQ); if (IsDeadSocket(to)) return -1; /* already pending to be closed */ SetDeadSocket(to); /* We may get here because of the 'CPR' in check_deadsockets(). * In which case, we return -1 as well. */ if (to->local->error_str) return -1; /* don't overwrite & don't send multiple times */ if (!IsUser(to) && !IsUnknown(to) && !IsRPC(to) && !IsControl(to) && !IsClosing(to)) { /* Looks like a duplicate error message to me? * If so, remove it here. */ unreal_log(ULOG_ERROR, "link", "LINK_CLOSING", to, "Link to server $client.details closed: $reason", log_data_string("reason", notice)); } safe_strdup(to->local->error_str, notice); return -1; } /** This is a callback function from the event loop. * All it does is call send_queued(). */ void send_queued_cb(int fd, int revents, void *data) { Client *to = data; if (IsDeadSocket(to)) return; send_queued(to); } /** This function is called when queued data might be ready to be * sent to the client. It is called from the event loop and also * a couple of other places (such as when closing the connection). */ int send_queued(Client *to) { int len, rlen; dbufbuf *block; int want_read; /* We NEVER write to dead sockets. */ if (IsDeadSocket(to)) return -1; while (DBufLength(&to->local->sendQ) > 0) { block = container_of(to->local->sendQ.dbuf_list.next, dbufbuf, dbuf_node); len = block->size; /* Deliver it and check for fatal error.. */ if ((rlen = deliver_it(to, block->data, len, &want_read)) < 0) { char buf[256]; snprintf(buf, 256, "Write error: %s", STRERROR(ERRNO)); return dead_socket(to, buf); } dbuf_delete(&to->local->sendQ, rlen); if (want_read) { /* SSL_write indicated that it cannot write data at this * time and needs to READ data first. Let's stop talking * to the user and ask to notify us when there's data * to read. */ fd_setselect(to->local->fd, FD_SELECT_READ, send_queued_cb, to); fd_setselect(to->local->fd, FD_SELECT_WRITE, NULL, to); break; } /* Restore handling of reads towards read_packet(), since * it may be overwritten in an earlier call to send_queued(), * to handle reads by send_queued_cb(), see directly above. */ fd_setselect(to->local->fd, FD_SELECT_READ, read_packet, to); if (rlen < len) { /* incomplete write due to EWOULDBLOCK, reschedule */ fd_setselect(to->local->fd, FD_SELECT_WRITE, send_queued_cb, to); break; } } /* Nothing left to write, stop asking for write-ready notification. */ if ((DBufLength(&to->local->sendQ) == 0) && (to->local->fd >= 0)) fd_setselect(to->local->fd, FD_SELECT_WRITE, NULL, to); return (IsDeadSocket(to)) ? -1 : 0; } /** Mark "to" with "there is data to be send" */ void mark_data_to_send(Client *to) { if (!IsDeadSocket(to) && (to->local->fd >= 0) && (DBufLength(&to->local->sendQ) > 0)) { fd_setselect(to->local->fd, FD_SELECT_WRITE, send_queued_cb, to); } } /** Send data to clients, servers, channels, IRCOps, etc. * There are a lot of send functions. The most commonly functions * are: sendto_one() to send to an individual user, * sendnumeric() to send a numeric to an individual user * and sendto_channel() to send a message to a channel. * @defgroup SendFunctions Send functions * @{ */ /** Send a message to a single client. * This function is used quite a lot, after sendnumeric() it is the most-used send function. * @param to The client to send to * @param mtags Any message tags associated with this message (can be NULL) * @param pattern The format string / pattern to use. * @param ... Format string parameters. * @section sendto_one_examples Examples * @subsection sendto_one_mode_r Send "MODE -r" * This will send the `:serv.er.name MODE yournick -r` message. * Note that it will send only this message to illustrate the sendto_one() function. * It does *not* set anyone actually -r. * @code * sendto_one(client, NULL, ":%s MODE %s :-r", me.name, client->name); * @endcode */ void sendto_one(Client *to, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...) { va_list vl; va_start(vl, pattern); vsendto_one(to, mtags, pattern, vl); va_end(vl); } /** Send a message to a single client - va_list variant. * This function is similar to sendto_one() except that it * doesn't use varargs but uses a va_list instead. * Generally this function is NOT used outside send.c, so not by modules. * @param to The client to send to * @param mtags Any message tags associated with this message (can be NULL) * @param pattern The format string / pattern to use. * @param vl Format string parameters. */ void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl) { const char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL; /* Need to ignore -Wformat-nonliteral here */ #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif ircvsnprintf(sendbuf, sizeof(sendbuf)-3, pattern, vl); #if defined(__GNUC__) #pragma GCC diagnostic pop #endif if (BadPtr(mtags_str)) { /* Simple message without message tags */ sendbufto_one(to, sendbuf, 0); } else { /* Message tags need to be prepended */ snprintf(sendbuf2, sizeof(sendbuf2)-3, "@%s %s", mtags_str, sendbuf); sendbufto_one(to, sendbuf2, 0); } } /** Send a line buffer to the client. * This function is used (usually indirectly) for pretty much all * cases where a line needs to be sent to a client. * @param to The client to which the buffer should be send. * @param msg The message. * @param quick Normally set to 0, see the notes. * @note * - Neither 'to' or 'msg' may be NULL. * - If quick is set to 0 then the length is calculated, * the string is cut off at 510 bytes if needed, and * CR+LF is added if needed. * If quick is >0 then it is assumed the message already * is within boundaries and passed all safety checks and * contains CR+LF at the end. This if, for example, used in * channel broadcasts to save some CPU cycles. It is NOT * recommended as normal usage since you need to be very * careful to take everything into account, including side- * effects not mentioned here. */ void sendbufto_one(Client *to, char *msg, unsigned int quick) { int len; Hook *h; Client *intended_to = to; if (to->direction) to = to->direction; if (IsDeadSocket(to)) return; /* This socket has already been marked as dead */ if (to->local->fd < 0) { /* This is normal when 'to' was being closed (via exit_client * and close_connection) --Run */ return; } /* Unless 'quick' is set, we do some safety checks, * cut the string off at the appropriate place and add * CR+LF if needed (nearly always). */ if (!quick) { char *p = msg; if (*msg == '@') { /* The message includes one or more message tags: * Spec-wise the rules allow about 8K for message tags * (MAXTAGSIZE) and then 512 bytes for * the remainder of the message (BUFSIZE). */ p = strchr(msg+1, ' '); if (!p) { unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_MALFORMED_MSG", to, "Malformed message to $client: $buf", log_data_string("buf", msg)); return; } if (p - msg > MAXTAGSIZE) { unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_OVERSIZED_MSG", to, "Oversized message to $client (length $length): $buf", log_data_integer("length", p - msg), log_data_string("buf", msg)); return; } p++; /* skip space character */ } len = strlen(p); if (!len || (p[len - 1] != '\n')) { if (len > 510) len = 510; p[len++] = '\r'; p[len++] = '\n'; p[len] = '\0'; } len = strlen(msg); /* (note: we could use pointer jugling to avoid a strlen here) */ } else { len = quick; } if (len >= MAXLINELENGTH) { unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_OVERSIZED_MSG2", to, "Oversized message to $client (length $length): $buf", log_data_integer("length", len), log_data_string("buf", msg)); #ifdef DEBUGMODE abort(); #else return; #endif } if (IsMe(to)) { char tmp_msg[500]; strlcpy(tmp_msg, msg, sizeof(tmp_msg)); stripcrlf(tmp_msg); unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_ME_MESSAGE", to, "Trying to send data to myself: $buf", log_data_string("buf", tmp_msg)); return; } for (h = Hooks[HOOKTYPE_PACKET]; h; h = h->next) { (*(h->func.intfunc))(&me, to, intended_to, &msg, &len); if (!msg) return; } #if defined(RAWCMDLOGGING) { char copy[512], *p; strlcpy(copy, msg, len > sizeof(copy) ? sizeof(copy) : len); p = strchr(copy, '\n'); if (p) *p = '\0'; p = strchr(copy, '\r'); if (p) *p = '\0'; unreal_log(ULOG_INFO, "rawtraffic", "TRAFFIC_OUT", to, "-> $client: $data", log_data_string("data", copy)); } #endif if (DBufLength(&to->local->sendQ) > get_sendq(to)) { unreal_log(ULOG_INFO, "flood", "SENDQ_EXCEEDED", to, "Flood of queued data to $client.details [$client.ip] exceeds class::sendq ($sendq > $class_sendq) (Too much data queued to be sent to this client)", log_data_integer("sendq", DBufLength(&to->local->sendQ)), log_data_integer("class_sendq", get_sendq(to))); dead_socket(to, "Max SendQ exceeded"); return; } dbuf_put(&to->local->sendQ, msg, len); /* * Update statistics. The following is slightly incorrect * because it counts messages even if queued, but bytes * only really sent. Queued bytes get updated in SendQueued. */ // FIXME: something is wrong here, I think we do double counts, either in message or in traffic, I forgot.. CHECK !!!! to->local->traffic.messages_sent++; me.local->traffic.messages_sent++; /* Previously we ran send_queued() here directly, but that is * a bad idea, CPU-wise. So now we just mark the client indicating * that there is data to send. */ if (IsControl(to)) send_queued(to); /* send this one ASAP */ else mark_data_to_send(to); } /** A single function to send data to a channel. * Previously there were 6 different functions to send channel data, * now there is 1 single function. This also means that you most * likely will pass NULL or 0 as some parameters. * @param channel The channel to send to * @param from The source of the message * @param skip The client to skip (can be NULL). * Note that if you specify a remote link then * you usually mean xyz->direction and not xyz. * @param member_modes Require any of the member_modes to be set (eg: "o"), or NULL to skip this check. * @param clicap Client capability the recipient should have * (this only works for local clients, we will * always send the message to remote clients and * assume the server there will handle it) * @param sendflags Determines whether to send the message to local/remote users * @param mtags The message tags to attach to this message * @param pattern The pattern (eg: ":%s PRIVMSG %s :%s") * @param ... The parameters for the pattern. * @note For all channel messages, it is important to attach the correct * message tags (mtags) via a new_message() call, as can be seen * in the example. * @section sendto_channel_examples Examples * @subsection sendto_channel_privmsg Send a PRIVMSG to a channel * This command will send the message "Hello everyone!!!" to the channel when executed. * @code * CMD_FUNC(cmd_sayhello) * { * MessageTag *mtags = NULL; * Channel *channel = NULL; * if ((parc < 2) || BadPtr(parv[1])) * { * sendnumeric(client, ERR_NEEDMOREPARAMS, "SAYHELLO"); * return; * } * channel = find_channel(parv[1]); * if (!channel) * { * sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]); * return; * } * new_message(client, recv_mtags, &mtags); * sendto_channel(channel, client, client->direction, NULL, 0, * SEND_LOCAL|SEND_REMOTE, mtags, * ":%s PRIVMSG %s :Hello everyone!!!", * client->name, channel->name); * free_message_tags(mtags); * } * @endcode */ void sendto_channel(Channel *channel, Client *from, Client *skip, char *member_modes, long clicap, int sendflags, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...) { va_list vl; Member *lp; Client *acptr; char member_modes_ext[64]; if (member_modes) { channel_member_modes_generate_equal_or_greater(member_modes, member_modes_ext, sizeof(member_modes_ext)); member_modes = member_modes_ext; } ++current_serial; for (lp = channel->members; lp; lp = lp->next) { acptr = lp->client; /* Skip sending to 'skip' */ if ((acptr == skip) || (acptr->direction == skip)) continue; /* Don't send to deaf clients (unless 'senddeaf' is set) */ if (IsDeaf(acptr) && (sendflags & SKIP_DEAF)) continue; /* Don't send to NOCTCP clients */ if (has_user_mode(acptr, 'T') && (sendflags & SKIP_CTCP)) continue; /* Now deal with 'member_modes' (if not NULL) */ if (member_modes && !check_channel_access_member(lp, member_modes)) continue; /* Now deal with 'clicap' (if non-zero) */ if (clicap && MyUser(acptr) && ((clicap & CAP_INVERT) ? HasCapabilityFast(acptr, clicap) : !HasCapabilityFast(acptr, clicap))) continue; if (MyUser(acptr)) { /* Local client */ if (sendflags & SEND_LOCAL) { va_start(vl, pattern); vsendto_prefix_one(acptr, from, mtags, pattern, vl); va_end(vl); } } else { /* Remote client */ if (sendflags & SEND_REMOTE) { /* Message already sent to remote link? */ if (acptr->direction->local->serial != current_serial) { va_start(vl, pattern); vsendto_prefix_one(acptr, from, mtags, pattern, vl); va_end(vl); acptr->direction->local->serial = current_serial; } } } } if (sendflags & SEND_REMOTE) { /* For the remaining uplinks that we have not sent a message to yet... * broadcast-channel-messages=never: don't send it to them * broadcast-channel-messages=always: always send it to them * broadcast-channel-messages=auto: send it to them if the channel is set +H (history) */ if ((iConf.broadcast_channel_messages == BROADCAST_CHANNEL_MESSAGES_ALWAYS) || ((iConf.broadcast_channel_messages == BROADCAST_CHANNEL_MESSAGES_AUTO) && has_channel_mode(channel, 'H'))) { list_for_each_entry(acptr, &server_list, special_node) { if ((acptr == skip) || (acptr->direction == skip)) continue; /* still obey this rule.. */ if (acptr->direction->local->serial != current_serial) { va_start(vl, pattern); vsendto_prefix_one(acptr, from, mtags, pattern, vl); va_end(vl); acptr->direction->local->serial = current_serial; } } } } } /** Send a message to a server, taking into account server options if needed. * @param one The client to skip (can be NULL) * @param servercaps Server capabilities which must be present (OR'd together, if multiple) * @param noservercaps Server capabilities which must NOT be present (OR'd together, if multiple) * @param mtags The message tags to attach to this message. * @param format The format string / pattern, such as ":%s NICK %s". * @param ... The parameters for the format string */ void sendto_server(Client *one, unsigned long servercaps, unsigned long noservercaps, MessageTag *mtags, FORMAT_STRING(const char *format), ...) { Client *acptr; /* noone to send to.. */ if (list_empty(&server_list)) return; list_for_each_entry(acptr, &server_list, special_node) { va_list vl; if (one && acptr == one->direction) continue; if (servercaps && !CHECKSERVERPROTO(acptr, servercaps)) continue; if (noservercaps && CHECKSERVERPROTO(acptr, noservercaps)) continue; va_start(vl, format); vsendto_one(acptr, mtags, format, vl); va_end(vl); } } /** Send a message to all local users on all channels where * the user 'user' is on. * This is used for events such as a nick change and quit. * @param user The user and source of the message. * @param skip The client to skip (can be NULL) * @param clicap Client capability the recipient should have * (this only works for local clients, we will * always send the message to remote clients and * assume the server there will handle it) * @param mtags The message tags to attach to this message. * @param pattern The pattern (eg: ":%s NICK %s"). * @param ... The parameters for the pattern. */ void sendto_local_common_channels(Client *user, Client *skip, long clicap, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...) { va_list vl; Membership *channels; Member *users; Client *acptr; /* We now create the buffer _before_ we send it to the clients. -- Syzop */ *sendbuf = '\0'; va_start(vl, pattern); vmakebuf_local_withprefix(sendbuf, sizeof sendbuf, user, pattern, vl); va_end(vl); ++current_serial; if (user->user) { for (channels = user->user->channel; channels; channels = channels->next) { for (users = channels->channel->members; users; users = users->next) { acptr = users->client; if (!MyConnect(acptr)) continue; /* only process local clients */ if (acptr->local->serial == current_serial) continue; /* message already sent to this client */ if (clicap && ((clicap & CAP_INVERT) ? HasCapabilityFast(acptr, clicap) : !HasCapabilityFast(acptr, clicap))) continue; /* client does not have the specified capability */ if (acptr == skip) continue; /* the one to skip */ if (!user_can_see_member(acptr, user, channels->channel)) continue; /* the sending user (quit'ing or nick changing) is 'invisible' -- skip */ acptr->local->serial = current_serial; sendto_one(acptr, mtags, "%s", sendbuf); } } } } /** Send a QUIT message to all local users on all channels where * the user 'user' is on. * This is used for events such as a nick change and quit. * @param user The user and source of the message. * @param skip The client to skip (can be NULL) * @param clicap Client capability the recipient should have * (this only works for local clients, we will * always send the message to remote clients and * assume the server there will handle it) * @param mtags The message tags to attach to this message. * @param pattern The pattern (eg: ":%s NICK %s"). * @param ... The parameters for the pattern. */ void quit_sendto_local_common_channels(Client *user, MessageTag *mtags, const char *reason) { va_list vl; Membership *channels; Member *users; Client *acptr; char sender[512]; MessageTag *m; const char *real_quit_reason = NULL; m = find_mtag(mtags, "unrealircd.org/real-quit-reason"); if (m && m->value) real_quit_reason = m->value; if (IsUser(user)) { snprintf(sender, sizeof(sender), "%s!%s@%s", user->name, user->user->username, GetHost(user)); } else { strlcpy(sender, user->name, sizeof(sender)); } ++current_serial; if (user->user) { for (channels = user->user->channel; channels; channels = channels->next) { for (users = channels->channel->members; users; users = users->next) { acptr = users->client; if (!MyConnect(acptr)) continue; /* only process local clients */ if (acptr->local->serial == current_serial) continue; /* message already sent to this client */ if (!user_can_see_member(acptr, user, channels->channel)) continue; /* the sending user (QUITing) is 'invisible' -- skip */ acptr->local->serial = current_serial; if (!reason) sendto_one(acptr, mtags, ":%s QUIT", sender); else if (!IsOper(acptr) || !real_quit_reason) sendto_one(acptr, mtags, ":%s QUIT :%s", sender, reason); else sendto_one(acptr, mtags, ":%s QUIT :%s", sender, real_quit_reason); } } } } /* ** send a msg to all ppl on servers/hosts that match a specified mask ** (used for enhanced PRIVMSGs) ** ** addition -- Armin, 8jun90 (gruner@informatik.tu-muenchen.de) */ static int match_it(Client *one, const char *mask, int what) { switch (what) { case MATCH_HOST: return match_simple(mask, one->user->realhost); case MATCH_SERVER: default: return match_simple(mask, one->user->server); } } /** Send to all clients which match the mask. * This function is rarely used. * @param one The client to skip * @param from The sender * @param mask The mask * @param what One of MATCH_HOST or MATCH_SERVER * @param mtags Message tags associated with the message * @param pattern Format string * @param ... Parameters to the format string */ void sendto_match_butone(Client *one, Client *from, const char *mask, int what, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...) { va_list vl; Client *acptr; char cansendlocal, cansendglobal; if (MyConnect(from)) { cansendlocal = (ValidatePermissionsForPath("chat:notice:local",from,NULL,NULL,NULL)) ? 1 : 0; cansendglobal = (ValidatePermissionsForPath("chat:notice:global",from,NULL,NULL,NULL)) ? 1 : 0; } else cansendlocal = cansendglobal = 1; /* To servers... */ if (cansendglobal) { char buf[512]; va_start(vl, pattern); ircvsnprintf(buf, sizeof(buf), pattern, vl); va_end(vl); sendto_server(one, 0, 0, mtags, "%s", buf); } /* To local clients... */ if (cansendlocal) { list_for_each_entry(acptr, &lclient_list, lclient_node) { if (!IsMe(acptr) && (acptr != one) && IsUser(acptr) && match_it(acptr, mask, what)) { va_start(vl, pattern); vsendto_prefix_one(acptr, from, mtags, pattern, vl); va_end(vl); } } } } /** Send a message to all locally connected users with specified user mode. * @param umodes The umode that the recipient should have set (one of UMODE_) * @param pattern The format string / pattern to use. * @param ... Format string parameters. */ void sendto_umode(int umodes, FORMAT_STRING(const char *pattern), ...) { va_list vl; Client *acptr; char nbuf[1024]; list_for_each_entry(acptr, &lclient_list, lclient_node) if (IsUser(acptr) && (acptr->umodes & umodes) == umodes) { ircsnprintf(nbuf, sizeof(nbuf), ":%s NOTICE %s :", me.name, acptr->name); strlcat(nbuf, pattern, sizeof nbuf); va_start(vl, pattern); vsendto_one(acptr, NULL, nbuf, vl); va_end(vl); } } /** Send a message to all users with specified user mode (local & remote users). * @param umodes The umode that the recipient should have set (one of UMODE_*) * @param pattern The format string / pattern to use. * @param ... Format string parameters. */ void sendto_umode_global(int umodes, FORMAT_STRING(const char *pattern), ...) { va_list vl; Client *acptr; Umode *um; char nbuf[1024]; char modestr[128]; char *p; /* Convert 'umodes' (int) to 'modestr' (string) */ get_usermode_string_raw_r(umodes, modestr, sizeof(modestr)); list_for_each_entry(acptr, &lclient_list, lclient_node) { if (IsUser(acptr) && (acptr->umodes & umodes) == umodes) { ircsnprintf(nbuf, sizeof(nbuf), ":%s NOTICE %s :", me.name, acptr->name); strlcat(nbuf, pattern, sizeof nbuf); va_start(vl, pattern); vsendto_one(acptr, NULL, nbuf, vl); va_end(vl); } else if (IsServer(acptr) && *modestr) { snprintf(nbuf, sizeof(nbuf), ":%s SENDUMODE %s :%s", me.id, modestr, pattern); va_start(vl, pattern); vsendto_one(acptr, NULL, nbuf, vl); va_end(vl); } } } /** Send CAP DEL and CAP NEW notification to clients supporting it. * This function is mostly meant to be used by the CAP and SASL modules. * @param add Whether the CAP token is added (1) or removed (0) * @param token The CAP token */ void send_cap_notify(int add, const char *token) { Client *client; ClientCapability *clicap = ClientCapabilityFindReal(token); long CAP_NOTIFY = ClientCapabilityBit("cap-notify"); list_for_each_entry(client, &lclient_list, lclient_node) { if (HasCapabilityFast(client, CAP_NOTIFY)) { if (add) { const char *args = NULL; if (clicap) { if (clicap->visible && !clicap->visible(client)) continue; /* invisible CAP, so don't announce it */ if (clicap->parameter && (client->local->cap_protocol >= 302)) args = clicap->parameter(client); } if (!args) { sendto_one(client, NULL, ":%s CAP %s NEW :%s", me.name, (*client->name ? client->name : "*"), token); } else { sendto_one(client, NULL, ":%s CAP %s NEW :%s=%s", me.name, (*client->name ? client->name : "*"), token, args); } } else { sendto_one(client, NULL, ":%s CAP %s DEL :%s", me.name, (*client->name ? client->name : "*"), token); } } } } /* Prepare buffer based on format string and 'from' for LOCAL delivery. * The prefix (:) will be expanded to :nick!user@host if 'from' * is a person, taking into account the rules for hidden/cloaked host. * NOTE: Do not send this prepared buffer to remote clients or servers, * they do not want or need the expanded prefix. In that case, simply * use ircvsnprintf() directly. */ static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, const char *pattern, va_list vl) { int len; /* This expands the ":%s " part of the pattern * into ":nick!user@host ". * In case of a non-person (server) it doesn't do * anything since no expansion is needed. */ if (from && from->user && !strncmp(pattern, ":%s ", 4)) { va_arg(vl, char *); /* eat first parameter */ *buf = ':'; strlcpy(buf+1, from->name, buflen-1); if (IsUser(from)) { char *username = from->user->username; char *host = GetHost(from); if (*username) { strlcat(buf, "!", buflen); strlcat(buf, username, buflen); } if (*host) { strlcat(buf, "@", buflen); strlcat(buf, host, buflen); } } /* Now build the remaining string */ #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif ircvsnprintf(buf + strlen(buf), buflen - strlen(buf), &pattern[3], vl); #if defined(__GNUC__) #pragma GCC diagnostic pop #endif } else { ircvsnprintf(buf, buflen, pattern, vl); } len = strlen(buf); ADD_CRLF(buf, len); return len; } /** Send a message to a client, expand the sender prefix. * This is similar to sendto_one() except that it will expand the source part :%s * to :nick!user@host if needed, while with sendto_one() it will be :nick. * @param to The client to send to * @param mtags Any message tags associated with this message (can be NULL) * @param pattern The format string / pattern to use. * @param ... Format string parameters. */ void sendto_prefix_one(Client *to, Client *from, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...) { va_list vl; va_start(vl, pattern); vsendto_prefix_one(to, from, mtags, pattern, vl); va_end(vl); } /** Send a message to a single client, expand the sender prefix - va_list variant. * This is similar to vsendto_one() except that it will expand the source part :%s * to :nick!user@host if needed, while with sendto_one() it will be :nick. * This function is also similar to sendto_prefix_one(), but this is the va_list * variant. * @param to The client to send to * @param mtags Any message tags associated with this message (can be NULL) * @param pattern The format string / pattern to use. * @param ... Format string parameters. */ void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl) { const char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL; if (to && from && MyUser(to) && from->user) vmakebuf_local_withprefix(sendbuf, sizeof(sendbuf)-3, from, pattern, vl); else ircvsnprintf(sendbuf, sizeof(sendbuf)-3, pattern, vl); if (BadPtr(mtags_str)) { /* Simple message without message tags */ sendbufto_one(to, sendbuf, 0); } else { /* Message tags need to be prepended */ snprintf(sendbuf2, sizeof(sendbuf2)-3, "@%s %s", mtags_str, sendbuf); sendbufto_one(to, sendbuf2, 0); } } /** Introduce user to all other servers, except the one to skip. * @param one Server to skip (can be NULL) * @param client Client to introduce * @param umodes User modes of client */ void sendto_serv_butone_nickcmd(Client *one, MessageTag *mtags, Client *client, const char *umodes) { Client *acptr; list_for_each_entry(acptr, &server_list, special_node) { if (one && acptr == one->direction) continue; sendto_one_nickcmd(acptr, mtags, client, umodes); } } /** Introduce user to a server. * @param server Server to send to (locally connected!) * @param client Client to introduce * @param umodes User modes of client */ void sendto_one_nickcmd(Client *server, MessageTag *mtags, Client *client, const char *umodes) { char *vhost; char mtags_generated = 0; if (!*umodes) umodes = "+"; if (SupportVHP(server)) { if (IsHidden(client)) vhost = client->user->virthost; else vhost = client->user->realhost; } else { if (IsHidden(client) && client->umodes & UMODE_SETHOST) vhost = client->user->virthost; else vhost = "*"; } if (mtags == NULL) { moddata_add_s2s_mtags(client, &mtags); mtags_generated = 1; } sendto_one(server, mtags, ":%s UID %s %d %lld %s %s %s %s %s %s %s %s :%s", client->uplink->id, client->name, client->hopcount, (long long)client->lastnick, client->user->username, client->user->realhost, client->id, client->user->account, umodes, vhost, getcloak(client), encode_ip(client->ip), client->info); if (mtags_generated) safe_free_message_tags(mtags); } /* sidenote: sendnotice() and sendtxtnumeric() assume no client or server * has a % in their nick, which is a safe assumption since % is illegal. */ /** Send a server notice to a client. * @param to The client to send to * @param pattern The format string / pattern to use. * @param ... Format string parameters. */ void sendnotice(Client *to, FORMAT_STRING(const char *pattern), ...) { static char realpattern[1024]; va_list vl; char *name = *to->name ? to->name : "*"; ircsnprintf(realpattern, sizeof(realpattern), ":%s NOTICE %s :%s", me.name, name, pattern); va_start(vl, pattern); vsendto_one(to, NULL, realpattern, vl); va_end(vl); } /** Send MultiLine list as a notice, one for each line. * @param client The client to send to * @param m The MultiLine list. */ void sendnotice_multiline(Client *client, MultiLine *m) { for (; m; m = m->next) sendnotice(client, "%s", m->line); } /** Send numeric message to a client - format to user specific needs. * This will ignore the numeric definition of src/numeric.c and always send ":me.name numeric clientname " * followed by the pattern and format string you choose. * @param to The recipient * @param mtags NULL, or NULL-terminated array of message tags * @param numeric The numeric, one of RPL_* or ERR_*, see src/numeric.c * @param pattern The format string / pattern to use. * @param ... Format string parameters. * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake. */ void sendtaggednumericfmt(Client *to, MessageTag *mtags, int numeric, FORMAT_STRING(const char *pattern), ...) { va_list vl; char realpattern[512]; snprintf(realpattern, sizeof(realpattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", pattern); va_start(vl, pattern); vsendto_one(to, mtags, realpattern, vl); va_end(vl); } /** Send text numeric message to a client (RPL_TEXT). * Because this generic output numeric is commonly used it got a special function for it. * @param to The recipient * @param numeric The numeric, one of RPL_* or ERR_*, see src/numeric.c * @param pattern The format string / pattern to use. * @param ... Format string parameters. * @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake. */ void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...) { static char realpattern[1024]; va_list vl; ircsnprintf(realpattern, sizeof(realpattern), ":%s %d %s :%s", me.name, RPL_TEXT, to->name, pattern); va_start(vl, pattern); vsendto_one(to, NULL, realpattern, vl); va_end(vl); } /** Build buffer in order to send a numeric message to a client - rarely used. * @param buf The buffer that should be used * @param buflen The size of the buffer * @param to The recipient * @param numeric The numeric, one of RPL_* or ERR_*, see src/numeric.c * @param pattern The format string / pattern to use. * @param ... Format string parameters. */ void buildnumericfmt(char *buf, size_t buflen, Client *to, int numeric, FORMAT_STRING(const char *pattern), ...) { va_list vl; char realpattern[512]; snprintf(realpattern, sizeof(realpattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", pattern); va_start(vl, pattern); /* Need to ignore -Wformat-nonliteral here */ #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif vsnprintf(buf, buflen, realpattern, vl); #if defined(__GNUC__) #pragma GCC diagnostic pop #endif va_end(vl); } void add_nvplist_numeric_fmt(NameValuePrioList **lst, int priority, const char *name, Client *to, int numeric, FORMAT_STRING(const char *pattern), ...) { va_list vl; char realpattern[512], buf[512]; snprintf(realpattern, sizeof(realpattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", pattern); va_start(vl, pattern); /* Need to ignore -Wformat-nonliteral here */ #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif vsnprintf(buf, sizeof(buf), realpattern, vl); #if defined(__GNUC__) #pragma GCC diagnostic pop #endif va_end(vl); add_nvplist(lst, priority, name, buf); } /* Send raw data directly to socket, bypassing everything. * Looks like an interesting function to call? NO! STOP! * Don't use this function. It may only be used by the initial * Z-Line check via the codepath to banned_client(). * YOU SHOULD NEVER USE THIS FUNCTION. * If you want to send raw data (without formatting) to a client * then have a look at sendbufto_one() instead. * * Side-effects: * Too many to list here. Only in the early accept code the * "if's" and side-effects are under control. * * By the way, did I already mention that you SHOULD NOT USE THIS * FUNCTION? ;) */ void send_raw_direct(Client *user, FORMAT_STRING(const char *pattern), ...) { va_list vl; int sendlen; *sendbuf = '\0'; va_start(vl, pattern); sendlen = vmakebuf_local_withprefix(sendbuf, sizeof(sendbuf), user, pattern, vl); va_end(vl); (void)send(user->local->fd, sendbuf, sendlen, 0); } /** @} */