blob: 3b98bacf420290ba8198d9f6e1c480765c16ab78 [file] [log] [blame]
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001/* vi: set sw=4 ts=4: */
2/*
3 * Simple FTP daemon, based on vsftpd 2.0.7 (written by Chris Evans)
4 *
5 * Author: Adam Tkac <vonsch@gmail.com>
6 *
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00007 * Licensed under GPLv2, see file LICENSE in this tarball for details.
8 *
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00009 * Only subset of FTP protocol is implemented but vast majority of clients
10 * should not have any problem. You have to run this daemon via inetd.
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000011 */
12
13#include "libbb.h"
Denis Vlasenko5e4fda02009-03-08 23:46:48 +000014#include <syslog.h>
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000015#include <netinet/tcp.h>
16
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000017#define FTP_DATACONN 150
18#define FTP_NOOPOK 200
19#define FTP_TYPEOK 200
20#define FTP_PORTOK 200
21#define FTP_STRUOK 200
22#define FTP_MODEOK 200
23#define FTP_ALLOOK 202
24#define FTP_STATOK 211
25#define FTP_STATFILE_OK 213
26#define FTP_HELP 214
27#define FTP_SYSTOK 215
28#define FTP_GREET 220
29#define FTP_GOODBYE 221
30#define FTP_TRANSFEROK 226
31#define FTP_PASVOK 227
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +000032/*#define FTP_EPRTOK 228*/
33#define FTP_EPSVOK 229
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000034#define FTP_LOGINOK 230
35#define FTP_CWDOK 250
36#define FTP_RMDIROK 250
37#define FTP_DELEOK 250
38#define FTP_RENAMEOK 250
39#define FTP_PWDOK 257
40#define FTP_MKDIROK 257
41#define FTP_GIVEPWORD 331
42#define FTP_RESTOK 350
43#define FTP_RNFROK 350
Denis Vlasenko20c82162009-03-16 16:19:53 +000044#define FTP_TIMEOUT 421
Denis Vlasenkof1a11b52009-03-09 04:13:59 +000045#define FTP_BADSENDCONN 425
46#define FTP_BADSENDNET 426
47#define FTP_BADSENDFILE 451
48#define FTP_BADCMD 500
49#define FTP_COMMANDNOTIMPL 502
50#define FTP_NEEDUSER 503
51#define FTP_NEEDRNFR 503
52#define FTP_BADSTRU 504
53#define FTP_BADMODE 504
54#define FTP_LOGINERR 530
55#define FTP_FILEFAIL 550
56#define FTP_NOPERM 550
57#define FTP_UPLOADFAIL 553
58
59#define STR1(s) #s
60#define STR(s) STR1(s)
61
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000062/* Convert a constant to 3-digit string, packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000063enum {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000064 /* Shift for Nth decimal digit */
Denis Vlasenkofbf58462009-03-16 20:54:45 +000065 SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
66 SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
67 SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
68 /* And for 4th position (space) */
69 SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000070};
71#define STRNUM32(s) (uint32_t)(0 \
72 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
73 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
74 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
75)
Denis Vlasenkofbf58462009-03-16 20:54:45 +000076#define STRNUM32sp(s) (uint32_t)(0 \
77 | (' ' << SHIFTsp) \
78 | (('0' + ((s) / 1 % 10)) << SHIFT0) \
79 | (('0' + ((s) / 10 % 10)) << SHIFT1) \
80 | (('0' + ((s) / 100 % 10)) << SHIFT2) \
81)
82
83#define MSG_OK "Operation successful\r\n"
84#define MSG_ERR "Error\r\n"
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000085
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000086struct globals {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000087 int pasv_listen_fd;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +000088 int proc_self_fd;
Denis Vlasenko20c82162009-03-16 16:19:53 +000089 int local_file_fd;
90 int start_time;
91 int abs_timeout;
92 int timeout;
93 off_t local_file_pos;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000094 off_t restart_pos;
Denis Vlasenko20c82162009-03-16 16:19:53 +000095 len_and_sockaddr *local_addr;
96 len_and_sockaddr *port_addr;
Denis Vlasenko57a3b172009-03-09 04:38:37 +000097 char *ftp_cmd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +000098 char *ftp_arg;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +000099#if ENABLE_FEATURE_FTP_WRITE
100 char *rnfr_filename;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000101#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000102 /* We need these aligned to uint32_t */
103 char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
104 char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000105};
106#define G (*(struct globals*)&bb_common_bufsiz1)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000107#define INIT_G() do { \
108 strcpy(G.msg_ok + 4, MSG_OK ); \
109 strcpy(G.msg_err + 4, MSG_ERR); \
110} while (0)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000111
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000112
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000113static char *
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000114escape_text(const char *prepend, const char *str, unsigned escapee)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000115{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000116 unsigned retlen, remainlen, chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000117 char *ret, *found;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000118 char append;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000119
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000120 append = (char)escapee;
121 escapee >>= 8;
122
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000123 remainlen = strlen(str);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000124 retlen = strlen(prepend);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000125 ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000126 strcpy(ret, prepend);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000127
128 for (;;) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000129 found = strchrnul(str, escapee);
130 chunklen = found - str + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000131
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000132 /* Copy chunk up to and including escapee (or NUL) to ret */
133 memcpy(ret + retlen, str, chunklen);
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000134 retlen += chunklen;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000135
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000136 if (*found == '\0') {
137 /* It wasn't escapee, it was NUL! */
138 ret[retlen - 1] = append; /* replace NUL */
139 ret[retlen] = '\0'; /* add NUL */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000140 break;
141 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000142 ret[retlen++] = escapee; /* duplicate escapee */
143 str = found + 1;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000144 }
145 return ret;
146}
147
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000148/* Returns strlen as a bonus */
149static unsigned
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000150replace_char(char *str, char from, char to)
151{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000152 char *p = str;
153 while (*p) {
154 if (*p == from)
155 *p = to;
156 p++;
157 }
158 return p - str;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000159}
160
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000161/* NB: status_str is char[4] packed into uint32_t */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000162static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000163cmdio_write(uint32_t status_str, const char *str)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000164{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000165 char *response;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000166 int len;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000167
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000168 /* FTP allegedly uses telnet protocol for command link.
169 * In telnet, 0xff is an escape char, and needs to be escaped: */
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000170 response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000171
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000172 /* ?! does FTP send embedded LFs as NULs? wow */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000173 len = replace_char(response, '\n', '\0');
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000174
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000175 response[len++] = '\n'; /* tack on trailing '\n' */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000176 xwrite(STDOUT_FILENO, response, len);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000177 free(response);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000178}
179
180static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000181cmdio_write_ok(unsigned status)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000182{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000183 *(uint32_t *) G.msg_ok = status;
184 xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000185}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000186#define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000187
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000188/* TODO: output strerr(errno) if errno != 0? */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000189static void
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000190cmdio_write_error(unsigned status)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000191{
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000192 *(uint32_t *) G.msg_err = status;
193 xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000194}
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000195#define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000196
197static void
198cmdio_write_raw(const char *p_text)
199{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000200 xwrite_str(STDOUT_FILENO, p_text);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000201}
202
Denis Vlasenko20c82162009-03-16 16:19:53 +0000203static void
204timeout_handler(int sig UNUSED_PARAM)
205{
206 off_t pos;
207 int sv_errno = errno;
208
209 if (monotonic_sec() - G.start_time > G.abs_timeout)
210 goto timed_out;
211
212 if (!G.local_file_fd)
213 goto timed_out;
214
215 pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
216 if (pos == G.local_file_pos)
217 goto timed_out;
218 G.local_file_pos = pos;
219
220 alarm(G.timeout);
221 errno = sv_errno;
222 return;
223
224 timed_out:
225 cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
226/* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
227 exit(1);
228}
229
Denis Vlasenko9e959202009-03-09 03:15:05 +0000230/* Simple commands */
231
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000232static void
233handle_pwd(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000234{
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000235 char *cwd, *response;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000236
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000237 cwd = xrealloc_getcwd_or_warn(NULL);
238 if (cwd == NULL)
239 cwd = xstrdup("");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000240
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000241 /* We have to promote each " to "" */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000242 response = escape_text(" \"", cwd, ('"' << 8) + '"');
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000243 free(cwd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000244 cmdio_write(STRNUM32(FTP_PWDOK), response);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000245 free(response);
246}
247
248static void
249handle_cwd(void)
250{
251 if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000252 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000253 return;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000254 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000255 WRITE_OK(FTP_CWDOK);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000256}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000257
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000258static void
259handle_cdup(void)
260{
261 G.ftp_arg = (char*)"..";
262 handle_cwd();
263}
264
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000265static void
Denis Vlasenko9e959202009-03-09 03:15:05 +0000266handle_stat(void)
267{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000268 cmdio_write_raw(STR(FTP_STATOK)"-FTP server status:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000269 " TYPE: BINARY\r\n"
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000270 STR(FTP_STATOK)" Ok\r\n");
Denis Vlasenko9e959202009-03-09 03:15:05 +0000271}
272
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000273/* TODO: implement FEAT. Example:
274# nc -vvv ftp.kernel.org 21
275ftp.kernel.org (130.239.17.4:21) open
276220 Welcome to ftp.kernel.org.
277FEAT
278211-Features:
279 EPRT
280 EPSV
281 MDTM
282 PASV
283 REST STREAM
284 SIZE
285 TVFS
286 UTF8
287211 End
288HELP
289214-The following commands are recognized.
290 ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
291 MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
292 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
293 XPWD XRMD
294214 Help OK.
295*/
Denis Vlasenko9e959202009-03-09 03:15:05 +0000296static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000297handle_help(void)
298{
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000299 cmdio_write_raw(STR(FTP_HELP)"-Commands:\r\n"
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000300 " ALLO CDUP CWD EPSV HELP LIST\r\n"
301 " MODE NLST NOOP PASS PASV PORT PWD QUIT\r\n"
302 " REST RETR SIZE STAT STRU SYST TYPE USER\r\n"
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000303#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000304 " APPE DELE MKD RMD RNFR RNTO STOR STOU\r\n"
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000305#endif
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000306 STR(FTP_HELP)" Ok\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000307}
308
Denis Vlasenko9e959202009-03-09 03:15:05 +0000309/* Download commands */
310
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000311static inline int
312port_active(void)
313{
314 return (G.port_addr != NULL);
315}
316
317static inline int
318pasv_active(void)
319{
320 return (G.pasv_listen_fd > STDOUT_FILENO);
321}
322
323static void
324port_pasv_cleanup(void)
325{
326 free(G.port_addr);
327 G.port_addr = NULL;
328 if (G.pasv_listen_fd > STDOUT_FILENO)
329 close(G.pasv_listen_fd);
330 G.pasv_listen_fd = -1;
331}
332
333/* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000334static int
335ftpdataio_get_pasv_fd(void)
336{
337 int remote_fd;
338
339 remote_fd = accept(G.pasv_listen_fd, NULL, 0);
340
341 if (remote_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000342 WRITE_ERR(FTP_BADSENDCONN);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000343 return remote_fd;
344 }
345
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000346 setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000347 return remote_fd;
348}
349
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000350/* Clears port/pasv data.
351 * This means we dont waste resources, for example, keeping
352 * PASV listening socket open when it is no longer needed.
353 * On error, emits error code to the peer (or exits).
354 * On success, emits p_status_msg to the peer.
355 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000356static int
357get_remote_transfer_fd(const char *p_status_msg)
358{
359 int remote_fd;
360
361 if (pasv_active())
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000362 /* On error, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000363 remote_fd = ftpdataio_get_pasv_fd();
364 else
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000365 /* Exits on error */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000366 remote_fd = xconnect_stream(G.port_addr);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000367
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000368 port_pasv_cleanup();
369
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000370 if (remote_fd < 0)
371 return remote_fd;
372
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000373 cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000374 return remote_fd;
375}
376
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000377/* If there were neither PASV nor PORT, emits error code to the peer */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000378static int
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000379port_or_pasv_was_seen(void)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000380{
381 if (!pasv_active() && !port_active()) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000382 cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT or PASV first\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000383 return 0;
384 }
385
386 return 1;
387}
388
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000389/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000390static unsigned
391bind_for_passive_mode(void)
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000392{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000393 int fd;
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000394 unsigned port;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000395
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000396 port_pasv_cleanup();
397
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000398 G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
399 setsockopt_reuseaddr(fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000400
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000401 set_nport(G.local_addr, 0);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000402 xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
403 xlisten(fd, 1);
404 getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000405
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000406 port = get_nport(&G.local_addr->u.sa);
407 port = ntohs(port);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000408 return port;
409}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000410
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000411/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000412static void
413handle_pasv(void)
414{
415 unsigned port;
416 char *addr, *response;
417
418 port = bind_for_passive_mode();
419
420 if (G.local_addr->u.sa.sa_family == AF_INET)
421 addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
422 else /* seen this in the wild done by other ftp servers: */
423 addr = xstrdup("0.0.0.0");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000424 replace_char(addr, '.', ',');
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000425
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000426 response = xasprintf(STR(FTP_PASVOK)" Entering Passive Mode (%s,%u,%u)\r\n",
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000427 addr, (int)(port >> 8), (int)(port & 255));
428 free(addr);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000429 cmdio_write_raw(response);
430 free(response);
431}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000432
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000433/* Exits on error */
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000434static void
435handle_epsv(void)
436{
437 unsigned port;
438 char *response;
439
440 port = bind_for_passive_mode();
441 response = xasprintf(STR(FTP_EPSVOK)" EPSV Ok (|||%u|)\r\n", port);
442 cmdio_write_raw(response);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000443 free(response);
444}
445
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000446/* libbb candidate */
447static
448len_and_sockaddr* get_peer_lsa(int fd)
449{
450 len_and_sockaddr *lsa;
451 socklen_t len = 0;
452
453 if (getpeername(fd, NULL, &len) != 0)
454 return NULL;
455 lsa = xzalloc(LSA_LEN_SIZE + len);
456 lsa->len = len;
457 getpeername(fd, &lsa->u.sa, &lsa->len);
458 return lsa;
459}
460
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000461static void
462handle_port(void)
463{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000464 unsigned port, port_hi;
465 char *raw, *comma;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000466#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000467 socklen_t peer_ipv4_len;
468 struct sockaddr_in peer_ipv4;
469 struct in_addr port_ipv4_sin_addr;
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000470#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000471
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000472 port_pasv_cleanup();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000473
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000474 raw = G.ftp_arg;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000475
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000476 /* PORT command format makes sense only over IPv4 */
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000477 if (!raw
478#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
479 || G.local_addr->u.sa.sa_family != AF_INET
480#endif
481 ) {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000482 bail:
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000483 WRITE_ERR(FTP_BADCMD);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000484 return;
485 }
486
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000487 comma = strrchr(raw, ',');
488 if (comma == NULL)
489 goto bail;
490 *comma = '\0';
491 port = bb_strtou(&comma[1], NULL, 10);
492 if (errno || port > 0xff)
493 goto bail;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000494
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000495 comma = strrchr(raw, ',');
496 if (comma == NULL)
497 goto bail;
498 *comma = '\0';
499 port_hi = bb_strtou(&comma[1], NULL, 10);
500 if (errno || port_hi > 0xff)
501 goto bail;
502 port |= port_hi << 8;
503
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000504#ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000505 replace_char(raw, ',', '.');
506
507 /* We are verifying that PORT's IP matches getpeername().
508 * Otherwise peer can make us open data connections
509 * to other hosts (security problem!)
510 * This code would be too simplistic:
511 * lsa = xdotted2sockaddr(raw, port);
512 * if (lsa == NULL) goto bail;
513 */
514 if (!inet_aton(raw, &port_ipv4_sin_addr))
515 goto bail;
516 peer_ipv4_len = sizeof(peer_ipv4);
517 if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
518 goto bail;
519 if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
520 goto bail;
521
522 G.port_addr = xdotted2sockaddr(raw, port);
Denis Vlasenko43bb7bb2009-03-16 19:54:06 +0000523#else
524 G.port_addr = get_peer_lsa(STDIN_FILENO);
525 set_nport(G.port_addr, port);
526#endif
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000527 WRITE_OK(FTP_PORTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000528}
529
530static void
531handle_rest(void)
532{
533 /* When ftp_arg == NULL simply restart from beginning */
534 G.restart_pos = G.ftp_arg ? xatoi_u(G.ftp_arg) : 0;
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000535 WRITE_OK(FTP_RESTOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000536}
537
538static void
539handle_retr(void)
540{
541 struct stat statbuf;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000542 off_t bytes_transferred;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000543 int remote_fd;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000544 int local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000545 off_t offset = G.restart_pos;
546 char *response;
547
548 G.restart_pos = 0;
549
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000550 if (!port_or_pasv_was_seen())
551 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000552
553 /* O_NONBLOCK is useful if file happens to be a device node */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000554 local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
555 if (local_file_fd < 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000556 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000557 return;
558 }
559
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000560 if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000561 /* Note - pretend open failed */
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000562 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000563 goto file_close_out;
564 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000565 G.local_file_fd = local_file_fd;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000566
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000567 /* Now deactive O_NONBLOCK, otherwise we have a problem
568 * on DMAPI filesystems such as XFS DMAPI.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000569 */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000570 ndelay_off(local_file_fd);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000571
572 /* Set the download offset (from REST) if any */
573 if (offset != 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000574 xlseek(local_file_fd, offset, SEEK_SET);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000575
576 response = xasprintf(
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000577 " Opening BINARY mode data connection for %s (%"OFF_FMT"u bytes)",
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000578 G.ftp_arg, statbuf.st_size);
579 remote_fd = get_remote_transfer_fd(response);
580 free(response);
581 if (remote_fd < 0)
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000582 goto file_close_out;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000583
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000584 bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000585 close(remote_fd);
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000586 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000587 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000588 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000589 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000590
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000591 file_close_out:
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000592 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000593 G.local_file_fd = 0;
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000594}
595
Denis Vlasenko9e959202009-03-09 03:15:05 +0000596/* List commands */
597
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000598static int
599popen_ls(const char *opt)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000600{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000601 char *cwd;
602 const char *argv[5] = { "ftpd", opt, NULL, G.ftp_arg, NULL };
603 struct fd_pair outfd;
604 pid_t pid;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000605
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000606 cwd = xrealloc_getcwd_or_warn(NULL);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000607 xpiped_pair(outfd);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000608
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000609 /*fflush(NULL); - so far we dont use stdio on output */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000610 pid = vfork();
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000611 switch (pid) {
612 case -1: /* failure */
613 bb_perror_msg_and_die("vfork");
614 case 0: /* child */
615 /* NB: close _first_, then move fds! */
616 close(outfd.rd);
617 xmove_fd(outfd.wr, STDOUT_FILENO);
618 close(STDIN_FILENO);
619 /* xopen("/dev/null", O_RDONLY); - chroot may lack it! */
620 if (fchdir(G.proc_self_fd) == 0) {
621 close(G.proc_self_fd);
622 argv[2] = cwd;
623 /* ftpd ls helper chdirs to argv[2],
624 * preventing peer from seeing /proc/self
625 */
626 execv("exe", (char**) argv);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000627 }
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000628 _exit(127);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000629 }
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000630
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000631 /* parent */
632 close(outfd.wr);
633 free(cwd);
634 return outfd.rd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000635}
636
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000637enum {
638 USE_CTRL_CONN = 1,
639 LONG_LISTING = 2,
640};
641
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000642static void
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000643handle_dir_common(int opts)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000644{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000645 FILE *ls_fp;
646 char *line;
647 int ls_fd;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000648
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000649 if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
650 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000651
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000652 /* -n prevents user/groupname display,
653 * which can be problematic in chroot */
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000654 ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000655 ls_fp = fdopen(ls_fd, "r");
656 if (!ls_fp) /* never happens. paranoia */
657 bb_perror_msg_and_die("fdopen");
658
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000659 if (opts & USE_CTRL_CONN) {
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000660 /* STAT <filename> */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000661 cmdio_write_raw(STR(FTP_STATFILE_OK)"-Status follows:\r\n");
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000662 while (1) {
663 line = xmalloc_fgetline(ls_fp);
664 if (!line)
665 break;
666 cmdio_write(0, line); /* hack: 0 results in no status at all */
667 free(line);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000668 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000669 WRITE_OK(FTP_STATFILE_OK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000670 } else {
671 /* LIST/NLST [<filename>] */
672 int remote_fd = get_remote_transfer_fd(" Here comes the directory listing");
673 if (remote_fd >= 0) {
674 while (1) {
675 line = xmalloc_fgetline(ls_fp);
676 if (!line)
677 break;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000678 /* I've seen clients complaining when they
679 * are fed with ls output with bare '\n'.
680 * Pity... that would be much simpler.
681 */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000682 xwrite_str(remote_fd, line);
683 xwrite(remote_fd, "\r\n", 2);
684 free(line);
685 }
686 }
687 close(remote_fd);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000688 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000689 }
690 fclose(ls_fp); /* closes ls_fd too */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000691}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000692static void
693handle_list(void)
694{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000695 handle_dir_common(LONG_LISTING);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000696}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000697static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000698handle_nlst(void)
699{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000700 handle_dir_common(0);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000701}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000702static void
703handle_stat_file(void)
704{
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000705 handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000706}
707
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000708static void
709handle_size(void)
710{
711 struct stat statbuf;
712 char buf[sizeof(STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n") + sizeof(off_t)*3];
713
714 if (!G.ftp_arg
715 || stat(G.ftp_arg, &statbuf) != 0
716 || !S_ISREG(statbuf.st_mode)
717 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000718 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +0000719 return;
720 }
721 sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
722 cmdio_write_raw(buf);
723}
724
Denis Vlasenko9e959202009-03-09 03:15:05 +0000725/* Upload commands */
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000726
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000727#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000728static void
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000729handle_mkd(void)
730{
731 if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000732 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000733 return;
734 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000735 WRITE_OK(FTP_MKDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000736}
737
738static void
739handle_rmd(void)
740{
741 if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000742 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000743 return;
744 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000745 WRITE_OK(FTP_RMDIROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000746}
747
748static void
749handle_dele(void)
750{
751 if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000752 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000753 return;
754 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000755 WRITE_OK(FTP_DELEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000756}
757
758static void
759handle_rnfr(void)
760{
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000761 free(G.rnfr_filename);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000762 G.rnfr_filename = xstrdup(G.ftp_arg);
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000763 WRITE_OK(FTP_RNFROK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000764}
765
766static void
767handle_rnto(void)
768{
769 int retval;
770
771 /* If we didn't get a RNFR, throw a wobbly */
772 if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000773 cmdio_write_raw(STR(FTP_NEEDRNFR)" RNFR required first\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000774 return;
775 }
776
777 retval = rename(G.rnfr_filename, G.ftp_arg);
778 free(G.rnfr_filename);
779 G.rnfr_filename = NULL;
780
781 if (retval) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000782 WRITE_ERR(FTP_FILEFAIL);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000783 return;
784 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000785 WRITE_OK(FTP_RENAMEOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000786}
787
788static void
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000789handle_upload_common(int is_append, int is_unique)
790{
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000791 struct stat statbuf;
792 char *tempname;
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000793 off_t bytes_transferred;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000794 off_t offset;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000795 int local_file_fd;
796 int remote_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000797
798 offset = G.restart_pos;
799 G.restart_pos = 0;
800
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000801 if (!port_or_pasv_was_seen())
802 return; /* port_or_pasv_was_seen emitted error response */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000803
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000804 tempname = NULL;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000805 local_file_fd = -1;
806 if (is_unique) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000807 tempname = xstrdup(" FILE: uniq.XXXXXX");
808 local_file_fd = mkstemp(tempname + 7);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000809 } else if (G.ftp_arg) {
810 int flags = O_WRONLY | O_CREAT | O_TRUNC;
811 if (is_append)
812 flags = O_WRONLY | O_CREAT | O_APPEND;
813 if (offset)
814 flags = O_WRONLY | O_CREAT;
815 local_file_fd = open(G.ftp_arg, flags, 0666);
816 }
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000817
818 if (local_file_fd < 0
819 || fstat(local_file_fd, &statbuf) != 0
820 || !S_ISREG(statbuf.st_mode)
821 ) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000822 WRITE_ERR(FTP_UPLOADFAIL);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000823 if (local_file_fd >= 0)
824 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000825 return;
826 }
Denis Vlasenko20c82162009-03-16 16:19:53 +0000827 G.local_file_fd = local_file_fd;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000828
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000829 if (offset)
830 xlseek(local_file_fd, offset, SEEK_SET);
831
Denis Vlasenkof1a11b52009-03-09 04:13:59 +0000832 remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000833 free(tempname);
834
835 if (remote_fd < 0)
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000836 goto close_local_and_bail;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000837
Denis Vlasenkofc58ba12009-03-15 15:54:58 +0000838 bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
839 close(remote_fd);
840 if (bytes_transferred < 0)
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000841 WRITE_ERR(FTP_BADSENDFILE);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000842 else
Denis Vlasenkofbf58462009-03-16 20:54:45 +0000843 WRITE_OK(FTP_TRANSFEROK);
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000844
845 close_local_and_bail:
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000846 close(local_file_fd);
Denis Vlasenko20c82162009-03-16 16:19:53 +0000847 G.local_file_fd = 0;
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000848}
849
850static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000851handle_stor(void)
852{
853 handle_upload_common(0, 0);
854}
855
856static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000857handle_appe(void)
858{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000859 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000860 handle_upload_common(1, 0);
861}
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000862
863static void
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000864handle_stou(void)
865{
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000866 G.restart_pos = 0;
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000867 handle_upload_common(0, 1);
868}
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000869#endif /* ENABLE_FEATURE_FTP_WRITE */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000870
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000871static uint32_t
872cmdio_get_cmd_and_arg(void)
873{
874 size_t len;
875 uint32_t cmdval;
876 char *cmd;
877
Denis Vlasenko20c82162009-03-16 16:19:53 +0000878 alarm(G.timeout);
879
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000880 free(G.ftp_cmd);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000881 len = 8 * 1024; /* Paranoia. Peer may send 1 gigabyte long cmd... */
Denis Vlasenko57a3b172009-03-09 04:38:37 +0000882 G.ftp_cmd = cmd = xmalloc_reads(STDIN_FILENO, NULL, &len);
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000883 if (!cmd)
884 exit(0);
885
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000886 /* Trailing '\n' is already stripped, strip '\r' */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000887 len = strlen(cmd) - 1;
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000888 while ((ssize_t)len >= 0 && cmd[len] == '\r') {
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000889 cmd[len] = '\0';
890 len--;
891 }
892
893 G.ftp_arg = strchr(cmd, ' ');
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000894 if (G.ftp_arg != NULL)
895 *G.ftp_arg++ = '\0';
896
897 /* Uppercase and pack into uint32_t first word of the command */
Denis Vlasenkoffb4bb32009-03-09 02:23:45 +0000898 cmdval = 0;
899 while (*cmd)
900 cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
901
902 return cmdval;
903}
904
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000905#define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
906#define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
907enum {
908 const_ALLO = mk_const4('A', 'L', 'L', 'O'),
909 const_APPE = mk_const4('A', 'P', 'P', 'E'),
910 const_CDUP = mk_const4('C', 'D', 'U', 'P'),
911 const_CWD = mk_const3('C', 'W', 'D'),
912 const_DELE = mk_const4('D', 'E', 'L', 'E'),
913 const_EPSV = mk_const4('E', 'P', 'S', 'V'),
914 const_HELP = mk_const4('H', 'E', 'L', 'P'),
915 const_LIST = mk_const4('L', 'I', 'S', 'T'),
916 const_MKD = mk_const3('M', 'K', 'D'),
917 const_MODE = mk_const4('M', 'O', 'D', 'E'),
918 const_NLST = mk_const4('N', 'L', 'S', 'T'),
919 const_NOOP = mk_const4('N', 'O', 'O', 'P'),
920 const_PASS = mk_const4('P', 'A', 'S', 'S'),
921 const_PASV = mk_const4('P', 'A', 'S', 'V'),
922 const_PORT = mk_const4('P', 'O', 'R', 'T'),
923 const_PWD = mk_const3('P', 'W', 'D'),
924 const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
925 const_REST = mk_const4('R', 'E', 'S', 'T'),
926 const_RETR = mk_const4('R', 'E', 'T', 'R'),
927 const_RMD = mk_const3('R', 'M', 'D'),
928 const_RNFR = mk_const4('R', 'N', 'F', 'R'),
929 const_RNTO = mk_const4('R', 'N', 'T', 'O'),
930 const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
931 const_STAT = mk_const4('S', 'T', 'A', 'T'),
932 const_STOR = mk_const4('S', 'T', 'O', 'R'),
933 const_STOU = mk_const4('S', 'T', 'O', 'U'),
934 const_STRU = mk_const4('S', 'T', 'R', 'U'),
935 const_SYST = mk_const4('S', 'Y', 'S', 'T'),
936 const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
937 const_USER = mk_const4('U', 'S', 'E', 'R'),
938
939 OPT_l = (1 << 0),
940 OPT_1 = (1 << 1),
941 OPT_v = (1 << 2),
942 OPT_S = (1 << 3),
943 OPT_w = (1 << 4),
944};
945
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000946int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000947int ftpd_main(int argc, char **argv)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000948{
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000949 smallint opts;
950
Denis Vlasenko20c82162009-03-16 16:19:53 +0000951 INIT_G();
952
953 G.start_time = monotonic_sec();
954 G.abs_timeout = 1 * 60 * 60;
955 G.timeout = 2 * 60;
956 opt_complementary = "t+:T+";
957 opts = getopt32(argv, "l1vS" USE_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &G.abs_timeout);
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000958
959 if (opts & (OPT_l|OPT_1)) {
960 /* Our secret backdoor to ls */
Denis Vlasenko20c82162009-03-16 16:19:53 +0000961 memset(&G, 0, sizeof(G));
Denis Vlasenkof2160b62009-03-16 14:53:54 +0000962/* TODO: pass -n too? */
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000963 xchdir(argv[2]);
964 argv[2] = (char*)"--";
965 return ls_main(argc, argv);
966 }
967
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000968
969 G.local_addr = get_sock_lsa(STDIN_FILENO);
970 if (!G.local_addr) {
971 /* This is confusing:
972 * bb_error_msg_and_die("stdin is not a socket");
973 * Better: */
974 bb_show_usage();
975 /* Help text says that ftpd must be used as inetd service,
976 * which is by far the most usual cause of get_sock_lsa
977 * failure */
978 }
979
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000980 if (!(opts & OPT_v))
Denis Vlasenko4221e902009-03-11 15:07:44 +0000981 logmode = LOGMODE_NONE;
982 if (opts & OPT_S) {
983 /* LOG_NDELAY is needed since we may chroot later */
984 openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
985 logmode |= LOGMODE_SYSLOG;
986 }
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000987
Denis Vlasenkoc41cba52009-03-09 15:46:07 +0000988 G.proc_self_fd = xopen("/proc/self", O_RDONLY | O_DIRECTORY);
989
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000990 if (argv[optind]) {
991 xchdir(argv[optind]);
992 chroot(".");
993 }
994
Denis Vlasenko5e4fda02009-03-08 23:46:48 +0000995 //umask(077); - admin can set umask before starting us
Denis Vlasenkobf9d1792009-03-08 09:31:28 +0000996
997 /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
998 signal(SIGPIPE, SIG_IGN);
999
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001000 /* Set up options on the command socket (do we need these all? why?) */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001001 setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
1002 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
1003 setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
1004
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001005 WRITE_OK(FTP_GREET);
Denis Vlasenko20c82162009-03-16 16:19:53 +00001006 signal(SIGALRM, timeout_handler);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001007
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001008#ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
1009 {
1010 smallint user_was_specified = 0;
1011 while (1) {
1012 uint32_t cmdval = cmdio_get_cmd_and_arg();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001013
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001014 if (cmdval == const_USER) {
1015 if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001016 cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001017 else {
1018 user_was_specified = 1;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001019 cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001020 }
1021 } else if (cmdval == const_PASS) {
1022 if (user_was_specified)
1023 break;
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001024 cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001025 } else if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001026 WRITE_OK(FTP_GOODBYE);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001027 return 0;
1028 } else {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001029 cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001030 }
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001031 }
1032 }
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001033 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001034#endif
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001035
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001036 /* RFC-959 Section 5.1
1037 * The following commands and options MUST be supported by every
1038 * server-FTP and user-FTP, except in cases where the underlying
1039 * file system or operating system does not allow or support
1040 * a particular command.
1041 * Type: ASCII Non-print, IMAGE, LOCAL 8
1042 * Mode: Stream
1043 * Structure: File, Record*
1044 * (Record structure is REQUIRED only for hosts whose file
1045 * systems support record structure).
1046 * Commands:
1047 * USER, PASS, ACCT, [bbox: ACCT not supported]
1048 * PORT, PASV,
1049 * TYPE, MODE, STRU,
1050 * RETR, STOR, APPE,
1051 * RNFR, RNTO, DELE,
1052 * CWD, CDUP, RMD, MKD, PWD,
1053 * LIST, NLST,
1054 * SYST, STAT,
1055 * HELP, NOOP, QUIT.
1056 */
1057 /* ACCOUNT (ACCT)
Denis Vlasenko34552852009-03-09 04:18:00 +00001058 * "The argument field is a Telnet string identifying the user's account.
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001059 * The command is not necessarily related to the USER command, as some
1060 * sites may require an account for login and others only for specific
1061 * access, such as storing files. In the latter case the command may
1062 * arrive at any time.
1063 * There are reply codes to differentiate these cases for the automation:
1064 * when account information is required for login, the response to
1065 * a successful PASSword command is reply code 332. On the other hand,
1066 * if account information is NOT required for login, the reply to
1067 * a successful PASSword command is 230; and if the account information
1068 * is needed for a command issued later in the dialogue, the server
1069 * should return a 332 or 532 reply depending on whether it stores
1070 * (pending receipt of the ACCounT command) or discards the command,
Denis Vlasenko34552852009-03-09 04:18:00 +00001071 * respectively."
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001072 */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001073
1074 while (1) {
1075 uint32_t cmdval = cmdio_get_cmd_and_arg();
1076
1077 if (cmdval == const_QUIT) {
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001078 WRITE_OK(FTP_GOODBYE);
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001079 return 0;
1080 }
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001081 else if (cmdval == const_USER)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001082 WRITE_OK(FTP_GIVEPWORD);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001083 else if (cmdval == const_PASS)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001084 WRITE_OK(FTP_LOGINOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001085 else if (cmdval == const_NOOP)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001086 WRITE_OK(FTP_NOOPOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001087 else if (cmdval == const_TYPE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001088 WRITE_OK(FTP_TYPEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001089 else if (cmdval == const_STRU)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001090 WRITE_OK(FTP_STRUOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001091 else if (cmdval == const_MODE)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001092 WRITE_OK(FTP_MODEOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001093 else if (cmdval == const_ALLO)
Denis Vlasenkofbf58462009-03-16 20:54:45 +00001094 WRITE_OK(FTP_ALLOOK);
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001095 else if (cmdval == const_SYST)
1096 cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
1097 else if (cmdval == const_PWD)
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001098 handle_pwd();
1099 else if (cmdval == const_CWD)
1100 handle_cwd();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001101 else if (cmdval == const_CDUP) /* cd .. */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001102 handle_cdup();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001103 else if (cmdval == const_HELP)
1104 handle_help();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001105 else if (cmdval == const_LIST) /* ls -l */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001106 handle_list();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001107 else if (cmdval == const_NLST) /* "name list", bare ls */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001108 handle_nlst();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001109 else if (cmdval == const_SIZE)
1110 handle_size();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001111 else if (cmdval == const_STAT) {
1112 if (G.ftp_arg == NULL)
1113 handle_stat();
1114 else
1115 handle_stat_file();
Denis Vlasenko9b2fbda2009-03-09 13:01:08 +00001116 }
1117 else if (cmdval == const_PASV)
1118 handle_pasv();
1119 else if (cmdval == const_EPSV)
1120 handle_epsv();
1121 else if (cmdval == const_RETR)
1122 handle_retr();
1123 else if (cmdval == const_PORT)
1124 handle_port();
1125 else if (cmdval == const_REST)
1126 handle_rest();
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001127#if ENABLE_FEATURE_FTP_WRITE
Denis Vlasenkoc41cba52009-03-09 15:46:07 +00001128 else if (opts & OPT_w) {
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001129 if (cmdval == const_STOR)
1130 handle_stor();
1131 else if (cmdval == const_MKD)
1132 handle_mkd();
1133 else if (cmdval == const_RMD)
1134 handle_rmd();
1135 else if (cmdval == const_DELE)
1136 handle_dele();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001137 else if (cmdval == const_RNFR) /* "rename from" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001138 handle_rnfr();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001139 else if (cmdval == const_RNTO) /* "rename to" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001140 handle_rnto();
1141 else if (cmdval == const_APPE)
1142 handle_appe();
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001143 else if (cmdval == const_STOU) /* "store unique" */
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001144 handle_stou();
1145 }
1146#endif
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001147#if 0
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001148 else if (cmdval == const_STOR
1149 || cmdval == const_MKD
1150 || cmdval == const_RMD
1151 || cmdval == const_DELE
1152 || cmdval == const_RNFR
1153 || cmdval == const_RNTO
1154 || cmdval == const_APPE
1155 || cmdval == const_STOU
1156 ) {
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001157 cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001158 }
1159#endif
1160 else {
Denis Vlasenko51c9bb12009-03-09 02:51:46 +00001161 /* Which unsupported commands were seen in the wild?
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001162 * (doesn't necessarily mean "we must support them")
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001163 * lftp 3.6.3: FEAT - is it useful?
1164 * MDTM - works fine without it anyway
Denis Vlasenko5e4fda02009-03-08 23:46:48 +00001165 */
Denis Vlasenkof1a11b52009-03-09 04:13:59 +00001166 cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
Denis Vlasenkobf9d1792009-03-08 09:31:28 +00001167 }
1168 }
1169}