X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=client_common.c;h=cd1ccc619ce06710d9fad42652d6868942fc17d8;hp=a4aa6d8c3ecc3ae386cbb14515f05f8257d96e0d;hb=1ada5f286b031bde90d5cdaf8f85311cbfa7db6b;hpb=b66dfd41e268e64b65d2990bdec3d3a7e119e234 diff --git a/client_common.c b/client_common.c index a4aa6d8c..cd1ccc61 100644 --- a/client_common.c +++ b/client_common.c @@ -1,19 +1,23 @@ /* - * Copyright (C) 1997-2012 Andre Noll + * Copyright (C) 1997 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file client_common.c Common functions of para_client and para_audiod. */ +#include +#include #include #include +#include +#include +#include #include "para.h" #include "error.h" #include "list.h" #include "sched.h" -#include "client.cmdline.h" #include "crypt.h" #include "net.h" #include "fd.h" @@ -23,74 +27,45 @@ #include "client.h" #include "buffer_tree.h" #include "version.h" +#include "ggo.h" /** The size of the receiving buffer. */ #define CLIENT_BUFSIZE 4000 -/** - * Close the connection to para_server and deallocate per-command ressources. - * - * \param ct The client task. - * - * This frees all ressources of the current command but keeps the configuration - * in \p ct->conf. - * - * \sa \ref client_close(). - */ -void client_disconnect(struct client_task *ct) -{ - if (!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); - ct->scc.send = NULL; - btr_remove_node(&ct->btrn); -} - /** * Close the connection to para_server and free all resources. * * \param ct Pointer to the client data. * - * \sa \ref client_open(), \ref client_disconnect(). + * \sa \ref client_open(). */ void client_close(struct client_task *ct) { if (!ct) return; - client_disconnect(ct); free(ct->user); free(ct->config_file); free(ct->key_file); client_cmdline_parser_free(&ct->conf); free(ct->challenge_hash); - sb_free(ct->sbc); + sb_free(ct->sbc[0]); + sb_free(ct->sbc[1]); free(ct); } -/** +/* * The preselect hook for server commands. * - * \param s Pointer to the scheduler. - * \param t Pointer to the task struct for this command. - * * The task pointer must contain a pointer to the initialized client data * structure as it is returned by client_open(). * * This function checks the state of the connection and adds the file descriptor - * of the connection to the read or write fd set of \a s accordingly. - * - * \sa register_task() client_open(), struct sched, struct task. + * of the connection to the read or write fd set of s accordingly. */ -static void client_pre_select(struct sched *s, struct task *t) +static void client_pre_select(struct sched *s, void *context) { int ret; - struct client_task *ct = container_of(t, struct client_task, task); - struct btr_node *btrn = ct->btrn; + struct client_task *ct = context; if (ct->scc.fd < 0) return; @@ -98,7 +73,6 @@ static void client_pre_select(struct sched *s, struct task *t) case CL_CONNECTED: case CL_SENT_AUTH: case CL_SENT_CH_RESPONSE: - case CL_SENT_COMMAND: para_fd_set(ct->scc.fd, &s->rfds, &s->max_fileno); return; @@ -108,76 +82,49 @@ static void client_pre_select(struct sched *s, struct task *t) para_fd_set(ct->scc.fd, &s->wfds, &s->max_fileno); return; - case CL_RECEIVING: - ret = btr_node_status(btrn, 0, BTR_NT_ROOT); - if (ret != 0) { + case CL_SENDING: + if (ct->btrn[1]) { + ret = btr_node_status(ct->btrn[1], 0, BTR_NT_LEAF); if (ret < 0) sched_min_delay(s); - else - para_fd_set(ct->scc.fd, &s->rfds, - &s->max_fileno); + else if (ret > 0) + para_fd_set(ct->scc.fd, &s->wfds, &s->max_fileno); } - return; - case CL_SENDING: - ret = btr_node_status(btrn, 0, BTR_NT_LEAF); - if (ret != 0) { + /* fall though */ + case CL_EXECUTING: + if (ct->btrn[0]) { + ret = btr_node_status(ct->btrn[0], 0, BTR_NT_ROOT); if (ret < 0) sched_min_delay(s); - else - para_fd_set(ct->scc.fd, &s->wfds, - &s->max_fileno); + else if (ret > 0) + para_fd_set(ct->scc.fd, &s->rfds, &s->max_fileno); } return; } } -static int client_recv_buffer(struct client_task *ct, fd_set *rfds, - char *buf, size_t sz, size_t *n) -{ - int ret; - - if (ct->status < CL_SENT_CH_RESPONSE) - return read_nonblock(ct->scc.fd, buf, sz, rfds, n); - - *n = 0; - ret = sc_recv_buffer(&ct->scc, buf, sz); - /* - * sc_recv_buffer is used with blocking fds elsewhere, so it - * does not use the nonblock-API. Therefore we need to - * check for EOF and EAGAIN. - */ - if (ret == 0) - return -E_SERVER_EOF; - if (ret == -ERRNO_TO_PARA_ERROR(EAGAIN)) - return 0; - if (ret < 0) - return ret; - *n = ret; - return 0; -} - -static int send_sb(struct client_task *ct, void *buf, size_t numbytes, +static int send_sb(struct client_task *ct, int channel, 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) { + if (!ct->sbc[channel]) { 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); + ct->sbc[channel] = sb_new_send(&sbb, dont_free, trafo, ct->scc.send); } - ret = sb_get_send_buffers(ct->sbc, iov); + ret = sb_get_send_buffers(ct->sbc[channel], iov); ret = xwritev(fd, iov, ret); if (ret < 0) { - sb_free(ct->sbc); - ct->sbc = NULL; + sb_free(ct->sbc[channel]); + ct->sbc[channel] = NULL; return ret; } - if (sb_sent(ct->sbc, ret)) { - ct->sbc = NULL; + if (sb_sent(ct->sbc[channel], ret)) { + ct->sbc[channel] = NULL; return 1; } return 0; @@ -200,21 +147,24 @@ static int recv_sb(struct client_task *ct, fd_set *rfds, trafo = sc_trafo; trafo_context = ct->scc.recv; } - if (!ct->sbc) - ct->sbc = sb_new_recv(0, trafo, trafo_context); + if (!ct->sbc[0]) + ct->sbc[0] = sb_new_recv(0, trafo, trafo_context); again: - sb_get_recv_buffer(ct->sbc, &iov); + sb_get_recv_buffer(ct->sbc[0], &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; + sb_free(ct->sbc[0]); + ct->sbc[0] = NULL; return ret; } if (n == 0) return 0; - if (!sb_received(ct->sbc, n, result)) + ret = sb_received(ct->sbc[0], n, result); + if (ret < 0) + return ret; + if (ret == 0) goto again; - ct->sbc = NULL; + ct->sbc[0] = NULL; return 1; } @@ -250,10 +200,14 @@ static int dispatch_sbb(struct client_task *ct, struct sb_buffer *sbb) PARA_DEBUG_LOG("band: %s\n", designator[sbb->band]); switch (sbb->band) { + case SBD_AWAITING_DATA: + ct->status = CL_SENDING; + ret = 1; + goto out; case SBD_OUTPUT: if (iov_valid(&sbb->iov)) btr_add_output(sbb->iov.iov_base, sbb->iov.iov_len, - ct->btrn); + ct->btrn[0]); ret = 1; goto out; case SBD_DEBUG_LOG: @@ -298,8 +252,8 @@ static int send_sb_command(struct client_task *ct) char *command, *p; size_t len = 0; - if (ct->sbc) - return send_sb(ct, NULL, 0, 0, false); + if (ct->sbc[1]) + return send_sb(ct, 0, NULL, 0, 0, false); for (i = 0; i < ct->conf.inputs_num; i++) len += strlen(ct->conf.inputs[i]) + 1; @@ -309,55 +263,53 @@ static int send_sb_command(struct client_task *ct) p += strlen(ct->conf.inputs[i]) + 1; } PARA_DEBUG_LOG("--> %s\n", command); - return send_sb(ct, command, len, SBD_COMMAND, false); + return send_sb(ct, 0, command, len, SBD_COMMAND, false); } -/** +/* * The post select hook for client commands. * - * \param s Pointer to the scheduler. - * \param t Pointer to the task struct for this command. - * * Depending on the current state of the connection and the status of the read - * and write fd sets of \a s, this function performs the necessary steps to - * authenticate the connection, to send the command given by \a t->private_data + * and write fd sets of s, this function performs the necessary steps to + * authenticate the connection, to send the command given by t->private_data * and to receive para_server's output, if any. - * - * \sa struct sched, struct task. */ -static void client_post_select(struct sched *s, struct task *t) +static int client_post_select(struct sched *s, void *context) { - struct client_task *ct = container_of(t, struct client_task, task); - struct btr_node *btrn = ct->btrn; + struct client_task *ct = context; int ret = 0; size_t n; char buf[CLIENT_BUFSIZE]; - t->error = 0; + ret = task_get_notification(ct->task); + if (ret < 0) + goto out; if (ct->scc.fd < 0) - return; + return 0; switch (ct->status) { case CL_CONNECTED: /* receive welcome message */ - ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n); + ret = read_nonblock(ct->scc.fd, buf, sizeof(buf), &s->rfds, &n); if (ret < 0 || n == 0) goto out; ct->features = parse_features(buf); + if (!has_feature("sideband", ct)) { + PARA_ERROR_LOG("server has no sideband support\n"); + ret = -E_INCOMPAT_FEAT; + goto out; + } ct->status = CL_RECEIVED_WELCOME; - return; + return 0; case CL_RECEIVED_WELCOME: /* send auth command */ 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); + return 0; + sprintf(buf, AUTH_REQUEST_MSG "%s sideband%s", ct->user, + has_feature("aes_ctr128", ct)? ",aes_ctr128" : ""); PARA_INFO_LOG("--> %s\n", buf); ret = write_buffer(ct->scc.fd, buf); if (ret < 0) goto out; ct->status = CL_SENT_AUTH; - return; + return 0; case CL_SENT_AUTH: /* * Receive challenge and session keys, decrypt the challenge and @@ -366,203 +318,141 @@ 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 */ + struct sb_buffer sbb; + bool use_aes; - 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); + 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); - 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; } + 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; 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); + use_aes = has_feature("aes_ctr128", ct); + ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN, use_aes); ct->scc.recv = sc_new(crypt_buf + CHALLENGE_SIZE + SESSION_KEY_LEN, - SESSION_KEY_LEN); + SESSION_KEY_LEN, use_aes); hash_to_asc(ct->challenge_hash, buf); PARA_INFO_LOG("--> %s\n", buf); ct->status = CL_RECEIVED_CHALLENGE; - return; + return 0; } 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; - } + ret = send_sb(ct, 0, ct->challenge_hash, HASH_SIZE, + SBD_CHALLENGE_RESPONSE, false); + if (ret != 0) + ct->challenge_hash = NULL; + 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; - /* check if server has sent "Proceed" message */ - ret = -E_CLIENT_AUTH; - if (n < PROCEED_MSG_LEN) - goto out; - if (!strstr(buf, PROCEED_MSG)) + struct sb_buffer sbb; + ret = recv_sb(ct, &s->rfds, &sbb); + if (ret <= 0) goto out; - ct->status = CL_RECEIVED_PROCEED; - return; + free(sbb.iov.iov_base); + if (sbb.band != SBD_PROCEED) + ret = -E_BAD_BAND; + else + ct->status = CL_RECEIVED_PROCEED; + goto out; } case CL_RECEIVED_PROCEED: /* concat args and send command */ { - int i; - 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? - command : "", ct->conf.inputs[i]); - free(tmp); - } - command = para_strcat(command, EOC_MSG "\n"); - PARA_DEBUG_LOG("--> %s\n", command); - ret = sc_send_buffer(&ct->scc, command); - free(command); - if (ret < 0) + return 0; + ret = send_sb_command(ct); + if (ret <= 0) goto out; - ct->status = CL_SENT_COMMAND; - return; + ct->status = CL_EXECUTING; + return 0; } - 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; + case CL_SENDING: + if (ct->btrn[1]) { + char *buf2; + size_t sz; + ret = btr_node_status(ct->btrn[1], 0, BTR_NT_LEAF); + if (ret == -E_BTR_EOF) { + /* empty blob data packet indicates EOF */ + PARA_INFO_LOG("blob sent\n"); + ret = send_sb(ct, 1, NULL, 0, SBD_BLOB_DATA, true); + if (ret >= 0) + ret = -E_BTR_EOF; } - 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); - if (n > 0) { - if (strstr(buf2, AWAITING_DATA_MSG)) { - free(buf2); - ct->status = CL_SENDING; - return; + if (ret < 0) + goto close1; + if (ret > 0 && FD_ISSET(ct->scc.fd, &s->wfds)) { + sz = btr_next_buffer(ct->btrn[1], &buf2); + assert(sz); + ret = send_sb(ct, 1, buf2, sz, SBD_BLOB_DATA, true); + if (ret < 0) + goto close1; + if (ret > 0) + btr_consume(ct->btrn[1], sz); } - ct->status = CL_RECEIVING; - btr_add_output(buf2, n, btrn); - } else - free(buf2); - goto out; - } - case CL_SENDING: - { - char *buf2; - size_t sz; - ret = btr_node_status(btrn, 0, BTR_NT_LEAF); - if (ret < 0) - goto out; - if (ret == 0) - return; - if (!FD_ISSET(ct->scc.fd, &s->wfds)) - return; - sz = btr_next_buffer(btrn, &buf2); - ret = sc_send_bin_buffer(&ct->scc, buf2, sz); - if (ret < 0) - goto out; - btr_consume(btrn, sz); - return; } - case CL_RECEIVING: - { - char *buf2; - ret = btr_node_status(btrn, 0, BTR_NT_ROOT); - if (ret < 0) - goto out; - if (ret == 0) - return; - /* - * The FD_ISSET() is not strictly necessary, but is allows us - * to skip the malloc below if there is nothing to read anyway. - */ - 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; + /* fall through */ + case CL_EXECUTING: + if (ct->btrn[0]) { + ret = btr_node_status(ct->btrn[0], 0, BTR_NT_ROOT); + if (ret < 0) + goto close0; + if (ret > 0 && FD_ISSET(ct->scc.fd, &s->rfds)) { + struct sb_buffer sbb; + ret = recv_sb(ct, &s->rfds, &sbb); + if (ret < 0) + goto close0; + if (ret > 0) { + ret = dispatch_sbb(ct, &sbb); + if (ret < 0) + goto close0; + } + } } - buf2 = para_malloc(CLIENT_BUFSIZE); - ret = client_recv_buffer(ct, &s->rfds, buf2, CLIENT_BUFSIZE, &n); - if (n > 0) { - buf2 = para_realloc(buf2, n); - btr_add_output(buf2, n, btrn); - } else - free(buf2); + ret = 0; goto out; - } } +close1: + PARA_INFO_LOG("channel 1: %s\n", para_strerror(-ret)); + btr_remove_node(&ct->btrn[1]); + if (ct->btrn[0]) + return 0; + goto out; +close0: + PARA_INFO_LOG("channel 0: %s\n", para_strerror(-ret)); + btr_remove_node(&ct->btrn[0]); + if (ct->btrn[1] && ct->status == CL_SENDING) + return 0; out: - t->error = ret; - if (ret < 0) { - 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(&ct->btrn); + if (ret >= 0) + return 0; + btr_remove_node(&ct->btrn[0]); + btr_remove_node(&ct->btrn[1]); + if (ret != -E_SERVER_CMD_SUCCESS && ret != -E_SERVER_CMD_FAILURE) + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + if (ct->scc.fd >= 0) { + close(ct->scc.fd); + ct->scc.fd = -1; } + free_argv(ct->features); + ct->features = NULL; + sc_free(ct->scc.recv); + ct->scc.recv = NULL; + sc_free(ct->scc.send); + ct->scc.send = NULL; + return ret; } /** @@ -595,13 +485,17 @@ int client_connect(struct client_task *ct, struct sched *s, if (ret < 0) goto err_out; ct->status = CL_CONNECTED; - ct->btrn = btr_new_node(&(struct btr_node_description) - EMBRACE(.name = "client", .parent = parent, .child = child)); - ct->task.pre_select = client_pre_select; - ct->task.post_select = client_post_select; - ct->task.error = 0; - sprintf(ct->task.status, "client"); - register_task(s, &ct->task); + ct->btrn[0] = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "client recv", .parent = NULL, .child = child)); + ct->btrn[1] = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "client send", .parent = parent, .child = NULL)); + + ct->task = task_register(&(struct task_info) { + .name = "client", + .pre_select = client_pre_select, + .post_select = client_post_select, + .context = ct, + }, s); return 1; err_out: close(ct->scc.fd); @@ -609,6 +503,15 @@ err_out: return ret; } +__noreturn static void print_help_and_die(struct client_task *ct) +{ + struct ggo_help h = DEFINE_GGO_HELP(client); + bool d = ct->conf.detailed_help_given; + + ggo_print_help(&h, d? GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS); + exit(0); +} + /** * Parse a client configuration. * @@ -639,7 +542,9 @@ int client_parse_config(int argc, char *argv[], struct client_task **ct_ptr, ret = -E_CLIENT_SYNTAX; if (client_cmdline_parser(argc, argv, &ct->conf)) goto out; - HANDLE_VERSION_FLAG("client", ct->conf); + version_handle_flag("client", ct->conf.version_given); + if (ct->conf.help_given || ct->conf.detailed_help_given) + print_help_and_die(ct); ct->config_file = ct->conf.config_file_given? para_strdup(ct->conf.config_file_arg) :