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 }