/*
- * Copyright (C) 1997-2012 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 1997 Andre Noll <maan@tuebingen.mpg.de>
*
* Licensed under the GPL v2. For licencing details see COPYING.
*/
/** \file client_common.c Common functions of para_client and para_audiod. */
+#include <netinet/in.h>
+#include <sys/socket.h>
#include <regex.h>
#include <sys/types.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
#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"
#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_free_node(ct->btrn);
- ct->btrn = NULL;
-}
-
/**
* 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;
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;
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;
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;
}
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:
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;
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
{
/* 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 though */
+ 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(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;
}
/**
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);
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.
*
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) :