From: Andre Noll Date: Sun, 24 Jun 2012 18:53:23 +0000 (+0200) Subject: Merge branch 't/sb' X-Git-Tag: v0.4.11~14 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=1745a87654ce08b57c46ce0870d8571eacbe27c9;hp=5d5132e5bf39298739737d75aafb66a7405978a3 Merge branch 't/sb' Was cooking in next for several weeks with no problems so far. fd864c Fix documentation of pass_buffer_as_shm(). 32ffc0 Pass command exit status via sideband to client. 7dc1b5 Send command output via sideband. 0229d7 Introduce send_strerror(). d5a9e8 Simplify pass_buffer_as_shm(). 3667d7 command.c: Improve get_status(). aa3fe7 Pass command via sideband. 9c2a26 Use sideband also for "proceed" handshake. 4744d9 Use sideband also for challenge response. fa9e0e Send and receive challenge via sideband. 2830b9 Add sideband implementation. e151db Implement client-server feature negotiation. e31d5d afs: Rename sc_send_result() to afs_cb_result_handler(). 68cb0a Introduce afs_max_size_handler_data and afs_max_size_handler(). d04b83 client: Improve error diagnostics. --- diff --git a/NEWS b/NEWS index 0ac2240e..fa8e0249 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ 0.4.11 (to be announced) "mutual diversity" ------------------------------------------- + - Sideband connections: If both para_server and para_client + support this feature, data is sent as a multiplexed stream. - The --no_default_filters option of para_filter has been removed. - Documentation improvements. diff --git a/afs.c b/afs.c index 5939cbe6..a1381e73 100644 --- a/afs.c +++ b/afs.c @@ -26,6 +26,7 @@ #include "signal.h" #include "fd.h" #include "mood.h" +#include "sideband.h" #include "command.h" /** The osl tables used by afs. \sa blob.c. */ @@ -141,6 +142,8 @@ struct callback_query { struct callback_result { /** The number of bytes of the result. */ size_t result_size; + /** The band designator (loglevel for the result). */ + uint8_t band; }; static int dispatch_result(int result_shmid, callback_result_handler *handler, @@ -148,7 +151,8 @@ static int dispatch_result(int result_shmid, callback_result_handler *handler, { struct osl_object result; void *result_shm; - int ret2, ret = shm_attach(result_shmid, ATTACH_RO, &result_shm); + /* must attach r/w as result.data might get encrypted in-place. */ + int ret2, ret = shm_attach(result_shmid, ATTACH_RW, &result_shm); struct callback_result *cr = result_shm; if (ret < 0) { @@ -159,7 +163,7 @@ static int dispatch_result(int result_shmid, callback_result_handler *handler, result.data = result_shm + sizeof(*cr); if (result.size) { assert(handler); - ret = handler(&result, private_result_data); + ret = handler(&result, cr->band, private_result_data); if (ret < 0) PARA_NOTICE_LOG("result handler error: %s\n", para_strerror(-ret)); @@ -546,8 +550,11 @@ static void com_select_callback(int fd, const struct osl_object *query) { struct para_buffer pb = { .max_size = shm_get_shmmax(), - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }, + .max_size_handler = afs_max_size_handler, }; char *arg = query->data; int num_admissible, ret, ret2; @@ -577,7 +584,7 @@ static void com_select_callback(int fd, const struct osl_object *query) current_mop : "dummy mood", num_admissible); out: if (ret2 >= 0 && pb.offset) - pass_buffer_as_shm(pb.buf, pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset); free(pb.buf); } @@ -585,23 +592,24 @@ out: * Result handler for sending data to the para_client process. * * \param result The data to be sent. - * \param private Pointer to the context. + * \param band The band designator. + * \param private Pointer to the command context. * - * \return The return value of the underlying call to sc_send_bin_buffer(). + * \return The return value of the underlying call to \ref command.c::send_sb. * - * \sa \ref callback_result_handler, \ref sc_send_bin_buffer(). + * \sa \ref callback_result_handler, \ref command.c::send_sb. */ -int sc_send_result(struct osl_object *result, void *private) +int afs_cb_result_handler(struct osl_object *result, uint8_t band, + void *private) { struct command_context *cc = private; - int ret; if (!result->size) return 1; - ret = sc_send_bin_buffer(&cc->scc, result->data, result->size); - if (ret < 0 || ret == result->size) - return ret; - return -E_SHORT_WRITE; + if (cc->use_sideband) + return send_sb(&cc->scc, result->data, result->size, band, + true); + return sc_send_bin_buffer(&cc->scc, result->data, result->size); } int com_select(struct command_context *cc) @@ -613,7 +621,7 @@ int com_select(struct command_context *cc) query.data = cc->argv[1]; query.size = strlen(cc->argv[1]) + 1; return send_callback_request(com_select_callback, &query, - &sc_send_result, cc); + &afs_cb_result_handler, cc); } static void init_admissible_files(char *arg) @@ -783,30 +791,30 @@ static void command_pre_select(struct sched *s, struct task *t) /** * Send data as shared memory to a file descriptor. * + * \param fd File descriptor to send the shmid to. + * \param band The band designator for this data. * \param buf The buffer holding the data to be sent. * \param size The size of \a buf. - * \param fd_ptr A pointer to the file descriptor. - * - * This function is used as the \a max_size handler in a \ref para_buffer - * structure. If used this way, it is called by \ref para_printf() whenever - * the buffer passed to para_printf() is about to exceed its maximal size. * * This function creates a shared memory area large enough to hold * the content given by \a buf and \a size and sends the identifier - * of this area to the file descriptor given by \a fd_ptr. + * of this area to the file descriptor \a fd. + * + * It is called by the AFS max_size handler as well as directly by the AFS + * command callbacks to send command output to the command handlers. * * \return Zero if \a buf is \p NULL or \a size is zero. Negative on errors, * and positive on success. */ -int pass_buffer_as_shm(char *buf, size_t size, void *fd_ptr) +int pass_buffer_as_shm(int fd, uint8_t band, char *buf, size_t size) { - int ret, shmid, fd = *(int *)fd_ptr; + int ret, shmid; void *shm; struct callback_result *cr; if (!buf || !size) return 0; - ret = shm_new(size + sizeof(struct callback_result)); + ret = shm_new(size + sizeof(*cr)); if (ret < 0) return ret; shmid = ret; @@ -815,6 +823,7 @@ int pass_buffer_as_shm(char *buf, size_t size, void *fd_ptr) goto err; cr = shm; cr->result_size = size; + cr->band = band; memcpy(shm + sizeof(*cr), buf, size); ret = shm_detach(shm); if (ret < 0) @@ -1023,7 +1032,7 @@ out: if (ret < 0) para_printf(&pb, "%s\n", para_strerror(-ret)); if (pb.buf) - pass_buffer_as_shm(pb.buf, pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset); free(pb.buf); } @@ -1053,7 +1062,7 @@ int com_init(struct command_context *cc) } } ret = send_callback_request(create_tables_callback, &query, - sc_send_result, cc); + afs_cb_result_handler, cc); if (ret < 0) /* ignore return value */ sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(-ret)); @@ -1107,19 +1116,19 @@ int com_check(struct command_context *cc) flags = ~0U; if (flags & CHECK_AFT) { ret = send_callback_request(aft_check_callback, NULL, - sc_send_result, cc); + afs_cb_result_handler, cc); if (ret < 0) return ret; } if (flags & CHECK_PLAYLISTS) { ret = send_callback_request(playlist_check_callback, - NULL, sc_send_result, cc); + NULL, afs_cb_result_handler, cc); if (ret < 0) return ret; } if (flags & CHECK_MOODS) { ret = send_callback_request(mood_check_callback, NULL, - sc_send_result, cc); + afs_cb_result_handler, cc); if (ret < 0) return ret; } diff --git a/afs.cmd b/afs.cmd index c7808040..11b16311 100644 --- a/afs.cmd +++ b/afs.cmd @@ -4,7 +4,7 @@ HC: Prototypes for the commands of the audio file selector. CC: Array of commands for the audio file selector. AT: server_command SI: osl regex -IN: para error crypt command string afh afs server list user_list +IN: para error crypt sideband command string afh afs server list user_list SN: list of afs commands TM: mood lyr img pl --- diff --git a/afs.h b/afs.h index 7145c41b..2789e94e 100644 --- a/afs.h +++ b/afs.h @@ -205,9 +205,38 @@ typedef void callback_function(int fd, const struct osl_object *); * * \sa \ref send_callback_request(). */ -typedef int callback_result_handler(struct osl_object *result, void *private); -int sc_send_result(struct osl_object *result, void *private); -int pass_buffer_as_shm(char *buf, size_t size, void *fd_ptr); +typedef int callback_result_handler(struct osl_object *result, uint8_t band, void *private); +int afs_cb_result_handler(struct osl_object *result, uint8_t band, void *private); +int pass_buffer_as_shm(int fd, uint8_t band, char *buf, size_t size); + +/** Structure passed to the AFS max_size handler. */ +struct afs_max_size_handler_data { + /** Local socket connecting the command handler and the AFS process. */ + int fd; + /** The sideband designator for this data packet. */ + uint8_t band; +}; + +/** + * Standard max_size handler for AFS commands. + * + * \param buf Contains (part of) the AFS command output. + * \param size The number of bytes in \a buf. + * \param private Pointer to a \ref afs_max_size_handler_data structure. + * + * Whenever the output of an AFS command exceeds the maximal size of a shared + * memory area, the max size handler of the para_buffer which holds the command + * output is called with \a private being a pointer to a structure of type + * afs_max_size_handler_data. + * + * \return The return value of the underlying call to \ref + * pass_buffer_as_shm(). + */ +_static_inline_ int afs_max_size_handler(char *buf, size_t size, void *private) +{ + struct afs_max_size_handler_data *amshd = private; + return pass_buffer_as_shm(amshd->fd, amshd->band, buf, size); +} __noreturn void afs_init(uint32_t cookie, int socket_fd); void afs_event(enum afs_events event, struct para_buffer *pb, diff --git a/aft.c b/aft.c index 7516e328..27327909 100644 --- a/aft.c +++ b/aft.c @@ -21,6 +21,7 @@ #include "fd.h" #include "ipc.h" #include "portable_io.h" +#include "sideband.h" #include "command.h" static struct osl_table *audio_file_table; @@ -1349,9 +1350,15 @@ static void com_ls_callback(int fd, const struct osl_object *query) { struct ls_options *opts = query->data; char *p, *pattern_start = (char *)query->data + sizeof(*opts); - struct para_buffer b = {.max_size = shm_get_shmmax(), + struct para_buffer b = { + .max_size = shm_get_shmmax(), .flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0, - .max_size_handler = pass_buffer_as_shm, .private_data = &fd}; + .max_size_handler = afs_max_size_handler, + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + } + }; int i = 0, ret; time_t current_time; @@ -1393,7 +1400,7 @@ static void com_ls_callback(int fd, const struct osl_object *query) } out: if (b.offset) - pass_buffer_as_shm(b.buf, b.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, b.buf, b.offset); free(b.buf); free(opts->data); free(opts->data_ptr); @@ -1515,7 +1522,7 @@ int com_ls(struct command_context *cc) opts.mode = mode; opts.num_patterns = cc->argc - i; ret = send_option_arg_callback_request(&query, opts.num_patterns, - cc->argv + i, com_ls_callback, sc_send_result, cc); + cc->argv + i, com_ls_callback, afs_cb_result_handler, cc); return ret; } @@ -1674,8 +1681,14 @@ static void com_add_callback(int fd, const struct osl_object *query) char afsi_buf[AFSI_SIZE]; uint32_t flags = read_u32(buf + CAB_FLAGS_OFFSET); struct afs_info default_afsi = {.last_played = 0}; - struct para_buffer msg = {.max_size = shm_get_shmmax(), - .max_size_handler = pass_buffer_as_shm, .private_data = &fd}; + struct para_buffer msg = { + .max_size = shm_get_shmmax(), + .max_size_handler = afs_max_size_handler, + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + } + }; uint16_t afhi_offset, chunks_offset; hash = (unsigned char *)buf + CAB_HASH_OFFSET; @@ -1796,7 +1809,7 @@ out: if (ret < 0) para_printf(&msg, "%s\n", para_strerror(-ret)); if (msg.offset) - pass_buffer_as_shm(msg.buf, msg.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, msg.buf, msg.offset); free(msg.buf); } @@ -1815,7 +1828,8 @@ static void path_brother_callback(int fd, const struct osl_object *query) int ret = aft_get_row_of_path(path, &path_brother); if (ret < 0) return; - pass_buffer_as_shm((char *)&path_brother, sizeof(path_brother), &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, (char *)&path_brother, + sizeof(path_brother)); } static void hash_sister_callback(int fd, const struct osl_object *query) @@ -1826,10 +1840,12 @@ static void hash_sister_callback(int fd, const struct osl_object *query) hash_sister = find_hash_sister(hash); if (!hash_sister) return; - pass_buffer_as_shm((char *)&hash_sister, sizeof(hash_sister), &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, (char *)&hash_sister, + sizeof(hash_sister)); } -static int get_row_pointer_from_result(struct osl_object *result, void *private) +static int get_row_pointer_from_result(struct osl_object *result, + __a_unused uint8_t band, void *private) { struct osl_row **row = private; *row = *(struct osl_row **)(result->data); @@ -1858,8 +1874,12 @@ static int add_one_audio_file(const char *path, void *private_data) ret = 1; if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */ if (pad->flags & ADD_FLAG_VERBOSE) - send_ret = sc_send_va_buffer(&pad->cc->scc, - "lazy-ignore: %s\n", path); + send_ret = pad->cc->use_sideband? + send_sb_va(&pad->cc->scc, SBD_OUTPUT, + "lazy-ignore: %s\n", path) + : + sc_send_va_buffer(&pad->cc->scc, + "lazy-ignore: %s\n", path); goto out_free; } /* We still want to add this file. Compute its hash. */ @@ -1879,8 +1899,12 @@ static int add_one_audio_file(const char *path, void *private_data) ret = 1; if (pb && hs && hs == pb && !(pad->flags & ADD_FLAG_FORCE)) { if (pad->flags & ADD_FLAG_VERBOSE) - send_ret = sc_send_va_buffer(&pad->cc->scc, - "%s exists, not forcing update\n", path); + send_ret = pad->cc->use_sideband? + send_sb_va(&pad->cc->scc, SBD_OUTPUT, + "%s exists, not forcing update\n", path) + : + sc_send_va_buffer(&pad->cc->scc, + "%s exists, not forcing update\n", path); goto out_unmap; } /* @@ -1897,13 +1921,19 @@ static int add_one_audio_file(const char *path, void *private_data) munmap(map.data, map.size); close(fd); if (pad->flags & ADD_FLAG_VERBOSE) { - send_ret = sc_send_va_buffer(&pad->cc->scc, "adding %s\n", path); + send_ret = pad->cc->use_sideband? + send_sb_va(&pad->cc->scc, SBD_OUTPUT, + "adding %s\n", path) + : + sc_send_va_buffer(&pad->cc->scc, + "adding %s\n", path); if (send_ret < 0) goto out_free; } save_add_callback_buffer(hash, path, afhi_ptr, pad->flags, format_num, &obj); /* Ask afs to consider this entry for adding. */ - ret = send_callback_request(com_add_callback, &obj, sc_send_result, pad->cc); + ret = send_callback_request(com_add_callback, &obj, + afs_cb_result_handler, pad->cc); goto out_free; out_unmap: @@ -1911,8 +1941,14 @@ out_unmap: munmap(map.data, map.size); out_free: if (ret < 0 && send_ret >= 0) - send_ret = sc_send_va_buffer(&pad->cc->scc, - "failed to add %s (%s)\n", path, para_strerror(-ret)); + send_ret = pad->cc->use_sideband? + send_sb_va(&pad->cc->scc, SBD_ERROR_LOG, + "failed to add %s (%s)\n", path, + para_strerror(-ret)) + : + sc_send_va_buffer(&pad->cc->scc, + "failed to add %s (%s)\n", path, + para_strerror(-ret)); free(obj.data); clear_afhi(afhi_ptr); /* Stop adding files only on send errors. */ @@ -1956,7 +1992,11 @@ int com_add(struct command_context *cc) char *path; ret = verify_path(cc->argv[i], &path); if (ret < 0) { - ret = sc_send_va_buffer(&cc->scc, "%s: %s\n", + ret = cc->use_sideband? + send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s: %s\n", + cc->argv[i], para_strerror(-ret)) + : + sc_send_va_buffer(&cc->scc, "%s: %s\n", cc->argv[i], para_strerror(-ret)); if (ret < 0) return ret; @@ -1964,9 +2004,14 @@ int com_add(struct command_context *cc) } ret = stat(path, &statbuf); if (ret < 0) { - ret = sc_send_va_buffer(&cc->scc, - "failed to stat %s (%s)\n", path, - strerror(errno)); + ret = cc->use_sideband? + send_sb_va(&cc->scc, SBD_ERROR_LOG, + "failed to stat %s (%s)\n", path, + strerror(errno)) + : + sc_send_va_buffer(&cc->scc, + "failed to stat %s (%s)\n", path, + strerror(errno)); free(path); if (ret < 0) return ret; @@ -1978,8 +2023,12 @@ int com_add(struct command_context *cc) else ret = add_one_audio_file(path, &pad); if (ret < 0) { - sc_send_va_buffer(&cc->scc, "%s: %s\n", path, - para_strerror(-ret)); + if (cc->use_sideband) + send_sb_va(&cc->scc, SBD_OUTPUT, "%s: %s\n", path, + para_strerror(-ret)); + else + sc_send_va_buffer(&cc->scc, "%s: %s\n", path, + para_strerror(-ret)); free(path); return ret; } @@ -2082,7 +2131,7 @@ static void com_touch_callback(int fd, const struct osl_object *query) .pb = { .max_size = shm_get_shmmax(), .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .max_size_handler = afs_max_size_handler } }; int ret, ret2 = 0; @@ -2103,7 +2152,7 @@ static void com_touch_callback(int fd, const struct osl_object *query) else if (pmd.num_matches == 0) ret2 = para_printf(&tad.pb, "no matches\n"); if (ret2 >= 0 && tad.pb.offset) - pass_buffer_as_shm(tad.pb.buf, tad.pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, tad.pb.buf, tad.pb.offset); free(tad.pb.buf); } @@ -2175,9 +2224,9 @@ int com_touch(struct command_context *cc) if (i >= cc->argc) return -E_AFT_SYNTAX; ret = send_option_arg_callback_request(&query, cc->argc - i, - cc->argv + i, com_touch_callback, sc_send_result, cc); + cc->argv + i, com_touch_callback, afs_cb_result_handler, cc); if (ret < 0) - sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(-ret)); + send_strerror(cc, -ret); return ret; } @@ -2223,7 +2272,7 @@ static void com_rm_callback(int fd, const struct osl_object *query) .pb = { .max_size = shm_get_shmmax(), .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .max_size_handler = afs_max_size_handler } }; int ret; @@ -2250,7 +2299,7 @@ static void com_rm_callback(int fd, const struct osl_object *query) pmd.num_matches); } if (ret >= 0 && crd.pb.offset) - pass_buffer_as_shm(crd.pb.buf, crd.pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, crd.pb.buf, crd.pb.offset); free(crd.pb.buf); } @@ -2286,9 +2335,9 @@ int com_rm(struct command_context *cc) if (i >= cc->argc) return -E_AFT_SYNTAX; ret = send_option_arg_callback_request(&query, cc->argc - i, - cc->argv + i, com_rm_callback, sc_send_result, cc); + cc->argv + i, com_rm_callback, afs_cb_result_handler, cc); if (ret < 0) - sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(-ret)); + send_strerror(cc, -ret); return ret; } @@ -2365,7 +2414,7 @@ static void com_cpsi_callback(int fd, const struct osl_object *query) .pb = { .max_size = shm_get_shmmax(), .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .max_size_handler = afs_max_size_handler } }; int ret; @@ -2396,7 +2445,7 @@ out: para_printf(&cad.pb, "nothing copied\n"); } if (cad.pb.offset) - pass_buffer_as_shm(cad.pb.buf, cad.pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, cad.pb.buf, cad.pb.offset); free(cad.pb.buf); } @@ -2445,9 +2494,9 @@ int com_cpsi(struct command_context *cc) if (!(flags & ~CPSI_FLAG_VERBOSE)) /* no copy flags given */ flags = ~(unsigned)CPSI_FLAG_VERBOSE | flags; ret = send_option_arg_callback_request(&options, cc->argc - i, - cc->argv + i, com_cpsi_callback, sc_send_result, cc); + cc->argv + i, com_cpsi_callback, afs_cb_result_handler, cc); if (ret < 0) - sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(-ret)); + send_strerror(cc, -ret); return ret; } @@ -2459,7 +2508,7 @@ static void afs_stat_callback(int fd, const struct osl_object *query) if (!buf) return; - pass_buffer_as_shm(buf, strlen(buf), &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, buf, strlen(buf)); } /** @@ -2481,7 +2530,8 @@ int send_afs_status(struct command_context *cc, int parser_friendly) struct osl_object query = {.data = &parser_friendly, .size = sizeof(parser_friendly)}; - return send_callback_request(afs_stat_callback, &query, sc_send_result, cc); + return send_callback_request(afs_stat_callback, &query, + afs_cb_result_handler, cc); } /* TODO: optionally fix problems by removing offending rows */ @@ -2538,8 +2588,11 @@ void aft_check_callback(int fd, __a_unused const struct osl_object *query) { struct para_buffer pb = { .max_size = shm_get_shmmax(), - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }, + .max_size_handler = afs_max_size_handler }; int ret = para_printf(&pb, "checking audio file table...\n"); @@ -2547,7 +2600,7 @@ void aft_check_callback(int fd, __a_unused const struct osl_object *query) return; audio_file_loop(&pb, check_audio_file); if (pb.offset) - pass_buffer_as_shm(pb.buf, pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset); free(pb.buf); } diff --git a/attribute.c b/attribute.c index 19700944..638e22ea 100644 --- a/attribute.c +++ b/attribute.c @@ -16,6 +16,7 @@ #include "afh.h" #include "afs.h" #include "ipc.h" +#include "sideband.h" #include "command.h" static struct osl_table *attribute_table; @@ -151,8 +152,11 @@ static void com_lsatt_callback(int fd, const struct osl_object *query) .flags = *(unsigned *) query->data, .pb = { .max_size = shm_get_shmmax(), - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }, + .max_size_handler = afs_max_size_handler } }; @@ -172,7 +176,7 @@ static void com_lsatt_callback(int fd, const struct osl_object *query) pmd.pm_flags |= PM_REVERSE_LOOP; for_each_matching_row(&pmd); if (laad.pb.offset) - pass_buffer_as_shm(laad.pb.buf, laad.pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, laad.pb.buf, laad.pb.offset); free(laad.pb.buf); } @@ -204,12 +208,14 @@ int com_lsatt(struct command_context *cc) } } ret = send_option_arg_callback_request(&options, cc->argc - i, cc->argv + i, - com_lsatt_callback, sc_send_result, cc); - if (!ret) { - if (cc->argc > 1) - ret = sc_send_va_buffer(&cc->scc, "no matches\n"); - } else if (ret < 0) - sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(-ret)); + com_lsatt_callback, afs_cb_result_handler, cc); + if (ret < 0) + send_strerror(cc, -ret); + else if (ret == 0 && cc->argc > 1) + ret = cc->use_sideband? + send_sb_va(&cc->scc, SBD_ERROR_LOG, "no matches\n") + : + sc_send_va_buffer(&cc->scc, "no matches\n"); return ret; } @@ -297,8 +303,11 @@ static void com_addatt_callback(int fd, const struct osl_object *query) int ret = 1, ret2 = 0; struct para_buffer pb = { .max_size = shm_get_shmmax(), - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }, + .max_size_handler = afs_max_size_handler }; size_t len; @@ -354,7 +363,7 @@ out: if (ret < 0 && ret2 >= 0) para_printf(&pb, "%s: %s\n", p, para_strerror(-ret)); if (pb.offset) - pass_buffer_as_shm(pb.buf, pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset); free(pb.buf); } @@ -365,9 +374,9 @@ int com_addatt(struct command_context *cc) if (cc->argc < 2) return -E_ATTR_SYNTAX; ret = send_standard_callback_request(cc->argc - 1, cc->argv + 1, - com_addatt_callback, sc_send_result, cc); + com_addatt_callback, afs_cb_result_handler, cc); if (ret < 0) - sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(-ret)); + send_strerror(cc, -ret); return ret; } @@ -380,8 +389,11 @@ static void com_mvatt_callback(int fd, const struct osl_object *query) struct osl_row *row; struct para_buffer pb = { .max_size = shm_get_shmmax(), - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }, + .max_size_handler = afs_max_size_handler, }; int ret; @@ -397,7 +409,7 @@ out: else afs_event(ATTRIBUTE_RENAME, &pb, NULL); if (pb.offset) - pass_buffer_as_shm(pb.buf, pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset); free(pb.buf); } @@ -408,9 +420,9 @@ int com_mvatt(struct command_context *cc) if (cc->argc != 3) return -E_ATTR_SYNTAX; ret = send_standard_callback_request(cc->argc - 1, cc->argv + 1, - com_mvatt_callback, sc_send_result, cc); + com_mvatt_callback, afs_cb_result_handler, cc); if (ret < 0) - sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(-ret)); + send_strerror(cc, -ret); return ret; } @@ -450,8 +462,11 @@ static void com_rmatt_callback(int fd, const struct osl_object *query) .num_removed = 0, .pb = { .max_size = shm_get_shmmax(), - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }, + .max_size_handler = afs_max_size_handler, } }; int ret, ret2 = 0; @@ -469,7 +484,7 @@ static void com_rmatt_callback(int fd, const struct osl_object *query) else if (!raad.num_removed) ret2 = para_printf(&raad.pb, "no match -- nothing removed\n"); if (ret2 >= 0 && raad.pb.offset) - pass_buffer_as_shm(raad.pb.buf, raad.pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, raad.pb.buf, raad.pb.offset); free(raad.pb.buf); } @@ -480,9 +495,9 @@ int com_rmatt(struct command_context *cc) if (cc->argc < 2) return -E_ATTR_SYNTAX; ret = send_standard_callback_request(cc->argc - 1, cc->argv + 1, - com_rmatt_callback, sc_send_result, cc); + com_rmatt_callback, afs_cb_result_handler, cc); if (ret < 0) - sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(-ret)); + send_strerror(cc, -ret); return ret; } diff --git a/blob.c b/blob.c index 409e3487..f51f4de0 100644 --- a/blob.c +++ b/blob.c @@ -18,6 +18,7 @@ #include "afs.h" #include "ipc.h" #include "portable_io.h" +#include "sideband.h" #include "command.h" /** @@ -133,8 +134,11 @@ static void com_lsblob_callback(struct osl_table *table, .flags = *(uint32_t *)query->data, .pb = { .max_size = shm_get_shmmax(), - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }, + .max_size_handler = afs_max_size_handler, } }; struct pattern_match_data pmd = { @@ -160,7 +164,7 @@ static void com_lsblob_callback(struct osl_table *table, else if (pmd.num_matches == 0 && pmd.patterns.size > 0) para_printf(&lbad.pb, "no matches\n"); if (lbad.pb.offset) - pass_buffer_as_shm(lbad.pb.buf, lbad.pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, lbad.pb.buf, lbad.pb.offset); free(lbad.pb.buf); } @@ -195,20 +199,20 @@ static int com_lsblob(callback_function *f, struct command_context *cc) // if (argc > i) // return -E_BLOB_SYNTAX; return send_option_arg_callback_request(&options, cc->argc - i, - cc->argv + i, f, sc_send_result, cc); + cc->argv + i, f, afs_cb_result_handler, cc); } static int cat_blob(struct osl_table *table, struct osl_row *row, __a_unused const char *name, void *data) { - int ret = 0, ret2; + int ret = 0, ret2, fd = *(int *)data; struct osl_object obj; ret = osl(osl_open_disk_object(table, row, BLOBCOL_DEF, &obj)); if (ret < 0) return (ret == osl(-E_OSL_EMPTY))? 0 : ret; assert(obj.size > 0); - ret = pass_buffer_as_shm(obj.data, obj.size, data); + ret = pass_buffer_as_shm(fd, SBD_OUTPUT, obj.data, obj.size); ret2 = osl(osl_close_disk_object(&obj)); return (ret < 0)? ret : ret2; } @@ -228,7 +232,7 @@ static void com_catblob_callback(struct osl_table *table, int fd, for_each_matching_row(&pmd); if (pmd.num_matches == 0) { char err_msg[] = "no matches\n"; - pass_buffer_as_shm(err_msg, sizeof(err_msg), &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, err_msg, sizeof(err_msg)); } } @@ -237,7 +241,7 @@ static int com_catblob(callback_function *f, struct command_context *cc) if (cc->argc < 2) return -E_BLOB_SYNTAX; return send_standard_callback_request(cc->argc - 1, cc->argv + 1, f, - sc_send_result, cc); + afs_cb_result_handler, cc); } /** Used for removing rows from a blob table. */ @@ -265,8 +269,11 @@ static void com_rmblob_callback(struct osl_table *table, int fd, struct rmblob_data rmbd = { .pb = { .max_size = shm_get_shmmax(), - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }, + .max_size_handler = afs_max_size_handler, } }; struct pattern_match_data pmd = { @@ -292,7 +299,7 @@ static void com_rmblob_callback(struct osl_table *table, int fd, } out: if (ret2 >= 0 && rmbd.pb.offset) - pass_buffer_as_shm(rmbd.pb.buf, rmbd.pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, rmbd.pb.buf, rmbd.pb.offset); free(rmbd.pb.buf); } @@ -301,7 +308,7 @@ static int com_rmblob(callback_function *f, struct command_context *cc) if (cc->argc < 2) return -E_MOOD_SYNTAX; return send_option_arg_callback_request(NULL, cc->argc - 1, cc->argv + 1, f, - sc_send_result, cc); + afs_cb_result_handler, cc); } static void com_addblob_callback(struct osl_table *table, __a_unused int fd, @@ -432,7 +439,10 @@ static int stdin_command(struct command_context *cc, struct osl_object *arg_obj, struct osl_object query, stdin_obj; int ret; - ret = sc_send_buffer(&cc->scc, AWAITING_DATA_MSG); + if (cc->use_sideband) + ret = send_sb(&cc->scc, NULL, 0, SBD_AWAITING_DATA, false); + else + ret = sc_send_buffer(&cc->scc, AWAITING_DATA_MSG); if (ret < 0) return ret; ret = fd2buf(&cc->scc, max_len, &stdin_obj); diff --git a/client.c b/client.c index 5f511708..c194e192 100644 --- a/client.c +++ b/client.c @@ -603,14 +603,23 @@ int main(int argc, char *argv[]) EMBRACE(.name = "stdout", .parent = ct->btrn)); register_task(&sched, &svt); ret = schedule(&sched); + if (ret >= 0 && ct->task.error < 0) { + switch(ct->task.error) { + /* these are not errors */ + case -E_SERVER_CMD_SUCCESS: + case -E_EOF: + case -E_SERVER_EOF: + case -E_BTR_EOF: + ret = 0; + break; + default: ret = ct->task.error; + } + } out: + if (ret < 0) + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); client_close(ct); btr_free_node(sit.btrn); btr_free_node(sot.btrn); - if (ret < 0) { - /* can not use PARA_LOG here because ct is NULL */ - fprintf(stderr, "%s\n", para_strerror(-ret)); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; + return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/client.h b/client.h index 8490b72f..b841b7e1 100644 --- a/client.h +++ b/client.h @@ -14,6 +14,8 @@ enum { CL_RECEIVED_WELCOME, /** Client sends the authentification request. */ CL_SENT_AUTH, + /** Server sends encrypted challenge. */ + CL_RECEIVED_CHALLENGE, /** Client solves the challenge and sends the result. */ CL_SENT_CH_RESPONSE, /** Server accepts this authentication. */ @@ -32,6 +34,12 @@ struct client_task { int status; /** The file descriptor and the session keys. */ struct stream_cipher_context scc; + /** True if this connections uses the sideband API. */ + bool use_sideband; + /** The sideband context, ignored if \a use_sideband is false. */ + struct sb_context *sbc; + /** The hash value of the decrypted challenge. */ + unsigned char *challenge_hash; /** The configuration (including the command). */ struct client_args_info conf; /** The config file for client options. */ @@ -44,6 +52,8 @@ struct client_task { struct task task; /** The buffer tree node of the client task. */ struct btr_node *btrn; + /** List of features supported by the server. */ + char **features; }; void client_disconnect(struct client_task *ct); diff --git a/client_common.c b/client_common.c index 5bd2241b..649a1b4f 100644 --- a/client_common.c +++ b/client_common.c @@ -17,6 +17,7 @@ #include "crypt.h" #include "net.h" #include "fd.h" +#include "sideband.h" #include "string.h" #include "client.cmdline.h" #include "client.h" @@ -42,6 +43,7 @@ void client_disconnect(struct client_task *ct) return; if (ct->scc.fd >= 0) close(ct->scc.fd); + free_argv(ct->features); sc_free(ct->scc.recv); ct->scc.recv = NULL; sc_free(ct->scc.send); @@ -66,6 +68,8 @@ void client_close(struct client_task *ct) free(ct->config_file); free(ct->key_file); client_cmdline_parser_free(&ct->conf); + free(ct->challenge_hash); + sb_free(ct->sbc); free(ct); } @@ -101,6 +105,7 @@ static void client_pre_select(struct sched *s, struct task *t) case CL_RECEIVED_WELCOME: case CL_RECEIVED_PROCEED: + case CL_RECEIVED_CHALLENGE: para_fd_set(ct->scc.fd, &s->wfds, &s->max_fileno); return; @@ -152,6 +157,162 @@ static int client_recv_buffer(struct client_task *ct, fd_set *rfds, return 0; } +static int send_sb(struct client_task *ct, void *buf, size_t numbytes, + enum sb_designator band, bool dont_free) +{ + int ret, fd = ct->scc.fd; + struct iovec iov[2]; + + if (!ct->sbc) { + struct sb_buffer sbb; + sb_transformation trafo = ct->status < CL_RECEIVED_PROCEED? + NULL : sc_trafo; + sbb = (typeof(sbb))SBB_INIT(band, buf, numbytes); + ct->sbc = sb_new_send(&sbb, dont_free, trafo, ct->scc.send); + } + ret = sb_get_send_buffers(ct->sbc, iov); + ret = xwritev(fd, iov, ret); + if (ret < 0) { + sb_free(ct->sbc); + ct->sbc = NULL; + return ret; + } + if (sb_sent(ct->sbc, ret)) { + ct->sbc = NULL; + return 1; + } + return 0; +} + +static int recv_sb(struct client_task *ct, fd_set *rfds, + struct sb_buffer *result) +{ + int ret; + size_t n; + sb_transformation trafo; + void *trafo_context; + struct iovec iov; + + if (!FD_ISSET(ct->scc.fd, rfds)) + return 0; + if (ct->status < CL_SENT_CH_RESPONSE) + trafo = trafo_context = NULL; + else { + trafo = sc_trafo; + trafo_context = ct->scc.recv; + } + if (!ct->sbc) + ct->sbc = sb_new_recv(0, trafo, trafo_context); +again: + sb_get_recv_buffer(ct->sbc, &iov); + ret = read_nonblock(ct->scc.fd, iov.iov_base, iov.iov_len, rfds, &n); + if (ret < 0) { + sb_free(ct->sbc); + ct->sbc = NULL; + return ret; + } + if (n == 0) + return 0; + if (!sb_received(ct->sbc, n, result)) + goto again; + ct->sbc = NULL; + return 1; +} + + +static char **parse_features(char *buf) +{ + int i; + const char id[] = "\nFeatures: "; + char *p, *q, **features; + + p = strstr(buf, id); + if (!p) + return NULL; + p += strlen(id); + q = strchr(p, '\n'); + if (!q) + return NULL; + *q = '\0'; + create_argv(p, ",", &features); + for (i = 0; features[i]; i++) + PARA_INFO_LOG("server feature: %s\n", features[i]); + return features; +} + +static int dispatch_sbb(struct client_task *ct, struct sb_buffer *sbb) +{ + int ret; + const char *designator[] = {SB_DESIGNATORS_ARRAY}; + + if (!sbb) + return 0; + if (sbb->band < NUM_SB_DESIGNATORS) + PARA_DEBUG_LOG("band: %s\n", designator[sbb->band]); + + switch (sbb->band) { + case SBD_OUTPUT: + if (iov_valid(&sbb->iov)) + btr_add_output(sbb->iov.iov_base, sbb->iov.iov_len, + ct->btrn); + ret = 1; + goto out; + case SBD_DEBUG_LOG: + case SBD_INFO_LOG: + case SBD_NOTICE_LOG: + case SBD_WARNING_LOG: + case SBD_ERROR_LOG: + case SBD_CRIT_LOG: + case SBD_EMERG_LOG: + if (iov_valid(&sbb->iov)) { + int ll = sbb->band - SBD_DEBUG_LOG; + para_log(ll, "remote: %s", (char *)sbb->iov.iov_base); + } + ret = 1; + goto deallocate; + case SBD_EXIT__SUCCESS: + ret = -E_SERVER_CMD_SUCCESS; + goto deallocate; + case SBD_EXIT__FAILURE: + ret = -E_SERVER_CMD_FAILURE; + goto deallocate; + default: + PARA_ERROR_LOG("invalid band %d\n", sbb->band); + ret = -E_BAD_BAND; + goto deallocate; + } +deallocate: + free(sbb->iov.iov_base); +out: + sbb->iov.iov_base = NULL; + return ret; +} + +static bool has_feature(const char *feature, struct client_task *ct) +{ + return find_arg(feature, ct->features) >= 0? true : false; +} + +static int send_sb_command(struct client_task *ct) +{ + int i; + char *command, *p; + size_t len = 0; + + if (ct->sbc) + return send_sb(ct, NULL, 0, 0, false); + + for (i = 0; i < ct->conf.inputs_num; i++) + len += strlen(ct->conf.inputs[i]) + 1; + p = command = para_malloc(len); + for (i = 0; i < ct->conf.inputs_num; i++) { + strcpy(p, ct->conf.inputs[i]); + p += strlen(ct->conf.inputs[i]) + 1; + } + PARA_DEBUG_LOG("--> %s\n", command); + return send_sb(ct, command, len, SBD_COMMAND, false); +} + /** * The post select hook for client commands. * @@ -181,13 +342,18 @@ static void client_post_select(struct sched *s, struct task *t) ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n); if (ret < 0 || n == 0) goto out; + ct->features = parse_features(buf); ct->status = CL_RECEIVED_WELCOME; return; case CL_RECEIVED_WELCOME: /* send auth command */ - sprintf(buf, AUTH_REQUEST_MSG "%s", ct->user); - PARA_INFO_LOG("--> %s\n", buf); if (!FD_ISSET(ct->scc.fd, &s->wfds)) return; + if (has_feature("sideband", ct)) { + ct->use_sideband = true; + sprintf(buf, AUTH_REQUEST_MSG "%s sideband", ct->user); + } else + sprintf(buf, AUTH_REQUEST_MSG "%s", ct->user); + PARA_INFO_LOG("--> %s\n", buf); ret = write_buffer(ct->scc.fd, buf); if (ret < 0) goto out; @@ -202,30 +368,73 @@ static void client_post_select(struct sched *s, struct task *t) /* decrypted challenge/session key buffer */ unsigned char crypt_buf[1024]; /* the SHA1 of the decrypted challenge */ - unsigned char challenge_hash[HASH_SIZE]; - ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n); - if (ret < 0 || n == 0) - goto out; - PARA_INFO_LOG("<-- [challenge] (%zu bytes)\n", n); - ret = priv_decrypt(ct->key_file, crypt_buf, - (unsigned char *)buf, n); - if (ret < 0) - goto out; - hash_function((char *)crypt_buf, CHALLENGE_SIZE, challenge_hash); + if (ct->use_sideband) { + struct sb_buffer sbb; + ret = recv_sb(ct, &s->rfds, &sbb); + if (ret <= 0) + goto out; + if (sbb.band != SBD_CHALLENGE) { + ret = -E_BAD_BAND; + free(sbb.iov.iov_base); + goto out; + } + n = sbb.iov.iov_len; + PARA_INFO_LOG("<-- [challenge] (%zu bytes)\n", n); + ret = priv_decrypt(ct->key_file, crypt_buf, + sbb.iov.iov_base, n); + free(sbb.iov.iov_base); + if (ret < 0) + goto out; + } else { + ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n); + if (ret < 0 || n == 0) + goto out; + PARA_INFO_LOG("<-- [challenge] (%zu bytes)\n", n); + ret = priv_decrypt(ct->key_file, crypt_buf, + (unsigned char *)buf, n); + if (ret < 0) + goto out; + } + ct->challenge_hash = para_malloc(HASH_SIZE); + hash_function((char *)crypt_buf, CHALLENGE_SIZE, ct->challenge_hash); ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN); ct->scc.recv = sc_new(crypt_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, SESSION_KEY_LEN); - hash_to_asc(challenge_hash, buf); + hash_to_asc(ct->challenge_hash, buf); PARA_INFO_LOG("--> %s\n", buf); - ret = write_all(ct->scc.fd, (char *)challenge_hash, HASH_SIZE); - if (ret < 0) - goto out; - ct->status = CL_SENT_CH_RESPONSE; + ct->status = CL_RECEIVED_CHALLENGE; return; } + case CL_RECEIVED_CHALLENGE: + if (ct->use_sideband) { + ret = send_sb(ct, ct->challenge_hash, HASH_SIZE, + SBD_CHALLENGE_RESPONSE, false); + if (ret != 0) + ct->challenge_hash = NULL; + if (ret <= 0) + goto out; + } else { + ret = write_all(ct->scc.fd, (char *)ct->challenge_hash, HASH_SIZE); + if (ret < 0) + goto out; + } + ct->status = CL_SENT_CH_RESPONSE; + goto out; case CL_SENT_CH_RESPONSE: /* read server response */ { + if (ct->use_sideband) { + struct sb_buffer sbb; + ret = recv_sb(ct, &s->rfds, &sbb); + if (ret <= 0) + goto out; + free(sbb.iov.iov_base); + if (sbb.band != SBD_PROCEED) + ret = -E_BAD_BAND; + else + ct->status = CL_RECEIVED_PROCEED; + goto out; + } ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n); if (ret < 0 || n == 0) goto out; @@ -244,6 +453,13 @@ static void client_post_select(struct sched *s, struct task *t) char *command = NULL; if (!FD_ISSET(ct->scc.fd, &s->wfds)) return; + if (ct->use_sideband) { + ret = send_sb_command(ct); + if (ret <= 0) + goto out; + ct->status = CL_SENT_COMMAND; + return; + } for (i = 0; i < ct->conf.inputs_num; i++) { char *tmp = command; command = make_message("%s\n%s", command? @@ -262,6 +478,20 @@ static void client_post_select(struct sched *s, struct task *t) case CL_SENT_COMMAND: { char *buf2; + if (ct->use_sideband) { + struct sb_buffer sbb; + ret = recv_sb(ct, &s->rfds, &sbb); + if (ret <= 0) + goto out; + if (sbb.band == SBD_AWAITING_DATA) { + ct->status = CL_SENDING; + free(sbb.iov.iov_base); + goto out; + } + ct->status = CL_RECEIVING; + ret = dispatch_sbb(ct, &sbb); + goto out; + } /* can not use "buf" here because we need a malloced buffer */ buf2 = para_malloc(CLIENT_BUFSIZE); ret = client_recv_buffer(ct, &s->rfds, buf2, CLIENT_BUFSIZE, &n); @@ -309,6 +539,13 @@ static void client_post_select(struct sched *s, struct task *t) */ if (!FD_ISSET(ct->scc.fd, &s->rfds)) return; + if (ct->use_sideband) { + struct sb_buffer sbb; + ret = recv_sb(ct, &s->rfds, &sbb); + if (ret > 0) + ret = dispatch_sbb(ct, &sbb); + goto out; + } buf2 = para_malloc(CLIENT_BUFSIZE); ret = client_recv_buffer(ct, &s->rfds, buf2, CLIENT_BUFSIZE, &n); if (n > 0) { @@ -322,7 +559,8 @@ static void client_post_select(struct sched *s, struct task *t) out: t->error = ret; if (ret < 0) { - if (ret != -E_SERVER_EOF && ret != -E_BTR_EOF) + if (!ct->use_sideband && ret != -E_SERVER_EOF && + ret != -E_BTR_EOF && ret != -E_EOF) PARA_ERROR_LOG("%s\n", para_strerror(-t->error)); btr_remove_node(btrn); } diff --git a/command.c b/command.c index f520e3ea..2cac57f9 100644 --- a/command.c +++ b/command.c @@ -15,6 +15,7 @@ #include "para.h" #include "error.h" #include "crypt.h" +#include "sideband.h" #include "command.h" #include "server.cmdline.h" #include "string.h" @@ -101,7 +102,8 @@ static char *vss_get_status_flags(unsigned int flags) return msg; } -static char *get_status(struct misc_meta_data *nmmd, int parser_friendly) +static unsigned get_status(struct misc_meta_data *nmmd, int parser_friendly, + char **result) { char mtime[30] = ""; char *status, *flags; /* vss status info */ @@ -141,7 +143,8 @@ static char *get_status(struct misc_meta_data *nmmd, int parser_friendly) free(flags); free(status); free(ut); - return b.buf; + *result = b.buf; + return b.offset; } static int check_sender_args(int argc, char * const * argv, struct sender_command_data *scd) @@ -191,19 +194,142 @@ static int check_sender_args(int argc, char * const * argv, struct sender_comman return 1; } +/** + * Send a sideband packet through a blocking file descriptor. + * + * \param scc fd and crypto keys. + * \param buf The buffer to send. + * \param numbytes The size of \a buf. + * \param band The sideband designator of this packet. + * \param dont_free If true, never deallocate \a buf. + * + * The nonblock flag must be disabled for the file descriptor given by \a scc. + * + * Stream cipher encryption is automatically activated if neccessary via the + * sideband transformation, depending on the value of \a band. + * + * \return Standard. + * + * \sa \ref send_sb_va(). + */ +int send_sb(struct stream_cipher_context *scc, void *buf, size_t numbytes, + int band, bool dont_free) +{ + int ret; + struct sb_context *sbc; + struct iovec iov[2]; + struct sb_buffer sbb = SBB_INIT(band, buf, numbytes); + sb_transformation trafo = band < SBD_PROCEED? NULL : sc_trafo; + + sbc = sb_new_send(&sbb, dont_free, trafo, scc->send); + do { + ret = sb_get_send_buffers(sbc, iov); + ret = xwritev(scc->fd, iov, ret); + if (ret < 0) + goto fail; + } while (sb_sent(sbc, ret) == false); + return 1; +fail: + sb_free(sbc); + return ret; +} + +/** + * Create a variable sized buffer and send it as a sideband packet. + * + * \param scc Passed to \ref send_sb. + * \param band See \ref send_sb. + * \param fmt The format string. + * + * \return The return value of the underlying call to \ref send_sb. + */ +__printf_3_4 int send_sb_va(struct stream_cipher_context *scc, int band, + const char *fmt, ...) +{ + va_list ap; + char *msg; + int ret; + + va_start(ap, fmt); + ret = xvasprintf(&msg, fmt, ap); + va_end(ap); + return send_sb(scc, msg, ret, band, false); +} + +int send_strerror(struct command_context *cc, int err) +{ + return cc->use_sideband? + send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s\n", para_strerror(err)) + : + sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(err)); +} + +/** + * Send a sideband packet through a blocking file descriptor. + * + * \param scc fd and crypto keys. + * \param expected_band The expected band designator. + * \param max_size Passed to \ref sb_new_recv(). + * \param result Body of the sideband packet is returned here. + * + * If \a expected_band is not \p SBD_ANY, the band designator of the received + * sideband packet is compared to \a expected_band and a mismatch is considered + * an error. + * + * \return Standard. + */ +int recv_sb(struct stream_cipher_context *scc, + enum sb_designator expected_band, + size_t max_size, struct iovec *result) +{ + int ret; + struct sb_context *sbc; + struct iovec iov; + struct sb_buffer sbb; + sb_transformation trafo; + + trafo = expected_band != SBD_ANY && expected_band < SBD_PROCEED? + NULL : sc_trafo; + sbc = sb_new_recv(max_size, trafo, scc->recv); + for (;;) { + sb_get_recv_buffer(sbc, &iov); + ret = recv_bin_buffer(scc->fd, iov.iov_base, iov.iov_len); + if (ret == 0) + ret = -E_EOF; + if (ret < 0) + goto fail; + ret = sb_received(sbc, ret, &sbb); + if (ret < 0) + goto fail; + if (ret > 0) + break; + } + ret = -E_BAD_BAND; + if (expected_band != SBD_ANY && sbb.band != expected_band) + goto fail; + *result = sbb.iov; + return 1; +fail: + sb_free(sbc); + return ret; +} + int com_sender(struct command_context *cc) { - int i, ret; + int i, ret = 0; char *msg = NULL; struct sender_command_data scd; if (cc->argc < 2) { for (i = 0; senders[i].name; i++) { - char *tmp = make_message("%s%s\n", - msg? msg : "", senders[i].name); + char *tmp; + ret = xasprintf(&tmp, "%s%s\n", msg? msg : "", + senders[i].name); free(msg); msg = tmp; } + if (cc->use_sideband) + return send_sb(&cc->scc, msg, ret, SBD_OUTPUT, false); ret = sc_send_buffer(&cc->scc, msg); free(msg); return ret; @@ -213,6 +339,9 @@ int com_sender(struct command_context *cc) if (scd.sender_num < 0) return ret; msg = senders[scd.sender_num].help(); + if (cc->use_sideband) + return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, + false); ret = sc_send_buffer(&cc->scc, msg); free(msg); return ret; @@ -245,8 +374,7 @@ int com_sender(struct command_context *cc) int com_si(struct command_context *cc) { int i, ret; - char *ut; - char *sender_info = NULL; + char *msg, *ut, *sender_info = NULL; if (cc->argc != 1) return -E_COMMAND_SYNTAX; @@ -257,7 +385,7 @@ int com_si(struct command_context *cc) free(info); } ut = get_server_uptime_str(now); - ret = sc_send_va_buffer(&cc->scc, "version: " GIT_VERSION "\n" + ret = xasprintf(&msg, "version: " GIT_VERSION "\n" "up: %s\nplayed: %u\n" "server_pid: %d\n" "afs_pid: %d\n" @@ -278,18 +406,27 @@ int com_si(struct command_context *cc) mutex_unlock(mmd_mutex); free(ut); free(sender_info); + if (cc->use_sideband) + return send_sb(&cc->scc, msg, ret, SBD_OUTPUT, false); + ret = sc_send_bin_buffer(&cc->scc, msg, ret); + free(msg); return ret; } /* version */ int com_version(struct command_context *cc) { + char *msg; + size_t len; + if (cc->argc != 1) return -E_COMMAND_SYNTAX; - return sc_send_buffer(&cc->scc, VERSION_TEXT("server") - "built: " BUILD_DATE "\n" - UNAME_RS ", " CC_VERSION "\n" - ); + msg = VERSION_TEXT("server") "built: " BUILD_DATE "\n" UNAME_RS + ", " CC_VERSION "\n"; + len = strlen(msg); + if (cc->use_sideband) + return send_sb(&cc->scc, msg, len, SBD_OUTPUT, true); + return sc_send_bin_buffer(&cc->scc, msg, len); } #define EMPTY_STATUS_ITEMS \ @@ -325,10 +462,16 @@ int com_version(struct command_context *cc) * * This is used by vss when currently no audio file is open. */ -static char *empty_status_items(int parser_friendly) +static unsigned empty_status_items(int parser_friendly, char **result) { + static char *esi; + static unsigned len; + + if (esi) + goto out; + if (parser_friendly) - return make_message( + len = xasprintf(&esi, #define ITEM(x) "0004 %02x:\n" EMPTY_STATUS_ITEMS #undef ITEM @@ -336,14 +479,18 @@ static char *empty_status_items(int parser_friendly) EMPTY_STATUS_ITEMS #undef ITEM ); - return make_message( - #define ITEM(x) "%s:\n" - EMPTY_STATUS_ITEMS - #undef ITEM - #define ITEM(x) ,status_item_list[SI_ ## x] - EMPTY_STATUS_ITEMS - #undef ITEM - ); + else + len = xasprintf(&esi, + #define ITEM(x) "%s:\n" + EMPTY_STATUS_ITEMS + #undef ITEM + #define ITEM(x) ,status_item_list[SI_ ## x] + EMPTY_STATUS_ITEMS + #undef ITEM + ); +out: + *result = esi; + return len; } #undef EMPTY_STATUS_ITEMS @@ -352,7 +499,7 @@ int com_stat(struct command_context *cc) { int i, ret; struct misc_meta_data tmp, *nmmd = &tmp; - char *s; + char *s, *esi = NULL; int32_t num = 0; int parser_friendly = 0; @@ -382,16 +529,22 @@ int com_stat(struct command_context *cc) return -E_COMMAND_SYNTAX; for (;;) { mmd_dup(nmmd); - s = get_status(nmmd, parser_friendly); - ret = sc_send_buffer(&cc->scc, s); - free(s); + ret = get_status(nmmd, parser_friendly, &s); + if (cc->use_sideband) + ret = send_sb(&cc->scc, s, ret, SBD_OUTPUT, false); + else { + ret = sc_send_bin_buffer(&cc->scc, s, ret); + free(s); + } if (ret < 0) goto out; if (nmmd->vss_status_flags & VSS_NEXT) { - static char *esi; - if (!esi) - esi = empty_status_items(parser_friendly); - ret = sc_send_buffer(&cc->scc, esi); + ret = empty_status_items(parser_friendly, &esi); + if (cc->use_sideband) + ret = send_sb(&cc->scc, esi, ret, SBD_OUTPUT, + true); + else + ret = sc_send_bin_buffer(&cc->scc, esi, ret); if (ret < 0) goto out; } else @@ -400,29 +553,34 @@ int com_stat(struct command_context *cc) if (num > 0 && !--num) goto out; sleep(50); + ret = -E_SERVER_CRASH; if (getppid() == 1) - return -E_SERVER_CRASH; + goto out; } out: + free(esi); return ret; } -static int send_list_of_commands(struct stream_cipher_context *scc, struct server_command *cmd, +static int send_list_of_commands(struct command_context *cc, struct server_command *cmd, const char *handler) { - int ret, i; + int ret; + char *msg = NULL; - for (i = 1; cmd->name; cmd++, i++) { - char *perms = cmd_perms_itohuman(cmd->perms); - ret = sc_send_va_buffer(scc, "%s\t%s\t%s\t%s\n", cmd->name, - handler, - perms, - cmd->description); + for (; cmd->name; cmd++) { + char *tmp, *perms = cmd_perms_itohuman(cmd->perms); + tmp = make_message("%s\t%s\t%s\t%s\n", cmd->name, handler, + perms, cmd->description); free(perms); - if (ret < 0) - return ret; + msg = para_strcat(msg, tmp); + free(tmp); } - return 1; + if (cc->use_sideband) + return send_sb(&cc->scc, msg, strlen(msg), SBD_OUTPUT, false); + ret = sc_send_buffer(&cc->scc, msg); + free(msg); + return ret; } /* returns string that must be freed by the caller */ @@ -450,22 +608,21 @@ static struct server_command *get_cmd_ptr(const char *name, char **handler) int com_help(struct command_context *cc) { struct server_command *cmd; - char *perms, *handler; + char *perms, *handler, *buf; int ret; if (cc->argc < 2) { /* no argument given, print list of commands */ - if ((ret = send_list_of_commands(&cc->scc, server_cmds, "server")) < 0) + if ((ret = send_list_of_commands(cc, server_cmds, "server")) < 0) return ret; - return send_list_of_commands(&cc->scc, afs_cmds, "afs"); + return send_list_of_commands(cc, afs_cmds, "afs"); } /* argument given for help */ cmd = get_cmd_ptr(cc->argv[1], &handler); if (!cmd) return -E_BAD_CMD; perms = cmd_perms_itohuman(cmd->perms); - ret = sc_send_va_buffer(&cc->scc, - "%s - %s\n\n" + ret = xasprintf(&buf, "%s - %s\n\n" "handler: %s\n" "permissions: %s\n" "usage: %s\n\n" @@ -479,6 +636,10 @@ int com_help(struct command_context *cc) ); free(perms); free(handler); + if (cc->use_sideband) + return send_sb(&cc->scc, buf, ret, SBD_OUTPUT, false); + ret = sc_send_buffer(&cc->scc, buf); + free(buf); return ret; } @@ -701,6 +862,80 @@ static void reset_signals(void) para_sigaction(SIGHUP, SIG_DFL); } +static int parse_auth_request(char *buf, int len, struct user **u, + bool *use_sideband) +{ + int ret; + char *p, *username, **features = NULL; + size_t auth_rq_len = strlen(AUTH_REQUEST_MSG); + + *u = NULL; + *use_sideband = false; + if (len < auth_rq_len + 2) + return -E_AUTH_REQUEST; + if (strncmp(buf, AUTH_REQUEST_MSG, auth_rq_len) != 0) + return -E_AUTH_REQUEST; + username = buf + auth_rq_len; + p = strchr(username, ' '); + if (p) { + int i; + if (p == username) + return -E_AUTH_REQUEST; + *p = '\0'; + p++; + create_argv(p, ",", &features); + for (i = 0; features[i]; i++) { + if (strcmp(features[i], "sideband") == 0) + *use_sideband = true; + else { + ret = -E_BAD_FEATURE; + goto out; + } + } + } + PARA_DEBUG_LOG("received auth request for user %s (sideband = %s)\n", + username, *use_sideband? "true" : "false"); + *u = lookup_user(username); + ret = 1; +out: + free_argv(features); + return ret; +} + +#define HANDSHAKE_BUFSIZE 4096 + +static int parse_sb_command(struct command_context *cc, struct iovec *iov) +{ + int ret, i; + char *p, *end; + + ret = -E_BAD_CMD; + if (iov->iov_base == NULL || iov->iov_len == 0) + goto out; + p = iov->iov_base; + p[iov->iov_len - 1] = '\0'; /* just to be sure */ + cc->cmd = get_cmd_ptr(p, NULL); + if (!cc->cmd) + goto out; + ret = check_perms(cc->u->perms, cc->cmd); + if (ret < 0) + goto out; + end = iov->iov_base + iov->iov_len; + for (i = 0, p = iov->iov_base; p < end; i++) + p += strlen(p) + 1; + cc->argc = i; + cc->argv = para_malloc((cc->argc + 1) * sizeof(char *)); + for (i = 0, p = iov->iov_base; p < end; i++) { + cc->argv[i] = para_strdup(p); + p += strlen(p) + 1; + } + cc->argv[cc->argc] = NULL; + ret = cc->argc; +out: + free(iov->iov_base); + return ret; +} + /** * Perform user authentication and execute a command. * @@ -731,10 +966,9 @@ static void reset_signals(void) __noreturn void handle_connect(int fd, const char *peername) { int ret; - char buf[4096]; unsigned char rand_buf[CHALLENGE_SIZE + 2 * SESSION_KEY_LEN]; unsigned char challenge_hash[HASH_SIZE]; - char *p, *command = NULL; + char *p, *command = NULL, *buf = para_malloc(HANDSHAKE_BUFSIZE) /* must be on the heap */; size_t numbytes; struct command_context cc_struct = {.peer = peername}, *cc = &cc_struct; @@ -746,19 +980,17 @@ __noreturn void handle_connect(int fd, const char *peername) goto net_err; /* send Welcome message */ ret = write_va_buffer(fd, "This is para_server, version " - PACKAGE_VERSION ".\n" ); + PACKAGE_VERSION ".\n" + "Features: sideband,foo\n" + ); if (ret < 0) goto net_err; /* recv auth request line */ - ret = recv_buffer(fd, buf, sizeof(buf)); + ret = recv_buffer(fd, buf, HANDSHAKE_BUFSIZE); if (ret < 0) goto net_err; - if (ret < 10) { - ret = -E_AUTH_REQUEST; - goto net_err; - } - ret = -E_AUTH_REQUEST; - if (strncmp(buf, AUTH_REQUEST_MSG, strlen(AUTH_REQUEST_MSG))) + ret = parse_auth_request(buf, ret, &cc->u, &cc->use_sideband); + if (ret < 0) goto net_err; p = buf + strlen(AUTH_REQUEST_MSG); PARA_DEBUG_LOG("received auth request for user %s\n", p); @@ -781,15 +1013,29 @@ __noreturn void handle_connect(int fd, const char *peername) } PARA_DEBUG_LOG("sending %u byte challenge + rc4 keys (%zu bytes)\n", CHALLENGE_SIZE, numbytes); - ret = write_all(fd, buf, numbytes); - if (ret < 0) - goto net_err; - /* recv challenge response */ - ret = recv_bin_buffer(fd, buf, HASH_SIZE); - if (ret < 0) - goto net_err; - numbytes = ret; - PARA_DEBUG_LOG("received %d bytes challenge response\n", ret); + if (cc->use_sideband) { + struct iovec iov; + ret = send_sb(&cc->scc, buf, numbytes, SBD_CHALLENGE, false); + buf = NULL; + if (ret < 0) + goto net_err; + ret = recv_sb(&cc->scc, SBD_CHALLENGE_RESPONSE, + HANDSHAKE_BUFSIZE, &iov); + if (ret < 0) + goto net_err; + buf = iov.iov_base; + numbytes = iov.iov_len; + } else { + ret = write_all(fd, buf, numbytes); + if (ret < 0) + goto net_err; + /* recv challenge response */ + ret = recv_bin_buffer(fd, buf, HASH_SIZE); + if (ret < 0) + goto net_err; + numbytes = ret; + } + PARA_DEBUG_LOG("received %zu bytes challenge response\n", numbytes); ret = -E_BAD_USER; if (!cc->u) goto net_err; @@ -809,27 +1055,41 @@ __noreturn void handle_connect(int fd, const char *peername) /* init stream cipher keys with the second part of the random buffer */ cc->scc.recv = sc_new(rand_buf + CHALLENGE_SIZE, SESSION_KEY_LEN); cc->scc.send = sc_new(rand_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, SESSION_KEY_LEN); - ret = sc_send_buffer(&cc->scc, PROCEED_MSG); - if (ret < 0) - goto net_err; - ret = read_command(&cc->scc, &command); - if (ret == -E_COMMAND_SYNTAX) - goto err_out; + if (cc->use_sideband) + ret = send_sb(&cc->scc, NULL, 0, SBD_PROCEED, false); + else + ret = sc_send_buffer(&cc->scc, PROCEED_MSG); if (ret < 0) goto net_err; - ret = -E_BAD_CMD; - cc->cmd = parse_cmd(command); - if (!cc->cmd) - goto err_out; - /* valid command, check permissions */ - ret = check_perms(cc->u->perms, cc->cmd); - if (ret < 0) - goto err_out; - /* valid command and sufficient perms */ - ret = create_argv(command, "\n", &cc->argv); - if (ret < 0) - goto err_out; - cc->argc = ret; + if (cc->use_sideband) { + struct iovec iov; + ret = recv_sb(&cc->scc, SBD_COMMAND, MAX_COMMAND_LEN, &iov); + if (ret < 0) + goto net_err; + ret = parse_sb_command(cc, &iov); + if (ret < 0) + goto err_out; + cc->argc = ret; + } else { + ret = read_command(&cc->scc, &command); + if (ret == -E_COMMAND_SYNTAX) + goto err_out; + if (ret < 0) + goto net_err; + ret = -E_BAD_CMD; + cc->cmd = parse_cmd(command); + if (!cc->cmd) + goto err_out; + /* valid command, check permissions */ + ret = check_perms(cc->u->perms, cc->cmd); + if (ret < 0) + goto err_out; + /* valid command and sufficient perms */ + ret = create_argv(command, "\n", &cc->argv); + if (ret < 0) + goto err_out; + cc->argc = ret; + } PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", cc->cmd->name, cc->u->name, peername); ret = cc->cmd->handler(cc); @@ -840,10 +1100,12 @@ __noreturn void handle_connect(int fd, const char *peername) if (ret >= 0) goto out; err_out: - sc_send_va_buffer(&cc->scc, "%s\n", para_strerror(-ret)); + if (send_strerror(cc, -ret) >= 0 && cc->use_sideband) + send_sb(&cc->scc, NULL, 0, SBD_EXIT__FAILURE, true); net_err: PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); out: + free(buf); free(command); sc_free(cc->scc.recv); sc_free(cc->scc.send); @@ -852,5 +1114,12 @@ out: mmd->events++; mmd->active_connections--; mutex_unlock(mmd_mutex); + if (ret < 0) + exit(EXIT_FAILURE); + if (!cc->use_sideband) + exit(EXIT_SUCCESS); + ret = send_sb(&cc->scc, NULL, 0, SBD_EXIT__SUCCESS, true); + if (ret < 0) + PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS); } diff --git a/command.h b/command.h index 05510cc0..e4159e6b 100644 --- a/command.h +++ b/command.h @@ -14,6 +14,8 @@ struct command_context { struct server_command *cmd; /** File descriptor and crypto keys. */ struct stream_cipher_context scc; + /** Whether to use the sideband API for this command. */ + bool use_sideband; }; /** @@ -33,3 +35,12 @@ struct server_command { /** The long help text. */ const char *help; }; + +int send_sb(struct stream_cipher_context *scc, void *buf, size_t numbytes, + int band, bool dont_free); +__printf_3_4 int send_sb_va(struct stream_cipher_context *scc, int band, + const char *fmt, ...); +int send_strerror(struct command_context *cc, int err); +int recv_sb(struct stream_cipher_context *scc, + enum sb_designator expected_band, + size_t max_size, struct iovec *result); diff --git a/configure.ac b/configure.ac index a57d8d72..c616b0a5 100644 --- a/configure.ac +++ b/configure.ac @@ -100,7 +100,7 @@ all_errlist_objs="mp3_afh afh_common net string signal time daemon exec send_common ggo udp_recv color fec fecdec_filter prebuffer_filter audiod_command_list bitstream imdct wma_afh wma_common wmadec_filter buffer_tree crypt_common - gui gui_theme" + gui gui_theme sideband" executables="recv filter audioc write client afh audiod" @@ -122,7 +122,7 @@ audioc_errlist_objs="audioc string net fd" audioc_ldflags="" audiod_cmdline_objs="add_cmdline(audiod compress_filter http_recv dccp_recv file_write client amp_filter udp_recv prebuffer_filter)" -audiod_errlist_objs="audiod signal string daemon stat net crypt_common +audiod_errlist_objs="audiod signal string daemon stat net crypt_common sideband time grab_client filter_common wav_filter compress_filter amp_filter http_recv dccp_recv recv_common fd sched write_common file_write audiod_command fecdec_filter client_common ggo udp_recv color fec prebuffer_filter audiod_command_list @@ -142,7 +142,7 @@ writers=" file" default_writer="FILE_WRITE" client_cmdline_objs="add_cmdline(client)" -client_errlist_objs="client net string fd sched stdin stdout time +client_errlist_objs="client net string fd sched stdin stdout time sideband client_common buffer_tree crypt_common" client_ldflags="" @@ -270,7 +270,7 @@ else crypt_common ipc dccp_send fd user_list chunk_queue afs aft mood score attribute blob playlist sched acl send_common udp_send color fec server_command_list - afs_command_list wma_afh wma_common" + afs_command_list wma_afh wma_common sideband" all_errlist_objs="$all_errlist_objs server vss command http_send close_on_fork mm ipc dccp_send user_list diff --git a/crypt.c b/crypt.c index f33f769f..b754c091 100644 --- a/crypt.c +++ b/crypt.c @@ -317,6 +317,23 @@ int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf, return ret; } +void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) +{ + RC4_KEY *key = &sc->key; + + *dst = (typeof(*dst)) { + /* + * Add one for the terminating zero byte. Integer overflow is + * no problem here as para_malloc() aborts when given a zero + * size argument. + */ + .iov_base = para_malloc(src->iov_len + 1), + .iov_len = src->iov_len + }; + RC4(key, src->iov_len, src->iov_base, dst->iov_base); + ((char *)dst->iov_base)[dst->iov_len] = '\0'; +} + void hash_function(const char *data, unsigned long len, unsigned char *hash) { SHA_CTX c; diff --git a/crypt.h b/crypt.h index c4666695..ac110882 100644 --- a/crypt.h +++ b/crypt.h @@ -128,6 +128,36 @@ struct stream_cipher_context { */ struct stream_cipher *sc_new(const unsigned char *data, int len); +/** + * Encrypt or decrypt a buffer using a stream cipher. + * + * \param sc Crypto key. + * \param src The source buffer and length. + * \param dst The destination buffer and length, filled out by the function. + * + * It is up to the implementation to decide whether the crypt operation is + * performed in place. The caller can tell by looking if the buffers given by + * \a src and \a dst coincide after the call. If (and only if) the crypt + * operation was not performed in place, the function allocated a new buffer + * for the result, so dst->iov_base is different from src->iov_base. In this + * case, the destination buffer must be freed by the caller when it is no + * longer needed. + */ +void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst); + +/** + * Wrapper for \ref sc_crypt() that can be used as a sideband transformation. + * + * \param src Passed verbatim to \ref sc_crypt(). + * \param dst Passed verbatim to \ref sc_crypt(). + * \param trafo_context Must point to an initialized stream cipher. + */ +_static_inline_ void sc_trafo(struct iovec *src, struct iovec *dst, + void *trafo_context) +{ + sc_crypt(trafo_context, src, dst); +} + /** * Deallocate a stream cipher structure. * @@ -200,7 +230,6 @@ int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf, */ int sc_recv_buffer(struct stream_cipher_context *scc, char *buf, size_t size); - /** Size of the hash value in bytes. */ #define HASH_SIZE 20 diff --git a/crypt_common.c b/crypt_common.c index 5ad4d43d..705c7249 100644 --- a/crypt_common.c +++ b/crypt_common.c @@ -11,8 +11,8 @@ #include "para.h" #include "error.h" #include "string.h" -#include "crypt_backend.h" #include "crypt.h" +#include "crypt_backend.h" /** If the key begins with this text, we treat it as an ssh key. */ #define KEY_TYPE_TXT "ssh-rsa" diff --git a/error.h b/error.h index cfd2b1bd..3100b312 100644 --- a/error.h +++ b/error.h @@ -39,6 +39,11 @@ DEFINE_ERRLIST_OBJECT_ENUM; extern const char **para_errlist[]; +#define SIDEBAND_ERRORS \ + PARA_ERROR(BAD_BAND, "invalid or unexpected band designator"), \ + PARA_ERROR(SB_PACKET_SIZE, "invalid sideband packet size or protocol error"), \ + + #define FLACDEC_FILTER_ERRORS \ PARA_ERROR(FLACDEC_DECODER_ALLOC, "could not allocate stream decoder"), \ PARA_ERROR(FLACDEC_DECODER_INIT, "could not init stream decoder"), \ @@ -258,6 +263,8 @@ extern const char **para_errlist[]; PARA_ERROR(BAD_CONFIG, "syntax error in config file"), \ PARA_ERROR(CLIENT_AUTH, "authentication failed"), \ PARA_ERROR(SERVER_EOF, "connection closed by para_server"), \ + PARA_ERROR(SERVER_CMD_SUCCESS, "command terminated successfully"), \ + PARA_ERROR(SERVER_CMD_FAILURE, "command failed"), \ #define SCHED_ERRORS \ @@ -352,7 +359,8 @@ extern const char **para_errlist[]; PARA_ERROR(ATOI_NO_DIGITS, "no digits found in string"), \ PARA_ERROR(ATOI_JUNK_AT_END, "further characters after number"), \ PARA_ERROR(SIZE_PREFIX, "bad size prefix"), \ - PARA_ERROR(REGEX, "regular expression error") \ + PARA_ERROR(REGEX, "regular expression error"), \ + PARA_ERROR(ARG_NOT_FOUND, "argument not found in arg vector"), \ #define EXEC_ERRORS \ @@ -431,6 +439,7 @@ extern const char **para_errlist[]; PARA_ERROR(SENDER_CMD, "command not supported by this sender"), \ PARA_ERROR(SERVER_CRASH, "para_server crashed -- can not live without it"), \ PARA_ERROR(BAD_USER, "auth request for invalid user"), \ + PARA_ERROR(BAD_FEATURE, "request for unknown or invalid feature"), \ PARA_ERROR(BAD_AUTH, "authentication failure"), \ diff --git a/gcc-compat.h b/gcc-compat.h index 5d207288..dd6afe1d 100644 --- a/gcc-compat.h +++ b/gcc-compat.h @@ -16,9 +16,10 @@ * As direct use of __printf(p,q) confuses doxygen, here are some extra macros * for those values p,q that are actually used. */ -#define __printf_2_0 __printf(2,0) -#define __printf_1_2 __printf(1,2) -#define __printf_2_3 __printf(2,3) +#define __printf_2_0 __printf(2,0) +#define __printf_1_2 __printf(1,2) +#define __printf_2_3 __printf(2,3) +#define __printf_3_4 __printf(3,4) # if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 3) # define __must_check __attribute__ ((warn_unused_result)) diff --git a/gcrypt.c b/gcrypt.c index d11e94c7..926eb15f 100644 --- a/gcrypt.c +++ b/gcrypt.c @@ -972,3 +972,15 @@ int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf, assert(gret == 0); return ret; } + +void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst) +{ + gcry_cipher_hd_t handle = sc->handle; + gcry_error_t gret; + + /* perform in-place encryption */ + *dst = *src; + gret = gcry_cipher_encrypt(handle, src->iov_base, src->iov_len, + NULL, 0); + assert(gret == 0); +} diff --git a/mood.c b/mood.c index f580400d..bafe710c 100644 --- a/mood.c +++ b/mood.c @@ -17,6 +17,7 @@ #include "list.h" #include "ipc.h" #include "mm.h" +#include "sideband.h" /** * Contains statistical data of the currently admissible audio files. @@ -425,8 +426,11 @@ void mood_check_callback(int fd, __a_unused const struct osl_object *query) { struct para_buffer pb = { .max_size = shm_get_shmmax(), - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }, + .max_size_handler = afs_max_size_handler }; int ret = para_printf(&pb, "checking moods...\n"); @@ -435,7 +439,7 @@ void mood_check_callback(int fd, __a_unused const struct osl_object *query) osl_rbtree_loop(moods_table, BLOBCOL_ID, &pb, check_mood); if (pb.offset) - pass_buffer_as_shm(pb.buf, pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset); free(pb.buf); } diff --git a/para.h b/para.h index 0a14b99a..5e0501a6 100644 --- a/para.h +++ b/para.h @@ -131,6 +131,18 @@ _static_inline_ long int para_random(unsigned max) return ((max + 0.0) * (random() / (RAND_MAX + 1.0))); } +/** + * Simple sanity check for I/O vectors. + * + * \param iov Pointer to the I/O vector to check. + * + * \return True if \a iov points to a non-empty buffer. + */ +_static_inline_ bool iov_valid(const struct iovec *iov) +{ + return iov && iov->iov_len > 0 && iov->iov_base; +} + /** Round up x to next multiple of y. */ #define ROUND_UP(x, y) ({ \ const typeof(y) _divisor = y; \ diff --git a/playlist.c b/playlist.c index 13c29404..48ee4ca7 100644 --- a/playlist.c +++ b/playlist.c @@ -13,6 +13,7 @@ #include "afh.h" #include "afs.h" #include "ipc.h" +#include "sideband.h" /** \file playlist.c Functions for loading and saving playlists. */ @@ -132,8 +133,11 @@ void playlist_check_callback(int fd, __a_unused const struct osl_object *query) { struct para_buffer pb = { .max_size = shm_get_shmmax(), - .private_data = &fd, - .max_size_handler = pass_buffer_as_shm + .private_data = &(struct afs_max_size_handler_data) { + .fd = fd, + .band = SBD_OUTPUT + }, + .max_size_handler = afs_max_size_handler, }; int ret = para_printf(&pb, "checking playlists...\n"); @@ -142,7 +146,7 @@ void playlist_check_callback(int fd, __a_unused const struct osl_object *query) osl_rbtree_loop(playlists_table, BLOBCOL_ID, &pb, check_playlist); if (pb.offset) - pass_buffer_as_shm(pb.buf, pb.offset, &fd); + pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset); free(pb.buf); } diff --git a/server.cmd b/server.cmd index c9b34d1c..d0e8f271 100644 --- a/server.cmd +++ b/server.cmd @@ -4,7 +4,7 @@ HC: prototypes for the server command handlers CC: array of server commands AT: server_command SI: osl regex -IN: para error crypt command string afh afs server list user_list +IN: para error crypt sideband command string afh afs server list user_list SN: list of server commands --- N: ff diff --git a/sideband.c b/sideband.c new file mode 100644 index 00000000..bf990889 --- /dev/null +++ b/sideband.c @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2012 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file sideband.c Implementation of the sideband API. */ + +#include +#include + +#include "para.h" +#include "error.h" +#include "portable_io.h" +#include "string.h" +#include "sideband.h" + +/** Each sideband packet consists of a header and a data part. */ +#define SIDEBAND_HEADER_SIZE 5 + +struct sb_context { + char header[SIDEBAND_HEADER_SIZE]; + size_t bytes_dispatched; /* including header */ + sb_transformation trafo; + void *trafo_context; + struct sb_buffer sbb; + size_t max_size; + bool dont_free; +}; + +/** + * Prepare to receive a sideband packet. + * + * \param max_size Do not allocate more than this many bytes. + * \param t Optional sideband transformation. + * \param trafo_context Passed verbatim to \a t. + * + * \a trafo_context is ignored if \a t is \p NULL. + * + * \return An opaque sideband handle. + */ +struct sb_context *sb_new_recv(size_t max_size, sb_transformation t, + void *trafo_context) +{ + struct sb_context *c = para_calloc(sizeof(*c)); + + c->max_size = max_size; + c->trafo = t; + c->trafo_context = trafo_context; + return c; +} + +/** + * Prepare to write a sideband packet. + * + * \param sbb Data and meta data to send. + * \param dont_free Do not try to deallocate the sideband buffer. + * \param t See \ref sb_new_recv(). + * \param trafo_context See \ref sb_new_recv(). + * + * It's OK to supply a zero-sized buffer in \a sbb. In this case only the band + * designator is sent through the sideband channel. Otherwise, if \a dont_free + * is false, the buffer of \a sbb is freed after the data has been sent. + * + * \return See \ref sb_new_recv(). + */ +struct sb_context *sb_new_send(struct sb_buffer *sbb, bool dont_free, + sb_transformation t, void *trafo_context) +{ + struct sb_context *c = para_calloc(sizeof(*c)); + struct iovec src, dst, *srcp, *dstp; + + assert(sbb); + c->trafo = t; + c->trafo_context = trafo_context; + c->dont_free = dont_free; + c->sbb = *sbb; + write_u32(c->header, sbb->iov.iov_len); + write_u8(c->header + 4, sbb->band); + if (!t) + goto out; + src = (typeof(src)){.iov_base = c->header, .iov_len = SIDEBAND_HEADER_SIZE}; + t(&src, &dst, trafo_context); + if (src.iov_base != dst.iov_base) { + memcpy(c->header, dst.iov_base, SIDEBAND_HEADER_SIZE); + free(dst.iov_base); + } + if (!iov_valid(&sbb->iov)) + goto out; + srcp = &sbb->iov; + dstp = &c->sbb.iov; + t(srcp, dstp, trafo_context); + if (srcp->iov_base != dstp->iov_base) { + if (!c->dont_free) + free(srcp->iov_base); + c->dont_free = false; + } +out: + return c; +} + +/** + * Deallocate all memory associated with a sideband handle. + * + * \param c The sideband handle. + * + * \a c must point to a handle previously returned by \ref sb_new_recv() or + * \ref sb_new_send(). It \a c is \p NULL, the function does nothing. + */ +void sb_free(struct sb_context *c) +{ + if (!c) + return; + if (!c->dont_free) + free(c->sbb.iov.iov_base); + free(c); +} + +/** + * Obtain pointer(s) to the sideband send buffer(s). + * + * \param c The sideband handle. + * \param iov Array of two I/O vectors. + * + * \return The number of buffers that need to be sent, either 1 or 2. + * + * This function fills out the buffers described by \a iov. The result can be + * passed to \ref xwritev() or similar. + * + * \sa \ref sb_get_recv_buffer(). + */ +int sb_get_send_buffers(struct sb_context *c, struct iovec iov[2]) +{ + struct sb_buffer *sbb = &c->sbb; + size_t n = c->bytes_dispatched; + + if (n < SIDEBAND_HEADER_SIZE) { + iov[0].iov_base = c->header + n; + iov[0].iov_len = SIDEBAND_HEADER_SIZE - n; + if (!iov_valid(&sbb->iov)) + goto out; + iov[1] = sbb->iov; + return 2; + } + n -= SIDEBAND_HEADER_SIZE; + assert(n < sbb->iov.iov_len); + iov[0].iov_base = sbb->iov.iov_base + n; + iov[0].iov_len = sbb->iov.iov_len - n; +out: + iov[1].iov_base = NULL; + iov[1].iov_len = 0; + return 1; +} + +/** + * Update the sideband context after data has been sent. + * + * \param c The sideband handle. + * \param nbytes The number of sent bytes. + * + * \return False if more data must be sent to complete the sideband transfer, + * true if the transfer is complete. In this case all resources are freed and + * the sideband handle must not be used any more. + */ +bool sb_sent(struct sb_context *c, size_t nbytes) +{ + struct sb_buffer *sbb = &c->sbb; + size_t sz = SIDEBAND_HEADER_SIZE + sbb->iov.iov_len; + + assert(c->bytes_dispatched + nbytes <= sz); + c->bytes_dispatched += nbytes; + if (c->bytes_dispatched < sz) + return false; + sb_free(c); + return true; +} + +/** + * Obtain a pointer to the next sideband read buffer. + * + * \param c The sideband handle. + * \param iov Result IO vector. + * + * This fills in \a iov to point to the buffer to which the next chunk of + * received data should be written. + */ +void sb_get_recv_buffer(struct sb_context *c, struct iovec *iov) +{ + struct sb_buffer *sbb = &c->sbb; + size_t n = c->bytes_dispatched; + + if (n < SIDEBAND_HEADER_SIZE) { + iov->iov_base = c->header + n; + iov->iov_len = SIDEBAND_HEADER_SIZE - n; + return; + } + n -= SIDEBAND_HEADER_SIZE; + assert(sbb->iov.iov_base); + assert(sbb->iov.iov_len > n); + iov->iov_base = sbb->iov.iov_base + n; + iov->iov_len = sbb->iov.iov_len - n; +} + +/** + * Update the sideband context after data has been received. + * + * \param c The sideband handle. + * \param nbytes The number of bytes that have been received. + * \param result The received sideband packet. + * + * \return Negative on errors, zero if more data needs to be read to complete + * this sideband packet, one if the sideband packet has been received + * completely. + * + * Only if the function returns one, the sideband buffer pointed to by \a + * result is set to point to the received data. + */ +int sb_received(struct sb_context *c, size_t nbytes, struct sb_buffer *result) +{ + struct sb_buffer *sbb = &c->sbb; + size_t n = c->bytes_dispatched, + sz = SIDEBAND_HEADER_SIZE + sbb->iov.iov_len; + + assert(n + nbytes <= sz); + c->bytes_dispatched += nbytes; + if (c->bytes_dispatched < SIDEBAND_HEADER_SIZE) + return 0; + if (n >= SIDEBAND_HEADER_SIZE) { /* header has already been received */ + if (c->bytes_dispatched < sz) /* need to recv more body data */ + return 0; + /* received everything, decrypt and return sbb */ + if (c->trafo) { + struct iovec dst; + c->trafo(&sbb->iov, &dst, c->trafo_context); + if (sbb->iov.iov_base != dst.iov_base) { + free(sbb->iov.iov_base); + sbb->iov.iov_base = dst.iov_base; + } + } + ((char *)(sbb->iov.iov_base))[sbb->iov.iov_len] = '\0'; + goto success; + } + /* header has been received, decrypt and decode it */ + if (c->trafo) { /* decrypt */ + struct iovec dst, src = (typeof(src)) { + .iov_base = c->header, + .iov_len = SIDEBAND_HEADER_SIZE + }; + c->trafo(&src, &dst, c->trafo_context); + if (src.iov_base != dst.iov_base) { + memcpy(c->header, dst.iov_base, + SIDEBAND_HEADER_SIZE); + free(dst.iov_base); + } + } + /* Decode header, setup sbb */ + sbb->iov.iov_len = read_u32(c->header); + sbb->band = read_u8(c->header + 4); + sbb->iov.iov_base = NULL; + if (sbb->iov.iov_len == 0) /* zero-sized msg */ + goto success; + if (c->max_size > 0 && sbb->iov.iov_len > c->max_size) { + PARA_ERROR_LOG("packet too big (is %zu, max %zu)\n", + sbb->iov.iov_len, c->max_size); + return -E_SB_PACKET_SIZE; + } + /* + * We like to reserve one extra byte for the terminating NULL + * character. However, we must make sure the +1 below does not + * overflow iov_len. + */ + if (sbb->iov.iov_len == (size_t)-1) + return -E_SB_PACKET_SIZE; + sbb->iov.iov_base = para_malloc(sbb->iov.iov_len + 1); + return 0; /* ready to read body */ +success: + *result = c->sbb; + free(c); + return 1; +} diff --git a/sideband.h b/sideband.h new file mode 100644 index 00000000..4f4ed0ac --- /dev/null +++ b/sideband.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file sideband.h exported symbols from sideband.c */ + +/** + * The valid sideband designators. + * + * A sideband packet consists of a header and a body. The header is sent prior + * to the data and contains the length of the buffer and the sideband + * designator, an integer. This scheme allows receivers to never read more data + * than what was specified in the header. + * + * The sideband designator indicates the type of the data. The authentication + * handshake between the command handler (child of para_server) and the client + * requires to exchange several small packages. Each such package is sent as a + * sideband package with a dedicated designator. + * + * Other designators are employed for normal command output and for log + * messages, where each loglevel corresponds to a sideband designator. + * + * Note that the values of this enum are part of the ABI, so never change + * or remove entries. Appending is OK though. + */ +#define SB_DESIGNATORS \ + /* Special value for receiving only: Accept any band. */ \ + DESIGNATOR(ANY), \ + /* This packet contains the authentication challenge. */ \ + DESIGNATOR(CHALLENGE), \ + /* I solved your challenge and here is the proof. */ \ + DESIGNATOR(CHALLENGE_RESPONSE), \ + /* Congratulations, you are authenticated. */ \ + DESIGNATOR(PROCEED), \ + /* This is the command I want you to execute. */ \ + DESIGNATOR(COMMAND), \ + /* Ready to receive a blob (addblob commands only). */ \ + DESIGNATOR(AWAITING_DATA), \ + /* Normal command output (default). */ \ + DESIGNATOR(OUTPUT), \ + /* LL_DEBUG. */ \ + DESIGNATOR(DEBUG_LOG), \ + /* LL_INFO. */ \ + DESIGNATOR(INFO_LOG), \ + /* LL_NOTICE. */ \ + DESIGNATOR(NOTICE_LOG), \ + /* LL_WARNING, */ \ + DESIGNATOR(WARNING_LOG), \ + /* LL_ERROR. */ \ + DESIGNATOR(ERROR_LOG), \ + /* LL_CRIT. */ \ + DESIGNATOR(CRIT_LOG), \ + /* LL_EMERG. */ \ + DESIGNATOR(EMERG_LOG), \ + /* Command terminated successfully. */ \ + DESIGNATOR(EXIT__SUCCESS), \ + /* Command failed. */ \ + DESIGNATOR(EXIT__FAILURE), \ + +/** Just prefix with \p SBD_. */ +#define DESIGNATOR(x) SBD_ ## x + +/** All valid sideband designators. */ +enum sb_designator {SB_DESIGNATORS NUM_SB_DESIGNATORS}; +#undef DESIGNATOR +/** One stringified sideband designator. */ +#define DESIGNATOR(x) #x +/** List of stringified sidedband designators. */ +#define SB_DESIGNATORS_ARRAY SB_DESIGNATORS + +/** + * The information contained in a sideband buffer. + * + * This structure is used for both sending and receiving data through a + * sideband channel. A pointer to a sideband buffer is passed to the sending + * side of a sideband while the receiving end yields a filled out structure + * once all data has been received. + */ +struct sb_buffer { + /** Data and length. */ + struct iovec iov; + /** The band designator. */ + uint8_t band; +}; + +/** + * The opaque sideband context structure. + * + * A pointer to a structure of this type is returned by the two \a sb_new_xxx + * functions as an opaque handle. Other public functions of the sideband API + * take such a handle as a parameter. + */ +struct sb_context; + +/** + * The type of a sideband transformation. + * + * The sideband API allows to filter all data through an arbitrary + * transformation, which is useful for crypto purposes. The transformation may + * either transform the data in place, or return a pointer to a new buffer + * which contains the transformed source buffer. The internal sideband + * functions can tell the two types of transformations apart by checking + * whether the destination buffer coincides with the source buffer. + * + * If the transformation allocates a new buffer, it _must_ allocate one extra + * byte for \p NULL termination. + */ +typedef void (*sb_transformation)(struct iovec *src, struct iovec *dst, + void *trafo_context); + + +/** Initialize a sideband buffer. */ +#define SBB_INIT(_band, _buf, _numbytes) \ + { \ + .band = band, \ + .iov = { \ + .iov_base = _buf, \ + .iov_len = _numbytes \ + } \ + }; + +/* receiving */ +struct sb_context *sb_new_recv(size_t max_size, sb_transformation t, + void *trafo_context); +void sb_get_recv_buffer(struct sb_context *c, struct iovec *iov); +int sb_received(struct sb_context *c, size_t nbytes, struct sb_buffer *result); + +/* sending */ +struct sb_context *sb_new_send(struct sb_buffer *sbb, bool dont_free, + sb_transformation t, void *trafo_context); +int sb_get_send_buffers(struct sb_context *c, struct iovec iov[2]); +bool sb_sent(struct sb_context *c, size_t nbytes); + +void sb_free(struct sb_context *c); diff --git a/string.c b/string.c index aa3bcbdd..7123ba1a 100644 --- a/string.c +++ b/string.c @@ -855,6 +855,27 @@ err: return ret; } +/** + * Find out if the given string is contained in the arg vector. + * + * \param arg The string to look for. + * \param argv The array to search. + * + * \return The first index whose value equals \a arg, or \p -E_ARG_NOT_FOUND if + * arg was not found in \a argv. + */ +int find_arg(const char *arg, char **argv) +{ + int i; + + if (!argv) + return -E_ARG_NOT_FOUND; + for (i = 0; argv[i]; i++) + if (strcmp(arg, argv[i]) == 0) + return i; + return -E_ARG_NOT_FOUND; +} + /** * Compile a regular expression. * diff --git a/string.h b/string.h index cdc55d2d..aebb6516 100644 --- a/string.h +++ b/string.h @@ -83,6 +83,7 @@ int para_atoi32(const char *str, int32_t *value); int get_loglevel_by_name(const char *txt); int read_size_header(const char *buf); int create_argv(const char *buf, const char *delim, char ***result); +int find_arg(const char *arg, char **argv); void free_argv(char **argv); int para_regcomp(regex_t *preg, const char *regex, int cflags); void freep(void *arg);