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