1 |
/* IRC Services -- main source file. |
2 |
* Copyright (c) 1996-2009 Andrew Church <achurch@achurch.org> |
3 |
* Parts written by Andrew Kempe and others. |
4 |
* |
5 |
* This program is free software; you can redistribute it and/or modify |
6 |
* it under the terms of the GNU General Public License as published by |
7 |
* the Free Software Foundation, either version 2 of the License, or |
8 |
* (at your option) any later version. |
9 |
* |
10 |
* This program is distributed in the hope that it will be useful, |
11 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 |
* GNU General Public License for more details. |
14 |
* |
15 |
* You should have received a copy of the GNU General Public License |
16 |
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
17 |
*/ |
18 |
|
19 |
/*************************************************************************/ |
20 |
|
21 |
#include "services.h" |
22 |
#include "databases.h" |
23 |
#include "modules.h" |
24 |
#include "timeout.h" |
25 |
#include <fcntl.h> |
26 |
#include <setjmp.h> |
27 |
|
28 |
|
29 |
/* Hack for sigsetjmp(); since (at least with glibc, and it shouldn't hurt |
30 |
* anywhere else) sigsetjmp() only works if you don't leave the stack frame |
31 |
* it was called from, we have to call it before calling the signals.c |
32 |
* wrapper. */ |
33 |
|
34 |
#define DO_SIGSETJMP() do { \ |
35 |
static sigjmp_buf buf; \ |
36 |
if (!sigsetjmp(buf, 1)) \ |
37 |
do_sigsetjmp(&buf); \ |
38 |
} while (0) |
39 |
|
40 |
|
41 |
/******** Global variables! ********/ |
42 |
|
43 |
/* Command-line options: (note that configuration variables are in init.c) */ |
44 |
const char *services_dir = SERVICES_DIR;/* -dir=dirname */ |
45 |
int debug = 0; /* -debug */ |
46 |
int readonly = 0; /* -readonly */ |
47 |
int nofork = 0; /* -nofork */ |
48 |
int noexpire = 0; /* -noexpire */ |
49 |
int noakill = 0; /* -noakill */ |
50 |
int forceload = 0; /* -forceload */ |
51 |
int encrypt_all = 0; /* -encrypt-all */ |
52 |
|
53 |
|
54 |
/* Set to 1 while we are linked to the network */ |
55 |
int linked = 0; |
56 |
|
57 |
/* Set to 1 if we are to quit */ |
58 |
int quitting = 0; |
59 |
|
60 |
/* Set to 1 if we are to quit after saving databases */ |
61 |
int delayed_quit = 0; |
62 |
|
63 |
/* Set to 1 if we are to restart */ |
64 |
int restart = 0; |
65 |
|
66 |
/* Contains a message as to why services is terminating */ |
67 |
char quitmsg[BUFSIZE] = ""; |
68 |
|
69 |
/* Input buffer - global, so we can dump it if something goes wrong */ |
70 |
char inbuf[BUFSIZE]; |
71 |
|
72 |
/* Socket for talking to server */ |
73 |
Socket *servsock = NULL; |
74 |
|
75 |
/* Should we update the databases now? */ |
76 |
int save_data = 0; |
77 |
|
78 |
/* At what time were we started? */ |
79 |
time_t start_time; |
80 |
|
81 |
/* Were we unable to open the log? (and the error that occurred) */ |
82 |
int openlog_failed, openlog_errno; |
83 |
|
84 |
/* Module callbacks (global so init.c can set them): */ |
85 |
int cb_connect = -1; |
86 |
int cb_save_complete = -1; |
87 |
|
88 |
|
89 |
/*************************************************************************/ |
90 |
/*************************************************************************/ |
91 |
|
92 |
/* Callbacks for uplink IRC server socket. */ |
93 |
|
94 |
/*************************************************************************/ |
95 |
|
96 |
/* Actions to perform when connection to server completes. */ |
97 |
|
98 |
void connect_callback(Socket *s, void *param_unused) |
99 |
{ |
100 |
sock_set_blocking(s, 1); |
101 |
sock_setcb(s, SCB_READLINE, readfirstline_callback); |
102 |
send_server(); |
103 |
} |
104 |
|
105 |
/*************************************************************************/ |
106 |
|
107 |
/* Actions to perform when connection to server is broken. */ |
108 |
|
109 |
void disconnect_callback(Socket *s, void *param) |
110 |
{ |
111 |
/* We are no longer linked */ |
112 |
linked = 0; |
113 |
|
114 |
if (param == DISCONN_REMOTE || param == DISCONN_CONNFAIL) { |
115 |
int errno_save = errno; |
116 |
const char *msg = (param==DISCONN_REMOTE ? "Read error from server" |
117 |
: "Connection to server failed"); |
118 |
snprintf(quitmsg, sizeof(quitmsg), |
119 |
"%s: %s", msg, strerror(errno_save)); |
120 |
if (param == DISCONN_REMOTE) { |
121 |
/* If we were already connected, make sure any changed data is |
122 |
* updated before we terminate. */ |
123 |
delayed_quit = 1; |
124 |
save_data = 1; |
125 |
} else { |
126 |
/* The connection was never made in the first place, so we |
127 |
* discard any changes (such as expirations) made on the |
128 |
* assumption that either a configuration problem or other |
129 |
* external problem exists. Such changes will be saved the |
130 |
* next time Services successfully connects to a server. */ |
131 |
quitting = 1; |
132 |
} |
133 |
} |
134 |
sock_setcb(s, SCB_READLINE, NULL); |
135 |
} |
136 |
|
137 |
/*************************************************************************/ |
138 |
|
139 |
/* Actions to perform when first line is read from socket. */ |
140 |
|
141 |
void readfirstline_callback(Socket *s, void *param_unused) |
142 |
{ |
143 |
sock_setcb(s, SCB_READLINE, readline_callback); |
144 |
|
145 |
if (!sgets2(inbuf, sizeof(inbuf), s)) { |
146 |
/* This shouldn't happen, but just in case... */ |
147 |
disconn(s); |
148 |
fatal("Unable to read greeting from server socket"); |
149 |
} |
150 |
if (strnicmp(inbuf, "ERROR", 5) == 0) { |
151 |
/* Close server socket first to stop wallops, since the other |
152 |
* server doesn't want to listen to us anyway */ |
153 |
disconn(s); |
154 |
fatal("Remote server returned: %s", inbuf); |
155 |
} |
156 |
|
157 |
/* We're now linked to the network */ |
158 |
linked = 1; |
159 |
|
160 |
/* Announce a logfile error if there was one */ |
161 |
if (openlog_failed) { |
162 |
wallops(NULL, "Warning: couldn't open logfile: %s", |
163 |
strerror(openlog_errno)); |
164 |
openlog_failed = 0; |
165 |
} |
166 |
|
167 |
/* Bring in our pseudo-clients */ |
168 |
introduce_user(NULL); |
169 |
|
170 |
/* Let modules do their startup stuff */ |
171 |
call_callback(cb_connect); |
172 |
|
173 |
/* Process the line we read in above */ |
174 |
process(); |
175 |
} |
176 |
|
177 |
/*************************************************************************/ |
178 |
|
179 |
/* Actions to perform when subsequent lines are read from socket. */ |
180 |
|
181 |
void readline_callback(Socket *s, void *param_unused) |
182 |
{ |
183 |
if (sgets2(inbuf, sizeof(inbuf), s)) |
184 |
process(); |
185 |
} |
186 |
|
187 |
/*************************************************************************/ |
188 |
/*************************************************************************/ |
189 |
|
190 |
/* Lock the data directory if possible; return nonzero on success, zero on |
191 |
* failure (data directory already locked or cannot create lock file). |
192 |
* On failure, errno will be EEXIST if the directory was already locked or |
193 |
* a value other than EEXIST if an error occurred creating the lock file. |
194 |
* |
195 |
* This does not attempt to correct for NFS brokenness w.r.t. O_EXCL and |
196 |
* will contain a race condition when used on an NFS filesystem (or any |
197 |
* other filesystem which does not support O_EXCL properly). |
198 |
*/ |
199 |
|
200 |
int lock_data(void) |
201 |
{ |
202 |
int fd; |
203 |
|
204 |
errno = 0; |
205 |
fd = open(LockFilename, O_WRONLY | O_CREAT | O_EXCL, 0); |
206 |
if (fd >= 0) { |
207 |
close(fd); |
208 |
return 1; |
209 |
} |
210 |
return 0; |
211 |
} |
212 |
|
213 |
/*************************************************************************/ |
214 |
|
215 |
/* Check whether the data directory is locked without actually attempting |
216 |
* to lock it. Returns 1 if locked, 0 if not, or -1 if an error occurred |
217 |
* while trying to check (in which case errno will be set to an appropriate |
218 |
* value, i.e. whatever access() returned). |
219 |
*/ |
220 |
|
221 |
int is_data_locked(void) |
222 |
{ |
223 |
errno = 0; |
224 |
if (access(LockFilename, F_OK) == 0) |
225 |
return 1; |
226 |
if (errno == ENOENT) |
227 |
return 0; |
228 |
return -1; |
229 |
} |
230 |
|
231 |
/*************************************************************************/ |
232 |
|
233 |
/* Unlock the data directory. Assumes we locked it in the first place. |
234 |
* Returns 1 on success, 0 on failure (unable to remove the lock file), or |
235 |
* -1 if the lock file didn't exist in the first place (possibly because it |
236 |
* was removed by another (misbehaving) program). |
237 |
*/ |
238 |
|
239 |
int unlock_data(void) |
240 |
{ |
241 |
errno = 0; |
242 |
if (unlink(LockFilename) == 0) |
243 |
return 1; |
244 |
if (errno == ENOENT) |
245 |
return -1; |
246 |
return 0; |
247 |
} |
248 |
|
249 |
/*************************************************************************/ |
250 |
|
251 |
/* Subroutine to save databases. */ |
252 |
|
253 |
void save_data_now(void) |
254 |
{ |
255 |
if (!lock_data()) { |
256 |
if (errno == EEXIST) { |
257 |
log("warning: databases are locked, not updating"); |
258 |
wallops(NULL, |
259 |
"\2Warning:\2 Databases are locked, and cannot be updated." |
260 |
" Remove the `%s%s%s' file to allow database updates.", |
261 |
*LockFilename=='/' ? "" : services_dir, |
262 |
*LockFilename=='/' ? "" : "/", LockFilename); |
263 |
} else { |
264 |
log_perror("warning: unable to lock databases, not updating"); |
265 |
wallops(NULL, "\2Warning:\2 Unable to lock databases; databases" |
266 |
" will not be updated."); |
267 |
} |
268 |
call_callback_1(cb_save_complete, 0); |
269 |
} else { |
270 |
log_debug(1, "Saving databases"); |
271 |
save_all_dbtables(); |
272 |
if (!unlock_data()) { |
273 |
log_perror("warning: unable to unlock databases"); |
274 |
wallops(NULL, |
275 |
"\2Warning:\2 Unable to unlock databases; future database" |
276 |
" updates may fail until the `%s%s%s' file is removed.", |
277 |
*LockFilename=='/' ? "" : services_dir, |
278 |
*LockFilename=='/' ? "" : "/", LockFilename); |
279 |
} |
280 |
call_callback_1(cb_save_complete, 1); |
281 |
} |
282 |
} |
283 |
|
284 |
/*************************************************************************/ |
285 |
/*************************************************************************/ |
286 |
|
287 |
/* Main routine. (What does it look like? :-) ) */ |
288 |
|
289 |
int main(int ac, char **av, char **envp) |
290 |
{ |
291 |
volatile time_t last_update; /* When did we last update the databases? */ |
292 |
volatile uint32 last_check; /* When did we last check timeouts? */ |
293 |
|
294 |
|
295 |
/*** Initialization stuff. ***/ |
296 |
|
297 |
if (init(ac, av) < 0) { |
298 |
fprintf(stderr, "Initialization failed, exiting.\n"); |
299 |
return 1; |
300 |
} |
301 |
|
302 |
|
303 |
/* Set up timers. */ |
304 |
last_send = time(NULL); |
305 |
last_update = time(NULL); |
306 |
last_check = time(NULL); |
307 |
|
308 |
/* The signal handler routine will drop back here with quitting != 0 |
309 |
* if it gets called. */ |
310 |
DO_SIGSETJMP(); |
311 |
|
312 |
|
313 |
/*** Main loop. ***/ |
314 |
|
315 |
while (!quitting) { |
316 |
time_t now = time(NULL); |
317 |
int32 now_msec = time_msec(); |
318 |
|
319 |
log_debug(2, "Top of main loop"); |
320 |
|
321 |
if (!readonly && (save_data || now-last_update >= UpdateTimeout)) { |
322 |
save_data_now(); |
323 |
save_data = 0; |
324 |
last_update = now; |
325 |
} |
326 |
if (delayed_quit) |
327 |
break; |
328 |
|
329 |
if (sock_isconn(servsock)) { |
330 |
if (PingFrequency && now - last_send >= PingFrequency) |
331 |
send_cmd(NULL, "PING :%s", ServerName); |
332 |
} |
333 |
|
334 |
if (now_msec - last_check >= TimeoutCheck) { |
335 |
check_timeouts(); |
336 |
last_check = now_msec; |
337 |
} |
338 |
|
339 |
check_sockets(); |
340 |
|
341 |
if (!MergeChannelModes) |
342 |
set_cmode(NULL, NULL); /* flush out any mode changes made */ |
343 |
} |
344 |
|
345 |
|
346 |
/*** Cleanup stuff. ***/ |
347 |
|
348 |
cleanup(); |
349 |
|
350 |
/* Check for restart instead of exit */ |
351 |
if (restart) { |
352 |
execve(SERVICES_BIN, av, envp); |
353 |
/* If we get here, the exec() failed; override readonly and write a |
354 |
* note to the log file */ |
355 |
{ |
356 |
int errno_save = errno; |
357 |
open_log(); |
358 |
errno = errno_save; |
359 |
} |
360 |
log_perror("Restart failed"); |
361 |
close_log(); |
362 |
return 1; |
363 |
} |
364 |
|
365 |
/* Terminate program */ |
366 |
return 0; |
367 |
} |
368 |
|
369 |
/*************************************************************************/ |
370 |
|
371 |
/* |
372 |
* Local variables: |
373 |
* c-file-style: "stroustrup" |
374 |
* c-file-offsets: ((case-label . *) (statement-case-intro . *)) |
375 |
* indent-tabs-mode: nil |
376 |
* End: |
377 |
* |
378 |
* vim: expandtab shiftwidth=4: |
379 |
*/ |