audiod: Use non-blocking writes for stat clients.
[paraslash.git] / client_common.c
1 /*
2  * Copyright (C) 1997-2011 Andre Noll <maan@systemlinux.org>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file client_common.c Common functions of para_client and para_audiod. */
8
9 #include <regex.h>
10 #include <sys/types.h>
11
12 #include "para.h"
13 #include "error.h"
14 #include "list.h"
15 #include "sched.h"
16 #include "client.cmdline.h"
17 #include "crypt.h"
18 #include "net.h"
19 #include "fd.h"
20 #include "string.h"
21 #include "client.cmdline.h"
22 #include "client.h"
23 #include "buffer_tree.h"
24 #include "version.h"
25
26 /** The size of the receiving buffer. */
27 #define CLIENT_BUFSIZE 4000
28
29 /**
30  * Close the connection to para_server and free all resources.
31  *
32  * \param ct Pointer to the client data.
33  *
34  * \sa client_open.
35  */
36 void client_close(struct client_task *ct)
37 {
38         if (!ct)
39                 return;
40         if (ct->scc.fd >= 0)
41                 close(ct->scc.fd);
42         sc_free(ct->scc.recv);
43         sc_free(ct->scc.send);
44         free(ct->user);
45         free(ct->config_file);
46         free(ct->key_file);
47         client_cmdline_parser_free(&ct->conf);
48         free(ct);
49 }
50
51 /**
52  * The preselect hook for server commands.
53  *
54  * \param s Pointer to the scheduler.
55  * \param t Pointer to the task struct for this command.
56  *
57  * The task pointer must contain a pointer to the initialized client data
58  * structure as it is returned by client_open().
59  *
60  * This function checks the state of the connection and adds the file descriptor
61  * of the connection to the read or write fd set of \a s accordingly.
62  *
63  * \sa register_task() client_open(), struct sched, struct task.
64  */
65 static void client_pre_select(struct sched *s, struct task *t)
66 {
67         int ret;
68         struct client_task *ct = container_of(t, struct client_task, task);
69         struct btr_node *btrn = ct->btrn;
70
71         if (ct->scc.fd < 0)
72                 return;
73         switch (ct->status) {
74         case CL_CONNECTED:
75         case CL_SENT_AUTH:
76         case CL_SENT_CH_RESPONSE:
77         case CL_SENT_COMMAND:
78                 para_fd_set(ct->scc.fd, &s->rfds, &s->max_fileno);
79                 return;
80
81         case CL_RECEIVED_WELCOME:
82         case CL_RECEIVED_PROCEED:
83                 para_fd_set(ct->scc.fd, &s->wfds, &s->max_fileno);
84                 return;
85
86         case CL_RECEIVING:
87                 ret = btr_node_status(btrn, 0, BTR_NT_ROOT);
88                 if (ret != 0) {
89                         if (ret < 0)
90                                 sched_min_delay(s);
91                         else
92                                 para_fd_set(ct->scc.fd, &s->rfds,
93                                         &s->max_fileno);
94                 }
95                 return;
96         case CL_SENDING:
97                 ret = btr_node_status(btrn, 0, BTR_NT_LEAF);
98                 if (ret != 0) {
99                         if (ret < 0)
100                                 sched_min_delay(s);
101                         else
102                                 para_fd_set(ct->scc.fd, &s->wfds,
103                                         &s->max_fileno);
104                 }
105                 return;
106         }
107 }
108
109 static int client_recv_buffer(struct client_task *ct, fd_set *rfds,
110                 char *buf, size_t sz, size_t *n)
111 {
112         int ret;
113
114         if (ct->status < CL_SENT_CH_RESPONSE)
115                 return read_nonblock(ct->scc.fd, buf, sz, rfds, n);
116
117         *n = 0;
118         ret = sc_recv_buffer(&ct->scc, buf, sz);
119         /*
120          * sc_recv_buffer is used with blocking fds elsewhere, so it
121          * does not use the nonblock-API. Therefore we need to
122          * check for EOF and EAGAIN.
123          */
124         if (ret == 0)
125                 return -E_SERVER_EOF;
126         if (ret == -ERRNO_TO_PARA_ERROR(EAGAIN))
127                 return 0;
128         if (ret < 0)
129                 return ret;
130         *n = ret;
131         return 0;
132 }
133
134 /**
135  * The post select hook for client commands.
136  *
137  * \param s Pointer to the scheduler.
138  * \param t Pointer to the task struct for this command.
139  *
140  * Depending on the current state of the connection and the status of the read
141  * and write fd sets of \a s, this function performs the necessary steps to
142  * authenticate the connection, to send the command given by \a t->private_data
143  * and to receive para_server's output, if any.
144  *
145  * \sa struct sched, struct task.
146  */
147 static void client_post_select(struct sched *s, struct task *t)
148 {
149         struct client_task *ct = container_of(t, struct client_task, task);
150         struct btr_node *btrn = ct->btrn;
151         int ret = 0;
152         size_t n;
153         char buf[CLIENT_BUFSIZE];
154
155         t->error = 0;
156         if (ct->scc.fd < 0)
157                 return;
158         switch (ct->status) {
159         case CL_CONNECTED: /* receive welcome message */
160                 ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n);
161                 if (ret < 0 || n == 0)
162                         goto out;
163                 ct->status = CL_RECEIVED_WELCOME;
164                 return;
165         case CL_RECEIVED_WELCOME: /* send auth command */
166                 sprintf(buf, AUTH_REQUEST_MSG "%s", ct->user);
167                 PARA_INFO_LOG("--> %s\n", buf);
168                 if (!FD_ISSET(ct->scc.fd, &s->wfds))
169                         return;
170                 ret = send_buffer(ct->scc.fd, buf);
171                 if (ret < 0)
172                         goto out;
173                 ct->status = CL_SENT_AUTH;
174                 return;
175         case CL_SENT_AUTH:
176                 /*
177                  * Receive challenge and session keys, decrypt the challenge and
178                  * send back the hash of the decrypted challenge.
179                  */
180                 {
181                 /* decrypted challenge/session key buffer */
182                 unsigned char crypt_buf[1024];
183                 /* the SHA1 of the decrypted challenge */
184                 unsigned char challenge_hash[HASH_SIZE];
185
186                 ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n);
187                 if (ret < 0 || n == 0)
188                         goto out;
189                 PARA_INFO_LOG("<-- [challenge] (%zu bytes)\n", n);
190                 ret = priv_decrypt(ct->key_file, crypt_buf,
191                         (unsigned char *)buf, n);
192                 if (ret < 0)
193                         goto out;
194                 hash_function((char *)crypt_buf, CHALLENGE_SIZE, challenge_hash);
195                 ct->scc.send = sc_new(crypt_buf + CHALLENGE_SIZE, SESSION_KEY_LEN);
196                 ct->scc.recv = sc_new(crypt_buf + CHALLENGE_SIZE + SESSION_KEY_LEN,
197                         SESSION_KEY_LEN);
198                 hash_to_asc(challenge_hash, buf);
199                 PARA_INFO_LOG("--> %s\n", buf);
200                 ret = send_bin_buffer(ct->scc.fd, (char *)challenge_hash,
201                         HASH_SIZE);
202                 if (ret < 0)
203                         goto out;
204                 ct->status = CL_SENT_CH_RESPONSE;
205                 return;
206                 }
207         case CL_SENT_CH_RESPONSE: /* read server response */
208                 {
209                 ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n);
210                 if (ret < 0 || n == 0)
211                         goto out;
212                 /* check if server has sent "Proceed" message */
213                 ret = -E_CLIENT_AUTH;
214                 if (n < PROCEED_MSG_LEN)
215                         goto out;
216                 if (!strstr(buf, PROCEED_MSG))
217                         goto out;
218                 ct->status = CL_RECEIVED_PROCEED;
219                 return;
220                 }
221         case CL_RECEIVED_PROCEED: /* concat args and send command */
222                 {
223                 int i;
224                 char *command = NULL;
225                 if (!FD_ISSET(ct->scc.fd, &s->wfds))
226                         return;
227                 for (i = 0; i < ct->conf.inputs_num; i++) {
228                         char *tmp = command;
229                         command = make_message("%s\n%s", command?
230                                 command : "", ct->conf.inputs[i]);
231                         free(tmp);
232                 }
233                 command = para_strcat(command, EOC_MSG "\n");
234                 PARA_DEBUG_LOG("--> %s\n", command);
235                 ret = sc_send_buffer(&ct->scc, command);
236                 free(command);
237                 if (ret < 0)
238                         goto out;
239                 ct->status = CL_SENT_COMMAND;
240                 return;
241                 }
242         case CL_SENT_COMMAND:
243                 {
244                 char *buf2;
245                 /* can not use "buf" here because we need a malloced buffer */
246                 buf2 = para_malloc(CLIENT_BUFSIZE);
247                 ret = client_recv_buffer(ct, &s->rfds, buf2, CLIENT_BUFSIZE, &n);
248                 if (n > 0) {
249                         if (strstr(buf2, AWAITING_DATA_MSG)) {
250                                 free(buf2);
251                                 ct->status = CL_SENDING;
252                                 return;
253                         }
254                         ct->status = CL_RECEIVING;
255                         btr_add_output(buf2, n, btrn);
256                 } else
257                         free(buf2);
258                 goto out;
259                 }
260         case CL_SENDING:
261                 {
262                 char *buf2;
263                 size_t sz;
264                 ret = btr_node_status(btrn, 0, BTR_NT_LEAF);
265                 if (ret < 0)
266                         goto out;
267                 if (ret == 0)
268                         return;
269                 if (!FD_ISSET(ct->scc.fd, &s->wfds))
270                         return;
271                 sz = btr_next_buffer(btrn, &buf2);
272                 ret = sc_send_bin_buffer(&ct->scc, buf2, sz);
273                 if (ret < 0)
274                         goto out;
275                 btr_consume(btrn, sz);
276                 return;
277                 }
278         case CL_RECEIVING:
279                 {
280                 char *buf2;
281                 ret = btr_node_status(btrn, 0, BTR_NT_ROOT);
282                 if (ret < 0)
283                         goto out;
284                 if (ret == 0)
285                         return;
286                 /*
287                  * The FD_ISSET() is not strictly necessary, but is allows us
288                  * to skip the malloc below if there is nothing to read anyway.
289                  */
290                 if (!FD_ISSET(ct->scc.fd, &s->rfds))
291                         return;
292                 buf2 = para_malloc(CLIENT_BUFSIZE);
293                 ret = client_recv_buffer(ct, &s->rfds, buf2, CLIENT_BUFSIZE, &n);
294                 if (n > 0) {
295                         buf2 = para_realloc(buf2, n);
296                         btr_add_output(buf2, n, btrn);
297                 } else
298                         free(buf2);
299                 goto out;
300                 }
301         }
302 out:
303         t->error = ret;
304         if (ret < 0) {
305                 if (ret != -E_SERVER_EOF && ret != -E_BTR_EOF)
306                         PARA_ERROR_LOG("%s\n", para_strerror(-t->error));
307                 btr_remove_node(btrn);
308         }
309 }
310
311 /* connect to para_server and register the client task */
312 static int client_connect(struct client_task *ct)
313 {
314         int ret;
315
316         ct->scc.fd = -1;
317         ret = para_connect_simple(IPPROTO_TCP, ct->conf.hostname_arg,
318                                                ct->conf.server_port_arg);
319         if (ret < 0)
320                 return ret;
321         ct->scc.fd = ret;
322         ct->status = CL_CONNECTED;
323         ret = mark_fd_nonblocking(ct->scc.fd);
324         if (ret < 0)
325                 goto err_out;
326         ct->task.pre_select = client_pre_select;
327         ct->task.post_select = client_post_select;
328         sprintf(ct->task.status, "client");
329         register_task(&ct->task);
330         return 1;
331 err_out:
332         close(ct->scc.fd);
333         ct->scc.fd = -1;
334         return ret;
335 }
336
337 /**
338  * Open connection to para_server.
339  *
340  * \param argc Usual argument count.
341  * \param argv Usual argument vector.
342  * \param ct_ptr Points to dynamically allocated and initialized client task
343  * struct upon successful return.
344  * \param loglevel If not \p NULL, the number of the loglevel is stored here.
345  * \param parent Add the new buffer tree node as a child of this node.
346  * \param child Add the new buffer tree node as a parent of this node.
347  *
348  * Check the command line options given by \a argc and argv, set default values
349  * for user name and rsa key file, read further option from the config file.
350  * Finally, establish a connection to para_server.
351  *
352  * \return Standard.
353  */
354 int client_open(int argc, char *argv[], struct client_task **ct_ptr,
355                 int *loglevel, struct btr_node *parent, struct btr_node *child)
356 {
357         char *home = para_homedir();
358         int ret;
359         struct client_task *ct = para_calloc(sizeof(struct client_task));
360
361         ct->btrn = btr_new_node(&(struct btr_node_description)
362                 EMBRACE(.name = "client", .parent = parent, .child = child));
363         *ct_ptr = ct;
364         ct->scc.fd = -1;
365         ret = -E_CLIENT_SYNTAX;
366         if (client_cmdline_parser(argc, argv, &ct->conf))
367                 goto out;
368         HANDLE_VERSION_FLAG("client", ct->conf);
369         ret = -E_CLIENT_SYNTAX;
370         if (!ct->conf.inputs_num)
371                 goto out;
372
373         ct->config_file = ct->conf.config_file_given?
374                 para_strdup(ct->conf.config_file_arg) :
375                 make_message("%s/.paraslash/client.conf", home);
376         ret = file_exists(ct->config_file);
377         if (!ret && ct->conf.config_file_given) {
378                 ret = -E_NO_CONFIG;
379                 goto out;
380         }
381         if (ret) {
382                 struct client_cmdline_parser_params params = {
383                         .override = 0,
384                         .initialize = 0,
385                         .check_required = 0,
386                         .check_ambiguity = 0,
387                         .print_errors = 0
388                 };
389                 ret = -E_BAD_CONFIG;
390                 if (client_cmdline_parser_config_file(ct->config_file,
391                         &ct->conf, &params))
392                         goto out;
393         }
394         ct->user = ct->conf.user_given?
395                 para_strdup(ct->conf.user_arg) : para_logname();
396
397         if (ct->conf.key_file_given)
398                 ct->key_file = para_strdup(ct->conf.key_file_arg);
399         else {
400                 ct->key_file = make_message("%s/.paraslash/key.%s",
401                         home, ct->user);
402                 if (!file_exists(ct->key_file)) {
403                         free(ct->key_file);
404                         ct->key_file = make_message("%s/.ssh/id_rsa", home);
405                 }
406         }
407
408         if (loglevel)
409                 *loglevel = get_loglevel_by_name(ct->conf.loglevel_arg);
410         PARA_INFO_LOG("loglevel: %s\n", ct->conf.loglevel_arg);
411         PARA_INFO_LOG("config_file: %s\n", ct->config_file);
412         PARA_INFO_LOG("key_file: %s\n", ct->key_file);
413         PARA_NOTICE_LOG("connecting %s:%d\n", ct->conf.hostname_arg,
414                 ct->conf.server_port_arg);
415         ret = client_connect(ct);
416 out:
417         free(home);
418         if (ret < 0) {
419                 PARA_ERROR_LOG("%s\n", para_strerror(-ret));
420                 btr_remove_node(ct->btrn);
421                 btr_free_node(ct->btrn);
422                 client_close(ct);
423                 *ct_ptr = NULL;
424         }
425         return ret;
426 }