/* * 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" /* Types */ typedef struct _configitem_webirc ConfigItem_webirc; typedef enum { WEBIRC_PASS=1, WEBIRC_WEBIRC=2 } WEBIRCType; struct _configitem_webirc { ConfigItem_webirc *prev, *next; ConfigFlag flag; ConfigItem_mask *mask; WEBIRCType type; anAuthStruct *auth; }; /* Module header */ ModuleHeader MOD_HEADER(webirc) = { "webirc", "4.2", "WebIRC/CGI:IRC Support", "3.2-b8-1", NULL }; /* Global variables */ ModDataInfo *webirc_md = NULL; ConfigItem_webirc *conf_webirc = NULL; /* Forward declarations */ CMD_FUNC(m_webirc); int webirc_check_init(aClient *cptr, char *sockn, size_t size); int webirc_local_pass(aClient *sptr, char *password); int webirc_config_test(ConfigFile *, ConfigEntry *, int, int *); int webirc_config_run(ConfigFile *, ConfigEntry *, int); void webirc_free_conf(void); void delete_webircblock(ConfigItem_webirc *e); char *webirc_md_serialize(ModData *m); void webirc_md_unserialize(char *str, ModData *m); void webirc_md_free(ModData *md); int webirc_secure_connect(aClient *acptr); #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" MOD_TEST(webirc) { HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, webirc_config_test); return MOD_SUCCESS; } /** Called upon module init */ MOD_INIT(webirc) { 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 = 1; webirc_md = ModDataAdd(modinfo->handle, mreq); if (!webirc_md) { config_error("could not register webirc moddata"); return MOD_FAILED; } HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, webirc_config_run); HookAdd(modinfo->handle, HOOKTYPE_CHECK_INIT, 0, webirc_check_init); 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, m_webirc, MAXPARA, M_UNREGISTERED); return MOD_SUCCESS; } /** Called upon module load */ MOD_LOAD(webirc) { return MOD_SUCCESS; } /** Called upon unload */ MOD_UNLOAD(webirc) { webirc_free_conf(); return MOD_SUCCESS; } void webirc_free_conf(void) { ConfigItem_webirc *webirc_ptr, *next; for (webirc_ptr = conf_webirc; webirc_ptr; webirc_ptr = next) { next = webirc_ptr->next; delete_webircblock(webirc_ptr); } conf_webirc = NULL; } void delete_webircblock(ConfigItem_webirc *e) { unreal_delete_masks(e->mask); if (e->auth) Auth_DeleteAuthStruct(e->auth); DelListItem(e, conf_webirc); MyFree(e); } int webirc_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) { ConfigEntry *cep, *cepp; int errors = 0; char has_mask = 0; /* mandatory */ char has_password = 0; /* mandatory */ char has_type = 0; /* optional (used for dup checking) */ WEBIRCType webirc_type = WEBIRC_WEBIRC; /* the default */ if (type != CONFIG_MAIN) return 0; if (!ce || !ce->ce_varname) return 0; if (!strcmp(ce->ce_varname, "cgiirc")) { config_error("%s:%i: the cgiirc block has been renamed to webirc and " "the syntax has changed in UnrealIRCd 4", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); need_34_upgrade = 1; *errs = 1; return -1; } if (strcmp(ce->ce_varname, "webirc")) return 0; /* not interested in non-webirc stuff.. */ /* Now actually go parse the webirc { } block */ for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!cep->ce_varname) { config_error_blank(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "webirc"); errors++; continue; } if (!cep->ce_vardata) { config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "webirc", cep->ce_varname); errors++; continue; } if (!strcmp(cep->ce_varname, "mask")) { if (cep->ce_vardata || cep->ce_entries) has_mask = 1; } else if (!strcmp(cep->ce_varname, "password")) { if (has_password) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "webirc::password"); continue; } has_password = 1; if (Auth_CheckError(cep) < 0) errors++; } else if (!strcmp(cep->ce_varname, "type")) { if (has_type) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "webirc::type"); } has_type = 1; if (!strcmp(cep->ce_vardata, "webirc")) webirc_type = WEBIRC_WEBIRC; else if (!strcmp(cep->ce_vardata, "old")) webirc_type = WEBIRC_PASS; else { config_error("%s:%i: unknown webirc::type '%s', should be either 'webirc' or 'old'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata); errors++; } } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "webirc", cep->ce_varname); errors++; } } if (!has_mask) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "webirc::mask"); errors++; } if (!has_password && (webirc_type == WEBIRC_WEBIRC)) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "webirc::password"); errors++; } if (has_password && (webirc_type == WEBIRC_PASS)) { config_error("%s:%i: webirc block has type set to 'old' but has a password set. " "Passwords are not used with type 'old'. Either remove the password or " "use the 'webirc' method instead.", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; } *errs = errors; return errors ? -1 : 1; } int webirc_config_run(ConfigFile *cf, ConfigEntry *ce, int type) { ConfigEntry *cep; ConfigEntry *cepp; ConfigItem_webirc *webirc = NULL; if (type != CONFIG_MAIN) return 0; if (!ce || !ce->ce_varname || strcmp(ce->ce_varname, "webirc")) return 0; /* not interested */ webirc = MyMallocEx(sizeof(ConfigItem_webirc)); webirc->type = WEBIRC_WEBIRC; /* default */ for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "mask")) unreal_add_masks(&webirc->mask, cep); else if (!strcmp(cep->ce_varname, "password")) webirc->auth = Auth_ConvertConf2AuthStruct(cep); else if (!strcmp(cep->ce_varname, "type")) { if (!strcmp(cep->ce_vardata, "webirc")) webirc->type = WEBIRC_WEBIRC; else if (!strcmp(cep->ce_vardata, "old")) webirc->type = WEBIRC_PASS; else abort(); } } AddListItem(webirc, conf_webirc); return 0; } 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(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_webirc *Find_webirc(aClient *sptr, char *password, WEBIRCType type, char **errorstr) { ConfigItem_webirc *e; char *error = NULL; for (e = conf_webirc; e; e = e->next) { if ((e->type == type) && unreal_mask_match(sptr, e->mask)) { if (type == WEBIRC_WEBIRC) { /* Check password */ if (Auth_Check(sptr, e->auth, password) == -1) error = "CGI:IRC -- Invalid password"; else return e; /* Found matching block, return straight away */ } else { return e; /* The 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 */ int dowebirc(aClient *cptr, char *ip, char *host, char *options) { char scratch[64]; char *sockhost; int secure = 0; if (IsWEBIRC(cptr)) return exit_client(cptr, cptr, &me, "Double CGI:IRC request (already identified)"); if (host && !strcmp(ip, host)) host = NULL; /* host did not resolve, make it NULL */ /* STEP 1: Update cptr->local->ip inet_pton() returns 1 on success, 0 on bad input, -1 on bad AF */ if ((inet_pton(AF_INET, ip, scratch) != 1) && (inet_pton(AF_INET6, ip, scratch) != 1)) { /* then we have an invalid IP */ return exit_client(cptr, cptr, &me, "Invalid IP address"); } /* STEP 2: Update GetIP() */ safefree(cptr->ip); cptr->ip = strdup(ip); /* STEP 3: Update cptr->local->hostp */ /* (free old) */ if (cptr->local->hostp) { unreal_free_hostent(cptr->local->hostp); cptr->local->hostp = NULL; } /* (create new) */ if (host && verify_hostname(host)) cptr->local->hostp = unreal_create_hostent(host, cptr->ip); /* STEP 4: Update sockhost Make sure that if this any IPv4 address is _not_ prefixed with "::ffff:" by using Inet_ia2p(). */ // Hmm I ignored above warning. May be bad during transition period. strlcpy(cptr->local->sockhost, cptr->ip, sizeof(cptr->local->sockhost)); SetWEBIRC(cptr); if (options) { char *name, *p = NULL, *p2; for (name = strtoken(&p, options, " "); name; name = strtoken(&p, NULL, " ")) { p2 = strchr(name, '='); if (p2) *p2 = '\0'; if (!strcmp(name, "secure") && IsSecure(cptr)) { /* The entire [client]--[webirc gw]--[server] chain is secure */ SetWEBIRCSecure(cptr); } } } /* blacklist_start_check() */ if (RCallbacks[CALLBACKTYPE_BLACKLIST_CHECK] != NULL) RCallbacks[CALLBACKTYPE_BLACKLIST_CHECK]->func.intfunc(cptr); /* Check (g)zlines right now; these are normally checked upon accept(), * but since we know the IP only now after PASS/WEBIRC, we have to check * here again... */ return check_banned(cptr); } /* WEBIRC "cgiirc" [:option1 [option2...]]*/ CMD_FUNC(m_webirc) { char *ip, *host, *password, *options; size_t ourlen; ConfigItem_webirc *e; char *error = NULL; if ((parc < 5) || BadPtr(parv[4])) { sendto_one(cptr, err_str(ERR_NEEDMOREPARAMS), me.name, "*", "WEBIRC"); return -1; } 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(sptr, password, WEBIRC_WEBIRC, &error); if (!e) return exit_client(cptr, sptr, &me, error); /* And do our job.. */ return dowebirc(cptr, ip, host, options); } int webirc_check_init(aClient *cptr, char *sockn, size_t size) { if (IsWEBIRC(cptr)) { strlcpy(sockn, GetIP(cptr), size); /* use already set value */ return 0; } return 1; /* nothing to do */ } int webirc_local_pass(aClient *sptr, char *password) { if (!strncmp(password, WEBIRC_STRING, WEBIRC_STRINGLEN)) { char *ip, *host; ConfigItem_webirc *e; char *error = NULL; e = Find_webirc(sptr, NULL, WEBIRC_PASS, &error); if (e) { /* Ok now we got that sorted out, proceed: * Syntax: WEBIRC__ * The has been checked ip->host AND host->ip by CGI:IRC itself * already so we trust it. */ ip = password + WEBIRC_STRINGLEN; host = strchr(ip, '_'); if (!host) return exit_client(sptr, sptr, &me, "Invalid CGI:IRC IP received"); *host++ = '\0'; return dowebirc(sptr, ip, host, NULL); } /* falltrough if not in webirc block.. */ } return 0; /* not webirc */ } /** Called from register_user() right after setting user +z */ int webirc_secure_connect(aClient *acptr) { /* 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(acptr) && IsSecureConnect(acptr) && !IsWEBIRCSecure(acptr)) acptr->umodes &= ~UMODE_SECURE; return 0; }