pissircd/src/modules/webirc.c
Bram Matthys 4f3e524602
Add function set_client_ip() and call HOOKTYPE_IP_CHANGE there if needed.
This to replace the scattered IP setting. It is very important to always
use set_client_ip() from this point. Everywhere!

Also, in addition to client->ip, this adds client->rawip that contains
the IP in network byte order. In older UnrealIRCd versions we always had
the raw IP but not the IP as a string, so we moved to IP as a string,
but it can be useful to have both in terms of optimizations.
Of course, then the client->ip and client->rawip always need to 100% match,
hence the set_client_ip().

This also changes IsIPV6() to do A BUGFIX, it changes it from:
* if local user is the user connected over IPv6? Otherwise, does it have ':' in the IP?
To:
* check if the IPv6 flag is set (which is set if IP contains ':')
This may seem insignificant but it means that for spoofed IP addresses,
such as WEBIRC or transparant proxy, we use the correct transport.
Previously, if the proxy was IPv6 then even if the spoofed user was using
IPv4, the ident check would still be tried over IPv6. That sort of fun.
From now in, in such a situation client->local->socket_type will be
SOCKET_TYPE_IPV6 but since client->ip (and rawip) will contain IPv4
the IsIPV6() will actually return false, as it should be.

Also, in the HOOKTYPE_IP_CHANGE, enforce that if HOOK_DENY is returned,
the the user is killed by dead_link(). The user must be killed because
that is what we expect, and you cannot use exit_client() because from
some code paths that would be too much freed structures / hassle,
as a comment in src/modules/connect-flood.c correctly states:
/* There are two reasons why we can't use exit_client() here:
 * 1) Because the HOOKTYPE_IP_CHANGE call may be too deep.
 *    Eg: read_packet -> webserver_packet_in ->
 *    webserver_handle_request_header -> webserver_handle_request ->
 *    RunHook().... and then returning without touching anything
 *    after an exit_client() would not be feasible.
 * 2) Because in HOOKTYPE_ACCEPT we always need to use dead_socket
 *    if we want to print a friendly message to TLS users.
 */
2024-07-20 12:22:26 +02:00

272 lines
6.8 KiB
C

