unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

message-tags.c (7409B)

      1 /*
      2  *   IRC - Internet Relay Chat, src/modules/message-tags.c
      3  *   (C) 2019 Syzop & The UnrealIRCd Team
      4  *
      5  *   See file AUTHORS in IRC package for additional names of
      6  *   the programmers.
      7  *
      8  *   This program is free software; you can redistribute it and/or modify
      9  *   it under the terms of the GNU General Public License as published by
     10  *   the Free Software Foundation; either version 1, or (at your option)
     11  *   any later version.
     12  *
     13  *   This program is distributed in the hope that it will be useful,
     14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  *   GNU General Public License for more details.
     17  *
     18  *   You should have received a copy of the GNU General Public License
     19  *   along with this program; if not, write to the Free Software
     20  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     21  */
     22 
     23 #include "unrealircd.h"
     24 
     25 ModuleHeader MOD_HEADER
     26   = {
     27 	"message-tags",
     28 	"5.0",
     29 	"Message tags CAP", 
     30 	"UnrealIRCd Team",
     31 	"unrealircd-6",
     32 	};
     33 
     34 long CAP_MESSAGE_TAGS = 0L;
     35 const char *_mtags_to_string(MessageTag *m, Client *client);
     36 void _parse_message_tags(Client *client, char **str, MessageTag **mtag_list);
     37 
     38 MOD_TEST()
     39 {
     40 	MARK_AS_OFFICIAL_MODULE(modinfo);
     41 
     42 	EfunctionAddConstString(modinfo->handle, EFUNC_MTAGS_TO_STRING, _mtags_to_string);
     43 	EfunctionAddVoid(modinfo->handle, EFUNC_PARSE_MESSAGE_TAGS, _parse_message_tags);
     44 
     45 	return 0;
     46 }
     47 
     48 MOD_INIT()
     49 {
     50 	ClientCapabilityInfo cap;
     51 
     52 	MARK_AS_OFFICIAL_MODULE(modinfo);
     53 
     54 	memset(&cap, 0, sizeof(cap));
     55 	cap.name = "message-tags";
     56 	ClientCapabilityAdd(modinfo->handle, &cap, &CAP_MESSAGE_TAGS);
     57 	return MOD_SUCCESS;
     58 }
     59 
     60 MOD_LOAD()
     61 {
     62 	return MOD_SUCCESS;
     63 }
     64 
     65 MOD_UNLOAD()
     66 {
     67 	return MOD_SUCCESS;
     68 }
     69 
     70 /** Unescape a message tag (name or value).
     71  * @param in  The input string
     72  * @param out The output string for writing
     73  * @note  No size checking, so ensure that the output buffer
     74  *        is at least as long as the input buffer.
     75  */
     76 void message_tag_unescape(char *in, char *out)
     77 {
     78 	for (; *in; in++)
     79 	{
     80 		if (*in == '\\')
     81 		{
     82 			in++;
     83 			if (*in == ':')
     84 				*out++ = ';';  /* \: to ; */
     85 			else if (*in == 's')
     86 				*out++ = ' ';  /* \s to SPACE */
     87 			else if (*in == 'r')
     88 				*out++ = '\r'; /* \r to CR */
     89 			else if (*in == 'n')
     90 				*out++ = '\n'; /* \n to LF */
     91 			else if (*in == '\0')
     92 				break; /* unfinished escaping (\) */
     93 			else
     94 				*out++ = *in; /* all rest is as-is */
     95 			continue;
     96 		}
     97 		*out++ = *in;
     98 	}
     99 	*out = '\0';
    100 }
    101 
    102 /** Escape a message tag (name or value).
    103  * @param in  The input string
    104  * @param out The output string for writing
    105  * @note  No size checking, so ensure that the output buffer
    106  *        is at least twice as long as the input buffer + 1.
    107  */
    108 void message_tag_escape(char *in, char *out)
    109 {
    110 	for (; *in; in++)
    111 	{
    112 		if (*in == ';')
    113 		{
    114 			*out++ = '\\';
    115 			*out++ = ':';
    116 		} else
    117 		if (*in == ' ')
    118 		{
    119 			*out++ = '\\';
    120 			*out++ = 's';
    121 		} else
    122 		if (*in == '\\')
    123 		{
    124 			*out++ = '\\';
    125 			*out++ = '\\';
    126 		} else
    127 		if (*in == '\r')
    128 		{
    129 			*out++ = '\\';
    130 			*out++ = 'r';
    131 		} else
    132 		if (*in == '\n')
    133 		{
    134 			*out++ = '\\';
    135 			*out++ = 'n';
    136 		} else
    137 		{
    138 			*out++ = *in;
    139 		}
    140 	}
    141 	*out = '\0';
    142 }
    143 
    144 /** Incoming filter for message tags */
    145 int message_tag_ok(Client *client, char *name, char *value)
    146 {
    147 	MessageTagHandler *m;
    148 
    149 	m = MessageTagHandlerFind(name);
    150 	if (!m)
    151 	{
    152 		/* Permit unknown message tags from trusted servers */
    153 		if (IsServer(client) || !MyConnect(client))
    154 			return 1;
    155 
    156 		return 0;
    157 	}
    158 
    159 	if (m->is_ok(client, name, value))
    160 		return 1;
    161 
    162 	return 0;
    163 }
    164 
    165 void _parse_message_tags(Client *client, char **str, MessageTag **mtag_list)
    166 {
    167 	char *remainder;
    168 	char *element, *p, *x;
    169 	static char name[8192], value[8192];
    170 	MessageTag *m;
    171 
    172 	remainder = strchr(*str, ' ');
    173 	if (remainder)
    174 		*remainder = '\0';
    175 
    176 	if (!IsServer(client) && (strlen(*str) > 4094))
    177 	{
    178 		sendnumeric(client, ERR_INPUTTOOLONG);
    179 		remainder = NULL; /* stop parsing */
    180 	}
    181 
    182 	if (!remainder)
    183 	{
    184 		/* A message with only message tags (or starting with @ anyway).
    185 		 * This is useless. So we make it point to the NUL byte,
    186 		 * aka: empty message.
    187 		 * This is also used by a line-length-check above to force the
    188 		 * same error condition ("don't parse this").
    189 		 */
    190 		for (; **str; *str += 1);
    191 		return;
    192 	}
    193 
    194 	/* Now actually parse the tags: */
    195 	for (element = strtoken(&p, *str+1, ";"); element; element = strtoken(&p, NULL, ";"))
    196 	{
    197 		*name = *value = '\0';
    198 
    199 		/* Element has style: 'name=value', or it could be just 'name' */
    200 		x = strchr(element, '=');
    201 		if (x)
    202 		{
    203 			*x++ = '\0';
    204 			message_tag_unescape(x, value);
    205 		}
    206 		message_tag_unescape(element, name);
    207 
    208 		/* Let the message tag handler check if this mtag is
    209 		 * acceptable. If so, we add it to the list.
    210 		 */
    211 		if (message_tag_ok(client, name, value))
    212 		{
    213 			m = safe_alloc(sizeof(MessageTag));
    214 			safe_strdup(m->name, name);
    215 			/* Both NULL and empty become NULL: */
    216 			if (!*value)
    217 				m->value = NULL;
    218 			else /* a real value... */
    219 				safe_strdup(m->value, value);
    220 			AddListItem(m, *mtag_list);
    221 		}
    222 	}
    223 
    224 	*str = remainder + 1;
    225 }
    226 
    227 /** Outgoing filter for tags */
    228 int client_accepts_tag(const char *token, Client *client)
    229 {
    230 	MessageTagHandler *m;
    231 
    232 	/* Send all tags to remote links, without checking here.
    233 	 * Note that mtags_to_string() already prevents sending messages
    234 	 * with message tags to links without PROTOCTL MTAGS, so we can
    235 	 * simply always return 1 here, regardless of checking (again).
    236 	 */
    237 	if (IsServer(client) || !MyConnect(client))
    238 		return 1;
    239 
    240 	m = MessageTagHandlerFind(token);
    241 	if (!m)
    242 		return 0;
    243 
    244 	/* Maybe there is an outgoing filter in effect (usually not) */
    245 	if (m->should_send_to_client && !m->should_send_to_client(client))
    246 		return 0;
    247 
    248 	/* If the client has indicated 'message-tags' support then we can
    249 	 * send any message tag, regardless of other CAP's.
    250 	 */
    251 	if (HasCapability(client, "message-tags"))
    252 		return 1;
    253 
    254 	/* We continue here if the client did not indicate 'message-tags' support... */
    255 
    256 	/* If 'message-tags' is not indicated, then these cannot be sent as they don't
    257 	 * have a CAP to enable anyway (eg: msgid):
    258 	 */
    259 	if (m->flags & MTAG_HANDLER_FLAGS_NO_CAP_NEEDED)
    260 		return 0;
    261 
    262 	/* Otherwise, check if the capability is set:
    263 	 * eg 'account-tag' for 'account', 'time' for 'server-time' and so on..
    264 	 */
    265 	if (m->clicap_handler && (client->local->caps & m->clicap_handler->cap))
    266 		return 1;
    267 
    268 	return 0;
    269 }
    270 
    271 /** Return the message tag string (without @) of the message tag linked list.
    272  * Taking into account the restrictions that 'client' may have.
    273  * @returns A string (static buffer) or NULL if no tags at all (!)
    274  */
    275 const char *_mtags_to_string(MessageTag *m, Client *client)
    276 {
    277 	static char buf[4096], name[8192], value[8192];
    278 	static char tbuf[4094];
    279 
    280 	if (!m)
    281 		return NULL;
    282 
    283 	/* Remote servers need to indicate support via PROTOCTL MTAGS */
    284 	if (client->direction && IsServer(client->direction) && !SupportMTAGS(client->direction))
    285 		return NULL;
    286 
    287 	*buf = '\0';
    288 	for (; m; m = m->next)
    289 	{
    290 		if (!client_accepts_tag(m->name, client))
    291 			continue;
    292 		if (m->value)
    293 		{
    294 			message_tag_escape(m->name, name);
    295 			message_tag_escape(m->value, value);
    296 			snprintf(tbuf, sizeof(tbuf), "%s=%s;", name, value);
    297 		} else {
    298 			message_tag_escape(m->name, name);
    299 			snprintf(tbuf, sizeof(tbuf), "%s;", name);
    300 		}
    301 		strlcat(buf, tbuf, sizeof(buf));
    302 	}
    303 
    304 	if (!*buf)
    305 		return NULL;
    306 
    307 	/* Strip off the final semicolon */
    308 	buf[strlen(buf)-1] = '\0';
    309 
    310 	return buf;
    311 }