1 #include <xmpp/biboumi_component.hpp>
2 #include <utils/timed_events.hpp>
3 #include <network/poller.hpp>
4 #include <config/config.hpp>
5 #include <logger/logger.hpp>
6 #include <utils/xdg.hpp>
7 #include <utils/reload.hpp>
8 
9 #ifdef UDNS_FOUND
10 # include <network/dns_handler.hpp>
11 #endif
12 
13 #include <atomic>
14 #include <csignal>
15 
16 #include <identd/identd_server.hpp>
17 
18 // A flag set by the SIGINT signal handler.
19 static std::atomic<bool> stop(false);
20 // Flag set by the SIGUSR1/2 signal handler.
21 static std::atomic<bool> reload(false);
22 // A flag indicating that we are wanting to exit the process. i.e: if this
23 // flag is set and all connections are closed, we can exit properly.
24 static bool exiting = false;
25 
26 /**
27  * Provide an helpful message to help the user write a minimal working
28  * configuration file.
29  */
config_help(const std::string & missing_option)30 int config_help(const std::string& missing_option)
31 {
32   if (!missing_option.empty())
33     log_error("Configuration error: empty value for option ", missing_option, ".");
34   log_error("Please provide a configuration file filled like this:\n\n"
35             "hostname=irc.example.com\npassword=S3CR3T");
36   return 1;
37 }
38 
display_help()39 int display_help()
40 {
41   std::cout << "Usage: biboumi [configuration_file]" << std::endl;
42   return 0;
43 }
44 
sigint_handler(int sig,siginfo_t *,void *)45 static void sigint_handler(int sig, siginfo_t*, void*)
46 {
47   // In 2 seconds, repeat the same signal, to force the exit
48   TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 2s,
49                                     [sig]() { raise(sig); }));
50   stop.store(true);
51 }
52 
sigusr_handler(int,siginfo_t *,void *)53 static void sigusr_handler(int, siginfo_t*, void*)
54 {
55   reload.store(true);
56 }
57 
setup_signals()58 static void setup_signals()
59 {
60   // Block the signals we want to manage. They will be unblocked only during
61   // the epoll_pwait or ppoll calls. This avoids some race conditions,
62   // explained in man 2 pselect on linux
63   sigset_t mask{};
64   sigemptyset(&mask);
65   sigaddset(&mask, SIGINT);
66   sigaddset(&mask, SIGTERM);
67   sigaddset(&mask, SIGUSR1);
68   sigaddset(&mask, SIGUSR2);
69   sigaddset(&mask, SIGHUP);
70   sigprocmask(SIG_BLOCK, &mask, nullptr);
71 
72   // Install the signals used to exit the process cleanly, or reload the
73   // config
74   struct sigaction on_sigint;
75   on_sigint.sa_sigaction = &sigint_handler;
76   // All signals must be blocked while a signal handler is running
77   sigfillset(&on_sigint.sa_mask);
78   // we want to catch that signal only once.
79   // Sending SIGINT again will "force" an exit
80   on_sigint.sa_flags = 0 & SA_RESETHAND;
81   sigaction(SIGINT, &on_sigint, nullptr);
82   sigaction(SIGTERM, &on_sigint, nullptr);
83 
84   // Install a signal to reload the config on SIGUSR1/2
85   struct sigaction on_sigusr;
86   on_sigusr.sa_sigaction = &sigusr_handler;
87   sigfillset(&on_sigusr.sa_mask);
88   on_sigusr.sa_flags = 0;
89   sigaction(SIGUSR1, &on_sigusr, nullptr);
90   sigaction(SIGUSR2, &on_sigusr, nullptr);
91   sigaction(SIGHUP, &on_sigusr, nullptr);
92 }
93 
main_loop(std::string hostname,std::string password)94 static int main_loop(std::string hostname, std::string password)
95 {
96   auto p = std::make_shared<Poller>();
97 
98 #ifdef UDNS_FOUND
99   DNSHandler dns_handler(p);
100 #endif
101 
102   auto xmpp_component =
103     std::make_shared<BiboumiComponent>(p, hostname, password);
104   xmpp_component->start();
105 
106   std::unique_ptr<IdentdServer> identd;
107   if (Config::get_int("identd_port", 113) != 0)
108     identd = std::make_unique<IdentdServer>(*xmpp_component, p, static_cast<uint16_t>(Config::get_int("identd_port", 113)));
109 
110   auto timeout = TimedEventsManager::instance().get_timeout();
111   while (p->poll(timeout) != -1)
112   {
113     TimedEventsManager::instance().execute_expired_events();
114     // Check for empty irc_clients (not connected, or with no joined
115     // channel) and remove them
116     xmpp_component->clean();
117     if (identd)
118       identd->clean();
119     if (stop)
120     {
121       log_info("Signal received, exiting...");
122 #ifdef SYSTEMD_FOUND
123       sd_notify(0, "STOPPING=1");
124 #endif
125       exiting = true;
126       stop.store(false);
127       xmpp_component->shutdown();
128 #ifdef UDNS_FOUND
129       dns_handler.destroy();
130 #endif
131       if (identd)
132         identd->shutdown();
133       // Cancel the timer for a potential reconnection
134       TimedEventsManager::instance().cancel("XMPP reconnection");
135     }
136     if (reload)
137     {
138       log_info("Signal received, reloading the config...");
139       ::reload_process();
140       reload.store(false);
141     }
142     // Reconnect to the XMPP server if this was not intended.  This may have
143     // happened because we sent something invalid to it and it decided to
144     // close the connection.  This is a bug that should be fixed, but we
145     // still reconnect automatically instead of dropping everything
146     if (!exiting &&
147         !xmpp_component->is_connected() &&
148         !xmpp_component->is_connecting())
149     {
150       if (xmpp_component->ever_auth)
151         {
152           static const std::string reconnect_name{"XMPP reconnection"};
153           if (xmpp_component->first_connection_try == true)
154             { // immediately re-try to connect
155               xmpp_component->reset();
156               xmpp_component->start();
157             }
158           else if (!TimedEventsManager::instance().find_event(reconnect_name))
159             { // Re-connecting failed, we now try only each few seconds
160               auto reconnect_later = [xmpp_component]()
161               {
162                 xmpp_component->reset();
163                 xmpp_component->start();
164               };
165               TimedEvent event(std::chrono::steady_clock::now() + 2s, reconnect_later, reconnect_name);
166               TimedEventsManager::instance().add_event(std::move(event));
167             }
168         }
169       else
170         {
171 #ifdef UDNS_FOUND
172           dns_handler.destroy();
173 #endif
174           if (identd)
175             identd->shutdown();
176         }
177     }
178     // If the only existing connection is the one to the XMPP component:
179     // close the XMPP stream.
180     if (exiting && xmpp_component->is_connecting())
181       xmpp_component->close();
182     if (exiting && p->size() == 1 && xmpp_component->is_document_open())
183       xmpp_component->close_document();
184     if (exiting) // If we are exiting, do not wait for any timed event
185       timeout = utils::no_timeout;
186     else
187       timeout = TimedEventsManager::instance().get_timeout();
188   }
189   if (!xmpp_component->ever_auth)
190     return 1; // To signal that the process did not properly start
191   log_info("All connections cleanly closed, have a nice day.");
192   return 0;
193 }
194 
main(int ac,char ** av)195 int main(int ac, char** av)
196 {
197   if (ac > 1)
198     {
199       const std::string arg = av[1];
200       if (arg.size() >= 2 && arg[0] == '-' && arg[1] == '-')
201         {
202           if (arg == "--help")
203             return display_help();
204           else
205             {
206               std::cerr << "Unknow command line option: " << arg
207                         << std::endl;
208               return 1;
209             }
210         }
211     }
212   const std::string conf_filename =
213       ac > 1 ? av[1]: xdg_config_path("biboumi.cfg");
214   std::cout << "Using configuration file: " << conf_filename << std::endl;
215 
216   if (!Config::read_conf(conf_filename))
217     return config_help("");
218 
219   const std::string password = Config::get("password", "");
220   if (password.empty())
221     return config_help("password");
222   const std::string hostname = Config::get("hostname", "");
223   if (hostname.empty())
224     return config_help("hostname");
225 
226 #ifdef USE_DATABASE
227   try
228     {
229       open_database();
230     }
231   catch (const std::exception& e)
232     {
233       log_error(e.what());
234       return 1;
235     }
236 #endif
237 
238   setup_signals();
239 
240   return main_loop(std::move(hostname), std::move(password));
241 }
242