/*
* WebIRC / CGI:IRC Support
* (C) Copyright 2006-.. Bram Matthys (Syzop) and the UnrealIRCd team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 1, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "unrealircd.h"
/* Module header */
ModuleHeader MOD_HEADER
= {
"webirc",
"5.0",
"WebIRC/CGI:IRC Support",
"UnrealIRCd Team",
"unrealircd-6",
};
/* Global variables */
ModDataInfo *webirc_md = NULL;
/* Forward declarations */
CMD_FUNC(cmd_webirc);
int webirc_local_pass(Client *client, const char *password);
const char *webirc_md_serialize(ModData *m);
void webirc_md_unserialize(const char *str, ModData *m);
void webirc_md_free(ModData *md);
int webirc_secure_connect(Client *client);
#define IsWEBIRC(x) (moddata_client(x, webirc_md).l)
#define IsWEBIRCSecure(x) (moddata_client(x, webirc_md).l == 2)
#define ClearWEBIRC(x) do { moddata_client(x, webirc_md).l = 0; } while(0)
#define SetWEBIRC(x) do { moddata_client(x, webirc_md).l = 1; } while(0)
#define SetWEBIRCSecure(x) do { moddata_client(x, webirc_md).l = 2; } while(0)
#define MSG_WEBIRC "WEBIRC"
/** Called upon module init */
MOD_INIT()
{
ModDataInfo mreq;
MARK_AS_OFFICIAL_MODULE(modinfo);
memset(&mreq, 0, sizeof(mreq));
mreq.name = "webirc";
mreq.type = MODDATATYPE_CLIENT;
mreq.serialize = webirc_md_serialize;
mreq.unserialize = webirc_md_unserialize;
mreq.free = webirc_md_free;
mreq.sync = MODDATA_SYNC_EARLY;
webirc_md = ModDataAdd(modinfo->handle, mreq);
if (!webirc_md)
{
config_error("could not register webirc moddata");
return MOD_FAILED;
}
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_PASS, 0, webirc_local_pass);
HookAdd(modinfo->handle, HOOKTYPE_SECURE_CONNECT, 0, webirc_secure_connect);
CommandAdd(modinfo->handle, MSG_WEBIRC, cmd_webirc, MAXPARA, CMD_UNREGISTERED);
return MOD_SUCCESS;
}
/** Called upon module load */
MOD_LOAD()
{
return MOD_SUCCESS;
}
/** Called upon unload */
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
const char *webirc_md_serialize(ModData *m)
{
static char buf[32];
if (m->i == 0)
return NULL; /* not set */
snprintf(buf, sizeof(buf), "%d", m->i);
return buf;
}
void webirc_md_unserialize(const char *str, ModData *m)
{
m->i = atoi(str);
}
void webirc_md_free(ModData *md)
{
/* we have nothing to free actually, but we must set to zero */
md->l = 0;
}
ConfigItem_proxy *find_webirc(Client *client, const char *password, ProxyType type, char **errorstr)
{
ConfigItem_proxy *e;
char *error = NULL;
for (e = conf_proxy; e; e = e->next)
{
if ((e->type == type) && user_allowed_by_security_group(client, e->mask))
{
if (type == PROXY_WEBIRC)
{
/* Check password */
if (!Auth_Check(client, e->auth, password))
error = "CGI:IRC -- Invalid password";
else
return e; /* Found matching block, return straight away */
} else
if (type == PROXY_WEBIRC_PASS) {
return e; /* The PROXY_WEBIRC_PASS type has no password checking */
}
}
}
if (error)
*errorstr = error; /* Invalid password (this error was delayed) */
else
*errorstr = "CGI:IRC -- No access"; /* No match found */
return NULL;
}
#define WEBIRC_STRING "WEBIRC_"
#define WEBIRC_STRINGLEN (sizeof(WEBIRC_STRING)-1)
/* Does the CGI:IRC host spoofing work */
void dowebirc(Client *client, const char *ip, const char *host, const char *options)
{
if (IsWEBIRC(client))
{
exit_client(client, NULL, "Double CGI:IRC request (already identified)");
return;
}
if (host && !strcmp(ip, host))
host = NULL; /* host did not resolve, make it NULL */
if (!set_client_ip(client, ip))
return; /* Failed, client exited */
/* Update client->local->hostp */
/* (free old) */
if (client->local->hostp)
{
unreal_free_hostent(client->local->hostp);
client->local->hostp = NULL;
}
/* (create new) */
if (host && valid_host(host, 1))
client->local->hostp = unreal_create_hostent(host, client->ip);
/* Update sockhost */
set_sockhost(client, client->ip);
SetWEBIRC(client);
if (options)
{
char optionsbuf[BUFSIZE];
char *name, *p = NULL, *p2;
strlcpy(optionsbuf, options, sizeof(optionsbuf));
for (name = strtoken(&p, optionsbuf, " "); name; name = strtoken(&p, NULL, " "))
{
p2 = strchr(name, '=');
if (p2)
*p2 = '\0';
if (!strcmp(name, "secure") && IsSecure(client))
{
/* The entire [client]--[webirc gw]--[server] chain is secure */
SetWEBIRCSecure(client);
}
}
}
}
/* WEBIRC <pass> "cgiirc" <hostname> <ip> [:option1 [option2...]]*/
CMD_FUNC(cmd_webirc)
{
const char *ip, *host, *password, *options;
ConfigItem_proxy *e;
char *error = NULL;
if ((parc < 5) || BadPtr(parv[4]))
{
sendnumeric(client, ERR_NEEDMOREPARAMS, "WEBIRC");
return;
}
password = parv[1];
host = !DONT_RESOLVE ? parv[3] : parv[4];
ip = parv[4];
options = parv[5]; /* can be NULL */
/* Check if allowed host */
e = find_webirc(client, password, PROXY_WEBIRC, &error);
if (!e)
{
exit_client(client, NULL, error);
return;
}
/* And do our job.. */
dowebirc(client, ip, host, options);
}
int webirc_local_pass(Client *client, const char *password)
{
if (!strncmp(password, WEBIRC_STRING, WEBIRC_STRINGLEN))
{
char buf[512];
char *ip, *host;
ConfigItem_proxy *e;
char *error = NULL;
/* Work on a copy as we may trash it */
strlcpy(buf, password, sizeof(buf));
e = find_webirc(client, NULL, PROXY_WEBIRC_PASS, &error);
if (e)
{
/* Ok now we got that sorted out, proceed:
* Syntax: WEBIRC_<ip>_<resolvedhostname>
* The <resolvedhostname> has been checked ip->host AND host->ip by CGI:IRC itself
* already so we trust it.
*/
ip = buf + WEBIRC_STRINGLEN;
host = strchr(ip, '_');
if (!host)
{
exit_client(client, NULL, "Invalid CGI:IRC IP received");
return HOOK_DENY;
}
*host++ = '\0';
dowebirc(client, ip, host, NULL);
return HOOK_DENY;
}
/* fallthrough if not in webirc block.. */
}
return HOOK_CONTINUE; /* not webirc */
}
/** Called from register_user() right after setting user +z */
int webirc_secure_connect(Client *client)
{
/* Remove secure mode (-z) if the WEBIRC gateway did not ensure
* us that their [client]--[webirc gateway] connection is also
* secure (eg: using https)
*/
if (IsWEBIRC(client) && IsSecureConnect(client) && !IsWEBIRCSecure(client))
client->umodes &= ~UMODE_SECURE;
return 0;
}