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> ¶ms) 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)