/********************************************************************\ * BitlBee -- An IRC to other IM-networks gateway * * * * Copyright 2002-2012 Wilmer van der Gaast and others * \********************************************************************/ /* IPC - communication between BitlBee processes */ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License with the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA */ #define BITLBEE_CORE #include "bitlbee.h" #include "ipc.h" #include "commands.h" #include #include GSList *child_list = NULL; static int ipc_child_recv_fd = -1; static void ipc_master_takeover_fail(struct bitlbee_child *child, gboolean both); static gboolean ipc_send_fd(int fd, int send_fd); /* On Solaris and possibly other systems passing FDs between processes is * not possible (or at least not using the method used in this file. * Just disable that code, the functionality is not that important. */ #if defined(NO_FD_PASSING) && !defined(CMSG_SPACE) #define CMSG_SPACE(len) 1 #endif static void ipc_master_cmd_client(irc_t *data, char **cmd) { /* Normally data points at an irc_t block, but for the IPC master this is different. We think this scary cast is better than creating a new command_t structure, just to make the compiler happy. */ struct bitlbee_child *child = (void *) data; if (child && cmd[1]) { child->host = g_strdup(cmd[1]); child->nick = g_strdup(cmd[2]); child->realname = g_strdup(cmd[3]); } /* CLIENT == On initial connects, HELLO is after /RESTARTs. */ if (g_strcasecmp(cmd[0], "CLIENT") == 0) { ipc_to_children_str("OPERMSG :Client connecting (PID=%d): %s@%s (%s)\r\n", (int) (child ? child->pid : -1), cmd[2], cmd[1], cmd[3]); } } static void ipc_master_cmd_nick(irc_t *data, char **cmd) { struct bitlbee_child *child = (void *) data; if (child && cmd[1]) { g_free(child->nick); child->nick = g_strdup(cmd[1]); } } static void ipc_master_cmd_die(irc_t *data, char **cmd) { if (global.conf->runmode == RUNMODE_FORKDAEMON) { ipc_to_children_str("DIE\r\n"); } bitlbee_shutdown(NULL, -1, 0); } static void ipc_master_cmd_deaf(irc_t *data, char **cmd) { if (global.conf->runmode == RUNMODE_DAEMON) { b_event_remove(global.listen_watch_source_id); close(global.listen_socket); global.listen_socket = global.listen_watch_source_id = -1; ipc_to_children_str("OPERMSG :Closed listening socket, waiting " "for all users to disconnect."); } else { ipc_to_children_str("OPERMSG :The DEAF command only works in " "normal daemon mode. Try DIE instead."); } } void ipc_master_cmd_rehash(irc_t *data, char **cmd) { runmode_t oldmode; oldmode = global.conf->runmode; g_free(global.conf); global.conf = conf_load(0, NULL); if (global.conf->runmode != oldmode) { log_message(LOGLVL_WARNING, "Can't change RunMode setting at runtime, restoring original setting"); global.conf->runmode = oldmode; } if (global.conf->runmode == RUNMODE_FORKDAEMON) { ipc_to_children(cmd); } } void ipc_master_cmd_restart(irc_t *data, char **cmd) { if (global.conf->runmode != RUNMODE_FORKDAEMON) { /* Tell child that this is unsupported. */ return; } global.restart = -1; bitlbee_shutdown(NULL, -1, 0); } void ipc_master_cmd_identify(irc_t *data, char **cmd) { struct bitlbee_child *child = (void *) data, *old = NULL; char *resp; GSList *l; if (!child || !child->nick || strcmp(child->nick, cmd[1]) != 0) { return; } g_free(child->password); child->password = g_strdup(cmd[2]); for (l = child_list; l; l = l->next) { old = l->data; if (child != old && old->nick && nick_cmp(NULL, old->nick, child->nick) == 0 && old->password && strcmp(old->password, child->password) == 0) { break; } } if (l && !child->to_child && !old->to_child) { resp = "TAKEOVER INIT\r\n"; child->to_child = old; old->to_child = child; } else { /* Won't need the fd since we can't send it anywhere. */ closesocket(child->to_fd); child->to_fd = -1; resp = "TAKEOVER NO\r\n"; } if (write(child->ipc_fd, resp, strlen(resp)) != strlen(resp)) { ipc_master_free_one(child); } } void ipc_master_cmd_takeover(irc_t *data, char **cmd) { struct bitlbee_child *child = (void *) data; char *fwd = NULL; /* Normal daemon mode doesn't keep these and has simplified code for takeovers. */ if (child == NULL) { return; } if (child->to_child == NULL || g_slist_find(child_list, child->to_child) == NULL) { return ipc_master_takeover_fail(child, FALSE); } if (strcmp(cmd[1], "AUTH") == 0) { /* New connection -> Master */ if (child->to_child && child->nick && child->to_child->nick && cmd[2] && child->password && child->to_child->password && cmd[3] && strcmp(child->nick, child->to_child->nick) == 0 && strcmp(child->nick, cmd[2]) == 0 && strcmp(child->password, child->to_child->password) == 0 && strcmp(child->password, cmd[3]) == 0) { ipc_send_fd(child->to_child->ipc_fd, child->to_fd); fwd = irc_build_line(cmd); if (write(child->to_child->ipc_fd, fwd, strlen(fwd)) != strlen(fwd)) { ipc_master_free_one(child); } g_free(fwd); } else { return ipc_master_takeover_fail(child, TRUE); } } else if (strcmp(cmd[1], "DONE") == 0 || strcmp(cmd[1], "FAIL") == 0) { /* Old connection -> Master */ int fd; /* The copy was successful (or not), we don't need it anymore. */ closesocket(child->to_fd); child->to_fd = -1; /* Pass it through to the other party, and flush all state. */ fwd = irc_build_line(cmd); fd = child->to_child->ipc_fd; child->to_child->to_child = NULL; child->to_child = NULL; if (write(fd, fwd, strlen(fwd)) != strlen(fwd)) { ipc_master_free_one(child); } g_free(fwd); } } static const command_t ipc_master_commands[] = { { "client", 3, ipc_master_cmd_client, 0 }, { "hello", 0, ipc_master_cmd_client, 0 }, { "nick", 1, ipc_master_cmd_nick, 0 }, { "die", 0, ipc_master_cmd_die, 0 }, { "deaf", 0, ipc_master_cmd_deaf, 0 }, { "wallops", 1, NULL, IPC_CMD_TO_CHILDREN }, { "wall", 1, NULL, IPC_CMD_TO_CHILDREN }, { "opermsg", 1, NULL, IPC_CMD_TO_CHILDREN }, { "rehash", 0, ipc_master_cmd_rehash, 0 }, { "kill", 2, NULL, IPC_CMD_TO_CHILDREN }, { "restart", 0, ipc_master_cmd_restart, 0 }, { "identify", 2, ipc_master_cmd_identify, 0 }, { "takeover", 1, ipc_master_cmd_takeover, 0 }, { NULL } }; static void ipc_child_cmd_die(irc_t *irc, char **cmd) { irc_abort(irc, 0, "Shutdown requested by operator"); } static void ipc_child_cmd_wallops(irc_t *irc, char **cmd) { if (!(irc->status & USTATUS_LOGGED_IN)) { return; } if (strchr(irc->umode, 'w')) { irc_write(irc, ":%s WALLOPS :%s", irc->root->host, cmd[1]); } } static void ipc_child_cmd_wall(irc_t *irc, char **cmd) { if (!(irc->status & USTATUS_LOGGED_IN)) { return; } if (strchr(irc->umode, 's')) { irc_write(irc, ":%s NOTICE %s :%s", irc->root->host, irc->user->nick, cmd[1]); } } static void ipc_child_cmd_opermsg(irc_t *irc, char **cmd) { if (!(irc->status & USTATUS_LOGGED_IN)) { return; } if (strchr(irc->umode, 'o')) { irc_write(irc, ":%s NOTICE %s :*** OperMsg *** %s", irc->root->host, irc->user->nick, cmd[1]); } } static void ipc_child_cmd_rehash(irc_t *irc, char **cmd) { runmode_t oldmode; oldmode = global.conf->runmode; g_free(global.conf); global.conf = conf_load(0, NULL); global.conf->runmode = oldmode; } static void ipc_child_cmd_kill(irc_t *irc, char **cmd) { if (!(irc->status & USTATUS_LOGGED_IN)) { return; } if (nick_cmp(NULL, cmd[1], irc->user->nick) != 0) { return; /* It's not for us. */ } irc_write(irc, ":%s!%s@%s KILL %s :%s", irc->root->nick, irc->root->nick, irc->root->host, irc->user->nick, cmd[2]); irc_abort(irc, 0, "Killed by operator: %s", cmd[2]); } static void ipc_child_cmd_hello(irc_t *irc, char **cmd) { if (!(irc->status & USTATUS_LOGGED_IN)) { ipc_to_master_str("HELLO\r\n"); } else { ipc_to_master_str("HELLO %s %s :%s\r\n", irc->user->host, irc->user->nick, irc->user->fullname); } } static void ipc_child_cmd_takeover_yes(void *data); static void ipc_child_cmd_takeover_no(void *data); static void ipc_child_cmd_takeover(irc_t *irc, char **cmd) { if (strcmp(cmd[1], "NO") == 0) { /* Master->New connection */ /* No takeover, finish the login. */ } else if (strcmp(cmd[1], "INIT") == 0) { /* Master->New connection */ if (!set_getbool(&irc->b->set, "allow_takeover")) { ipc_child_cmd_takeover_no(irc); return; } /* Offer to take over the old session, unless for some reason we're already logging into IM connections. */ if (irc->login_source_id != -1) { query_add(irc, NULL, "You're already connected to this server. " "Would you like to take over this session?", ipc_child_cmd_takeover_yes, ipc_child_cmd_takeover_no, NULL, irc); } /* This one's going to connect to accounts, avoid that. */ b_event_remove(irc->login_source_id); irc->login_source_id = -1; } else if (strcmp(cmd[1], "AUTH") == 0) { /* Master->Old connection */ if (irc->password && cmd[2] && cmd[3] && ipc_child_recv_fd != -1 && strcmp(irc->user->nick, cmd[2]) == 0 && strcmp(irc->password, cmd[3]) == 0 && set_getbool(&irc->b->set, "allow_takeover")) { irc_switch_fd(irc, ipc_child_recv_fd); irc_sync(irc); irc_rootmsg(irc, "You've successfully taken over your old session"); ipc_child_recv_fd = -1; ipc_to_master_str("TAKEOVER DONE\r\n"); } else { ipc_to_master_str("TAKEOVER FAIL\r\n"); } } else if (strcmp(cmd[1], "DONE") == 0) { /* Master->New connection (now taken over by old process) */ irc_free(irc); } else if (strcmp(cmd[1], "FAIL") == 0) { /* Master->New connection */ irc_rootmsg(irc, "Could not take over old session"); } } static void ipc_child_cmd_takeover_yes(void *data) { irc_t *irc = data, *old = NULL; char *to_auth[] = { "TAKEOVER", "AUTH", irc->user->nick, irc->password, NULL }; /* Master->New connection */ ipc_to_master_str("TAKEOVER AUTH %s :%s\r\n", irc->user->nick, irc->password); if (global.conf->runmode == RUNMODE_DAEMON) { GSList *l; for (l = irc_connection_list; l; l = l->next) { old = l->data; if (irc != old && irc->user->nick && old->user->nick && irc->password && old->password && strcmp(irc->user->nick, old->user->nick) == 0 && strcmp(irc->password, old->password) == 0) { break; } } if (l == NULL) { to_auth[1] = "FAIL"; ipc_child_cmd_takeover(irc, to_auth); return; } } /* Drop credentials, we'll shut down soon and shouldn't overwrite any settings. */ irc_rootmsg(irc, "Trying to take over existing session"); irc_desync(irc); if (old) { ipc_child_recv_fd = dup(irc->fd); ipc_child_cmd_takeover(old, to_auth); } /* TODO: irc_setpass() should do all of this. */ irc_setpass(irc, NULL); irc->status &= ~USTATUS_IDENTIFIED; irc_umode_set(irc, "-R", 1); if (old) { irc_abort(irc, FALSE, NULL); } } static void ipc_child_cmd_takeover_no(void *data) { ipc_to_master_str("TAKEOVER NO\r\n"); cmd_identify_finish(data, 0, 0); } static const command_t ipc_child_commands[] = { { "die", 0, ipc_child_cmd_die, 0 }, { "wallops", 1, ipc_child_cmd_wallops, 0 }, { "wall", 1, ipc_child_cmd_wall, 0 }, { "opermsg", 1, ipc_child_cmd_opermsg, 0 }, { "rehash", 0, ipc_child_cmd_rehash, 0 }, { "kill", 2, ipc_child_cmd_kill, 0 }, { "hello", 0, ipc_child_cmd_hello, 0 }, { "takeover", 1, ipc_child_cmd_takeover, 0 }, { NULL } }; gboolean ipc_child_identify(irc_t *irc) { if (global.conf->runmode == RUNMODE_FORKDAEMON) { #ifndef NO_FD_PASSING if (!ipc_send_fd(global.listen_socket, irc->fd)) { ipc_child_disable(); } ipc_to_master_str("IDENTIFY %s :%s\r\n", irc->user->nick, irc->password); #endif return TRUE; } else if (global.conf->runmode == RUNMODE_DAEMON) { GSList *l; irc_t *old = NULL; char *to_init[] = { "TAKEOVER", "INIT", NULL }; for (l = irc_connection_list; l; l = l->next) { old = l->data; if (irc != old && irc->user->nick && old->user->nick && irc->password && old->password && strcmp(irc->user->nick, old->user->nick) == 0 && strcmp(irc->password, old->password) == 0) { break; } } if (l == NULL || old == NULL || !set_getbool(&irc->b->set, "allow_takeover") || !set_getbool(&old->b->set, "allow_takeover")) { return FALSE; } ipc_child_cmd_takeover(irc, to_init); return TRUE; } else { return FALSE; } } static void ipc_master_takeover_fail(struct bitlbee_child *child, gboolean both) { if (child == NULL || g_slist_find(child_list, child) == NULL) { return; } if (both && child->to_child != NULL) { ipc_master_takeover_fail(child->to_child, FALSE); } if (child->to_fd > -1) { /* Send this error only to the new connection, which can be recognised by to_fd being set. */ if (write(child->ipc_fd, "TAKEOVER FAIL\r\n", 15) != 15) { ipc_master_free_one(child); return; } close(child->to_fd); child->to_fd = -1; } child->to_child = NULL; } static void ipc_command_exec(void *data, char **cmd, const command_t *commands) { int i, j; if (!cmd[0]) { return; } for (i = 0; commands[i].command; i++) { if (g_strcasecmp(commands[i].command, cmd[0]) == 0) { /* There is no typo in this line: */ for (j = 1; cmd[j]; j++) { ; } j--; if (j < commands[i].required_parameters) { break; } if (commands[i].flags & IPC_CMD_TO_CHILDREN) { ipc_to_children(cmd); } else { commands[i].execute(data, cmd); } break; } } } /* Return just one line. Returns NULL if something broke, an empty string on temporary "errors" (EAGAIN and friends). */ static char *ipc_readline(int fd, int *recv_fd) { struct msghdr msg; struct iovec iov; char ccmsg[CMSG_SPACE(sizeof(recv_fd))]; struct cmsghdr *cmsg; char buf[513], *eol; int size; /* Because this is internal communication, it should be pretty safe to just peek at the message, find its length (by searching for the end-of-line) and then just read that message. With internal sockets and limites message length, messages should always be complete. Saves us quite a lot of code and buffering. */ size = recv(fd, buf, sizeof(buf) - 1, MSG_PEEK); if (size == 0 || (size < 0 && !sockerr_again())) { return NULL; } else if (size < 0) { /* && sockerr_again() */ return(g_strdup("")); } else { buf[size] = 0; } if ((eol = strstr(buf, "\r\n")) == NULL) { return NULL; } else { size = eol - buf + 2; } iov.iov_base = buf; iov.iov_len = size; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; #ifndef NO_FD_PASSING msg.msg_control = ccmsg; msg.msg_controllen = sizeof(ccmsg); #endif if (recvmsg(fd, &msg, 0) != size) { return NULL; } #ifndef NO_FD_PASSING if (recv_fd) { for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { /* Getting more than one shouldn't happen but if it does, make sure we don't leave them around. */ if (*recv_fd != -1) { close(*recv_fd); } *recv_fd = *(int *) CMSG_DATA(cmsg); /* fprintf( stderr, "pid %d received fd %d\n", (int) getpid(), *recv_fd ); */ } } } #endif /* fprintf( stderr, "pid %d received: %s", (int) getpid(), buf ); */ return g_strndup(buf, size - 2); } gboolean ipc_master_read(gpointer data, gint source, b_input_condition cond) { struct bitlbee_child *child = data; char *buf, **cmd; if ((buf = ipc_readline(source, &child->to_fd))) { cmd = irc_parse_line(buf); if (cmd) { ipc_command_exec(child, cmd, ipc_master_commands); g_free(cmd); } g_free(buf); } else { ipc_master_free_fd(source); } return TRUE; } gboolean ipc_child_read(gpointer data, gint source, b_input_condition cond) { char *buf, **cmd; if ((buf = ipc_readline(source, &ipc_child_recv_fd))) { cmd = irc_parse_line(buf); if (cmd) { ipc_command_exec(data, cmd, ipc_child_commands); g_free(cmd); } g_free(buf); } else { ipc_child_disable(); } return TRUE; } void ipc_to_master(char **cmd) { if (global.conf->runmode == RUNMODE_FORKDAEMON) { char *s = irc_build_line(cmd); ipc_to_master_str("%s", s); g_free(s); } else if (global.conf->runmode == RUNMODE_DAEMON) { ipc_command_exec(NULL, cmd, ipc_master_commands); } } void ipc_to_master_str(char *format, ...) { char *msg_buf; va_list params; va_start(params, format); msg_buf = g_strdup_vprintf(format, params); va_end(params); if (strlen(msg_buf) > 512) { /* Don't send it, it's too long... */ } else if (global.conf->runmode == RUNMODE_FORKDAEMON) { if (global.listen_socket >= 0) { if (write(global.listen_socket, msg_buf, strlen(msg_buf)) <= 0) { ipc_child_disable(); } } } else if (global.conf->runmode == RUNMODE_DAEMON) { char **cmd, *s; if ((s = strchr(msg_buf, '\r'))) { *s = 0; } cmd = irc_parse_line(msg_buf); ipc_command_exec(NULL, cmd, ipc_master_commands); g_free(cmd); } g_free(msg_buf); } void ipc_to_children(char **cmd) { if (global.conf->runmode == RUNMODE_FORKDAEMON) { char *msg_buf = irc_build_line(cmd); ipc_to_children_str("%s", msg_buf); g_free(msg_buf); } else if (global.conf->runmode == RUNMODE_DAEMON) { GSList *l; for (l = irc_connection_list; l; l = l->next) { ipc_command_exec(l->data, cmd, ipc_child_commands); } } } void ipc_to_children_str(char *format, ...) { char *msg_buf; va_list params; va_start(params, format); msg_buf = g_strdup_vprintf(format, params); va_end(params); if (strlen(msg_buf) > 512) { /* Don't send it, it's too long... */ } else if (global.conf->runmode == RUNMODE_FORKDAEMON) { int msg_len = strlen(msg_buf); GSList *l, *next; for (l = child_list; l; l = next) { struct bitlbee_child *c = l->data; next = l->next; if (write(c->ipc_fd, msg_buf, msg_len) <= 0) { ipc_master_free_one(c); } } } else if (global.conf->runmode == RUNMODE_DAEMON) { char **cmd, *s; if ((s = strchr(msg_buf, '\r'))) { *s = 0; } cmd = irc_parse_line(msg_buf); ipc_to_children(cmd); g_free(cmd); } g_free(msg_buf); } static gboolean ipc_send_fd(int fd, int send_fd) { struct msghdr msg; struct iovec iov; char ccmsg[CMSG_SPACE(sizeof(fd))]; struct cmsghdr *cmsg; memset(&msg, 0, sizeof(msg)); iov.iov_base = "0x90\r\n"; /* Ja, noppes */ iov.iov_len = 6; msg.msg_iov = &iov; msg.msg_iovlen = 1; #ifndef NO_FD_PASSING msg.msg_control = ccmsg; msg.msg_controllen = sizeof(ccmsg); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd)); *(int *) CMSG_DATA(cmsg) = send_fd; msg.msg_controllen = cmsg->cmsg_len; #endif return sendmsg(fd, &msg, 0) == 6; } void ipc_master_free_one(struct bitlbee_child *c) { GSList *l; b_event_remove(c->ipc_inpa); closesocket(c->ipc_fd); if (c->to_fd != -1) { close(c->to_fd); } g_free(c->host); g_free(c->nick); g_free(c->realname); g_free(c->password); g_free(c); child_list = g_slist_remove(child_list, c); /* Also, if any child has a reference to this one, remove it. */ for (l = child_list; l; l = l->next) { struct bitlbee_child *oc = l->data; if (oc->to_child == c) { ipc_master_takeover_fail(oc, FALSE); } } } void ipc_master_free_fd(int fd) { GSList *l; struct bitlbee_child *c; for (l = child_list; l; l = l->next) { c = l->data; if (c->ipc_fd == fd) { ipc_master_free_one(c); break; } } } void ipc_master_free_all() { while (child_list) { ipc_master_free_one(child_list->data); } } void ipc_child_disable() { b_event_remove(global.listen_watch_source_id); close(global.listen_socket); global.listen_socket = -1; } char *ipc_master_save_state() { char *fn = g_strdup("/tmp/bee-restart.XXXXXX"); int fd = mkstemp(fn); GSList *l; FILE *fp; int i; if (fd == -1) { log_message(LOGLVL_ERROR, "Could not create temporary file: %s", strerror(errno)); g_free(fn); return NULL; } /* This is more convenient now. */ fp = fdopen(fd, "w"); for (l = child_list, i = 0; l; l = l->next) { i++; } /* Number of client processes. */ fprintf(fp, "%d\n", i); for (l = child_list; l; l = l->next) { fprintf(fp, "%d %d\n", (int) ((struct bitlbee_child*) l->data)->pid, ((struct bitlbee_child*) l->data)->ipc_fd); } if (fclose(fp) == 0) { return fn; } else { unlink(fn); g_free(fn); return NULL; } } static gboolean new_ipc_client(gpointer data, gint serversock, b_input_condition cond) { struct bitlbee_child *child = g_new0(struct bitlbee_child, 1); child->to_fd = -1; child->ipc_fd = accept(serversock, NULL, 0); if (child->ipc_fd == -1) { log_message(LOGLVL_WARNING, "Unable to accept connection on UNIX domain socket: %s", strerror(errno)); return TRUE; } child->ipc_inpa = b_input_add(child->ipc_fd, B_EV_IO_READ, ipc_master_read, child); child_list = g_slist_prepend(child_list, child); return TRUE; } int ipc_master_listen_socket() { struct sockaddr_un un_addr; int serversock; if (!IPCSOCKET || !*IPCSOCKET) { return 1; } /* Clean up old socket files that were hanging around.. */ if (unlink(IPCSOCKET) == -1 && errno != ENOENT) { log_message(LOGLVL_ERROR, "Could not remove old IPC socket at %s: %s", IPCSOCKET, strerror(errno)); return 0; } un_addr.sun_family = AF_UNIX; strcpy(un_addr.sun_path, IPCSOCKET); serversock = socket(AF_UNIX, SOCK_STREAM, PF_UNIX); if (serversock == -1) { log_message(LOGLVL_WARNING, "Unable to create UNIX socket: %s", strerror(errno)); return 0; } if (bind(serversock, (struct sockaddr *) &un_addr, sizeof(un_addr)) == -1) { log_message(LOGLVL_WARNING, "Unable to bind UNIX socket to %s: %s", IPCSOCKET, strerror(errno)); return 0; } if (listen(serversock, 5) == -1) { log_message(LOGLVL_WARNING, "Unable to listen on UNIX socket: %s", strerror(errno)); return 0; } b_input_add(serversock, B_EV_IO_READ, new_ipc_client, NULL); return 1; } int ipc_master_load_state(char *statefile) { struct bitlbee_child *child; FILE *fp; int i, n; if (statefile == NULL) { return 0; } fp = fopen(statefile, "r"); unlink(statefile); /* Why do it later? :-) */ if (fp == NULL) { return 0; } if (fscanf(fp, "%d", &n) != 1) { log_message(LOGLVL_WARNING, "Could not import state information for child processes."); fclose(fp); return 0; } log_message(LOGLVL_INFO, "Importing information for %d child processes.", n); for (i = 0; i < n; i++) { child = g_new0(struct bitlbee_child, 1); if (fscanf(fp, "%d %d", (int *) &child->pid, &child->ipc_fd) != 2) { log_message(LOGLVL_WARNING, "Unexpected end of file: Only processed %d clients.", i); g_free(child); fclose(fp); return 0; } child->ipc_inpa = b_input_add(child->ipc_fd, B_EV_IO_READ, ipc_master_read, child); child->to_fd = -1; child_list = g_slist_prepend(child_list, child); } ipc_to_children_str("HELLO\r\n"); ipc_to_children_str("OPERMSG :New %s master process started (version %s)\r\n", PACKAGE, BITLBEE_VERSION); fclose(fp); return 1; }