-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathconnection.c
394 lines (332 loc) · 9.36 KB
/
connection.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
#include <apr_strings.h>
#include <apr_poll.h>
#include "connection.h"
#include "config.h"
#include "utils.h"
#include "cmdio.h"
#include "ftpcodes.h"
static void port_cleanup(struct lfd_sess* sess)
{
//there is no resource to free() except the allocated memory
// which will be freed when the pool gets destroyed
sess->p_port_sockaddr = NULL;
}
static void pasv_cleanup(struct lfd_sess* sess)
{
if (NULL != sess->pasv_listen_fd)
{
(void) apr_socket_close(sess->pasv_listen_fd);
sess->pasv_listen_fd = NULL;
}
}
void lfd_communication_cleanup(struct lfd_sess * sess)
{
port_cleanup(sess);
pasv_cleanup(sess);
}
static apr_status_t bind_to_random_passive_port(struct lfd_sess * sess, apr_sockaddr_t ** psaddr, apr_port_t * pport)
{
apr_port_t bind_retries = lfd_config_max_sockets_to_try_to_bind;
apr_port_t min_port = (lfd_config_min_pasv_port < 1023 ) ? 1024 : lfd_config_min_pasv_port;
apr_port_t max_port = (lfd_config_min_pasv_port > 65535) ? 65535 : lfd_config_max_pasv_port;
apr_port_t rand_port;
unsigned char randBuff[2];
apr_status_t rc = -1;
apr_socket_t *listen_fd;
apr_sockaddr_t *saddr;
while(--bind_retries)
{
rc = apr_generate_random_bytes(randBuff, (apr_size_t)2);
if(APR_SUCCESS != rc)
{
continue;
}
//get random short value
rand_port = (apr_port_t) (randBuff[0] << 8 | randBuff[1]);
//scale it to fit the interval
rand_port = (apr_port_t) ( ( (apr_uint64_t) (max_port - min_port + 1)) * rand_port / max_port + min_port );
rc = apr_sockaddr_info_get(&saddr, lfd_config_listen_host, APR_UNSPEC, rand_port, 0, sess->sess_pool);
if(APR_SUCCESS != rc)
{
continue;
}
rc = apr_socket_create(&listen_fd, saddr->family, SOCK_STREAM, APR_PROTO_TCP, sess->sess_pool);
if(APR_SUCCESS != rc)
{
continue;
}
rc = apr_socket_opt_set(listen_fd, APR_SO_REUSEADDR, 1);
if(APR_SUCCESS != rc)
{
continue;
}
rc = apr_socket_bind(listen_fd, saddr);
if(APR_SUCCESS == rc)
{
break;
}
}
if(!bind_retries)
{
lfd_log(LFD_ERROR, "bind_to_random_passive_port failed");
return APR_ENOSOCKET;
}
if (APR_SUCCESS == rc)
{
*pport = rand_port;
*psaddr = saddr;
sess->pasv_listen_fd = listen_fd;
}
return rc;
}
/**
*@brief parses a string of delimited numbers between 0 and 255 and stores them in the p_items buffer
*/
const unsigned char * lfd_str_parse_uchar_string_sep(char * input_str, char sep, unsigned char* p_items, unsigned int items)
{
char * last, * str;
unsigned int i;
apr_status_t rc;
char sep_str[] = " ";
sep_str[0] = sep;
last = input_str;
for (i = 0; i < items; i++)
{
apr_off_t this_number;
str = apr_strtok(input_str, sep_str, &last);
if((NULL == str) || ('\0' == *str))
return 0;
/* Sanity - check for too many or two few tokens! */
if ( (i < (items-1) && (0 == strlen(last))) ||
(i == (items-1) && (0 != strlen(last))))
{
return 0;
}
rc = apr_strtoff(&this_number, input_str, NULL, 10);
if(APR_SUCCESS != rc)
return 0;
// validate range fits into one byte
if(this_number < 0 || this_number > 255)
return 0;
/* If this truncates from int to uchar, we don't care */
p_items[i] = (unsigned char) this_number;
/* The right hand side of the comma now becomes the new string to breakdown */
input_str = last;
}
return p_items;
}
apr_status_t handle_pasv(struct lfd_sess * sess)
{
apr_status_t rc;
apr_sockaddr_t *saddr;
apr_port_t port;
unsigned char vals[6];
char *addr;
lfd_communication_cleanup(sess);
rc = bind_to_random_passive_port(sess, &saddr, &port);
if(APR_SUCCESS != rc)
{
lfd_log_apr_err(rc, "bind_to_random_passive_port failed");
return rc;
}
rc = apr_socket_listen(sess->pasv_listen_fd, 1); //backlog of one!
if(APR_SUCCESS != rc)
{
lfd_log_apr_err(rc, "apr_socket_listen failed");
return rc;
}
//get the IP represented as a string of characters
rc = apr_sockaddr_ip_get(&addr, saddr);
if(APR_SUCCESS != rc)
{
lfd_log_apr_err(rc, "apr_sockaddr_ip_get failed");
return rc;
}
// fill the first 4 elemnts of vals with the numbers from the IP address
lfd_str_parse_uchar_string_sep(addr, '.', vals, 4);
// append the decomposed port number
vals[4] = (unsigned char) (port >> 8);
vals[5] = (unsigned char) (port & 0xFF);
rc = lfd_cmdio_write(sess, FTP_PASVOK, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
(int)vals[0], (int)vals[1], (int)vals[2],
(int)vals[3], (int)vals[4], (int)vals[5]);
if(APR_SUCCESS != rc)
{
lfd_log_apr_err(rc, "lfd_cmdio_write failed");
}
return rc;
}
// specify an alternate data port by use of the PORT command
apr_status_t handle_port(struct lfd_sess* sess)
{
apr_status_t rc;
apr_port_t the_port;
unsigned char vals[6];
const unsigned char* p_raw;
struct apr_sockaddr_t * saddr;
char * ip_str;
lfd_communication_cleanup(sess);
// the received command argumet is a string of the form:
// ip1,ip2,ip3,ip4,po1,po2 - representing the remote host's IP and port number.
p_raw = lfd_str_parse_uchar_string_sep(sess->ftp_arg_str, ',', vals, sizeof(vals));
if (p_raw == 0)
{
lfd_cmdio_write(sess, FTP_BADCMD, "Illegal PORT command.");
return APR_EINVAL;
}
the_port = (vals[4] << 8) | vals[5];
ip_str = apr_psprintf(sess->loop_pool, "%d.%d.%d.%d", vals[0], vals[1], vals[2], vals[3]);
rc = apr_sockaddr_info_get(&saddr, ip_str, APR_UNSPEC, the_port, 0, sess->sess_pool);
sess->p_port_sockaddr = saddr;
if(APR_SUCCESS != rc)
{
lfd_cmdio_write(sess, FTP_BADCMD, "Illegal PORT command.");
return rc;
}
lfd_cmdio_write(sess, FTP_PORTOK, "PORT command successful. Consider using PASV.");
return APR_SUCCESS;
}
static int port_active(struct lfd_sess* sess);
static int pasv_active(struct lfd_sess* sess);
static int port_active(struct lfd_sess* sess)
{
int rc = 0;
if (NULL != sess->p_port_sockaddr)
{
rc = 1;
if (pasv_active(sess))
{
bug("port and pasv both active");
}
}
return rc;
}
static int pasv_active(struct lfd_sess* sess)
{
int rc = 0;
if (NULL != sess->pasv_listen_fd)
{
rc = 1;
if (port_active(sess))
{
bug("pasv and port both active");
}
}
return rc;
}
int data_transfer_checks_ok(struct lfd_sess* sess)
{
if (!pasv_active(sess) && !port_active(sess))
{
lfd_cmdio_write(sess, FTP_BADSENDCONN, "Use PORT or PASV first.");
return 0;
}
return 1;
}
static void init_data_sock_params(struct lfd_sess* sess, apr_socket_t * sock_fd)
{
if (NULL != sess->data_conn->data_sock)
{
bug("data descriptor still present in init_data_sock_params");
}
sess->data_conn->data_sock = sock_fd;
sess->data_conn->in_progress = 0;
apr_socket_opt_set(sock_fd, APR_SO_KEEPALIVE, 1);
//Set up lingering, so that we wait for all data to transfer, and report
// more accurate transfer rates.
apr_socket_opt_set(sock_fd, APR_SO_LINGER, 1);
}
/**
* @brief Get a connected socket to the client's listening data port.
* Also binds the local end of the socket to a configurable (default 20) data port.
*/
static apr_status_t get_bound_and_connected_ftp_port_sock(struct lfd_sess* sess, apr_socket_t ** psock)
{
apr_socket_t * sock;
apr_sockaddr_t * saddr;
apr_status_t rc;
*psock = NULL;
rc = apr_sockaddr_info_get(&saddr, NULL, APR_UNSPEC, lfd_config_data_port, 0, sess->loop_pool);
if(APR_SUCCESS != rc)
return rc;
rc = apr_socket_create(&sock, APR_INET, SOCK_STREAM, APR_PROTO_TCP, sess->loop_pool);
if(APR_SUCCESS != rc)
return rc;
rc = apr_socket_opt_set(sock, APR_SO_REUSEADDR, 1);
if(APR_SUCCESS != rc)
return rc;
rc = apr_socket_bind(sock, saddr);
if(APR_SUCCESS != rc)
return rc;
rc = apr_socket_connect (sock, sess->p_port_sockaddr);
if(APR_SUCCESS != rc)
return rc;
*psock = sock;
return APR_SUCCESS;
}
static apr_status_t lfd_ftpdataio_get_pasv_fd(struct lfd_sess* sess, apr_socket_t ** psock)
{
apr_status_t rc;
*psock = NULL;
/*
* FIXME: need timeout
*/
rc=apr_socket_accept(psock, sess->pasv_listen_fd, sess->loop_pool);
if (APR_SUCCESS != rc)
{
lfd_cmdio_write(sess, FTP_BADSENDCONN, "Failed to accept connection.");
lfd_log_apr_err(rc, "apr_socket_accept failed");
return rc;
}
init_data_sock_params(sess, *psock);
return APR_SUCCESS;
}
static apr_status_t ftpdataio_get_port_fd(struct lfd_sess* sess, apr_socket_t ** psock)
{
apr_status_t rc;
apr_socket_t * remote_fd;
*psock = NULL;
rc = get_bound_and_connected_ftp_port_sock(sess, &remote_fd);
if (APR_SUCCESS != rc)
{
lfd_cmdio_write(sess, FTP_BADSENDCONN, "Failed to establish connection.");
lfd_log_apr_err(rc, "get_bound_and_connected_ftp_port_sock failed");
return rc;
}
init_data_sock_params(sess, remote_fd);
*psock = remote_fd;
return APR_SUCCESS;
}
/**
* @brief gets an apr_socket_t which is to be used for data transfer.
* On success sends the p_status_msg message to the client, confirming a successful connection.
*/
apr_socket_t* lfd_get_remote_transfer_fd(struct lfd_sess* sess, const char* p_status_msg)
{
apr_socket_t * remote_fd;
if (!pasv_active(sess) && !port_active(sess))
{
bug("neither PORT nor PASV active in get_remote_transfer_fd");
}
sess->data_conn->abor_received = 0;
if (pasv_active(sess))
{
lfd_ftpdataio_get_pasv_fd(sess, &remote_fd);
}
else
{
ftpdataio_get_port_fd(sess, & remote_fd);
}
if (NULL == remote_fd)
{
return NULL;
}
lfd_cmdio_write(sess, FTP_DATACONN, p_status_msg);
return remote_fd;
}
void lfd_dispose_transfer_fd(struct lfd_sess* sess)
{
apr_socket_opt_set(sess->data_conn->data_sock, APR_SO_LINGER, 0);
apr_socket_close(sess->data_conn->data_sock);
sess->data_conn->data_sock = NULL;
}