[2725] | 1 | /* 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 | *
|
---|
| 7 | * Licensed under GPLv2, see file LICENSE in this source tree.
|
---|
| 8 | *
|
---|
| 9 | * Only subset of FTP protocol is implemented but vast majority of clients
|
---|
| 10 | * should not have any problem.
|
---|
| 11 | *
|
---|
| 12 | * You have to run this daemon via inetd.
|
---|
| 13 | */
|
---|
| 14 |
|
---|
[3232] | 15 | //usage:#define ftpd_trivial_usage
|
---|
| 16 | //usage: "[-wvS] [-t N] [-T N] [DIR]"
|
---|
| 17 | //usage:#define ftpd_full_usage "\n\n"
|
---|
| 18 | //usage: "Anonymous FTP server\n"
|
---|
| 19 | //usage: "\n"
|
---|
| 20 | //usage: "ftpd should be used as an inetd service.\n"
|
---|
| 21 | //usage: "ftpd's line for inetd.conf:\n"
|
---|
| 22 | //usage: " 21 stream tcp nowait root ftpd ftpd /files/to/serve\n"
|
---|
| 23 | //usage: "It also can be ran from tcpsvd:\n"
|
---|
| 24 | //usage: " tcpsvd -vE 0.0.0.0 21 ftpd /files/to/serve\n"
|
---|
| 25 | //usage: "\n -w Allow upload"
|
---|
| 26 | //usage: "\n -v Log errors to stderr. -vv: verbose log"
|
---|
| 27 | //usage: "\n -S Log errors to syslog. -SS: verbose log"
|
---|
| 28 | //usage: "\n -t,-T Idle and absolute timeouts"
|
---|
| 29 | //usage: "\n DIR Change root to this directory"
|
---|
| 30 |
|
---|
[2725] | 31 | #include "libbb.h"
|
---|
| 32 | #include <syslog.h>
|
---|
| 33 | #include <netinet/tcp.h>
|
---|
| 34 |
|
---|
| 35 | #define FTP_DATACONN 150
|
---|
| 36 | #define FTP_NOOPOK 200
|
---|
| 37 | #define FTP_TYPEOK 200
|
---|
| 38 | #define FTP_PORTOK 200
|
---|
| 39 | #define FTP_STRUOK 200
|
---|
| 40 | #define FTP_MODEOK 200
|
---|
| 41 | #define FTP_ALLOOK 202
|
---|
| 42 | #define FTP_STATOK 211
|
---|
| 43 | #define FTP_STATFILE_OK 213
|
---|
| 44 | #define FTP_HELP 214
|
---|
| 45 | #define FTP_SYSTOK 215
|
---|
| 46 | #define FTP_GREET 220
|
---|
| 47 | #define FTP_GOODBYE 221
|
---|
| 48 | #define FTP_TRANSFEROK 226
|
---|
| 49 | #define FTP_PASVOK 227
|
---|
| 50 | /*#define FTP_EPRTOK 228*/
|
---|
| 51 | #define FTP_EPSVOK 229
|
---|
| 52 | #define FTP_LOGINOK 230
|
---|
| 53 | #define FTP_CWDOK 250
|
---|
| 54 | #define FTP_RMDIROK 250
|
---|
| 55 | #define FTP_DELEOK 250
|
---|
| 56 | #define FTP_RENAMEOK 250
|
---|
| 57 | #define FTP_PWDOK 257
|
---|
| 58 | #define FTP_MKDIROK 257
|
---|
| 59 | #define FTP_GIVEPWORD 331
|
---|
| 60 | #define FTP_RESTOK 350
|
---|
| 61 | #define FTP_RNFROK 350
|
---|
| 62 | #define FTP_TIMEOUT 421
|
---|
| 63 | #define FTP_BADSENDCONN 425
|
---|
| 64 | #define FTP_BADSENDNET 426
|
---|
| 65 | #define FTP_BADSENDFILE 451
|
---|
| 66 | #define FTP_BADCMD 500
|
---|
| 67 | #define FTP_COMMANDNOTIMPL 502
|
---|
| 68 | #define FTP_NEEDUSER 503
|
---|
| 69 | #define FTP_NEEDRNFR 503
|
---|
| 70 | #define FTP_BADSTRU 504
|
---|
| 71 | #define FTP_BADMODE 504
|
---|
| 72 | #define FTP_LOGINERR 530
|
---|
| 73 | #define FTP_FILEFAIL 550
|
---|
| 74 | #define FTP_NOPERM 550
|
---|
| 75 | #define FTP_UPLOADFAIL 553
|
---|
| 76 |
|
---|
| 77 | #define STR1(s) #s
|
---|
| 78 | #define STR(s) STR1(s)
|
---|
| 79 |
|
---|
| 80 | /* Convert a constant to 3-digit string, packed into uint32_t */
|
---|
| 81 | enum {
|
---|
| 82 | /* Shift for Nth decimal digit */
|
---|
| 83 | SHIFT2 = 0 * BB_LITTLE_ENDIAN + 24 * BB_BIG_ENDIAN,
|
---|
| 84 | SHIFT1 = 8 * BB_LITTLE_ENDIAN + 16 * BB_BIG_ENDIAN,
|
---|
| 85 | SHIFT0 = 16 * BB_LITTLE_ENDIAN + 8 * BB_BIG_ENDIAN,
|
---|
| 86 | /* And for 4th position (space) */
|
---|
| 87 | SHIFTsp = 24 * BB_LITTLE_ENDIAN + 0 * BB_BIG_ENDIAN,
|
---|
| 88 | };
|
---|
| 89 | #define STRNUM32(s) (uint32_t)(0 \
|
---|
| 90 | | (('0' + ((s) / 1 % 10)) << SHIFT0) \
|
---|
| 91 | | (('0' + ((s) / 10 % 10)) << SHIFT1) \
|
---|
| 92 | | (('0' + ((s) / 100 % 10)) << SHIFT2) \
|
---|
| 93 | )
|
---|
| 94 | #define STRNUM32sp(s) (uint32_t)(0 \
|
---|
| 95 | | (' ' << SHIFTsp) \
|
---|
| 96 | | (('0' + ((s) / 1 % 10)) << SHIFT0) \
|
---|
| 97 | | (('0' + ((s) / 10 % 10)) << SHIFT1) \
|
---|
| 98 | | (('0' + ((s) / 100 % 10)) << SHIFT2) \
|
---|
| 99 | )
|
---|
| 100 |
|
---|
| 101 | #define MSG_OK "Operation successful\r\n"
|
---|
| 102 | #define MSG_ERR "Error\r\n"
|
---|
| 103 |
|
---|
| 104 | struct globals {
|
---|
| 105 | int pasv_listen_fd;
|
---|
| 106 | #if !BB_MMU
|
---|
| 107 | int root_fd;
|
---|
| 108 | #endif
|
---|
| 109 | int local_file_fd;
|
---|
| 110 | unsigned end_time;
|
---|
| 111 | unsigned timeout;
|
---|
| 112 | unsigned verbose;
|
---|
| 113 | off_t local_file_pos;
|
---|
| 114 | off_t restart_pos;
|
---|
| 115 | len_and_sockaddr *local_addr;
|
---|
| 116 | len_and_sockaddr *port_addr;
|
---|
| 117 | char *ftp_cmd;
|
---|
| 118 | char *ftp_arg;
|
---|
| 119 | #if ENABLE_FEATURE_FTP_WRITE
|
---|
| 120 | char *rnfr_filename;
|
---|
| 121 | #endif
|
---|
| 122 | /* We need these aligned to uint32_t */
|
---|
| 123 | char msg_ok [(sizeof("NNN " MSG_OK ) + 3) & 0xfffc];
|
---|
| 124 | char msg_err[(sizeof("NNN " MSG_ERR) + 3) & 0xfffc];
|
---|
| 125 | } FIX_ALIASING;
|
---|
| 126 | #define G (*(struct globals*)&bb_common_bufsiz1)
|
---|
| 127 | #define INIT_G() do { \
|
---|
| 128 | /* Moved to main */ \
|
---|
| 129 | /*strcpy(G.msg_ok + 4, MSG_OK );*/ \
|
---|
| 130 | /*strcpy(G.msg_err + 4, MSG_ERR);*/ \
|
---|
| 131 | } while (0)
|
---|
| 132 |
|
---|
| 133 |
|
---|
| 134 | static char *
|
---|
| 135 | escape_text(const char *prepend, const char *str, unsigned escapee)
|
---|
| 136 | {
|
---|
| 137 | unsigned retlen, remainlen, chunklen;
|
---|
| 138 | char *ret, *found;
|
---|
| 139 | char append;
|
---|
| 140 |
|
---|
| 141 | append = (char)escapee;
|
---|
| 142 | escapee >>= 8;
|
---|
| 143 |
|
---|
| 144 | remainlen = strlen(str);
|
---|
| 145 | retlen = strlen(prepend);
|
---|
| 146 | ret = xmalloc(retlen + remainlen * 2 + 1 + 1);
|
---|
| 147 | strcpy(ret, prepend);
|
---|
| 148 |
|
---|
| 149 | for (;;) {
|
---|
| 150 | found = strchrnul(str, escapee);
|
---|
| 151 | chunklen = found - str + 1;
|
---|
| 152 |
|
---|
| 153 | /* Copy chunk up to and including escapee (or NUL) to ret */
|
---|
| 154 | memcpy(ret + retlen, str, chunklen);
|
---|
| 155 | retlen += chunklen;
|
---|
| 156 |
|
---|
| 157 | if (*found == '\0') {
|
---|
| 158 | /* It wasn't escapee, it was NUL! */
|
---|
| 159 | ret[retlen - 1] = append; /* replace NUL */
|
---|
| 160 | ret[retlen] = '\0'; /* add NUL */
|
---|
| 161 | break;
|
---|
| 162 | }
|
---|
| 163 | ret[retlen++] = escapee; /* duplicate escapee */
|
---|
| 164 | str = found + 1;
|
---|
| 165 | }
|
---|
| 166 | return ret;
|
---|
| 167 | }
|
---|
| 168 |
|
---|
| 169 | /* Returns strlen as a bonus */
|
---|
| 170 | static unsigned
|
---|
| 171 | replace_char(char *str, char from, char to)
|
---|
| 172 | {
|
---|
| 173 | char *p = str;
|
---|
| 174 | while (*p) {
|
---|
| 175 | if (*p == from)
|
---|
| 176 | *p = to;
|
---|
| 177 | p++;
|
---|
| 178 | }
|
---|
| 179 | return p - str;
|
---|
| 180 | }
|
---|
| 181 |
|
---|
| 182 | static void
|
---|
| 183 | verbose_log(const char *str)
|
---|
| 184 | {
|
---|
| 185 | bb_error_msg("%.*s", (int)strcspn(str, "\r\n"), str);
|
---|
| 186 | }
|
---|
| 187 |
|
---|
| 188 | /* NB: status_str is char[4] packed into uint32_t */
|
---|
| 189 | static void
|
---|
| 190 | cmdio_write(uint32_t status_str, const char *str)
|
---|
| 191 | {
|
---|
| 192 | char *response;
|
---|
| 193 | int len;
|
---|
| 194 |
|
---|
| 195 | /* FTP uses telnet protocol for command link.
|
---|
| 196 | * In telnet, 0xff is an escape char, and needs to be escaped: */
|
---|
| 197 | response = escape_text((char *) &status_str, str, (0xff << 8) + '\r');
|
---|
| 198 |
|
---|
| 199 | /* FTP sends embedded LFs as NULs */
|
---|
| 200 | len = replace_char(response, '\n', '\0');
|
---|
| 201 |
|
---|
| 202 | response[len++] = '\n'; /* tack on trailing '\n' */
|
---|
| 203 | xwrite(STDOUT_FILENO, response, len);
|
---|
| 204 | if (G.verbose > 1)
|
---|
| 205 | verbose_log(response);
|
---|
| 206 | free(response);
|
---|
| 207 | }
|
---|
| 208 |
|
---|
| 209 | static void
|
---|
| 210 | cmdio_write_ok(unsigned status)
|
---|
| 211 | {
|
---|
| 212 | *(uint32_t *) G.msg_ok = status;
|
---|
| 213 | xwrite(STDOUT_FILENO, G.msg_ok, sizeof("NNN " MSG_OK) - 1);
|
---|
| 214 | if (G.verbose > 1)
|
---|
| 215 | verbose_log(G.msg_ok);
|
---|
| 216 | }
|
---|
| 217 | #define WRITE_OK(a) cmdio_write_ok(STRNUM32sp(a))
|
---|
| 218 |
|
---|
| 219 | /* TODO: output strerr(errno) if errno != 0? */
|
---|
| 220 | static void
|
---|
| 221 | cmdio_write_error(unsigned status)
|
---|
| 222 | {
|
---|
| 223 | *(uint32_t *) G.msg_err = status;
|
---|
| 224 | xwrite(STDOUT_FILENO, G.msg_err, sizeof("NNN " MSG_ERR) - 1);
|
---|
[3232] | 225 | if (G.verbose > 0)
|
---|
[2725] | 226 | verbose_log(G.msg_err);
|
---|
| 227 | }
|
---|
| 228 | #define WRITE_ERR(a) cmdio_write_error(STRNUM32sp(a))
|
---|
| 229 |
|
---|
| 230 | static void
|
---|
| 231 | cmdio_write_raw(const char *p_text)
|
---|
| 232 | {
|
---|
| 233 | xwrite_str(STDOUT_FILENO, p_text);
|
---|
| 234 | if (G.verbose > 1)
|
---|
| 235 | verbose_log(p_text);
|
---|
| 236 | }
|
---|
| 237 |
|
---|
| 238 | static void
|
---|
| 239 | timeout_handler(int sig UNUSED_PARAM)
|
---|
| 240 | {
|
---|
| 241 | off_t pos;
|
---|
| 242 | int sv_errno = errno;
|
---|
| 243 |
|
---|
| 244 | if ((int)(monotonic_sec() - G.end_time) >= 0)
|
---|
| 245 | goto timed_out;
|
---|
| 246 |
|
---|
| 247 | if (!G.local_file_fd)
|
---|
| 248 | goto timed_out;
|
---|
| 249 |
|
---|
| 250 | pos = xlseek(G.local_file_fd, 0, SEEK_CUR);
|
---|
| 251 | if (pos == G.local_file_pos)
|
---|
| 252 | goto timed_out;
|
---|
| 253 | G.local_file_pos = pos;
|
---|
| 254 |
|
---|
| 255 | alarm(G.timeout);
|
---|
| 256 | errno = sv_errno;
|
---|
| 257 | return;
|
---|
| 258 |
|
---|
| 259 | timed_out:
|
---|
| 260 | cmdio_write_raw(STR(FTP_TIMEOUT)" Timeout\r\n");
|
---|
| 261 | /* TODO: do we need to abort (as opposed to usual shutdown) data transfer? */
|
---|
| 262 | exit(1);
|
---|
| 263 | }
|
---|
| 264 |
|
---|
| 265 | /* Simple commands */
|
---|
| 266 |
|
---|
| 267 | static void
|
---|
| 268 | handle_pwd(void)
|
---|
| 269 | {
|
---|
| 270 | char *cwd, *response;
|
---|
| 271 |
|
---|
| 272 | cwd = xrealloc_getcwd_or_warn(NULL);
|
---|
| 273 | if (cwd == NULL)
|
---|
| 274 | cwd = xstrdup("");
|
---|
| 275 |
|
---|
| 276 | /* We have to promote each " to "" */
|
---|
| 277 | response = escape_text(" \"", cwd, ('"' << 8) + '"');
|
---|
| 278 | free(cwd);
|
---|
| 279 | cmdio_write(STRNUM32(FTP_PWDOK), response);
|
---|
| 280 | free(response);
|
---|
| 281 | }
|
---|
| 282 |
|
---|
| 283 | static void
|
---|
| 284 | handle_cwd(void)
|
---|
| 285 | {
|
---|
| 286 | if (!G.ftp_arg || chdir(G.ftp_arg) != 0) {
|
---|
| 287 | WRITE_ERR(FTP_FILEFAIL);
|
---|
| 288 | return;
|
---|
| 289 | }
|
---|
| 290 | WRITE_OK(FTP_CWDOK);
|
---|
| 291 | }
|
---|
| 292 |
|
---|
| 293 | static void
|
---|
| 294 | handle_cdup(void)
|
---|
| 295 | {
|
---|
| 296 | G.ftp_arg = (char*)"..";
|
---|
| 297 | handle_cwd();
|
---|
| 298 | }
|
---|
| 299 |
|
---|
| 300 | static void
|
---|
| 301 | handle_stat(void)
|
---|
| 302 | {
|
---|
| 303 | cmdio_write_raw(STR(FTP_STATOK)"-Server status:\r\n"
|
---|
| 304 | " TYPE: BINARY\r\n"
|
---|
| 305 | STR(FTP_STATOK)" Ok\r\n");
|
---|
| 306 | }
|
---|
| 307 |
|
---|
| 308 | /* Examples of HELP and FEAT:
|
---|
| 309 | # nc -vvv ftp.kernel.org 21
|
---|
| 310 | ftp.kernel.org (130.239.17.4:21) open
|
---|
| 311 | 220 Welcome to ftp.kernel.org.
|
---|
| 312 | FEAT
|
---|
| 313 | 211-Features:
|
---|
| 314 | EPRT
|
---|
| 315 | EPSV
|
---|
| 316 | MDTM
|
---|
| 317 | PASV
|
---|
| 318 | REST STREAM
|
---|
| 319 | SIZE
|
---|
| 320 | TVFS
|
---|
| 321 | UTF8
|
---|
| 322 | 211 End
|
---|
| 323 | HELP
|
---|
| 324 | 214-The following commands are recognized.
|
---|
| 325 | ABOR ACCT ALLO APPE CDUP CWD DELE EPRT EPSV FEAT HELP LIST MDTM MKD
|
---|
| 326 | MODE NLST NOOP OPTS PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR
|
---|
| 327 | RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
|
---|
| 328 | XPWD XRMD
|
---|
| 329 | 214 Help OK.
|
---|
| 330 | */
|
---|
| 331 | static void
|
---|
| 332 | handle_feat(unsigned status)
|
---|
| 333 | {
|
---|
| 334 | cmdio_write(status, "-Features:");
|
---|
| 335 | cmdio_write_raw(" EPSV\r\n"
|
---|
| 336 | " PASV\r\n"
|
---|
| 337 | " REST STREAM\r\n"
|
---|
| 338 | " MDTM\r\n"
|
---|
| 339 | " SIZE\r\n");
|
---|
| 340 | cmdio_write(status, " Ok");
|
---|
| 341 | }
|
---|
| 342 |
|
---|
| 343 | /* Download commands */
|
---|
| 344 |
|
---|
| 345 | static inline int
|
---|
| 346 | port_active(void)
|
---|
| 347 | {
|
---|
| 348 | return (G.port_addr != NULL);
|
---|
| 349 | }
|
---|
| 350 |
|
---|
| 351 | static inline int
|
---|
| 352 | pasv_active(void)
|
---|
| 353 | {
|
---|
| 354 | return (G.pasv_listen_fd > STDOUT_FILENO);
|
---|
| 355 | }
|
---|
| 356 |
|
---|
| 357 | static void
|
---|
| 358 | port_pasv_cleanup(void)
|
---|
| 359 | {
|
---|
| 360 | free(G.port_addr);
|
---|
| 361 | G.port_addr = NULL;
|
---|
| 362 | if (G.pasv_listen_fd > STDOUT_FILENO)
|
---|
| 363 | close(G.pasv_listen_fd);
|
---|
| 364 | G.pasv_listen_fd = -1;
|
---|
| 365 | }
|
---|
| 366 |
|
---|
| 367 | /* On error, emits error code to the peer */
|
---|
| 368 | static int
|
---|
| 369 | ftpdataio_get_pasv_fd(void)
|
---|
| 370 | {
|
---|
| 371 | int remote_fd;
|
---|
| 372 |
|
---|
| 373 | remote_fd = accept(G.pasv_listen_fd, NULL, 0);
|
---|
| 374 |
|
---|
| 375 | if (remote_fd < 0) {
|
---|
| 376 | WRITE_ERR(FTP_BADSENDCONN);
|
---|
| 377 | return remote_fd;
|
---|
| 378 | }
|
---|
| 379 |
|
---|
| 380 | setsockopt(remote_fd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
|
---|
| 381 | return remote_fd;
|
---|
| 382 | }
|
---|
| 383 |
|
---|
| 384 | /* Clears port/pasv data.
|
---|
| 385 | * This means we dont waste resources, for example, keeping
|
---|
| 386 | * PASV listening socket open when it is no longer needed.
|
---|
| 387 | * On error, emits error code to the peer (or exits).
|
---|
| 388 | * On success, emits p_status_msg to the peer.
|
---|
| 389 | */
|
---|
| 390 | static int
|
---|
| 391 | get_remote_transfer_fd(const char *p_status_msg)
|
---|
| 392 | {
|
---|
| 393 | int remote_fd;
|
---|
| 394 |
|
---|
| 395 | if (pasv_active())
|
---|
| 396 | /* On error, emits error code to the peer */
|
---|
| 397 | remote_fd = ftpdataio_get_pasv_fd();
|
---|
| 398 | else
|
---|
| 399 | /* Exits on error */
|
---|
| 400 | remote_fd = xconnect_stream(G.port_addr);
|
---|
| 401 |
|
---|
| 402 | port_pasv_cleanup();
|
---|
| 403 |
|
---|
| 404 | if (remote_fd < 0)
|
---|
| 405 | return remote_fd;
|
---|
| 406 |
|
---|
| 407 | cmdio_write(STRNUM32(FTP_DATACONN), p_status_msg);
|
---|
| 408 | return remote_fd;
|
---|
| 409 | }
|
---|
| 410 |
|
---|
| 411 | /* If there were neither PASV nor PORT, emits error code to the peer */
|
---|
| 412 | static int
|
---|
| 413 | port_or_pasv_was_seen(void)
|
---|
| 414 | {
|
---|
| 415 | if (!pasv_active() && !port_active()) {
|
---|
| 416 | cmdio_write_raw(STR(FTP_BADSENDCONN)" Use PORT/PASV first\r\n");
|
---|
| 417 | return 0;
|
---|
| 418 | }
|
---|
| 419 |
|
---|
| 420 | return 1;
|
---|
| 421 | }
|
---|
| 422 |
|
---|
| 423 | /* Exits on error */
|
---|
| 424 | static unsigned
|
---|
| 425 | bind_for_passive_mode(void)
|
---|
| 426 | {
|
---|
| 427 | int fd;
|
---|
| 428 | unsigned port;
|
---|
| 429 |
|
---|
| 430 | port_pasv_cleanup();
|
---|
| 431 |
|
---|
| 432 | G.pasv_listen_fd = fd = xsocket(G.local_addr->u.sa.sa_family, SOCK_STREAM, 0);
|
---|
| 433 | setsockopt_reuseaddr(fd);
|
---|
| 434 |
|
---|
[3232] | 435 | set_nport(&G.local_addr->u.sa, 0);
|
---|
[2725] | 436 | xbind(fd, &G.local_addr->u.sa, G.local_addr->len);
|
---|
| 437 | xlisten(fd, 1);
|
---|
| 438 | getsockname(fd, &G.local_addr->u.sa, &G.local_addr->len);
|
---|
| 439 |
|
---|
| 440 | port = get_nport(&G.local_addr->u.sa);
|
---|
| 441 | port = ntohs(port);
|
---|
| 442 | return port;
|
---|
| 443 | }
|
---|
| 444 |
|
---|
| 445 | /* Exits on error */
|
---|
| 446 | static void
|
---|
| 447 | handle_pasv(void)
|
---|
| 448 | {
|
---|
| 449 | unsigned port;
|
---|
| 450 | char *addr, *response;
|
---|
| 451 |
|
---|
| 452 | port = bind_for_passive_mode();
|
---|
| 453 |
|
---|
| 454 | if (G.local_addr->u.sa.sa_family == AF_INET)
|
---|
| 455 | addr = xmalloc_sockaddr2dotted_noport(&G.local_addr->u.sa);
|
---|
| 456 | else /* seen this in the wild done by other ftp servers: */
|
---|
| 457 | addr = xstrdup("0.0.0.0");
|
---|
| 458 | replace_char(addr, '.', ',');
|
---|
| 459 |
|
---|
| 460 | response = xasprintf(STR(FTP_PASVOK)" PASV ok (%s,%u,%u)\r\n",
|
---|
| 461 | addr, (int)(port >> 8), (int)(port & 255));
|
---|
| 462 | free(addr);
|
---|
| 463 | cmdio_write_raw(response);
|
---|
| 464 | free(response);
|
---|
| 465 | }
|
---|
| 466 |
|
---|
| 467 | /* Exits on error */
|
---|
| 468 | static void
|
---|
| 469 | handle_epsv(void)
|
---|
| 470 | {
|
---|
| 471 | unsigned port;
|
---|
| 472 | char *response;
|
---|
| 473 |
|
---|
| 474 | port = bind_for_passive_mode();
|
---|
| 475 | response = xasprintf(STR(FTP_EPSVOK)" EPSV ok (|||%u|)\r\n", port);
|
---|
| 476 | cmdio_write_raw(response);
|
---|
| 477 | free(response);
|
---|
| 478 | }
|
---|
| 479 |
|
---|
| 480 | static void
|
---|
| 481 | handle_port(void)
|
---|
| 482 | {
|
---|
| 483 | unsigned port, port_hi;
|
---|
| 484 | char *raw, *comma;
|
---|
| 485 | #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
|
---|
| 486 | socklen_t peer_ipv4_len;
|
---|
| 487 | struct sockaddr_in peer_ipv4;
|
---|
| 488 | struct in_addr port_ipv4_sin_addr;
|
---|
| 489 | #endif
|
---|
| 490 |
|
---|
| 491 | port_pasv_cleanup();
|
---|
| 492 |
|
---|
| 493 | raw = G.ftp_arg;
|
---|
| 494 |
|
---|
| 495 | /* PORT command format makes sense only over IPv4 */
|
---|
| 496 | if (!raw
|
---|
| 497 | #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
|
---|
| 498 | || G.local_addr->u.sa.sa_family != AF_INET
|
---|
| 499 | #endif
|
---|
| 500 | ) {
|
---|
| 501 | bail:
|
---|
| 502 | WRITE_ERR(FTP_BADCMD);
|
---|
| 503 | return;
|
---|
| 504 | }
|
---|
| 505 |
|
---|
| 506 | comma = strrchr(raw, ',');
|
---|
| 507 | if (comma == NULL)
|
---|
| 508 | goto bail;
|
---|
| 509 | *comma = '\0';
|
---|
| 510 | port = bb_strtou(&comma[1], NULL, 10);
|
---|
| 511 | if (errno || port > 0xff)
|
---|
| 512 | goto bail;
|
---|
| 513 |
|
---|
| 514 | comma = strrchr(raw, ',');
|
---|
| 515 | if (comma == NULL)
|
---|
| 516 | goto bail;
|
---|
| 517 | *comma = '\0';
|
---|
| 518 | port_hi = bb_strtou(&comma[1], NULL, 10);
|
---|
| 519 | if (errno || port_hi > 0xff)
|
---|
| 520 | goto bail;
|
---|
| 521 | port |= port_hi << 8;
|
---|
| 522 |
|
---|
| 523 | #ifdef WHY_BOTHER_WE_CAN_ASSUME_IP_MATCHES
|
---|
| 524 | replace_char(raw, ',', '.');
|
---|
| 525 |
|
---|
| 526 | /* We are verifying that PORT's IP matches getpeername().
|
---|
| 527 | * Otherwise peer can make us open data connections
|
---|
| 528 | * to other hosts (security problem!)
|
---|
| 529 | * This code would be too simplistic:
|
---|
| 530 | * lsa = xdotted2sockaddr(raw, port);
|
---|
| 531 | * if (lsa == NULL) goto bail;
|
---|
| 532 | */
|
---|
| 533 | if (!inet_aton(raw, &port_ipv4_sin_addr))
|
---|
| 534 | goto bail;
|
---|
| 535 | peer_ipv4_len = sizeof(peer_ipv4);
|
---|
| 536 | if (getpeername(STDIN_FILENO, &peer_ipv4, &peer_ipv4_len) != 0)
|
---|
| 537 | goto bail;
|
---|
| 538 | if (memcmp(&port_ipv4_sin_addr, &peer_ipv4.sin_addr, sizeof(struct in_addr)) != 0)
|
---|
| 539 | goto bail;
|
---|
| 540 |
|
---|
| 541 | G.port_addr = xdotted2sockaddr(raw, port);
|
---|
| 542 | #else
|
---|
| 543 | G.port_addr = get_peer_lsa(STDIN_FILENO);
|
---|
[3232] | 544 | set_nport(&G.port_addr->u.sa, htons(port));
|
---|
[2725] | 545 | #endif
|
---|
| 546 | WRITE_OK(FTP_PORTOK);
|
---|
| 547 | }
|
---|
| 548 |
|
---|
| 549 | static void
|
---|
| 550 | handle_rest(void)
|
---|
| 551 | {
|
---|
| 552 | /* When ftp_arg == NULL simply restart from beginning */
|
---|
| 553 | G.restart_pos = G.ftp_arg ? xatoi_positive(G.ftp_arg) : 0;
|
---|
| 554 | WRITE_OK(FTP_RESTOK);
|
---|
| 555 | }
|
---|
| 556 |
|
---|
| 557 | static void
|
---|
| 558 | handle_retr(void)
|
---|
| 559 | {
|
---|
| 560 | struct stat statbuf;
|
---|
| 561 | off_t bytes_transferred;
|
---|
| 562 | int remote_fd;
|
---|
| 563 | int local_file_fd;
|
---|
| 564 | off_t offset = G.restart_pos;
|
---|
| 565 | char *response;
|
---|
| 566 |
|
---|
| 567 | G.restart_pos = 0;
|
---|
| 568 |
|
---|
| 569 | if (!port_or_pasv_was_seen())
|
---|
| 570 | return; /* port_or_pasv_was_seen emitted error response */
|
---|
| 571 |
|
---|
| 572 | /* O_NONBLOCK is useful if file happens to be a device node */
|
---|
| 573 | local_file_fd = G.ftp_arg ? open(G.ftp_arg, O_RDONLY | O_NONBLOCK) : -1;
|
---|
| 574 | if (local_file_fd < 0) {
|
---|
| 575 | WRITE_ERR(FTP_FILEFAIL);
|
---|
| 576 | return;
|
---|
| 577 | }
|
---|
| 578 |
|
---|
| 579 | if (fstat(local_file_fd, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) {
|
---|
| 580 | /* Note - pretend open failed */
|
---|
| 581 | WRITE_ERR(FTP_FILEFAIL);
|
---|
| 582 | goto file_close_out;
|
---|
| 583 | }
|
---|
| 584 | G.local_file_fd = local_file_fd;
|
---|
| 585 |
|
---|
| 586 | /* Now deactive O_NONBLOCK, otherwise we have a problem
|
---|
| 587 | * on DMAPI filesystems such as XFS DMAPI.
|
---|
| 588 | */
|
---|
| 589 | ndelay_off(local_file_fd);
|
---|
| 590 |
|
---|
| 591 | /* Set the download offset (from REST) if any */
|
---|
| 592 | if (offset != 0)
|
---|
| 593 | xlseek(local_file_fd, offset, SEEK_SET);
|
---|
| 594 |
|
---|
| 595 | response = xasprintf(
|
---|
| 596 | " Opening BINARY connection for %s (%"OFF_FMT"u bytes)",
|
---|
| 597 | G.ftp_arg, statbuf.st_size);
|
---|
| 598 | remote_fd = get_remote_transfer_fd(response);
|
---|
| 599 | free(response);
|
---|
| 600 | if (remote_fd < 0)
|
---|
| 601 | goto file_close_out;
|
---|
| 602 |
|
---|
| 603 | bytes_transferred = bb_copyfd_eof(local_file_fd, remote_fd);
|
---|
| 604 | close(remote_fd);
|
---|
| 605 | if (bytes_transferred < 0)
|
---|
| 606 | WRITE_ERR(FTP_BADSENDFILE);
|
---|
| 607 | else
|
---|
| 608 | WRITE_OK(FTP_TRANSFEROK);
|
---|
| 609 |
|
---|
| 610 | file_close_out:
|
---|
| 611 | close(local_file_fd);
|
---|
| 612 | G.local_file_fd = 0;
|
---|
| 613 | }
|
---|
| 614 |
|
---|
| 615 | /* List commands */
|
---|
| 616 |
|
---|
| 617 | static int
|
---|
| 618 | popen_ls(const char *opt)
|
---|
| 619 | {
|
---|
| 620 | const char *argv[5];
|
---|
| 621 | struct fd_pair outfd;
|
---|
| 622 | pid_t pid;
|
---|
| 623 |
|
---|
| 624 | argv[0] = "ftpd";
|
---|
| 625 | argv[1] = opt; /* "-l" or "-1" */
|
---|
| 626 | #if BB_MMU
|
---|
| 627 | argv[2] = "--";
|
---|
| 628 | #else
|
---|
| 629 | /* NOMMU ftpd ls helper chdirs to argv[2],
|
---|
| 630 | * preventing peer from seeing real root. */
|
---|
| 631 | argv[2] = xrealloc_getcwd_or_warn(NULL);
|
---|
| 632 | #endif
|
---|
| 633 | argv[3] = G.ftp_arg;
|
---|
| 634 | argv[4] = NULL;
|
---|
| 635 |
|
---|
| 636 | /* Improve compatibility with non-RFC conforming FTP clients
|
---|
| 637 | * which send e.g. "LIST -l", "LIST -la", "LIST -aL".
|
---|
| 638 | * See https://bugs.kde.org/show_bug.cgi?id=195578 */
|
---|
| 639 | if (ENABLE_FEATURE_FTPD_ACCEPT_BROKEN_LIST
|
---|
| 640 | && G.ftp_arg && G.ftp_arg[0] == '-'
|
---|
| 641 | ) {
|
---|
| 642 | const char *tmp = strchr(G.ftp_arg, ' ');
|
---|
| 643 | if (tmp) /* skip the space */
|
---|
| 644 | tmp++;
|
---|
| 645 | argv[3] = tmp;
|
---|
| 646 | }
|
---|
| 647 |
|
---|
| 648 | xpiped_pair(outfd);
|
---|
| 649 |
|
---|
| 650 | /*fflush_all(); - so far we dont use stdio on output */
|
---|
| 651 | pid = BB_MMU ? xfork() : xvfork();
|
---|
| 652 | if (pid == 0) {
|
---|
| 653 | /* child */
|
---|
| 654 | #if !BB_MMU
|
---|
| 655 | /* On NOMMU, we want to execute a child - copy of ourself.
|
---|
| 656 | * In chroot we usually can't do it. Thus we chdir
|
---|
| 657 | * out of the chroot back to original root,
|
---|
| 658 | * and (see later below) execute bb_busybox_exec_path
|
---|
| 659 | * relative to current directory */
|
---|
| 660 | if (fchdir(G.root_fd) != 0)
|
---|
| 661 | _exit(127);
|
---|
| 662 | /*close(G.root_fd); - close_on_exec_on() took care of this */
|
---|
| 663 | #endif
|
---|
| 664 | /* NB: close _first_, then move fd! */
|
---|
| 665 | close(outfd.rd);
|
---|
| 666 | xmove_fd(outfd.wr, STDOUT_FILENO);
|
---|
| 667 | /* Opening /dev/null in chroot is hard.
|
---|
| 668 | * Just making sure STDIN_FILENO is opened
|
---|
| 669 | * to something harmless. Paranoia,
|
---|
| 670 | * ls won't read it anyway */
|
---|
| 671 | close(STDIN_FILENO);
|
---|
| 672 | dup(STDOUT_FILENO); /* copy will become STDIN_FILENO */
|
---|
| 673 | #if BB_MMU
|
---|
| 674 | /* memset(&G, 0, sizeof(G)); - ls_main does it */
|
---|
| 675 | exit(ls_main(ARRAY_SIZE(argv) - 1, (char**) argv));
|
---|
| 676 | #else
|
---|
| 677 | /* + 1: we must use relative path here if in chroot.
|
---|
| 678 | * For example, execv("/proc/self/exe") will fail, since
|
---|
| 679 | * it looks for "/proc/self/exe" _relative to chroot!_ */
|
---|
| 680 | execv(bb_busybox_exec_path + 1, (char**) argv);
|
---|
| 681 | _exit(127);
|
---|
| 682 | #endif
|
---|
| 683 | }
|
---|
| 684 |
|
---|
| 685 | /* parent */
|
---|
| 686 | close(outfd.wr);
|
---|
| 687 | #if !BB_MMU
|
---|
| 688 | free((char*)argv[2]);
|
---|
| 689 | #endif
|
---|
| 690 | return outfd.rd;
|
---|
| 691 | }
|
---|
| 692 |
|
---|
| 693 | enum {
|
---|
| 694 | USE_CTRL_CONN = 1,
|
---|
| 695 | LONG_LISTING = 2,
|
---|
| 696 | };
|
---|
| 697 |
|
---|
| 698 | static void
|
---|
| 699 | handle_dir_common(int opts)
|
---|
| 700 | {
|
---|
| 701 | FILE *ls_fp;
|
---|
| 702 | char *line;
|
---|
| 703 | int ls_fd;
|
---|
| 704 |
|
---|
| 705 | if (!(opts & USE_CTRL_CONN) && !port_or_pasv_was_seen())
|
---|
| 706 | return; /* port_or_pasv_was_seen emitted error response */
|
---|
| 707 |
|
---|
| 708 | /* -n prevents user/groupname display,
|
---|
| 709 | * which can be problematic in chroot */
|
---|
| 710 | ls_fd = popen_ls((opts & LONG_LISTING) ? "-l" : "-1");
|
---|
| 711 | ls_fp = xfdopen_for_read(ls_fd);
|
---|
| 712 |
|
---|
| 713 | if (opts & USE_CTRL_CONN) {
|
---|
| 714 | /* STAT <filename> */
|
---|
| 715 | cmdio_write_raw(STR(FTP_STATFILE_OK)"-File status:\r\n");
|
---|
| 716 | while (1) {
|
---|
| 717 | line = xmalloc_fgetline(ls_fp);
|
---|
| 718 | if (!line)
|
---|
| 719 | break;
|
---|
| 720 | /* Hack: 0 results in no status at all */
|
---|
| 721 | /* Note: it's ok that we don't prepend space,
|
---|
| 722 | * ftp.kernel.org doesn't do that too */
|
---|
| 723 | cmdio_write(0, line);
|
---|
| 724 | free(line);
|
---|
| 725 | }
|
---|
| 726 | WRITE_OK(FTP_STATFILE_OK);
|
---|
| 727 | } else {
|
---|
| 728 | /* LIST/NLST [<filename>] */
|
---|
| 729 | int remote_fd = get_remote_transfer_fd(" Directory listing");
|
---|
| 730 | if (remote_fd >= 0) {
|
---|
| 731 | while (1) {
|
---|
| 732 | line = xmalloc_fgetline(ls_fp);
|
---|
| 733 | if (!line)
|
---|
| 734 | break;
|
---|
| 735 | /* I've seen clients complaining when they
|
---|
| 736 | * are fed with ls output with bare '\n'.
|
---|
| 737 | * Pity... that would be much simpler.
|
---|
| 738 | */
|
---|
| 739 | /* TODO: need to s/LF/NUL/g here */
|
---|
| 740 | xwrite_str(remote_fd, line);
|
---|
| 741 | xwrite(remote_fd, "\r\n", 2);
|
---|
| 742 | free(line);
|
---|
| 743 | }
|
---|
| 744 | }
|
---|
| 745 | close(remote_fd);
|
---|
| 746 | WRITE_OK(FTP_TRANSFEROK);
|
---|
| 747 | }
|
---|
| 748 | fclose(ls_fp); /* closes ls_fd too */
|
---|
| 749 | }
|
---|
| 750 | static void
|
---|
| 751 | handle_list(void)
|
---|
| 752 | {
|
---|
| 753 | handle_dir_common(LONG_LISTING);
|
---|
| 754 | }
|
---|
| 755 | static void
|
---|
| 756 | handle_nlst(void)
|
---|
| 757 | {
|
---|
| 758 | /* NLST returns list of names, "\r\n" terminated without regard
|
---|
| 759 | * to the current binary flag. Names may start with "/",
|
---|
| 760 | * then they represent full names (we don't produce such names),
|
---|
| 761 | * otherwise names are relative to current directory.
|
---|
| 762 | * Embedded "\n" are replaced by NULs. This is safe since names
|
---|
| 763 | * can never contain NUL.
|
---|
| 764 | */
|
---|
| 765 | handle_dir_common(0);
|
---|
| 766 | }
|
---|
| 767 | static void
|
---|
| 768 | handle_stat_file(void)
|
---|
| 769 | {
|
---|
| 770 | handle_dir_common(LONG_LISTING + USE_CTRL_CONN);
|
---|
| 771 | }
|
---|
| 772 |
|
---|
| 773 | /* This can be extended to handle MLST, as all info is available
|
---|
| 774 | * in struct stat for that:
|
---|
| 775 | * MLST file_name
|
---|
| 776 | * 250-Listing file_name
|
---|
| 777 | * type=file;size=4161;modify=19970214165800; /dir/dir/file_name
|
---|
| 778 | * 250 End
|
---|
| 779 | * Nano-doc:
|
---|
| 780 | * MLST [<file or dir name, "." assumed if not given>]
|
---|
| 781 | * Returned name should be either the same as requested, or fully qualified.
|
---|
| 782 | * If there was no parameter, return "" or (preferred) fully-qualified name.
|
---|
| 783 | * Returned "facts" (case is not important):
|
---|
| 784 | * size - size in octets
|
---|
| 785 | * modify - last modification time
|
---|
| 786 | * type - entry type (file,dir,OS.unix=block)
|
---|
| 787 | * (+ cdir and pdir types for MLSD)
|
---|
| 788 | * unique - unique id of file/directory (inode#)
|
---|
| 789 | * perm -
|
---|
| 790 | * a: can be appended to (APPE)
|
---|
| 791 | * d: can be deleted (RMD/DELE)
|
---|
| 792 | * f: can be renamed (RNFR)
|
---|
| 793 | * r: can be read (RETR)
|
---|
| 794 | * w: can be written (STOR)
|
---|
| 795 | * e: can CWD into this dir
|
---|
| 796 | * l: this dir can be listed (dir only!)
|
---|
| 797 | * c: can create files in this dir
|
---|
| 798 | * m: can create dirs in this dir (MKD)
|
---|
| 799 | * p: can delete files in this dir
|
---|
| 800 | * UNIX.mode - unix file mode
|
---|
| 801 | */
|
---|
| 802 | static void
|
---|
| 803 | handle_size_or_mdtm(int need_size)
|
---|
| 804 | {
|
---|
| 805 | struct stat statbuf;
|
---|
| 806 | struct tm broken_out;
|
---|
| 807 | char buf[(sizeof("NNN %"OFF_FMT"u\r\n") + sizeof(off_t) * 3)
|
---|
| 808 | | sizeof("NNN YYYYMMDDhhmmss\r\n")
|
---|
| 809 | ];
|
---|
| 810 |
|
---|
| 811 | if (!G.ftp_arg
|
---|
| 812 | || stat(G.ftp_arg, &statbuf) != 0
|
---|
| 813 | || !S_ISREG(statbuf.st_mode)
|
---|
| 814 | ) {
|
---|
| 815 | WRITE_ERR(FTP_FILEFAIL);
|
---|
| 816 | return;
|
---|
| 817 | }
|
---|
| 818 | if (need_size) {
|
---|
| 819 | sprintf(buf, STR(FTP_STATFILE_OK)" %"OFF_FMT"u\r\n", statbuf.st_size);
|
---|
| 820 | } else {
|
---|
| 821 | gmtime_r(&statbuf.st_mtime, &broken_out);
|
---|
| 822 | sprintf(buf, STR(FTP_STATFILE_OK)" %04u%02u%02u%02u%02u%02u\r\n",
|
---|
| 823 | broken_out.tm_year + 1900,
|
---|
[3232] | 824 | broken_out.tm_mon + 1,
|
---|
[2725] | 825 | broken_out.tm_mday,
|
---|
| 826 | broken_out.tm_hour,
|
---|
| 827 | broken_out.tm_min,
|
---|
| 828 | broken_out.tm_sec);
|
---|
| 829 | }
|
---|
| 830 | cmdio_write_raw(buf);
|
---|
| 831 | }
|
---|
| 832 |
|
---|
| 833 | /* Upload commands */
|
---|
| 834 |
|
---|
| 835 | #if ENABLE_FEATURE_FTP_WRITE
|
---|
| 836 | static void
|
---|
| 837 | handle_mkd(void)
|
---|
| 838 | {
|
---|
| 839 | if (!G.ftp_arg || mkdir(G.ftp_arg, 0777) != 0) {
|
---|
| 840 | WRITE_ERR(FTP_FILEFAIL);
|
---|
| 841 | return;
|
---|
| 842 | }
|
---|
| 843 | WRITE_OK(FTP_MKDIROK);
|
---|
| 844 | }
|
---|
| 845 |
|
---|
| 846 | static void
|
---|
| 847 | handle_rmd(void)
|
---|
| 848 | {
|
---|
| 849 | if (!G.ftp_arg || rmdir(G.ftp_arg) != 0) {
|
---|
| 850 | WRITE_ERR(FTP_FILEFAIL);
|
---|
| 851 | return;
|
---|
| 852 | }
|
---|
| 853 | WRITE_OK(FTP_RMDIROK);
|
---|
| 854 | }
|
---|
| 855 |
|
---|
| 856 | static void
|
---|
| 857 | handle_dele(void)
|
---|
| 858 | {
|
---|
| 859 | if (!G.ftp_arg || unlink(G.ftp_arg) != 0) {
|
---|
| 860 | WRITE_ERR(FTP_FILEFAIL);
|
---|
| 861 | return;
|
---|
| 862 | }
|
---|
| 863 | WRITE_OK(FTP_DELEOK);
|
---|
| 864 | }
|
---|
| 865 |
|
---|
| 866 | static void
|
---|
| 867 | handle_rnfr(void)
|
---|
| 868 | {
|
---|
| 869 | free(G.rnfr_filename);
|
---|
| 870 | G.rnfr_filename = xstrdup(G.ftp_arg);
|
---|
| 871 | WRITE_OK(FTP_RNFROK);
|
---|
| 872 | }
|
---|
| 873 |
|
---|
| 874 | static void
|
---|
| 875 | handle_rnto(void)
|
---|
| 876 | {
|
---|
| 877 | int retval;
|
---|
| 878 |
|
---|
| 879 | /* If we didn't get a RNFR, throw a wobbly */
|
---|
| 880 | if (G.rnfr_filename == NULL || G.ftp_arg == NULL) {
|
---|
| 881 | cmdio_write_raw(STR(FTP_NEEDRNFR)" Use RNFR first\r\n");
|
---|
| 882 | return;
|
---|
| 883 | }
|
---|
| 884 |
|
---|
| 885 | retval = rename(G.rnfr_filename, G.ftp_arg);
|
---|
| 886 | free(G.rnfr_filename);
|
---|
| 887 | G.rnfr_filename = NULL;
|
---|
| 888 |
|
---|
| 889 | if (retval) {
|
---|
| 890 | WRITE_ERR(FTP_FILEFAIL);
|
---|
| 891 | return;
|
---|
| 892 | }
|
---|
| 893 | WRITE_OK(FTP_RENAMEOK);
|
---|
| 894 | }
|
---|
| 895 |
|
---|
| 896 | static void
|
---|
| 897 | handle_upload_common(int is_append, int is_unique)
|
---|
| 898 | {
|
---|
| 899 | struct stat statbuf;
|
---|
| 900 | char *tempname;
|
---|
| 901 | off_t bytes_transferred;
|
---|
| 902 | off_t offset;
|
---|
| 903 | int local_file_fd;
|
---|
| 904 | int remote_fd;
|
---|
| 905 |
|
---|
| 906 | offset = G.restart_pos;
|
---|
| 907 | G.restart_pos = 0;
|
---|
| 908 |
|
---|
| 909 | if (!port_or_pasv_was_seen())
|
---|
| 910 | return; /* port_or_pasv_was_seen emitted error response */
|
---|
| 911 |
|
---|
| 912 | tempname = NULL;
|
---|
| 913 | local_file_fd = -1;
|
---|
| 914 | if (is_unique) {
|
---|
| 915 | tempname = xstrdup(" FILE: uniq.XXXXXX");
|
---|
| 916 | local_file_fd = mkstemp(tempname + 7);
|
---|
| 917 | } else if (G.ftp_arg) {
|
---|
| 918 | int flags = O_WRONLY | O_CREAT | O_TRUNC;
|
---|
| 919 | if (is_append)
|
---|
| 920 | flags = O_WRONLY | O_CREAT | O_APPEND;
|
---|
| 921 | if (offset)
|
---|
| 922 | flags = O_WRONLY | O_CREAT;
|
---|
| 923 | local_file_fd = open(G.ftp_arg, flags, 0666);
|
---|
| 924 | }
|
---|
| 925 |
|
---|
| 926 | if (local_file_fd < 0
|
---|
| 927 | || fstat(local_file_fd, &statbuf) != 0
|
---|
| 928 | || !S_ISREG(statbuf.st_mode)
|
---|
| 929 | ) {
|
---|
[3232] | 930 | free(tempname);
|
---|
[2725] | 931 | WRITE_ERR(FTP_UPLOADFAIL);
|
---|
| 932 | if (local_file_fd >= 0)
|
---|
| 933 | goto close_local_and_bail;
|
---|
| 934 | return;
|
---|
| 935 | }
|
---|
| 936 | G.local_file_fd = local_file_fd;
|
---|
| 937 |
|
---|
| 938 | if (offset)
|
---|
| 939 | xlseek(local_file_fd, offset, SEEK_SET);
|
---|
| 940 |
|
---|
| 941 | remote_fd = get_remote_transfer_fd(tempname ? tempname : " Ok to send data");
|
---|
| 942 | free(tempname);
|
---|
| 943 |
|
---|
| 944 | if (remote_fd < 0)
|
---|
| 945 | goto close_local_and_bail;
|
---|
| 946 |
|
---|
| 947 | bytes_transferred = bb_copyfd_eof(remote_fd, local_file_fd);
|
---|
| 948 | close(remote_fd);
|
---|
| 949 | if (bytes_transferred < 0)
|
---|
| 950 | WRITE_ERR(FTP_BADSENDFILE);
|
---|
| 951 | else
|
---|
| 952 | WRITE_OK(FTP_TRANSFEROK);
|
---|
| 953 |
|
---|
| 954 | close_local_and_bail:
|
---|
| 955 | close(local_file_fd);
|
---|
| 956 | G.local_file_fd = 0;
|
---|
| 957 | }
|
---|
| 958 |
|
---|
| 959 | static void
|
---|
| 960 | handle_stor(void)
|
---|
| 961 | {
|
---|
| 962 | handle_upload_common(0, 0);
|
---|
| 963 | }
|
---|
| 964 |
|
---|
| 965 | static void
|
---|
| 966 | handle_appe(void)
|
---|
| 967 | {
|
---|
| 968 | G.restart_pos = 0;
|
---|
| 969 | handle_upload_common(1, 0);
|
---|
| 970 | }
|
---|
| 971 |
|
---|
| 972 | static void
|
---|
| 973 | handle_stou(void)
|
---|
| 974 | {
|
---|
| 975 | G.restart_pos = 0;
|
---|
| 976 | handle_upload_common(0, 1);
|
---|
| 977 | }
|
---|
| 978 | #endif /* ENABLE_FEATURE_FTP_WRITE */
|
---|
| 979 |
|
---|
| 980 | static uint32_t
|
---|
| 981 | cmdio_get_cmd_and_arg(void)
|
---|
| 982 | {
|
---|
| 983 | int len;
|
---|
| 984 | uint32_t cmdval;
|
---|
| 985 | char *cmd;
|
---|
| 986 |
|
---|
| 987 | alarm(G.timeout);
|
---|
| 988 |
|
---|
| 989 | free(G.ftp_cmd);
|
---|
| 990 | {
|
---|
| 991 | /* Paranoia. Peer may send 1 gigabyte long cmd... */
|
---|
| 992 | /* Using separate len_on_stk instead of len optimizes
|
---|
| 993 | * code size (allows len to be in CPU register) */
|
---|
| 994 | size_t len_on_stk = 8 * 1024;
|
---|
| 995 | G.ftp_cmd = cmd = xmalloc_fgets_str_len(stdin, "\r\n", &len_on_stk);
|
---|
| 996 | if (!cmd)
|
---|
| 997 | exit(0);
|
---|
| 998 | len = len_on_stk;
|
---|
| 999 | }
|
---|
| 1000 |
|
---|
| 1001 | /* De-escape telnet: 0xff,0xff => 0xff */
|
---|
| 1002 | /* RFC959 says that ABOR, STAT, QUIT may be sent even during
|
---|
| 1003 | * data transfer, and may be preceded by telnet's "Interrupt Process"
|
---|
| 1004 | * code (two-byte sequence 255,244) and then by telnet "Synch" code
|
---|
| 1005 | * 255,242 (byte 242 is sent with TCP URG bit using send(MSG_OOB)
|
---|
| 1006 | * and may generate SIGURG on our side. See RFC854).
|
---|
| 1007 | * So far we don't support that (may install SIGURG handler if we'd want to),
|
---|
| 1008 | * but we need to at least remove 255,xxx pairs. lftp sends those. */
|
---|
| 1009 | /* Then de-escape FTP: NUL => '\n' */
|
---|
| 1010 | /* Testing for \xff:
|
---|
| 1011 | * Create file named '\xff': echo Hello >`echo -ne "\xff"`
|
---|
| 1012 | * Try to get it: ftpget -v 127.0.0.1 Eff `echo -ne "\xff\xff"`
|
---|
| 1013 | * (need "\xff\xff" until ftpget applet is fixed to do escaping :)
|
---|
| 1014 | * Testing for embedded LF:
|
---|
| 1015 | * LF_HERE=`echo -ne "LF\nHERE"`
|
---|
| 1016 | * echo Hello >"$LF_HERE"
|
---|
| 1017 | * ftpget -v 127.0.0.1 LF_HERE "$LF_HERE"
|
---|
| 1018 | */
|
---|
| 1019 | {
|
---|
| 1020 | int dst, src;
|
---|
| 1021 |
|
---|
| 1022 | /* Strip "\r\n" if it is there */
|
---|
| 1023 | if (len != 0 && cmd[len - 1] == '\n') {
|
---|
| 1024 | len--;
|
---|
| 1025 | if (len != 0 && cmd[len - 1] == '\r')
|
---|
| 1026 | len--;
|
---|
| 1027 | cmd[len] = '\0';
|
---|
| 1028 | }
|
---|
| 1029 | src = strchrnul(cmd, 0xff) - cmd;
|
---|
| 1030 | /* 99,99% there are neither NULs nor 255s and src == len */
|
---|
| 1031 | if (src < len) {
|
---|
| 1032 | dst = src;
|
---|
| 1033 | do {
|
---|
| 1034 | if ((unsigned char)(cmd[src]) == 255) {
|
---|
| 1035 | src++;
|
---|
| 1036 | /* 255,xxx - skip 255 */
|
---|
| 1037 | if ((unsigned char)(cmd[src]) != 255) {
|
---|
| 1038 | /* 255,!255 - skip both */
|
---|
| 1039 | src++;
|
---|
| 1040 | continue;
|
---|
| 1041 | }
|
---|
| 1042 | /* 255,255 - retain one 255 */
|
---|
| 1043 | }
|
---|
| 1044 | /* NUL => '\n' */
|
---|
| 1045 | cmd[dst++] = cmd[src] ? cmd[src] : '\n';
|
---|
| 1046 | src++;
|
---|
| 1047 | } while (src < len);
|
---|
| 1048 | cmd[dst] = '\0';
|
---|
| 1049 | }
|
---|
| 1050 | }
|
---|
| 1051 |
|
---|
| 1052 | if (G.verbose > 1)
|
---|
| 1053 | verbose_log(cmd);
|
---|
| 1054 |
|
---|
| 1055 | G.ftp_arg = strchr(cmd, ' ');
|
---|
| 1056 | if (G.ftp_arg != NULL)
|
---|
| 1057 | *G.ftp_arg++ = '\0';
|
---|
| 1058 |
|
---|
| 1059 | /* Uppercase and pack into uint32_t first word of the command */
|
---|
| 1060 | cmdval = 0;
|
---|
| 1061 | while (*cmd)
|
---|
| 1062 | cmdval = (cmdval << 8) + ((unsigned char)*cmd++ & (unsigned char)~0x20);
|
---|
| 1063 |
|
---|
| 1064 | return cmdval;
|
---|
| 1065 | }
|
---|
| 1066 |
|
---|
| 1067 | #define mk_const4(a,b,c,d) (((a * 0x100 + b) * 0x100 + c) * 0x100 + d)
|
---|
| 1068 | #define mk_const3(a,b,c) ((a * 0x100 + b) * 0x100 + c)
|
---|
| 1069 | enum {
|
---|
| 1070 | const_ALLO = mk_const4('A', 'L', 'L', 'O'),
|
---|
| 1071 | const_APPE = mk_const4('A', 'P', 'P', 'E'),
|
---|
| 1072 | const_CDUP = mk_const4('C', 'D', 'U', 'P'),
|
---|
| 1073 | const_CWD = mk_const3('C', 'W', 'D'),
|
---|
| 1074 | const_DELE = mk_const4('D', 'E', 'L', 'E'),
|
---|
| 1075 | const_EPSV = mk_const4('E', 'P', 'S', 'V'),
|
---|
| 1076 | const_FEAT = mk_const4('F', 'E', 'A', 'T'),
|
---|
| 1077 | const_HELP = mk_const4('H', 'E', 'L', 'P'),
|
---|
| 1078 | const_LIST = mk_const4('L', 'I', 'S', 'T'),
|
---|
| 1079 | const_MDTM = mk_const4('M', 'D', 'T', 'M'),
|
---|
| 1080 | const_MKD = mk_const3('M', 'K', 'D'),
|
---|
| 1081 | const_MODE = mk_const4('M', 'O', 'D', 'E'),
|
---|
| 1082 | const_NLST = mk_const4('N', 'L', 'S', 'T'),
|
---|
| 1083 | const_NOOP = mk_const4('N', 'O', 'O', 'P'),
|
---|
| 1084 | const_PASS = mk_const4('P', 'A', 'S', 'S'),
|
---|
| 1085 | const_PASV = mk_const4('P', 'A', 'S', 'V'),
|
---|
| 1086 | const_PORT = mk_const4('P', 'O', 'R', 'T'),
|
---|
| 1087 | const_PWD = mk_const3('P', 'W', 'D'),
|
---|
| 1088 | const_QUIT = mk_const4('Q', 'U', 'I', 'T'),
|
---|
| 1089 | const_REST = mk_const4('R', 'E', 'S', 'T'),
|
---|
| 1090 | const_RETR = mk_const4('R', 'E', 'T', 'R'),
|
---|
| 1091 | const_RMD = mk_const3('R', 'M', 'D'),
|
---|
| 1092 | const_RNFR = mk_const4('R', 'N', 'F', 'R'),
|
---|
| 1093 | const_RNTO = mk_const4('R', 'N', 'T', 'O'),
|
---|
| 1094 | const_SIZE = mk_const4('S', 'I', 'Z', 'E'),
|
---|
| 1095 | const_STAT = mk_const4('S', 'T', 'A', 'T'),
|
---|
| 1096 | const_STOR = mk_const4('S', 'T', 'O', 'R'),
|
---|
| 1097 | const_STOU = mk_const4('S', 'T', 'O', 'U'),
|
---|
| 1098 | const_STRU = mk_const4('S', 'T', 'R', 'U'),
|
---|
| 1099 | const_SYST = mk_const4('S', 'Y', 'S', 'T'),
|
---|
| 1100 | const_TYPE = mk_const4('T', 'Y', 'P', 'E'),
|
---|
| 1101 | const_USER = mk_const4('U', 'S', 'E', 'R'),
|
---|
| 1102 |
|
---|
| 1103 | #if !BB_MMU
|
---|
| 1104 | OPT_l = (1 << 0),
|
---|
| 1105 | OPT_1 = (1 << 1),
|
---|
| 1106 | #endif
|
---|
| 1107 | OPT_v = (1 << ((!BB_MMU) * 2 + 0)),
|
---|
| 1108 | OPT_S = (1 << ((!BB_MMU) * 2 + 1)),
|
---|
| 1109 | OPT_w = (1 << ((!BB_MMU) * 2 + 2)) * ENABLE_FEATURE_FTP_WRITE,
|
---|
| 1110 | };
|
---|
| 1111 |
|
---|
| 1112 | int ftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
|
---|
| 1113 | #if !BB_MMU
|
---|
| 1114 | int ftpd_main(int argc, char **argv)
|
---|
| 1115 | #else
|
---|
| 1116 | int ftpd_main(int argc UNUSED_PARAM, char **argv)
|
---|
| 1117 | #endif
|
---|
| 1118 | {
|
---|
| 1119 | unsigned abs_timeout;
|
---|
| 1120 | unsigned verbose_S;
|
---|
| 1121 | smallint opts;
|
---|
| 1122 |
|
---|
| 1123 | INIT_G();
|
---|
| 1124 |
|
---|
| 1125 | abs_timeout = 1 * 60 * 60;
|
---|
| 1126 | verbose_S = 0;
|
---|
| 1127 | G.timeout = 2 * 60;
|
---|
| 1128 | opt_complementary = "t+:T+:vv:SS";
|
---|
| 1129 | #if BB_MMU
|
---|
| 1130 | opts = getopt32(argv, "vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
|
---|
| 1131 | #else
|
---|
| 1132 | opts = getopt32(argv, "l1vS" IF_FEATURE_FTP_WRITE("w") "t:T:", &G.timeout, &abs_timeout, &G.verbose, &verbose_S);
|
---|
| 1133 | if (opts & (OPT_l|OPT_1)) {
|
---|
| 1134 | /* Our secret backdoor to ls */
|
---|
| 1135 | /* TODO: pass -n? It prevents user/group resolution, which may not work in chroot anyway */
|
---|
| 1136 | /* TODO: pass -A? It shows dot files */
|
---|
| 1137 | /* TODO: pass --group-directories-first? would be nice, but ls doesn't do that yet */
|
---|
| 1138 | xchdir(argv[2]);
|
---|
| 1139 | argv[2] = (char*)"--";
|
---|
| 1140 | /* memset(&G, 0, sizeof(G)); - ls_main does it */
|
---|
| 1141 | return ls_main(argc, argv);
|
---|
| 1142 | }
|
---|
| 1143 | #endif
|
---|
| 1144 | if (G.verbose < verbose_S)
|
---|
| 1145 | G.verbose = verbose_S;
|
---|
| 1146 | if (abs_timeout | G.timeout) {
|
---|
| 1147 | if (abs_timeout == 0)
|
---|
| 1148 | abs_timeout = INT_MAX;
|
---|
| 1149 | G.end_time = monotonic_sec() + abs_timeout;
|
---|
| 1150 | if (G.timeout > abs_timeout)
|
---|
| 1151 | G.timeout = abs_timeout;
|
---|
| 1152 | }
|
---|
| 1153 | strcpy(G.msg_ok + 4, MSG_OK );
|
---|
| 1154 | strcpy(G.msg_err + 4, MSG_ERR);
|
---|
| 1155 |
|
---|
| 1156 | G.local_addr = get_sock_lsa(STDIN_FILENO);
|
---|
| 1157 | if (!G.local_addr) {
|
---|
| 1158 | /* This is confusing:
|
---|
| 1159 | * bb_error_msg_and_die("stdin is not a socket");
|
---|
| 1160 | * Better: */
|
---|
| 1161 | bb_show_usage();
|
---|
| 1162 | /* Help text says that ftpd must be used as inetd service,
|
---|
| 1163 | * which is by far the most usual cause of get_sock_lsa
|
---|
| 1164 | * failure */
|
---|
| 1165 | }
|
---|
| 1166 |
|
---|
| 1167 | if (!(opts & OPT_v))
|
---|
| 1168 | logmode = LOGMODE_NONE;
|
---|
| 1169 | if (opts & OPT_S) {
|
---|
| 1170 | /* LOG_NDELAY is needed since we may chroot later */
|
---|
| 1171 | openlog(applet_name, LOG_PID | LOG_NDELAY, LOG_DAEMON);
|
---|
| 1172 | logmode |= LOGMODE_SYSLOG;
|
---|
| 1173 | }
|
---|
| 1174 | if (logmode)
|
---|
| 1175 | applet_name = xasprintf("%s[%u]", applet_name, (int)getpid());
|
---|
| 1176 |
|
---|
| 1177 | #if !BB_MMU
|
---|
| 1178 | G.root_fd = xopen("/", O_RDONLY | O_DIRECTORY);
|
---|
| 1179 | close_on_exec_on(G.root_fd);
|
---|
| 1180 | #endif
|
---|
| 1181 |
|
---|
| 1182 | if (argv[optind]) {
|
---|
[3232] | 1183 | xchroot(argv[optind]);
|
---|
[2725] | 1184 | }
|
---|
| 1185 |
|
---|
| 1186 | //umask(077); - admin can set umask before starting us
|
---|
| 1187 |
|
---|
| 1188 | /* Signals. We'll always take -EPIPE rather than a rude signal, thanks */
|
---|
| 1189 | signal(SIGPIPE, SIG_IGN);
|
---|
| 1190 |
|
---|
| 1191 | /* Set up options on the command socket (do we need these all? why?) */
|
---|
| 1192 | setsockopt(STDIN_FILENO, IPPROTO_TCP, TCP_NODELAY, &const_int_1, sizeof(const_int_1));
|
---|
| 1193 | setsockopt(STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
|
---|
| 1194 | /* Telnet protocol over command link may send "urgent" data,
|
---|
| 1195 | * we prefer it to be received in the "normal" data stream: */
|
---|
| 1196 | setsockopt(STDIN_FILENO, SOL_SOCKET, SO_OOBINLINE, &const_int_1, sizeof(const_int_1));
|
---|
| 1197 |
|
---|
| 1198 | WRITE_OK(FTP_GREET);
|
---|
| 1199 | signal(SIGALRM, timeout_handler);
|
---|
| 1200 |
|
---|
| 1201 | #ifdef IF_WE_WANT_TO_REQUIRE_LOGIN
|
---|
| 1202 | {
|
---|
| 1203 | smallint user_was_specified = 0;
|
---|
| 1204 | while (1) {
|
---|
| 1205 | uint32_t cmdval = cmdio_get_cmd_and_arg();
|
---|
| 1206 |
|
---|
| 1207 | if (cmdval == const_USER) {
|
---|
| 1208 | if (G.ftp_arg == NULL || strcasecmp(G.ftp_arg, "anonymous") != 0)
|
---|
| 1209 | cmdio_write_raw(STR(FTP_LOGINERR)" Server is anonymous only\r\n");
|
---|
| 1210 | else {
|
---|
| 1211 | user_was_specified = 1;
|
---|
| 1212 | cmdio_write_raw(STR(FTP_GIVEPWORD)" Please specify the password\r\n");
|
---|
| 1213 | }
|
---|
| 1214 | } else if (cmdval == const_PASS) {
|
---|
| 1215 | if (user_was_specified)
|
---|
| 1216 | break;
|
---|
| 1217 | cmdio_write_raw(STR(FTP_NEEDUSER)" Login with USER\r\n");
|
---|
| 1218 | } else if (cmdval == const_QUIT) {
|
---|
| 1219 | WRITE_OK(FTP_GOODBYE);
|
---|
| 1220 | return 0;
|
---|
| 1221 | } else {
|
---|
| 1222 | cmdio_write_raw(STR(FTP_LOGINERR)" Login with USER and PASS\r\n");
|
---|
| 1223 | }
|
---|
| 1224 | }
|
---|
| 1225 | }
|
---|
| 1226 | WRITE_OK(FTP_LOGINOK);
|
---|
| 1227 | #endif
|
---|
| 1228 |
|
---|
| 1229 | /* RFC-959 Section 5.1
|
---|
| 1230 | * The following commands and options MUST be supported by every
|
---|
| 1231 | * server-FTP and user-FTP, except in cases where the underlying
|
---|
| 1232 | * file system or operating system does not allow or support
|
---|
| 1233 | * a particular command.
|
---|
| 1234 | * Type: ASCII Non-print, IMAGE, LOCAL 8
|
---|
| 1235 | * Mode: Stream
|
---|
| 1236 | * Structure: File, Record*
|
---|
| 1237 | * (Record structure is REQUIRED only for hosts whose file
|
---|
| 1238 | * systems support record structure).
|
---|
| 1239 | * Commands:
|
---|
| 1240 | * USER, PASS, ACCT, [bbox: ACCT not supported]
|
---|
| 1241 | * PORT, PASV,
|
---|
| 1242 | * TYPE, MODE, STRU,
|
---|
| 1243 | * RETR, STOR, APPE,
|
---|
| 1244 | * RNFR, RNTO, DELE,
|
---|
| 1245 | * CWD, CDUP, RMD, MKD, PWD,
|
---|
| 1246 | * LIST, NLST,
|
---|
| 1247 | * SYST, STAT,
|
---|
| 1248 | * HELP, NOOP, QUIT.
|
---|
| 1249 | */
|
---|
| 1250 | /* ACCOUNT (ACCT)
|
---|
| 1251 | * "The argument field is a Telnet string identifying the user's account.
|
---|
| 1252 | * The command is not necessarily related to the USER command, as some
|
---|
| 1253 | * sites may require an account for login and others only for specific
|
---|
| 1254 | * access, such as storing files. In the latter case the command may
|
---|
| 1255 | * arrive at any time.
|
---|
| 1256 | * There are reply codes to differentiate these cases for the automation:
|
---|
| 1257 | * when account information is required for login, the response to
|
---|
| 1258 | * a successful PASSword command is reply code 332. On the other hand,
|
---|
| 1259 | * if account information is NOT required for login, the reply to
|
---|
| 1260 | * a successful PASSword command is 230; and if the account information
|
---|
| 1261 | * is needed for a command issued later in the dialogue, the server
|
---|
| 1262 | * should return a 332 or 532 reply depending on whether it stores
|
---|
| 1263 | * (pending receipt of the ACCounT command) or discards the command,
|
---|
| 1264 | * respectively."
|
---|
| 1265 | */
|
---|
| 1266 |
|
---|
| 1267 | while (1) {
|
---|
| 1268 | uint32_t cmdval = cmdio_get_cmd_and_arg();
|
---|
| 1269 |
|
---|
| 1270 | if (cmdval == const_QUIT) {
|
---|
| 1271 | WRITE_OK(FTP_GOODBYE);
|
---|
| 1272 | return 0;
|
---|
| 1273 | }
|
---|
| 1274 | else if (cmdval == const_USER)
|
---|
| 1275 | /* This would mean "ok, now give me PASS". */
|
---|
| 1276 | /*WRITE_OK(FTP_GIVEPWORD);*/
|
---|
| 1277 | /* vsftpd can be configured to not require that,
|
---|
| 1278 | * and this also saves one roundtrip:
|
---|
| 1279 | */
|
---|
| 1280 | WRITE_OK(FTP_LOGINOK);
|
---|
| 1281 | else if (cmdval == const_PASS)
|
---|
| 1282 | WRITE_OK(FTP_LOGINOK);
|
---|
| 1283 | else if (cmdval == const_NOOP)
|
---|
| 1284 | WRITE_OK(FTP_NOOPOK);
|
---|
| 1285 | else if (cmdval == const_TYPE)
|
---|
| 1286 | WRITE_OK(FTP_TYPEOK);
|
---|
| 1287 | else if (cmdval == const_STRU)
|
---|
| 1288 | WRITE_OK(FTP_STRUOK);
|
---|
| 1289 | else if (cmdval == const_MODE)
|
---|
| 1290 | WRITE_OK(FTP_MODEOK);
|
---|
| 1291 | else if (cmdval == const_ALLO)
|
---|
| 1292 | WRITE_OK(FTP_ALLOOK);
|
---|
| 1293 | else if (cmdval == const_SYST)
|
---|
| 1294 | cmdio_write_raw(STR(FTP_SYSTOK)" UNIX Type: L8\r\n");
|
---|
| 1295 | else if (cmdval == const_PWD)
|
---|
| 1296 | handle_pwd();
|
---|
| 1297 | else if (cmdval == const_CWD)
|
---|
| 1298 | handle_cwd();
|
---|
| 1299 | else if (cmdval == const_CDUP) /* cd .. */
|
---|
| 1300 | handle_cdup();
|
---|
| 1301 | /* HELP is nearly useless, but we can reuse FEAT for it */
|
---|
| 1302 | /* lftp uses FEAT */
|
---|
| 1303 | else if (cmdval == const_HELP || cmdval == const_FEAT)
|
---|
| 1304 | handle_feat(cmdval == const_HELP
|
---|
| 1305 | ? STRNUM32(FTP_HELP)
|
---|
| 1306 | : STRNUM32(FTP_STATOK)
|
---|
| 1307 | );
|
---|
| 1308 | else if (cmdval == const_LIST) /* ls -l */
|
---|
| 1309 | handle_list();
|
---|
| 1310 | else if (cmdval == const_NLST) /* "name list", bare ls */
|
---|
| 1311 | handle_nlst();
|
---|
| 1312 | /* SIZE is crucial for wget's download indicator etc */
|
---|
| 1313 | /* Mozilla, lftp use MDTM (presumably for caching) */
|
---|
| 1314 | else if (cmdval == const_SIZE || cmdval == const_MDTM)
|
---|
| 1315 | handle_size_or_mdtm(cmdval == const_SIZE);
|
---|
| 1316 | else if (cmdval == const_STAT) {
|
---|
| 1317 | if (G.ftp_arg == NULL)
|
---|
| 1318 | handle_stat();
|
---|
| 1319 | else
|
---|
| 1320 | handle_stat_file();
|
---|
| 1321 | }
|
---|
| 1322 | else if (cmdval == const_PASV)
|
---|
| 1323 | handle_pasv();
|
---|
| 1324 | else if (cmdval == const_EPSV)
|
---|
| 1325 | handle_epsv();
|
---|
| 1326 | else if (cmdval == const_RETR)
|
---|
| 1327 | handle_retr();
|
---|
| 1328 | else if (cmdval == const_PORT)
|
---|
| 1329 | handle_port();
|
---|
| 1330 | else if (cmdval == const_REST)
|
---|
| 1331 | handle_rest();
|
---|
| 1332 | #if ENABLE_FEATURE_FTP_WRITE
|
---|
| 1333 | else if (opts & OPT_w) {
|
---|
| 1334 | if (cmdval == const_STOR)
|
---|
| 1335 | handle_stor();
|
---|
| 1336 | else if (cmdval == const_MKD)
|
---|
| 1337 | handle_mkd();
|
---|
| 1338 | else if (cmdval == const_RMD)
|
---|
| 1339 | handle_rmd();
|
---|
| 1340 | else if (cmdval == const_DELE)
|
---|
| 1341 | handle_dele();
|
---|
| 1342 | else if (cmdval == const_RNFR) /* "rename from" */
|
---|
| 1343 | handle_rnfr();
|
---|
| 1344 | else if (cmdval == const_RNTO) /* "rename to" */
|
---|
| 1345 | handle_rnto();
|
---|
| 1346 | else if (cmdval == const_APPE)
|
---|
| 1347 | handle_appe();
|
---|
| 1348 | else if (cmdval == const_STOU) /* "store unique" */
|
---|
| 1349 | handle_stou();
|
---|
| 1350 | else
|
---|
| 1351 | goto bad_cmd;
|
---|
| 1352 | }
|
---|
| 1353 | #endif
|
---|
| 1354 | #if 0
|
---|
| 1355 | else if (cmdval == const_STOR
|
---|
| 1356 | || cmdval == const_MKD
|
---|
| 1357 | || cmdval == const_RMD
|
---|
| 1358 | || cmdval == const_DELE
|
---|
| 1359 | || cmdval == const_RNFR
|
---|
| 1360 | || cmdval == const_RNTO
|
---|
| 1361 | || cmdval == const_APPE
|
---|
| 1362 | || cmdval == const_STOU
|
---|
| 1363 | ) {
|
---|
| 1364 | cmdio_write_raw(STR(FTP_NOPERM)" Permission denied\r\n");
|
---|
| 1365 | }
|
---|
| 1366 | #endif
|
---|
| 1367 | else {
|
---|
| 1368 | /* Which unsupported commands were seen in the wild?
|
---|
| 1369 | * (doesn't necessarily mean "we must support them")
|
---|
| 1370 | * foo 1.2.3: XXXX - comment
|
---|
| 1371 | */
|
---|
| 1372 | #if ENABLE_FEATURE_FTP_WRITE
|
---|
| 1373 | bad_cmd:
|
---|
| 1374 | #endif
|
---|
| 1375 | cmdio_write_raw(STR(FTP_BADCMD)" Unknown command\r\n");
|
---|
| 1376 | }
|
---|
| 1377 | }
|
---|
| 1378 | }
|
---|