anope

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

ns_cert.cpp (11837B)

      1 /* NickServ core functions
      2  *
      3  * (C) 2003-2022 Anope Team
      4  * Contact us at team@anope.org
      5  *
      6  * Please read COPYING and README for further details.
      7  *
      8  * Based on the original code of Epona by Lara.
      9  * Based on the original code of Services by Andy Church.
     10  */
     11 
     12 #include "module.h"
     13 #include "modules/ns_cert.h"
     14 
     15 static Anope::hash_map<NickCore *> certmap;
     16 
     17 struct CertServiceImpl : CertService
     18 {
     19 	CertServiceImpl(Module *o) : CertService(o) { }
     20 
     21 	NickCore* FindAccountFromCert(const Anope::string &cert) anope_override
     22 	{
     23 		Anope::hash_map<NickCore *>::iterator it = certmap.find(cert);
     24 		if (it != certmap.end())
     25 			return it->second;
     26 		return NULL;
     27 	}
     28 };
     29 
     30 struct NSCertListImpl : NSCertList
     31 {
     32 	Serialize::Reference<NickCore> nc;
     33 	std::vector<Anope::string> certs;
     34 
     35  public:
     36 	NSCertListImpl(Extensible *obj) : nc(anope_dynamic_static_cast<NickCore *>(obj)) { }
     37 
     38 	~NSCertListImpl()
     39 	{
     40 		ClearCert();
     41 	}
     42 
     43 	/** Add an entry to the nick's certificate list
     44 	 *
     45 	 * @param entry The fingerprint to add to the cert list
     46 	 *
     47 	 * Adds a new entry into the cert list.
     48 	 */
     49 	void AddCert(const Anope::string &entry) anope_override
     50 	{
     51 		this->certs.push_back(entry);
     52 		certmap[entry] = nc;
     53 		FOREACH_MOD(OnNickAddCert, (this->nc, entry));
     54 	}
     55 
     56 	/** Get an entry from the nick's cert list by index
     57 	 *
     58 	 * @param entry Index in the certificate list vector to retrieve
     59 	 * @return The fingerprint entry of the given index if within bounds, an empty string if the vector is empty or the index is out of bounds
     60 	 *
     61 	 * Retrieves an entry from the certificate list corresponding to the given index.
     62 	 */
     63 	Anope::string GetCert(unsigned entry) const anope_override
     64 	{
     65 		if (entry >= this->certs.size())
     66 			return "";
     67 		return this->certs[entry];
     68 	}
     69 
     70 	unsigned GetCertCount() const anope_override
     71 	{
     72 		return this->certs.size();
     73 	}
     74 
     75 	/** Find an entry in the nick's cert list
     76 	 *
     77 	 * @param entry The fingerprint to search for
     78 	 * @return True if the fingerprint is found in the cert list, false otherwise
     79 	 *
     80 	 * Search for an fingerprint within the cert list.
     81 	 */
     82 	bool FindCert(const Anope::string &entry) const anope_override
     83 	{
     84 		return std::find(this->certs.begin(), this->certs.end(), entry) != this->certs.end();
     85 	}
     86 
     87 	/** Erase a fingerprint from the nick's certificate list
     88 	 *
     89 	 * @param entry The fingerprint to remove
     90 	 *
     91 	 * Removes the specified fingerprint from the cert list.
     92 	 */
     93 	void EraseCert(const Anope::string &entry) anope_override
     94 	{
     95 		std::vector<Anope::string>::iterator it = std::find(this->certs.begin(), this->certs.end(), entry);
     96 		if (it != this->certs.end())
     97 		{
     98 			FOREACH_MOD(OnNickEraseCert, (this->nc, entry));
     99 			certmap.erase(entry);
    100 			this->certs.erase(it);
    101 		}
    102 	}
    103 
    104 	/** Clears the entire nick's cert list
    105 	 *
    106 	 * Deletes all the memory allocated in the certificate list vector and then clears the vector.
    107 	 */
    108 	void ClearCert() anope_override
    109 	{
    110 		FOREACH_MOD(OnNickClearCert, (this->nc));
    111 		for (unsigned i = 0; i < certs.size(); ++i)
    112 			certmap.erase(certs[i]);
    113 		this->certs.clear();
    114 	}
    115 
    116 	void Check() anope_override
    117 	{
    118 		if (this->certs.empty())
    119 			nc->Shrink<NSCertList>("certificates");
    120 	}
    121 
    122 	struct ExtensibleItem : ::ExtensibleItem<NSCertListImpl>
    123 	{
    124 		ExtensibleItem(Module *m, const Anope::string &ename) : ::ExtensibleItem<NSCertListImpl>(m, ename) { }
    125 
    126 		void ExtensibleSerialize(const Extensible *e, const Serializable *s, Serialize::Data &data) const anope_override
    127 		{
    128 			if (s->GetSerializableType()->GetName() != "NickCore")
    129 				return;
    130 
    131 			const NickCore *n = anope_dynamic_static_cast<const NickCore *>(e);
    132 			NSCertList *c = this->Get(n);
    133 			if (c == NULL || !c->GetCertCount())
    134 				return;
    135 
    136 			for (unsigned i = 0; i < c->GetCertCount(); ++i)
    137 				data["cert"] << c->GetCert(i) << " ";
    138 		}
    139 
    140 		void ExtensibleUnserialize(Extensible *e, Serializable *s, Serialize::Data &data) anope_override
    141 		{
    142 			if (s->GetSerializableType()->GetName() != "NickCore")
    143 				return;
    144 
    145 			NickCore *n = anope_dynamic_static_cast<NickCore *>(e);
    146 			NSCertListImpl *c = this->Require(n);
    147 
    148 			Anope::string buf;
    149 			data["cert"] >> buf;
    150 			spacesepstream sep(buf);
    151 			for (unsigned i = 0; i < c->certs.size(); ++i)
    152 				certmap.erase(c->certs[i]);
    153 			c->certs.clear();
    154 			while (sep.GetToken(buf))
    155 			{
    156 				c->certs.push_back(buf);
    157 				certmap[buf] = n;
    158 			}
    159 		}
    160 	};
    161 };
    162 
    163 class CommandNSCert : public Command
    164 {
    165  private:
    166 	void DoAdd(CommandSource &source, NickCore *nc, Anope::string certfp)
    167 	{
    168 		NSCertList *cl = nc->Require<NSCertList>("certificates");
    169 		unsigned max = Config->GetModule(this->owner)->Get<unsigned>("max", "5");
    170 
    171 		if (cl->GetCertCount() >= max)
    172 		{
    173 			source.Reply(_("Sorry, the maximum of %d certificate entries has been reached."), max);
    174 			return;
    175 		}
    176 
    177 		if (source.GetAccount() == nc)
    178 		{
    179 			User *u = source.GetUser();
    180 
    181 			if (!u || u->fingerprint.empty())
    182 			{
    183 				source.Reply(_("You are not using a client certificate."));
    184 				return;
    185 			}
    186 
    187 			certfp = u->fingerprint;
    188 		}
    189 
    190 		if (cl->FindCert(certfp))
    191 		{
    192 			source.Reply(_("Fingerprint \002%s\002 already present on %s's certificate list."), certfp.c_str(), nc->display.c_str());
    193 			return;
    194 		}
    195 
    196 		if (certmap.find(certfp) != certmap.end())
    197 		{
    198 			source.Reply(_("Fingerprint \002%s\002 is already in use."), certfp.c_str());
    199 			return;
    200 		}
    201 
    202 		cl->AddCert(certfp);
    203 		Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD certificate fingerprint " << certfp << " to " << nc->display;
    204 		source.Reply(_("\002%s\002 added to %s's certificate list."), certfp.c_str(), nc->display.c_str());
    205 	}
    206 
    207 	void DoDel(CommandSource &source, NickCore *nc, Anope::string certfp)
    208 	{
    209 		NSCertList *cl = nc->Require<NSCertList>("certificates");
    210 
    211 		if (certfp.empty())
    212 		{
    213 			User *u = source.GetUser();
    214 			if (u)
    215 				certfp = u->fingerprint;
    216 		}
    217 
    218 		if (certfp.empty())
    219 		{
    220 			this->OnSyntaxError(source, "DEL");
    221 			return;
    222 		}
    223 
    224 		if (!cl->FindCert(certfp))
    225 		{
    226 			source.Reply(_("\002%s\002 not found on %s's certificate list."), certfp.c_str(), nc->display.c_str());
    227 			return;
    228 		}
    229 
    230 		cl->EraseCert(certfp);
    231 		cl->Check();
    232 		Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE certificate fingerprint " << certfp << " from " << nc->display;
    233 		source.Reply(_("\002%s\002 deleted from %s's certificate list."), certfp.c_str(), nc->display.c_str());
    234 	}
    235 
    236 	void DoList(CommandSource &source, const NickCore *nc)
    237 	{
    238 		NSCertList *cl = nc->GetExt<NSCertList>("certificates");
    239 
    240 		if (!cl || !cl->GetCertCount())
    241 		{
    242 			source.Reply(_("%s's certificate list is empty."), nc->display.c_str());
    243 			return;
    244 		}
    245 
    246 		source.Reply(_("Certificate list for %s:"), nc->display.c_str());
    247 		for (unsigned i = 0; i < cl->GetCertCount(); ++i)
    248 		{
    249 			Anope::string fingerprint = cl->GetCert(i);
    250 			source.Reply("    %s", fingerprint.c_str());
    251 		}
    252 	}
    253 
    254  public:
    255 	CommandNSCert(Module *creator) : Command(creator, "nickserv/cert", 1, 3)
    256 	{
    257 		this->SetDesc(_("Modify the nickname client certificate list"));
    258 		this->SetSyntax(_("ADD [\037nickname\037] [\037fingerprint\037]"));
    259 		this->SetSyntax(_("DEL [\037nickname\037] \037fingerprint\037"));
    260 		this->SetSyntax(_("LIST [\037nickname\037]"));
    261 	}
    262 
    263 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    264 	{
    265 		const Anope::string &cmd = params[0];
    266 		Anope::string nick, certfp;
    267 
    268 		if (cmd.equals_ci("LIST"))
    269 			nick = params.size() > 1 ? params[1] : "";
    270 		else
    271 		{
    272 			nick = params.size() == 3 ? params[1] : "";
    273 			certfp = params.size() > 1 ? params[params.size() - 1] : "";
    274 		}
    275 
    276 		NickCore *nc;
    277 		if (!nick.empty())
    278 		{
    279 			const NickAlias *na = NickAlias::Find(nick);
    280 			if (na == NULL)
    281 			{
    282 				source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
    283 				return;
    284 			}
    285 			else if (na->nc != source.GetAccount() && !source.HasPriv("nickserv/access"))
    286 			{
    287 				source.Reply(ACCESS_DENIED);
    288 				return;
    289 			}
    290 			else if (Config->GetModule("nickserv")->Get<bool>("secureadmins", "yes") && source.GetAccount() != na->nc && na->nc->IsServicesOper() && !cmd.equals_ci("LIST"))
    291 			{
    292 				source.Reply(_("You may view but not modify the certificate list of other Services Operators."));
    293 				return;
    294 			}
    295 
    296 			nc = na->nc;
    297 		}
    298 		else
    299 			nc = source.nc;
    300 
    301 		if (cmd.equals_ci("LIST"))
    302 			return this->DoList(source, nc);
    303 		else if (nc->HasExt("NS_SUSPENDED"))
    304 			source.Reply(NICK_X_SUSPENDED, nc->display.c_str());
    305 		else if (Anope::ReadOnly)
    306 			source.Reply(READ_ONLY_MODE);
    307 		else if (cmd.equals_ci("ADD"))
    308 			return this->DoAdd(source, nc, certfp);
    309 		else if (cmd.equals_ci("DEL"))
    310 			return this->DoDel(source, nc, certfp);
    311 		else
    312 			this->OnSyntaxError(source, "");
    313 	}
    314 
    315 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    316 	{
    317 		this->SendSyntax(source);
    318 		source.Reply(" ");
    319 		source.Reply(_("Modifies or displays the certificate list for your nick.\n"
    320 				"If you connect to IRC and provide a client certificate with a\n"
    321 				"matching fingerprint in the cert list, you will be\n"
    322 				"automatically identified to services. Services Operators\n"
    323 				"may provide a nick to modify other users' certificate lists.\n"
    324 				" \n"));
    325 		source.Reply(_("Examples:\n"
    326 				" \n"
    327 				"    \002CERT ADD\002\n"
    328 				"        Adds your current fingerprint to the certificate list and\n"
    329 				"        automatically identifies you when you connect to IRC\n"
    330 				"        using this fingerprint.\n"
    331 				" \n"
    332 				"    \002CERT DEL <fingerprint>\002\n"
    333 				"        Removes the fingerprint <fingerprint> from your certificate list.\n"
    334 				" \n"
    335 				"    \002CERT LIST\002\n"
    336 				"        Displays the current certificate list."));
    337 		return true;
    338 	}
    339 };
    340 
    341 class NSCert : public Module
    342 {
    343 	CommandNSCert commandnscert;
    344 	NSCertListImpl::ExtensibleItem certs;
    345 	CertServiceImpl cs;
    346 
    347  public:
    348 	NSCert(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    349 		commandnscert(this), certs(this, "certificates"), cs(this)
    350 	{
    351 		if (!IRCD || !IRCD->CanCertFP)
    352 			throw ModuleException("Your IRCd does not support ssl client certificates");
    353 	}
    354 
    355 	void OnFingerprint(User *u) anope_override
    356 	{
    357 		BotInfo *NickServ = Config->GetClient("NickServ");
    358 		if (!NickServ || u->IsIdentified())
    359 			return;
    360 
    361 		NickCore *nc = cs.FindAccountFromCert(u->fingerprint);
    362 		if (!nc || nc->HasExt("NS_SUSPENDED"))
    363 			return;
    364 
    365 		unsigned int maxlogins = Config->GetModule("ns_identify")->Get<unsigned int>("maxlogins");
    366 		if (maxlogins && nc->users.size() >= maxlogins)
    367 		{
    368 			u->SendMessage(NickServ, _("Account \002%s\002 has already reached the maximum number of simultaneous logins (%u)."), nc->display.c_str(), maxlogins);
    369 			return;
    370 		}
    371 
    372 		NickAlias *na = NickAlias::Find(u->nick);
    373 		if (na && na->nc == nc)
    374 			u->Identify(na);
    375 		else
    376 			u->Login(nc);
    377 
    378 		u->SendMessage(NickServ, _("SSL certificate fingerprint accepted, you are now identified to \002%s\002."), nc->display.c_str());
    379 		Log(NickServ) << u->GetMask() << " automatically identified for account " << nc->display << " via SSL certificate fingerprint";
    380 	}
    381 
    382 	EventReturn OnNickValidate(User *u, NickAlias *na) anope_override
    383 	{
    384 		NSCertList *cl = certs.Get(na->nc);
    385 		if (!u->fingerprint.empty() && cl && cl->FindCert(u->fingerprint))
    386 		{
    387 			BotInfo *NickServ = Config->GetClient("NickServ");
    388 
    389 			unsigned int maxlogins = Config->GetModule("ns_identify")->Get<unsigned int>("maxlogins");
    390 			if (maxlogins && na->nc->users.size() >= maxlogins)
    391 			{
    392 				u->SendMessage(NickServ, _("Account \002%s\002 has already reached the maximum number of simultaneous logins (%u)."), na->nc->display.c_str(), maxlogins);
    393 				return EVENT_CONTINUE;
    394 			}
    395 
    396 			u->Identify(na);
    397 
    398 			u->SendMessage(NickServ, _("SSL certificate fingerprint accepted, you are now identified."));
    399 			Log(NickServ) << u->GetMask() << " automatically identified for account " << na->nc->display << " via SSL certificate fingerprint";
    400 			return EVENT_ALLOW;
    401 		}
    402 
    403 		return EVENT_CONTINUE;
    404 	}
    405 };
    406 
    407 MODULE_INIT(NSCert)