]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/inspircd.cpp
Fixes for m_nicklock desync
[user/henk/code/inspircd.git] / src / inspircd.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 #include "inspircd.h"
15 #include "configreader.h"
16 #include <signal.h>
17
18 #ifndef WIN32
19         #include <dirent.h>
20         #include <unistd.h>
21         #include <sys/resource.h>
22         #include <dlfcn.h>
23         #include <getopt.h>
24
25         /* Some systems don't define RUSAGE_SELF. This should fix them. */
26         #ifndef RUSAGE_SELF
27                 #define RUSAGE_SELF 0
28         #endif
29 #endif
30
31 #include <exception>
32 #include <fstream>
33 #include "modules.h"
34 #include "mode.h"
35 #include "xline.h"
36 #include "socketengine.h"
37 #include "inspircd_se_config.h"
38 #include "socket.h"
39 #include "typedefs.h"
40 #include "command_parse.h"
41 #include "exitcodes.h"
42 #include "caller.h"
43
44 using irc::sockets::NonBlocking;
45 using irc::sockets::Blocking;
46 using irc::sockets::insp_ntoa;
47 using irc::sockets::insp_inaddr;
48 using irc::sockets::insp_sockaddr;
49
50 InspIRCd* SI = NULL;
51 int* mysig = NULL;
52
53 /* Burlex: Moved from exitcodes.h -- due to duplicate symbols */
54 const char* ExitCodes[] =
55 {
56                 "No error", /* 0 */
57                 "DIE command", /* 1 */
58                 "execv() failed", /* 2 */
59                 "Internal error", /* 3 */
60                 "Config file error", /* 4 */
61                 "Logfile error", /* 5 */
62                 "POSIX fork failed", /* 6 */
63                 "Bad commandline parameters", /* 7 */
64                 "No ports could be bound", /* 8 */
65                 "Can't write PID file", /* 9 */
66                 "SocketEngine could not initialize", /* 10 */
67                 "Refusing to start up as root", /* 11 */
68                 "Found a <die> tag!", /* 12 */
69                 "Couldn't load module on startup", /* 13 */
70                 "Could not create windows forked process", /* 14 */
71                 "Received SIGTERM", /* 15 */
72 };
73
74 void InspIRCd::Cleanup()
75 {
76         std::vector<std::string> mymodnames;
77         int MyModCount = this->GetModuleCount();
78
79         for (unsigned int i = 0; i < Config->ports.size(); i++)
80         {
81                 /* This calls the constructor and closes the listening socket */
82                 delete Config->ports[i];
83         }
84
85         Config->ports.clear();
86
87         /* Close all client sockets, or the new process inherits them */
88         for (std::vector<userrec*>::const_iterator i = this->local_users.begin(); i != this->local_users.end(); i++)
89         {
90                 (*i)->SetWriteError("Server shutdown");
91                 (*i)->CloseSocket();
92         }
93
94         /* We do this more than once, so that any service providers get a
95          * chance to be unhooked by the modules using them, but then get
96          * a chance to be removed themsleves.
97          */
98         for (int tries = 0; tries < 3; tries++)
99         {
100                 MyModCount = this->GetModuleCount();
101                 mymodnames.clear();
102
103                 /* Unload all modules, so they get a chance to clean up their listeners */
104                 for (int j = 0; j <= MyModCount; j++)
105                         mymodnames.push_back(Config->module_names[j]);
106
107                 for (int k = 0; k <= MyModCount; k++)
108                         this->UnloadModule(mymodnames[k].c_str());
109         }
110
111         /* Close logging */
112         this->Logger->Close();
113
114         /* Cleanup Server Names */
115         for(servernamelist::iterator itr = servernames.begin(); itr != servernames.end(); ++itr)
116                 delete (*itr);
117
118 #ifdef WINDOWS
119         /* WSACleanup */
120         WSACleanup();
121 #endif
122 }
123
124 void InspIRCd::Restart(const std::string &reason)
125 {
126         /* SendError flushes each client's queue,
127          * regardless of writeability state
128          */
129         this->SendError(reason);
130
131         this->Cleanup();
132
133         /* Figure out our filename (if theyve renamed it, we're boned) */
134         std::string me;
135
136 #ifdef WINDOWS
137         char module[MAX_PATH];
138         if (GetModuleFileName(NULL, module, MAX_PATH))
139                 me = module;
140 #else
141         me = Config->MyDir + "/inspircd";
142 #endif
143
144         if (execv(me.c_str(), Config->argv) == -1)
145         {
146                 /* Will raise a SIGABRT if not trapped */
147                 throw CoreException(std::string("Failed to execv()! error: ") + strerror(errno));
148         }
149 }
150
151 void InspIRCd::ResetMaxBans()
152 {
153         for (chan_hash::const_iterator i = chanlist->begin(); i != chanlist->end(); i++)
154                 i->second->ResetMaxBans();
155 }
156
157 /** Because hash_map doesnt free its buckets when we delete items (this is a 'feature')
158  * we must occasionally rehash the hash (yes really).
159  * We do this by copying the entries from the old hash to a new hash, causing all
160  * empty buckets to be weeded out of the hash. We dont do this on a timer, as its
161  * very expensive, so instead we do it when the user types /REHASH and expects a
162  * short delay anyway.
163  */
164 void InspIRCd::RehashUsersAndChans()
165 {
166         user_hash* old_users = this->clientlist;
167         chan_hash* old_chans = this->chanlist;
168
169         this->clientlist = new user_hash();
170         this->chanlist = new chan_hash();
171
172         for (user_hash::const_iterator n = old_users->begin(); n != old_users->end(); n++)
173                 this->clientlist->insert(*n);
174
175         delete old_users;
176
177         for (chan_hash::const_iterator n = old_chans->begin(); n != old_chans->end(); n++)
178                 this->chanlist->insert(*n);
179
180         delete old_chans;
181 }
182
183 void InspIRCd::CloseLog()
184 {
185         this->Logger->Close();
186 }
187
188 void InspIRCd::SetSignals()
189 {
190 #ifndef WIN32
191         signal(SIGALRM, SIG_IGN);
192         signal(SIGHUP, InspIRCd::SetSignal);
193         signal(SIGPIPE, SIG_IGN);
194         signal(SIGCHLD, SIG_IGN);
195 #endif
196         signal(SIGTERM, InspIRCd::SetSignal);
197 }
198
199 void InspIRCd::QuickExit(int status)
200 {
201         exit(0);
202 }
203
204 bool InspIRCd::DaemonSeed()
205 {
206 #ifdef WINDOWS
207         printf_c("InspIRCd Process ID: \033[1;32m%lu\033[0m\n", GetCurrentProcessId());
208         return true;
209 #else
210         signal(SIGTERM, InspIRCd::QuickExit);
211
212         int childpid;
213         if ((childpid = fork ()) < 0)
214                 return false;
215         else if (childpid > 0)
216         {
217                 /* We wait here for the child process to kill us,
218                  * so that the shell prompt doesnt come back over
219                  * the output.
220                  * Sending a kill with a signal of 0 just checks
221                  * if the child pid is still around. If theyre not,
222                  * they threw an error and we should give up.
223                  */
224                 while (kill(childpid, 0) != -1)
225                         sleep(1);
226                 exit(0);
227         }
228         setsid ();
229         umask (007);
230         printf("InspIRCd Process ID: \033[1;32m%lu\033[0m\n",(unsigned long)getpid());
231
232         signal(SIGTERM, InspIRCd::SetSignal);
233
234         rlimit rl;
235         if (getrlimit(RLIMIT_CORE, &rl) == -1)
236         {
237                 this->Log(DEFAULT,"Failed to getrlimit()!");
238                 return false;
239         }
240         else
241         {
242                 rl.rlim_cur = rl.rlim_max;
243                 if (setrlimit(RLIMIT_CORE, &rl) == -1)
244                         this->Log(DEFAULT,"setrlimit() failed, cannot increase coredump size.");
245         }
246
247         return true;
248 #endif
249 }
250
251 void InspIRCd::WritePID(const std::string &filename)
252 {
253         std::string fname = (filename.empty() ? "inspircd.pid" : filename);
254         if (*(fname.begin()) != '/')
255         {
256                 std::string::size_type pos;
257                 std::string confpath = this->ConfigFileName;
258                 if ((pos = confpath.rfind("/")) != std::string::npos)
259                 {
260                         /* Leaves us with just the path */
261                         fname = confpath.substr(0, pos) + std::string("/") + fname;
262                 }
263         }
264         std::ofstream outfile(fname.c_str());
265         if (outfile.is_open())
266         {
267                 outfile << getpid();
268                 outfile.close();
269         }
270         else
271         {
272                 printf("Failed to write PID-file '%s', exiting.\n",fname.c_str());
273                 this->Log(DEFAULT,"Failed to write PID-file '%s', exiting.",fname.c_str());
274                 Exit(EXIT_STATUS_PID);
275         }
276 }
277
278 InspIRCd::InspIRCd(int argc, char** argv)
279         : ModCount(0),
280           GlobalCulls(this),
281
282          /* Functor initialisation. Note that the ordering here is very important. */
283          HandleProcessUser(this),
284          HandleIsNick(this),
285          HandleIsIdent(this),
286          HandleFindDescriptor(this),
287          HandleFloodQuitUser(this),
288
289          /* Functor pointer initialisation. Must match the order of the list above */
290          ProcessUser(&HandleProcessUser),
291          IsNick(&HandleIsNick),
292          IsIdent(&HandleIsIdent),
293          FindDescriptor(&HandleFindDescriptor),
294          FloodQuitUser(&HandleFloodQuitUser)
295
296 {
297
298         int found_ports = 0;
299         FailedPortList pl;
300         int do_version = 0, do_nofork = 0, do_debug = 0, do_nolog = 0, do_root = 0;    /* flag variables */
301         char c = 0;
302
303         modules.resize(255);
304         factory.resize(255);
305         memset(&server, 0, sizeof(server));
306         memset(&client, 0, sizeof(client));
307
308         this->s_signal = 0;
309
310         this->unregistered_count = 0;
311
312         this->clientlist = new user_hash();
313         this->chanlist = new chan_hash();
314
315         this->Config = new ServerConfig(this);
316
317         this->Config->argv = argv;
318         this->Config->argc = argc;
319
320         if (chdir(Config->GetFullProgDir().c_str()))
321         {
322                 printf("Unable to change to my directory: %s\nAborted.", strerror(errno));
323                 exit(0);
324         }
325
326         this->Config->opertypes.clear();
327         this->Config->operclass.clear();
328         this->SNO = new SnomaskManager(this);
329         this->TIME = this->OLDTIME = this->startup_time = time(NULL);
330         this->time_delta = 0;
331         this->next_call = this->TIME + 3;
332         srand(this->TIME);
333
334         *this->LogFileName = 0;
335         strlcpy(this->ConfigFileName, CONFIG_FILE, MAXBUF);
336
337         struct option longopts[] =
338         {
339                 { "nofork",     no_argument,            &do_nofork,     1       },
340                 { "logfile",    required_argument,      NULL,           'f'     },
341                 { "config",     required_argument,      NULL,           'c'     },
342                 { "debug",      no_argument,            &do_debug,      1       },
343                 { "nolog",      no_argument,            &do_nolog,      1       },
344                 { "runasroot",  no_argument,            &do_root,       1       },
345                 { "version",    no_argument,            &do_version,    1       },
346                 { 0, 0, 0, 0 }
347         };
348
349         while ((c = getopt_long_only(argc, argv, ":f:", longopts, NULL)) != -1)
350         {
351                 switch (c)
352                 {
353                         case 'f':
354                                 /* Log filename was set */
355                                 strlcpy(LogFileName, optarg, MAXBUF);
356                         break;
357                         case 'c':
358                                 /* Config filename was set */
359                                 strlcpy(ConfigFileName, optarg, MAXBUF);
360                         break;
361                         case 0:
362                                 /* getopt_long_only() set an int variable, just keep going */
363                         break;
364                         default:
365                                 /* Unknown parameter! DANGER, INTRUDER.... err.... yeah. */
366                                 printf("Usage: %s [--nofork] [--nolog] [--debug] [--logfile <filename>] [--runasroot] [--version] [--config <config>]\n", argv[0]);
367                                 Exit(EXIT_STATUS_ARGV);
368                         break;
369                 }
370         }
371
372         if (do_version)
373         {
374                 printf("\n%s r%s\n", VERSION, REVISION);
375                 Exit(EXIT_STATUS_NOERROR);
376         }
377
378 #ifdef WIN32
379
380         // Handle forking
381         if(!do_nofork)
382         {
383                 DWORD ExitCode = WindowsForkStart(this);
384                 if(ExitCode)
385                         Exit(ExitCode);
386         }
387
388         // Set up winsock
389         WSADATA wsadata;
390         WSAStartup(MAKEWORD(2,0), &wsadata);
391
392         ChangeWindowsSpecificPointers(this);
393 #endif
394         if (!ServerConfig::FileExists(this->ConfigFileName))
395         {
396                 printf("ERROR: Cannot open config file: %s\nExiting...\n", this->ConfigFileName);
397                 this->Log(DEFAULT,"Unable to open config file %s", this->ConfigFileName);
398                 Exit(EXIT_STATUS_CONFIG);
399         }
400
401         printf_c("\033[1;32mInspire Internet Relay Chat Server, compiled %s at %s\n",__DATE__,__TIME__);
402         printf_c("(C) InspIRCd Development Team.\033[0m\n\n");
403         printf_c("Developers:\t\t\033[1;32mBrain, FrostyCoolSlug, w00t, Om, Special, pippijn, peavey, Burlex\033[0m\n");
404         printf_c("Others:\t\t\t\033[1;32mSee /INFO Output\033[0m\n");
405
406         /* Set the finished argument values */
407         Config->nofork = do_nofork;
408         Config->forcedebug = do_debug;
409         Config->writelog = !do_nolog;
410
411         strlcpy(Config->MyExecutable,argv[0],MAXBUF);
412
413         this->OpenLog(argv, argc);
414
415         this->stats = new serverstats();
416         this->Timers = new TimerManager(this);
417         this->Parser = new CommandParser(this);
418         this->XLines = new XLineManager(this);
419         Config->ClearStack();
420         Config->Read(true, NULL);
421
422         if (!do_root)
423                 this->CheckRoot();
424         else
425         {
426                 printf("* WARNING * WARNING * WARNING * WARNING * WARNING * \n\n");
427                 printf("YOU ARE RUNNING INSPIRCD AS ROOT. THIS IS UNSUPPORTED\n");
428                 printf("AND IF YOU ARE HACKED, CRACKED, SPINDLED OR MUTILATED\n");
429                 printf("OR ANYTHING ELSE UNEXPECTED HAPPENS TO YOU OR YOUR\n");
430                 printf("SERVER, THEN IT IS YOUR OWN FAULT. IF YOU DID NOT MEAN\n");
431                 printf("TO START INSPIRCD AS ROOT, HIT CTRL+C NOW AND RESTART\n");
432                 printf("THE PROGRAM AS A NORMAL USER. YOU HAVE BEEN WARNED!\n");
433                 printf("\nInspIRCd starting in 20 seconds, ctrl+c to abort...\n");
434                 sleep(20);
435         }
436
437         this->SetSignals();
438
439         if (!Config->nofork)
440         {
441                 if (!this->DaemonSeed())
442                 {
443                         printf("ERROR: could not go into daemon mode. Shutting down.\n");
444                         Log(DEFAULT,"ERROR: could not go into daemon mode. Shutting down.");
445                         Exit(EXIT_STATUS_FORK);
446                 }
447         }
448
449
450         /* Because of limitations in kqueue on freebsd, we must fork BEFORE we
451          * initialize the socket engine.
452          */
453         SocketEngineFactory* SEF = new SocketEngineFactory();
454         SE = SEF->Create(this);
455         delete SEF;
456
457         this->Modes = new ModeParser(this);
458         this->AddServerName(Config->ServerName);
459         CheckDie();
460         int bounditems = BindPorts(true, found_ports, pl);
461
462         for(int t = 0; t < 255; t++)
463                 Config->global_implementation[t] = 0;
464
465         memset(&Config->implement_lists,0,sizeof(Config->implement_lists));
466
467         printf("\n");
468
469         this->Res = new DNS(this);
470
471         this->LoadAllModules();
472         /* Just in case no modules were loaded - fix for bug #101 */
473         this->BuildISupport();
474         InitializeDisabledCommands(Config->DisabledCommands, this);
475
476         if ((Config->ports.size() == 0) && (found_ports > 0))
477         {
478                 printf("\nERROR: I couldn't bind any ports! Are you sure you didn't start InspIRCd twice?\n");
479                 Log(DEFAULT,"ERROR: I couldn't bind any ports! Are you sure you didn't start InspIRCd twice?");
480                 Exit(EXIT_STATUS_BIND);
481         }
482
483         if (Config->ports.size() != (unsigned int)found_ports)
484         {
485                 printf("\nWARNING: Not all your client ports could be bound --\nstarting anyway with %d of %d client ports bound.\n\n", bounditems, found_ports);
486                 printf("The following port(s) failed to bind:\n");
487                 int j = 1;
488                 for (FailedPortList::iterator i = pl.begin(); i != pl.end(); i++, j++)
489                 {
490                         printf("%d.\tIP: %s\tPort: %lu\n", j, i->first.empty() ? "<all>" : i->first.c_str(), (unsigned long)i->second);
491                 }
492         }
493 #ifndef WINDOWS
494         if (!Config->nofork)
495         {
496                 if (kill(getppid(), SIGTERM) == -1)
497                 {
498                         printf("Error killing parent process: %s\n",strerror(errno));
499                         Log(DEFAULT,"Error killing parent process: %s",strerror(errno));
500                 }
501         }
502
503         if (isatty(0) && isatty(1) && isatty(2))
504         {
505                 /* We didn't start from a TTY, we must have started from a background process -
506                  * e.g. we are restarting, or being launched by cron. Dont kill parent, and dont
507                  * close stdin/stdout
508                  */
509                 if (!do_nofork)
510                 {
511                         fclose(stdin);
512                         fclose(stderr);
513                         fclose(stdout);
514                 }
515                 else
516                 {
517                         Log(DEFAULT,"Keeping pseudo-tty open as we are running in the foreground.");
518                 }
519         }
520 #else
521         WindowsIPC = new IPC(this);
522         if(!Config->nofork)
523         {
524                 WindowsForkKillOwner(this);
525                 FreeConsole();
526         }
527 #endif
528         printf("\nInspIRCd is now running!\n");
529         Log(DEFAULT,"Startup complete.");
530
531         this->WritePID(Config->PID);
532 }
533
534 void InspIRCd::DoOneIteration(bool process_module_sockets)
535 {
536 #ifndef WIN32
537         static rusage ru;
538 #else
539         static time_t uptime;
540         static struct tm * stime;
541         static char window_title[100];
542 #endif
543
544         /* time() seems to be a pretty expensive syscall, so avoid calling it too much.
545          * Once per loop iteration is pleanty.
546          */
547         OLDTIME = TIME;
548         TIME = time(NULL);
549
550         /* Run background module timers every few seconds
551          * (the docs say modules shouldnt rely on accurate
552          * timing using this event, so we dont have to
553          * time this exactly).
554          */
555         if (TIME != OLDTIME)
556         {
557                 if (TIME < OLDTIME)
558                         WriteOpers("*** \002EH?!\002 -- Time is flowing BACKWARDS in this dimension! Clock drifted backwards %d secs.",abs(OLDTIME-TIME));
559                 if ((TIME % 3600) == 0)
560                 {
561                         this->RehashUsersAndChans();
562                         FOREACH_MOD_I(this, I_OnGarbageCollect, OnGarbageCollect());
563                 }
564                 Timers->TickTimers(TIME);
565                 this->DoBackgroundUserStuff(TIME);
566
567                 if ((TIME % 5) == 0)
568                 {
569                         XLines->expire_lines();
570                         FOREACH_MOD_I(this,I_OnBackgroundTimer,OnBackgroundTimer(TIME));
571                         Timers->TickMissedTimers(TIME);
572                 }
573 #ifndef WIN32
574                 /* Same change as in cmd_stats.cpp, use RUSAGE_SELF rather than '0' -- Om */
575                 if (!getrusage(RUSAGE_SELF, &ru))
576                 {
577                         gettimeofday(&this->stats->LastSampled, NULL);
578                         this->stats->LastCPU = ru.ru_utime;
579                 }
580 #else
581                 WindowsIPC->Check();
582
583                 if(Config->nofork)
584                 {
585                         uptime = Time() - startup_time;
586                         stime = gmtime(&uptime);
587                         snprintf(window_title, 100, "InspIRCd - %u clients, %u accepted connections - Up %u days, %.2u:%.2u:%.2u",
588                                 LocalUserCount(), stats->statsAccept, stime->tm_yday, stime->tm_hour, stime->tm_min, stime->tm_sec);
589                         SetConsoleTitle(window_title);
590                 }
591 #endif
592         }
593
594         /* Call the socket engine to wait on the active
595          * file descriptors. The socket engine has everything's
596          * descriptors in its list... dns, modules, users,
597          * servers... so its nice and easy, just one call.
598          * This will cause any read or write events to be
599          * dispatched to their handlers.
600          */
601         this->SE->DispatchEvents();
602
603         /* if any users was quit, take them out */
604         this->GlobalCulls.Apply();
605
606         /* If any inspsockets closed, remove them */
607         this->InspSocketCull();
608
609         if (this->s_signal)
610         {
611                 this->SignalHandler(s_signal);
612                 this->s_signal = 0;
613         }
614
615 }
616
617 void InspIRCd::InspSocketCull()
618 {
619         for (std::map<InspSocket*,InspSocket*>::iterator x = SocketCull.begin(); x != SocketCull.end(); ++x)
620         {
621                 SE->DelFd(x->second);
622                 x->second->Close();
623                 delete x->second;
624         }
625         SocketCull.clear();
626 }
627
628 int InspIRCd::Run()
629 {
630         while (true)
631         {
632                 DoOneIteration(true);
633         }
634         /* This is never reached -- we hope! */
635         return 0;
636 }
637
638 /**********************************************************************************/
639
640 /**
641  * An ircd in four lines! bwahahaha. ahahahahaha. ahahah *cough*.
642  */
643
644 int main(int argc, char** argv)
645 {
646         SI = new InspIRCd(argc, argv);
647         mysig = &SI->s_signal;
648         SI->Run();
649         delete SI;
650         return 0;
651 }
652
653 /* this returns true when all modules are satisfied that the user should be allowed onto the irc server
654  * (until this returns true, a user will block in the waiting state, waiting to connect up to the
655  * registration timeout maximum seconds)
656  */
657 bool InspIRCd::AllModulesReportReady(userrec* user)
658 {
659         if (!Config->global_implementation[I_OnCheckReady])
660                 return true;
661
662         for (int i = 0; i <= this->GetModuleCount(); i++)
663         {
664                 if (Config->implement_lists[i][I_OnCheckReady])
665                 {
666                         int res = modules[i]->OnCheckReady(user);
667                         if (!res)
668                                 return false;
669                 }
670         }
671         return true;
672 }
673
674 int InspIRCd::GetModuleCount()
675 {
676         return this->ModCount;
677 }
678
679 time_t InspIRCd::Time(bool delta)
680 {
681         if (delta)
682                 return TIME + time_delta;
683         return TIME;
684 }
685
686 int InspIRCd::SetTimeDelta(int delta)
687 {
688         int old = time_delta;
689         time_delta = delta;
690         this->Log(DEBUG, "Time delta set to %d (was %d)", time_delta, old);
691         return old;
692 }
693
694 void InspIRCd::AddLocalClone(userrec* user)
695 {
696         clonemap::iterator x = local_clones.find(user->GetIPString());
697         if (x != local_clones.end())
698                 x->second++;
699         else
700                 local_clones[user->GetIPString()] = 1;
701 }
702
703 void InspIRCd::AddGlobalClone(userrec* user)
704 {
705         clonemap::iterator y = global_clones.find(user->GetIPString());
706         if (y != global_clones.end())
707                 y->second++;
708         else
709                 global_clones[user->GetIPString()] = 1;
710 }
711
712 int InspIRCd::GetTimeDelta()
713 {
714         return time_delta;
715 }
716
717 void InspIRCd::SetSignal(int signal)
718 {
719         *mysig = signal;
720 }
721