unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
chghost.c (11427B)
1 /* 2 * IRC - Internet Relay Chat, src/modules/chghost.c 3 * (C) 1999-2001 Carsten Munk (Techie/Stskeeps) <stskeeps@tspre.org> 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 #define MSG_CHGHOST "CHGHOST" 26 27 CMD_FUNC(cmd_chghost); 28 void _userhost_save_current(Client *client); 29 void _userhost_changed(Client *client); 30 31 long CAP_CHGHOST = 0L; 32 33 ModuleHeader MOD_HEADER 34 = { 35 "chghost", /* Name of module */ 36 "5.0", /* Version */ 37 "/chghost", /* Short description of module */ 38 "UnrealIRCd Team", 39 "unrealircd-6", 40 }; 41 42 MOD_TEST() 43 { 44 MARK_AS_OFFICIAL_MODULE(modinfo); 45 EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_SAVE_CURRENT, _userhost_save_current); 46 EfunctionAddVoid(modinfo->handle, EFUNC_USERHOST_CHANGED, _userhost_changed); 47 return MOD_SUCCESS; 48 } 49 50 MOD_INIT() 51 { 52 ClientCapabilityInfo c; 53 54 CommandAdd(modinfo->handle, MSG_CHGHOST, cmd_chghost, MAXPARA, CMD_USER|CMD_SERVER); 55 MARK_AS_OFFICIAL_MODULE(modinfo); 56 57 memset(&c, 0, sizeof(c)); 58 c.name = "chghost"; 59 ClientCapabilityAdd(modinfo->handle, &c, &CAP_CHGHOST); 60 61 return MOD_SUCCESS; 62 } 63 64 MOD_LOAD() 65 { 66 return MOD_SUCCESS; 67 68 } 69 70 MOD_UNLOAD() 71 { 72 return MOD_SUCCESS; 73 } 74 75 76 static char remember_nick[NICKLEN+1]; 77 static char remember_user[USERLEN+1]; 78 static char remember_host[HOSTLEN+1]; 79 80 /** Save current nick/user/host. Used later by userhost_changed(). */ 81 void _userhost_save_current(Client *client) 82 { 83 strlcpy(remember_nick, client->name, sizeof(remember_nick)); 84 strlcpy(remember_user, client->user->username, sizeof(remember_user)); 85 strlcpy(remember_host, GetHost(client), sizeof(remember_host)); 86 } 87 88 /** User/Host changed for user. 89 * Note that userhost_save_current() needs to be called before this 90 * to save the old username/hostname. 91 * This userhost_changed() function deals with notifying local clients 92 * about the user/host change by sending PART+JOIN+MODE if 93 * set::allow-userhost-change force-rejoin is in use, 94 * and it wills end "CAP chghost" to such capable clients. 95 * It will also deal with bumping fakelag for the user since a user/host 96 * change is costly, doesn't matter if it was self-induced or not. 97 * 98 * Please call this function for any user/host change by doing: 99 * userhost_save_current(client); 100 * << change username or hostname here >> 101 * userhost_changed(client); 102 */ 103 void _userhost_changed(Client *client) 104 { 105 Membership *channels; 106 Member *lp; 107 Client *acptr; 108 int impact = 0; 109 char buf[512]; 110 long CAP_EXTENDED_JOIN = ClientCapabilityBit("extended-join"); 111 112 if (strcmp(remember_nick, client->name)) 113 { 114 unreal_log(ULOG_ERROR, "main", "BUG_USERHOST_CHANGED", client, 115 "[BUG] userhost_changed() was called but without calling userhost_save_current() first! Affected user: $client\n" 116 "Please report above bug on https://bugs.unrealircd.org/"); 117 return; /* We cannot safely process this request anymore */ 118 } 119 120 /* It's perfectly acceptable to call us even if the userhost didn't change. */ 121 if (!strcmp(remember_user, client->user->username) && !strcmp(remember_host, GetHost(client))) 122 return; /* Nothing to do */ 123 124 /* Most of the work is only necessary for set::allow-userhost-change force-rejoin */ 125 if (UHOST_ALLOWED == UHALLOW_REJOIN) 126 { 127 /* Walk through all channels of this user.. */ 128 for (channels = client->user->channel; channels; channels = channels->next) 129 { 130 Channel *channel = channels->channel; 131 char *modes; 132 char partbuf[512]; /* PART */ 133 char joinbuf[512]; /* JOIN */ 134 char exjoinbuf[512]; /* JOIN (for CAP extended-join) */ 135 char modebuf[512]; /* MODE (if any) */ 136 int chanops_only = invisible_user_in_channel(client, channel); 137 138 modebuf[0] = '\0'; 139 140 /* If the user is banned, don't send any rejoins, it would only be annoying */ 141 if (is_banned(client, channel, BANCHK_JOIN, NULL, NULL)) 142 continue; 143 144 /* Prepare buffers for PART, JOIN, MODE */ 145 ircsnprintf(partbuf, sizeof(partbuf), ":%s!%s@%s PART %s :%s", 146 remember_nick, remember_user, remember_host, 147 channel->name, 148 "Changing host"); 149 150 ircsnprintf(joinbuf, sizeof(joinbuf), ":%s!%s@%s JOIN %s", 151 client->name, client->user->username, GetHost(client), channel->name); 152 153 ircsnprintf(exjoinbuf, sizeof(exjoinbuf), ":%s!%s@%s JOIN %s %s :%s", 154 client->name, client->user->username, GetHost(client), channel->name, 155 IsLoggedIn(client) ? client->user->account : "*", 156 client->info); 157 158 modes = get_chmodes_for_user(client, channels->member_modes); 159 if (!BadPtr(modes)) 160 ircsnprintf(modebuf, sizeof(modebuf), ":%s MODE %s %s", me.name, channel->name, modes); 161 162 for (lp = channel->members; lp; lp = lp->next) 163 { 164 acptr = lp->client; 165 166 if (acptr == client) 167 continue; /* skip self */ 168 169 if (!MyConnect(acptr)) 170 continue; /* only locally connected clients */ 171 172 if (chanops_only && !check_channel_access_member(lp, "hoaq")) 173 continue; /* skip non-ops if requested to (used for mode +D) */ 174 175 if (HasCapabilityFast(acptr, CAP_CHGHOST)) 176 continue; /* we notify 'CAP chghost' users in a different way, so don't send it here. */ 177 178 impact++; 179 180 /* FIXME: if a client does not have the "chghost" cap then 181 * here we will not generate a proper new message, probably 182 * needs to be fixed... I skipped doing it for now. 183 */ 184 sendto_one(acptr, NULL, "%s", partbuf); 185 186 if (HasCapabilityFast(acptr, CAP_EXTENDED_JOIN)) 187 sendto_one(acptr, NULL, "%s", exjoinbuf); 188 else 189 sendto_one(acptr, NULL, "%s", joinbuf); 190 191 if (*modebuf) 192 sendto_one(acptr, NULL, "%s", modebuf); 193 } 194 } 195 } 196 197 /* Now deal with "CAP chghost" clients. 198 * This only needs to be sent one per "common channel". 199 * This would normally call sendto_common_channels_local_butone() but the user already 200 * has the new user/host.. so we do it here.. 201 */ 202 ircsnprintf(buf, sizeof(buf), ":%s!%s@%s CHGHOST %s %s", 203 remember_nick, remember_user, remember_host, 204 client->user->username, 205 GetHost(client)); 206 current_serial++; 207 for (channels = client->user->channel; channels; channels = channels->next) 208 { 209 for (lp = channels->channel->members; lp; lp = lp->next) 210 { 211 acptr = lp->client; 212 if (MyUser(acptr) && HasCapabilityFast(acptr, CAP_CHGHOST) && 213 (acptr->local->serial != current_serial) && (client != acptr)) 214 { 215 /* FIXME: send mtag */ 216 sendto_one(acptr, NULL, "%s", buf); 217 acptr->local->serial = current_serial; 218 } 219 } 220 } 221 222 RunHook(HOOKTYPE_USERHOST_CHANGE, client, remember_user, remember_host); 223 224 if (MyUser(client)) 225 { 226 /* We take the liberty of sending the CHGHOST to the impacted user as 227 * well. This makes things easy for client coders. 228 * (Note that this cannot be merged with the for loop from 15 lines up 229 * since the user may not be in any channels) 230 */ 231 if (HasCapabilityFast(client, CAP_CHGHOST)) 232 sendto_one(client, NULL, "%s", buf); 233 234 if (MyUser(client)) 235 sendnumeric(client, RPL_HOSTHIDDEN, GetHost(client)); 236 237 /* A userhost change always generates the following network traffic: 238 * server to server traffic, CAP "chghost" notifications, and 239 * possibly PART+JOIN+MODE if force-rejoin had work to do. 240 * We give the user a penalty so they don't flood... 241 */ 242 if (impact) 243 add_fake_lag(client, 7000); /* Resulted in rejoins and such. */ 244 else 245 add_fake_lag(client, 4000); /* No rejoins */ 246 } 247 } 248 249 /* 250 * cmd_chghost - 12/07/1999 (two months after I made SETIDENT) - Stskeeps 251 * :prefix CHGHOST <nick> <new hostname> 252 * parv[1] - target user 253 * parv[2] - hostname 254 * 255 */ 256 257 CMD_FUNC(cmd_chghost) 258 { 259 Client *target; 260 261 if (MyUser(client) && !ValidatePermissionsForPath("client:set:host",client,NULL,NULL,NULL)) 262 { 263 sendnumeric(client, ERR_NOPRIVILEGES); 264 return; 265 } 266 267 if ((parc < 3) || BadPtr(parv[2])) 268 { 269 sendnumeric(client, ERR_NEEDMOREPARAMS, "CHGHOST"); 270 return; 271 } 272 273 if (strlen(parv[2]) > (HOSTLEN)) 274 { 275 sendnotice(client, "*** ChgName Error: Requested hostname too long -- rejected."); 276 return; 277 } 278 279 if (!valid_host(parv[2], 0)) 280 { 281 sendnotice(client, "*** /ChgHost Error: A hostname may contain a-z, A-Z, 0-9, '-' & '.' - Please only use them"); 282 return; 283 } 284 285 if (parv[2][0] == ':') 286 { 287 sendnotice(client, "*** A hostname cannot start with ':'"); 288 return; 289 } 290 291 target = find_client(parv[1], NULL); 292 if (!MyUser(client) && !target && (target = find_server_by_uid(parv[1]))) 293 { 294 /* CHGHOST for a UID that is not online. 295 * Let's assume it may not YET be online and forward the message to 296 * the remote server and stop processing ourselves. 297 * That server will then handle pre-registered processing of the 298 * CHGHOST and later communicate the host when the user actually 299 * comes online in the UID message. 300 */ 301 sendto_one(target, recv_mtags, ":%s CHGHOST %s %s", client->id, parv[1], parv[2]); 302 return; 303 } 304 305 if (!target || !target->user) 306 { 307 sendnumeric(client, ERR_NOSUCHNICK, parv[1]); 308 return; 309 } 310 311 if (!strcmp(GetHost(target), parv[2])) 312 { 313 sendnotice(client, "*** /ChgHost Error: requested host is same as current host."); 314 return; 315 } 316 317 userhost_save_current(target); 318 319 switch (UHOST_ALLOWED) 320 { 321 case UHALLOW_NEVER: 322 if (MyUser(client)) 323 { 324 sendnumeric(client, ERR_DISABLED, "CHGHOST", 325 "This command is disabled on this server"); 326 return; 327 } 328 break; 329 case UHALLOW_ALWAYS: 330 break; 331 case UHALLOW_NOCHANS: 332 if (IsUser(target) && MyUser(client) && target->user->joined) 333 { 334 sendnotice(client, "*** /ChgHost can not be used while %s is on a channel", target->name); 335 return; 336 } 337 break; 338 case UHALLOW_REJOIN: 339 /* rejoin sent later when the host has been changed */ 340 break; 341 } 342 343 if (!IsULine(client)) 344 { 345 const char *issuer = command_issued_by_rpc(recv_mtags); 346 if (issuer) 347 { 348 unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client, 349 "CHGHOST: $issuer changed the virtual hostname of $target.details to be $new_hostname", 350 log_data_string("issuer", issuer), 351 log_data_string("change_type", "hostname"), 352 log_data_client("target", target), 353 log_data_string("new_hostname", parv[2])); 354 } else { 355 unreal_log(ULOG_INFO, "chgcmds", "CHGHOST_COMMAND", client, 356 "CHGHOST: $client changed the virtual hostname of $target.details to be $new_hostname", 357 log_data_string("change_type", "hostname"), 358 log_data_client("target", target), 359 log_data_string("new_hostname", parv[2])); 360 } 361 } 362 363 target->umodes |= UMODE_HIDE; 364 target->umodes |= UMODE_SETHOST; 365 366 /* Send to other servers too, unless the client is still in the registration phase (SASL) */ 367 if (IsUser(target)) 368 sendto_server(client, 0, 0, recv_mtags, ":%s CHGHOST %s %s", client->id, target->id, parv[2]); 369 370 safe_strdup(target->user->virthost, parv[2]); 371 userhost_changed(target); 372 }