source: unix.c @ 19ba56e

Last change on this file since 19ba56e was 0d2cd10, checked in by dequis <dx@…>, at 2018-03-26T16:48:32Z

Write backtrace to /var/lib/bitlbee/crash.log on SIGSEGV

Async-signal-safe code is very restricted (nothing that may call malloc
indirectly), so this code tries its best to show meaningful stuff, but
the output is still fairly raw. The contents of the log file are:

  • BITLBEE_VERSION, BITLBEE_CONFIGURE_ARGS
  • Backtrace as generated by backtrace()/backtrace_symbols_fd()
  • A small help text explaining how to get more useful symbol names
  • Memory maps (/proc/self/maps), which also mentions loaded plugins

The backtrace() function is a GNU extension, /proc/ is a linux thing.
Non-glibc platforms (such as musl) won't show anything, non-linux
platforms will skip the memory maps when /proc/self/maps fails to open.

I'd like to include timestamps, but I can't find a safe way to format
them. Even turning raw unix timestamps to strings is hard. Fun stuff.

I used the config directory because it's the only place we can be sure
we can write to. The filename is hardcoded for the same reason there are
no timestamps.

  • Property mode set to 100644
File size: 11.4 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* Main file (Unix specific part)                                       */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#include "bitlbee.h"
27
28#include "arc.h"
29#include "base64.h"
30#include "commands.h"
31#include "protocols/nogaim.h"
32#include "help.h"
33#include "ipc.h"
34#include "md5.h"
35#include "misc.h"
36#include <signal.h>
37#include <unistd.h>
38#include <sys/time.h>
39#include <sys/wait.h>
40#include <pwd.h>
41#include <locale.h>
42#include <grp.h>
43
44#if defined(OTR_BI) || defined(OTR_PI)
45#include "otr.h"
46#endif
47
48#ifdef HAVE_BACKTRACE
49#include <execinfo.h>
50#endif
51
52global_t global;        /* Against global namespace pollution */
53
54static struct {
55        int fd[2];
56        int tag;
57} shutdown_pipe = {{-1 , -1}, 0};
58
59static void sighandler_shutdown(int signal);
60static void sighandler_crash(int signal);
61
62static int crypt_main(int argc, char *argv[]);
63
64int main(int argc, char *argv[])
65{
66        int i = 0;
67        char *old_cwd = NULL;
68        struct sigaction sig, old;
69#ifdef HAVE_BACKTRACE
70        void *unused[1];
71#endif
72
73        /* Required to make iconv to ASCII//TRANSLIT work. This makes BitlBee
74           system-locale-sensitive. :-( */
75        setlocale(LC_CTYPE, "");
76
77        if (argc > 1 && strcmp(argv[1], "-x") == 0) {
78                return crypt_main(argc, argv);
79        }
80
81        log_init();
82
83        global.conf_file = g_strdup(CONF_FILE_DEF);
84        global.conf = conf_load(argc, argv);
85        if (global.conf == NULL) {
86                return(1);
87        }
88
89        if (global.conf->runmode == RUNMODE_INETD) {
90                log_link(LOGLVL_ERROR, LOGOUTPUT_IRC);
91                log_link(LOGLVL_WARNING, LOGOUTPUT_IRC);
92        } else {
93                log_link(LOGLVL_ERROR, LOGOUTPUT_CONSOLE);
94                log_link(LOGLVL_WARNING, LOGOUTPUT_CONSOLE);
95        }
96
97        b_main_init();
98
99        /* libpurple doesn't like fork()s after initializing itself, so if
100           we use it, do this init a little later (in case we're running in
101           ForkDaemon mode). */
102#ifndef WITH_PURPLE
103        nogaim_init();
104#endif
105
106#ifdef OTR_BI
107        otr_init();
108#endif
109
110        global.helpfile = g_strdup(HELP_FILE);
111        if (help_init(&global.help, global.helpfile) == NULL) {
112                log_message(LOGLVL_WARNING, "Error opening helpfile %s.", HELP_FILE);
113        }
114
115        global.storage = storage_init(global.conf->primary_storage, global.conf->migrate_storage);
116        if (global.storage == NULL) {
117                log_message(LOGLVL_ERROR, "Unable to load storage backend '%s'", global.conf->primary_storage);
118                return(1);
119        }
120
121        global.auth = auth_init(global.conf->auth_backend);
122        if (global.conf->auth_backend && global.auth == NULL) {
123                log_message(LOGLVL_ERROR, "Unable to load authentication backend '%s'", global.conf->auth_backend);
124                return(1);
125        }
126
127        if (global.conf->runmode == RUNMODE_INETD) {
128                i = bitlbee_inetd_init();
129                log_message(LOGLVL_INFO, "%s %s starting in inetd mode.", PACKAGE, BITLBEE_VERSION);
130
131        } else if (global.conf->runmode == RUNMODE_DAEMON) {
132                i = bitlbee_daemon_init();
133                log_message(LOGLVL_INFO, "%s %s starting in daemon mode.", PACKAGE, BITLBEE_VERSION);
134        } else if (global.conf->runmode == RUNMODE_FORKDAEMON) {
135                /* In case the operator requests a restart, we need this. */
136                old_cwd = g_malloc(256);
137                if (getcwd(old_cwd, 255) == NULL) {
138                        log_message(LOGLVL_WARNING, "Could not save current directory: %s", strerror(errno));
139                        g_free(old_cwd);
140                        old_cwd = NULL;
141                }
142
143                i = bitlbee_daemon_init();
144                log_message(LOGLVL_INFO, "%s %s starting in forking daemon mode.", PACKAGE, BITLBEE_VERSION);
145        }
146        if (i != 0) {
147                return(i);
148        }
149
150        if ((global.conf->user && *global.conf->user) &&
151            (global.conf->runmode == RUNMODE_DAEMON ||
152             global.conf->runmode == RUNMODE_FORKDAEMON) &&
153            (!getuid() || !geteuid())) {
154                struct passwd *pw = NULL;
155                pw = getpwnam(global.conf->user);
156                if (!pw) {
157                        log_message(LOGLVL_ERROR, "Failed to look up user %s.", global.conf->user);
158
159                } else if (initgroups(global.conf->user, pw->pw_gid) != 0) {
160                        log_message(LOGLVL_ERROR, "initgroups: %s.", strerror(errno));
161
162                } else if (setgid(pw->pw_gid) != 0) {
163                        log_message(LOGLVL_ERROR, "setgid(%d): %s.", pw->pw_gid, strerror(errno));
164
165                } else if (setuid(pw->pw_uid) != 0) {
166                        log_message(LOGLVL_ERROR, "setuid(%d): %s.", pw->pw_uid, strerror(errno));
167                }
168        }
169
170        /* Catch some signals to tell the user what's happening before quitting */
171        memset(&sig, 0, sizeof(sig));
172        sig.sa_handler = SIG_IGN;
173        sigaction(SIGCHLD, &sig, &old);
174        sigaction(SIGPIPE, &sig, &old);
175        sig.sa_flags = SA_RESETHAND;
176        sig.sa_handler = sighandler_crash;
177        sigaction(SIGSEGV, &sig, &old);
178
179        sighandler_shutdown_setup();
180
181        sig.sa_handler = sighandler_shutdown;
182        sigaction(SIGINT, &sig, &old);
183        sigaction(SIGTERM, &sig, &old);
184
185#ifdef HAVE_BACKTRACE
186        /* As per the backtrace(3) man page, call this outside of the signal
187         * handler once to ensure any dynamic libraries are loaded in an
188         * async-signal-safe environment to prevent deadlocks */
189        backtrace(unused, 1);
190#endif
191
192        if (!getuid() || !geteuid()) {
193                log_message(LOGLVL_WARNING, "BitlBee is running with root privileges. Why?");
194        }
195
196        b_main_run();
197
198        /* Mainly good for restarting, to make sure we close the help.txt fd. */
199        help_free(&global.help);
200
201        if (global.restart) {
202                char *fn = ipc_master_save_state();
203                char *env;
204
205                env = g_strdup_printf("_BITLBEE_RESTART_STATE=%s", fn);
206                putenv(env);
207                g_free(fn);
208                /* Looks like env should *not* be freed here as putenv
209                   doesn't make a copy. Odd. */
210
211                i = chdir(old_cwd);
212                close(global.listen_socket);
213
214                if (execv(argv[0], argv) == -1) {
215                        /* Apparently the execve() failed, so let's just
216                           jump back into our own/current main(). */
217                        /* Need more cleanup code to make this work. */
218                        return 1; /* main( argc, argv ); */
219                }
220        }
221
222        return(0);
223}
224
225static int crypt_main(int argc, char *argv[])
226{
227        int pass_len;
228        unsigned char *pass_cr, *pass_cl;
229
230        if (argc < 4 || (strcmp(argv[2], "hash") != 0 &&
231                         strcmp(argv[2], "unhash") != 0 && argc < 5)) {
232                printf("Supported:\n"
233                       "  %s -x enc <key> <cleartext password>\n"
234                       "  %s -x dec <key> <encrypted password>\n"
235                       "  %s -x hash <cleartext password>\n"
236                       "  %s -x unhash <hashed password>\n"
237                       "  %s -x chkhash <hashed password> <cleartext password>\n",
238                       argv[0], argv[0], argv[0], argv[0], argv[0]);
239        } else if (strcmp(argv[2], "enc") == 0) {
240                char *encoded;
241
242                pass_len = arc_encode(argv[4], strlen(argv[4]), &pass_cr, argv[3], 12);
243
244                encoded = base64_encode(pass_cr, pass_len);
245                printf("%s\n", encoded);
246                g_free(encoded);
247                g_free(pass_cr);
248        } else if (strcmp(argv[2], "dec") == 0) {
249                pass_len = base64_decode(argv[4], &pass_cr);
250                arc_decode(pass_cr, pass_len, (char **) &pass_cl, argv[3]);
251                printf("%s\n", pass_cl);
252
253                g_free(pass_cr);
254                g_free(pass_cl);
255        } else if (strcmp(argv[2], "hash") == 0) {
256                md5_byte_t pass_md5[21];
257                md5_state_t md5_state;
258                char *encoded;
259
260                random_bytes(pass_md5 + 16, 5);
261                md5_init(&md5_state);
262                md5_append(&md5_state, (md5_byte_t *) argv[3], strlen(argv[3]));
263                md5_append(&md5_state, pass_md5 + 16, 5);   /* Add the salt. */
264                md5_finish(&md5_state, pass_md5);
265
266                encoded = base64_encode(pass_md5, 21);
267                printf("%s\n", encoded);
268                g_free(encoded);
269        } else if (strcmp(argv[2], "unhash") == 0) {
270                printf("Hash %s submitted to a massive Beowulf cluster of\n"
271                       "overclocked 486s. Expect your answer next year somewhere around this time. :-)\n", argv[3]);
272        } else if (strcmp(argv[2], "chkhash") == 0) {
273                char *hash = strncmp(argv[3], "md5:", 4) == 0 ? argv[3] + 4 : argv[3];
274                int st = md5_verify_password(argv[4], hash);
275
276                printf("Hash %s given password.\n", st == 0 ? "matches" : "does not match");
277
278                return st;
279        }
280
281        return 0;
282}
283
284/* Set up a pipe for SIGTERM/SIGINT so the actual signal handler doesn't do anything unsafe */
285void sighandler_shutdown_setup()
286{
287        if (shutdown_pipe.fd[0] != -1) {
288                /* called again from a forked process, clean up to avoid propagating the signal */
289                b_event_remove(shutdown_pipe.tag);
290                close(shutdown_pipe.fd[0]);
291                close(shutdown_pipe.fd[1]);
292        }
293
294        if (pipe(shutdown_pipe.fd) == 0) {
295                shutdown_pipe.tag = b_input_add(shutdown_pipe.fd[0], B_EV_IO_READ, bitlbee_shutdown, NULL);
296        }
297}
298
299/* Signal handler for SIGTERM and SIGINT */
300static void sighandler_shutdown(int signal)
301{
302        int unused G_GNUC_UNUSED;
303        /* Write a single null byte to the pipe, just to send a message to the main loop.
304         * This gets handled by bitlbee_shutdown (the b_input_add callback for this pipe) */
305        unused = write(shutdown_pipe.fd[1], "", 1);
306}
307
308#ifdef HAVE_BACKTRACE
309/* Writes a backtrace to (usually) /var/lib/bitlbee/crash.log
310 * No malloc allowed means not a lot can be written to that file */
311static void sighandler_crash_backtrace()
312{
313        int fd, mapsfd;
314        int size;
315        void *trace[128];
316        const char message[] = "## " PACKAGE " crashed\n"
317                "## Version: " BITLBEE_VERSION "\n"
318                "## Configure args: " BITLBEE_CONFIGURE_ARGS "\n"
319                "##\n"
320                "## Backtrace:\n\n";
321        const char message2[] = "\n"
322                "## Hint: To get details on addresses use\n"
323                "##   addr2line -e <binary> <address>\n"
324                "## or\n"
325                "##   gdb <binary> -ex 'l *<address>' -ex q\n"
326                "## where <binary> is a filename from above and <address> is the part between (...)\n"
327                "##\n\n";
328        const char message3[] = "\n## End of memory maps. See above for the backtrace\n\n";
329
330        fd = open(CRASHFILE, O_WRONLY | O_APPEND | O_CREAT, 0600);
331
332        if (fd == -1 || write(fd, message, sizeof(message) - 1) == -1) {
333                return;
334        }
335
336        size = backtrace(trace, 128);
337        backtrace_symbols_fd(trace, size, fd);
338
339        (void) write(fd, message2, sizeof(message2) - 1);
340
341        /* a bit too linux-specific, so fail gracefully */
342        mapsfd = open("/proc/self/maps", O_RDONLY, 0);
343
344        if (mapsfd != -1) {
345                char buf[4096] = {0};
346                ssize_t bytes;
347
348                while ((bytes = read(mapsfd, buf, sizeof(buf))) > 0) {
349                        (void) write(fd, buf, bytes);
350                }
351                (void) close(mapsfd);
352                (void) write(fd, message3, sizeof(message3) - 1);
353        }
354
355        (void) close(fd);
356}
357#endif
358
359/* Signal handler for SIGSEGV
360 * A desperate attempt to tell the user that everything is wrong in the world.
361 * Avoids using irc_abort() because it has several unsafe calls to malloc */
362static void sighandler_crash(int signal)
363{
364        GSList *l;
365        const char message[] = "ERROR :BitlBee crashed! (SIGSEGV received)\r\n"
366#ifdef HAVE_BACKTRACE
367                "ERROR :Writing backtrace to " CRASHFILE "\r\n"
368#endif
369                "ERROR :This is a bug either in BitlBee or a plugin, ask us on IRC if unsure\r\n";
370
371        for (l = irc_connection_list; l; l = l->next) {
372                irc_t *irc = l->data;
373                sock_make_blocking(irc->fd);
374                if (irc->sendbuffer) {
375                        (void) write(irc->fd, irc->sendbuffer, strlen(irc->sendbuffer));
376                }
377                (void) write(irc->fd, message, sizeof(message) - 1);
378        }
379
380#ifdef HAVE_BACKTRACE
381        sighandler_crash_backtrace();
382#endif
383
384        raise(signal);
385}
386
387double gettime()
388{
389        struct timeval time[1];
390
391        gettimeofday(time, 0);
392        return((double) time->tv_sec + (double) time->tv_usec / 1000000);
393}
Note: See TracBrowser for help on using the repository browser.