"Fossies" - the Fresh Open Source Software Archive 
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "ftp_commands.c" see the
Fossies "Dox" file reference documentation.
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 /*
18 * Original Copyright (c) 2005 Covalent Technologies
19 *
20 * FTP Protocol module for Apache 2.0
21 */
22
23 #define CORE_PRIVATE
24 #include "mod_ftp.h"
25 #include "ftp_internal.h"
26 #include "apr_version.h"
27 #include "apr_network_io.h"
28 #include "http_vhost.h"
29
30 /* seem to need this for LONG_MAX */
31 #if APR_HAVE_LIMITS_H
32 #include <limits.h>
33 #endif
34
35 /* Wish APR had one of these */
36 #if defined(WIN32) || defined(USE_WINSOCK)
37 #define FTP_APR_EADDRINUSE (APR_OS_START_SYSERR + WSAEADDRINUSE)
38 #else
39 #define FTP_APR_EADDRINUSE EADDRINUSE
40 #endif /* WIN32 */
41
42 extern ap_filter_rec_t *ftp_count_size_filter_handle;
43 extern ap_filter_rec_t *ftp_content_length_filter_handle;
44
45 static apr_hash_t *FTPMethodHash;
46 static apr_pool_t *FTPMethodPool;
47 static const char *FTPHelpText;
48 static apr_size_t FTPHelpTextLen;
49 static const char *FTPFeatText;
50 static apr_size_t FTPFeatTextLen;
51
52 /*
53 * The FTP command structure contains useful information about the FTP
54 * handler. This information is filled out when a command is registered
55 * using ftp_hook_cmd(), which also puts the handler into the global hash.
56 */
57 typedef struct ftp_cmd_entry
58 {
59 const char *key; /* The key, e.g. "DELE" */
60 ftp_hook_fn *pf; /* Pointer to the handler */
61 const char *alias; /* The aliased command e.g. "CDUP" */
62 int order; /* Handler ordering */
63 int flags; /* Flags for this command. See FTP_CMD_ */
64 const char *help; /* Help string for this command */
65 struct ftp_cmd_entry *next; /* Pointer to the next handler */
66 } ftp_cmd_entry;
67
68
69 FTP_DECLARE(void) ftp_hook_cmd_any(const char *key, ftp_hook_fn *pf,
70 const char *alias, int order,
71 int flags, const char *help)
72 {
73 ftp_cmd_entry *cmd, *curr;
74
75 cmd = apr_pcalloc(FTPMethodPool, sizeof(ftp_cmd_entry));
76
77 /* Duplicate for storage into the hash */
78 key = apr_pstrdup(FTPMethodPool, key);
79 help = apr_pstrdup(FTPMethodPool, help);
80
81 cmd->key = key;
82 cmd->pf = pf;
83 cmd->alias = alias;
84 cmd->flags = flags;
85 cmd->order = order;
86 cmd->help = help;
87
88 if (!FTPMethodHash) {
89 /*
90 * Should never get here. If a user tries to load an extension
91 * module before the core FTP module is loaded, they should get an
92 * undefined symbol.
93 */
94 fprintf(stderr, "Could not process registration for %s.", key);
95 return;
96 }
97
98 curr = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
99
100 if (curr) {
101 if (curr->order > cmd->order) {
102 cmd->next = curr;
103 apr_hash_set(FTPMethodHash, key, APR_HASH_KEY_STRING, cmd);
104 }
105 else {
106 while (curr->next && (curr->order < cmd->order)) {
107 curr = curr->next;
108 }
109 cmd->next = curr->next;
110 curr->next = cmd;
111 }
112 }
113 else {
114 apr_hash_set(FTPMethodHash, key, APR_HASH_KEY_STRING, cmd);
115 }
116
117 /*
118 * Only limit commands that are implemented and access checked (not
119 * aliases). PASS is a special case, it is verified after invocation,
120 * login isn't needed prior to processing.
121 */
122 if (pf && (flags & FTP_NEED_LOGIN)) {
123 ap_method_register(FTPMethodPool, key);
124 }
125 ap_method_register(FTPMethodPool, "PASS");
126 }
127
128 static int ftp_run_handler(request_rec *r, struct ftp_cmd_entry *cmd,
129 const char *arg)
130 {
131 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
132 request_rec *rr;
133 int res;
134
135 /*
136 * Set the authorization header and the user name for all requests. This
137 * information may not be needed for all requests, but is required for
138 * logging purposes.
139 */
140 ftp_set_authorization(r);
141
142 /*
143 * Run the header parser hook for mod_setenv. For conditional logging,
144 * etc
145 */
146 ap_run_header_parser(r);
147
148 /*
149 * Run a subrequest before the command is run. This allows us to check
150 * if a command is allowed for the user's current location
151 */
152 if ((res = ftp_set_uri(r, fc->cwd))) {
153 return res;
154 }
155
156 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
157
158 /*
159 * XXX - JKS
160 *
161 * This will check authz for the *CURRENT* location. For commands where the
162 * URI can change, the command handler will need to rerun the auth/authz
163 * checks to ensure the user also has permission for the new URI.
164 */
165 if ((rr->status == HTTP_UNAUTHORIZED &&
166 (cmd->flags & FTP_NEED_LOGIN)) ||
167 ((rr->status == HTTP_FORBIDDEN) &&
168 (cmd->flags & FTP_NEED_LOGIN))) {
169 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOTALLOWED,
170 ftp_escape_control_text(r->method, r->pool));
171 ap_destroy_sub_req(rr);
172 return FTP_REPLY_FILE_NOT_FOUND;
173 }
174 ap_destroy_sub_req(rr);
175
176 /* Ok, this method is allowed, run through the list */
177 res = cmd->pf(r, arg);
178 if (res != DECLINED) {
179 return res;
180 }
181
182 if (cmd->next) {
183 cmd = cmd->next;
184 if (cmd->pf) {
185 return ftp_run_handler(r, cmd, arg);
186 }
187 }
188 return (cmd->flags & FTP_EXTENSIBLE)
189 ? FTP_REPLY_COMMAND_NOT_IMPL_PARAM
190 : FTP_REPLY_COMMAND_NOT_IMPLEMENTED;
191 }
192
193 const char *ftp_get_cmd_alias(const char *key)
194 {
195 ftp_cmd_entry *cmd;
196
197 if (!FTPMethodHash) {
198 return key;
199 }
200
201 cmd = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
202
203 if (cmd && cmd->alias) {
204 return (const char *) cmd->alias;
205 }
206
207 return key;
208 }
209
210 void ftp_cmd_finalize(apr_pool_t *pool, apr_pool_t *ptemp)
211 {
212 ftp_cmd_entry *cmd, *basecmd;
213 apr_hash_index_t *hi;
214 void *val;
215 int i;
216
217 FTPHelpText = apr_psprintf(ptemp, "%d-%s", FTP_REPLY_HELP_MESSAGE,
218 "The following commands are recognized "
219 "(* =>'s unimplemented).");
220
221 FTPFeatText = apr_psprintf(ptemp, "%d-%s", FTP_REPLY_SYSTEM_STATUS,
222 "Extensions supported");
223
224 for (hi = apr_hash_first(ptemp, FTPMethodHash), i = 0; hi;
225 hi = apr_hash_next(hi), i++)
226 {
227 apr_hash_this(hi, NULL, NULL, &val);
228 cmd = (struct ftp_cmd_entry *) val;
229
230 if (cmd->alias)
231 basecmd = apr_hash_get(FTPMethodHash, cmd->alias, APR_HASH_KEY_STRING);
232 else
233 basecmd = cmd;
234
235 if (!(cmd->flags & FTP_NO_HELP))
236 FTPHelpText = apr_psprintf(ptemp, "%s%s %c%-4s",
237 FTPHelpText, (i % 8) ? "" : CRLF,
238 (basecmd->pf) ? ' ' : '*', cmd->key);
239 else
240 --i;
241
242 if (cmd->flags & FTP_NEW_FEAT)
243 FTPFeatText = apr_pstrcat(ptemp, FTPFeatText, CRLF " ",
244 cmd->key, NULL);
245 }
246
247 FTPHelpText = apr_pstrcat(pool, FTPHelpText, CRLF, NULL);
248 FTPHelpTextLen = strlen(FTPHelpText);
249
250 FTPFeatText = apr_pstrcat(pool, FTPFeatText, CRLF, NULL);
251 FTPFeatTextLen = strlen(FTPFeatText);
252 }
253
254 /* ftp_parse2: Parse a FTP request that is expected to have 2 arguments.
255 *
256 * Arguments: pool - Pool to allocate from
257 * cmd - The complete request (e.g. GET /foo.html)
258 * a1 - The first argument
259 * a2 - The second argument
260 *
261 * Returns: 1 on error or 0 on success, a1 and a2 are modified to point
262 * to the correct values.
263 */
264 static int ftp_parse2(apr_pool_t *pool, const char *cmd,
265 char **a1, char **a2, int keepws)
266 {
267 if (keepws)
268 {
269 const char *save = cmd;
270 while (*cmd && *cmd != ' ') ++cmd;
271 *a1 = apr_pstrndup(pool, save, cmd - save);
272 if (*cmd && *cmd == ' ') ++cmd;
273 *a2 = apr_pstrdup(pool, cmd);
274 if (!*a1 || !*a2)
275 return 1;
276 }
277 else
278 {
279 char *fix;
280 *a1 = ap_getword(pool, &cmd, ' ');
281 *a2 = apr_pstrdup(pool, cmd);
282 if (!*a1 || !*a2)
283 return 1;
284 fix = strchr(*a2, '\0');
285 while (fix > *a2 && *(fix - 1) == ' ')
286 *(--fix) = '\0';
287 }
288 return 0;
289 }
290
291 int ftp_run_cmd(request_rec *r, const char *key)
292 {
293 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
294 ftp_cmd_entry *cmd;
295 char *method, *arg = NULL;
296 int res;
297
298 if (!FTPMethodHash) {
299 return FTP_REPLY_LOCAL_ERROR;
300 }
301
302 cmd = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
303
304 if (cmd) {
305
306 /*
307 * Commands must have been traslated to their real equivilants back
308 * in ftp_read_request_line, by the ftp_get_cmd_alias function above.
309 *
310 * Note: recursive aliases are unsupported
311 */
312 if (cmd->pf == NULL) {
313 return (cmd->flags & FTP_EXTENSIBLE)
314 ? FTP_REPLY_COMMAND_NOT_IMPL_PARAM
315 : FTP_REPLY_COMMAND_NOT_IMPLEMENTED;
316 }
317
318 if ((cmd->flags & FTP_NEED_LOGIN) && !fc->logged_in) {
319 fc->response_notes = "Please log in with USER and PASS";
320 return FTP_REPLY_NOT_LOGGED_IN;
321 }
322 else {
323 res = ftp_parse2(r->pool, r->the_request,
324 &method, &arg, cmd->flags & FTP_KEEP_WHITESPACE);
325 if (res || (!(cmd->flags & FTP_TAKE0) && !*arg)
326 || (!(cmd->flags & FTP_TAKE1) && *arg))
327 return FTP_REPLY_SYNTAX_ERROR;
328
329 return ftp_run_handler(r, cmd, arg);
330 }
331 }
332
333 return FTP_REPLY_COMMAND_UNRECOGNIZED;
334 }
335
336 int ftp_cmd_abort_data(const char *key)
337 {
338 ftp_cmd_entry *cmd;
339
340 cmd = apr_hash_get(FTPMethodHash, key, APR_HASH_KEY_STRING);
341
342 /* Return true if cmd should abort an active data connection */
343 return (cmd && (cmd->flags & FTP_DATA_INTR));
344 }
345
346 /* Begin definition of our command handlers. */
347 static int ftp_cmd_abor(request_rec *r, const char *arg)
348 {
349 r->the_request = apr_pstrdup(r->pool, "ABOR");
350 r->method = apr_pstrdup(r->pool, "ABOR");
351 apr_table_setn(r->subprocess_env, "ftp_transfer_ok", "0");
352 return FTP_REPLY_TRANSFER_ABORTED;
353 }
354
355 static int ftp_cmd_auth(request_rec *r, const char *arg)
356 {
357 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
358
359 if (!ftp_have_ssl() || (!fc->ssl_input_ctx || !fc->ssl_output_ctx)) {
360 fc->response_notes = "AUTH mechanism not available";
361 return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
362 }
363
364 /*
365 * RFC 2228 states these arguments are case insensitive.
366 * draft-murray-auth-ftp-ssl-06.txt defined these 4 AUTH mechanisms. TLS
367 * or TLS-C will encrypt the control connection, leaving the data
368 * channel clear. SSL or TLS-P will encrypt both the control and data
369 * connections. As it evolved to publication, all but "TLS" were dropped
370 * from RFC4217, as RFC 2228 previously insisted that PROT defaults to
371 * 'C'lear text.
372 */
373 if ((strcasecmp(arg, "SSL") == 0) ||
374 (strcasecmp(arg, "TLS-P") == 0)) {
375
376 fc->prot = FTP_PROT_PRIVATE;
377 fc->auth = FTP_AUTH_SSL;
378 }
379 else if ((strcasecmp(arg, "TLS") == 0) ||
380 (strcasecmp(arg, "TLS-C") == 0)) {
381
382 fc->prot = FTP_PROT_CLEAR;
383 fc->auth = FTP_AUTH_TLS;
384 }
385 else {
386 fc->response_notes = "AUTH mechanism not supported";
387 return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
388 }
389
390 return FTP_REPLY_SECURITY_EXCHANGE_DONE;
391 }
392
393 /* XXX: RPM 6/11/2002
394 * This needs rewriting. Too many special cases.
395 */
396 static int ftp_change_dir(request_rec *r, const char *arg)
397 {
398 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
399 ftp_dir_config *dconf;
400 request_rec *rr;
401 conn_rec *c = r->connection;
402 int res, response;
403
404 if ((res = ftp_set_uri(r, arg))) {
405 return res;
406 }
407
408 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
409
410 if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
411 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
412 ftp_escape_control_text(r->parsed_uri.path,
413 r->pool));
414 ap_destroy_sub_req(rr);
415 return FTP_REPLY_FILE_NOT_FOUND;
416 }
417
418 dconf = ftp_get_module_config(rr->per_dir_config);
419
420 /* Handle special case where the URI is / */
421 if (r->uri[0] == '/' && !r->uri[1]) {
422 apr_cpystrn(fc->cwd, r->uri, APR_PATH_MAX + 1);
423
424 if (dconf->readme) {
425
426 /*
427 * We do not inherit readme messages. If this readme was not
428 * specifically meant for us, we skip it. The exception to the
429 * rule is if FTPReadmeMessage was placed in the global server
430 * configuration.
431 */
432 if (!dconf->path ||
433 !strncmp(dconf->path, r->filename,
434 strlen(r->filename) - 1)) {
435
436 if (dconf->readme_isfile) {
437 ftp_show_file(c->output_filters, r->pool,
438 FTP_REPLY_COMPLETED, fc,
439 dconf->readme);
440 }
441 else {
442 char outbuf[BUFSIZ];
443
444 ftp_message_generate(fc, dconf->readme, outbuf,
445 sizeof(outbuf));
446 ftp_reply(fc, c->output_filters, r->pool,
447 FTP_REPLY_COMPLETED, 1, outbuf);
448 }
449 }
450 }
451
452 ap_destroy_sub_req(rr);
453 return FTP_REPLY_COMPLETED;
454 }
455
456
457 /* Check access permissions for the new directory. */
458 if (!((rr->status == HTTP_OK) || (rr->status == HTTP_MOVED_PERMANENTLY))) {
459 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
460 ftp_escape_control_text(r->parsed_uri.path,
461 r->pool));
462 ap_destroy_sub_req(rr);
463 return FTP_REPLY_FILE_NOT_FOUND;
464 }
465
466 if (rr->finfo.filetype != 0) {
467 if (rr->finfo.filetype != APR_DIR) {
468 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOT_A_DIR,
469 ftp_escape_control_text(r->parsed_uri.path,
470 r->pool));
471 response = FTP_REPLY_FILE_NOT_FOUND;
472 }
473 else {
474 apr_cpystrn(fc->cwd, r->parsed_uri.path, APR_PATH_MAX + 1);
475
476 if (dconf->readme) {
477 /*
478 * We do not inherit readme messages. If this readme was not
479 * specifically meant for us, we skip it. The exception to
480 * the rule is if FTPReadmeMessage was placed in the global
481 * server configuration.
482 */
483 if (!dconf->path ||
484 !strncmp(dconf->path, r->filename,
485 strlen(r->filename) - 1)) {
486
487 if (dconf->readme_isfile) {
488 ftp_show_file(c->output_filters, r->pool,
489 FTP_REPLY_COMPLETED, fc,
490 dconf->readme);
491 }
492 else {
493 char outbuf[BUFSIZ];
494
495 ftp_message_generate(fc, dconf->readme, outbuf,
496 sizeof(outbuf));
497 ftp_reply(fc, c->output_filters, r->pool,
498 FTP_REPLY_COMPLETED, 1, outbuf);
499 }
500 }
501 }
502 response = FTP_REPLY_COMPLETED;
503 }
504 }
505 else {
506 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
507 ftp_escape_control_text(r->parsed_uri.path,
508 r->pool));
509 response = FTP_REPLY_FILE_NOT_FOUND;
510 }
511
512 ap_destroy_sub_req(rr);
513 return response;
514 }
515
516 static int ftp_cmd_cdup(request_rec *r, const char *arg)
517 {
518 return ftp_change_dir(r, "..");
519 }
520
521 static int ftp_cmd_cwd(request_rec *r, const char *arg)
522 {
523 return ftp_change_dir(r, arg);
524 }
525
526 static int ftp_cmd_dele(request_rec *r, const char *arg)
527 {
528 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
529 request_rec *rr;
530 apr_status_t rv;
531 int res, response;
532
533 if ((res = ftp_set_uri(r, arg))) {
534 return res;
535 }
536
537 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
538
539 /*
540 * If the user does not have permission to view the file, do not let them
541 * delete it.
542 */
543 if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
544 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
545 ftp_escape_control_text(r->parsed_uri.path,
546 r->pool));
547 response = FTP_REPLY_FILE_NOT_FOUND;
548 }
549 /* We do have permission, and the file exists. Try to remove. */
550 else if (rr->finfo.filetype == APR_DIR) {
551
552 rv = apr_dir_remove(r->filename, r->pool);
553
554 if (rv != APR_SUCCESS) {
555 char error_str[120];
556 char *err = apr_strerror(rv, error_str, sizeof(error_str));
557 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
558 ftp_escape_control_text(err, r->pool));
559 response = FTP_REPLY_FILE_NOT_FOUND;
560 }
561 else {
562 response = FTP_REPLY_COMPLETED;
563 }
564 }
565 else if (rr->finfo.filetype == APR_REG) {
566
567 rv = apr_file_remove(r->filename, r->pool);
568
569 if (rv != APR_SUCCESS) {
570 /* Call to apr_file_remove failed */
571 char error_str[120];
572 char *err = apr_strerror(rv, error_str, sizeof(error_str));
573 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
574 ftp_escape_control_text(err, r->pool));
575 response = FTP_REPLY_FILE_NOT_FOUND;
576 }
577 else {
578 response = FTP_REPLY_COMPLETED;
579 }
580 }
581 else {
582 /* File does not exist */
583 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
584 ftp_escape_control_text(r->parsed_uri.path,
585 r->pool));
586 response = FTP_REPLY_FILE_NOT_FOUND;
587 }
588 ap_destroy_sub_req(rr);
589 return response;
590 }
591
592 static int ftp_cmd_feat(request_rec *r, const char *arg)
593 {
594 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
595 conn_rec *c = r->connection;
596 apr_bucket_brigade *bb;
597 apr_bucket *b;
598
599 bb = apr_brigade_create(r->pool, c->bucket_alloc);
600 b = apr_bucket_immortal_create(FTPFeatText, FTPFeatTextLen, c->bucket_alloc);
601 APR_BRIGADE_INSERT_TAIL(bb, b);
602 fc->traffic += FTPFeatTextLen;
603
604 b = apr_bucket_flush_create(c->bucket_alloc);
605 APR_BRIGADE_INSERT_TAIL(bb, b);
606 ap_pass_brigade(c->output_filters, bb);
607
608 fc->response_notes = "End";
609 return FTP_REPLY_SYSTEM_STATUS;
610 }
611
612 static int ftp_cmd_help(request_rec *r, const char *arg)
613 {
614 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
615 conn_rec *c = r->connection;
616 apr_bucket_brigade *bb;
617 apr_bucket *b;
618 ftp_cmd_entry *cmd;
619 char *method;
620
621 if (*arg) {
622 method = ftp_toupper(r->pool, arg);
623 cmd = apr_hash_get(FTPMethodHash, method, APR_HASH_KEY_STRING);
624
625 if (cmd) {
626 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_HELP_SYNTAX,
627 arg, cmd->help);
628 return FTP_REPLY_HELP_MESSAGE;
629 }
630 else {
631 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOTIMPL,
632 ftp_escape_control_text(arg, r->pool));
633 return FTP_REPLY_COMMAND_NOT_IMPLEMENTED;
634 }
635 }
636
637 /* given no argument, pre-prepared HELP message */
638 bb = apr_brigade_create(r->pool, c->bucket_alloc);
639 b = apr_bucket_immortal_create(FTPHelpText, FTPHelpTextLen, c->bucket_alloc);
640 APR_BRIGADE_INSERT_TAIL(bb, b);
641 fc->traffic += FTPHelpTextLen;
642
643 b = apr_bucket_flush_create(c->bucket_alloc);
644 APR_BRIGADE_INSERT_TAIL(bb, b);
645 ap_pass_brigade(c->output_filters, bb);
646
647 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_HELP,
648 ftp_escape_control_text(r->server->server_admin,
649 r->pool));
650 return FTP_REPLY_HELP_MESSAGE;
651 }
652
653 static int common_list(request_rec *r, const char *arg, int is_list)
654 {
655 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
656 conn_rec *c = r->connection;
657 ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
658 conn_rec *cdata;
659 request_rec *rr;
660 apr_bucket_brigade *bb;
661 apr_bucket *b;
662 struct ftp_direntry *direntry, *dirp;
663 char *word, *buf = NULL, *pattern;
664 int dashl = 0;
665 apr_status_t rv;
666 apr_size_t nbytes;
667 char *varg = apr_pstrdup(r->pool, arg);
668 const char *test, *sl;
669 int res;
670 int decend = 0;
671
672 while (*varg == '-')
673 {
674 if (ftp_parse2(r->pool, varg, &word, &varg, FTP_KEEP_WHITESPACE)) {
675 varg = word;
676 break;
677 }
678 /* More Cowbell! TODO: expand the accepted dash patterns */
679 if (ap_strchr(word, 'l')) {
680 dashl = 1;
681 }
682 /* -- 'end of dash-opts' by convention allows patterns like '-*' */
683 if (ap_strchr(word + 1, '-')) {
684 break;
685 }
686 }
687
688 /* Special FTPOption that maps NLST directly to LIST */
689 if (is_list && (fsc->options & FTP_OPT_LISTISNLST) && !dashl) {
690 is_list = 0;
691 }
692 else
693 /* Special FTPOption that maps NLST directly to LIST */
694 if (!is_list && ((fsc->options & FTP_OPT_NLSTISLIST) || dashl)) {
695 is_list = 1;
696 }
697
698 arg = varg;
699
700 if (is_list && (ap_strchr_c(arg, '*') != NULL))
701 {
702 /* Prevent DOS attacks, only allow one segment to have a wildcard */
703 int found = 0; /* The number of segments with a wildcard */
704 const char *pos = arg; /* Pointer for iterating over the string */
705
706 while (*pos != '\0') {
707 if (*pos == '*') {
708 /*
709 * We found a wildcard in this segment. Increment the count
710 * and move the pointer to the beginning of the next segment
711 */
712 found++;
713 while (*pos != '\0' && *pos != '/') {
714 pos++;
715 }
716 }
717 else {
718 /* Nothing here, move on */
719 pos++;
720 }
721 }
722
723 /* In the future this can be configurable */
724 if (found > 1) {
725 ap_log_error(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0, 0,
726 "Ignoring directory listing request for %s", arg);
727 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
728 ftp_escape_control_text(arg, r->pool));
729 return FTP_REPLY_FILE_NOT_FOUND;
730 }
731 }
732
733 if ((res = ftp_set_uri(r, arg))) {
734 return res;
735 }
736
737 /* Need to run intermediate subrequest to check for redirect */
738 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
739 if (rr->status == HTTP_MOVED_PERMANENTLY)
740 {
741 ap_parse_uri(r, apr_pstrcat(r->pool, rr->uri, "/", NULL));
742
743 /* Rerun the subrequest with the new uri */
744 ap_destroy_sub_req(rr);
745 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
746 }
747
748 if (rr->status != HTTP_OK) {
749 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
750 ftp_escape_control_text(r->parsed_uri.path,
751 r->pool));
752 ap_destroy_sub_req(rr);
753 return FTP_REPLY_FILE_NOT_FOUND;
754 }
755
756 ap_destroy_sub_req(rr);
757
758 if (arg[0] == '\0') {
759 pattern = apr_pstrcat(r->pool, r->filename, "/*", NULL);
760 }
761 else {
762 pattern = r->filename;
763 }
764
765 #ifdef FTP_DEBUG
766 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Pattern: %s", pattern);
767 #endif
768
769 /* Construct the sorted array of directory contents */
770 if ((direntry = ftp_direntry_get(r, pattern)) == NULL) {
771 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
772 ftp_escape_control_text(arg, r->pool));
773 return FTP_REPLY_FILE_NOT_FOUND;
774 }
775
776 fc->response_notes = FTP_MSG_OPENASCII;
777 ftp_send_response(r, FTP_REPLY_FILE_STATUS_OK);
778
779 if (!(cdata = ftp_open_dataconn(r, 1))) {
780 return FTP_REPLY_CANNOT_OPEN_DATACONN;
781 }
782
783 if (is_list)
784 {
785 /*
786 * We have the directory tree, and its time to print it to the client.
787 * At this point we could have one of three things.
788 *
789 * 1. One entry, a directory. Just print the contents
790 * 2. No directories. Just print the contents.
791 * 3. Mixture. Print files first, then directories.
792 */
793
794 /* Handle case 1 */
795 if (direntry->child && !direntry->next) {
796 direntry = direntry->child;
797 }
798 else {
799 /* Handle case 2 & 3 */
800 for (dirp = direntry; dirp; dirp = dirp->next) {
801 if (dirp->child)
802 decend = 1;
803 }
804 }
805 }
806
807 bb = apr_brigade_create(r->pool, c->bucket_alloc);
808
809 /* Print the directory listing, skipping directories as needed */
810 for (dirp = direntry; dirp; dirp = dirp->next)
811 {
812 if (dirp->modestring[0] == 'd')
813 {
814 if (is_list && decend)
815 continue;
816 else if (!is_list && !(fsc->options & FTP_OPT_NLSTSHOWDIRS))
817 continue;
818 }
819
820 for (test = dirp->name; (sl = ap_strchr_c(test, '/')); test = sl + 1)
821 /* noop */ ;
822
823 if (!strcmp(".", test)) {
824 continue;
825 }
826
827 if (is_list)
828 buf = apr_psprintf(r->pool,
829 "%10s%5d %-8s %-8s%9" APR_OFF_T_FMT " %s %s"
830 CRLF, dirp->modestring, dirp->nlink,
831 dirp->username, dirp->groupname,
832 dirp->size, dirp->datestring, dirp->name);
833 else
834 buf = apr_psprintf(r->pool, "%s" CRLF, dirp->name);
835
836 nbytes = strlen(buf);
837 rv = apr_brigade_write(bb, ap_filter_flush, cdata->output_filters,
838 buf, nbytes);
839 fc->traffic += nbytes;
840 }
841
842 if (is_list && decend) {
843 for (dirp = direntry; dirp; dirp = dirp->next) {
844 if (dirp->modestring[0] == 'd' && dirp->child)
845 {
846 ftp_direntry *decend;
847
848 /* Iterate through the sub directory */
849 buf = apr_psprintf(r->pool, CRLF "%s:" CRLF, dirp->name);
850 nbytes = strlen(buf);
851 rv = apr_brigade_write(bb, ap_filter_flush,
852 cdata->output_filters, buf, nbytes);
853 fc->traffic += nbytes;
854
855 buf = apr_pstrcat(r->pool, "total 0" CRLF, NULL);
856 nbytes = strlen(buf);
857 rv = apr_brigade_write(bb, ap_filter_flush,
858 cdata->output_filters, buf, nbytes);
859 fc->traffic += nbytes;
860
861 for (decend = dirp->child; decend; decend = decend->next)
862 {
863 for (test = dirp->name; (sl = ap_strchr_c(test, '/'));
864 test = sl + 1)
865 /* noop */ ;
866
867 if (!strcmp(".", test)) {
868 continue;
869 }
870
871 buf = apr_psprintf(r->pool, "%10s%5d %-8s %-8s%9"
872 APR_OFF_T_FMT " %s %s" CRLF,
873 decend->modestring, decend->nlink,
874 decend->username, decend->groupname,
875 decend->size, decend->datestring,
876 decend->name);
877 nbytes = strlen(buf);
878 rv = apr_brigade_write(bb, ap_filter_flush,
879 cdata->output_filters, buf,
880 nbytes);
881 fc->traffic += nbytes;
882 }
883 }
884 }
885 }
886
887 /* If the brigade is empty, just send an eos */
888 if (APR_BRIGADE_EMPTY(bb)) {
889 b = apr_bucket_eos_create(c->bucket_alloc);
890 APR_BRIGADE_INSERT_TAIL(bb, b);
891 }
892
893 /* Flush the brigade down the filter chain */
894 b = apr_bucket_flush_create(cdata->bucket_alloc);
895 APR_BRIGADE_INSERT_TAIL(bb, b);
896 ap_pass_brigade(cdata->output_filters, bb);
897
898 ap_lingering_close(cdata);
899 fc->datasock = NULL;
900 fc->transfers++;
901
902 if (cdata->aborted)
903 return FTP_REPLY_TRANSFER_ABORTED;
904 else
905 return FTP_REPLY_DATA_CLOSE;
906 }
907
908 static int ftp_cmd_list(request_rec *r, const char *arg)
909 {
910 return common_list(r, arg, 1);
911 }
912
913 static int ftp_cmd_nlst(request_rec *r, const char *arg)
914 {
915 return common_list(r, arg, 0);
916 }
917
918 static int ftp_cmd_mdtm(request_rec *r, const char *arg)
919 {
920 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
921 int res;
922 request_rec *rr;
923 apr_time_exp_t t;
924
925 if ((res = ftp_set_uri(r, arg))) {
926 return res;
927 }
928
929 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
930
931 if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
932 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
933 ftp_escape_control_text(r->parsed_uri.path,
934 r->pool));
935 ap_destroy_sub_req(rr);
936 return FTP_REPLY_FILE_NOT_FOUND;
937 }
938 apr_time_exp_lt(&t, rr->finfo.mtime);
939 fc->response_notes = apr_psprintf(r->pool,
940 "%04d%02d%02d%02d%02d%02d",
941 1900 + t.tm_year, t.tm_mon + 1,
942 t.tm_mday, t.tm_hour, t.tm_min,
943 t.tm_sec);
944 ap_destroy_sub_req(rr);
945 return FTP_REPLY_FILE_STATUS;
946 }
947
948 static int ftp_cmd_mkd(request_rec *r, const char *arg)
949 {
950 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
951 apr_status_t rv;
952 int res;
953 request_rec *rr;
954 ftp_dir_config *dconf;
955 apr_fileperms_t dirperms;
956
957 if ((res = ftp_set_uri(r, arg))) {
958 return res;
959 }
960
961 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
962
963 if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
964 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
965 ftp_escape_control_text(r->parsed_uri.path,
966 r->pool));
967 ap_destroy_sub_req(rr);
968 return FTP_REPLY_FILE_NOT_FOUND;
969 }
970
971 dconf = ftp_get_module_config(rr->per_dir_config);
972 dirperms = dconf->dirperms;
973 ap_destroy_sub_req(rr);
974
975 if (dirperms == APR_OS_DEFAULT)
976 dirperms = FTP_DEFAULT_UMASK;
977
978 /*
979 * In the config phase, ->fileperms was a negative umask. for operation,
980 * exchange this with a positive protections to pass to the apr_dir_make
981 * protection flag.
982 */
983 dirperms = (APR_UREAD | APR_UWRITE | APR_UEXECUTE |
984 APR_GREAD | APR_GWRITE | APR_GEXECUTE |
985 APR_WREAD | APR_WWRITE | APR_WEXECUTE)
986 & ~dirperms;
987 rv = apr_dir_make(r->filename, dirperms, r->pool);
988
989 if (rv != APR_SUCCESS) {
990 char error_str[120];
991 char *err = apr_strerror(rv, error_str, sizeof(error_str));
992 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
993 ftp_escape_control_text(err, r->pool));
994 return FTP_REPLY_FILE_NOT_FOUND;
995 }
996 else {
997 apr_file_perms_set(r->filename, dirperms);
998 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_DIR_CREAT,
999 ftp_escape_control_text(r->parsed_uri.path,
1000 r->pool));
1001 return FTP_REPLY_PATH_CREATED;
1002 }
1003 }
1004
1005 static int ftp_cmd_mode(request_rec *r, const char *arg)
1006 {
1007 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
1008
1009 if (*arg && !arg[1]) {
1010 switch (toupper(*arg)) {
1011 case 'S':
1012 fc->response_notes = "Mode set to S";
1013 return FTP_REPLY_COMMAND_OK;
1014 }
1015 }
1016 fc->response_notes = apr_psprintf(r->pool, "Mode %s not implemented",
1017 ftp_escape_control_text(arg, r->pool));
1018 return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
1019 }
1020
1021 static int ftp_cmd_noop(request_rec *r, const char *arg)
1022 {
1023 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
1024
1025 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_SUCCESS, r->method);
1026 return FTP_REPLY_COMMAND_OK;
1027 }
1028
1029 static int ftp_cmd_pass(request_rec *r, const char *arg)
1030 {
1031 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
1032 conn_rec *c = r->connection;
1033 ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
1034 core_server_config *ftpcore = NULL;
1035 char *userdir = NULL;
1036 ftp_dir_config *dconf;
1037 request_rec *rr;
1038 char *userpass;
1039 server_rec *ftpserver;
1040 apr_status_t rv;
1041 char *tmppath;
1042
1043 if (fc->user == ftp_unknown_username) {
1044 return FTP_REPLY_BAD_SEQUENCE;
1045 }
1046
1047 apr_cpystrn(fc->cwd, "/", APR_PATH_MAX + 1);
1048
1049 /*
1050 * By this point we have already extracted the arguments, so rewrite the
1051 * original request to not include the password
1052 */
1053 r->the_request = apr_pstrdup(r->pool, "PASS xxx");
1054
1055 userpass = apr_psprintf(r->pool, "%s:%s", fc->user, arg);
1056 fc->authorization = apr_psprintf(fc->login_pool, "Basic %s",
1057 ap_pbase64encode(r->pool, userpass));
1058
1059 /*
1060 * This is normally set in the dispatcher, but since we just read the
1061 * password, we need to reset the auth header and r->user
1062 */
1063 ftp_set_authorization(r);
1064
1065 /* Prepare to overwrite ap_document_root */
1066 if (fsc->jailuser || fsc->docrootenv) {
1067 int i;
1068 ap_conf_vector_t *conf_vector;
1069 module *modp;
1070
1071 ftpserver = apr_pcalloc(fc->login_pool, sizeof(*ftpserver));
1072 memcpy(ftpserver, r->server, sizeof(*ftpserver));
1073
1074 /*
1075 * We need to count the number of modules in order to know how big an
1076 * array to allocate. There is a variable in the server for this,
1077 * but it isn't exported, so we are stuck doing this. I guess this
1078 * could be a static variable that we compute once, but it shouldn't
1079 * be that expensive to do it this way.
1080 */
1081 for (i = 0; ap_loaded_modules[i]; i++);
1082 conf_vector = apr_pcalloc(fc->login_pool, sizeof(void *) * i);
1083
1084 for (modp = ap_top_module; modp; modp = modp->next) {
1085 /*
1086 * This is a hack. Basically, to keep the user in thier own
1087 * directory, we are re-writing the DocumentRoot when the user
1088 * logs in. To do this, we copy the entire server_rec for this
1089 * Vhost, and then we copy the whole module_config for that
1090 * server. For the core module, we don't just copy the pointer,
1091 * instead we create a new core_server_config, copy the old one
1092 * to it, and change the docroot. Yuck.
1093 */
1094 if (modp == &core_module) {
1095 core_server_config *core;
1096
1097 ftpcore = apr_pcalloc(fc->login_pool, sizeof(*ftpcore));
1098 core = ap_get_module_config(r->server->module_config, modp);
1099 *ftpcore = *core;
1100 ap_set_module_config(conf_vector, modp, ftpcore);
1101 }
1102 else {
1103 ap_set_module_config(conf_vector, modp,
1104 ap_get_module_config(r->server->module_config, modp));
1105 }
1106 }
1107 ftpserver->module_config = (ap_conf_vector_t *) conf_vector;
1108 fc->orig_server = r->server = ftpserver;
1109 }
1110
1111 if (fsc->docrootenv) {
1112 const char *docroot;
1113
1114 /*
1115 * Invoke a request that forces auth to occur, Then check for an
1116 * fsc->docrootenv that translates to a valid envvar.
1117 */
1118 rr = ap_sub_req_method_uri(r->method, "/", r, NULL);
1119 docroot = apr_table_get(rr->subprocess_env, fsc->docrootenv);
1120
1121 if (!docroot || !*docroot) {
1122 ap_log_perror(APLOG_MARK, APLOG_WARNING | APLOG_NOERRNO, 0,
1123 r->pool,
1124 "Warning: Document Root variable from FTPDocRootEnv [%s] "
1125 "was empty or not found, using default DocumentRoot",
1126 fsc->docrootenv);
1127 }
1128 else if (((rv = apr_filepath_merge(&tmppath, NULL, docroot,
1129 APR_FILEPATH_TRUENAME |
1130 APR_FILEPATH_NOTRELATIVE,
1131 fc->login_pool)) != APR_SUCCESS)
1132 || (rv = APR_ENOTDIR, !ap_is_directory(r->pool, tmppath))) {
1133 ap_log_perror(APLOG_MARK, APLOG_WARNING, rv,
1134 r->pool,
1135 "Warning: Document Root [%s] from FTPDocRootEnv [%s] "
1136 "is invalid or does not exist, using default DocumentRoot",
1137 docroot, fsc->docrootenv);
1138 }
1139 else {
1140 ftpcore->ap_document_root = tmppath;
1141 }
1142
1143 ap_destroy_sub_req(rr);
1144 }
1145
1146 /* Check for a configured home directory */
1147 if (fsc->homedir) {
1148
1149 apr_filepath_merge(&userdir, fsc->homedir, fc->user, 0, r->pool);
1150
1151 if (userdir) {
1152
1153 rr = ap_sub_req_method_uri(r->method, userdir, r, NULL);
1154
1155 if (rr->finfo.filetype == APR_DIR &&
1156 (rr->status == HTTP_OK ||
1157 rr->status == HTTP_MOVED_PERMANENTLY)) {
1158 tmppath = apr_pstrcat(r->pool, userdir, "/", NULL);
1159 apr_cpystrn(fc->cwd, tmppath, APR_PATH_MAX + 1);
1160 }
1161 else if (rr->status == HTTP_OK &&
1162 rr->finfo.filetype == APR_NOFILE) {
1163
1164 /* See if we should create the directory automatically */
1165 if (fsc->options & FTP_OPT_CREATEHOMEDIRS) {
1166
1167 if (rr->finfo.filetype == APR_NOFILE && rr->filename
1168 && ap_is_directory(rr->pool,
1169 ap_make_dirstr_parent(rr->pool, rr->filename))) {
1170
1171 rv = apr_dir_make(rr->filename, APR_OS_DEFAULT,
1172 r->pool);
1173
1174 if (rv != APR_SUCCESS) {
1175 ap_log_error(APLOG_MARK, APLOG_ERR, rv,
1176 r->server, "Couldn't "
1177 "create home directory (%s) for "
1178 "user %s",
1179 rr->filename, fc->user);
1180 }
1181 else {
1182 /* The new directory was created. */
1183 tmppath = apr_pstrcat(r->pool, userdir, "/", NULL);
1184 apr_cpystrn(fc->cwd, tmppath, APR_PATH_MAX + 1);
1185 ap_log_error(APLOG_MARK, APLOG_INFO | APLOG_NOERRNO,
1186 0, r->server, "Created home "
1187 "directory (%s) for user %s",
1188 rr->filename, fc->user);
1189 }
1190 }
1191 else {
1192 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO,
1193 0, r->server,
1194 "Home directory for user %s not "
1195 "found. Using \"/\"", fc->user);
1196
1197 }
1198 }
1199 else {
1200 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0,
1201 r->server, "Home directory for "
1202 "user %s not found. Using \"/\"",
1203 fc->user);
1204 }
1205 }
1206 else {
1207
1208 ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0,
1209 r->server, "Permission denied entering"
1210 " home directory for user %s. Using \"/\"",
1211 fc->user);
1212 }
1213
1214 ap_destroy_sub_req(rr);
1215 }
1216 }
1217
1218 if (fsc->jailuser) {
1219 /*
1220 * If we didn't set cwd the Jailed user can't be allowed to log in.
1221 * The cwd was set to userdir if it existed or was created above, but
1222 * we must ignore the trailing slash.
1223 */
1224 if (!userdir || (strncmp(userdir, fc->cwd, strlen(userdir)) != 0)) {
1225 goto pass_try_again;
1226 }
1227 ftpcore->ap_document_root = apr_pstrcat(fc->login_pool,
1228 ftpcore->ap_document_root, userdir, NULL);
1229 apr_cpystrn(fc->cwd, "/", APR_PATH_MAX + 1);
1230 }
1231
1232 /* Our document_root and cwd are now constructed for the user... */
1233 rr = ap_sub_req_method_uri(r->method, fc->cwd, r, NULL);
1234
1235 dconf = ftp_get_module_config(rr->per_dir_config);
1236
1237 if (rr->status == HTTP_OK) {
1238 /*
1239 * We now must iterate over the entire scoreboard checking to see if
1240 * we have another server that can handle an incoming request. If we
1241 * don't see another server in the ready state, that means we are the
1242 * last available server, and must send the client a message that the
1243 * server is full.
1244 *
1245 * For those wondering, there is a race condition here that could cause
1246 * a client to be put in the accept queue, but we should be able to
1247 * recover from this once a client disconnects.
1248 *
1249 * This has a few side effects: - We can really only service Max - 1 FTP
1250 * sessions concurrently.
1251 *
1252 * - If a hybid server is heavily loaded, such that all servers are busy
1253 * serving HTTP traffic, it is possible to starve FTP requests, since
1254 * when we check the scoreboard, all servers will be busy.
1255 */
1256 ftp_loginlimit_t limrv;
1257
1258 if ((fsc->options & FTP_OPT_CHECKMAXCLIENTS) &&
1259 ftp_check_maxclients(r)) {
1260 ap_destroy_sub_req(rr);
1261 fc->response_notes = FTP_MSG_SESSIONLIMIT;
1262 fc->close_connection = 1;
1263
1264 return FTP_REPLY_SERVICE_NOT_AVAILABLE;
1265 }
1266
1267 limrv = ftp_limitlogin_check(fc->user, r);
1268 /* I love switch */
1269 switch (limrv) {
1270 case FTP_LIMIT_HIT_PERSERVER:
1271 ap_destroy_sub_req(rr);
1272 ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, 0,
1273 r->server, "FTP per server login limit hit for %s",
1274 fc->user);
1275
1276 fc->response_notes = FTP_MSG_SESSIONLIMIT;
1277 fc->close_connection = 1;
1278 return FTP_REPLY_SERVICE_NOT_AVAILABLE;
1279
1280 case FTP_LIMIT_HIT_PERUSER:
1281 ap_destroy_sub_req(rr);
1282 ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, 0,
1283 r->server, "FTP per user login limit hit for %s",
1284 fc->user);
1285
1286 fc->response_notes = FTP_MSG_SESSIONLIMIT;
1287 fc->close_connection = 1;
1288 return FTP_REPLY_SERVICE_NOT_AVAILABLE;
1289
1290 case FTP_LIMIT_HIT_PERIP:
1291 ap_destroy_sub_req(rr);
1292 ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, 0,
1293 r->server, "FTP per IP login limit hit for %s",
1294 fc->user);
1295
1296 fc->response_notes = FTP_MSG_SESSIONLIMIT;
1297 fc->close_connection = 1;
1298 return FTP_REPLY_SERVICE_NOT_AVAILABLE;
1299
1300 case FTP_LIMIT_ERROR:
1301 ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, 0,
1302 r->server, "Error accessing DB for user %s; no login limit in effect",
1303 fc->user);
1304 break;
1305
1306 case FTP_LIMIT_OK:
1307 default:
1308 break;
1309 }
1310
1311 ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_INFO, 0,
1312 r->server, "FTP LOGIN FROM %s as %s",
1313 c->remote_ip, fc->user);
1314
1315 if (dconf->readme) {
1316 if (dconf->readme_isfile) {
1317 ftp_show_file(c->output_filters, r->pool,
1318 FTP_REPLY_USER_LOGGED_IN, fc,
1319 dconf->readme);
1320 }
1321 else {
1322 char outbuf[BUFSIZ];
1323
1324 ftp_message_generate(fc, dconf->readme, outbuf,
1325 sizeof(outbuf));
1326 ftp_reply(fc, c->output_filters, r->pool,
1327 FTP_REPLY_USER_LOGGED_IN, 1, outbuf);
1328 }
1329 }
1330
1331 fc->logged_in = 1;
1332 ap_destroy_sub_req(rr);
1333 return FTP_REPLY_USER_LOGGED_IN;
1334 }
1335
1336 ap_destroy_sub_req(rr);
1337
1338 /*
1339 * Here we return the generic FTP_REPLY_NOT_LOGGED_IN error (530), but it
1340 * may have been because of a varity of reasons. If the return status is
1341 * 401 HTTP_UNAUTHORIZED, then the username/password combination was
1342 * incorrect. If we get a HTTP_FORBIDDEN, it is likely that the access
1343 * checkers failed because the location was configured with 'deny from
1344 * all'.
1345 */
1346 if (strcmp(fc->user, "anonymous") == 0) {
1347 ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0,
1348 r->server,
1349 "ANONYMOUS FTP LOGIN REFUSED FROM %s",
1350 c->remote_ip);
1351 }
1352 else {
1353 ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0,
1354 r->server, "FTP LOGIN REFUSED FROM %s, %s",
1355 c->remote_ip, fc->user);
1356 }
1357 pass_try_again:
1358 if (++fc->login_attempts == fsc->max_login_attempts) {
1359 ap_log_error(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0,
1360 r->server, "repeated login failures from %s",
1361 c->remote_ip);
1362
1363 fc->response_notes = "Maximum login attempts reached, closing connection.";
1364 fc->close_connection = 1;
1365
1366 return FTP_REPLY_SERVICE_NOT_AVAILABLE;
1367 }
1368
1369 /*
1370 * Sleep one second for every failed login attempt. This is a common
1371 * practice among FTP servers to prevent DOS attacks
1372 */
1373 apr_sleep(fc->login_attempts * APR_USEC_PER_SEC);
1374
1375 fc->user = ftp_unknown_username;
1376 fc->authorization = NULL;
1377 fc->response_notes = "Please log in with USER and PASS";
1378 return FTP_REPLY_NOT_LOGGED_IN;
1379 }
1380
1381 static int init_pasv_socket(request_rec *r, int bindfamily,
1382 const char *bindaddr)
1383 {
1384 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
1385 ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
1386 apr_sockaddr_t *sa;
1387 apr_socket_t *s;
1388 apr_status_t rv;
1389 apr_port_t port;
1390 int tries = 0;
1391
1392 rv = apr_sockaddr_info_get(&sa, bindaddr, bindfamily, 0, 0, fc->data_pool);
1393
1394 if (!sa || rv) {
1395 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1396 "Couldn't resolve local socket address %s (%d)"
1397 " (ftp, apr or socket stack bug?)",
1398 bindaddr, bindfamily);
1399 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1400 }
1401
1402 #if APR_MAJOR_VERSION < 1
1403 rv = apr_socket_create_ex(&s, sa->family, SOCK_STREAM,
1404 APR_PROTO_TCP, fc->data_pool);
1405 #else
1406 rv = apr_socket_create(&s, sa->family, SOCK_STREAM,
1407 APR_PROTO_TCP, fc->data_pool);
1408 #endif
1409 if (rv != APR_SUCCESS) {
1410 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
1411 "Couldn't create passive socket");
1412 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1413 }
1414
1415 rv = apr_socket_opt_set(s, APR_SO_LINGER,
1416 APR_MAX_SECS_TO_LINGER);
1417 if (rv != APR_SUCCESS) {
1418 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
1419 "Couldn't set APR_SO_LINGER socket option");
1420 apr_socket_close(s);
1421 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1422 }
1423
1424 rv = apr_socket_opt_set(s, APR_SO_REUSEADDR, 1);
1425 if (rv != APR_SUCCESS && rv != APR_ENOTIMPL) {
1426 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
1427 "Couldn't set APR_SO_REUSEADDR socket option");
1428 apr_socket_close(s);
1429 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1430 }
1431
1432 /*
1433 * XXX: should pick an arbitrary port within the acceptable range, but
1434 * dodge the throttle timer on the first reloop (init tries = -1?)
1435 */
1436 port = fsc->pasv_min;
1437 while (1) {
1438 /* Magic here when port == 0, accepts an ephemeral port assignment */
1439 sa->port = port;
1440 sa->sa.sin.sin_port = htons(port);
1441 rv = apr_socket_bind(s, sa);
1442 if (rv == APR_SUCCESS) {
1443 break;
1444 }
1445
1446 if (rv != FTP_APR_EADDRINUSE || tries >= FTP_MAX_TRIES) {
1447 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
1448 "Couldn't bind to passive socket");
1449 apr_socket_close(s);
1450 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1451 }
1452
1453 /*
1454 * If we are using a range, increment the port, and continue. If we
1455 * have reached the top of the range start over at the beginning
1456 */
1457 if (port != 0) {
1458 if (port < fsc->pasv_max) {
1459 ++port;
1460 continue;
1461 }
1462 port = fsc->pasv_min;
1463 /* Fall through into throttle */
1464 }
1465
1466 /*
1467 * Under high load we may have many sockets in TIME_WAIT, causing
1468 * bind to fail with EADDRINUSE. In these conditions we throttle the
1469 * system by sleeping before we attempt to bind again within our
1470 * acceptable port range
1471 */
1472 ++tries;
1473 ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
1474 "Couldn't find port within range for passive "
1475 "connection. Restarting at %d (retry %d)", port, tries);
1476 apr_sleep(tries * APR_USEC_PER_SEC);
1477 }
1478
1479 rv = apr_socket_listen(s, 1);
1480 if (rv != APR_SUCCESS) {
1481 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
1482 "Couldn't listen on passive socket");
1483 apr_socket_close(s);
1484 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1485 }
1486
1487 fc->csock = s;
1488 fc->passive_created = apr_time_now();
1489 return 0;
1490 }
1491
1492
1493 static int ftp_cmd_epsv(request_rec *r, const char *arg)
1494 {
1495 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
1496 conn_rec *c = r->connection;
1497 ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
1498 int res;
1499 apr_sockaddr_t *sa;
1500 const char *addr;
1501 int family = 0;
1502
1503 if (strcasecmp(arg, "ALL") == 0) {
1504 /* A contract to never respond to other data connection methods */
1505 fc->all_epsv = 1;
1506 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_SUCCESS, r->method);
1507 return FTP_REPLY_COMMAND_OK;
1508 }
1509
1510 ftp_reset_dataconn(fc);
1511
1512 /*
1513 * Why don't pasv_bindaddr/bindfamily appear below? These directives
1514 * provide an override which the client is informed of, while EPSV only
1515 * informs the client of the port to use, not a family, and never an
1516 * address. FTPEPSVIgnoreFamily should offer sufficient customization.
1517 */
1518 if (!*arg || ((arg[0] == '1' || arg[0] == '2') && !arg[1]
1519 && fsc->epsv_ignore_family)) {
1520 #if APR_HAVE_IPV6
1521 if (c->local_addr->family == AF_INET6 &&
1522 IN6_IS_ADDR_V4MAPPED((struct in6_addr *)
1523 c->local_addr->ipaddr_ptr)) {
1524 /* httpd assures us local_ip is in ipv4 notation for mapped addrs */
1525 addr = c->local_ip;
1526 family = APR_INET;
1527 }
1528 else
1529 #endif
1530 {
1531 addr = c->local_ip;
1532 family = c->local_addr->family;
1533 }
1534 }
1535 else if (arg[0] == '1' && !arg[1]) {
1536 if (c->local_addr->family == AF_INET
1537 #if APR_HAVE_IPV6
1538 || (c->local_addr->family == AF_INET6 &&
1539 IN6_IS_ADDR_V4MAPPED((struct in6_addr *)
1540 c->local_addr->ipaddr_ptr))
1541 #endif
1542 ) {
1543 /* httpd assures us local_ip is in ipv4 notation for mapped addrs */
1544 addr = c->local_ip;
1545 family = APR_INET;
1546 }
1547 else {
1548 return FTP_REPLY_BAD_PROTOCOL;
1549 }
1550 }
1551 #if APR_HAVE_IPV6
1552 else if (arg[0] == '2' && !arg[1]) {
1553 family = AF_INET6;
1554 if (c->local_addr->family == AF_INET6 &&
1555 IN6_IS_ADDR_V4MAPPED((struct in6_addr *)
1556 c->local_addr->ipaddr_ptr)) {
1557 /* httpd assures us local_ip is in ipv4 notation for mapped addrs */
1558 addr = c->local_ip;
1559 family = APR_INET;
1560 }
1561 else if (c->local_addr->family == AF_INET6) {
1562 addr = c->local_ip;
1563 family = AF_INET6;
1564 }
1565 else {
1566 return FTP_REPLY_BAD_PROTOCOL;
1567 }
1568 }
1569 #endif
1570 else {
1571 return FTP_REPLY_BAD_PROTOCOL;
1572 }
1573
1574 if ((res = init_pasv_socket(r, family, addr))) {
1575 return res;
1576 }
1577
1578 apr_socket_addr_get(&sa, APR_LOCAL, fc->csock);
1579 fc->response_notes = apr_psprintf(r->pool,
1580 "Entering Extended Passive Mode (|||%u|)",
1581 sa->port);
1582
1583 return FTP_REPLY_EXTENDED_PASSIVE_MODE;
1584 }
1585
1586
1587 static int ftp_cmd_pasv(request_rec *r, const char *arg)
1588 {
1589 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
1590 conn_rec *c = r->connection;
1591 ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
1592 int res;
1593 apr_sockaddr_t *sa;
1594 apr_socket_t *s;
1595 char *report_addr, *period;
1596 apr_port_t port;
1597 short high, low;
1598 const char *addr;
1599 int family;
1600
1601 ftp_reset_dataconn(fc);
1602
1603 if (fc->all_epsv) {
1604 fc->response_notes = "Restricted by EPSV ALL";
1605 return FTP_REPLY_BAD_SEQUENCE;
1606 }
1607
1608 if (fsc->pasv_bindaddr) {
1609 addr = fsc->pasv_bindaddr;
1610 family = fsc->pasv_bindfamily;
1611 }
1612 #if APR_HAVE_IPV6
1613 else if (c->local_addr->family == AF_INET6 &&
1614 IN6_IS_ADDR_V4MAPPED((struct in6_addr *) c->local_addr->ipaddr_ptr)) {
1615 /* httpd assures us local_ip is in ipv4 notation for mapped addrs */
1616 addr = c->local_ip;
1617 family = APR_INET;
1618 }
1619 #endif
1620 else {
1621 addr = c->local_ip;
1622 family = c->local_addr->family;
1623 }
1624
1625 if ((res = init_pasv_socket(r, family, addr))) {
1626 return res;
1627 }
1628 s = fc->csock;
1629
1630 apr_socket_addr_get(&sa, APR_LOCAL, s);
1631
1632 /*
1633 * pasv_addr is an ADVERTISED address, not related to the true address.
1634 * It should always be evaluated first for the benefit of servers hiding
1635 * behind load balancers, vpn's, nat, etc.
1636 */
1637 if (fsc->pasv_addr) {
1638 report_addr = apr_pstrdup(r->pool, fsc->pasv_addr);
1639 }
1640 else if (fsc->pasv_bindaddr && (fsc->pasv_bindfamily == APR_INET)) {
1641 report_addr = apr_pstrdup(r->pool, fsc->pasv_bindaddr);
1642 }
1643 else if ((c->local_addr->family == AF_INET)
1644 #if APR_HAVE_IPV6
1645 || (c->local_addr->family == AF_INET6
1646 && IN6_IS_ADDR_V4MAPPED((struct in6_addr *) c->local_addr->ipaddr_ptr))
1647 #endif
1648 ) {
1649 /* httpd assures us local_ip is in ipv4 notation for mapped addrs */
1650 report_addr = apr_pstrdup(r->pool, c->local_ip);
1651 }
1652 else {
1653 /*
1654 * a bogus answer, which will not be translated below, wherein
1655 * clients can choose to connect back to the original, same address.
1656 * Suggested as an early solution at http://cr.yp.to/ftp/retr.html
1657 */
1658 report_addr = "127,555,555,555";
1659 }
1660
1661 /* Translate x.x.x.x to x,x,x,x */
1662 while ((period = strstr(report_addr, ".")) != NULL) {
1663 *period = ',';
1664 }
1665
1666 port = sa->port;
1667 high = ((port & 0xff00) >> 8);
1668 low = (port & 0x00ff);
1669 fc->response_notes = apr_psprintf(r->pool,
1670 "Entering Passive Mode (%s,%u,%u)",
1671 report_addr, high, low);
1672 return FTP_REPLY_PASSIVE_MODE;
1673 }
1674
1675 static int ftp_cmd_pbsz(request_rec *r, const char *arg)
1676 {
1677 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
1678 char *endp;
1679
1680 /*
1681 * Check if we have completed the security exchange. Note, some
1682 * clients still send PBSZ even with Implicit SSL. So we
1683 * allow this misbehavior...
1684 */
1685 if (fc->auth == FTP_AUTH_NONE) {
1686 return FTP_REPLY_BAD_SEQUENCE;
1687 }
1688
1689 fc->pbsz = strtol(arg, &endp, 10);
1690 /*
1691 * Return 501 if we were unable to parse the argument or if there was a
1692 * possibility of an overflow
1693 */
1694 if (((*arg == '\0') || (*endp != '\0')) || fc->pbsz < 0
1695 || fc->pbsz == LONG_MAX) {
1696 fc->response_notes = "Could not parse PBSZ argument";
1697 return FTP_REPLY_SYNTAX_ERROR;
1698 }
1699
1700 fc->response_notes = apr_psprintf(r->pool, "PBSZ Command OK. "
1701 "Protection buffer size set to %d",
1702 fc->pbsz);
1703 return FTP_REPLY_COMMAND_OK;
1704 }
1705
1706 static int get_outbound_port(apr_sockaddr_t **sa_rv, request_rec *r,
1707 apr_int32_t family)
1708 {
1709 conn_rec *c = r->connection;
1710 ftp_connection *fc = ftp_get_module_config(c->conn_config);
1711 ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
1712 apr_sockaddr_t *sa;
1713 apr_socket_t *s;
1714 apr_status_t rv;
1715 apr_port_t local_port;
1716
1717 if (fsc->active_min == -1) {
1718 local_port = 0;
1719 }
1720 else if (fsc->active_max == fsc->active_min) {
1721 local_port = fsc->active_min;
1722 }
1723 else {
1724 local_port = fsc->active_min +
1725 (apr_port_t) (rand() % (fsc->active_max -
1726 fsc->active_min + 1));
1727 }
1728
1729 if (c->local_addr->family == family) {
1730 /* Shortcut; duplicate the contents */
1731 sa = apr_palloc(fc->data_pool, sizeof(apr_sockaddr_t));
1732 memcpy(sa, c->local_addr, sizeof(apr_sockaddr_t));
1733 sa->next = NULL;
1734 if (sa->family == APR_INET)
1735 sa->ipaddr_ptr = &(sa->sa.sin.sin_addr);
1736 #if APR_HAVE_IPV6
1737 else if (sa->family == APR_INET6)
1738 sa->ipaddr_ptr = &(sa->sa.sin6.sin6_addr);
1739 #endif
1740 sa->sa.sin.sin_port = htons(local_port);
1741 }
1742 else {
1743 /* Long way around, create a fresh sa, attempt to use
1744 * the original port, before falling back on the nul adapter
1745 * TODO: this could use a config option to specify the
1746 * preferred interface/local address
1747 */
1748 rv = apr_sockaddr_info_get(&sa, c->local_ip, family,
1749 local_port, 0, fc->data_pool);
1750 if (!sa || rv) {
1751 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
1752 "Couldn't resolve explicit local socket address %s "
1753 "(apr or socket stack bug?) Retrying", c->local_ip);
1754 rv = apr_sockaddr_info_get(&sa, NULL, APR_INET,
1755 local_port, 0, fc->data_pool);
1756 }
1757
1758 if (!sa || rv) {
1759 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1760 "Couldn't resolve emphemeral local socket address "
1761 "(apr or socket stack bug?) Giving up");
1762 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1763 }
1764 }
1765
1766 #ifdef HAVE_FTP_LOWPORTD
1767 if ((local_port > 0) && (local_port < 1024)) {
1768 /*
1769 * Here's the case of low numbered port creation; we have spun off
1770 * a worker to serve socket fd's through a unix domain socket via the
1771 * ftp_request_lowport client.
1772 */
1773 rv = ftp_request_lowport(&s, r, sa, fc->data_pool);
1774
1775 if (rv != APR_SUCCESS) {
1776 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1777 "Request socket failed from FTP low port daemon");
1778 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1779 }
1780 }
1781 else
1782 #endif
1783 {
1784 #if APR_MAJOR_VERSION < 1
1785 rv = apr_socket_create_ex(&s, family, SOCK_STREAM, APR_PROTO_TCP,
1786 fc->data_pool);
1787 #else
1788 rv = apr_socket_create(&s, family, SOCK_STREAM, APR_PROTO_TCP,
1789 fc->data_pool);
1790 #endif
1791
1792 if (rv != APR_SUCCESS) {
1793 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1794 "Couldn't create socket");
1795 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1796 }
1797
1798 apr_socket_opt_set(s, APR_SO_REUSEADDR, 1);
1799
1800 rv = apr_socket_bind(s, sa);
1801
1802 if (rv != APR_SUCCESS) {
1803 #ifdef EACCES
1804 if (sa->port < 1024 && rv == EACCES)
1805 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1806 "Couldn't bind to low numbered port (<1024). "
1807 "See FTPActiveRange directive");
1808 else
1809 #endif
1810 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1811 "Couldn't bind to socket");
1812 apr_socket_close(s);
1813 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1814 }
1815 }
1816
1817 *sa_rv = sa;
1818 fc->csock = s;
1819 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_SUCCESS, r->method);
1820 return FTP_REPLY_COMMAND_OK;
1821 }
1822
1823 static int ftp_cmd_eprt(request_rec *r, const char *arg)
1824 {
1825 conn_rec *c = r->connection;
1826 ftp_connection *fc = ftp_get_module_config(c->conn_config);
1827 ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
1828 apr_sockaddr_t *sa;
1829 apr_status_t rv;
1830 char *arg_tok, *ip_addr;
1831 apr_int32_t family;
1832 apr_port_t port;
1833 int res;
1834
1835 ftp_reset_dataconn(fc);
1836
1837 if (fc->all_epsv) {
1838 fc->response_notes = "Restricted by EPSV ALL";
1839 return FTP_REPLY_BAD_SEQUENCE;
1840 }
1841
1842 arg_tok = apr_pstrdup(fc->data_pool, arg);
1843 if ((res = ftp_eprt_decode(&family, &ip_addr, &port, arg_tok))
1844 != FTP_REPLY_COMMAND_OK) {
1845 fc->response_notes = "Invalid EPRT request";
1846 return res;
1847 }
1848
1849 rv = apr_sockaddr_info_get(&fc->clientsa, ip_addr, family,
1850 port, 0, fc->data_pool);
1851 if (!fc->clientsa || rv) {
1852 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
1853 "Unable to resolve EPRT address of |%d|%s|%d|",
1854 #if APR_HAVE_IPV6
1855 (family == APR_INET6) ? 2 : 1, ip_addr, port);
1856 #else
1857 1, ip_addr, port);
1858 #endif
1859 fc->response_notes = "Invalid EPRT command, unable to resolve request";
1860 return FTP_REPLY_SYNTAX_ERROR;
1861 }
1862
1863 /*
1864 * Open data connection only if the EPRT connection is to the client's IP
1865 * address. All other EPRT connection requests are denied, unless
1866 * disabled using:
1867 *
1868 * FTPOptions AllowProxyPORT
1869 *
1870 * We must canonicalize the IP address first to compare it to our own idea
1871 * of the client's IP address.
1872 */
1873 if (!(fsc->options & FTP_OPT_ALLOWPROXYPORT)) {
1874 char *test_ip = "(unknown)";
1875 if (apr_sockaddr_ip_get(&test_ip, fc->clientsa) != APR_SUCCESS
1876 || (strcasecmp(test_ip, c->remote_ip) != 0)) {
1877 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
1878 "Rejected EPRT data connection request to %s "
1879 "(doesn't match the client IP %s and "
1880 "not configured to AllowProxyPORT)",
1881 test_ip, c->remote_ip);
1882 fc->response_notes = "Invalid EPRT command, proxy EPRT is"
1883 " not permitted";
1884 return FTP_REPLY_SYNTAX_ERROR;
1885 }
1886 }
1887
1888 return get_outbound_port(&sa, r, family);
1889 }
1890
1891 static int ftp_cmd_port(request_rec *r, const char *arg)
1892 {
1893 conn_rec *c = r->connection;
1894 ftp_connection *fc = ftp_get_module_config(c->conn_config);
1895 ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
1896 apr_sockaddr_t *sa;
1897 apr_status_t rv;
1898 apr_port_t port;
1899 char *ip_addr, tc;
1900 int res, val[6];
1901
1902 ftp_reset_dataconn(fc);
1903
1904 if (fc->all_epsv) {
1905 fc->response_notes = "Restricted by EPSV ALL";
1906 return FTP_REPLY_BAD_SEQUENCE;
1907 }
1908
1909 if ((res = sscanf(arg, "%d,%d,%d,%d,%d,%d%c", &val[0], &val[1], &val[2],
1910 &val[3], &val[4], &val[5], &tc)) != 6) {
1911 fc->response_notes = "Invalid PORT request";
1912 return FTP_REPLY_SYNTAX_ERROR;
1913 }
1914
1915 ip_addr = apr_psprintf(fc->data_pool, "%d.%d.%d.%d",
1916 val[0], val[1], val[2], val[3]);
1917
1918 port = ((val[4] << 8) + val[5]);
1919
1920 /*
1921 * Open data connection only if the PORT connection is to the client's IP
1922 * address. All other PORT connection requests are denied, unless
1923 * enabled using:
1924 *
1925 * FTPOptions AllowProxyPORT
1926 */
1927 if (!(fsc->options & FTP_OPT_ALLOWPROXYPORT)) {
1928 if (strcasecmp(ip_addr, c->remote_ip) != 0) {
1929 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
1930 "Rejected PORT data connection request to %s "
1931 "(doesn't match the client IP %s and "
1932 "not configured to AllowProxyPORT)",
1933 ip_addr, c->remote_ip);
1934 fc->response_notes = "Invalid PORT command, proxy PORT is"
1935 " not permitted";
1936 return FTP_REPLY_SYNTAX_ERROR;
1937 }
1938 }
1939
1940 rv = apr_sockaddr_info_get(&fc->clientsa, ip_addr, APR_INET,
1941 port, 0, fc->data_pool);
1942
1943 if (!fc->clientsa || rv) {
1944 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1945 "Couldn't resolve remote socket address %s"
1946 " (apr or socket stack bug?)", ip_addr);
1947 return FTP_REPLY_CANNOT_OPEN_DATACONN;
1948 }
1949
1950 return get_outbound_port(&sa, r, APR_INET);
1951 }
1952
1953 static int ftp_cmd_prot(request_rec *r, const char *arg)
1954 {
1955 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
1956
1957 /*
1958 * Return 503 if the user has not done a AUTH command yet. Although
1959 * RFC2228 and RFC4217 are very explicit that PBSZ must preceed PROT,
1960 * it's entirely worthless in the context of TLS, and not even worth
1961 * enforcing.
1962 */
1963 if (fc->auth == FTP_AUTH_NONE) {
1964 return FTP_REPLY_BAD_SEQUENCE;
1965 }
1966
1967 switch (*arg) {
1968 case 'C':
1969 fc->response_notes = "PROT Command OK. Using clear data channel";
1970 fc->prot = FTP_PROT_CLEAR;
1971 return FTP_REPLY_COMMAND_OK;
1972 case 'S':
1973 return FTP_REPLY_PROT_NOT_SUPPORTED;
1974 case 'E':
1975 return FTP_REPLY_PROT_NOT_SUPPORTED;
1976 case 'P':
1977 fc->response_notes = "PROT Command OK. Using private data channel";
1978 fc->prot = FTP_PROT_PRIVATE;
1979 return FTP_REPLY_COMMAND_OK;
1980 }
1981
1982 /* We don't understand */
1983 fc->response_notes = "PROT argument not understood.";
1984 return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
1985 }
1986
1987 static int ftp_cmd_pwd(request_rec *r, const char *arg)
1988 {
1989 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
1990
1991 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_DIR_CUR,
1992 ftp_escape_control_text(fc->cwd, r->pool));
1993 return FTP_REPLY_PATH_CREATED;
1994 }
1995
1996 static int ftp_cmd_quit(request_rec *r, const char *arg)
1997 {
1998 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
1999
2000 fc->close_connection = 1;
2001 return FTP_REPLY_CONTROL_CLOSE;
2002 }
2003
2004 static int ftp_cmd_rest(request_rec *r, const char *arg)
2005 {
2006 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
2007 char *endp;
2008
2009 #if APR_MAJOR_VERSION < 1
2010 fc->restart_point = apr_atoi64(arg);
2011 endp = (char *) arg + strspn(arg, "0123456789");
2012 if (
2013 #else
2014 apr_status_t rv = apr_strtoff(&(fc->restart_point), arg, &endp, 10);
2015 if ((rv != APR_SUCCESS) ||
2016 #endif
2017 (*arg == '\0') || (*endp != '\0') || (fc->restart_point < 0)) {
2018 fc->response_notes = "REST requires a non-negative integer value";
2019 return FTP_REPLY_SYNTAX_ERROR;
2020 }
2021
2022 fc->response_notes = apr_psprintf(r->pool, "Restarting at %" APR_OFF_T_FMT
2023 ". Send STORE or RETRIEVE to initiate "
2024 "transfer.", fc->restart_point);
2025 return FTP_REPLY_PENDING;
2026 }
2027
2028 static int ftp_cmd_retr(request_rec *r, const char *arg)
2029 {
2030 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
2031 request_rec *rr;
2032 conn_rec *c = r->connection;
2033 conn_rec *cdata;
2034 ap_filter_t *f, *rinput, *routput, *rinput_proto, *routput_proto;
2035 int res;
2036
2037 /* Put a note in the env table for logging */
2038 /*
2039 * This envar determines whether or not the command being
2040 * processed is one which causes a file to be uploaded or
2041 * downloaded ("transferred"). This allows a logfile to be
2042 * created of just transfers attempts. The success or failure
2043 * of the transfer is determined by the ftp_transfer_ok envar
2044 */
2045 apr_table_setn(r->subprocess_env, "do_transfer_log", "1");
2046
2047 if ((res = ftp_set_uri(r, arg))) {
2048 return res;
2049 }
2050
2051 /* If anything fails in the subrequest, simply return permission denied */
2052 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
2053 if (rr->status != HTTP_OK) {
2054 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
2055 ftp_escape_control_text(arg, r->pool));
2056 ap_destroy_sub_req(rr);
2057 return FTP_REPLY_FILE_NOT_FOUND;
2058 }
2059 ap_destroy_sub_req(rr);
2060
2061 ftp_reply(fc, c->output_filters, r->pool, FTP_REPLY_FILE_STATUS_OK, 0,
2062 apr_pstrcat(r->pool, "Opening ",
2063 (fc->type == TYPE_A) ? "ASCII" : "BINARY",
2064 " mode data connection for ",
2065 ftp_escape_control_text(arg, r->pool), NULL));
2066
2067 if (!(cdata = ftp_open_dataconn(r, 1))) {
2068 return FTP_REPLY_CANNOT_OPEN_DATACONN;
2069 }
2070
2071 /* Save the original filters */
2072 rinput = r->input_filters;
2073 rinput_proto = r->proto_input_filters;
2074 routput = r->output_filters;
2075 routput_proto = r->proto_output_filters;
2076
2077 r->proto_input_filters = cdata->input_filters;
2078 r->input_filters = r->proto_input_filters;
2079 r->proto_output_filters = cdata->output_filters;
2080 r->output_filters = r->proto_output_filters;
2081
2082 ap_add_input_filter_handle(ftp_input_filter_handle, NULL, r, r->connection);
2083
2084 r->connection = cdata;
2085
2086 /*
2087 * If we are sending a ASCII file, we need to run the CRLF filter.
2088 * Running the CRLF filter will result in a bunch of buckets, so we
2089 * should also add the COALESCE filter to put everything back into a
2090 * single bucket.
2091 */
2092 if (fc->type == TYPE_A) {
2093 fc->filter_mask += FTP_NEED_CRLF;
2094 }
2095
2096 if (fc->restart_point > 0) {
2097 fc->filter_mask += FTP_NEED_BYTERANGE;
2098 }
2099
2100 rr = ap_sub_req_method_uri("GET", r->uri, r, NULL);
2101 if (rr->status != HTTP_OK) {
2102 /*
2103 * Should never get here since we have already run this subrequest,
2104 * but better safe than sorry
2105 */
2106 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
2107 ftp_escape_control_text(arg, r->pool));
2108 res = FTP_REPLY_FILE_NOT_FOUND;
2109 goto clean_up;
2110 }
2111
2112 /* If this is a resume request, set the "Range: {start}-" header */
2113 if (fc->restart_point > 0) {
2114 apr_table_setn(rr->headers_in, "Range",
2115 apr_psprintf(r->pool, "bytes=%" APR_OFF_T_FMT "-",
2116 fc->restart_point));
2117
2118 /* Byterange requires that we are not assbackwards (HTTP/0.9) */
2119 rr->assbackwards = 0;
2120 }
2121
2122 /*
2123 * Filter manipulation. We remove the subrequest filter so that the EOS
2124 * buckets stay intact. We also add the content length filter so that
2125 * we can record the bytes_sent
2126 */
2127 for (f = rr->output_filters; f; f = f->next) {
2128 if (strcasecmp(f->frec->name, "SUBREQ_CORE") == 0) {
2129 ap_remove_output_filter(f);
2130 }
2131 }
2132
2133 /* Need content length rr->bytes_sent */
2134 ap_add_output_filter_handle(ftp_content_length_filter_handle, NULL,
2135 rr, rr->connection);
2136
2137 if ((res = ap_run_sub_req(rr)) != OK) {
2138 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
2139 ftp_escape_control_text(arg, r->pool));
2140 res = FTP_REPLY_FILE_NOT_FOUND;
2141 }
2142
2143 else {
2144 res = FTP_REPLY_DATA_CLOSE;
2145 }
2146
2147 fc->restart_point = 0;
2148 fc->traffic += rr->bytes_sent;
2149 fc->bytes += rr->bytes_sent;
2150 fc->files += 1;
2151 fc->transfers += 1;
2152
2153 /*
2154 * We use a subrequest here in an weird way, which causes some
2155 * information to be lost. Here we hack around that by setting values in
2156 * r->main, but in the future, we may just want to do away with running
2157 * the subrequest, and run the main request.
2158 *
2159 * There may be other values we need to save here.
2160 */
2161 rr->main->sent_bodyct = 1;
2162
2163 /* Check to make sure the connection wasnt aborted */
2164 if (rr->connection->aborted || cdata->aborted) {
2165 rr->main->bytes_sent = 0;
2166 res = FTP_REPLY_TRANSFER_ABORTED;
2167 rr->main->connection->aborted = 0;
2168 }
2169 else {
2170 rr->main->bytes_sent = rr->bytes_sent;
2171 }
2172
2173 clean_up:
2174 ap_destroy_sub_req(rr);
2175
2176 /* Replace the filters and connection */
2177 r->input_filters = rinput;
2178 r->proto_input_filters = rinput_proto;
2179 r->output_filters = routput;
2180 r->proto_output_filters = routput_proto;
2181 r->connection = c;
2182
2183 /* Close the data connection, send confirmation, and return */
2184 ap_lingering_close(cdata);
2185 fc->datasock = NULL;
2186 fc->filter_mask = 0;
2187
2188 return res;
2189 }
2190
2191 static int ftp_cmd_rnfr(request_rec *r, const char *arg)
2192 {
2193 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
2194 request_rec *rr;
2195 apr_status_t res;
2196 int response;
2197
2198 if ((res = ftp_set_uri(r, arg))) {
2199 return res;
2200 }
2201
2202 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
2203
2204 if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
2205 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
2206 ftp_escape_control_text(r->parsed_uri.path,
2207 r->pool));
2208 response = FTP_REPLY_FILE_NOT_FOUND;
2209 }
2210 else if (rr->finfo.filetype != APR_NOFILE) {
2211 fc->response_notes = "File exists, ready for destination name";
2212 apr_cpystrn(fc->rename_from, r->filename, APR_PATH_MAX + 1);
2213 response = FTP_REPLY_PENDING;
2214 }
2215 else {
2216 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
2217 ftp_escape_control_text(r->parsed_uri.path,
2218 r->pool));
2219 response = FTP_REPLY_FILE_NOT_FOUND;
2220 }
2221
2222 ap_destroy_sub_req(rr);
2223
2224 return response;
2225 }
2226
2227 static int ftp_cmd_rnto(request_rec *r, const char *arg)
2228 {
2229 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
2230 apr_status_t rv;
2231 int response, res;
2232 request_rec *rr;
2233
2234 if ((res = ftp_set_uri(r, arg))) {
2235 fc->rename_from[0] = '\0';
2236 return res;
2237 }
2238
2239 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
2240
2241 if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
2242 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
2243 ftp_escape_control_text(r->parsed_uri.path,
2244 r->pool));
2245 ap_destroy_sub_req(rr);
2246 return FTP_REPLY_FILE_NOT_FOUND;
2247 }
2248
2249 ap_destroy_sub_req(rr);
2250
2251 /* RNTO *must* be preceeded by RNFR */
2252 if (fc->rename_from[0] == '\0') {
2253 return FTP_REPLY_BAD_SEQUENCE;
2254 }
2255
2256 rv = apr_file_rename(fc->rename_from, r->filename, r->pool);
2257
2258 if (rv != APR_SUCCESS) {
2259 response = FTP_REPLY_LOCAL_ERROR;
2260 }
2261 else {
2262 response = FTP_REPLY_COMPLETED;
2263 }
2264
2265 fc->rename_from[0] = '\0';
2266 return response;
2267 }
2268
2269 static int ftp_cmd_rmd(request_rec *r, const char *arg)
2270 {
2271 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
2272 apr_status_t rv, res;
2273 request_rec *rr;
2274
2275 if ((res = ftp_set_uri(r, arg))) {
2276 return res;
2277 }
2278
2279 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
2280
2281 if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
2282 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
2283 ftp_escape_control_text(r->parsed_uri.path,
2284 r->pool));
2285 ap_destroy_sub_req(rr);
2286 return FTP_REPLY_FILE_NOT_FOUND;
2287 }
2288 ap_destroy_sub_req(rr);
2289
2290 rv = apr_dir_remove(r->filename, r->pool);
2291
2292 if (rv != APR_SUCCESS) {
2293 char error_str[120];
2294 char *err = apr_strerror(rv, error_str, sizeof(error_str));
2295 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
2296 ftp_escape_control_text(err, r->pool));
2297 return FTP_REPLY_FILE_NOT_FOUND;
2298 }
2299 else {
2300 return FTP_REPLY_COMPLETED;
2301 }
2302 }
2303
2304 static int ftp_cmd_size(request_rec *r, const char *arg)
2305 {
2306 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
2307 int res, response;
2308 request_rec *rr;
2309
2310 if ((res = ftp_set_uri(r, arg))) {
2311 return res;
2312 }
2313
2314 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
2315
2316 if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
2317 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
2318 ftp_escape_control_text(r->parsed_uri.path, r->pool));
2319 return FTP_REPLY_FILE_NOT_FOUND;
2320 }
2321
2322 if (rr->finfo.filetype == 0) {
2323 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOSUCHFILE,
2324 ftp_escape_control_text(arg, r->pool));
2325 response = FTP_REPLY_FILE_NOT_FOUND;
2326 }
2327 else if (rr->finfo.filetype != APR_REG) {
2328 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_NOTPLAIN,
2329 ftp_escape_control_text(arg, r->pool));
2330 response = FTP_REPLY_FILE_NOT_FOUND;
2331 }
2332 else {
2333 /*
2334 * XXX: big bug - going back to the beginning. We must compute size
2335 * rather than stating the file... this could be done trivially with
2336 * a request to a null 'data' port which simply counts up the bytes.
2337 * Will be implemented as a special-case of the ftp_core_data
2338 * connection endpoint-filter.
2339 * See RFC3659 - especially with respect to TYPE A/I
2340 */
2341 fc->response_notes = apr_psprintf(r->pool, "%" APR_OFF_T_FMT,
2342 rr->finfo.size);
2343 response = FTP_REPLY_FILE_STATUS;
2344 }
2345
2346 ap_destroy_sub_req(rr);
2347 return response;
2348 }
2349
2350 static int ftp_get_client_block(conn_rec *c, ap_input_mode_t mode,
2351 apr_bucket_brigade *bb,
2352 char *buffer, apr_size_t bufsiz,
2353 apr_size_t *len)
2354 {
2355 apr_size_t total;
2356 apr_status_t rv;
2357 apr_bucket *b;
2358 const char *tempbuf;
2359
2360 if (APR_BRIGADE_EMPTY(bb)) {
2361 if ((rv = ap_get_brigade(c->input_filters, bb, mode,
2362 APR_BLOCK_READ, bufsiz)) != APR_SUCCESS) {
2363 apr_brigade_destroy(bb);
2364 return rv;
2365 }
2366 }
2367
2368 b = APR_BRIGADE_FIRST(bb);
2369 if (APR_BUCKET_IS_EOS(b)) { /* From previous invocation */
2370 apr_bucket_delete(b);
2371 return APR_EOF;
2372 }
2373
2374 total = 0;
2375 while (total < bufsiz
2376 && b != APR_BRIGADE_SENTINEL(bb)
2377 && !APR_BUCKET_IS_EOS(b)) {
2378 apr_size_t len_read;
2379 apr_bucket *old;
2380
2381 if ((rv = apr_bucket_read(b, &tempbuf, &len_read,
2382 APR_BLOCK_READ)) != APR_SUCCESS) {
2383 return rv;
2384 }
2385 if (total + len_read > bufsiz) {
2386 len_read = bufsiz - total;
2387 apr_bucket_split(b, bufsiz - total);
2388 }
2389 memcpy(buffer, tempbuf, len_read);
2390 buffer += len_read;
2391 total += len_read;
2392
2393 old = b;
2394 b = APR_BUCKET_NEXT(b);
2395 apr_bucket_delete(old);
2396 }
2397
2398 *len = total;
2399 return APR_SUCCESS;
2400 }
2401
2402 /*
2403 * This handles all STOR and APPE requests
2404 */
2405 static int ftp_cmd_stor(request_rec *r, const char *arg)
2406 {
2407 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
2408 conn_rec *c = r->connection;
2409 conn_rec *cdata;
2410 ftp_dir_config *dconf;
2411 apr_file_t *file;
2412 apr_status_t rv, res;
2413 apr_bucket_brigade *bb;
2414 apr_int32_t openflag;
2415 char *buffer;
2416 apr_off_t total = 0;
2417 apr_size_t len;
2418 request_rec *rr;
2419 int clientstatus = APR_SUCCESS;
2420 int status = FTP_REPLY_DATA_CLOSE;
2421 #ifndef WIN32
2422 int seen_cr = 0;
2423 #endif
2424 apr_fileperms_t creatperms;
2425 #ifdef HAVE_FCHMOD
2426 mode_t fixmode;
2427 apr_finfo_t finfo;
2428 int fd;
2429 #endif
2430
2431 apr_table_setn(r->subprocess_env, "do_transfer_log", "1");
2432
2433 if ((res = ftp_set_uri(r, arg))) {
2434 return res;
2435 }
2436
2437 rr = ap_sub_req_method_uri(r->method, r->uri, r, NULL);
2438
2439 if ((rr->status == HTTP_UNAUTHORIZED) || (rr->status == HTTP_FORBIDDEN)) {
2440 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
2441 ftp_escape_control_text(r->parsed_uri.path,
2442 r->pool));
2443 ap_destroy_sub_req(rr);
2444 return FTP_REPLY_FILE_NOT_FOUND;
2445 }
2446
2447 dconf = ftp_get_module_config(rr->per_dir_config);
2448 creatperms = dconf->fileperms;
2449 ap_destroy_sub_req(rr);
2450
2451 if (creatperms == APR_OS_DEFAULT)
2452 creatperms = FTP_DEFAULT_UMASK;
2453
2454 /*
2455 * In the config phase, ->creatperms was a negative umask. for operation,
2456 * exchange this with a positive protections to pass to the apr_file_open
2457 * protection flag.
2458 */
2459 creatperms = (APR_UREAD | APR_UWRITE |
2460 APR_GREAD | APR_GWRITE |
2461 APR_WREAD | APR_WWRITE)
2462 & ~creatperms;
2463
2464 #ifdef HAVE_FCHMOD
2465 fixmode = ftp_unix_perms2mode(creatperms);
2466 creatperms = 0;
2467 #endif
2468
2469 /*
2470 * For the remainder of the operation, (openflag & APR_APPEND) reflects
2471 * this was an append operation and we have no need to truncate.
2472 * Presume, if not append and our restart point is zero, that
2473 * open(O_TRUNC) is supported and preferable.
2474 */
2475 openflag = APR_WRITE | APR_CREATE;
2476 if (strcmp(r->method, "APPE") == 0) {
2477 openflag |= APR_APPEND;
2478 }
2479 else if (fc->restart_point == 0) {
2480 openflag |= APR_TRUNCATE;
2481 }
2482
2483 rv = apr_file_open(&file, r->filename, openflag, creatperms, r->pool);
2484
2485 /* File permissions deny server write access */
2486 if (rv != APR_SUCCESS) {
2487 char error_str[120];
2488 char *err = apr_strerror(rv, error_str, sizeof(error_str));
2489 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
2490 ftp_escape_control_text(err, r->pool));
2491 fc->restart_point = 0;
2492 return FTP_REPLY_FILE_NOT_FOUND;
2493 }
2494
2495 #ifdef HAVE_FCHMOD
2496 /*
2497 * If we opened an existing file, we have nothing to do later in the code
2498 * (leave fd as -1). If we created the file with perms zero, grab the fd
2499 * and creatperms to adjust the perms when finished.
2500 */
2501 if (apr_file_info_get(&finfo, APR_FINFO_PROT, file)
2502 != APR_SUCCESS) {
2503 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
2504 "Unable to perform file upload; "
2505 "failed to get fileinfo");
2506 fc->restart_point = 0;
2507 return FTP_REPLY_FILE_NOT_FOUND;
2508 }
2509 if (finfo.protection) {
2510 fd = -1;
2511 }
2512 else {
2513 apr_os_file_get(&fd, file);
2514 }
2515 #endif
2516
2517 /*
2518 * Once the file is opened, non-append/non-truncate, we need to space
2519 * forward to the restart point. Unset the restart pointer once we have
2520 * grabbed it to space forward.
2521 */
2522 if (!(openflag & (APR_APPEND | APR_TRUNCATE))) {
2523 apr_off_t restart = fc->restart_point;
2524 if (apr_file_seek(file, APR_SET, &restart) != APR_SUCCESS) {
2525 fc->response_notes = "Requested action not taken: invalid REST "
2526 "parameter; failed to skip to restart point";
2527 #ifdef HAVE_FCHMOD
2528 if (fd != -1)
2529 fchmod(fd, fixmode);
2530 #endif
2531 fc->restart_point = 0;
2532 return FTP_REPLY_INVALID_REST_PARAM;
2533 }
2534 }
2535 fc->restart_point = 0;
2536
2537 ftp_reply(fc, c->output_filters, r->pool, FTP_REPLY_FILE_STATUS_OK, 0,
2538 apr_pstrcat(r->pool, "Opening ",
2539 (fc->type == TYPE_A) ? "ASCII" : "BINARY",
2540 " mode data connection for ",
2541 ftp_escape_control_text(arg, r->pool), NULL));
2542
2543 /*
2544 * Open the data connection to the client
2545 */
2546 if (!(cdata = ftp_open_dataconn(r, 0))) {
2547 #ifdef HAVE_FCHMOD
2548 if (fd != -1)
2549 fchmod(fd, fixmode);
2550 #endif
2551 return FTP_REPLY_CANNOT_OPEN_DATACONN;
2552 }
2553
2554 bb = apr_brigade_create(r->pool, c->bucket_alloc);
2555 buffer = apr_palloc(r->pool, AP_IOBUFSIZE);
2556
2557 while ((clientstatus = ftp_get_client_block(cdata,
2558 fc->type == TYPE_A ? AP_MODE_GETLINE : AP_MODE_READBYTES,
2559 bb, buffer, AP_IOBUFSIZE, &len)) == APR_SUCCESS) {
2560 #ifndef WIN32
2561 /*
2562 * If we are in ASCII mode, translate to our own internal form. This
2563 * means CRLF for windows and plain LF for Unicies
2564 */
2565 if ((fc->type == TYPE_A) && (len > 0)) {
2566 /*
2567 * The last read may have been incomplete if we had activity on
2568 * the control connection. Watch out for trailing CR's from the
2569 * last write, and back up the file a byte if one had been
2570 * written for this now-leading LF.
2571 */
2572 if (seen_cr && (*buffer == APR_ASCII_LF)) {
2573 apr_off_t off = -1;
2574 apr_file_seek(file, APR_CUR, &off);
2575 --total;
2576 }
2577
2578 /*
2579 * We may reach EOF without an ending newline. Make sure we
2580 * don't truncate any important data
2581 */
2582 if ((len > 1) && (*(buffer + len - 2) == APR_ASCII_CR)) {
2583 *(buffer + len - 2) = APR_ASCII_LF;
2584 len--;
2585 }
2586 else {
2587 /* Now note any trailing CR on this write. */
2588 seen_cr = (*(buffer + len - 1) == APR_ASCII_CR);
2589 }
2590 }
2591
2592 #endif /* WIN32 */
2593
2594 rv = apr_file_write(file, buffer, &len);
2595 if (rv != APR_SUCCESS) {
2596 char error_str[120];
2597 char *err = apr_strerror(rv, error_str, sizeof(error_str));
2598 ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server,
2599 "Error writing uploaded file");
2600 fc->response_notes = ftp_escape_control_text(err, r->pool);
2601 status = FTP_REPLY_LOCAL_ERROR;
2602 break;
2603 }
2604
2605 total += len;
2606 }
2607
2608 if ((clientstatus != APR_SUCCESS && !APR_STATUS_IS_EOF(clientstatus))
2609 || (cdata->aborted)) {
2610 status = FTP_REPLY_TRANSFER_ABORTED;
2611 cdata->aborted = 1;
2612 }
2613
2614 fc->traffic += total;
2615 fc->bytes += total;
2616 fc->files += 1;
2617 fc->transfers += 1;
2618
2619 /* Keep track of bytes sent for logging purposes. */
2620 r->sent_bodyct = 1;
2621 r->bytes_sent = total;
2622
2623 /*
2624 * Once we have finished this upload - we need to reset the file perms to
2625 * no longer hold a lock on the uploaded file...
2626 */
2627 #ifdef HAVE_FCHMOD
2628 if (fd != -1)
2629 fchmod(fd, fixmode);
2630 #endif
2631
2632 /*
2633 * and truncate anything beyond the end of the most recent upload
2634 */
2635 if (!(openflag & (APR_APPEND | APR_TRUNCATE))) {
2636 /* use apr_file_seek to do apr_file_tell's... */
2637 apr_off_t totsize = 0;
2638 apr_off_t restart = 0;
2639 if ((rv = apr_file_seek(file, APR_CUR, &restart)) != APR_SUCCESS) {
2640 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
2641 "STOR resume: failed to determine current position for truncation");
2642 }
2643 else if ((rv = apr_file_seek(file, APR_END, &totsize)) != APR_SUCCESS) {
2644 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
2645 "STOR resume: failed to determine current file size for truncation");
2646 }
2647 else if (totsize <= restart) {
2648 ; /* noop - nothing to truncate */
2649 }
2650 else if ((rv = apr_file_trunc(file, restart)) != APR_SUCCESS) {
2651 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
2652 "STOR resume: failed to truncate remaining file contents");
2653 }
2654 }
2655
2656 rv = apr_file_close(file);
2657 if (rv != APR_SUCCESS) {
2658 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
2659 "STOR/APPE: failed to close file");
2660 }
2661
2662 ap_lingering_close(cdata);
2663 fc->datasock = NULL;
2664
2665 return status;
2666 }
2667
2668 static int ftp_cmd_stru(request_rec *r, const char *arg)
2669 {
2670 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
2671
2672 if (*arg && !arg[1]) {
2673 switch (toupper(*arg)) {
2674 case 'F':
2675 fc->response_notes = "Structure set to F";
2676 return FTP_REPLY_COMMAND_OK;
2677 }
2678 }
2679 fc->response_notes = apr_psprintf(r->pool, "Structure %s not implemented",
2680 ftp_escape_control_text(arg, r->pool));
2681
2682 return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
2683 }
2684
2685 static int ftp_cmd_syst(request_rec *r, const char *arg)
2686 {
2687 return FTP_REPLY_SYSTEM_TYPE;
2688 }
2689
2690 static int ftp_cmd_type(request_rec *r, const char *arg)
2691 {
2692 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
2693
2694 if (*arg && !arg[1])
2695 {
2696 switch (toupper(*arg)) {
2697 case 'A':
2698 fc->type = TYPE_A;
2699 fc->response_notes = "Type set to A";
2700 return FTP_REPLY_COMMAND_OK;
2701 case 'I':
2702 fc->type = TYPE_I;
2703 fc->response_notes = "Type set to I";
2704 return FTP_REPLY_COMMAND_OK;
2705 }
2706 }
2707 else if (!strcasecmp(arg, "A N")) {
2708 /*
2709 * Accept Ascii Non-Print
2710 */
2711 fc->type = TYPE_A;
2712 fc->response_notes = "Type set to A N";
2713 return FTP_REPLY_COMMAND_OK;
2714 }
2715 else if (!strcasecmp(arg, "L 8")) {
2716 /*
2717 * Treat Local 8 as indistinguishible from Image for httpd platforms
2718 */
2719 fc->type = TYPE_I;
2720 fc->response_notes = "Type set to L 8";
2721 return FTP_REPLY_COMMAND_OK;
2722 }
2723
2724 fc->response_notes = apr_psprintf(r->pool, "Type %s not implemented",
2725 ftp_escape_control_text(arg, r->pool));
2726
2727 return FTP_REPLY_COMMAND_NOT_IMPL_PARAM;
2728 }
2729
2730 static int ftp_cmd_user(request_rec *r, const char *arg)
2731 {
2732 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
2733 conn_rec *c = r->connection;
2734 ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
2735 apr_time_t prev_timeout;
2736 apr_status_t rv;
2737 char *username;
2738 char *hostname;
2739
2740 /* Implicit logout */
2741 if (fc->logged_in) {
2742 ftp_limitlogin_loggedout(c);
2743 }
2744 fc->logged_in = 0;
2745 r->server = fc->orig_server = c->base_server;
2746 r->per_dir_config = r->server->lookup_defaults;
2747 r->hostname = fc->host = NULL;
2748 apr_pool_clear(fc->login_pool);
2749
2750 fc->user = username = apr_pstrdup(fc->login_pool, arg);
2751
2752 /*
2753 * Identify virtual host (user@{hostname}) for named vhost lookup, and
2754 * split from user name if so configured.
2755 */
2756 if ((hostname = ap_strchr(username, '@')) != NULL) {
2757 /*
2758 * Toggle to the Host:-based vhost's timeout mode to process this
2759 * login request
2760 */
2761 if (fsc->options & FTP_OPT_VHOST_BY_USER) {
2762 r->hostname = hostname + 1;
2763
2764 ap_update_vhost_from_headers(r);
2765
2766 fc->host = r->hostname;
2767 fc->orig_server = r->server;
2768 }
2769 }
2770
2771 /* we may have switched to another server */
2772 fsc = ftp_get_module_config(r->server->module_config);
2773 r->per_dir_config = r->server->lookup_defaults;
2774
2775 /*
2776 * Now that we switched virtual hosts, it's time to determine if the
2777 * username fc->user's "@{hostname}" should be discarded
2778 */
2779 if ((hostname != NULL) && (fsc->options & FTP_OPT_STRIP_HOSTNAME))
2780 *hostname = '\0';
2781
2782 /*
2783 * We have nominally 'logged out', and also potentially changed virtual
2784 * host contexts; reset to the proper timeout_login
2785 */
2786 rv = apr_socket_timeout_get(fc->cntlsock, &prev_timeout);
2787 if (rv != APR_SUCCESS || prev_timeout != fsc->timeout_login) {
2788 rv = apr_socket_timeout_set(fc->cntlsock,
2789 fsc->timeout_login * APR_USEC_PER_SEC);
2790 if (rv != APR_SUCCESS)
2791 ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server,
2792 "Couldn't set SO_TIMEOUT socket option");
2793 }
2794
2795 if ((fsc->options & FTP_OPT_REQUIRESSL) && !fc->is_secure) {
2796 r->hostname = fc->host = NULL;
2797 fc->user = ftp_unknown_username;
2798 fc->authorization = NULL;
2799 r->server = fc->orig_server = c->base_server;
2800 r->per_dir_config = r->server->lookup_defaults;
2801 fc->response_notes = "This server requires the use of SSL";
2802 return FTP_REPLY_NOT_LOGGED_IN;
2803 }
2804
2805 /* TODO: these should really be configurable */
2806 if ((strcmp(fc->user, "anonymous") == 0) ||
2807 (strncmp(fc->user, "anonymous@", 10) == 0) ||
2808 (strcmp(fc->user, "ftp") == 0) ||
2809 (strncmp(fc->user, "ftp@", 4) == 0)) {
2810 fc->response_notes = "Guest login ok, type your email address"
2811 " as the password";
2812 /* force this for limit and access control */
2813 fc->user = "anonymous";
2814 }
2815 else {
2816 fc->response_notes = apr_psprintf(r->pool, "Password required for %s",
2817 ftp_escape_control_text(fc->user, r->pool));
2818 }
2819
2820 return FTP_REPLY_USER_OK;
2821 }
2822
2823 void ftp_register_core_cmds(apr_pool_t *p)
2824 {
2825
2826 /*
2827 * Create the FTP command hash. All external modules will need to make
2828 * sure that their register hooks callbacks are called after the FTP
2829 * module
2830 */
2831
2832 FTPMethodHash = apr_hash_make(p);
2833 FTPMethodPool = p; /* The config pool */
2834
2835 /* Register all of our core command handlers */
2836
2837 ftp_hook_cmd("ABOR", ftp_cmd_abor, FTP_HOOK_LAST,
2838 FTP_NEED_LOGIN | FTP_TAKE0 | FTP_DATA_INTR,
2839 "(abort operation)");
2840
2841 ftp_hook_cmd("ACCT", NULL, FTP_HOOK_LAST,
2842 FTP_NEED_LOGIN | FTP_TAKE0,
2843 "(specify account)");
2844
2845 ftp_hook_cmd("ALLO", NULL, FTP_HOOK_LAST,
2846 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2847 "allocate storage (vacuously)");
2848
2849 ftp_hook_cmd("APPE", ftp_cmd_stor, FTP_HOOK_LAST,
2850 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2851 "<sp> file-name");
2852
2853 /* AUTH does not require a user to be logged in */
2854 ftp_hook_cmd("AUTH", ftp_cmd_auth, FTP_HOOK_LAST,
2855 FTP_TAKE1,
2856 "<sp> mechanism-name");
2857
2858 /* XXX: Advertise only if configured! */
2859 ftp_feat_advert("AUTH TLS");
2860
2861 ftp_hook_cmd("CDUP", ftp_cmd_cdup, FTP_HOOK_LAST,
2862 FTP_NEED_LOGIN | FTP_TAKE0,
2863 "change to parent directory");
2864
2865 ftp_hook_cmd("CWD", ftp_cmd_cwd, FTP_HOOK_LAST,
2866 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2867 "[ <sp> directory-name ]");
2868
2869 ftp_hook_cmd("DELE", ftp_cmd_dele, FTP_HOOK_LAST,
2870 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2871 "<sp> file-name");
2872
2873 ftp_hook_cmd("EPRT", ftp_cmd_eprt, FTP_HOOK_LAST,
2874 FTP_NEED_LOGIN | FTP_TAKE1 | FTP_NEW_FEAT,
2875 "<sp> <d>af<d>addr<d>port<d>");
2876
2877 ftp_hook_cmd("EPSV", ftp_cmd_epsv, FTP_HOOK_LAST,
2878 FTP_NEED_LOGIN | FTP_TAKE0 | FTP_TAKE1 | FTP_NEW_FEAT,
2879 "[ <sp> af|ALL ]");
2880
2881 ftp_hook_cmd("FEAT", ftp_cmd_feat, FTP_HOOK_LAST,
2882 FTP_TAKE0,
2883 "show server features");
2884
2885 ftp_hook_cmd("HELP", ftp_cmd_help, FTP_HOOK_LAST,
2886 FTP_TAKE0 | FTP_TAKE1,
2887 "[ <sp> <string> ]");
2888
2889 ftp_hook_cmd("LANG", NULL, FTP_HOOK_LAST,
2890 FTP_TAKE1,
2891 "<sp> lang-code");
2892
2893 ftp_hook_cmd("LIST", ftp_cmd_list, FTP_HOOK_LAST,
2894 FTP_NEED_LOGIN | FTP_TAKE0 | FTP_TAKE1_PATH,
2895 "[ <sp> path-name ]");
2896
2897 ftp_hook_cmd("MDTM", ftp_cmd_mdtm, FTP_HOOK_LAST,
2898 FTP_NEED_LOGIN | FTP_TAKE1_PATH | FTP_NEW_FEAT,
2899 "<sp> path-name");
2900
2901 ftp_hook_cmd("MKD", ftp_cmd_mkd, FTP_HOOK_LAST,
2902 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2903 "<sp> path-name");
2904
2905 ftp_hook_cmd("MODE", ftp_cmd_mode, FTP_HOOK_LAST,
2906 FTP_NEED_LOGIN | FTP_TAKE1,
2907 "<sp> [ S | B | C ] (specify transfer mode)");
2908
2909 ftp_hook_cmd("NLST", ftp_cmd_nlst, FTP_HOOK_LAST,
2910 FTP_NEED_LOGIN | FTP_TAKE0 | FTP_TAKE1_PATH,
2911 "[ <sp> path-name ]");
2912
2913 ftp_hook_cmd("NOOP", ftp_cmd_noop, FTP_HOOK_LAST,
2914 FTP_TAKE0,
2915 "");
2916
2917 ftp_hook_cmd("OPTS", NULL, FTP_HOOK_LAST,
2918 FTP_NEED_LOGIN | FTP_TAKE1 | FTP_EXTENSIBLE,
2919 "<sp> command [ <sp> options ]");
2920
2921 /* Pass must be handled literally to ensure leading, trailing spaces
2922 * or empty string are recognized
2923 */
2924 ftp_hook_cmd("PASS", ftp_cmd_pass, FTP_HOOK_LAST,
2925 FTP_TAKE0 | FTP_TAKE1_PATH,
2926 "<sp> password");
2927
2928 ftp_hook_cmd("PASV", ftp_cmd_pasv, FTP_HOOK_LAST,
2929 FTP_NEED_LOGIN | FTP_TAKE0,
2930 "(set server in passive mode)");
2931
2932 /* PBSZ does not require a user to be logged in */
2933 ftp_hook_cmd("PBSZ", ftp_cmd_pbsz, FTP_HOOK_LAST,
2934 FTP_TAKE1 | FTP_NEW_FEAT,
2935 "<sp> decimal-integer");
2936
2937 ftp_hook_cmd("PORT", ftp_cmd_port, FTP_HOOK_LAST,
2938 FTP_NEED_LOGIN | FTP_TAKE1,
2939 "<sp> b0, b1, b1, b3, b4, b5");
2940
2941 /* PROT does not require a user to be logged in */
2942 ftp_hook_cmd("PROT", ftp_cmd_prot, FTP_HOOK_LAST,
2943 FTP_TAKE1 | FTP_NEW_FEAT,
2944 "<sp> prot-code");
2945
2946 ftp_hook_cmd("PWD", ftp_cmd_pwd, FTP_HOOK_LAST,
2947 FTP_NEED_LOGIN | FTP_TAKE0,
2948 "(return current directory)");
2949
2950 ftp_hook_cmd("QUIT", ftp_cmd_quit, FTP_HOOK_LAST,
2951 FTP_TAKE0 | FTP_DATA_INTR,
2952 "(terminate service)");
2953
2954 ftp_hook_cmd("REIN", NULL, FTP_HOOK_LAST,
2955 FTP_NEED_LOGIN | FTP_TAKE0 | FTP_DATA_INTR,
2956 "(reinitialize server)");
2957
2958 ftp_hook_cmd("REST", ftp_cmd_rest, FTP_HOOK_LAST,
2959 FTP_NEED_LOGIN | FTP_TAKE1,
2960 "<sp> offset (restart command)");
2961
2962 ftp_feat_advert("REST STREAM");
2963
2964 ftp_hook_cmd("RETR", ftp_cmd_retr, FTP_HOOK_LAST,
2965 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2966 "<sp> file-name");
2967
2968 ftp_hook_cmd("RMD", ftp_cmd_rmd, FTP_HOOK_LAST,
2969 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2970 "<sp> path-name");
2971
2972 ftp_hook_cmd("RNFR", ftp_cmd_rnfr, FTP_HOOK_LAST,
2973 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2974 "<sp> file-name");
2975
2976 ftp_hook_cmd("RNTO", ftp_cmd_rnto, FTP_HOOK_LAST,
2977 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2978 "<sp> file-name");
2979
2980 ftp_hook_cmd("SITE", NULL, FTP_HOOK_LAST,
2981 FTP_NEED_LOGIN | FTP_TAKE1 | FTP_EXTENSIBLE,
2982 "<sp> site-cmd [ <sp> arguments ]");
2983
2984 ftp_hook_cmd("SIZE", ftp_cmd_size, FTP_HOOK_LAST,
2985 FTP_NEED_LOGIN | FTP_TAKE1_PATH | FTP_NEW_FEAT,
2986 "<sp> path-name");
2987
2988 ftp_hook_cmd("SMNT", NULL, FTP_HOOK_LAST,
2989 FTP_NEED_LOGIN | FTP_TAKE0,
2990 "(structure mount)");
2991
2992 ftp_hook_cmd("STAT", NULL, FTP_HOOK_LAST,
2993 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2994 "[ <sp> path-name ]");
2995
2996 ftp_hook_cmd("STOR", ftp_cmd_stor, FTP_HOOK_LAST,
2997 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
2998 "<sp> file-name");
2999
3000 ftp_hook_cmd("STOU", NULL, FTP_HOOK_LAST,
3001 FTP_NEED_LOGIN | FTP_TAKE1_PATH,
3002 "<sp> file-name");
3003
3004 ftp_hook_cmd("STRU", ftp_cmd_stru, FTP_HOOK_LAST,
3005 FTP_NEED_LOGIN | FTP_TAKE1,
3006 "<sp> [ F | R | P ] (specify file structure)");
3007
3008 ftp_hook_cmd("SYST", ftp_cmd_syst, FTP_HOOK_LAST,
3009 FTP_NEED_LOGIN | FTP_TAKE0,
3010 "(get type of operating system)");
3011
3012 /* RFC3659 TVFS advertising simply insists that '/' is not a filename
3013 * character, but a path delimiter in a tree structured filesystem.
3014 * That's us, advertise it;
3015 */
3016 ftp_feat_advert("TVFS");
3017
3018 ftp_hook_cmd("TYPE", ftp_cmd_type, FTP_HOOK_LAST,
3019 FTP_NEED_LOGIN | FTP_TAKE1,
3020 "<sp> [ A [ fmt ] | E [ fmt ] | I | L size ]");
3021
3022 ftp_hook_cmd("USER", ftp_cmd_user, FTP_HOOK_LAST,
3023 FTP_TAKE1,
3024 "<sp> username");
3025
3026 /* RFC2640 offers UTF-8; it does not insist all octets are UTF-8,
3027 * even for arbitrary file names. This reflects 'most unix' today.
3028 * Because we want to permit admins not to advertise support,
3029 * this is handled in post-config; see mod_ftp.c.
3030 */
3031 /* ftp_feat_advert("UTF8"); */
3032
3033 /* Unadvertised, deprecated RFC775 X-flavor CDUP */
3034 ftp_hook_cmd_alias("XCUP", "CDUP", FTP_HOOK_LAST,
3035 FTP_NO_HELP | FTP_NEED_LOGIN | FTP_TAKE0,
3036 "change to parent directory");
3037
3038 /* Unadvertised, deprecated RFC542 X-flavor CWD */
3039 ftp_hook_cmd_alias("XCWD", "CWD", FTP_HOOK_LAST,
3040 FTP_NO_HELP | FTP_NEED_LOGIN | FTP_TAKE1,
3041 "[ <sp> directory-name ]");
3042
3043 /* Unadvertised, deprecated RFC775 X-flavor MKD */
3044 ftp_hook_cmd_alias("XMKD", "MKD", FTP_HOOK_LAST,
3045 FTP_NO_HELP | FTP_NEED_LOGIN | FTP_TAKE1,
3046 "<sp> path-name");
3047
3048 /* Unadvertised, deprecated RFC775 X-flavor PWD */
3049 ftp_hook_cmd_alias("XPWD", "PWD", FTP_HOOK_LAST,
3050 FTP_NO_HELP | FTP_NEED_LOGIN | FTP_TAKE0,
3051 "(return current directory)");
3052
3053 /* Unadvertised, deprecated RFC775 X-flavor RMD */
3054 ftp_hook_cmd_alias("XRMD", "RMD", FTP_HOOK_LAST,
3055 FTP_NO_HELP | FTP_NEED_LOGIN | FTP_TAKE1,
3056 "<sp> path-name");
3057
3058 }