unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
list.c (13172B)
1 /* 2 * IRC - Internet Relay Chat, src/modules/list.c 3 * (C) 2004 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 CMD_FUNC(cmd_list); 26 int send_list(Client *client); 27 28 #define MSG_LIST "LIST" 29 30 ModuleHeader MOD_HEADER 31 = { 32 "list", 33 "5.0", 34 "command /LIST", 35 "UnrealIRCd Team", 36 "unrealircd-6", 37 }; 38 39 typedef struct ChannelListOptions ChannelListOptions; 40 struct ChannelListOptions { 41 NameList *yeslist; 42 NameList *nolist; 43 unsigned int starthash; 44 short int showall; 45 unsigned short usermin; 46 int usermax; 47 time_t currenttime; 48 time_t chantimemin; 49 time_t chantimemax; 50 time_t topictimemin; 51 time_t topictimemax; 52 void *lr_context; 53 }; 54 55 /* Global variables */ 56 ModDataInfo *list_md = NULL; 57 char modebuf[BUFSIZE], parabuf[BUFSIZE]; 58 59 /* Macros */ 60 #define CHANNELLISTOPTIONS(x) ((ChannelListOptions *)moddata_local_client(x, list_md).ptr) 61 #define ALLOCATE_CHANNELLISTOPTIONS(client) do { moddata_local_client(client, list_md).ptr = safe_alloc(sizeof(ChannelListOptions)); } while(0) 62 #define free_list_options(client) list_md_free(&moddata_local_client(client, list_md)) 63 64 #define DoList(x) (MyUser((x)) && CHANNELLISTOPTIONS((x))) 65 #define IsSendable(x) (DBufLength(&x->local->sendQ) < 2048) 66 67 /* Forward declarations */ 68 EVENT(send_queued_list_data); 69 void list_md_free(ModData *md); 70 71 MOD_TEST() 72 { 73 MARK_AS_OFFICIAL_MODULE(modinfo); 74 return MOD_SUCCESS; 75 } 76 77 MOD_INIT() 78 { 79 ModDataInfo mreq; 80 81 MARK_AS_OFFICIAL_MODULE(modinfo); 82 83 memset(&mreq, 0, sizeof(mreq)); 84 mreq.name = "list"; 85 mreq.type = MODDATATYPE_LOCAL_CLIENT; 86 mreq.free = list_md_free; 87 list_md = ModDataAdd(modinfo->handle, mreq); 88 if (!list_md) 89 { 90 config_error("could not register list moddata"); 91 return MOD_FAILED; 92 } 93 94 CommandAdd(modinfo->handle, MSG_LIST, cmd_list, MAXPARA, CMD_USER); 95 EventAdd(modinfo->handle, "send_queued_list_data", send_queued_list_data, NULL, 1500, 0); 96 97 return MOD_SUCCESS; 98 } 99 100 MOD_LOAD() 101 { 102 return MOD_SUCCESS; 103 } 104 105 MOD_UNLOAD() 106 { 107 return MOD_SUCCESS; 108 } 109 110 /* Originally from bahamut, modified a bit for UnrealIRCd by codemastr 111 * also Opers can now see +s channels -- codemastr */ 112 113 /* 114 * parv[1] = channel 115 */ 116 CMD_FUNC(cmd_list) 117 { 118 Channel *channel; 119 time_t currenttime = TStime(); 120 char *name, *p = NULL; 121 ChannelListOptions *lopt = NULL; 122 int usermax, usermin, error = 0, doall = 0; 123 time_t chantimemin, chantimemax; 124 time_t topictimemin, topictimemax; 125 NameList *yeslist = NULL; 126 NameList *nolist = NULL; 127 int ntargets = 0; 128 int maxtargets = max_targets_for_command("LIST"); 129 char request[BUFSIZE]; 130 131 static char *usage[] = { 132 " Usage: /LIST <options>", 133 "", 134 "If you don't include any options, the default is to send you the", 135 "entire unfiltered list of channels. Below are the options you can", 136 "use, and what channels LIST will return when you use them.", 137 ">number List channels with more than <number> people.", 138 "<number List channels with less than <number> people.", 139 "C>number List channels created more than <number> minutes ago.", 140 "C<number List channels created less than <number> minutes ago.", 141 "T>number List channels whose topics are older than <number> minutes", 142 " (Ie, they have not changed in the last <number> minutes.", 143 "T<number List channels whose topics are not older than <number> minutes.", 144 "*mask* List channels that match *mask*", 145 "!*mask* List channels that do not match *mask*", 146 NULL 147 }; 148 149 /* Remote /LIST is not supported */ 150 if (!MyUser(client)) 151 return; 152 153 /* If a /LIST is in progress then a new one will cancel it */ 154 if (CHANNELLISTOPTIONS(client)) 155 { 156 sendnumeric(client, RPL_LISTEND); 157 free_list_options(client); 158 return; 159 } 160 161 if (parc < 2 || BadPtr(parv[1])) 162 { 163 sendnumeric(client, RPL_LISTSTART); 164 ALLOCATE_CHANNELLISTOPTIONS(client); 165 CHANNELLISTOPTIONS(client)->showall = 1; 166 167 if (send_list(client)) 168 { 169 /* Save context since there is more to be sent */ 170 CHANNELLISTOPTIONS(client)->lr_context = labeled_response_save_context(); 171 labeled_response_inhibit_end = 1; 172 } 173 174 return; 175 } 176 177 if ((parc == 2) && (parv[1][0] == '?') && (parv[1][1] == '\0')) 178 { 179 char **ptr = usage; 180 for (; *ptr; ptr++) 181 sendnumeric(client, RPL_LISTSYNTAX, *ptr); 182 return; 183 } 184 185 sendnumeric(client, RPL_LISTSTART); 186 187 chantimemax = topictimemax = currenttime + 86400; 188 chantimemin = topictimemin = 0; 189 usermin = 0; /* Minimum of 0 */ 190 usermax = -1; /* No maximum */ 191 192 strlcpy(request, parv[1], sizeof(request)); 193 for (name = strtoken(&p, request, ","); name && !error; name = strtoken(&p, NULL, ",")) 194 { 195 if (MyUser(client) && (++ntargets > maxtargets)) 196 { 197 sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "LIST"); 198 break; 199 } 200 switch (*name) 201 { 202 case '<': 203 usermax = atoi(name + 1) - 1; 204 doall = 1; 205 break; 206 case '>': 207 usermin = atoi(name + 1) + 1; 208 doall = 1; 209 break; 210 case 'C': 211 case 'c': /* Channel time -- creation time? */ 212 ++name; 213 switch (*name++) 214 { 215 case '<': 216 chantimemin = currenttime - 60 * atoi(name); 217 doall = 1; 218 break; 219 case '>': 220 chantimemax = currenttime - 60 * atoi(name); 221 doall = 1; 222 break; 223 default: 224 sendnumeric(client, ERR_LISTSYNTAX); 225 error = 1; 226 } 227 break; 228 case 'T': 229 case 't': 230 ++name; 231 switch (*name++) 232 { 233 case '<': 234 topictimemin = currenttime - 60 * atoi(name); 235 doall = 1; 236 break; 237 case '>': 238 topictimemax = currenttime - 60 * atoi(name); 239 doall = 1; 240 break; 241 default: 242 sendnumeric(client, ERR_LISTSYNTAX); 243 error = 1; 244 } 245 break; 246 default: 247 /* A channel, possibly with wildcards. 248 * Thought for the future: Consider turning wildcard 249 * processing on the fly. 250 * new syntax: !channelmask will tell ircd to ignore 251 * any channels matching that mask, and then 252 * channelmask will tell ircd to send us a list of 253 * channels only masking channelmask. Note: Specifying 254 * a channel without wildcards will return that 255 * channel even if any of the !channelmask masks 256 * matches it. 257 */ 258 if (*name == '!') 259 { 260 /* Negative matching by name */ 261 doall = 1; 262 add_name_list(nolist, name + 1); 263 } 264 else if (strchr(name, '*') || strchr(name, '?')) 265 { 266 /* Channel with wildcards */ 267 doall = 1; 268 add_name_list(yeslist, name); 269 } 270 else 271 { 272 /* A specific channel name without wildcards */ 273 channel = find_channel(name); 274 if (channel && (ShowChannel(client, channel) || ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL))) 275 { 276 modebuf[0] = '['; 277 channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0); 278 279 if (modebuf[2] == '\0') 280 modebuf[0] = '\0'; 281 else 282 strlcat(modebuf, "]", sizeof modebuf); 283 284 sendnumeric(client, RPL_LIST, name, channel->users, modebuf, 285 channel->topic ? channel->topic : ""); 286 } 287 } 288 } /* switch */ 289 } /* for */ 290 291 if (doall) 292 { 293 ALLOCATE_CHANNELLISTOPTIONS(client); 294 CHANNELLISTOPTIONS(client)->usermin = usermin; 295 CHANNELLISTOPTIONS(client)->usermax = usermax; 296 CHANNELLISTOPTIONS(client)->topictimemax = topictimemax; 297 CHANNELLISTOPTIONS(client)->topictimemin = topictimemin; 298 CHANNELLISTOPTIONS(client)->chantimemax = chantimemax; 299 CHANNELLISTOPTIONS(client)->chantimemin = chantimemin; 300 CHANNELLISTOPTIONS(client)->nolist = nolist; 301 CHANNELLISTOPTIONS(client)->yeslist = yeslist; 302 303 if (send_list(client)) 304 { 305 /* Save context since there is more to be sent */ 306 CHANNELLISTOPTIONS(client)->lr_context = labeled_response_save_context(); 307 labeled_response_inhibit_end = 1; 308 } 309 return; 310 } 311 312 sendnumeric(client, RPL_LISTEND); 313 } 314 /* 315 * The function which sends the actual channel list back to the user. 316 * Operates by stepping through the hashtable, sending the entries back if 317 * they match the criteria. 318 * client = Local client to send the output back to. 319 * Taken from bahamut, modified for UnrealIRCd by codemastr. 320 */ 321 int send_list(Client *client) 322 { 323 Channel *channel; 324 ChannelListOptions *lopt = CHANNELLISTOPTIONS(client); 325 unsigned int hashnum; 326 int numsend = (get_sendq(client) / 768) + 1; /* (was previously hard-coded) */ 327 /* ^ 328 * numsend = Number (roughly) of lines to send back. Once this number has 329 * been exceeded, send_list will finish with the current hash bucket, 330 * and record that number as the number to start next time send_list 331 * is called for this user. So, this function will almost always send 332 * back more lines than specified by numsend (though not by much, 333 * assuming the hashing algorithm works well). Be conservative in your 334 * choice of numsend. -Rak 335 */ 336 337 /* Begin of /LIST? then send official channels first. */ 338 if ((lopt->starthash == 0) && conf_offchans) 339 { 340 ConfigItem_offchans *x; 341 for (x = conf_offchans; x; x = x->next) 342 { 343 if (find_channel(x->name)) 344 continue; /* exists, >0 users.. will be sent later */ 345 sendnumeric(client, RPL_LIST, x->name, 0, "", 346 x->topic ? x->topic : ""); 347 } 348 } 349 350 for (hashnum = lopt->starthash; hashnum < CHAN_HASH_TABLE_SIZE; hashnum++) 351 { 352 if (numsend > 0) 353 for (channel = hash_get_chan_bucket(hashnum); channel; channel = channel->hnextch) 354 { 355 if (SecretChannel(channel) 356 && !IsMember(client, channel) 357 && !ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL)) 358 continue; 359 360 /* set::hide-list { deny-channel } */ 361 if (!IsOper(client) && iConf.hide_list && find_channel_allowed(client, channel->name)) 362 continue; 363 364 /* Similarly, hide unjoinable channels for non-ircops since it would be confusing */ 365 if (!IsOper(client) && !valid_channelname(channel->name)) 366 continue; 367 368 /* Much more readable like this -- codemastr */ 369 if ((!lopt->showall)) 370 { 371 /* User count must be in range */ 372 if ((channel->users < lopt->usermin) || 373 ((lopt->usermax >= 0) && (channel->users > lopt->usermax))) 374 continue; 375 376 /* Creation time must be in range */ 377 if ((channel->creationtime && (channel->creationtime < lopt->chantimemin)) || 378 (channel->creationtime > lopt->chantimemax)) 379 continue; 380 381 /* Topic time must be in range */ 382 if ((channel->topic_time < lopt->topictimemin) || 383 (channel->topic_time > lopt->topictimemax)) 384 continue; 385 386 /* Must not be on nolist (if it exists) */ 387 if (lopt->nolist && find_name_list_match(lopt->nolist, channel->name)) 388 continue; 389 390 /* Must be on yeslist (if it exists) */ 391 if (lopt->yeslist && !find_name_list_match(lopt->yeslist, channel->name)) 392 continue; 393 } 394 modebuf[0] = '['; 395 channel_modes(client, modebuf+1, parabuf, sizeof(modebuf)-1, sizeof(parabuf), channel, 0); 396 if (modebuf[2] == '\0') 397 modebuf[0] = '\0'; 398 else 399 strlcat(modebuf, "]", sizeof modebuf); 400 if (!ValidatePermissionsForPath("channel:see:list:secret",client,NULL,channel,NULL)) 401 sendnumeric(client, RPL_LIST, 402 ShowChannel(client, 403 channel) ? channel->name : 404 "*", channel->users, 405 ShowChannel(client, channel) ? 406 modebuf : "", 407 ShowChannel(client, 408 channel) ? (channel->topic ? 409 channel->topic : "") : ""); 410 else 411 sendnumeric(client, RPL_LIST, channel->name, 412 channel->users, 413 modebuf, 414 (channel->topic ? channel->topic : "")); 415 numsend--; 416 } 417 else 418 break; 419 } 420 421 /* All done */ 422 if (hashnum == CHAN_HASH_TABLE_SIZE) 423 { 424 sendnumeric(client, RPL_LISTEND); 425 free_list_options(client); 426 return 0; 427 } 428 429 /* 430 * We've exceeded the limit on the number of channels to send back 431 * at once. 432 */ 433 lopt->starthash = hashnum; 434 return 1; 435 } 436 437 EVENT(send_queued_list_data) 438 { 439 Client *client, *saved; 440 list_for_each_entry_safe(client, saved, &lclient_list, lclient_node) 441 { 442 if (DoList(client) && IsSendable(client)) 443 { 444 labeled_response_set_context(CHANNELLISTOPTIONS(client)->lr_context); 445 if (!send_list(client)) 446 { 447 /* We are done! */ 448 labeled_response_force_end(); 449 } 450 labeled_response_set_context(NULL); 451 } 452 } 453 } 454 455 /** Called on client exit: free the channel list options of this user */ 456 void list_md_free(ModData *md) 457 { 458 ChannelListOptions *lopt = (ChannelListOptions *)md->ptr; 459 460 if (!lopt) 461 return; 462 463 free_entire_name_list(lopt->yeslist); 464 free_entire_name_list(lopt->nolist); 465 safe_free(lopt->lr_context); 466 467 safe_free(md->ptr); 468 }