/* * 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); static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, const char *pattern, va_list vl); #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[4096]; /** 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, 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) && !IsClosing(to)) sendto_ops_and_log("Link to server %s (%s) closed: %s", to->name, to->ip?to->ip:"", notice); Debug((DEBUG_ERROR, "dead_socket: %s - %s", notice, get_client_name(to, FALSE))); 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); to->local->lastsq = DBufLength(&to->local->sendQ) / 1024; 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 a message to a single client. * @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_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 a va_list instead. * Generally this 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) { char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL; ircvsnprintf(sendbuf, sizeof(sendbuf), 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), "@%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; Debug((DEBUG_ERROR, "Sending [%s] to %s", msg, to->name)); 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 * Print the debug message anyway... */ Debug((DEBUG_ERROR, "Local socket %s with negative fd %d... AARGH!", to->name, to->local->fd)); 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 * and then 512 bytes for the remainder of the message. * Since we do not allow user tags and only permit a * limited set of tags we can have our own limits for * the outgoing messages that we generate: a maximum of * 500 bytes for message tags and 512 for the remainder. * These limits will never be hit unless there is a bug * somewhere. */ p = strchr(msg+1, ' '); if (!p) { ircd_log(LOG_ERROR, "[BUG] sendbufto_one(): Malformed message: %s", msg); return; } if (p - msg > 500) { ircd_log(LOG_ERROR, "[BUG] sendbufto_one(): Spec-wise legal, but massively oversized message-tag (len %d)", (int)(p - 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 >= 1024) { ircd_log(LOG_ERROR, "sendbufto_one: len=%d, quick=%u", len, quick); abort(); } if (IsMe(to)) { char tmp_msg[500], *p; p = strchr(msg, '\r'); if (p) *p = '\0'; snprintf(tmp_msg, 500, "Trying to send data to myself! '%s'", msg); ircd_log(LOG_ERROR, "%s", tmp_msg); sendto_ops("%s", tmp_msg); /* recursion? */ return; } for (h = Hooks[HOOKTYPE_PACKET]; h; h = h->next) { (*(h->func.intfunc))(&me, to, intended_to, &msg, &len); if (!msg) return; } #if defined(DEBUGMODE) && 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'; ircd_log(LOG_ERROR, "-> %s: %s", to->name, copy); } #endif if (DBufLength(&to->local->sendQ) > get_sendq(to)) { if (IsServer(to)) sendto_ops("Max SendQ limit exceeded for %s: %u > %d", get_client_name(to, FALSE), DBufLength(&to->local->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->sendM += 1; me.local->sendM += 1; /* 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. */ mark_data_to_send(to); } /** A single function to send data to a channel. * Previously there were 6, now there is 1. This means there * are likely some parameters that you will pass as NULL or 0 * but at least we can all use one single function. * @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 prefix Any combination of PREFIX_* (can be 0 for all) * @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. */ void sendto_channel(Channel *channel, Client *from, Client *skip, int prefix, long clicap, int sendflags, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...) { va_list vl; Member *lp; Client *acptr; ++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 'prefix' (if non-zero) */ if (!prefix) goto good; if ((prefix & PREFIX_HALFOP) && (lp->flags & CHFL_HALFOP)) goto good; if ((prefix & PREFIX_VOICE) && (lp->flags & CHFL_VOICE)) goto good; if ((prefix & PREFIX_OP) && (lp->flags & CHFL_CHANOP)) goto good; #ifdef PREFIX_AQ if ((prefix & PREFIX_ADMIN) && (lp->flags & CHFL_CHANADMIN)) goto good; if ((prefix & PREFIX_OWNER) && (lp->flags & CHFL_CHANOWNER)) goto good; #endif continue; good: /* 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 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, 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); } } /* * sendto_match_butone * * Send to all clients which match the mask in a way defined on 'what'; * either by user hostname or user servername. */ void sendto_match_butone(Client *one, Client *from, 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); } } } } /* * sendto_ops * * Send to *local* ops only. */ void sendto_ops(FORMAT_STRING(const char *pattern), ...) { va_list vl; Client *acptr; char nbuf[1024]; list_for_each_entry(acptr, &lclient_list, lclient_node) if (!IsServer(acptr) && !IsMe(acptr) && SendServNotice(acptr)) { 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); } } /* * sendto_umode * * Send to specified umode */ 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); } } /* * sendto_umode_global * * Send to specified umode *GLOBALLY* (on all servers) */ void sendto_umode_global(int umodes, FORMAT_STRING(const char *pattern), ...) { va_list vl; Client *acptr; char nbuf[1024]; int i; char modestr[128]; char *p; /* Convert 'umodes' (int) to 'modestr' (string) */ *modestr = '\0'; p = modestr; for(i = 0; i <= Usermode_highest; i++) { if (!Usermode_Table[i].flag) continue; if (umodes & Usermode_Table[i].mode) *p++ = Usermode_Table[i].flag; } *p = '\0'; 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 to specified snomask - local / operonly. * @param snomask Snomask to send to (can be a bitmask [AND]) * @param pattern printf-style pattern, followed by parameters. * This function does not send snomasks to non-opers. */ void sendto_snomask(int snomask, FORMAT_STRING(const char *pattern), ...) { va_list vl; Client *acptr; char nbuf[2048]; va_start(vl, pattern); ircvsnprintf(nbuf, sizeof(nbuf), pattern, vl); va_end(vl); list_for_each_entry(acptr, &oper_list, special_node) { if (acptr->user->snomask & snomask) sendnotice(acptr, "%s", nbuf); } } /** Send to specified snomask - global / operonly. * @param snomask Snomask to send to (can be a bitmask [AND]) * @param pattern printf-style pattern, followed by parameters * This function does not send snomasks to non-opers. */ void sendto_snomask_global(int snomask, FORMAT_STRING(const char *pattern), ...) { va_list vl; Client *acptr; int i; char nbuf[2048], snobuf[32], *p; va_start(vl, pattern); ircvsnprintf(nbuf, sizeof(nbuf), pattern, vl); va_end(vl); list_for_each_entry(acptr, &oper_list, special_node) { if (acptr->user->snomask & snomask) sendnotice(acptr, "%s", nbuf); } /* Build snomasks-to-send-to buffer */ snobuf[0] = '\0'; for (i = 0, p=snobuf; i<= Snomask_highest; i++) if (snomask & Snomask_Table[i].mode) *p++ = Snomask_Table[i].flag; *p = '\0'; sendto_server(NULL, 0, 0, NULL, ":%s SENDSNO %s :%s", me.id, snobuf, nbuf); } /* * send_cap_notify * * Send CAP DEL or CAP NEW to clients supporting this. */ void send_cap_notify(int add, 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) { 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); } } } } /* ** sendto_ops_butone ** Send message to all operators. ** one - client not to send message to ** from- client which message is from *NEVER* NULL!! */ void sendto_ops_butone(Client *one, Client *from, FORMAT_STRING(const char *pattern), ...) { va_list vl; Client *acptr; ++current_serial; list_for_each_entry(acptr, &client_list, client_node) { if (!SendWallops(acptr)) continue; if (acptr->direction->local->serial == current_serial) /* sent message along it already ? */ continue; if (acptr->direction == one) continue; /* ...was the one I should skip */ acptr->direction->local->serial = current_serial; va_start(vl, pattern); vsendto_prefix_one(acptr->direction, from, NULL, pattern, vl); va_end(vl); } } /* 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 = ':'; strcpy(buf+1, from->name); if (IsUser(from)) { char *username = from->user->username; char *host = GetHost(from); if (*username) { strcat(buf, "!"); strcat(buf, username); } if (*host) { strcat(buf, "@"); strcat(buf, host); } } /* Now build the remaining string */ ircvsnprintf(buf + strlen(buf), buflen - strlen(buf), &pattern[3], vl); } else { ircvsnprintf(buf, buflen, pattern, vl); } len = strlen(buf); ADD_CRLF(buf, len); return len; } void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl) { char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL; if (to && from && MyUser(to) && from->user) vmakebuf_local_withprefix(sendbuf, sizeof sendbuf, from, pattern, vl); else ircvsnprintf(sendbuf, sizeof(sendbuf), 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), "@%s %s", mtags_str, sendbuf); sendbufto_one(to, sendbuf2, 0); } } /* * sendto_prefix_one * * to - destination client * from - client which message is from * * NOTE: NEITHER OF THESE SHOULD *EVER* BE NULL!! * -avalon */ 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); } /* * sendto_realops * * Send to *local* ops only but NOT +s nonopers. */ void sendto_realops(FORMAT_STRING(const char *pattern), ...) { va_list vl; Client *acptr; char nbuf[1024]; list_for_each_entry(acptr, &oper_list, special_node) { 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); } } /* Sends a message to all (local) opers AND logs to the ircdlog (as LOG_ERROR) */ void sendto_realops_and_log(FORMAT_STRING(const char *fmt), ...) { va_list vl; static char buf[2048]; va_start(vl, fmt); vsnprintf(buf, sizeof(buf), fmt, vl); va_end(vl); sendto_realops("%s", buf); ircd_log(LOG_ERROR, "%s", buf); } void sendto_connectnotice(Client *newuser, int disconnect, char *comment) { Client *acptr; char connect[512], secure[256]; if (!disconnect) { RunHook(HOOKTYPE_LOCAL_CONNECT, newuser); *secure = '\0'; if (IsSecure(newuser)) snprintf(secure, sizeof(secure), " [secure %s]", tls_get_cipher(newuser->local->ssl)); ircsnprintf(connect, sizeof(connect), "*** Client connecting: %s (%s@%s) [%s] {%s}%s", newuser->name, newuser->user->username, newuser->user->realhost, newuser->ip, newuser->local->class ? newuser->local->class->name : "0", secure); } else { ircsnprintf(connect, sizeof(connect), "*** Client exiting: %s (%s@%s) [%s] (%s)", newuser->name, newuser->user->username, newuser->user->realhost, newuser->ip, comment); } list_for_each_entry(acptr, &oper_list, special_node) { if (acptr->user->snomask & SNO_CLIENT) sendnotice(acptr, "%s", connect); } } void sendto_fconnectnotice(Client *newuser, int disconnect, char *comment) { Client *acptr; char connect[512], secure[256]; if (!disconnect) { *secure = '\0'; if (IsSecureConnect(newuser)) strcpy(secure, " [secure]"); /* will we ever expand this? */ ircsnprintf(connect, sizeof(connect), "*** Client connecting: %s (%s@%s) [%s] {0}%s", newuser->name, newuser->user->username, newuser->user->realhost, newuser->ip ? newuser->ip : "0", secure); } else { ircsnprintf(connect, sizeof(connect), "*** Client exiting: %s (%s@%s) [%s] (%s)", newuser->name, newuser->user->username, newuser->user->realhost, newuser->ip ? newuser->ip : "0", comment); } list_for_each_entry(acptr, &oper_list, special_node) { if (acptr->user->snomask & SNO_FCLIENT) sendto_one(acptr, NULL, ":%s NOTICE %s :%s", newuser->user->server, acptr->name, connect); } } /** 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, Client *client, char *umodes) { Client *acptr; list_for_each_entry(acptr, &server_list, special_node) { if (one && acptr == one->direction) continue; sendto_one_nickcmd(acptr, 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, Client *client, char *umodes) { char *vhost; 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 = "*"; } sendto_one(server, NULL, ":%s UID %s %d %lld %s %s %s %s %s %s %s %s :%s", client->srvptr->id, client->name, client->hopcount, (long long)client->lastnick, client->user->username, client->user->realhost, client->id, client->user->svid, umodes, vhost, getcloak(client), encode_ip(client->ip), client->info); } /* sidenote: sendnotice() and sendtxtnumeric() assume no client or server * has a % in their nick, which is a safe assumption since % is illegal. */ 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 */ void sendnotice_multiline(Client *client, MultiLine *m) { for (; m; m = m->next) sendnotice(client, "%s", m->line); } 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); } /** Send numeric to IRC client */ void sendnumeric(Client *to, int numeric, ...) { va_list vl; char pattern[512]; snprintf(pattern, sizeof(pattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", rpl_str(numeric)); va_start(vl, numeric); vsendto_one(to, NULL, pattern, vl); va_end(vl); } /** Send numeric to IRC client */ void sendnumericfmt(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); vsendto_one(to, NULL, realpattern, vl); va_end(vl); } /** 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(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); } /** Send a message to all locally connected IRCOps and log the error. */ void sendto_ops_and_log(FORMAT_STRING(const char *pattern), ...) { va_list vl; char buf[1024]; va_start(vl, pattern); ircvsnprintf(buf, sizeof(buf), pattern, vl); va_end(vl); ircd_log(LOG_ERROR, "%s", buf); sendto_umode(UMODE_OPER, "%s", buf); }