unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
json.c (19047B)
1 /************************************************************************ 2 * UnralIRCd JSON functions, src/json.c 3 * (C) 2021-.. Bram Matthys (Syzop) and the UnrealIRCd Team 4 * License: GPLv2 or later 5 */ 6 7 #include "unrealircd.h" 8 9 /** @file 10 * @brief JSON functions - used for logging and RPC. 11 */ 12 13 /** Are we currently in the logging code? */ 14 int log_json_filter = 0; 15 16 /** Calculate expansion of a JSON string thanks to double escaping. 17 * orig => JSON => IRC 18 * " => \" => \\" 19 * \ => \\ => \\\\ 20 */ 21 int json_dump_string_length(const char *s) 22 { 23 int len = 0; 24 for (; *s; s++) 25 { 26 if (*s == '\\') 27 len += 4; 28 else if (*s == '"') 29 len += 3; 30 else 31 len++; 32 } 33 return len; 34 } 35 36 /** Convert a regular string value to a JSON string. 37 * In UnrealIRCd, this must be used instead of json_string() 38 * as we may use non-UTF8 sequences. Also, this takes care 39 * of using json_null() if the string was NULL, which is 40 * usually what we want as well. 41 * @param s Input string 42 * @returns a json string value or json null value. 43 */ 44 json_t *json_string_unreal(const char *s) 45 { 46 char buf1[512], buf2[512]; 47 char *verified_s; 48 const char *stripped; 49 50 if (s == NULL) 51 return json_null(); 52 53 if (log_json_filter) 54 { 55 stripped = StripControlCodesEx(s, buf1, sizeof(buf1), UNRL_STRIP_LOW_ASCII|UNRL_STRIP_KEEP_LF); 56 verified_s = unrl_utf8_make_valid(buf1, buf2, sizeof(buf2), 0); 57 } else { 58 verified_s = unrl_utf8_make_valid(s, buf2, sizeof(buf2), 0); 59 } 60 61 return json_string(verified_s); 62 } 63 64 const char *json_object_get_string(json_t *j, const char *name) 65 { 66 json_t *v = json_object_get(j, name); 67 return v ? json_string_value(v) : NULL; 68 } 69 70 /** Get integer value of a JSON object. 71 * @param j The JSON object that should contain a 'name' item 72 * @param name The item to search for 73 * @param default_value The value to return when the JSON object 'name' is not found 74 * or not an integer. 75 * @returns The integer value, or default_value if the object does not exist or is not an integer. 76 */ 77 int json_object_get_integer(json_t *j, const char *name, int default_value) 78 { 79 json_t *v = json_object_get(j, name); 80 if (!v || !json_is_integer(v)) 81 return default_value; 82 return json_integer_value(v); 83 } 84 85 int json_object_get_boolean(json_t *j, const char *name, int default_value) 86 { 87 json_t *v = json_object_get(j, name); 88 if (!v) 89 return default_value; 90 if (json_is_true(v)) 91 return 1; 92 return 0; 93 } 94 95 #define json_string __BAD___DO__NOT__USE__JSON__STRING__PLZ 96 97 const char *json_get_value(json_t *t) 98 { 99 static char buf[32]; 100 101 if (json_is_string(t)) 102 return json_string_value(t); 103 104 if (json_is_integer(t)) 105 { 106 snprintf(buf, sizeof(buf), "%lld", (long long)json_integer_value(t)); 107 return buf; 108 } 109 110 if (json_is_boolean(t)) 111 { 112 if (json_is_true(t)) 113 return "true"; 114 return "false"; 115 } 116 117 if (json_is_array(t)) 118 return "<array>"; 119 120 return NULL; 121 } 122 123 json_t *json_timestamp(time_t v) 124 { 125 const char *ts = timestamp_iso8601(v); 126 if (ts) 127 return json_string_unreal(ts); 128 return json_null(); 129 } 130 131 const char *timestamp_iso8601_now(void) 132 { 133 struct timeval t; 134 struct tm *tm; 135 time_t sec; 136 static char buf[64]; 137 138 gettimeofday(&t, NULL); 139 sec = t.tv_sec; 140 tm = gmtime(&sec); 141 142 snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", 143 tm->tm_year + 1900, 144 tm->tm_mon + 1, 145 tm->tm_mday, 146 tm->tm_hour, 147 tm->tm_min, 148 tm->tm_sec, 149 (int)(t.tv_usec / 1000)); 150 151 return buf; 152 } 153 154 const char *timestamp_iso8601(time_t v) 155 { 156 struct tm *tm; 157 static char buf[64]; 158 159 if (v == 0) 160 return NULL; 161 162 tm = gmtime(&v); 163 164 if (tm == NULL) 165 return NULL; 166 167 snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", 168 tm->tm_year + 1900, 169 tm->tm_mon + 1, 170 tm->tm_mday, 171 tm->tm_hour, 172 tm->tm_min, 173 tm->tm_sec, 174 0); 175 176 return buf; 177 } 178 179 void json_expand_client_security_groups(json_t *parent, Client *client) 180 { 181 SecurityGroup *s; 182 json_t *child = json_array(); 183 json_object_set_new(parent, "security-groups", child); 184 185 /* We put known-users or unknown-users at the beginning. 186 * The latter is special and doesn't actually exist 187 * in the linked list, hence the special code here, 188 * and again later in the for loop to skip it. 189 */ 190 if (user_allowed_by_security_group_name(client, "known-users")) 191 json_array_append_new(child, json_string_unreal("known-users")); 192 else 193 json_array_append_new(child, json_string_unreal("unknown-users")); 194 195 for (s = securitygroups; s; s = s->next) 196 if (strcmp(s->name, "known-users") && user_allowed_by_security_group(client, s)) 197 json_array_append_new(child, json_string_unreal(s->name)); 198 } 199 200 /* detail=0: only name, id 201 * detail=1: only name, id, hostname, ip, details, geoip 202 * detail=2: everything, except 'channels' 203 * detail=3: everything, with 'channels' being a max 384 character string (meant for JSON logging only) 204 * detail=4: everything, with 'channels' object (full). 205 */ 206 void json_expand_client(json_t *j, const char *key, Client *client, int detail) 207 { 208 char buf[BUFSIZE+1]; 209 json_t *child; 210 json_t *user = NULL; 211 time_t ts; 212 213 if (key) 214 { 215 child = json_object(); 216 json_object_set_new(j, key, child); 217 } else { 218 child = j; 219 } 220 221 /* First the information that is available for ALL client types: */ 222 json_object_set_new(child, "name", json_string_unreal(client->name)); 223 json_object_set_new(child, "id", json_string_unreal(client->id)); 224 225 if (detail == 0) 226 return; 227 228 /* hostname is available for all, it just depends a bit on whether it is DNS or IP */ 229 if (client->user && *client->user->realhost) 230 json_object_set_new(child, "hostname", json_string_unreal(client->user->realhost)); 231 else if (client->local && *client->local->sockhost) 232 json_object_set_new(child, "hostname", json_string_unreal(client->local->sockhost)); 233 else 234 json_object_set_new(child, "hostname", json_string_unreal(GetIP(client))); 235 236 /* same for ip, is there for all (well, some services pseudo-users may not have one) */ 237 json_object_set_new(child, "ip", json_string_unreal(client->ip)); 238 /* client.details is always available: it is nick!user@host, nick@host, server@host 239 * server@ip, or just server. 240 */ 241 if (client->user) 242 { 243 snprintf(buf, sizeof(buf), "%s!%s@%s", client->name, client->user->username, client->user->realhost); 244 json_object_set_new(child, "details", json_string_unreal(buf)); 245 } else if (client->ip) { 246 if (*client->name) 247 snprintf(buf, sizeof(buf), "%s@%s", client->name, client->ip); 248 else 249 snprintf(buf, sizeof(buf), "[%s]", client->ip); 250 json_object_set_new(child, "details", json_string_unreal(buf)); 251 } else { 252 json_object_set_new(child, "details", json_string_unreal(client->name)); 253 } 254 255 if (detail < 2) 256 { 257 RunHook(HOOKTYPE_JSON_EXPAND_CLIENT, client, detail, child); 258 return; 259 } 260 261 if (client->local && client->local->listener) 262 json_object_set_new(child, "server_port", json_integer(client->local->listener->port)); 263 if (client->local && client->local->port) 264 json_object_set_new(child, "client_port", json_integer(client->local->port)); 265 if ((ts = get_creationtime(client))) 266 json_object_set_new(child, "connected_since", json_timestamp(ts)); 267 if (client->local && client->local->idle_since) 268 json_object_set_new(child, "idle_since", json_timestamp(client->local->idle_since)); 269 270 if (client->user) 271 { 272 char buf[512]; 273 const char *str; 274 /* client.user */ 275 user = json_object(); 276 json_object_set_new(child, "user", user); 277 278 json_object_set_new(user, "username", json_string_unreal(client->user->username)); 279 if (!BadPtr(client->info)) 280 json_object_set_new(user, "realname", json_string_unreal(client->info)); 281 if (has_user_mode(client, 'x') && client->user->virthost && strcmp(client->user->virthost, client->user->realhost)) 282 json_object_set_new(user, "vhost", json_string_unreal(client->user->virthost)); 283 if (*client->user->cloakedhost) 284 json_object_set_new(user, "cloakedhost", json_string_unreal(client->user->cloakedhost)); 285 if (client->uplink) 286 json_object_set_new(user, "servername", json_string_unreal(client->uplink->name)); 287 if (IsLoggedIn(client)) 288 json_object_set_new(user, "account", json_string_unreal(client->user->account)); 289 json_object_set_new(user, "reputation", json_integer(GetReputation(client))); 290 json_expand_client_security_groups(user, client); 291 292 /* user modes and snomasks */ 293 get_usermode_string_r(client, buf, sizeof(buf)); 294 json_object_set_new(user, "modes", json_string_unreal(buf+1)); 295 if (client->user->snomask) 296 json_object_set_new(user, "snomasks", json_string_unreal(client->user->snomask)); 297 298 /* if oper then we can possibly expand a bit more */ 299 str = get_operlogin(client); 300 if (str) 301 json_object_set_new(user, "operlogin", json_string_unreal(str)); 302 str = get_operclass(client); 303 if (str) 304 json_object_set_new(user, "operclass", json_string_unreal(str)); 305 /* For detail>2 we will include the channels. 306 * Even if the user is on 0 channels we include "channels":[] 307 * so it is clear that the user is on 0 channels and it is 308 * not because of low detail level that channels are skipped. 309 */ 310 if (detail > 2) 311 { 312 Membership *m; 313 int cnt = 0; 314 int len = 0; 315 json_t *channels = json_array(); 316 json_object_set_new(user, "channels", channels); 317 318 if (detail == 3) 319 { 320 /* Short format, mainly for JSON logging */ 321 for (m = client->user->channel; m; m = m->next) 322 { 323 len += json_dump_string_length(m->channel->name); 324 if (len > 384) 325 { 326 /* Truncated */ 327 json_array_append_new(channels, json_string_unreal("...")); 328 break; 329 } 330 json_array_append_new(channels, json_string_unreal(m->channel->name)); 331 } 332 } else { 333 /* Long format for JSON-RPC */ 334 for (m = client->user->channel; m; m = m->next) 335 { 336 json_t *e = json_object(); 337 json_object_set_new(e, "name", json_string_unreal(m->channel->name)); 338 if (*m->member_modes) 339 json_object_set_new(e, "level", json_string_unreal(m->member_modes)); 340 json_array_append_new(channels, e); 341 } 342 } 343 } 344 RunHook(HOOKTYPE_JSON_EXPAND_CLIENT_USER, client, detail, child, user); 345 } else 346 if (IsMe(client)) 347 { 348 json_t *server = json_object(); 349 json_t *features; 350 351 /* client.server */ 352 json_object_set_new(child, "server", server); 353 354 if (!BadPtr(client->info)) 355 json_object_set_new(server, "info", json_string_unreal(client->info)); 356 json_object_set_new(server, "num_users", json_integer(client->server->users)); 357 json_object_set_new(server, "boot_time", json_timestamp(client->server->boottime)); 358 359 /* client.server.features */ 360 features = json_object(); 361 json_object_set_new(server, "features", features); 362 if (!BadPtr(client->server->features.software)) 363 { 364 char buf[256]; 365 snprintf(buf, sizeof(buf), "UnrealIRCd-%s", buildid); 366 json_object_set_new(features, "software", json_string_unreal(buf)); 367 } 368 json_object_set_new(features, "protocol", json_integer(UnrealProtocol)); 369 if (!BadPtr(client->server->features.usermodes)) 370 json_object_set_new(features, "usermodes", json_string_unreal(umodestring)); 371 372 /* client.server.features.chanmodes (array) */ 373 { 374 int i; 375 char buf[512]; 376 json_t *chanmodes = json_array(); 377 json_object_set_new(features, "chanmodes", chanmodes); 378 /* first one is special - wait.. is this still the case? lol. */ 379 snprintf(buf, sizeof(buf), "%s%s", CHPAR1, EXPAR1); 380 json_array_append_new(chanmodes, json_string_unreal(buf)); 381 for (i=1; i < 4; i++) 382 json_array_append_new(chanmodes, json_string_unreal(extchmstr[i])); 383 } 384 if (!BadPtr(client->server->features.nickchars)) 385 json_object_set_new(features, "nick_character_sets", json_string_unreal(charsys_get_current_languages())); 386 RunHook(HOOKTYPE_JSON_EXPAND_CLIENT_SERVER, client, detail, child, server); 387 } else 388 if (IsServer(client) && client->server) 389 { 390 /* client.server */ 391 392 /* Whenever a server is expanded, which is rare, 393 * we should probably expand as much as info as possible: 394 */ 395 json_t *server = json_object(); 396 json_t *features; 397 398 /* client.server */ 399 json_object_set_new(child, "server", server); 400 if (!BadPtr(client->info)) 401 json_object_set_new(server, "info", json_string_unreal(client->info)); 402 if (client->uplink) 403 json_object_set_new(server, "uplink", json_string_unreal(client->uplink->name)); 404 json_object_set_new(server, "num_users", json_integer(client->server->users)); 405 json_object_set_new(server, "boot_time", json_timestamp(client->server->boottime)); 406 json_object_set_new(server, "synced", json_boolean(client->server->flags.synced)); 407 json_object_set_new(server, "ulined", json_boolean(IsULine(client))); 408 409 /* client.server.features */ 410 features = json_object(); 411 json_object_set_new(server, "features", features); 412 if (!BadPtr(client->server->features.software)) 413 json_object_set_new(features, "software", json_string_unreal(client->server->features.software)); 414 json_object_set_new(features, "protocol", json_integer(client->server->features.protocol)); 415 if (!BadPtr(client->server->features.usermodes)) 416 json_object_set_new(features, "usermodes", json_string_unreal(client->server->features.usermodes)); 417 if (!BadPtr(client->server->features.chanmodes[0])) 418 { 419 /* client.server.features.chanmodes (array) */ 420 int i; 421 json_t *chanmodes = json_array(); 422 json_object_set_new(features, "chanmodes", chanmodes); 423 for (i=0; i < 4; i++) 424 json_array_append_new(chanmodes, json_string_unreal(client->server->features.chanmodes[i])); 425 } 426 if (!BadPtr(client->server->features.nickchars)) 427 json_object_set_new(features, "nick_character_sets", json_string_unreal(client->server->features.nickchars)); 428 RunHook(HOOKTYPE_JSON_EXPAND_CLIENT_SERVER, client, detail, child, server); 429 } 430 RunHook(HOOKTYPE_JSON_EXPAND_CLIENT, client, detail, child); 431 } 432 433 void json_expand_channel_ban(json_t *child, const char *banlist_name, Ban *banlist) 434 { 435 Ban *ban; 436 json_t *list, *e; 437 438 list = json_array(); 439 json_object_set_new(child, banlist_name, list); 440 for (ban = banlist; ban; ban = ban->next) 441 { 442 e = json_object(); 443 json_array_append_new(list, e); 444 json_object_set_new(e, "name", json_string_unreal(ban->banstr)); 445 json_object_set_new(e, "set_by", json_string_unreal(ban->who)); 446 json_object_set_new(e, "set_at", json_timestamp(ban->when)); 447 } 448 } 449 450 451 /* detail=1 adds bans, ban_exemptions and invite_exceptions 452 * detail=2 adds members 453 * detail=3+ makes the members more detailed 454 */ 455 void json_expand_channel(json_t *j, const char *key, Channel *channel, int detail) 456 { 457 char mode1[512], mode2[512], modes[512]; 458 json_t *child; 459 460 if (key) 461 { 462 child = json_object(); 463 json_object_set_new(j, key, child); 464 } else { 465 child = j; 466 } 467 468 json_object_set_new(child, "name", json_string_unreal(channel->name)); 469 if (detail == 0) 470 return; 471 472 json_object_set_new(child, "creation_time", json_timestamp(channel->creationtime)); 473 json_object_set_new(child, "num_users", json_integer(channel->users)); 474 if (channel->topic) 475 { 476 json_object_set_new(child, "topic", json_string_unreal(channel->topic)); 477 json_object_set_new(child, "topic_set_by", json_string_unreal(channel->topic_nick)); 478 json_object_set_new(child, "topic_set_at", json_timestamp(channel->topic_time)); 479 } 480 481 /* Add "mode" too */ 482 channel_modes(NULL, mode1, mode2, sizeof(mode1), sizeof(mode2), channel, 0); 483 if (*mode2) 484 { 485 snprintf(modes, sizeof(modes), "%s %s", mode1+1, mode2); 486 json_object_set_new(child, "modes", json_string_unreal(modes)); 487 } else { 488 json_object_set_new(child, "modes", json_string_unreal(mode1+1)); 489 } 490 491 if (detail > 1) 492 { 493 json_expand_channel_ban(child, "bans", channel->banlist); 494 json_expand_channel_ban(child, "ban_exemptions", channel->exlist); 495 json_expand_channel_ban(child, "invite_exceptions", channel->invexlist); 496 } 497 498 if (detail >= 3) 499 { 500 Member *u; 501 json_t *list = json_array(); 502 json_object_set_new(child, "members", list); 503 504 for (u = channel->members; u; u = u->next) 505 { 506 json_t *e = json_object(); 507 if (*u->member_modes) 508 json_object_set_new(e, "level", json_string_unreal(u->member_modes)); 509 json_expand_client(e, NULL, u->client, detail-3); 510 json_array_append_new(list, e); 511 } 512 } 513 514 // Possibly later: If detail is set to 1 then expand more... 515 RunHook(HOOKTYPE_JSON_EXPAND_CHANNEL, channel, detail, child); 516 } 517 518 void json_expand_tkl(json_t *root, const char *key, TKL *tkl, int detail) 519 { 520 char buf[BUFSIZE]; 521 json_t *j; 522 523 if (key) 524 { 525 j = json_object(); 526 json_object_set_new(root, key, j); 527 } else { 528 j = root; 529 } 530 531 json_object_set_new(j, "type", json_string_unreal(tkl_type_config_string(tkl))); // Eg 'kline' 532 json_object_set_new(j, "type_string", json_string_unreal(tkl_type_string(tkl))); // Eg 'Soft K-Line' 533 json_object_set_new(j, "set_by", json_string_unreal(tkl->set_by)); 534 json_object_set_new(j, "set_at", json_timestamp(tkl->set_at)); 535 json_object_set_new(j, "expire_at", json_timestamp(tkl->expire_at)); 536 *buf = '\0'; 537 short_date(tkl->set_at, buf); 538 strlcat(buf, " GMT", sizeof(buf)); 539 json_object_set_new(j, "set_at_string", json_string_unreal(buf)); 540 if (tkl->expire_at <= 0) 541 { 542 json_object_set_new(j, "expire_at_string", json_string_unreal("Never")); 543 json_object_set_new(j, "duration_string", json_string_unreal("permanent")); 544 } else { 545 *buf = '\0'; 546 short_date(tkl->expire_at, buf); 547 strlcat(buf, " GMT", sizeof(buf)); 548 json_object_set_new(j, "expire_at_string", json_string_unreal(buf)); 549 json_object_set_new(j, "duration_string", json_string_unreal(pretty_time_val_r(buf, sizeof(buf), tkl->expire_at - tkl->set_at))); 550 } 551 json_object_set_new(j, "set_at_delta", json_integer(TStime() - tkl->set_at)); 552 if (tkl->flags & TKL_FLAG_CONFIG) 553 json_object_set_new(j, "set_in_config", json_boolean(1)); 554 if (TKLIsServerBan(tkl)) 555 { 556 json_object_set_new(j, "name", json_string_unreal(tkl_uhost(tkl, buf, sizeof(buf), 0))); 557 json_object_set_new(j, "reason", json_string_unreal(tkl->ptr.serverban->reason)); 558 } else 559 if (TKLIsNameBan(tkl)) 560 { 561 json_object_set_new(j, "name", json_string_unreal(tkl->ptr.nameban->name)); 562 json_object_set_new(j, "reason", json_string_unreal(tkl->ptr.nameban->reason)); 563 } else 564 if (TKLIsBanException(tkl)) 565 { 566 json_object_set_new(j, "name", json_string_unreal(tkl_uhost(tkl, buf, sizeof(buf), 0))); 567 json_object_set_new(j, "reason", json_string_unreal(tkl->ptr.banexception->reason)); 568 json_object_set_new(j, "exception_types", json_string_unreal(tkl->ptr.banexception->bantypes)); 569 } else 570 if (TKLIsSpamfilter(tkl)) 571 { 572 json_object_set_new(j, "name", json_string_unreal(tkl->ptr.spamfilter->match->str)); 573 json_object_set_new(j, "match_type", json_string_unreal(unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type))); 574 json_object_set_new(j, "ban_action", json_string_unreal(banact_valtostring(tkl->ptr.spamfilter->action))); 575 json_object_set_new(j, "ban_duration", json_integer(tkl->ptr.spamfilter->tkl_duration)); 576 json_object_set_new(j, "ban_duration_string", json_string_unreal(pretty_time_val_r(buf, sizeof(buf), tkl->ptr.spamfilter->tkl_duration))); 577 json_object_set_new(j, "spamfilter_targets", json_string_unreal(spamfilter_target_inttostring(tkl->ptr.spamfilter->target))); 578 json_object_set_new(j, "reason", json_string_unreal(unreal_decodespace(tkl->ptr.spamfilter->tkl_reason))); 579 } 580 }