pissircd/src/modules/message-tags.c

313 lines
7.3 KiB
C

/*
* IRC - Internet Relay Chat, src/modules/message-tags.c
* (C) 2019 Syzop & The UnrealIRCd Team
*
* See file AUTHORS in IRC package for additional names of
* the programmers.
*
* 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.
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"message-tags",
"5.0",
"Message tags CAP",
"UnrealIRCd Team",
"unrealircd-6",
};
long CAP_MESSAGE_TAGS = 0L;
const char *_mtags_to_string(MessageTag *m, Client *client);
void _parse_message_tags(Client *client, char **str, MessageTag **mtag_list);
MOD_TEST()
{
MARK_AS_OFFICIAL_MODULE(modinfo);
EfunctionAddConstString(modinfo->handle, EFUNC_MTAGS_TO_STRING, _mtags_to_string);
EfunctionAddVoid(modinfo->handle, EFUNC_PARSE_MESSAGE_TAGS, _parse_message_tags);
return 0;
}
MOD_INIT()
{
ClientCapabilityInfo cap;
MARK_AS_OFFICIAL_MODULE(modinfo);
memset(&cap, 0, sizeof(cap));
cap.name = "message-tags";
cap.flags = CLICAP_FLAGS_AFFECTS_MTAGS; /* needed explicitly */
ClientCapabilityAdd(modinfo->handle, &cap, &CAP_MESSAGE_TAGS);
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
/** Unescape a message tag (name or value).
* @param in The input string
* @param out The output string for writing
* @note No size checking, so ensure that the output buffer
* is at least as long as the input buffer.
*/
void message_tag_unescape(char *in, char *out)
{
for (; *in; in++)
{
if (*in == '\\')
{
in++;
if (*in == ':')
*out++ = ';'; /* \: to ; */
else if (*in == 's')
*out++ = ' '; /* \s to SPACE */
else if (*in == 'r')
*out++ = '\r'; /* \r to CR */
else if (*in == 'n')
*out++ = '\n'; /* \n to LF */
else if (*in == '\0')
break; /* unfinished escaping (\) */
else
*out++ = *in; /* all rest is as-is */
continue;
}
*out++ = *in;
}
*out = '\0';
}
/** Escape a message tag (name or value).
* @param in The input string
* @param out The output string for writing
* @note No size checking, so ensure that the output buffer
* is at least twice as long as the input buffer + 1.
*/
void message_tag_escape(char *in, char *out)
{
for (; *in; in++)
{
if (*in == ';')
{
*out++ = '\\';
*out++ = ':';
} else
if (*in == ' ')
{
*out++ = '\\';
*out++ = 's';
} else
if (*in == '\\')
{
*out++ = '\\';
*out++ = '\\';
} else
if (*in == '\r')
{
*out++ = '\\';
*out++ = 'r';
} else
if (*in == '\n')
{
*out++ = '\\';
*out++ = 'n';
} else
{
*out++ = *in;
}
}
*out = '\0';
}
/** Incoming filter for message tags */
int message_tag_ok(Client *client, char *name, char *value)
{
MessageTagHandler *m;
m = MessageTagHandlerFind(name);
if (!m)
{
/* Permit unknown message tags from trusted servers */
if (IsServer(client) || !MyConnect(client))
return 1;
return 0;
}
if (m->is_ok(client, name, value))
return 1;
return 0;
}
void _parse_message_tags(Client *client, char **str, MessageTag **mtag_list)
{
char *remainder;
char *element, *p, *x;
static char name[8192], value[8192];
MessageTag *m;
remainder = strchr(*str, ' ');
if (remainder)
*remainder = '\0';
if (!IsServer(client) && (strlen(*str) > 4094))
{
sendnumeric(client, ERR_INPUTTOOLONG);
remainder = NULL; /* stop parsing */
}
if (!remainder)
{
/* A message with only message tags (or starting with @ anyway).
* This is useless. So we make it point to the NUL byte,
* aka: empty message.
* This is also used by a line-length-check above to force the
* same error condition ("don't parse this").
*/
for (; **str; *str += 1);
return;
}
/* Now actually parse the tags: */
for (element = strtoken(&p, *str+1, ";"); element; element = strtoken(&p, NULL, ";"))
{
*name = *value = '\0';
/* Element has style: 'name=value', or it could be just 'name' */
x = strchr(element, '=');
if (x)
{
*x++ = '\0';
message_tag_unescape(x, value);
}
message_tag_unescape(element, name);
/* Let the message tag handler check if this mtag is
* acceptable. If so, we add it to the list.
*/
if (message_tag_ok(client, name, value))
{
m = safe_alloc(sizeof(MessageTag));
safe_strdup(m->name, name);
/* Both NULL and empty become NULL: */
if (!*value)
m->value = NULL;
else /* a real value... */
safe_strdup(m->value, value);
AddListItem(m, *mtag_list);
}
}
*str = remainder + 1;
}
/** Outgoing filter for tags */
int client_accepts_tag(const char *token, Client *client)
{
MessageTagHandler *m;
/* Send all tags to remote links, without checking here.
* Note that mtags_to_string() already prevents sending messages
* with message tags to links without PROTOCTL MTAGS, so we can
* simply always return 1 here, regardless of checking (again).
*/
if (IsServer(client) || !MyConnect(client))
return 1;
m = MessageTagHandlerFind(token);
if (!m)
return 0;
/* Maybe there is an outgoing filter in effect (usually not) */
if (m->should_send_to_client && !m->should_send_to_client(client))
return 0;
/* If the client has indicated 'message-tags' support then we can
* send any message tag, regardless of other CAP's.
*/
if (HasCapability(client, "message-tags"))
return 1;
/* We continue here if the client did not indicate 'message-tags' support... */
/* If 'message-tags' is not indicated, then these cannot be sent as they don't
* have a CAP to enable anyway (eg: msgid):
*/
if (m->flags & MTAG_HANDLER_FLAGS_NO_CAP_NEEDED)
return 0;
/* Otherwise, check if the capability is set:
* eg 'account-tag' for 'account', 'time' for 'server-time' and so on..
*/
if (m->clicap_handler && (client->local->caps & m->clicap_handler->cap))
return 1;
return 0;
}
/** Return the message tag string (without @) of the message tag linked list.
* Taking into account the restrictions that 'client' may have.
* @returns A string (static buffer) or NULL if no tags at all (!)
*/
const char *_mtags_to_string(MessageTag *m, Client *client)
{
static char buf[4096], name[8192], value[8192];
static char tbuf[4094];
if (!m)
return NULL;
/* Remote servers need to indicate support via PROTOCTL MTAGS */
if (client->direction && IsServer(client->direction) && !SupportMTAGS(client->direction))
return NULL;
*buf = '\0';
for (; m; m = m->next)
{
if (!client_accepts_tag(m->name, client))
continue;
if (m->value)
{
message_tag_escape(m->name, name);
message_tag_escape(m->value, value);
snprintf(tbuf, sizeof(tbuf), "%s=%s;", name, value);
} else {
message_tag_escape(m->name, name);
snprintf(tbuf, sizeof(tbuf), "%s;", name);
}
strlcat(buf, tbuf, sizeof(buf));
}
if (!*buf)
return NULL;
/* Strip off the final semicolon */
buf[strlen(buf)-1] = '\0';
return buf;
}