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