unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
join.c (19215B)
1 /* 2 * IRC - Internet Relay Chat, src/modules/join.c 3 * (C) 2005 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 /* Forward declarations */ 26 CMD_FUNC(cmd_join); 27 void _join_channel(Channel *channel, Client *client, MessageTag *mtags, const char *member_modes); 28 void _do_join(Client *client, int parc, const char *parv[]); 29 int _can_join(Client *client, Channel *channel, const char *key, char **errmsg); 30 void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mtags); 31 char *_get_chmodes_for_user(Client *client, const char *flags); 32 void send_cannot_join_error(Client *client, int numeric, char *fmtstr, char *channel_name); 33 34 /* Externs */ 35 extern MODVAR int spamf_ugly_vchanoverride; 36 extern int find_invex(Channel *channel, Client *client); 37 38 /* Local vars */ 39 static int bouncedtimes = 0; 40 long CAP_EXTENDED_JOIN = 0L; 41 42 /* Macros */ 43 #define MAXBOUNCE 5 /** Most sensible */ 44 #define MSG_JOIN "JOIN" 45 46 ModuleHeader MOD_HEADER 47 = { 48 "join", 49 "5.0", 50 "command /join", 51 "UnrealIRCd Team", 52 "unrealircd-6", 53 }; 54 55 MOD_TEST() 56 { 57 MARK_AS_OFFICIAL_MODULE(modinfo); 58 EfunctionAddVoid(modinfo->handle, EFUNC_JOIN_CHANNEL, _join_channel); 59 EfunctionAddVoid(modinfo->handle, EFUNC_DO_JOIN, _do_join); 60 EfunctionAdd(modinfo->handle, EFUNC_CAN_JOIN, _can_join); 61 EfunctionAddVoid(modinfo->handle, EFUNC_SEND_JOIN_TO_LOCAL_USERS, _send_join_to_local_users); 62 EfunctionAddPVoid(modinfo->handle, EFUNC_GET_CHMODES_FOR_USER, TO_PVOIDFUNC(_get_chmodes_for_user)); 63 64 return MOD_SUCCESS; 65 } 66 67 MOD_INIT() 68 { 69 ClientCapabilityInfo c; 70 memset(&c, 0, sizeof(c)); 71 c.name = "extended-join"; 72 ClientCapabilityAdd(modinfo->handle, &c, &CAP_EXTENDED_JOIN); 73 74 CommandAdd(modinfo->handle, MSG_JOIN, cmd_join, MAXPARA, CMD_USER); 75 MARK_AS_OFFICIAL_MODULE(modinfo); 76 return MOD_SUCCESS; 77 } 78 79 MOD_LOAD() 80 { 81 return MOD_SUCCESS; 82 } 83 84 MOD_UNLOAD() 85 { 86 return MOD_SUCCESS; 87 } 88 89 /* This function checks if a locally connected user may join the channel. 90 * It also provides an number of hooks where modules can plug in to. 91 * Note that the order of checking has been carefully thought of 92 * (eg: bans at the end), so don't change it unless you have a good reason 93 * to do so -- Syzop. 94 */ 95 int _can_join(Client *client, Channel *channel, const char *key, char **errmsg) 96 { 97 Hook *h; 98 99 /* An /INVITE lets you bypass all restrictions */ 100 if (is_invited(client, channel)) 101 { 102 int j = 0; 103 for (h = Hooks[HOOKTYPE_INVITE_BYPASS]; h; h = h->next) 104 { 105 j = (*(h->func.intfunc))(client,channel); 106 if (j != 0) 107 break; 108 } 109 /* Bypass is OK, unless a HOOKTYPE_INVITE_BYPASS hook returns HOOK_DENY */ 110 if (j != HOOK_DENY) 111 return 0; 112 } 113 114 for (h = Hooks[HOOKTYPE_CAN_JOIN]; h; h = h->next) 115 { 116 int i = (*(h->func.intfunc))(client,channel,key, errmsg); 117 if (i != 0) 118 return i; 119 } 120 121 /* See if we can evade this ban */ 122 if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL)) 123 { 124 *errmsg = STR_ERR_BANNEDFROMCHAN; 125 return ERR_BANNEDFROMCHAN; 126 } 127 128 #ifndef NO_OPEROVERRIDE 129 #ifdef OPEROVERRIDE_VERIFY 130 if (ValidatePermissionsForPath("channel:override:privsecret",client,NULL,channel,NULL) && (channel->mode.mode & MODE_SECRET || 131 channel->mode.mode & MODE_PRIVATE) && !is_autojoin_chan(channel->name)) 132 { 133 *errmsg = STR_ERR_OPERSPVERIFY; 134 return (ERR_OPERSPVERIFY); 135 } 136 #endif 137 #endif 138 139 return 0; 140 } 141 142 /* 143 ** cmd_join 144 ** parv[1] = channel 145 ** parv[2] = channel password (key) 146 ** 147 ** Due to message tags, remote servers should only send 1 channel 148 ** per JOIN. Or even better, use SJOIN instead. 149 ** Otherwise we cannot use unique msgid's and such. 150 ** UnrealIRCd 4 and probably UnrealIRCd 3.2.something already do 151 ** this, so this comment is mostly for services coders, I guess. 152 */ 153 CMD_FUNC(cmd_join) 154 { 155 int r; 156 157 if (bouncedtimes) 158 { 159 unreal_log(ULOG_ERROR, "join", "BUG_JOIN_BOUNCEDTIMES", NULL, 160 "[BUG] join: bouncedtimes is not initialized to zero ($bounced_times)!! " 161 "Please report at https://bugs.unrealircd.org/", 162 log_data_integer("bounced_times", bouncedtimes)); 163 } 164 165 bouncedtimes = 0; 166 if (IsServer(client)) 167 return; 168 do_join(client, parc, parv); 169 bouncedtimes = 0; 170 } 171 172 /** Send JOIN message for 'client' to all users in 'channel'. 173 * Taking into account that not everyone in channel should see the JOIN (mode +D) 174 * and taking into account the different types of JOIN (due to CAP extended-join). 175 */ 176 void _send_join_to_local_users(Client *client, Channel *channel, MessageTag *mtags) 177 { 178 int chanops_only = invisible_user_in_channel(client, channel); 179 Member *lp; 180 Client *acptr; 181 char joinbuf[512]; 182 char exjoinbuf[512]; 183 184 ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN :%s", 185 client->name, client->user->username, GetHost(client), channel->name); 186 187 ircsnprintf(exjoinbuf, sizeof(exjoinbuf), ":%s!%s@%s JOIN %s %s :%s", 188 client->name, client->user->username, GetHost(client), channel->name, 189 IsLoggedIn(client) ? client->user->account : "*", 190 client->info); 191 192 for (lp = channel->members; lp; lp = lp->next) 193 { 194 acptr = lp->client; 195 196 if (!MyConnect(acptr)) 197 continue; /* only locally connected clients */ 198 199 if (chanops_only && !check_channel_access_member(lp, "hoaq") && (client != acptr)) 200 continue; /* skip non-ops if requested to (used for mode +D), but always send to 'client' */ 201 202 if (HasCapabilityFast(acptr, CAP_EXTENDED_JOIN)) 203 sendto_one(acptr, mtags, "%s", exjoinbuf); 204 else 205 sendto_one(acptr, mtags, "%s", joinbuf); 206 } 207 } 208 209 /* Routine that actually makes a user join the channel 210 * this does no actual checking (banned, etc.) it just adds the user. 211 * Note: this is called for local JOIN and remote JOIN, but not for SJOIN. 212 */ 213 void _join_channel(Channel *channel, Client *client, MessageTag *recv_mtags, const char *member_modes) 214 { 215 MessageTag *mtags = NULL; /** Message tags to send to local users (sender is :user) */ 216 MessageTag *mtags_sjoin = NULL; /* Message tags to send to remote servers for SJOIN (sender is :me.id) */ 217 const char *parv[3]; 218 219 /* Same way as in SJOIN */ 220 new_message_special(client, recv_mtags, &mtags, ":%s JOIN %s", client->name, channel->name); 221 222 new_message(&me, recv_mtags, &mtags_sjoin); 223 224 add_user_to_channel(channel, client, member_modes); 225 226 send_join_to_local_users(client, channel, mtags); 227 228 sendto_server(client, 0, 0, mtags_sjoin, ":%s SJOIN %lld %s :%s%s ", 229 me.id, (long long)channel->creationtime, 230 channel->name, modes_to_sjoin_prefix(member_modes), client->id); 231 232 if (MyUser(client)) 233 { 234 /* 235 ** Make a (temporal) creationtime, if someone joins 236 ** during a net.reconnect : between remote join and 237 ** the mode with TS. --Run 238 */ 239 if (channel->creationtime == 0) 240 { 241 channel->creationtime = TStime(); 242 sendto_server(client, 0, 0, NULL, ":%s MODE %s + %lld", 243 me.id, channel->name, (long long)channel->creationtime); 244 } 245 246 if (channel->topic) 247 { 248 sendnumeric(client, RPL_TOPIC, channel->name, channel->topic); 249 sendnumeric(client, RPL_TOPICWHOTIME, channel->name, channel->topic_nick, (long long)channel->topic_time); 250 } 251 252 /* Set default channel modes (set::modes-on-join). 253 * Set only if it's the 1st user and only if no other modes have been set 254 * already (eg: +P, permanent). 255 */ 256 if ((channel->users == 1) && !channel->mode.mode && MODES_ON_JOIN) 257 { 258 MessageTag *mtags_mode = NULL; 259 Cmode *cm; 260 char modebuf[BUFSIZE], parabuf[BUFSIZE]; 261 int should_destroy = 0; 262 263 channel->mode.mode = MODES_ON_JOIN; 264 265 /* Param fun */ 266 for (cm=channelmodes; cm; cm = cm->next) 267 { 268 if (!cm->letter || !cm->paracount) 269 continue; 270 if (channel->mode.mode & cm->mode) 271 cm_putparameter(channel, cm->letter, iConf.modes_on_join.extparams[cm->letter]); 272 } 273 274 *modebuf = *parabuf = 0; 275 channel_modes(client, modebuf, parabuf, sizeof(modebuf), sizeof(parabuf), channel, 0); 276 /* This should probably be in the SJOIN stuff */ 277 new_message_special(&me, recv_mtags, &mtags_mode, ":%s MODE %s %s %s", me.name, channel->name, modebuf, parabuf); 278 sendto_server(NULL, 0, 0, mtags_mode, ":%s MODE %s %s %s %lld", 279 me.id, channel->name, modebuf, parabuf, (long long)channel->creationtime); 280 sendto_one(client, mtags_mode, ":%s MODE %s %s %s", me.name, channel->name, modebuf, parabuf); 281 RunHook(HOOKTYPE_LOCAL_CHANMODE, &me, channel, mtags_mode, modebuf, parabuf, 0, 0, &should_destroy); 282 free_message_tags(mtags_mode); 283 } 284 285 parv[0] = NULL; 286 parv[1] = channel->name; 287 parv[2] = NULL; 288 do_cmd(client, NULL, "NAMES", 2, parv); 289 290 unreal_log(ULOG_INFO, "join", "LOCAL_CLIENT_JOIN", client, 291 "User $client joined $channel", 292 log_data_channel("channel", channel), 293 log_data_string("modes", member_modes)); 294 295 RunHook(HOOKTYPE_LOCAL_JOIN, client, channel, mtags); 296 } else { 297 unreal_log(ULOG_INFO, "join", "REMOTE_CLIENT_JOIN", client, 298 "User $client joined $channel", 299 log_data_channel("channel", channel), 300 log_data_string("modes", member_modes)); 301 RunHook(HOOKTYPE_REMOTE_JOIN, client, channel, mtags); 302 } 303 304 free_message_tags(mtags); 305 free_message_tags(mtags_sjoin); 306 } 307 308 /** User request to join a channel. 309 * This routine is normally called from cmd_join but can also be called from 310 * do_join->can_join->link module->do_join if the channel is 'linked' (chmode +L). 311 * We therefore use a counter 'bouncedtimes' which is set to 0 in cmd_join, 312 * increased every time we enter this loop and decreased anytime we leave the 313 * loop. So be carefull not to use a simple 'return' after bouncedtimes++. -- Syzop 314 */ 315 void _do_join(Client *client, int parc, const char *parv[]) 316 { 317 char request[BUFSIZE]; 318 char request_key[BUFSIZE]; 319 char jbuf[BUFSIZE], jbuf2[BUFSIZE]; 320 const char *orig_parv1; 321 Membership *lp; 322 Channel *channel; 323 char *name, *key = NULL; 324 int i, ishold; 325 char *p = NULL, *p2 = NULL; 326 TKL *tklban; 327 int ntargets = 0; 328 int maxtargets = max_targets_for_command("JOIN"); 329 const char *member_modes = ""; 330 331 #define RET() do { bouncedtimes--; parv[1] = orig_parv1; return; } while(0) 332 333 if (parc < 2 || *parv[1] == '\0') 334 { 335 sendnumeric(client, ERR_NEEDMOREPARAMS, "JOIN"); 336 return; 337 } 338 339 /* For our tests we need super accurate time for JOINs or they mail fail. */ 340 gettimeofday(&timeofday_tv, NULL); 341 timeofday = timeofday_tv.tv_sec; 342 343 bouncedtimes++; 344 orig_parv1 = parv[1]; 345 /* don't use 'return;' but 'RET();' from here ;p */ 346 347 if (bouncedtimes > MAXBOUNCE) 348 { 349 /* bounced too many times. yeah.. should be in the link module, I know.. then again, who cares.. */ 350 sendnotice(client, "*** Couldn't join %s ! - Link setting was too bouncy", parv[1]); 351 RET(); 352 } 353 354 *jbuf = '\0'; 355 /* 356 ** Rebuild list of channels joined to be the actual result of the 357 ** JOIN. Note that "JOIN 0" is the destructive problem. 358 */ 359 strlcpy(request, parv[1], sizeof(request)); 360 for (i = 0, name = strtoken(&p, request, ","); name; i++, name = strtoken(&p, NULL, ",")) 361 { 362 if (MyUser(client) && (++ntargets > maxtargets)) 363 { 364 sendnumeric(client, ERR_TOOMANYTARGETS, name, maxtargets, "JOIN"); 365 break; 366 } 367 if (*name == '0' && !atoi(name)) 368 { 369 /* UnrealIRCd 5+: we only support "JOIN 0", 370 * "JOIN 0,#somechan" etc... so only at the beginning. 371 * We do not support it half-way like "JOIN #a,0,#b" 372 * since that doesn't make sense, unless you are flooding... 373 * We still support it in remote joins for compatibility. 374 */ 375 if (MyUser(client) && (i != 0)) 376 continue; 377 strlcpy(jbuf, "0", sizeof(jbuf)); 378 continue; 379 } else 380 if (MyConnect(client) && !valid_channelname(name)) 381 { 382 send_invalid_channelname(client, name); 383 if (IsOper(client) && find_channel(name)) 384 { 385 /* Give IRCOps a bit more information */ 386 sendnotice(client, "Channel '%s' is unjoinable because it contains illegal characters. " 387 "However, it does exist because another server in your " 388 "network, which has a more loose restriction, created it. " 389 "See https://www.unrealircd.org/docs/Set_block#set::allowed-channelchars", 390 name); 391 } 392 continue; 393 } 394 else if (!IsChannelName(name)) 395 { 396 if (MyUser(client)) 397 sendnumeric(client, ERR_NOSUCHCHANNEL, name); 398 continue; 399 } 400 if (*jbuf) 401 strlcat(jbuf, ",", sizeof jbuf); 402 strlcat(jbuf, name, sizeof(jbuf)); 403 } 404 405 /* We are going to overwrite 'jbuf' with the calls to strtoken() 406 * a few lines further down. Copy it to 'jbuf2' and make that 407 * the new parv[1].. or at least temporarily. 408 */ 409 strlcpy(jbuf2, jbuf, sizeof(jbuf2)); 410 parv[1] = jbuf2; 411 412 p = NULL; 413 if (parv[2]) 414 { 415 strlcpy(request_key, parv[2], sizeof(request_key)); 416 key = strtoken(&p2, request_key, ","); 417 } 418 parv[2] = NULL; /* for cmd_names call later, parv[parc] must == NULL */ 419 420 for (name = strtoken(&p, jbuf, ","); 421 name; 422 key = key ? strtoken(&p2, NULL, ",") : NULL, name = strtoken(&p, NULL, ",")) 423 { 424 MessageTag *mtags = NULL; 425 426 /* 427 ** JOIN 0 sends out a part for all channels a user 428 ** has joined. 429 */ 430 if (*name == '0' && !atoi(name)) 431 { 432 /* Rewritten so to generate a PART for each channel to servers, 433 * so the same msgid is used for each part on all servers. -- Syzop 434 */ 435 while ((lp = client->user->channel)) 436 { 437 MessageTag *mtags = NULL; 438 channel = lp->channel; 439 440 new_message(client, NULL, &mtags); 441 442 sendto_channel(channel, client, NULL, 0, 0, SEND_LOCAL, mtags, 443 ":%s PART %s :%s", 444 client->name, channel->name, "Left all channels"); 445 sendto_server(client, 0, 0, mtags, ":%s PART %s :Left all channels", client->name, channel->name); 446 447 if (MyConnect(client)) 448 RunHook(HOOKTYPE_LOCAL_PART, client, channel, mtags, "Left all channels"); 449 450 remove_user_from_channel(client, channel, 0); 451 free_message_tags(mtags); 452 } 453 continue; 454 } 455 456 if (MyConnect(client)) 457 { 458 member_modes = (ChannelExists(name)) ? "" : LEVEL_ON_JOIN; 459 460 if (!ValidatePermissionsForPath("immune:maxchannelsperuser",client,NULL,NULL,NULL)) /* opers can join unlimited chans */ 461 if (client->user->joined >= MAXCHANNELSPERUSER) 462 { 463 sendnumeric(client, ERR_TOOMANYCHANNELS, name); 464 RET(); 465 } 466 /* RESTRICTCHAN */ 467 if (conf_deny_channel) 468 { 469 if (!ValidatePermissionsForPath("immune:server-ban:deny-channel",client,NULL,NULL,NULL)) 470 { 471 ConfigItem_deny_channel *d; 472 if ((d = find_channel_allowed(client, name))) 473 { 474 if (d->warn) 475 { 476 unreal_log(ULOG_INFO, "join", "JOIN_DENIED_FORBIDDEN_CHANNEL", client, 477 "Client $client.details tried to join forbidden channel $channel", 478 log_data_string("channel", name)); 479 } 480 if (d->reason) 481 sendnumeric(client, ERR_FORBIDDENCHANNEL, name, d->reason); 482 if (d->redirect) 483 { 484 sendnotice(client, "*** Redirecting you to %s", d->redirect); 485 parv[0] = NULL; 486 parv[1] = d->redirect; 487 do_join(client, 2, parv); 488 } 489 if (d->class) 490 sendnotice(client, "*** Can not join %s: Your class is not allowed", name); 491 continue; 492 } 493 } 494 } 495 if (!ValidatePermissionsForPath("immune:server-ban:deny-channel",client,NULL,NULL,NULL) && (tklban = find_qline(client, name, &ishold))) 496 { 497 sendnumeric(client, ERR_FORBIDDENCHANNEL, name, tklban->ptr.nameban->reason); 498 continue; 499 } 500 /* ugly set::spamfilter::virus-help-channel-deny hack.. */ 501 if (SPAMFILTER_VIRUSCHANDENY && SPAMFILTER_VIRUSCHAN && 502 !strcasecmp(name, SPAMFILTER_VIRUSCHAN) && 503 !ValidatePermissionsForPath("immune:server-ban:viruschan",client,NULL,NULL,NULL) && !spamf_ugly_vchanoverride) 504 { 505 Channel *channel = find_channel(name); 506 507 if (!channel || !is_invited(client, channel)) 508 { 509 sendnotice(client, "*** Cannot join '%s' because it's the virus-help-channel " 510 "which is reserved for infected users only", name); 511 continue; 512 } 513 } 514 } 515 516 channel = make_channel(name); 517 if (channel && (lp = find_membership_link(client->user->channel, channel))) 518 continue; 519 520 if (!channel) 521 continue; 522 523 i = HOOK_CONTINUE; 524 if (!MyConnect(client)) 525 member_modes = ""; 526 else 527 { 528 Hook *h; 529 char *errmsg = NULL; 530 for (h = Hooks[HOOKTYPE_PRE_LOCAL_JOIN]; h; h = h->next) 531 { 532 i = (*(h->func.intfunc))(client,channel,key); 533 if (i == HOOK_DENY || i == HOOK_ALLOW) 534 break; 535 } 536 /* Denied, get out now! */ 537 if (i == HOOK_DENY) 538 { 539 /* Rejected... if we just created a new chan we should destroy it too. -- Syzop */ 540 if (!channel->users) 541 sub1_from_channel(channel); 542 continue; 543 } 544 /* If they are allowed, don't check can_join */ 545 if (i != HOOK_ALLOW && 546 (i = can_join(client, channel, key, &errmsg))) 547 { 548 if (i != -1) 549 send_cannot_join_error(client, i, errmsg, name); 550 continue; 551 } 552 } 553 554 /* Generate a new message without inheritance. 555 * We can do this because remote joins don't follow this code path, 556 * or are highly discouraged to anyway. 557 * Remote servers use SJOIN and never reach this function. 558 * Locally we do follow this code path with JOIN and then generating 559 * a new_message() here is exactly what we want: 560 * Each "JOIN #a,#b,#c" gets processed individually in this loop 561 * and is sent by join_channel() as a SJOIN for #a, then SJOIN for #b, 562 * and so on, each with their own unique msgid and such. 563 */ 564 new_message(client, NULL, &mtags); 565 join_channel(channel, client, mtags, member_modes); 566 free_message_tags(mtags); 567 } 568 RET(); 569 #undef RET 570 } 571 572 #if defined(__GNUC__) 573 #pragma GCC diagnostic push 574 #pragma GCC diagnostic ignored "-Wformat-nonliteral" 575 #endif 576 void send_cannot_join_error(Client *client, int numeric, char *fmtstr, char *channel_name) 577 { 578 // TODO: add single %s validation ! 579 sendnumericfmt(client, numeric, fmtstr, channel_name); 580 } 581 #if defined(__GNUC__) 582 #pragma GCC diagnostic pop 583 #endif 584 585 /* Additional channel-related functions. I've put it here instead 586 * of the core so it could be upgraded on the fly should it be necessary. 587 */ 588 589 char *_get_chmodes_for_user(Client *client, const char *member_flags) 590 { 591 static char modebuf[512]; /* returned */ 592 char flagbuf[8]; /* For holding "vhoaq" */ 593 char parabuf[512]; 594 int n, i; 595 596 if (BadPtr(member_flags)) 597 return ""; 598 599 parabuf[0] = '\0'; 600 n = strlen(member_flags); 601 if (n) 602 { 603 for (i=0; i < n; i++) 604 { 605 strlcat(parabuf, client->name, sizeof(parabuf)); 606 if (i < n - 1) 607 strlcat(parabuf, " ", sizeof(parabuf)); 608 } 609 /* And we have our mode line! */ 610 snprintf(modebuf, sizeof(modebuf), "+%s %s", member_flags, parabuf); 611 return modebuf; 612 } 613 614 return ""; 615 }