]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/xline.cpp
37dc2c0e9bcd4df4d2cd0475b597c8cdac6d83a5
[user/henk/code/inspircd.git] / src / xline.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2007 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/Credits
7  *
8  * This program is free but copyrighted software; see
9  *          the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 /* $Core: libIRCDxline */
15
16 #include "inspircd.h"
17 #include "wildcard.h"
18 #include "xline.h"
19
20 /*
21  * This is now version 3 of the XLine subsystem, let's see if we can get it as nice and 
22  * efficient as we can this time so we can close this file and never ever touch it again ..
23  *
24  * Background:
25  *  Version 1 stored all line types in one list (one for g, one for z, etc). This was fine,
26  *  but both version 1 and 2 suck at applying lines efficiently. That is, every time a new line
27  *  was added, it iterated every existing line for every existing user. Ow. Expiry was also
28  *  expensive, as the lists were NOT sorted.
29  *
30  *  Version 2 moved permanent lines into a seperate list from non-permanent to help optimize
31  *  matching speed, but matched in the same way.
32  *  Expiry was also sped up by sorting the list by expiry (meaning just remove the items at the
33  *  head of the list that are outdated.)
34  *
35  * This was fine and good, but it looked less than ideal in code, and matching was still slower
36  * than it could have been, something which we address here.
37  *
38  * VERSION 3:
39  *  All lines are (as in v1) stored together -- no seperation of perm and non-perm. Expiry will
40  *  still use a sorted list, and we'll just ignore anything permanent.
41  *
42  *  Application will be by a list of lines 'pending' application, meaning only the newly added lines
43  *  will be gone over. Much faster.
44  *
45  * More of course is to come.
46  */
47
48 /* Version two, now with optimized expiry!
49  *
50  * Because the old way was horrendously slow, the new way of expiring xlines is very
51  * very efficient. I have improved the efficiency of the algorithm in two ways:
52  *
53  * (1) There are now two lists of items for each linetype. One list holds temporary
54  *     items, and the other list holds permanent items (ones which will expire).
55  *     Items which are on the permanent list are NEVER checked at all by the
56  *     expire_lines() function.
57  * (2) The temporary xline lists are always kept in strict numerical order, keyed by
58  *     current time + duration. This means that the line which is due to expire the
59  *     soonest is always pointed at by vector::begin(), so a simple while loop can
60  *     very efficiently, very quickly and above all SAFELY pick off the first few
61  *     items in the vector which need zapping.
62  *
63  *     -- Brain
64  */
65
66 bool XLine::Matches(User *u)
67 {
68         return false;
69 }
70
71 /*
72  * Checks what users match a given vector of ELines and sets their ban exempt flag accordingly.
73  */
74 void XLineManager::CheckELines(std::map<std::string, XLine *> &ELines)
75 {
76         if (ELines.empty())
77                 return;
78
79         for (std::vector<User*>::const_iterator u2 = ServerInstance->local_users.begin(); u2 != ServerInstance->local_users.end(); u2++)
80         {
81                 User* u = (User*)(*u2);
82
83                 for (std::map<std::string, XLine *>::iterator i = ELines.begin(); i != ELines.end(); i++)
84                 {
85                         XLine *e = i->second;
86                         u->exempt = e->Matches(u);
87                 }
88         }
89 }
90
91 // this should probably be moved to configreader, but atm it relies on CheckELines above.
92 bool DoneELine(ServerConfig* conf, const char* tag)
93 {
94         for (std::vector<User*>::const_iterator u2 = conf->GetInstance()->local_users.begin(); u2 != conf->GetInstance()->local_users.end(); u2++)
95         {
96                 User* u = (User*)(*u2);
97                 u->exempt = false;
98         }
99
100         conf->GetInstance()->XLines->CheckELines(conf->GetInstance()->XLines->lookup_lines['E']);
101         return true;
102 }
103
104
105 IdentHostPair XLineManager::IdentSplit(const std::string &ident_and_host)
106 {
107         IdentHostPair n = std::make_pair<std::string,std::string>("*","*");
108         std::string::size_type x = ident_and_host.find('@');
109         if (x != std::string::npos)
110         {
111                 n.second = ident_and_host.substr(x + 1,ident_and_host.length());
112                 n.first = ident_and_host.substr(0, x);
113                 if (!n.first.length())
114                         n.first.assign("*");
115                 if (!n.second.length())
116                         n.second.assign("*");
117         }
118         else
119         {
120                 n.second = ident_and_host;
121         }
122
123         return n;
124 }
125
126 // adds a g:line
127
128 /*bool XLineManager::AddELine(long duration, const char* source, const char* reason, const char* hostmask)*/
129 bool XLineManager::AddLine(XLine* line, User* user)
130 {
131         /*IdentHostPair ih = IdentSplit(hostmask);*/
132
133         if (DelLine(line->Displayable(), line->type, user, true))
134                 return false;
135
136         /*ELine* item = new ELine(ServerInstance, ServerInstance->Time(), duration, source, reason, ih.first.c_str(), ih.second.c_str());*/
137
138         active_lines.push_back(line);
139         sort(active_lines.begin(), active_lines.end(), XLineManager::XSortComparison);
140         pending_lines.push_back(line);
141         lookup_lines[line->type][line->Displayable()] = line;
142         line->OnAdd();
143
144         if (!line->duration)
145                 PermLines++;
146
147         FOREACH_MOD(I_OnAddLine,OnAddLine(user, line)); 
148
149         return true;
150 }
151
152 /*bool XLineManager::AddZLine(long duration, const char* source, const char* reason, const char* ipaddr)
153 {
154         if (strchr(ipaddr,'@'))
155         {
156                 while (*ipaddr != '@')
157                         ipaddr++;
158                 ipaddr++;
159         }*/
160
161 // deletes a g:line, returns true if the line existed and was removed
162
163 bool XLineManager::DelLine(const char* hostmask, char type, User* user, bool simulate)
164 {
165         IdentHostPair ih = IdentSplit(hostmask);
166         for (std::vector<XLine*>::iterator i = active_lines.begin(); i != active_lines.end(); i++)
167         {
168                 if ((*i)->type == type)
169                 {
170                         if ((*i)->MatchesLiteral(hostmask))
171                         {
172                                 if (!simulate)
173                                 {
174                                         (*i)->Unset();
175
176                                         if (lookup_lines.find(type) != lookup_lines.end())
177                                                 lookup_lines[type].erase(hostmask);
178
179                                         FOREACH_MOD(I_OnDelLine,OnDelLine(user, *i));
180
181                                         std::vector<XLine*>::iterator pptr = std::find(pending_lines.begin(), pending_lines.end(), *i);                                 
182                                         if (pptr != pending_lines.end())
183                                                 pending_lines.erase(pptr);
184
185                                         if (!(*i)->duration)
186                                                 PermLines--;
187
188                                         delete *i;
189                                         active_lines.erase(i);
190                                 }
191                                 return true;
192                         }
193                 }
194         }
195
196         return false;
197 }
198
199
200 void ELine::Unset()
201 {
202         /* remove exempt from everyone and force recheck after deleting eline */
203         for (std::vector<User*>::const_iterator u2 = ServerInstance->local_users.begin(); u2 != ServerInstance->local_users.end(); u2++)
204         {
205                 User* u = (User*)(*u2);
206                 u->exempt = false;
207         }
208
209         if (ServerInstance->XLines->lookup_lines.find('E') != ServerInstance->XLines->lookup_lines.end())
210                 ServerInstance->XLines->CheckELines(ServerInstance->XLines->lookup_lines['E']);
211 }
212
213 // returns a pointer to the reason if a nickname matches a qline, NULL if it didnt match
214
215 QLine* XLineManager::matches_qline(const char* nick)
216 {
217         if (lookup_lines.find('Q') == lookup_lines.end())
218                 return NULL;
219
220         if (lookup_lines.find('Q') != lookup_lines.end() && lookup_lines['Q'].empty())
221                 return NULL;
222
223         for (std::vector<XLine*>::iterator i = active_lines.begin(); i != active_lines.end(); i++)
224                 if ((*i)->type == 'Q' && (*i)->Matches(nick))
225                         return (QLine*)(*i);
226         return NULL;
227 }
228
229 // returns a pointer to the reason if a host matches a gline, NULL if it didnt match
230
231 GLine* XLineManager::matches_gline(User* user)
232 {
233         if (lookup_lines.find('G') == lookup_lines.end())
234                 return NULL;
235
236         if (lookup_lines.find('G') != lookup_lines.end() && lookup_lines['G'].empty())
237                 return NULL;
238
239         for (std::vector<XLine*>::iterator i = active_lines.begin(); i != active_lines.end(); i++)
240                 if ((*i)->type == 'G' && (*i)->Matches(user))
241                         return (GLine*)(*i);
242
243         return NULL;
244 }
245
246 ELine* XLineManager::matches_exception(User* user)
247 {
248         if (lookup_lines.find('E') == lookup_lines.end())
249                 return NULL;
250
251         if (lookup_lines.find('E') != lookup_lines.end() && lookup_lines['E'].empty())
252                 return NULL;
253
254         for (std::vector<XLine*>::iterator i = active_lines.begin(); i != active_lines.end(); i++)
255         {
256                 if ((*i)->type == 'E' && (*i)->Matches(user))
257                         return (ELine*)(*i);
258         }
259         return NULL;
260 }
261
262
263 void XLineManager::gline_set_creation_time(const char* host, time_t create_time)
264 {
265         /*for (std::vector<XLine*>::iterator i = glines.begin(); i != glines.end(); i++)
266         {
267                 if (!strcasecmp(host,(*i)->hostmask))
268                 {
269                         (*i)->set_time = create_time;
270                         (*i)->expiry = create_time + (*i)->duration;
271                         return;
272                 }
273         }*/
274
275         return ;
276 }
277
278 void XLineManager::eline_set_creation_time(const char* host, time_t create_time)
279 {
280         /*for (std::vector<ELine*>::iterator i = elines.begin(); i != elines.end(); i++)
281         {
282                 if (!strcasecmp(host,(*i)->hostmask))
283                 {
284                         (*i)->set_time = create_time;
285                         (*i)->expiry = create_time + (*i)->duration;
286                         return;
287                 }
288         }*/
289
290         return;
291 }
292
293 void XLineManager::qline_set_creation_time(const char* nick, time_t create_time)
294 {
295         /*for (std::vector<QLine*>::iterator i = qlines.begin(); i != qlines.end(); i++)
296         {
297                 if (!strcasecmp(nick,(*i)->nick))
298                 {
299                         (*i)->set_time = create_time;
300                         (*i)->expiry = create_time + (*i)->duration;
301                         return;
302                 }
303         }*/
304
305         return;
306 }
307
308 void XLineManager::zline_set_creation_time(const char* ip, time_t create_time)
309 {
310         /*for (std::vector<ZLine*>::iterator i = zlines.begin(); i != zlines.end(); i++)
311         {
312                 if (!strcasecmp(ip,(*i)->ipaddr))
313                 {
314                         (*i)->set_time = create_time;
315                         (*i)->expiry = create_time + (*i)->duration;
316                         return;
317                 }
318         }*/
319
320         return;
321 }
322
323 // returns a pointer to the reason if an ip address matches a zline, NULL if it didnt match
324
325 ZLine* XLineManager::matches_zline(User *u)
326 {
327         if (lookup_lines.find('Z') == lookup_lines.end())
328                 return NULL;
329
330         if (lookup_lines.find('Z') != lookup_lines.end() && lookup_lines['Z'].empty())
331                 return NULL;
332
333         for (std::vector<XLine*>::iterator i = active_lines.begin(); i != active_lines.end(); i++)
334                 if ((*i)->type == 'Z' && (*i)->Matches(u))
335                         return (ZLine*)(*i);
336         return NULL;
337 }
338
339 // returns a pointer to the reason if a host matches a kline, NULL if it didnt match
340
341 KLine* XLineManager::matches_kline(User* user)
342 {
343         if (lookup_lines.find('K') == lookup_lines.end())
344                 return NULL;
345
346         if (lookup_lines.find('K') != lookup_lines.end() && lookup_lines['K'].empty())
347                 return NULL;
348
349         for (std::vector<XLine*>::iterator i = active_lines.begin(); i != active_lines.end(); i++)
350                 if ((*i)->Matches(user))
351                         return (KLine*)(*i);
352
353         return NULL;
354 }
355
356 bool XLineManager::XSortComparison(const XLine *one, const XLine *two)
357 {
358         return (one->expiry) < (two->expiry);
359 }
360
361 // removes lines that have expired
362 void XLineManager::expire_lines()
363 {
364         time_t current = ServerInstance->Time();
365
366         /* Because we now store all our XLines in sorted order using ((*i)->duration + (*i)->set_time) as a key, this
367          * means that to expire the XLines we just need to do a while, picking off the top few until there are
368          * none left at the head of the queue that are after the current time. We use PermLines as an offset into the
369          * vector past the first item with a duration 0.
370          */
371
372         std::vector<XLine*>::iterator start = active_lines.begin() + PermLines;
373
374         while ((start < active_lines.end()) && (current > (*start)->expiry))
375         {
376                 (*start)->DisplayExpiry();
377                 (*start)->Unset();
378
379                 if (lookup_lines.find((*start)->type) != lookup_lines.end())
380                         lookup_lines[(*start)->type].erase((*start)->Displayable());
381
382                 std::vector<XLine*>::iterator pptr = std::find(pending_lines.begin(), pending_lines.end(), *start);
383                 if (pptr != pending_lines.end())
384                         pending_lines.erase(pptr);
385
386                 if (!(*start)->duration)
387                         PermLines--;
388
389                 delete *start;
390                 active_lines.erase(start);
391         }
392 }
393
394 // applies lines, removing clients and changing nicks etc as applicable
395 void XLineManager::ApplyLines()
396 {
397         for (std::vector<User*>::const_iterator u2 = ServerInstance->local_users.begin(); u2 != ServerInstance->local_users.end(); u2++)
398         {
399                 User* u = (User*)(*u2);
400
401                 for (std::vector<XLine *>::iterator i = pending_lines.begin(); i != pending_lines.end(); i++)
402                 {
403                         XLine *x = *i;
404                         if (x->Matches(u))
405                                 x->Apply(u);
406                 }
407         }
408
409         pending_lines.clear();
410 }
411
412 void XLineManager::stats_k(User* user, string_list &results)
413 {
414         /*std::string sn = ServerInstance->Config->ServerName;
415         for (std::vector<KLine*>::iterator i = klines.begin(); i != klines.end(); i++)
416                 results.push_back(sn+" 216 "+user->nick+" :"+(*i)->identmask+"@"+(*i)->hostmask+" "+ConvToStr((*i)->set_time)+" "+ConvToStr((*i)->duration)+" "+(*i)->source+" :"+(*i)->reason);*/
417 }
418
419 void XLineManager::stats_g(User* user, string_list &results)
420 {
421         /*std::string sn = ServerInstance->Config->ServerName;
422         for (std::vector<GLine*>::iterator i = glines.begin(); i != glines.end(); i++)
423                 results.push_back(sn+" 223 "+user->nick+" :"+(*i)->identmask+"@"+(*i)->hostmask+" "+ConvToStr((*i)->set_time)+" "+ConvToStr((*i)->duration)+" "+(*i)->source+" :"+(*i)->reason);*/
424 }
425
426 void XLineManager::stats_q(User* user, string_list &results)
427 {
428         /*std::string sn = ServerInstance->Config->ServerName;
429         for (std::vector<QLine*>::iterator i = qlines.begin(); i != qlines.end(); i++)
430                 results.push_back(sn+" 217 "+user->nick+" :"+(*i)->nick+" "+ConvToStr((*i)->set_time)+" "+ConvToStr((*i)->duration)+" "+(*i)->source+" :"+(*i)->reason);*/
431 }
432
433 void XLineManager::stats_z(User* user, string_list &results)
434 {
435         /*std::string sn = ServerInstance->Config->ServerName;
436         for (std::vector<ZLine*>::iterator i = zlines.begin(); i != zlines.end(); i++)
437                 results.push_back(sn+" 223 "+user->nick+" :"+(*i)->ipaddr+" "+ConvToStr((*i)->set_time)+" "+ConvToStr((*i)->duration)+" "+(*i)->source+" :"+(*i)->reason);*/
438 }
439
440 void XLineManager::stats_e(User* user, string_list &results)
441 {
442         /*std::string sn = ServerInstance->Config->ServerName;
443         for (std::vector<ELine*>::iterator i = elines.begin(); i != elines.end(); i++)
444                 results.push_back(sn+" 223 "+user->nick+" :"+(*i)->identmask+"@"+(*i)->hostmask+" "+ConvToStr((*i)->set_time)+" "+ConvToStr((*i)->duration)+" "+(*i)->source+" :"+(*i)->reason);*/
445 }
446
447 XLineManager::XLineManager(InspIRCd* Instance) : ServerInstance(Instance), PermLines(0)
448 {
449         GFact = new GLineFactory(Instance);
450         EFact = new ELineFactory(Instance);
451         KFact = new KLineFactory(Instance);
452         QFact = new QLineFactory(Instance);
453         ZFact = new ZLineFactory(Instance);
454
455         RegisterFactory(GFact);
456         RegisterFactory(EFact);
457         RegisterFactory(KFact);
458         RegisterFactory(QFact);
459         RegisterFactory(ZFact);
460 }
461
462 XLineManager::~XLineManager()
463 {
464         UnregisterFactory(GFact);
465         UnregisterFactory(EFact);
466         UnregisterFactory(KFact);
467         UnregisterFactory(QFact);
468         UnregisterFactory(ZFact);
469
470         delete GFact;
471         delete EFact;
472         delete KFact;
473         delete QFact;
474         delete ZFact;
475 }
476
477 void XLine::Apply(User* u)
478 {
479 }
480
481 void XLine::DefaultApply(User* u, char line)
482 {
483         char reason[MAXBUF];
484         snprintf(reason, MAXBUF, "%c-Lined: %s", line, this->reason);
485         if (*ServerInstance->Config->MoronBanner)
486                 u->WriteServ("NOTICE %s :*** %s", u->nick, ServerInstance->Config->MoronBanner);
487         if (ServerInstance->Config->HideBans)
488                 User::QuitUser(ServerInstance, u, line + std::string("-Lined"), reason);
489         else
490                 User::QuitUser(ServerInstance, u, reason);
491 }
492
493 bool KLine::Matches(User *u)
494 {
495         if (u->exempt)
496                 return false;
497
498         if ((match(u->ident, this->identmask)))
499         {
500                 if ((match(u->host, this->hostmask, true)) || (match(u->GetIPString(), this->hostmask, true)))
501                 {
502                         return true;
503                 }
504         }
505
506         return false;
507 }
508
509 void KLine::Apply(User* u)
510 {
511         DefaultApply(u, 'K');
512 }
513
514 bool GLine::Matches(User *u)
515 {
516         if (u->exempt)
517                 return false;
518
519         if ((match(u->ident, this->identmask)))
520         {
521                 if ((match(u->host, this->hostmask, true)) || (match(u->GetIPString(), this->hostmask, true)))
522                 {
523                         return true;
524                 }
525         }
526
527         return false;
528 }
529
530 void GLine::Apply(User* u)
531 {       
532         DefaultApply(u, 'G');
533 }
534
535 bool ELine::Matches(User *u)
536 {
537         if (u->exempt)
538                 return false;
539
540         if ((match(u->ident, this->identmask)))
541         {
542                 if ((match(u->host, this->hostmask, true)) || (match(u->GetIPString(), this->hostmask, true)))
543                 {
544                         return true;
545                 }
546         }
547
548         return false;
549 }
550
551 bool ZLine::Matches(User *u)
552 {
553         if (u->exempt)
554                 return false;
555
556         if (match(u->GetIPString(), this->ipaddr, true))
557                 return true;
558         else
559                 return false;
560 }
561
562 void ZLine::Apply(User* u)
563 {       
564         DefaultApply(u, 'Z');
565 }
566
567
568 bool QLine::Matches(User *u)
569 {
570         if (u->exempt)
571                 return false;
572
573         if (match(u->nick, this->nick))
574                 return true;
575
576         return false;
577 }
578
579 void QLine::Apply(User* u)
580 {       
581         /* Can we force the user to their uid here instead? */
582         DefaultApply(u, 'Q');
583 }
584
585
586 bool ZLine::Matches(const std::string &str)
587 {
588         if (match(str.c_str(), this->ipaddr, true))
589                 return true;
590         else
591                 return false;
592 }
593
594 bool QLine::Matches(const std::string &str)
595 {
596         if (match(str.c_str(), this->nick))
597                 return true;
598
599         return false;
600 }
601
602 bool ELine::Matches(const std::string &str)
603 {
604         return ((match(str.c_str(), matchtext.c_str(), true)));
605 }
606
607 bool KLine::Matches(const std::string &str)
608 {
609         return ((match(str.c_str(), matchtext.c_str(), true)));
610 }
611
612 bool GLine::Matches(const std::string &str)
613 {
614         return ((match(str.c_str(), matchtext.c_str(), true)));
615 }
616
617 bool ELine::MatchesLiteral(const std::string &str)
618 {
619         return (assign(str) == matchtext);
620 }
621
622 bool ZLine::MatchesLiteral(const std::string &str)
623 {       
624         return (assign(str) == this->ipaddr);
625 }
626
627 bool GLine::MatchesLiteral(const std::string &str)
628 {       
629         return (assign(str) == matchtext);
630 }
631
632 bool KLine::MatchesLiteral(const std::string &str)
633 {       
634         return (assign(str) == matchtext);
635 }
636
637 bool QLine::MatchesLiteral(const std::string &str)
638 {       
639         return (assign(str) == this->nick);
640 }
641
642 void ELine::OnAdd()
643 {
644         /* When adding one eline, only check the one eline */
645         for (std::vector<User*>::const_iterator u2 = ServerInstance->local_users.begin(); u2 != ServerInstance->local_users.end(); u2++)
646         {
647                 User* u = (User*)(*u2);
648                 if (this->Matches(u))
649                         u->exempt = true;
650         }
651 }
652
653 void ELine::DisplayExpiry()
654 {
655         ServerInstance->SNO->WriteToSnoMask('x',"Expiring timed E-Line %s@%s (set by %s %d seconds ago)",this->identmask,this->hostmask,this->source,this->duration);
656 }
657
658 void QLine::DisplayExpiry()
659 {
660         ServerInstance->SNO->WriteToSnoMask('x',"Expiring timed Q-Line %s (set by %s %d seconds ago)",this->nick,this->source,this->duration);
661 }
662
663 void ZLine::DisplayExpiry()
664 {
665         ServerInstance->SNO->WriteToSnoMask('x',"Expiring timed Z-Line %s (set by %s %d seconds ago)",this->ipaddr,this->source,this->duration);
666 }
667
668 void KLine::DisplayExpiry()
669 {
670         ServerInstance->SNO->WriteToSnoMask('x',"Expiring timed K-Line %s@%s (set by %s %d seconds ago)",this->identmask,this->hostmask,this->source,this->duration);
671 }
672
673 void GLine::DisplayExpiry()
674 {
675         ServerInstance->SNO->WriteToSnoMask('x',"Expiring timed G-Line %s@%s (set by %s %d seconds ago)",this->identmask,this->hostmask,this->source,this->duration);
676 }
677
678 const char* ELine::Displayable()
679 {
680         return matchtext.c_str();
681 }
682
683 const char* KLine::Displayable()
684 {
685         return matchtext.c_str();
686 }
687
688 const char* GLine::Displayable()
689 {
690         return matchtext.c_str();
691 }
692
693 const char* ZLine::Displayable()
694 {
695         return ipaddr;
696 }
697
698 const char* QLine::Displayable()
699 {
700         return nick;
701 }
702
703 bool XLineManager::RegisterFactory(XLineFactory* xlf)
704 {
705         std::map<char, XLineFactory*>::iterator n = line_factory.find(xlf->GetType());
706
707         if (n != line_factory.end())
708                 return false;
709
710         line_factory[xlf->GetType()] = xlf;
711
712         return true;
713 }
714
715 bool XLineManager::UnregisterFactory(XLineFactory* xlf)
716 {
717         std::map<char, XLineFactory*>::iterator n = line_factory.find(xlf->GetType());
718
719         if (n == line_factory.end())
720                 return false;
721
722         line_factory.erase(n);
723
724         return true;
725 }
726
727 XLineFactory* XLineManager::GetFactory(const char type)
728 {
729         std::map<char, XLineFactory*>::iterator n = line_factory.find(type);
730
731         if (n != line_factory.end())
732                 return NULL;
733
734         return n->second;
735 }
736