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 }