Pass full argument list to mood parsers.
[paraslash.git] / client_common.c
1 /*
2 * Copyright (C) 1997-2009 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 <sys/types.h>
10 #include <dirent.h>
11 #include <openssl/rc4.h>
12
13 #include "para.h"
14 #include "error.h"
15 #include "list.h"
16 #include "sched.h"
17 #include "client.cmdline.h"
18 #include "crypt.h"
19 #include "rc4.h"
20 #include "net.h"
21 #include "fd.h"
22 #include "string.h"
23 #include "client.cmdline.h"
24 #include "client.h"
25 #include "hash.h"
26
27 /**
28 * Close the connection to para_server and free all resources.
29 *
30 * \param ct Pointer to the client data.
31 *
32 * \sa client_open.
33 */
34 void client_close(struct client_task *ct)
35 {
36 if (!ct)
37 return;
38 if (ct->rc4c.fd >= 0)
39 close(ct->rc4c.fd);
40 free(ct->buf);
41 free(ct->user);
42 free(ct->config_file);
43 free(ct->key_file);
44 client_cmdline_parser_free(&ct->conf);
45 free(ct);
46 }
47
48 /**
49 * The preselect hook for server commands.
50 *
51 * \param s Pointer to the scheduler.
52 * \param t Pointer to the task struct for this command.
53 *
54 * The task pointer must contain a pointer to the initialized client data
55 * structure as it is returned by client_open().
56 *
57 * This function checks the state of the connection and adds the file descriptor
58 * of the connection to the read or write fd set of \a s accordingly.
59 *
60 * \sa register_task() client_open(), struct sched, struct task.
61 */
62 static void client_pre_select(struct sched *s, struct task *t)
63 {
64 struct client_task *ct = container_of(t, struct client_task, task);
65
66 ct->check_r = 0;
67 ct->check_w = 0;
68 if (ct->rc4c.fd < 0)
69 return;
70 switch (ct->status) {
71 case CL_CONNECTED:
72 case CL_SENT_AUTH:
73 case CL_SENT_CH_RESPONSE:
74 case CL_SENT_COMMAND:
75 para_fd_set(ct->rc4c.fd, &s->rfds, &s->max_fileno);
76 ct->check_r = 1;
77 return;
78
79 case CL_RECEIVED_WELCOME:
80 case CL_RECEIVED_CHALLENGE:
81 case CL_RECEIVED_PROCEED:
82 para_fd_set(ct->rc4c.fd, &s->wfds, &s->max_fileno);
83 ct->check_w = 1;
84 return;
85
86 case CL_RECEIVING:
87 if (ct->loaded < CLIENT_BUFSIZE - 1) {
88 para_fd_set(ct->rc4c.fd, &s->rfds, &s->max_fileno);
89 ct->check_r = 1;
90 }
91 return;
92 case CL_SENDING:
93 if (!ct->in_loaded) /* stdin task not yet started */
94 return;
95 if (*ct->in_loaded) {
96 PARA_INFO_LOG("loaded: %zd\n", *ct->in_loaded);
97 para_fd_set(ct->rc4c.fd, &s->wfds, &s->max_fileno);
98 ct->check_w = 1;
99 } else {
100 if (*ct->in_error) {
101 t->error = *ct->in_error;
102 s->timeout.tv_sec = 0;
103 s->timeout.tv_usec = 1;
104 }
105 }
106 return;
107 }
108 }
109
110 static ssize_t client_recv_buffer(struct client_task *ct)
111 {
112 ssize_t ret;
113
114 if (ct->status < CL_SENT_CH_RESPONSE)
115 ret = recv_buffer(ct->rc4c.fd, ct->buf + ct->loaded,
116 CLIENT_BUFSIZE - ct->loaded);
117 else
118 ret = rc4_recv_buffer(&ct->rc4c, ct->buf + ct->loaded,
119 CLIENT_BUFSIZE - ct->loaded);
120 if (!ret)
121 return -E_SERVER_EOF;
122 if (ret > 0)
123 ct->loaded += ret;
124 return ret;
125 }
126
127 /**
128 * The post select hook for client commands.
129 *
130 * \param s Pointer to the scheduler.
131 * \param t Pointer to the task struct for this command.
132 *
133 * Depending on the current state of the connection and the status of the read
134 * and write fd sets of \a s, this function performs the necessary steps to
135 * authenticate the connection, to send the command given by \a t->private_data
136 * and to receive para_server's output, if any.
137 *
138 * \sa struct sched, struct task.
139 */
140 static void client_post_select(struct sched *s, struct task *t)
141 {
142 struct client_task *ct = container_of(t, struct client_task, task);
143 unsigned char crypt_buf[1024];
144
145 t->error = 0;
146 if (ct->rc4c.fd < 0)
147 return;
148 if (!ct->check_r && !ct->check_w)
149 return;
150 if (ct->check_r && !FD_ISSET(ct->rc4c.fd, &s->rfds))
151 return;
152 if (ct->check_w && !FD_ISSET(ct->rc4c.fd, &s->wfds))
153 return;
154 switch (ct->status) {
155 case CL_CONNECTED: /* receive welcome message */
156 t->error = client_recv_buffer(ct);
157 if (t->error < 0)
158 goto err;
159 ct->status = CL_RECEIVED_WELCOME;
160 return;
161 case CL_RECEIVED_WELCOME: /* send auth command */
162 sprintf(ct->buf, AUTH_REQUEST_MSG "%s", ct->user);
163 PARA_INFO_LOG("--> %s\n", ct->buf);
164 t->error = send_buffer(ct->rc4c.fd, ct->buf);
165 if (t->error < 0)
166 goto err;
167 ct->status = CL_SENT_AUTH;
168 return;
169 case CL_SENT_AUTH: /* receive challenge and rc4 keys */
170 ct->loaded = 0;
171 t->error = client_recv_buffer(ct);
172 if (t->error < 0)
173 goto err;
174 PARA_INFO_LOG("<-- [challenge] (%d bytes)\n", t->error);
175 /* decrypt challenge/rc4 buffer */
176 t->error = para_decrypt_buffer(ct->key_file, crypt_buf,
177 (unsigned char *)ct->buf, t->error);
178 if (t->error < 0)
179 goto err;
180 ct->status = CL_RECEIVED_CHALLENGE;
181 RC4_set_key(&ct->rc4c.send_key, RC4_KEY_LEN,
182 crypt_buf + CHALLENGE_SIZE);
183 RC4_set_key(&ct->rc4c.recv_key, RC4_KEY_LEN,
184 crypt_buf + CHALLENGE_SIZE + RC4_KEY_LEN);
185 return;
186 case CL_RECEIVED_CHALLENGE:
187 {
188 unsigned char challenge_sha1[HASH_SIZE];
189 /* send sha1 of decrypted challenge */
190 sha1_hash((char *)crypt_buf, CHALLENGE_SIZE, challenge_sha1);
191 hash_to_asc(challenge_sha1, ct->buf);
192 PARA_INFO_LOG("--> %s\n", ct->buf);
193 t->error = send_bin_buffer(ct->rc4c.fd, (char *)challenge_sha1,
194 HASH_SIZE);
195 if (t->error < 0)
196 goto err;
197 ct->status = CL_SENT_CH_RESPONSE;
198 return;
199 }
200 case CL_SENT_CH_RESPONSE: /* read server response */
201 {
202 size_t bytes_received;
203 ct->loaded = 0;
204 t->error = client_recv_buffer(ct);
205 if (t->error < 0)
206 goto err;
207 bytes_received = t->error;
208 /* check if server has sent "Proceed" message */
209 t->error = -E_CLIENT_AUTH;
210 if (bytes_received < PROCEED_MSG_LEN)
211 goto err;
212 if (!strstr(ct->buf, PROCEED_MSG))
213 goto err;
214 ct->status = CL_RECEIVED_PROCEED;
215 t->error = 0;
216 return;
217 }
218 case CL_RECEIVED_PROCEED: /* concat args and send command */
219 {
220 int i;
221 char *command = NULL;
222 for (i = 0; i < ct->conf.inputs_num; i++) {
223 char *tmp = command;
224 command = make_message("%s\n%s", command?
225 command : "", ct->conf.inputs[i]);
226 free(tmp);
227 }
228 command = para_strcat(command, EOC_MSG "\n");
229 PARA_DEBUG_LOG("--> %s\n", command);
230 t->error = rc4_send_buffer(&ct->rc4c, command);
231 free(command);
232 if (t->error < 0)
233 goto err;
234 ct->status = CL_SENT_COMMAND;
235 return;
236 }
237 case CL_SENT_COMMAND:
238 ct->loaded = 0;
239 t->error = client_recv_buffer(ct);
240 if (t->error < 0)
241 goto err;
242 if (strstr(ct->buf, AWAITING_DATA_MSG))
243 ct->status = CL_SENDING;
244 else
245 ct->status = CL_RECEIVING;
246 return;
247 case CL_SENDING:
248 PARA_INFO_LOG("loaded: %zd\n", *ct->in_loaded);
249 t->error = rc4_send_bin_buffer(&ct->rc4c, ct->inbuf,
250 *ct->in_loaded);
251 if (t->error < 0)
252 goto err;
253 *ct->in_loaded = 0;
254 return;
255 case CL_RECEIVING:
256 t->error = client_recv_buffer(ct);
257 if (t->error < 0)
258 goto err;
259 return;
260 }
261 err:
262 if (t->error != -E_SERVER_EOF)
263 PARA_ERROR_LOG("%s\n", para_strerror(-t->error));
264 }
265
266 /* connect to para_server and register the client task */
267 static int client_connect(struct client_task *ct)
268 {
269 int ret;
270
271 ct->rc4c.fd = -1;
272 ret = makesock(AF_UNSPEC, IPPROTO_TCP, 0, ct->conf.hostname_arg,
273 ct->conf.server_port_arg);
274 if (ret < 0)
275 return ret;
276 ct->rc4c.fd = ret;
277 ct->status = CL_CONNECTED;
278 ret = mark_fd_nonblocking(ct->rc4c.fd);
279 if (ret < 0)
280 goto err_out;
281 ct->task.pre_select = client_pre_select;
282 ct->task.post_select = client_post_select;
283 sprintf(ct->task.status, "client");
284 register_task(&ct->task);
285 return 1;
286 err_out:
287 close(ct->rc4c.fd);
288 ct->rc4c.fd = -1;
289 return ret;
290 }
291
292 /**
293 * Open connection to para_server.
294 *
295 * \param argc Usual argument count.
296 * \param argv Usual argument vector.
297 * \param ct_ptr Points to dynamically allocated and initialized client task
298 * struct upon successful return.
299 * \param loglevel If not \p NULL, the number of the loglevel is stored here.
300 *
301 * Check the command line options given by \a argc and argv, set default values
302 * for user name and rsa key file, read further option from the config file.
303 * Finally, establish a connection to para_server.
304 *
305 * \return Standard.
306 */
307 int client_open(int argc, char *argv[], struct client_task **ct_ptr,
308 int *loglevel)
309 {
310 char *home = para_homedir();
311 int ret;
312 struct client_task *ct = para_calloc(sizeof(struct client_task));
313
314 ct->buf = para_malloc(CLIENT_BUFSIZE);
315 *ct_ptr = ct;
316 ct->rc4c.fd = -1;
317 ret = -E_CLIENT_SYNTAX;
318 if (client_cmdline_parser(argc, argv, &ct->conf))
319 goto out;
320 HANDLE_VERSION_FLAG("client", ct->conf);
321 ret = -E_CLIENT_SYNTAX;
322 if (!ct->conf.inputs_num)
323 goto out;
324 ct->user = ct->conf.user_given?
325 para_strdup(ct->conf.user_arg) : para_logname();
326
327 ct->key_file = ct->conf.key_file_given?
328 para_strdup(ct->conf.key_file_arg) :
329 make_message("%s/.paraslash/key.%s", home, ct->user);
330
331 ct->config_file = ct->conf.config_file_given?
332 para_strdup(ct->conf.config_file_arg) :
333 make_message("%s/.paraslash/client.conf", home);
334 ret = file_exists(ct->config_file);
335 if (!ret && ct->conf.config_file_given) {
336 ret = -E_NO_CONFIG;
337 goto out;
338 }
339 if (ret) {
340 struct client_cmdline_parser_params params = {
341 .override = 0,
342 .initialize = 0,
343 .check_required = 0,
344 .check_ambiguity = 0,
345 .print_errors = 0
346 };
347 ret = -E_BAD_CONFIG;
348 if (client_cmdline_parser_config_file(ct->config_file,
349 &ct->conf, &params))
350 goto out;
351 }
352 if (loglevel)
353 *loglevel = get_loglevel_by_name(ct->conf.loglevel_arg);
354 PARA_INFO_LOG("loglevel: %s\n", ct->conf.loglevel_arg);
355 PARA_INFO_LOG("config_file: %s\n", ct->config_file);
356 PARA_INFO_LOG("key_file: %s\n", ct->key_file);
357 PARA_NOTICE_LOG("connecting %s:%d\n", ct->conf.hostname_arg,
358 ct->conf.server_port_arg);
359 ret = client_connect(ct);
360 out:
361 free(home);
362 if (ret < 0) {
363 PARA_ERROR_LOG("%s\n", para_strerror(-ret));
364 client_close(ct);
365 *ct_ptr = NULL;
366 }
367 return ret;
368 }