1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-04 17:33:12 +02:00
Files
unrealircd/src/modules/sasl.c
T
Bram Matthys 977c4b433a Make it so services can CHGHOST/CHGIDENT in the SASL / registration phase.
This so users can come online directly with the correct vhost set,
and not first with a standard (usually cloaked) host while auto-(re-)joining
followed by a CHGHOST later.

This is a long outstanding wish from users, I think.

Services can simply send a CHGHOST/CHGIDENT to the UID, for example
right before they send the SASL ... D S message (SASL succeeded)
they can send like: CHGHOST 002ABCDEF some.nice.host

Then UnrealIRCd 6.0.7-git and later will handle the CHGHOST even if
the user is not known yet. Technically, the server where the UID is
on will handle the message. And remote servers that don't know the
user with this UID yet will forward to the server with the SID-portion
of the UID. The CHGHOST will not be a broadcast but the vhost will
show up in the UID protocol message that introduces the user.
For CHGIDENT it is a similar story.

Light testing has been done but more extensive testing is welcomed.
2023-02-08 10:49:15 +01:00

413 lines
11 KiB
C

/*
* IRC - Internet Relay Chat, src/modules/sasl.c
* (C) 2012 The UnrealIRCd Team
*
* See file AUTHORS in IRC package for additional names of
* the programmers.
*
* 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"
ModuleHeader MOD_HEADER
= {
"sasl",
"5.2.1",
"SASL",
"UnrealIRCd Team",
"unrealircd-6",
};
/* Forward declarations */
void saslmechlist_free(ModData *m);
const char *saslmechlist_serialize(ModData *m);
void saslmechlist_unserialize(const char *str, ModData *m);
const char *sasl_capability_parameter(Client *client);
int sasl_server_synced(Client *client);
int sasl_account_login(Client *client, MessageTag *mtags);
EVENT(sasl_timeout);
/* Macros */
#define MSG_AUTHENTICATE "AUTHENTICATE"
#define MSG_SASL "SASL"
#define AGENT_SID(agent_p) (agent_p->user != NULL ? agent_p->user->server : agent_p->name)
/* Variables */
long CAP_SASL = 0L;
/*
* The following people were involved in making the previous iteration of SASL over
* IRC which allowed psuedo-identifiers:
*
* danieldg, Daniel de Graff <danieldg@inspircd.org>
* jilles, Jilles Tjoelker <jilles@stack.nl>
* Jobe, Matthew Beeching <jobe@mdbnet.co.uk>
* gxti, Michael Tharp <gxti@partiallystapled.com>
* nenolod, William Pitcock <nenolod@dereferenced.org>
*
* Thanks also to all of the client authors which have implemented SASL in their
* clients. With the backwards-compatibility layer allowing "lightweight" SASL
* implementations, we now truly have a universal authentication mechanism for
* IRC.
*/
int sasl_account_login(Client *client, MessageTag *mtags)
{
if (!MyConnect(client))
return 0;
/* Notify user */
if (IsLoggedIn(client))
{
sendnumeric(client, RPL_LOGGEDIN,
BadPtr(client->name) ? "*" : client->name,
BadPtr(client->user->username) ? "*" : client->user->username,
BadPtr(client->user->realhost) ? "*" : client->user->realhost,
client->user->account, client->user->account);
}
else
{
sendnumeric(client, RPL_LOGGEDOUT,
BadPtr(client->name) ? "*" : client->name,
BadPtr(client->user->username) ? "*" : client->user->username,
BadPtr(client->user->realhost) ? "*" : client->user->realhost);
}
return 0;
}
/*
* SASL message
*
* parv[1]: distribution mask
* parv[2]: target
* parv[3]: mode/state
* parv[4]: data
* parv[5]: out-of-bound data
*/
CMD_FUNC(cmd_sasl)
{
if (!SASL_SERVER || MyUser(client) || (parc < 4) || !parv[4])
return;
if (!strcasecmp(parv[1], me.name) || !strncmp(parv[1], me.id, 3))
{
Client *target;
target = find_client(parv[2], NULL);
if (!target || !MyConnect(target))
return;
if (target->user == NULL)
make_user(target);
/* reject if another SASL agent is answering */
if (*target->local->sasl_agent && strcasecmp(client->name, target->local->sasl_agent))
return;
else
strlcpy(target->local->sasl_agent, client->name, sizeof(target->local->sasl_agent));
if (*parv[3] == 'C')
{
RunHookReturn(HOOKTYPE_SASL_CONTINUATION, !=0, target, parv[4]);
sendto_one(target, NULL, "AUTHENTICATE %s", parv[4]);
}
else if (*parv[3] == 'D')
{
*target->local->sasl_agent = '\0';
if (*parv[4] == 'F')
{
target->local->sasl_sent_time = 0;
add_fake_lag(target, 7000); /* bump fakelag due to failed authentication attempt */
RunHookReturn(HOOKTYPE_SASL_RESULT, !=0, target, 0);
sendnumeric(target, ERR_SASLFAIL);
}
else if (*parv[4] == 'S')
{
target->local->sasl_sent_time = 0;
target->local->sasl_complete++;
RunHookReturn(HOOKTYPE_SASL_RESULT, !=0, target, 1);
sendnumeric(target, RPL_SASLSUCCESS);
}
}
else if (*parv[3] == 'M')
sendnumeric(target, RPL_SASLMECHS, parv[4]);
return;
}
/* not for us; propagate. */
sendto_server(client, 0, 0, NULL, ":%s SASL %s %s %c %s %s",
client->name, parv[1], parv[2], *parv[3], parv[4], parc > 5 ? parv[5] : "");
}
/*
* AUTHENTICATE message
*
* parv[1]: data
*/
CMD_FUNC(cmd_authenticate)
{
Client *agent_p = NULL;
/* Failing to use CAP REQ for sasl is a protocol violation. */
if (!SASL_SERVER || !MyConnect(client) || BadPtr(parv[1]) || !HasCapability(client, "sasl"))
return;
if ((parv[1][0] == ':') || strchr(parv[1], ' '))
{
sendnumeric(client, ERR_CANNOTDOCOMMAND, "AUTHENTICATE", "Invalid parameter");
return;
}
if (strlen(parv[1]) > 400)
{
sendnumeric(client, ERR_SASLTOOLONG);
return;
}
if (client->user == NULL)
make_user(client);
if (*client->local->sasl_agent)
agent_p = find_client(client->local->sasl_agent, NULL);
if (agent_p == NULL)
{
char *addr = BadPtr(client->ip) ? "0" : client->ip;
const char *certfp = moddata_client_get(client, "certfp");
sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s H %s %s",
me.name, SASL_SERVER, client->id, addr, addr);
if (certfp)
sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s S %s %s",
me.name, SASL_SERVER, client->id, parv[1], certfp);
else
sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s S %s",
me.name, SASL_SERVER, client->id, parv[1]);
}
else
sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s C %s",
me.name, AGENT_SID(agent_p), client->id, parv[1]);
client->local->sasl_out++;
client->local->sasl_sent_time = TStime();
}
static int abort_sasl(Client *client)
{
client->local->sasl_sent_time = 0;
if (client->local->sasl_out == 0 || client->local->sasl_complete)
return 0;
client->local->sasl_out = client->local->sasl_complete = 0;
sendnumeric(client, ERR_SASLABORTED);
if (*client->local->sasl_agent)
{
Client *agent_p = find_client(client->local->sasl_agent, NULL);
if (agent_p != NULL)
{
sendto_server(NULL, 0, 0, NULL, ":%s SASL %s %s D A",
me.name, AGENT_SID(agent_p), client->id);
return 0;
}
}
sendto_server(NULL, 0, 0, NULL, ":%s SASL * %s D A", me.name, client->id);
return 0;
}
/** Is this capability visible?
* Note that 'client' may be NULL when queried from CAP DEL / CAP NEW
*/
int sasl_capability_visible(Client *client)
{
if (!SASL_SERVER || !find_server(SASL_SERVER, NULL))
return 0;
/* Don't advertise 'sasl' capability if we are going to reject the
* user anyway due to set::plaintext-policy. This way the client
* won't attempt SASL authentication and thus it prevents the client
* from sending the password unencrypted (in case of method PLAIN).
*/
if (client && !IsSecure(client) && !IsLocalhost(client) && (iConf.plaintext_policy_user == POLICY_DENY))
return 0;
/* Similarly, don't advertise when we are going to reject the user
* due to set::outdated-tls-policy.
*/
if (IsSecure(client) && (iConf.outdated_tls_policy_user == POLICY_DENY) && outdated_tls_client(client))
return 0;
return 1;
}
int sasl_connect(Client *client)
{
return abort_sasl(client);
}
int sasl_quit(Client *client, MessageTag *mtags, const char *comment)
{
return abort_sasl(client);
}
int sasl_server_quit(Client *client, MessageTag *mtags)
{
if (!SASL_SERVER)
return 0;
/* If the set::sasl-server is gone, let everyone know 'sasl' is no longer available */
if (!strcasecmp(client->name, SASL_SERVER))
send_cap_notify(0, "sasl");
return 0;
}
void auto_discover_sasl_server(int justlinked)
{
if (!SASL_SERVER && SERVICES_NAME)
{
Client *client = find_server(SERVICES_NAME, NULL);
if (client && moddata_client_get(client, "saslmechlist"))
{
/* SASL server found */
if (justlinked)
{
unreal_log(ULOG_INFO, "config", "SASL_SERVER_AUTODETECT", client,
"Services server $client provides SASL authentication, good! "
"I'm setting set::sasl-server to \"$client\" internally.");
}
safe_strdup(SASL_SERVER, SERVICES_NAME);
if (justlinked)
sasl_server_synced(client);
}
}
}
int sasl_server_synced(Client *client)
{
if (!SASL_SERVER)
{
auto_discover_sasl_server(1);
return 0;
}
/* If the set::sasl-server is gone, let everyone know 'sasl' is no longer available */
if (!strcasecmp(client->name, SASL_SERVER))
send_cap_notify(1, "sasl");
return 0;
}
MOD_INIT()
{
ClientCapabilityInfo cap;
ModDataInfo mreq;
MARK_AS_OFFICIAL_MODULE(modinfo);
CommandAdd(modinfo->handle, MSG_SASL, cmd_sasl, MAXPARA, CMD_USER|CMD_SERVER);
CommandAdd(modinfo->handle, MSG_AUTHENTICATE, cmd_authenticate, MAXPARA, CMD_UNREGISTERED|CMD_USER);
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_CONNECT, 0, sasl_connect);
HookAdd(modinfo->handle, HOOKTYPE_LOCAL_QUIT, 0, sasl_quit);
HookAdd(modinfo->handle, HOOKTYPE_SERVER_QUIT, 0, sasl_server_quit);
HookAdd(modinfo->handle, HOOKTYPE_SERVER_SYNCED, 0, sasl_server_synced);
HookAdd(modinfo->handle, HOOKTYPE_ACCOUNT_LOGIN, 0, sasl_account_login);
memset(&cap, 0, sizeof(cap));
cap.name = "sasl";
cap.visible = sasl_capability_visible;
cap.parameter = sasl_capability_parameter;
ClientCapabilityAdd(modinfo->handle, &cap, &CAP_SASL);
memset(&mreq, 0, sizeof(mreq));
mreq.name = "saslmechlist";
mreq.free = saslmechlist_free;
mreq.serialize = saslmechlist_serialize;
mreq.unserialize = saslmechlist_unserialize;
mreq.sync = MODDATA_SYNC_EARLY;
mreq.self_write = 1;
mreq.type = MODDATATYPE_CLIENT;
ModDataAdd(modinfo->handle, mreq);
EventAdd(modinfo->handle, "sasl_timeout", sasl_timeout, NULL, 2000, 0);
return MOD_SUCCESS;
}
MOD_LOAD()
{
auto_discover_sasl_server(0);
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
return MOD_SUCCESS;
}
void saslmechlist_free(ModData *m)
{
safe_free(m->str);
}
const char *saslmechlist_serialize(ModData *m)
{
if (!m->str)
return NULL;
return m->str;
}
void saslmechlist_unserialize(const char *str, ModData *m)
{
safe_strdup(m->str, str);
}
const char *sasl_capability_parameter(Client *client)
{
Client *server;
if (SASL_SERVER)
{
server = find_server(SASL_SERVER, NULL);
if (server)
return moddata_client_get(server, "saslmechlist"); /* NOTE: could still return NULL */
}
return NULL;
}
EVENT(sasl_timeout)
{
Client *client;
list_for_each_entry(client, &unknown_list, lclient_node)
{
if (client->local->sasl_sent_time &&
(TStime() - client->local->sasl_sent_time > iConf.sasl_timeout))
{
sendnotice(client, "SASL request timed out (server or client misbehaving) -- aborting SASL and continuing connection...");
abort_sasl(client);
}
}
}