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