mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-28 16:56:38 +02:00
4c6e259681
allow {
mask *;
password "secret";
password "letmein";
}
This is always an "OR" type of match, any match means you pass.
I was actually doing this for the dual-cert stuff from previous commit,
where this can come in handy:
link irc1.example.org {
...
password "AHMYBevUxXKU/S3pdBSjXP4zi4VOetYQQVJXoNYiBR0=" { spkifp; };
password "jNw8P4QMg9tqjEJ4/lFikXBNHdIGSeN2B4/T322VjIo=" { spkifp; };
...
}
542 lines
14 KiB
C
542 lines
14 KiB
C
/*
|
|
* Unreal Internet Relay Chat Daemon, src/modules/vhost.c
|
|
* (C) 2000-2024 Carsten V. Munk, Bram Matthys 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"
|
|
|
|
/* Defines */
|
|
#define MSG_VHOST "VHOST"
|
|
|
|
/* Structs */
|
|
ModuleHeader MOD_HEADER
|
|
= {
|
|
"vhost",
|
|
"6.1.8.1",
|
|
"command /VHOST and vhost { } blocks",
|
|
"UnrealIRCd Team",
|
|
"unrealircd-6",
|
|
};
|
|
|
|
typedef struct ConfigItem_vhost ConfigItem_vhost;
|
|
struct ConfigItem_vhost {
|
|
ConfigItem_vhost *prev, *next;
|
|
int auto_login; /**< Auto-login users on connect? If they match 'auth' */
|
|
SecurityGroup *match; /**< Match criteria for user */
|
|
char *login; /**< Login name for 'VHOST login pass' */
|
|
AuthConfig *auth; /**< Password for 'VHOST login pass */
|
|
char *virtuser; /**< Virtual ident to set */
|
|
char *virthost; /**< Virtual host to set */
|
|
SWhois *swhois; /**< SWhois items to set */
|
|
};
|
|
|
|
/* Variables */
|
|
ConfigItem_vhost *conf_vhost = NULL;
|
|
|
|
/* Forward declarations */
|
|
void free_vhost_config(void);
|
|
int vhost_config_test(ConfigFile *conf, ConfigEntry *ce, int type, int *errs);
|
|
int vhost_config_run(ConfigFile *conf, ConfigEntry *ce, int type);
|
|
static int stats_vhost(Client *client, const char *flag);
|
|
ConfigItem_vhost *find_vhost(const char *name);
|
|
void do_vhost(Client *client, ConfigItem_vhost *vhost);
|
|
CMD_FUNC(cmd_vhost);
|
|
int vhost_auto_set(Client *client);
|
|
|
|
MOD_TEST()
|
|
{
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, vhost_config_test);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_INIT()
|
|
{
|
|
MARK_AS_OFFICIAL_MODULE(modinfo);
|
|
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, vhost_config_run);
|
|
HookAdd(modinfo->handle, HOOKTYPE_STATS, 0, stats_vhost);
|
|
HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, INT_MAX, vhost_auto_set);
|
|
CommandAdd(modinfo->handle, MSG_VHOST, cmd_vhost, MAXPARA, CMD_USER);
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
MOD_LOAD()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
|
|
MOD_UNLOAD()
|
|
{
|
|
free_vhost_config();
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
void free_vhost_config(void)
|
|
{
|
|
ConfigItem_vhost *e, *e_next;
|
|
|
|
for (e = conf_vhost; e; e = e_next)
|
|
{
|
|
SWhois *s, *s_next;
|
|
|
|
e_next = e->next;
|
|
|
|
safe_free(e->login);
|
|
Auth_FreeAuthConfig(e->auth);
|
|
safe_free(e->virthost);
|
|
safe_free(e->virtuser);
|
|
free_security_group(e->match);
|
|
for (s = e->swhois; s; s = s_next)
|
|
{
|
|
s_next = s->next;
|
|
safe_free(s->line);
|
|
safe_free(s->setby);
|
|
safe_free(s);
|
|
}
|
|
DelListItem(e, conf_vhost);
|
|
safe_free(e);
|
|
}
|
|
conf_vhost = NULL;
|
|
}
|
|
/** Test a vhost { } block in the configuration file */
|
|
int vhost_config_test(ConfigFile *conf, ConfigEntry *ce, int type, int *errs)
|
|
{
|
|
ConfigEntry *cep;
|
|
int has_vhost = 0, has_login = 0, has_password = 0, has_mask = 0, has_match = 0;
|
|
int has_auto_login = 0;
|
|
int errors = 0;
|
|
|
|
/* We are only interested in vhost { } blocks */
|
|
if ((type != CONFIG_MAIN) || strcmp(ce->name, "vhost"))
|
|
return 0;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "auto-login"))
|
|
{
|
|
has_auto_login = config_checkval(cep->value, CFG_YESNO);
|
|
}
|
|
else if (!strcmp(cep->name, "vhost"))
|
|
{
|
|
char *at, *tmp, *host, *p;
|
|
config_detect_duplicate(&has_vhost, cep, &errors);
|
|
if (!cep->value)
|
|
{
|
|
config_error_empty(cep->file->filename,
|
|
cep->line_number, "vhost", "vhost");
|
|
errors++;
|
|
continue;
|
|
}
|
|
if (!potentially_valid_vhost(cep->value))
|
|
{
|
|
/* Note that the error message needs to be on the
|
|
* original cep->value and not on fakehost.
|
|
*/
|
|
config_error("%s:%i: vhost::vhost contains illegal characters or is too long: '%s'",
|
|
cep->file->filename, cep->line_number, cep->value);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "login"))
|
|
{
|
|
config_detect_duplicate(&has_login, cep, &errors);
|
|
if (!cep->value)
|
|
{
|
|
config_error_empty(cep->file->filename,
|
|
cep->line_number, "vhost", "login");
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "password"))
|
|
{
|
|
config_detect_duplicate(&has_password, cep, &errors);
|
|
if (!cep->value)
|
|
{
|
|
config_error_empty(cep->file->filename,
|
|
cep->line_number, "vhost", "password");
|
|
errors++;
|
|
continue;
|
|
}
|
|
if (Auth_CheckError(cep, 0) < 0)
|
|
errors++;
|
|
}
|
|
else if (!strcmp(cep->name, "mask"))
|
|
{
|
|
has_mask = 1;
|
|
test_match_block(conf, cep, &errors);
|
|
}
|
|
else if (!strcmp(cep->name, "match"))
|
|
{
|
|
has_match = 1;
|
|
test_match_block(conf, cep, &errors);
|
|
}
|
|
else if (!strcmp(cep->name, "swhois"))
|
|
{
|
|
/* multiple is ok */
|
|
}
|
|
else
|
|
{
|
|
config_error_unknown(cep->file->filename, cep->line_number,
|
|
"vhost", cep->name);
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
if (!has_vhost)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"vhost::vhost");
|
|
errors++;
|
|
}
|
|
|
|
if (!has_auto_login)
|
|
{
|
|
if (!has_login)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"vhost::login");
|
|
errors++;
|
|
|
|
}
|
|
if (!has_password)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"vhost::password");
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
if (!has_mask && !has_match)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"vhost::match");
|
|
errors++;
|
|
}
|
|
if (has_mask && has_match)
|
|
{
|
|
config_error("%s:%d: You cannot have both ::mask and ::match. "
|
|
"You should only use %s::match.",
|
|
ce->file->filename, ce->line_number, ce->name);
|
|
errors++;
|
|
}
|
|
|
|
if (has_auto_login && has_password)
|
|
{
|
|
config_error("%s:%d: If ::auto-login is set to 'yes' then you "
|
|
"cannot have a ::password configured. "
|
|
"Remove the password if you want to use auto-login.",
|
|
ce->file->filename, ce->line_number);
|
|
}
|
|
*errs = errors;
|
|
return errors ? -1 : 1;
|
|
}
|
|
|
|
/** Process a vhost { } block in the configuration file */
|
|
int vhost_config_run(ConfigFile *conf, ConfigEntry *ce, int type)
|
|
{
|
|
ConfigEntry *cep, *cepp;
|
|
ConfigItem_vhost *vhost;
|
|
|
|
/* We are only interested in vhost { } blocks */
|
|
if ((type != CONFIG_MAIN) || strcmp(ce->name, "vhost"))
|
|
return 0;
|
|
|
|
vhost = safe_alloc(sizeof(ConfigItem_vhost));
|
|
vhost->match = safe_alloc(sizeof(SecurityGroup));
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "auto-login"))
|
|
{
|
|
vhost->auto_login = config_checkval(cep->value, CFG_YESNO);
|
|
}
|
|
else if (!strcmp(cep->name, "vhost"))
|
|
{
|
|
char *user, *host;
|
|
user = strtok(cep->value, "@");
|
|
host = strtok(NULL, "");
|
|
if (!host)
|
|
safe_strdup(vhost->virthost, user);
|
|
else
|
|
{
|
|
safe_strdup(vhost->virtuser, user);
|
|
safe_strdup(vhost->virthost, host);
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "login"))
|
|
safe_strdup(vhost->login, cep->value);
|
|
else if (!strcmp(cep->name, "password"))
|
|
AuthBlockToAuthConfig(cep, &vhost->auth);
|
|
else if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask"))
|
|
{
|
|
conf_match_block(conf, cep, &vhost->match);
|
|
}
|
|
else if (!strcmp(cep->name, "swhois"))
|
|
{
|
|
SWhois *s;
|
|
if (cep->items)
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
s = safe_alloc(sizeof(SWhois));
|
|
safe_strdup(s->line, cepp->name);
|
|
safe_strdup(s->setby, "vhost");
|
|
AddListItem(s, vhost->swhois);
|
|
}
|
|
} else
|
|
if (cep->value)
|
|
{
|
|
s = safe_alloc(sizeof(SWhois));
|
|
safe_strdup(s->line, cep->value);
|
|
safe_strdup(s->setby, "vhost");
|
|
AddListItem(s, vhost->swhois);
|
|
}
|
|
}
|
|
}
|
|
AppendListItem(vhost, conf_vhost);
|
|
return 1;
|
|
}
|
|
|
|
static int stats_vhost(Client *client, const char *flag)
|
|
{
|
|
ConfigItem_vhost *vhosts;
|
|
NameValuePrioList *m;
|
|
|
|
if (strcmp(flag, "V") && strcasecmp(flag, "vhost"))
|
|
return 0; /* Not for us */
|
|
|
|
for (vhosts = conf_vhost; vhosts; vhosts = vhosts->next)
|
|
{
|
|
for (m = vhosts->match->printable_list; m; m = m->next)
|
|
{
|
|
sendtxtnumeric(client, "vhost %s%s%s %s %s",
|
|
vhosts->virtuser ? vhosts->virtuser : "",
|
|
vhosts->virtuser ? "@" : "",
|
|
vhosts->virthost,
|
|
vhosts->login ? vhosts->login : "*",
|
|
namevalue_nospaces(m));
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
ConfigItem_vhost *find_vhost(const char *name)
|
|
{
|
|
ConfigItem_vhost *vhost;
|
|
|
|
for (vhost = conf_vhost; vhost; vhost = vhost->next)
|
|
if (vhost->login && !strcmp(name, vhost->login))
|
|
return vhost;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
CMD_FUNC(cmd_vhost)
|
|
{
|
|
ConfigItem_vhost *vhost;
|
|
char login[HOSTLEN+1];
|
|
const char *password;
|
|
|
|
if (!MyUser(client))
|
|
return;
|
|
|
|
if ((parc < 2) || BadPtr(parv[1]))
|
|
{
|
|
sendnumeric(client, ERR_NEEDMOREPARAMS, "VHOST");
|
|
return;
|
|
|
|
}
|
|
|
|
/* cut-off too long login names. HOSTLEN is arbitrary, we just don't want our
|
|
* error messages to be cut off because the user is sending huge login names.
|
|
*/
|
|
strlcpy(login, parv[1], sizeof(login));
|
|
|
|
password = (parc > 2) ? parv[2] : "";
|
|
|
|
if (!(vhost = find_vhost(login)))
|
|
{
|
|
unreal_log(ULOG_WARNING, "vhost", "VHOST_FAILED", client,
|
|
"Failed VHOST attempt by $client.details [reason: $reason] [vhost-block: $vhost_block]",
|
|
log_data_string("reason", "Vhost block not found"),
|
|
log_data_string("fail_type", "UNKNOWN_VHOST_NAME"),
|
|
log_data_string("vhost_block", login));
|
|
sendnotice(client, "*** [\2vhost\2] Login for %s failed - password incorrect", login);
|
|
return;
|
|
}
|
|
|
|
if (!user_allowed_by_security_group(client, vhost->match))
|
|
{
|
|
unreal_log(ULOG_WARNING, "vhost", "VHOST_FAILED", client,
|
|
"Failed VHOST attempt by $client.details [reason: $reason] [vhost-block: $vhost_block]",
|
|
log_data_string("reason", "Host does not match"),
|
|
log_data_string("fail_type", "NO_HOST_MATCH"),
|
|
log_data_string("vhost_block", login));
|
|
sendnotice(client, "*** No vHost lines available for your host");
|
|
return;
|
|
}
|
|
|
|
if (!Auth_Check(client, vhost->auth, password))
|
|
{
|
|
unreal_log(ULOG_WARNING, "vhost", "VHOST_FAILED", client,
|
|
"Failed VHOST attempt by $client.details [reason: $reason] [vhost-block: $vhost_block]",
|
|
log_data_string("reason", "Authentication failed"),
|
|
log_data_string("fail_type", "AUTHENTICATION_FAILED"),
|
|
log_data_string("vhost_block", login));
|
|
sendnotice(client, "*** [\2vhost\2] Login for %s failed - password incorrect", login);
|
|
return;
|
|
}
|
|
|
|
/* Authentication passed, but.. there could still be other restrictions: */
|
|
switch (UHOST_ALLOWED)
|
|
{
|
|
case UHALLOW_NEVER:
|
|
if (MyUser(client))
|
|
{
|
|
sendnotice(client, "*** /vhost is disabled");
|
|
return;
|
|
}
|
|
break;
|
|
case UHALLOW_ALWAYS:
|
|
break;
|
|
case UHALLOW_NOCHANS:
|
|
if (MyUser(client) && client->user->joined)
|
|
{
|
|
sendnotice(client, "*** /vhost can not be used while you are on a channel");
|
|
return;
|
|
}
|
|
break;
|
|
case UHALLOW_REJOIN:
|
|
/* join sent later when the host has been changed */
|
|
break;
|
|
}
|
|
|
|
/* All checks passed, now let's go ahead and change the host */
|
|
do_vhost(client, vhost);
|
|
}
|
|
|
|
void do_vhost(Client *client, ConfigItem_vhost *vhost)
|
|
{
|
|
char olduser[USERLEN+1];
|
|
char newhost[HOSTLEN+1];
|
|
|
|
/* There are various IsUser() checks in the code below, that is because
|
|
* this code is also called for CLIENT_STATUS_UNKNOWN users in the handshake
|
|
* that have not yet been introduced to other servers. For such users we
|
|
* should not send SETIDENT and SETHOST messages out... such info will
|
|
* be sent in the UID message when the user is introduced.
|
|
*/
|
|
|
|
*newhost = '\0';
|
|
unreal_expand_string(vhost->virthost, newhost, sizeof(newhost), NULL, 0, client);
|
|
if (!valid_vhost(newhost))
|
|
{
|
|
sendnotice(client, "*** Unable to apply vhost automatically");
|
|
if (vhost->auto_login)
|
|
{
|
|
unreal_log(ULOG_WARNING, "vhost", "AUTO_VHOST_FAILED", client,
|
|
"Unable to set auto-vhost on user $client.details. "
|
|
"Vhost '$vhost_format' expanded to '$newhost' but is invalid.",
|
|
log_data_string("vhost_format", vhost->virthost),
|
|
log_data_string("newhost", newhost));
|
|
}
|
|
return;
|
|
}
|
|
|
|
userhost_save_current(client);
|
|
|
|
safe_strdup(client->user->virthost, newhost);
|
|
if (vhost->virtuser)
|
|
{
|
|
strlcpy(olduser, client->user->username, sizeof(olduser));
|
|
strlcpy(client->user->username, vhost->virtuser, sizeof(client->user->username));
|
|
if (IsUser(client))
|
|
sendto_server(client, 0, 0, NULL, ":%s SETIDENT %s", client->id, client->user->username);
|
|
}
|
|
client->umodes |= UMODE_HIDE;
|
|
client->umodes |= UMODE_SETHOST;
|
|
if (IsUser(client))
|
|
{
|
|
sendto_server(client, 0, 0, NULL, ":%s SETHOST %s", client->id, client->user->virthost);
|
|
sendto_one(client, NULL, ":%s MODE %s :+tx", client->name, client->name);
|
|
}
|
|
if (vhost->swhois)
|
|
{
|
|
SWhois *s;
|
|
for (s = vhost->swhois; s; s = s->next)
|
|
swhois_add(client, "vhost", -100, s->line, &me, NULL);
|
|
}
|
|
if (IsUser(client))
|
|
{
|
|
sendnotice(client, "*** Your vhost is now %s%s%s",
|
|
vhost->virtuser ? vhost->virtuser : "",
|
|
vhost->virtuser ? "@" : "",
|
|
newhost);
|
|
}
|
|
|
|
/* Only notify on logins, not on auto logins (should we make that configurable?)
|
|
* (if you do want it for auto logins, note that vhost->login will be NULL
|
|
* in the unreal_log() call currently below).
|
|
*/
|
|
if (vhost->login)
|
|
{
|
|
if (vhost->virtuser)
|
|
{
|
|
/* virtuser@virthost */
|
|
unreal_log(ULOG_INFO, "vhost", "VHOST_SUCCESS", client,
|
|
"$client.details is now using vhost $virtuser@$virthost [vhost-block: $vhost_block]",
|
|
log_data_string("virtuser", vhost->virtuser),
|
|
log_data_string("virthost", newhost),
|
|
log_data_string("vhost_block", vhost->login));
|
|
} else {
|
|
/* just virthost */
|
|
unreal_log(ULOG_INFO, "vhost", "VHOST_SUCCESS", client,
|
|
"$client.details is now using vhost $virthost [vhost-block: $vhost_block]",
|
|
log_data_string("virthost", newhost),
|
|
log_data_string("vhost_block", vhost->login));
|
|
}
|
|
}
|
|
|
|
userhost_changed(client);
|
|
}
|
|
|
|
int vhost_auto_set(Client *client)
|
|
{
|
|
ConfigItem_vhost *vhost;
|
|
|
|
if (IsSetHost(client))
|
|
return 0; /* Don't override if e.g. anope already set a vhost */
|
|
|
|
for (vhost = conf_vhost; vhost; vhost = vhost->next)
|
|
{
|
|
if (vhost->auto_login &&
|
|
!vhost->auth &&
|
|
vhost->match &&
|
|
user_allowed_by_security_group(client, vhost->match))
|
|
{
|
|
do_vhost(client, vhost);
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|