unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

webirc.c (12027B)

      1 /*
      2  * WebIRC / CGI:IRC Support
      3  * (C) Copyright 2006-.. Bram Matthys (Syzop) and the UnrealIRCd team
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 1, or (at your option)
      8  * any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program; if not, write to the Free Software
     17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     18  */
     19 #include "unrealircd.h"
     20 
     21 /* Types */
     22 typedef struct ConfigItem_webirc ConfigItem_webirc;
     23 
     24 typedef enum {
     25 	WEBIRC_PASS=1, WEBIRC_WEBIRC=2
     26 } WEBIRCType;
     27 
     28 struct ConfigItem_webirc {
     29 	ConfigItem_webirc *prev, *next;
     30 	ConfigFlag flag;
     31 	ConfigItem_mask *mask;
     32 	WEBIRCType type;
     33 	AuthConfig *auth;
     34 };
     35 
     36 /* Module header */
     37 ModuleHeader MOD_HEADER
     38 = {
     39 	"webirc",
     40 	"5.0",
     41 	"WebIRC/CGI:IRC Support",
     42 	"UnrealIRCd Team",
     43 	"unrealircd-6",
     44 };
     45 
     46 /* Global variables */
     47 ModDataInfo *webirc_md = NULL;
     48 ConfigItem_webirc *conf_webirc = NULL;
     49 
     50 /* Forward declarations */
     51 CMD_FUNC(cmd_webirc);
     52 int webirc_local_pass(Client *client, const char *password);
     53 int webirc_config_test(ConfigFile *, ConfigEntry *, int, int *);
     54 int webirc_config_run(ConfigFile *, ConfigEntry *, int);
     55 void webirc_free_conf(void);
     56 void delete_webircblock(ConfigItem_webirc *e);
     57 const char *webirc_md_serialize(ModData *m);
     58 void webirc_md_unserialize(const char *str, ModData *m);
     59 void webirc_md_free(ModData *md);
     60 int webirc_secure_connect(Client *client);
     61 
     62 #define IsWEBIRC(x)			(moddata_client(x, webirc_md).l)
     63 #define IsWEBIRCSecure(x)	(moddata_client(x, webirc_md).l == 2)
     64 #define ClearWEBIRC(x)		do { moddata_client(x, webirc_md).l = 0; } while(0)
     65 #define SetWEBIRC(x)		do { moddata_client(x, webirc_md).l = 1; } while(0)
     66 #define SetWEBIRCSecure(x)	do { moddata_client(x, webirc_md).l = 2; } while(0)
     67 
     68 #define MSG_WEBIRC "WEBIRC"
     69 
     70 MOD_TEST()
     71 {
     72 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, webirc_config_test);
     73 	return MOD_SUCCESS;
     74 }
     75 
     76 /** Called upon module init */
     77 MOD_INIT()
     78 {
     79 	ModDataInfo mreq;
     80 
     81 	MARK_AS_OFFICIAL_MODULE(modinfo);
     82 	
     83 	memset(&mreq, 0, sizeof(mreq));
     84 	mreq.name = "webirc";
     85 	mreq.type = MODDATATYPE_CLIENT;
     86 	mreq.serialize = webirc_md_serialize;
     87 	mreq.unserialize = webirc_md_unserialize;
     88 	mreq.free = webirc_md_free;
     89 	mreq.sync = MODDATA_SYNC_EARLY;
     90 	webirc_md = ModDataAdd(modinfo->handle, mreq);
     91 	if (!webirc_md)
     92 	{
     93 		config_error("could not register webirc moddata");
     94 		return MOD_FAILED;
     95 	}
     96 
     97 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, webirc_config_run);
     98 	HookAdd(modinfo->handle, HOOKTYPE_LOCAL_PASS, 0, webirc_local_pass);
     99 	HookAdd(modinfo->handle, HOOKTYPE_SECURE_CONNECT, 0, webirc_secure_connect);
    100 
    101 	CommandAdd(modinfo->handle, MSG_WEBIRC, cmd_webirc, MAXPARA, CMD_UNREGISTERED);
    102 		
    103 	return MOD_SUCCESS;
    104 }
    105 
    106 /** Called upon module load */
    107 MOD_LOAD()
    108 {
    109 	return MOD_SUCCESS;
    110 }
    111 
    112 /** Called upon unload */
    113 MOD_UNLOAD()
    114 {
    115 	webirc_free_conf();
    116 	return MOD_SUCCESS;
    117 }
    118 
    119 void webirc_free_conf(void)
    120 {
    121 	ConfigItem_webirc *webirc_ptr, *next;
    122 
    123 	for (webirc_ptr = conf_webirc; webirc_ptr; webirc_ptr = next)
    124 	{
    125 		next = webirc_ptr->next;
    126 		delete_webircblock(webirc_ptr);
    127 	}
    128 	conf_webirc = NULL;
    129 }
    130 
    131 void delete_webircblock(ConfigItem_webirc *e)
    132 {
    133 	unreal_delete_masks(e->mask);
    134 	if (e->auth)
    135 		Auth_FreeAuthConfig(e->auth);
    136 	DelListItem(e, conf_webirc);
    137 	safe_free(e);
    138 }
    139 
    140 int webirc_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    141 {
    142 	ConfigEntry *cep;
    143 	int errors = 0;
    144 	char has_mask = 0; /* mandatory */
    145 	char has_password = 0; /* mandatory */
    146 	char has_type = 0; /* optional (used for dup checking) */
    147 	WEBIRCType webirc_type = WEBIRC_WEBIRC; /* the default */
    148 
    149 	if (type != CONFIG_MAIN)
    150 		return 0;
    151 	
    152 	if (!ce)
    153 		return 0;
    154 	
    155 	if (!strcmp(ce->name, "cgiirc"))
    156 	{
    157 		config_error("%s:%i: the cgiirc block has been renamed to webirc and "
    158 		             "the syntax has changed in UnrealIRCd 4",
    159 		             ce->file->filename, ce->line_number);
    160 		*errs = 1;
    161 		return -1;
    162 	}
    163 
    164 	if (strcmp(ce->name, "webirc"))
    165 		return 0; /* not interested in non-webirc stuff.. */
    166 
    167 	/* Now actually go parse the webirc { } block */
    168 	for (cep = ce->items; cep; cep = cep->next)
    169 	{
    170 		if (!cep->value)
    171 		{
    172 			config_error_empty(cep->file->filename, cep->line_number,
    173 				"webirc", cep->name);
    174 			errors++;
    175 			continue;
    176 		}
    177 		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
    178 		{
    179 			if (cep->value || cep->items)
    180 				has_mask = 1;
    181 		}
    182 		else if (!strcmp(cep->name, "password"))
    183 		{
    184 			if (has_password)
    185 			{
    186 				config_warn_duplicate(cep->file->filename, 
    187 					cep->line_number, "webirc::password");
    188 				continue;
    189 			}
    190 			has_password = 1;
    191 			if (Auth_CheckError(cep) < 0)
    192 				errors++;
    193 		}
    194 		else if (!strcmp(cep->name, "type"))
    195 		{
    196 			if (has_type)
    197 			{
    198 				config_warn_duplicate(cep->file->filename,
    199 					cep->line_number, "webirc::type");
    200 			}
    201 			has_type = 1;
    202 			if (!strcmp(cep->value, "webirc"))
    203 				webirc_type = WEBIRC_WEBIRC;
    204 			else if (!strcmp(cep->value, "old"))
    205 				webirc_type = WEBIRC_PASS;
    206 			else
    207 			{
    208 				config_error("%s:%i: unknown webirc::type '%s', should be either 'webirc' or 'old'",
    209 					cep->file->filename, cep->line_number, cep->value);
    210 				errors++;
    211 			}
    212 		}
    213 		else
    214 		{
    215 			config_error_unknown(cep->file->filename, cep->line_number,
    216 				"webirc", cep->name);
    217 			errors++;
    218 		}
    219 	}
    220 	if (!has_mask)
    221 	{
    222 		config_error_missing(ce->file->filename, ce->line_number,
    223 			"webirc::mask");
    224 		errors++;
    225 	}
    226 
    227 	if (!has_password && (webirc_type == WEBIRC_WEBIRC))
    228 	{
    229 		config_error_missing(ce->file->filename, ce->line_number,
    230 			"webirc::password");
    231 		errors++;
    232 	}
    233 	
    234 	if (has_password && (webirc_type == WEBIRC_PASS))
    235 	{
    236 		config_error("%s:%i: webirc block has type set to 'old' but has a password set. "
    237 		             "Passwords are not used with type 'old'. Either remove the password or "
    238 		             "use the 'webirc' method instead.",
    239 		             ce->file->filename, ce->line_number);
    240 		errors++;
    241 	}
    242 
    243 	*errs = errors;
    244 	return errors ? -1 : 1;
    245 }
    246 
    247 int webirc_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
    248 {
    249 	ConfigEntry *cep;
    250 	ConfigItem_webirc *webirc = NULL;
    251 	
    252 	if (type != CONFIG_MAIN)
    253 		return 0;
    254 	
    255 	if (!ce || !ce->name || strcmp(ce->name, "webirc"))
    256 		return 0; /* not interested */
    257 
    258 	webirc = safe_alloc(sizeof(ConfigItem_webirc));
    259 	webirc->type = WEBIRC_WEBIRC; /* default */
    260 
    261 	for (cep = ce->items; cep; cep = cep->next)
    262 	{
    263 		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
    264 			unreal_add_masks(&webirc->mask, cep);
    265 		else if (!strcmp(cep->name, "password"))
    266 			webirc->auth = AuthBlockToAuthConfig(cep);
    267 		else if (!strcmp(cep->name, "type"))
    268 		{
    269 			if (!strcmp(cep->value, "webirc"))
    270 				webirc->type = WEBIRC_WEBIRC;
    271 			else if (!strcmp(cep->value, "old"))
    272 				webirc->type = WEBIRC_PASS;
    273 			else
    274 				abort();
    275 		}
    276 	}
    277 	
    278 	AddListItem(webirc, conf_webirc);
    279 	
    280 	return 0;
    281 }
    282 
    283 const char *webirc_md_serialize(ModData *m)
    284 {
    285 	static char buf[32];
    286 	if (m->i == 0)
    287 		return NULL; /* not set */
    288 	snprintf(buf, sizeof(buf), "%d", m->i);
    289 	return buf;
    290 }
    291 
    292 void webirc_md_unserialize(const char *str, ModData *m)
    293 {
    294 	m->i = atoi(str);
    295 }
    296 
    297 void webirc_md_free(ModData *md)
    298 {
    299 	/* we have nothing to free actually, but we must set to zero */
    300 	md->l = 0;
    301 }
    302 
    303 ConfigItem_webirc *find_webirc(Client *client, const char *password, WEBIRCType type, char **errorstr)
    304 {
    305 	ConfigItem_webirc *e;
    306 	char *error = NULL;
    307 
    308 	for (e = conf_webirc; e; e = e->next)
    309 	{
    310 		if ((e->type == type) && unreal_mask_match(client, e->mask))
    311 		{
    312 			if (type == WEBIRC_WEBIRC)
    313 			{
    314 				/* Check password */
    315 				if (!Auth_Check(client, e->auth, password))
    316 					error = "CGI:IRC -- Invalid password";
    317 				else
    318 					return e; /* Found matching block, return straight away */
    319 			} else {
    320 				return e; /* The WEBIRC_PASS type has no password checking */
    321 			}
    322 		}
    323 	}
    324 
    325 	if (error)
    326 		*errorstr = error; /* Invalid password (this error was delayed) */
    327 	else
    328 		*errorstr = "CGI:IRC -- No access"; /* No match found */
    329 
    330 	return NULL;
    331 }
    332 
    333 #define WEBIRC_STRING     "WEBIRC_"
    334 #define WEBIRC_STRINGLEN  (sizeof(WEBIRC_STRING)-1)
    335 
    336 /* Does the CGI:IRC host spoofing work */
    337 void dowebirc(Client *client, const char *ip, const char *host, const char *options)
    338 {
    339 	char oldip[64];
    340 	char scratch[64];
    341 
    342 	if (IsWEBIRC(client))
    343 	{
    344 		exit_client(client, NULL, "Double CGI:IRC request (already identified)");
    345 		return;
    346 	}
    347 
    348 	if (host && !strcmp(ip, host))
    349 		host = NULL; /* host did not resolve, make it NULL */
    350 
    351 	/* STEP 1: Update client->local->ip
    352 	   inet_pton() returns 1 on success, 0 on bad input, -1 on bad AF */
    353 	if (!is_valid_ip(ip))
    354 	{
    355 		/* then we have an invalid IP */
    356 		exit_client(client, NULL, "Invalid IP address");
    357 		return;
    358 	}
    359 
    360 	/* STEP 2: Update GetIP() */
    361 	strlcpy(oldip, client->ip, sizeof(oldip));
    362 	safe_strdup(client->ip, ip);
    363 		
    364 	/* STEP 3: Update client->local->hostp */
    365 	/* (free old) */
    366 	if (client->local->hostp)
    367 	{
    368 		unreal_free_hostent(client->local->hostp);
    369 		client->local->hostp = NULL;
    370 	}
    371 	/* (create new) */
    372 	if (host && valid_host(host, 1))
    373 		client->local->hostp = unreal_create_hostent(host, client->ip);
    374 
    375 	/* STEP 4: Update sockhost
    376 	   Make sure that if this any IPv4 address is _not_ prefixed with
    377 	   "::ffff:" by using Inet_ia2p().
    378 	 */
    379 	// Hmm I ignored above warning. May be bad during transition period.
    380 	strlcpy(client->local->sockhost, client->ip, sizeof(client->local->sockhost));
    381 
    382 	SetWEBIRC(client);
    383 
    384 	if (options)
    385 	{
    386 		char optionsbuf[BUFSIZE];
    387 		char *name, *p = NULL, *p2;
    388 		strlcpy(optionsbuf, options, sizeof(optionsbuf));
    389 		for (name = strtoken(&p, optionsbuf, " "); name; name = strtoken(&p, NULL, " "))
    390 		{
    391 			p2 = strchr(name, '=');
    392 			if (p2)
    393 				*p2 = '\0';
    394 			if (!strcmp(name, "secure") && IsSecure(client))
    395 			{
    396 				/* The entire [client]--[webirc gw]--[server] chain is secure */
    397 				SetWEBIRCSecure(client);
    398 			}
    399 		}
    400 	}
    401 
    402 	RunHook(HOOKTYPE_IP_CHANGE, client, oldip);
    403 }
    404 
    405 /* WEBIRC <pass> "cgiirc" <hostname> <ip> [:option1 [option2...]]*/
    406 CMD_FUNC(cmd_webirc)
    407 {
    408 	const char *ip, *host, *password, *options;
    409 	ConfigItem_webirc *e;
    410 	char *error = NULL;
    411 
    412 	if ((parc < 5) || BadPtr(parv[4]))
    413 	{
    414 		sendnumeric(client, ERR_NEEDMOREPARAMS, "WEBIRC");
    415 		return;
    416 	}
    417 
    418 	password = parv[1];
    419 	host = !DONT_RESOLVE ? parv[3] : parv[4];
    420 	ip = parv[4];
    421 	options = parv[5]; /* can be NULL */
    422 
    423 	/* Check if allowed host */
    424 	e = find_webirc(client, password, WEBIRC_WEBIRC, &error);
    425 	if (!e)
    426 	{
    427 		exit_client(client, NULL, error);
    428 		return;
    429 	}
    430 
    431 	/* And do our job.. */
    432 	dowebirc(client, ip, host, options);
    433 }
    434 
    435 int webirc_local_pass(Client *client, const char *password)
    436 {
    437 	if (!strncmp(password, WEBIRC_STRING, WEBIRC_STRINGLEN))
    438 	{
    439 		char buf[512];
    440 		char *ip, *host;
    441 		ConfigItem_webirc *e;
    442 		char *error = NULL;
    443 
    444 		/* Work on a copy as we may trash it */
    445 		strlcpy(buf, password, sizeof(buf));
    446 		e = find_webirc(client, NULL, WEBIRC_PASS, &error);
    447 		if (e)
    448 		{
    449 			/* Ok now we got that sorted out, proceed:
    450 			 * Syntax: WEBIRC_<ip>_<resolvedhostname>
    451 			 * The <resolvedhostname> has been checked ip->host AND host->ip by CGI:IRC itself
    452 			 * already so we trust it.
    453 			 */
    454 			ip = buf + WEBIRC_STRINGLEN;
    455 			host = strchr(ip, '_');
    456 			if (!host)
    457 			{
    458 				exit_client(client, NULL, "Invalid CGI:IRC IP received");
    459 				return HOOK_DENY;
    460 			}
    461 			*host++ = '\0';
    462 		
    463 			dowebirc(client, ip, host, NULL);
    464 			return HOOK_DENY;
    465 		}
    466 		/* fallthrough if not in webirc block.. */
    467 	}
    468 
    469 	return HOOK_CONTINUE; /* not webirc */
    470 }
    471 
    472 /** Called from register_user() right after setting user +z */
    473 int webirc_secure_connect(Client *client)
    474 {
    475 	/* Remove secure mode (-z) if the WEBIRC gateway did not ensure
    476 	 * us that their [client]--[webirc gateway] connection is also
    477 	 * secure (eg: using https)
    478 	 */
    479 	if (IsWEBIRC(client) && IsSecureConnect(client) && !IsWEBIRCSecure(client))
    480 		client->umodes &= ~UMODE_SECURE;
    481 	return 0;
    482 }