/****************************************************************************** * $Id$ * * Copyright (c) 2006 Transmission authors and contributors * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "transmission.h" #include "bencode.h" #include "conf.h" #include "io.h" #include "ipc.h" #include "util.h" #define PROTOVERS 1 /* IPC protocol version */ /* int, IPC protocol version */ #define MSG_VERSION ("version") /* list of strings, full paths to torrent files to load */ #define MSG_ADDFILES ("addfiles") enum contype { CON_SERV, CON_ADDFILE }; struct constate_serv { void *wind; add_torrents_func_t addfunc; void *cbdata; }; struct constate_addfile { GMainLoop *loop; GList *files; gboolean *succeeded; unsigned int addid; }; struct constate; typedef void (*handler_func_t)(struct constate*, const char*, benc_val_t *); struct handlerdef {char *name; handler_func_t func;}; struct constate { GSource *source; int fd; const struct handlerdef *funcs; enum contype type; union { struct constate_serv serv; struct constate_addfile addfile; } u; }; void ipc_socket_setup(void *parent, add_torrents_func_t addfunc, void *cbdata); gboolean ipc_sendfiles_blocking(GList *files); static void serv_bind(struct constate *con); static void rmsock(void); static gboolean client_connect(char *path, struct constate *con); static void srv_io_accept(GSource *source, int fd, struct sockaddr *sa, socklen_t len, void *vdata); static int send_msg(struct constate *con, const char *name, benc_val_t *val); static int send_msg_int(struct constate *con, const char *name, int num); static unsigned int all_io_received(GSource *source, char *data, unsigned int len, void *vdata); static void destroycon(struct constate *con); static void all_io_closed(GSource *source, void *vdata); static void srv_addfile(struct constate *con, const char *name, benc_val_t *val); static void afc_version(struct constate *con, const char *name, benc_val_t *val); static void afc_io_sent(GSource *source, unsigned int id, void *vdata); static const struct handlerdef gl_funcs_serv[] = { {MSG_ADDFILES, srv_addfile}, {NULL, NULL} }; static const struct handlerdef gl_funcs_addfile[] = { {MSG_VERSION, afc_version}, {NULL, NULL} }; /* this is only used on the server */ static char *gl_sockpath = NULL; void ipc_socket_setup(void *parent, add_torrents_func_t addfunc, void *cbdata) { struct constate *con; con = g_new0(struct constate, 1); con->source = NULL; con->fd = -1; con->funcs = gl_funcs_serv; con->type = CON_SERV; con->u.serv.wind = parent; con->u.serv.addfunc = addfunc; con->u.serv.cbdata = cbdata; serv_bind(con); } gboolean ipc_sendfiles_blocking(GList *files) { struct constate *con; char *path; gboolean ret = FALSE; con = g_new0(struct constate, 1); con->source = NULL; con->fd = -1; con->funcs = gl_funcs_addfile; con->type = CON_ADDFILE; con->u.addfile.loop = g_main_loop_new(NULL, TRUE); con->u.addfile.files = files; con->u.addfile.succeeded = &ret; con->u.addfile.addid = 0; path = cf_sockname(); if(!client_connect(path, con)) { g_free(path); destroycon(con); return FALSE; } g_main_loop_run(con->u.addfile.loop); return ret; } /* open a local socket for clients connections */ static void serv_bind(struct constate *con) { struct sockaddr_un sa; rmsock(); gl_sockpath = cf_sockname(); if(0 > (con->fd = socket(AF_LOCAL, SOCK_STREAM, 0))) goto fail; bzero(&sa, sizeof(sa)); sa.sun_family = AF_LOCAL; strncpy(sa.sun_path, gl_sockpath, sizeof(sa.sun_path) - 1); /* unlink any existing socket file before trying to create ours */ unlink(gl_sockpath); if(0 > bind(con->fd, (struct sockaddr *)&sa, SUN_LEN(&sa))) { /* bind may fail if there was already a socket, so try twice */ unlink(gl_sockpath); if(0 > bind(con->fd, (struct sockaddr *)&sa, SUN_LEN(&sa))) goto fail; } if(0 > listen(con->fd, 5)) goto fail; con->source = io_new_listening(con->fd, sizeof(struct sockaddr_un), srv_io_accept, all_io_closed, con); g_atexit(rmsock); return; fail: errmsg(con->u.serv.wind, _("Failed to set up socket:\n%s"), strerror(errno)); if(0 <= con->fd) close(con->fd); con->fd = -1; rmsock(); } static void rmsock(void) { if(NULL != gl_sockpath) { unlink(gl_sockpath); g_free(gl_sockpath); } } static gboolean client_connect(char *path, struct constate *con) { struct sockaddr_un addr; if(0 > (con->fd = socket(AF_UNIX, SOCK_STREAM, 0))) { fprintf(stderr, _("failed to create socket: %s\n"), strerror(errno)); return FALSE; } bzero(&addr, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); if(0 > connect(con->fd, (struct sockaddr*)&addr, SUN_LEN(&addr))) { fprintf(stderr, _("failed to connect to %s: %s\n"), path, strerror(errno)); return FALSE; } con->source = io_new(con->fd, afc_io_sent, all_io_received, all_io_closed, con); return TRUE; } static void srv_io_accept(GSource *source SHUTUP, int fd, struct sockaddr *sa SHUTUP, socklen_t len SHUTUP, void *vdata) { struct constate *con = vdata; struct constate *newcon; newcon = g_new(struct constate, 1); memcpy(newcon, con, sizeof(*newcon)); newcon->fd = fd; newcon->source = io_new(fd, NULL, all_io_received, all_io_closed, newcon); if(NULL != newcon->source) send_msg_int(newcon, MSG_VERSION, PROTOVERS); else { g_free(newcon); close(fd); } } static int send_msg(struct constate *con, const char *name, benc_val_t *val) { char *buf; int used, total; benc_val_t dict; char stupid; /* construct a dictionary value */ /* XXX I shouldn't be constructing benc_val_t's by hand */ bzero(&dict, sizeof(dict)); dict.type = TYPE_DICT; dict.val.l.alloc = 2; dict.val.l.count = 2; dict.val.l.vals = g_new0(benc_val_t, 2); dict.val.l.vals[0].type = TYPE_STR; dict.val.l.vals[0].val.s.i = strlen(name); dict.val.l.vals[0].val.s.s = (char*)name; dict.val.l.vals[1] = *val; /* bencode the dictionary, starting at offset 8 in the buffer */ buf = malloc(9); g_assert(NULL != buf); total = 9; used = 8; if(tr_bencSave(&dict, &buf, &used, &total)) { g_assert_not_reached(); } g_free(dict.val.l.vals); /* write the bencoded data length into the first 8 bytes of the buffer */ stupid = buf[8]; snprintf(buf, 9, "%08X", (unsigned int)used - 8); buf[8] = stupid; /* send the data */ return io_send_keepdata(con->source, buf, used); } static int send_msg_int(struct constate *con, const char *name, int num) { benc_val_t val; bzero(&val, sizeof(val)); val.type = TYPE_INT; val.val.i = num; return send_msg(con, name, &val); } static unsigned int all_io_received(GSource *source, char *data, unsigned int len, void *vdata) { struct constate *con = vdata; size_t size; char stupid; char *end; benc_val_t msgs; int ii, jj; if(9 > len) return 0; stupid = data[8]; data[8] = '\0'; size = strtoul(data, NULL, 16); data[8] = stupid; if(size + 8 > len) return 0; if(!tr_bencLoad(data + 8, size, &msgs, &end) && TYPE_DICT == msgs.type) { for(ii = 0; msgs.val.l.count > ii + 1; ii += 2) if(TYPE_STR == msgs.val.l.vals[ii].type) for(jj = 0; NULL != con->funcs[jj].name; jj++) if(0 == strcmp(msgs.val.l.vals[ii].val.s.s, con->funcs[jj].name)) { con->funcs[jj].func(con, msgs.val.l.vals[ii].val.s.s, msgs.val.l.vals + ii + 1); break; } tr_bencFree(&msgs); } return size + 8 + all_io_received(source, data + size + 8, len - size - 8, con); } static void destroycon(struct constate *con) { con->source = NULL; if(0 <= con->fd) close(con->fd); con->fd = -1; switch(con->type) { case CON_SERV: break; case CON_ADDFILE: freestrlist(con->u.addfile.files); g_main_loop_quit(con->u.addfile.loop); break; } } static void all_io_closed(GSource *source SHUTUP, void *vdata) { struct constate *con = vdata; destroycon(con); } static void srv_addfile(struct constate *con, const char *name SHUTUP, benc_val_t *val) { struct constate_serv *srv = &con->u.serv; GList *files; int ii; if(TYPE_LIST == val->type) { files = NULL; for(ii = 0; ii < val->val.l.count; ii++) if(TYPE_STR == val->val.l.vals[ii].type && /* XXX somehow escape invalid utf-8 */ g_utf8_validate(val->val.l.vals[ii].val.s.s, -1, NULL)) files = g_list_append(files, val->val.l.vals[ii].val.s.s); srv->addfunc(srv->cbdata, NULL, files, NULL, addactionflag(cf_getpref(PREF_ADDIPC))); g_list_free(files); } } static void afc_version(struct constate *con, const char *name SHUTUP, benc_val_t *val) { struct constate_addfile *afc = &con->u.addfile; GList *file; benc_val_t list, *str; if(TYPE_INT != val->type || PROTOVERS != val->val.i) { fprintf(stderr, _("bad IPC protocol version\n")); destroycon(con); } else { /* XXX handle getting a non-version tag, invalid data, or nothing (read timeout) */ bzero(&list, sizeof(list)); list.type = TYPE_LIST; list.val.l.alloc = g_list_length(afc->files); list.val.l.vals = g_new0(benc_val_t, list.val.l.alloc); for(file = afc->files; NULL != file; file = file->next) { str = list.val.l.vals + list.val.l.count; str->type = TYPE_STR; str->val.s.i = strlen(file->data); str->val.s.s = file->data; list.val.l.count++; } g_list_free(afc->files); afc->files = NULL; afc->addid = send_msg(con, MSG_ADDFILES, &list); tr_bencFree(&list); } } static void afc_io_sent(GSource *source SHUTUP, unsigned int id, void *vdata) { struct constate_addfile *afc = &((struct constate*)vdata)->u.addfile; if(0 < id && afc->addid == id) { *(afc->succeeded) = TRUE; destroycon(vdata); } }