unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
whox.c (26662B)
1 /* cmd_whox.c / WHOX. 2 * based on code from charybdis and ircu. 3 * was originally made for tircd and modified to work with u4. 4 * - 2018 i <ircd@servx.org> 5 * - 2019 Bram Matthys (Syzop) <syzop@vulnscan.org> 6 * License: GPLv2 or later 7 */ 8 9 #include "unrealircd.h" 10 11 /* Module header */ 12 ModuleHeader MOD_HEADER 13 = { 14 "whox", 15 "5.0", 16 "command /who", 17 "UnrealIRCd Team", 18 "unrealircd-6", 19 }; 20 21 22 /* Defines */ 23 #define FIELD_CHANNEL 0x0001 24 #define FIELD_HOP 0x0002 25 #define FIELD_FLAGS 0x0004 26 #define FIELD_HOST 0x0008 27 #define FIELD_IP 0x0010 28 #define FIELD_IDLE 0x0020 29 #define FIELD_NICK 0x0040 30 #define FIELD_INFO 0x0080 31 #define FIELD_SERVER 0x0100 32 #define FIELD_QUERYTYPE 0x0200 /* cookie for client */ 33 #define FIELD_USER 0x0400 34 #define FIELD_ACCOUNT 0x0800 35 #define FIELD_OPLEVEL 0x1000 /* meaningless and stupid, but whatever */ 36 #define FIELD_REALHOST 0x2000 37 #define FIELD_MODES 0x4000 38 #define FIELD_REPUTATION 0x8000 39 40 #define WMATCH_NICK 0x0001 41 #define WMATCH_USER 0x0002 42 #define WMATCH_OPER 0x0004 43 #define WMATCH_HOST 0x0008 44 #define WMATCH_INFO 0x0010 45 #define WMATCH_SERVER 0x0020 46 #define WMATCH_ACCOUNT 0x0040 47 #define WMATCH_IP 0x0080 48 #define WMATCH_MODES 0x0100 49 #define WMATCH_CONTIME 0x0200 50 51 #define RPL_WHOSPCRPL 354 52 53 #define WHO_ADD 1 54 #define WHO_DEL 0 55 56 #define HasField(x, y) ((x)->fields & (y)) 57 #define IsMatch(x, y) ((x)->matchsel & (y)) 58 59 #define IsMarked(x) (moddata_client(x, whox_md).l) 60 #define SetMark(x) do { moddata_client(x, whox_md).l = 1; } while(0) 61 #define ClearMark(x) do { moddata_client(x, whox_md).l = 0; } while(0) 62 63 /* Structs */ 64 struct who_format 65 { 66 int fields; 67 int matchsel; 68 int umodes; 69 int noumodes; 70 const char *querytype; 71 int show_realhost; 72 int show_ip; 73 time_t contimemin; 74 time_t contimemax; 75 }; 76 77 /* Global variables */ 78 ModDataInfo *whox_md = NULL; 79 80 /* Forward declarations */ 81 CMD_FUNC(cmd_whox); 82 static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt); 83 static void do_who(Client *client, Client *acptr, Channel *channel, struct who_format *fmt); 84 static void do_who_on_channel(Client *client, Channel *channel, 85 int member, int operspy, struct who_format *fmt); 86 static int convert_classical_who_request(Client *client, int *parc, const char *parv[], const char **orig_mask, struct who_format *fmt); 87 const char *whox_md_serialize(ModData *m); 88 void whox_md_unserialize(const char *str, ModData *m); 89 void whox_md_free(ModData *md); 90 static void append_format(char *buf, size_t bufsize, size_t *pos, const char *fmt, ...) __attribute__((format(printf,4,5))); 91 92 MOD_INIT() 93 { 94 ModDataInfo mreq; 95 96 MARK_AS_OFFICIAL_MODULE(modinfo); 97 98 if (!CommandAdd(modinfo->handle, "WHO", cmd_whox, MAXPARA, CMD_USER)) 99 { 100 config_warn("You cannot load both cmd_whox and cmd_who. You should ONLY load the cmd_whox module."); 101 return MOD_FAILED; 102 } 103 104 memset(&mreq, 0, sizeof(mreq)); 105 mreq.name = "whox"; 106 mreq.type = MODDATATYPE_CLIENT; 107 mreq.serialize = whox_md_serialize; 108 mreq.unserialize = whox_md_unserialize; 109 mreq.free = whox_md_free; 110 mreq.sync = 0; 111 whox_md = ModDataAdd(modinfo->handle, mreq); 112 if (!whox_md) 113 { 114 config_error("could not register whox moddata"); 115 return MOD_FAILED; 116 } 117 118 ISupportAdd(modinfo->handle, "WHOX", NULL); 119 return MOD_SUCCESS; 120 } 121 122 MOD_LOAD() 123 { 124 return MOD_SUCCESS; 125 } 126 127 MOD_UNLOAD() 128 { 129 return MOD_SUCCESS; 130 } 131 132 /** whox module data operations: serialize (rare) */ 133 const char *whox_md_serialize(ModData *m) 134 { 135 static char buf[32]; 136 if (m->i == 0) 137 return NULL; /* not set */ 138 snprintf(buf, sizeof(buf), "%d", m->i); 139 return buf; 140 } 141 142 /** whox module data operations: unserialize (rare) */ 143 void whox_md_unserialize(const char *str, ModData *m) 144 { 145 m->i = atoi(str); 146 } 147 148 /** whox module data operations: free */ 149 void whox_md_free(ModData *md) 150 { 151 /* we have nothing to free actually, but we must set to zero */ 152 md->l = 0; 153 } 154 155 /** cmd_whox: standardized "extended" version of WHO. 156 * The good thing about WHOX is that it allows the client to define what 157 * output they want to see. Another good thing is that it is standardized 158 * (ok, actually it isn't... but hey!). 159 * The bad thing is that it's a lot less flexible than the original WHO 160 * we had in UnrealIRCd in 3.2.x and 4.0.x and that the WHOX spec defines 161 * that there are two ways to specify a mask (neither one is logical): 162 * WHO <mask> <flags> 163 * WHO <mask> <flags> <mask2> 164 * In case the latter is present then mask2 overrides the mask 165 * (and the original 'mask' is ignored) 166 * Yes, this indeed damn ugly. It shows (yet again) that they should never 167 * have put the mask before the flags.. what kind of logic was that? 168 * I wonder if the person who invented this also writes C functions like: 169 * int (char *param)func 170 * or types: 171 * cp mode,time=--preserve 172 * or creates configuration files like: 173 * Syzop oper { 174 * 5 maxlogins; 175 * I mean... really...? -- Syzop 176 * So, we will try to abide to the WHOX spec as much as possible but 177 * try to warn our users in case they use the 'original' UnrealIRCd 178 * WHO syntax which was WHO [+-]<flags> <mask>. 179 * Note that we won't catch all cases, but we do our best. 180 * When in doubt, we will assume WHOX so to not break the spec 181 * (which again.. doesn't exist... but hey..) 182 */ 183 CMD_FUNC(cmd_whox) 184 { 185 char *mask; 186 const char *orig_mask; 187 char ch; /* Scratch char register */ 188 const char *p; /* Scratch char pointer */ 189 int member; 190 int operspy = 0; 191 struct who_format fmt; 192 const char *s; 193 char maskcopy[BUFSIZE]; 194 Membership *lp; 195 Client *acptr; 196 197 memset(&fmt, 0, sizeof(fmt)); 198 199 if (!MyUser(client)) 200 return; 201 202 if ((parc < 2)) 203 { 204 sendnumeric(client, ERR_NEEDMOREPARAMS, "WHO"); 205 return; 206 } 207 208 if ((parc > 3) && parv[3]) 209 orig_mask = parv[3]; 210 else 211 orig_mask = parv[1]; 212 213 if (!convert_classical_who_request(client, &parc, parv, &orig_mask, &fmt)) 214 return; 215 216 /* Evaluate the flags now, we consider the second parameter 217 * as "matchFlags%fieldsToInclude,querytype" */ 218 219 if ((parc > 2) && parv[2] && *parv[2]) 220 { 221 p = parv[2]; 222 while (((ch = *(p++))) && (ch != '%') && (ch != ',')) 223 { 224 switch (ch) 225 { 226 case 'o': fmt.matchsel |= WMATCH_OPER; continue; 227 case 'n': fmt.matchsel |= WMATCH_NICK; continue; 228 case 'u': fmt.matchsel |= WMATCH_USER; continue; 229 case 'h': fmt.matchsel |= WMATCH_HOST; continue; 230 case 'i': fmt.matchsel |= WMATCH_IP; continue; 231 case 'r': fmt.matchsel |= WMATCH_INFO; continue; 232 case 's': fmt.matchsel |= WMATCH_SERVER; continue; 233 case 'a': fmt.matchsel |= WMATCH_ACCOUNT; continue; 234 case 'm': fmt.matchsel |= WMATCH_MODES; continue; 235 case 't': fmt.matchsel |= WMATCH_CONTIME; continue; 236 case 'R': 237 if (IsOper(client)) 238 fmt.show_realhost = 1; 239 continue; 240 case 'I': 241 if (IsOper(client)) 242 fmt.show_ip = 1; 243 continue; 244 } 245 } 246 } 247 248 if ((parc > 2) && (s = strchr(parv[2], '%')) != NULL) 249 { 250 s++; 251 for (; *s != '\0'; s++) 252 { 253 switch (*s) 254 { 255 case 'c': fmt.fields |= FIELD_CHANNEL; break; 256 case 'd': fmt.fields |= FIELD_HOP; break; 257 case 'f': fmt.fields |= FIELD_FLAGS; break; 258 case 'h': fmt.fields |= FIELD_HOST; break; 259 case 'H': fmt.fields |= FIELD_REALHOST; break; 260 case 'i': fmt.fields |= FIELD_IP; break; 261 case 'l': fmt.fields |= FIELD_IDLE; break; 262 case 'n': fmt.fields |= FIELD_NICK; break; 263 case 'r': fmt.fields |= FIELD_INFO; break; 264 case 's': fmt.fields |= FIELD_SERVER; break; 265 case 't': fmt.fields |= FIELD_QUERYTYPE; break; 266 case 'u': fmt.fields |= FIELD_USER; break; 267 case 'a': fmt.fields |= FIELD_ACCOUNT; break; 268 case 'm': fmt.fields |= FIELD_MODES; break; 269 case 'o': fmt.fields |= FIELD_OPLEVEL; break; 270 case 'R': fmt.fields |= FIELD_REPUTATION; break; 271 case ',': 272 s++; 273 fmt.querytype = s; 274 s += strlen(s); 275 s--; 276 break; 277 } 278 } 279 if (BadPtr(fmt.querytype) || (strlen(fmt.querytype) > 3)) 280 fmt.querytype = "0"; 281 } 282 283 strlcpy(maskcopy, orig_mask, sizeof maskcopy); 284 mask = maskcopy; 285 286 collapse(mask); 287 288 if ((ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) && 289 ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL))) 290 { 291 operspy = 1; 292 } 293 294 if (fmt.matchsel & WMATCH_MODES) 295 { 296 char *s = mask; 297 int *umodes; 298 int what = WHO_ADD; 299 300 while (*s) 301 { 302 Umode *um; 303 304 switch (*s) 305 { 306 case '+': 307 what = WHO_ADD; 308 s++; 309 break; 310 case '-': 311 what = WHO_DEL; 312 s++; 313 break; 314 } 315 316 if (!*s) 317 break; 318 319 if (what == WHO_ADD) 320 umodes = &fmt.umodes; 321 else 322 umodes = &fmt.noumodes; 323 324 for (um = usermodes; um; um = um->next) 325 { 326 if (um->letter == *s) 327 { 328 *umodes |= um->mode; 329 break; 330 } 331 } 332 s++; 333 } 334 335 if (!IsOper(client)) 336 { 337 /* these are usermodes regular users may search for. just oper now. */ 338 fmt.umodes &= UMODE_OPER; 339 fmt.noumodes &= UMODE_OPER; 340 } 341 } 342 343 /* match connect time */ 344 if (fmt.matchsel & WMATCH_CONTIME) 345 { 346 char *s = mask; 347 time_t currenttime = TStime(); 348 349 fmt.contimemin = 0; 350 fmt.contimemax = 0; 351 352 switch (*s) 353 { 354 case '<': 355 if (*s++) 356 fmt.contimemin = currenttime - config_checkval(s, CFG_TIME); 357 break; 358 case '>': 359 if (*s++) 360 fmt.contimemax = currenttime - config_checkval(s, CFG_TIME); 361 break; 362 } 363 } 364 365 /* '/who #some_channel' */ 366 if (IsChannelName(mask)) 367 { 368 Channel *channel = NULL; 369 370 /* List all users on a given channel */ 371 if ((channel = find_channel(orig_mask)) != NULL) 372 { 373 if (IsMember(client, channel) || operspy) 374 do_who_on_channel(client, channel, 1, operspy, &fmt); 375 else if (!SecretChannel(channel)) 376 do_who_on_channel(client, channel, 0, operspy, &fmt); 377 } 378 379 sendnumeric(client, RPL_ENDOFWHO, mask); 380 return; 381 } 382 383 if (ValidatePermissionsForPath("channel:see:who:secret",client,NULL,NULL,NULL) || 384 ValidatePermissionsForPath("channel:see:whois",client,NULL,NULL,NULL)) 385 { 386 operspy = 1; 387 } 388 389 /* '/who 0' for a global list. this forces clients to actually 390 * request a full list. I presume its because of too many typos 391 * with "/who" ;) --fl 392 */ 393 if (!strcmp(mask, "0")) 394 who_global(client, NULL, 0, &fmt); 395 else 396 who_global(client, mask, operspy, &fmt); 397 398 sendnumeric(client, RPL_ENDOFWHO, mask); 399 } 400 401 /* do_match 402 * inputs - pointer to client requesting who 403 * - pointer to client to do who on 404 * - char * mask to match 405 * - format options 406 * output - 1 if match, 0 if no match 407 * side effects - NONE 408 */ 409 static int do_match(Client *client, Client *acptr, char *mask, struct who_format *fmt) 410 { 411 if (mask == NULL) 412 return 1; 413 414 /* default */ 415 if (fmt->matchsel == 0 && (match_simple(mask, acptr->name) || 416 match_simple(mask, acptr->user->username) || 417 match_simple(mask, GetHost(acptr)) || 418 (IsOper(client) && 419 (match_simple(mask, acptr->user->realhost) || 420 (acptr->ip && 421 match_simple(mask, acptr->ip)))))) 422 { 423 return 1; 424 } 425 426 /* match nick */ 427 if (IsMatch(fmt, WMATCH_NICK) && match_simple(mask, acptr->name)) 428 return 1; 429 430 /* match username */ 431 if (IsMatch(fmt, WMATCH_USER) && match_simple(mask, acptr->user->username)) 432 return 1; 433 434 /* match server */ 435 if (IsMatch(fmt, WMATCH_SERVER) && IsOper(client) && match_simple(mask, acptr->user->server)) 436 return 1; 437 438 /* match hostname */ 439 if (IsMatch(fmt, WMATCH_HOST) && (match_simple(mask, GetHost(acptr)) || 440 (IsOper(client) && (match_simple(mask, acptr->user->realhost) || 441 (acptr->ip && match_simple(mask, acptr->ip)))))) 442 { 443 return 1; 444 } 445 446 /* match realname */ 447 if (IsMatch(fmt, WMATCH_INFO) && match_simple(mask, acptr->info)) 448 return 1; 449 450 /* match ip address */ 451 if (IsMatch(fmt, WMATCH_IP) && IsOper(client) && acptr->ip && 452 match_user(mask, acptr, MATCH_CHECK_IP)) 453 return 1; 454 455 /* match account */ 456 if (IsMatch(fmt, WMATCH_ACCOUNT) && IsLoggedIn(acptr) && match_simple(mask, acptr->user->account)) 457 { 458 return 1; 459 } 460 461 /* match usermodes */ 462 if (IsMatch(fmt, WMATCH_MODES) && (fmt->umodes || fmt->noumodes)) 463 { 464 long umodes = acptr->umodes; 465 if ((acptr->umodes & UMODE_HIDEOPER) && !IsOper(client)) 466 umodes &= ~UMODE_OPER; /* pretend -o if +H */ 467 /* Now check 'umodes' (not acptr->umodes), 468 * If multiple conditions are specified it is an 469 * AND condition and not an OR. 470 */ 471 if (((umodes & fmt->umodes) == fmt->umodes) && 472 ((umodes & fmt->noumodes) == 0)) 473 { 474 return 1; 475 } 476 } 477 478 /* match connect time */ 479 if (IsMatch(fmt, WMATCH_CONTIME) && MyConnect(acptr) && (fmt->contimemin || fmt->contimemax)) 480 { 481 if (fmt->contimemin && (acptr->local->creationtime > fmt->contimemin)) 482 return 1; 483 484 if (fmt->contimemax && (acptr->local->creationtime < fmt->contimemax)) 485 return 1; 486 } 487 488 return 0; 489 } 490 491 /* who_common_channel 492 * inputs - pointer to client requesting who 493 * - pointer to channel. 494 * - char * mask to match 495 * - int if oper on a server or not 496 * - pointer to int maxmatches 497 * - format options 498 * output - NONE 499 * side effects - lists matching clients on specified channel, 500 * marks matched clients. 501 * 502 * NOTE: only call this from who_global() due to client marking! 503 */ 504 505 static void who_common_channel(Client *client, Channel *channel, 506 char *mask, int *maxmatches, struct who_format *fmt) 507 { 508 Member *cm = channel->members; 509 Client *acptr; 510 Hook *h; 511 int i = 0; 512 513 for (cm = channel->members; cm; cm = cm->next) 514 { 515 acptr = cm->client; 516 517 if (IsMarked(acptr)) 518 continue; 519 520 if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr)) 521 continue; 522 523 for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next) 524 { 525 i = (*(h->func.intfunc))(acptr,channel); 526 if (i != 0) 527 break; 528 } 529 530 if (i != 0 && !(check_channel_access(client, channel, "hoaq")) && !(check_channel_access(acptr, channel, "hoaq") || check_channel_access(acptr,channel, "v"))) 531 continue; 532 533 SetMark(acptr); 534 535 if (*maxmatches > 0) 536 { 537 if (do_match(client, acptr, mask, fmt)) 538 { 539 do_who(client, acptr, NULL, fmt); 540 --(*maxmatches); 541 } 542 } 543 } 544 } 545 546 /* 547 * who_global 548 * 549 * inputs - pointer to client requesting who 550 * - char * mask to match 551 * - int if oper on a server or not 552 * - int if operspy or not 553 * - format options 554 * output - NONE 555 * side effects - do a global scan of all clients looking for match 556 * this is slightly expensive on EFnet ... 557 * marks assumed cleared for all clients initially 558 * and will be left cleared on return 559 */ 560 561 static void who_global(Client *client, char *mask, int operspy, struct who_format *fmt) 562 { 563 Client *hunted = NULL; 564 Client *acptr; 565 int maxmatches = IsOper(client) ? INT_MAX : WHOLIMIT; 566 567 /* If searching for a nick explicitly, then include it later on in the result: */ 568 if (mask && ((fmt->matchsel & WMATCH_NICK) || (fmt->matchsel == 0))) 569 hunted = find_user(mask, NULL); 570 571 /* Initialize the markers to zero */ 572 list_for_each_entry(acptr, &client_list, client_node) 573 ClearMark(acptr); 574 575 /* First, if not operspy, then list all matching clients on common channels */ 576 if (!operspy) 577 { 578 Membership *lp; 579 580 for (lp = client->user->channel; lp; lp = lp->next) 581 who_common_channel(client, lp->channel, mask, &maxmatches, fmt); 582 } 583 584 /* Second, list all matching visible clients. */ 585 list_for_each_entry(acptr, &client_list, client_node) 586 { 587 if (!IsUser(acptr)) 588 continue; 589 590 if (IsInvisible(acptr) && !operspy && (client != acptr) && (acptr != hunted)) 591 continue; 592 593 if (IsMarked(acptr)) 594 continue; 595 596 if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr)) 597 continue; 598 599 if (maxmatches > 0) 600 { 601 if (do_match(client, acptr, mask, fmt)) 602 { 603 do_who(client, acptr, NULL, fmt); 604 --maxmatches; 605 } 606 } 607 } 608 609 if (maxmatches <= 0) 610 sendnumeric(client, ERR_TOOMANYMATCHES, "WHO", "output too large, truncated"); 611 } 612 613 /* 614 * do_who_on_channel 615 * 616 * inputs - pointer to client requesting who 617 * - pointer to channel to do who on 618 * - The "real name" of this channel 619 * - int if client is a server oper or not 620 * - int if client is member or not 621 * - format options 622 * output - NONE 623 * side effects - do a who on given channel 624 */ 625 626 static void do_who_on_channel(Client *client, Channel *channel, 627 int member, int operspy, struct who_format *fmt) 628 { 629 Member *cm = channel->members; 630 Hook *h; 631 int i = 0; 632 633 for (cm = channel->members; cm; cm = cm->next) 634 { 635 Client *acptr = cm->client; 636 637 if (IsMatch(fmt, WMATCH_OPER) && !IsOper(acptr)) 638 continue; 639 640 for (h = Hooks[HOOKTYPE_VISIBLE_IN_CHANNEL]; h; h = h->next) 641 { 642 i = (*(h->func.intfunc))(acptr,channel); 643 if (i != 0) 644 break; 645 } 646 647 if (!operspy && (acptr != client) && i != 0 && !(check_channel_access(client, channel, "hoaq")) && !(check_channel_access(acptr, channel, "hoaq") || check_channel_access(acptr,channel, "v"))) 648 continue; 649 650 if (member || !IsInvisible(acptr)) 651 do_who(client, acptr, channel, fmt); 652 } 653 } 654 655 /* 656 * append_format 657 * 658 * inputs - pointer to buffer 659 * - size of buffer 660 * - pointer to position 661 * - format string 662 * - arguments for format 663 * output - NONE 664 * side effects - position incremented, possibly beyond size of buffer 665 * this allows detecting overflow 666 */ 667 668 static void append_format(char *buf, size_t bufsize, size_t *pos, const char *fmt, ...) 669 { 670 size_t max, result; 671 va_list ap; 672 673 max = *pos >= bufsize ? 0 : bufsize - *pos; 674 va_start(ap, fmt); 675 result = vsnprintf(buf + *pos, max, fmt, ap); 676 va_end(ap); 677 *pos += result; 678 } 679 680 /* 681 * show_ip() - asks if the true IP should be shown when source is 682 * asking for info about target 683 * 684 * Inputs - client who is asking 685 * - acptr who do we want the info on 686 * Output - returns 1 if clear IP can be shown, otherwise 0 687 * Side Effects - none 688 */ 689 690 static int show_ip(Client *client, Client *acptr) 691 { 692 if (IsServer(acptr)) 693 return 0; 694 else if ((client != NULL) && (MyConnect(client) && !IsOper(client)) && (client == acptr)) 695 return 1; 696 else if (IsHidden(acptr) && ((client != NULL) && !IsOper(client))) 697 return 0; 698 else 699 return 1; 700 } 701 702 /* 703 * do_who 704 * 705 * inputs - pointer to client requesting who 706 * - pointer to client to do who on 707 * - channel or NULL 708 * - format options 709 * output - NONE 710 * side effects - do a who on given person 711 */ 712 713 static void do_who(Client *client, Client *acptr, Channel *channel, struct who_format *fmt) 714 { 715 char status[20]; 716 char str[510 + 1]; 717 size_t pos; 718 int hide = (FLAT_MAP && !IsOper(client)) ? 1 : 0; 719 int i = 0; 720 Hook *h; 721 722 if (acptr->user->away) 723 status[i++] = 'G'; 724 else 725 status[i++] = 'H'; 726 727 if (IsRegNick(acptr)) 728 status[i++] = 'r'; 729 730 if (IsSecureConnect(acptr)) 731 status[i++] = 's'; 732 733 for (h = Hooks[HOOKTYPE_WHO_STATUS]; h; h = h->next) 734 { 735 int ret = (*(h->func.intfunc))(client, acptr, NULL, NULL, status, 0); 736 if (ret != 0) 737 status[i++] = (char)ret; 738 } 739 740 if (IsOper(acptr) && (!IsHideOper(acptr) || client == acptr || IsOper(client))) 741 status[i++] = '*'; 742 743 if (IsOper(acptr) && (IsHideOper(acptr) && client != acptr && IsOper(client))) 744 status[i++] = '!'; 745 746 if (channel) 747 { 748 Membership *lp; 749 750 if ((lp = find_membership_link(acptr->user->channel, channel))) 751 { 752 if (!(fmt->fields || HasCapability(client, "multi-prefix"))) 753 { 754 /* Standard NAMES reply (single character) */ 755 char c = mode_to_prefix(*lp->member_modes); 756 if (c) 757 status[i++] = c; 758 } 759 else 760 { 761 /* NAMES reply with all rights included (multi-prefix / NAMESX) */ 762 strcpy(&status[i], modes_to_prefix(lp->member_modes)); 763 i += strlen(&status[i]); 764 } 765 } 766 } 767 768 status[i] = '\0'; 769 770 if (fmt->fields == 0) 771 { 772 char *host; 773 if (fmt->show_realhost) 774 host = acptr->user->realhost; 775 else if (fmt->show_ip) 776 host = GetIP(acptr); 777 else 778 host = GetHost(acptr); 779 sendnumeric(client, RPL_WHOREPLY, 780 channel ? channel->name : "*", 781 acptr->user->username, host, 782 hide ? "*" : acptr->user->server, 783 acptr->name, status, hide ? 0 : acptr->hopcount, acptr->info); 784 } else 785 { 786 str[0] = '\0'; 787 pos = 0; 788 append_format(str, sizeof str, &pos, ":%s %d %s", me.name, RPL_WHOSPCRPL, client->name); 789 if (HasField(fmt, FIELD_QUERYTYPE)) 790 append_format(str, sizeof str, &pos, " %s", fmt->querytype); 791 if (HasField(fmt, FIELD_CHANNEL)) 792 append_format(str, sizeof str, &pos, " %s", channel ? channel->name : "*"); 793 if (HasField(fmt, FIELD_USER)) 794 append_format(str, sizeof str, &pos, " %s", acptr->user->username); 795 if (HasField(fmt, FIELD_IP)) 796 { 797 if (show_ip(client, acptr) && acptr->ip) 798 append_format(str, sizeof str, &pos, " %s", acptr->ip); 799 else 800 append_format(str, sizeof str, &pos, " %s", "255.255.255.255"); 801 } 802 803 if (HasField(fmt, FIELD_HOST) || HasField(fmt, FIELD_REALHOST)) 804 { 805 if (IsOper(client) && HasField(fmt, FIELD_REALHOST)) 806 append_format(str, sizeof str, &pos, " %s", acptr->user->realhost); 807 else 808 append_format(str, sizeof str, &pos, " %s", GetHost(acptr)); 809 } 810 811 if (HasField(fmt, FIELD_SERVER)) 812 append_format(str, sizeof str, &pos, " %s", hide ? "*" : acptr->user->server); 813 if (HasField(fmt, FIELD_NICK)) 814 append_format(str, sizeof str, &pos, " %s", acptr->name); 815 if (HasField(fmt, FIELD_FLAGS)) 816 append_format(str, sizeof str, &pos, " %s", status); 817 if (HasField(fmt, FIELD_MODES)) 818 { 819 if (IsOper(client)) 820 { 821 const char *umodes = get_usermode_string(acptr); 822 if (*umodes == '+') 823 umodes++; 824 append_format(str, sizeof str, &pos, " %s", umodes); 825 } else { 826 append_format(str, sizeof str, &pos, " %s", "*"); 827 } 828 } 829 if (HasField(fmt, FIELD_HOP)) 830 append_format(str, sizeof str, &pos, " %d", hide ? 0 : acptr->hopcount); 831 if (HasField(fmt, FIELD_IDLE)) 832 { 833 append_format(str, sizeof str, &pos, " %d", 834 (int)((MyUser(acptr) && !hide_idle_time(client, acptr)) ? (TStime() - acptr->local->idle_since) : 0)); 835 } 836 if (HasField(fmt, FIELD_ACCOUNT)) 837 append_format(str, sizeof str, &pos, " %s", IsLoggedIn(acptr) ? acptr->user->account : "0"); 838 if (HasField(fmt, FIELD_OPLEVEL)) 839 append_format(str, sizeof str, &pos, " %s", (channel && check_channel_access(acptr, channel, "hoaq")) ? "999" : "n/a"); 840 if (HasField(fmt, FIELD_REPUTATION)) 841 { 842 if (IsOper(client)) 843 append_format(str, sizeof str, &pos, " %d", GetReputation(acptr)); 844 else 845 append_format(str, sizeof str, &pos, " %s", "*"); 846 } 847 if (HasField(fmt, FIELD_INFO)) 848 append_format(str, sizeof str, &pos, " :%s", acptr->info); 849 850 sendto_one(client, NULL, "%s", str); 851 } 852 } 853 854 /* Yeah, this is fun. Thank you WHOX !!! */ 855 static int convert_classical_who_request(Client *client, int *parc, const char *parv[], const char **orig_mask, struct who_format *fmt) 856 { 857 const char *p; 858 static char pbuf1[512], pbuf2[512]; 859 int points; 860 861 /* Figure out if the user is doing a 'classical' UnrealIRCd request, 862 * which can be recognized as follows: 863 * 1) Always a + or - as 1st character for the 1st parameter. 864 * 2) Unlikely to have a % (percent sign) in the 2nd parameter 865 * 3) Unlikely to have a 3rd parameter 866 * On the other hand WHOX requests are: 867 * 4) never going to have a '*', '?' or '.' as 2nd parameter 868 * 5) never going to have a '+' or '-' as 1st character in 2nd parameter 869 * Additionally, WHOX requests are useless - and thus unlikely - 870 * to search for a mask mask starting with + or - except when: 871 * 6) searching for 'm' (mode) 872 * 7) or 'r' (info, realname) 873 * ..for which this would be a meaningful request. 874 * The end result is that we can do quite some useful heuristics 875 * except for some corner cases. 876 */ 877 if (((*parv[1] == '+') || (*parv[1] == '-')) && 878 (!parv[2] || !strchr(parv[2], '%')) && 879 (*parc < 4)) 880 { 881 /* Conditions 1-3 of above comment are met, now we deal 882 * with conditions 4-7. 883 */ 884 if (parv[2] && 885 !strchr(parv[2], '*') && !strchr(parv[2], '?') && 886 !strchr(parv[2], '.') && 887 !strchr(parv[2], '+') && !strchr(parv[2], '-') && 888 (strchr(parv[2], 'm') || strchr(parv[2], 'r'))) 889 { 890 /* 'WHO +something m" or even 891 * 'WHO +c #something' (which includes 'm') 892 * could mean either WHO or WHOX style 893 */ 894 } else { 895 /* If we get here then it's an classical 896 * UnrealIRCd-style WHO request which has 897 * the order: WHO <options> <mask> 898 */ 899 char oldrequest[256]; 900 snprintf(oldrequest, sizeof(oldrequest), "WHO %s%s%s", 901 parv[1], parv[2] ? " " : "", parv[2] ? parv[2] : ""); 902 if (parv[2]) 903 { 904 const char *swap = parv[1]; 905 parv[1] = parv[2]; 906 parv[2] = swap; 907 } else { 908 /* A request like 'WHO +I' or 'WHO +R' */ 909 parv[2] = parv[1]; 910 parv[1] = "*"; 911 parv[3] = NULL; 912 *parc = 3; 913 } 914 915 /* Ok, that was the first step, now we need to convert the 916 * flags since they have changed a little as well: 917 * Flag a: user is away << no longer exists 918 * Flag c <channel>: user is on <channel> << no longer exists 919 * Flag g <gcos/realname>: user has string <gcos> in his/her GCOS << now called 'r' 920 * Flag h <host>: user has string <host> in his/her hostname << no change 921 * Flag i <ip>: user has string <ip> in his/her IP address << no change 922 * Flag m <usermodes>: user has <usermodes> set << behavior change 923 * Flag n <nick>: user has string <nick> in his/her nickname << no change 924 * Flag s <server>: user is on server <server> << no change 925 * Flag u <user>: user has string <user> in his/her username << no change 926 * Behavior flags: 927 * Flag M: check for user in channels I am a member of << no longer exists 928 * Flag R: show users' real hostnames << no change (re-added) 929 * Flag I: show users' IP addresses << no change (re-added) 930 */ 931 932 if (strchr(parv[2], 'a')) 933 { 934 sendnotice(client, "WHO request '%s' failed: flag 'a' no longer exists with WHOX.", oldrequest); 935 return 0; 936 } 937 if (strchr(parv[2], 'c')) 938 { 939 sendnotice(client, "WHO request '%s' failed: flag 'c' no longer exists with WHOX.", oldrequest); 940 return 0; 941 } 942 if (strchr(parv[2], 'g')) 943 { 944 char *w; 945 strlcpy(pbuf2, parv[2], sizeof(pbuf2)); 946 for (w = pbuf2; *w; w++) 947 { 948 if (*w == 'g') 949 { 950 *w = 'r'; 951 break; 952 } 953 } 954 parv[2] = pbuf2; 955 } 956 957 /* "WHO -m xyz" (now: xyz -m) should become "WHO -xyz m" 958 * Wow, this seems overly complex, but okay... 959 */ 960 points = 0; 961 for (p = parv[2]; *p; p++) 962 { 963 if (*p == '-') 964 points = 1; 965 else if (*p == '+') 966 points = 0; 967 else if (points && (*p == 'm')) 968 { 969 points = 2; 970 break; 971 } 972 } 973 if (points == 2) 974 { 975 snprintf(pbuf1, sizeof(pbuf1), "-%s", parv[1]); 976 parv[1] = pbuf1; 977 } 978 979 if ((*parv[2] == '+') || (*parv[2] == '-')) 980 parv[2] = parv[2]+1; /* strip '+'/'-' prefix, which does not exist in WHOX */ 981 982 sendnotice(client, "WHO request '%s' changed to match new WHOX syntax: 'WHO %s %s'", 983 oldrequest, parv[1], parv[2]); 984 *orig_mask = parv[1]; 985 } 986 } 987 return 1; 988 }