"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_util.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 #include "mod_ftp.h"
24 #include "ftp_internal.h"
25 #include "apr_fnmatch.h"
26 #include "ap_mpm.h" /* For MPM query interface */
27
28 /* Warning; the *arg is consumed, manipulated and must have the same lifetime
29 * as the desired *addr results!
30 */
31 int ftp_eprt_decode(apr_int32_t *family, char **addr, apr_port_t *port,
32 char *arg)
33 {
34 char *argv, delim = *arg;
35
36 if (delim <= ' ' || delim > 126)
37 return FTP_REPLY_SYNTAX_ERROR;
38
39 ++arg;
40 argv = arg;
41 while (isdigit(*arg))
42 ++arg;
43 if (*arg != delim)
44 return FTP_REPLY_SYNTAX_ERROR;
45 *(arg++) = '\0';
46 if (*argv) {
47 if (strcmp(argv, "1") == 0)
48 *family = APR_INET;
49 #if APR_HAVE_IPV6
50 else if (strcmp(argv, "2") == 0)
51 *family = APR_INET6;
52 #endif
53 else if (isdigit(argv[0]))
54 return FTP_REPLY_BAD_PROTOCOL;
55 else
56 return FTP_REPLY_SYNTAX_ERROR;
57 }
58
59 argv = arg;
60 if (*arg == delim)
61 return FTP_REPLY_SYNTAX_ERROR;
62 if (*family == APR_INET) {
63 while (isdigit(*arg) || (*arg == '.'))
64 ++arg;
65 }
66 #if APR_HAVE_IPV6
67 else if (*family == APR_INET6) {
68 while (isxdigit(*arg) || (*arg == ':'))
69 ++arg;
70 while (isdigit(*arg) || (*arg == '.'))
71 ++arg;
72 }
73 #endif
74 else
75 return FTP_REPLY_BAD_PROTOCOL;
76 if (*arg != delim)
77 return FTP_REPLY_SYNTAX_ERROR;
78 *(arg++) = '\0';
79 *addr = argv;
80
81 argv = arg;
82 if (*arg == delim)
83 return FTP_REPLY_SYNTAX_ERROR;
84 while (isdigit(*arg))
85 ++arg;
86 if (*arg != delim)
87 return FTP_REPLY_SYNTAX_ERROR;
88 *(arg++) = '\0';
89 if (*argv)
90 *port = atoi(argv);
91
92 if (*arg)
93 return FTP_REPLY_SYNTAX_ERROR;
94 return FTP_REPLY_COMMAND_OK;
95 }
96
97 static char *ftp_modestring_get(char *mode, apr_filetype_e typ,
98 apr_fileperms_t perms)
99 {
100
101 #ifdef WIN32
102 perms = ftp_unix_mode2perms(0);
103 #endif /* WIN32 */
104
105 if (perms < 0 || perms >= FTP_MAX_MODESTRING) {
106 return FTP_UNKNOWN_MODESTRING; /* see mod_ftp.h */
107 }
108
109 if (typ == APR_DIR) {
110 mode[0] = 'd';
111 }
112 if (perms & APR_UREAD) {
113 mode[1] = 'r';
114 }
115 if (perms & APR_UWRITE) {
116 mode[2] = 'w';
117 }
118 if (perms & APR_UEXECUTE) {
119 mode[3] = 'x';
120 }
121 if (perms & APR_USETID) {
122 mode[3] = 's';
123 }
124 if (perms & APR_GREAD) {
125 mode[4] = 'r';
126 }
127 if (perms & APR_GWRITE) {
128 mode[5] = 'w';
129 }
130 if (perms & APR_GEXECUTE) {
131 mode[6] = 'x';
132 }
133 if (perms & APR_GSETID) {
134 mode[6] = 's';
135 }
136 if (perms & APR_WREAD) {
137 mode[7] = 'r';
138 }
139 if (perms & APR_WWRITE) {
140 mode[8] = 'w';
141 }
142 if (perms & APR_WEXECUTE) {
143 mode[9] = 'x';
144 }
145 if (perms & APR_WSTICKY) {
146 mode[9] = 't';
147 }
148 return mode;
149 }
150
151
152 /* ftp_direntry_make: Fill out a directory entry structure from the
153 * directory being requested and a filename.
154 *
155 * Arguments: r - The request record for the directory index
156 * name - The filename to be checked.
157 * pattern - Pattern to get a directory listing for. If NULL
158 * we do not GLOB.
159 *
160 * Returns: ftp_direntry structure on success, NULL otherwise.
161 */
162 static struct ftp_direntry *ftp_direntry_make(request_rec *r,
163 const char *name,
164 const char *pattern)
165 {
166 ftp_server_config *fsc = ftp_get_module_config(r->server->module_config);
167 struct ftp_direntry *dirent;
168 request_rec *rr;
169 const char *test, *sl;
170 char mode[FTP_MODESTRING_LEN] = "----------";
171
172 for (test = name; (sl = ap_strchr_c(test, '/')); test = sl + 1)
173 /* noop */ ;
174
175 if (!strcmp("..", test)) {
176 return NULL;
177 }
178
179 #ifdef FTP_DEBUG
180 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Name - fdm: %s", name);
181 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Pattern - fdm: %s", pattern);
182 #endif
183
184 if (pattern && *pattern &&
185 (apr_fnmatch(pattern, name, APR_FNM_PATHNAME) != APR_SUCCESS)) {
186 #ifdef FTP_DEBUG
187 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "no match");
188 #endif
189 return NULL;
190 }
191
192 rr = ap_sub_req_lookup_file(name, r, NULL);
193
194 if ((rr->finfo.filetype != 0) &&
195 ((rr->status == HTTP_OK) || (rr->status == HTTP_MOVED_PERMANENTLY) ||
196 (rr->status == HTTP_UNAUTHORIZED &&
197 fsc->options & FTP_OPT_SHOWUNAUTH)) &&
198 (rr->uri != NULL)) {
199 apr_time_exp_t xt;
200 apr_size_t retcode;
201 char *lasts;
202
203 /* We don't mess with char returned here, so its fine to cast */
204 lasts = ap_strrchr((char *) name, '/');
205
206 dirent = apr_pcalloc(r->pool, sizeof(ftp_direntry));
207 dirent->next = NULL;
208 dirent->name = apr_pstrdup(r->pool, lasts + 1);
209 dirent->nlink = rr->finfo.nlink;
210 dirent->size = rr->finfo.size;
211 dirent->csize = rr->finfo.csize;
212 dirent->modestring = apr_pstrdup(r->pool,
213 ftp_modestring_get(
214 mode,
215 rr->finfo.filetype,
216 rr->finfo.protection)
217 );
218
219 /*
220 * If FTPOptions RemoveUserGroup is set, we don't bother looking up
221 * user and group information for each file
222 */
223 if (fsc->options & FTP_OPT_REMOVEUSERGROUP) {
224 dirent->username = apr_psprintf(r->pool, "%ld", (long)rr->finfo.user);
225 dirent->groupname = apr_psprintf(r->pool, "%ld", (long)rr->finfo.group);
226 }
227 else {
228 if ((apr_uid_name_get(&dirent->username, rr->finfo.user, r->pool)
229 !=APR_SUCCESS) || (!dirent->username)
230 || (!dirent->username[0])) {
231 dirent->username = apr_psprintf(r->pool, "%ld", (long)rr->finfo.user);
232 }
233 if ((apr_gid_name_get(&dirent->groupname, rr->finfo.group, r->pool)
234 !=APR_SUCCESS) || (!dirent->groupname)
235 || (!dirent->groupname[0])) {
236 dirent->groupname = apr_psprintf(r->pool, "%ld", (long)rr->finfo.group);
237 }
238 }
239
240 apr_time_exp_lt(&xt, rr->finfo.mtime);
241
242 if (r->request_time - rr->finfo.mtime >
243 180 * 24 * 60 * 60 * APR_USEC_PER_SEC) {
244 apr_strftime(dirent->datestring, &retcode,
245 sizeof(dirent->datestring), "%b %e %Y", &xt);
246 }
247 else {
248 apr_strftime(dirent->datestring, &retcode,
249 sizeof(dirent->datestring), "%b %e %H:%M", &xt);
250 }
251 }
252 else {
253 dirent = NULL;
254 }
255
256 ap_destroy_sub_req(rr);
257 return dirent;
258 }
259
260 /* ftp_dsortf: Used for sorting directory entries. Called by qsort()
261 *
262 * Arguments: d1 - The first directory entry
263 * d2 - The second directory entry
264 *
265 * Returns: An integer less than, equal to or greater than zero if
266 * d1 is found, respectively, to be less than, match or
267 * be greater than d2.
268 */
269 static int ftp_dsortf(struct ftp_direntry ** d1,
270 struct ftp_direntry ** d2)
271 {
272 /* Simple sort based on filename */
273 return strcmp((*d1)->name, (*d2)->name);
274 }
275
276 /* ftp_direntry_get: Return an array of ftp_direntry structures based
277 * on the uri stored in the request rec. An extra
278 * argument may be passed for pattern matching.
279 *
280 * Arguments: r - The request record for the directory index
281 * pattern - Pattern to get a directory listing for.
282 *
283 * Returns: The sorted array of directory entries on success, NULL otherwise
284 */
285 struct ftp_direntry *ftp_direntry_get(request_rec *r, const char *pattern)
286 {
287 struct ftp_direntry *p, *head, *current, **a;
288 apr_dir_t *dir;
289 apr_finfo_t finfo;
290 apr_status_t rv;
291 int num, i;
292 char *fname;
293 const char *path, *search;
294
295 /*
296 * The actual search pattern, used to determine if we should recurse into
297 * a directory or not. If the search pattern is just *, we should not
298 * decend. For search patterns like 'm*', we should.
299 */
300 #ifdef FTP_DEBUG
301 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Pattern - start: %s", pattern);
302 #endif
303
304 path = pattern;
305 search = ap_strrchr_c(pattern, '/');
306
307 if (search == NULL) {
308 search = ap_strrchr_c(pattern, '\\');
309 }
310 if (search != NULL) {
311 search++;
312 path = apr_pstrndup(r->pool, pattern, search - pattern);
313 }
314
315 #ifdef FTP_DEBUG
316 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Path - end: %s", path);
317 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Pattern - end: %s", pattern);
318 #endif
319
320 #ifdef WIN32
321
322 /*
323 * Win32 will always return sucess on apr_dir_open, which means we have
324 * to stat the file to see if the request was made for a directory or
325 * file.
326 */
327 rv = apr_stat(&finfo, path, APR_FINFO_MIN, r->pool);
328 if (finfo.filetype != APR_DIR) {
329 /* Must be for a single file */
330 return ftp_direntry_make(r, path, pattern);
331 }
332 #endif /* WIN32 */
333
334 rv = apr_dir_open(&dir, path, r->pool);
335
336 if (rv != APR_SUCCESS) {
337 if (APR_STATUS_IS_ENOTDIR(rv)) {
338 /* Must be for a single file */
339 return ftp_direntry_make(r, path, pattern);
340 }
341 else {
342 return NULL;
343 }
344 }
345
346 num = 0;
347 head = current = NULL;
348 while ((rv = apr_dir_read(&finfo, APR_FINFO_DIRENT, dir))
349 == APR_SUCCESS) {
350
351 fname = ap_make_full_path(r->pool, path, finfo.name);
352 p = ftp_direntry_make(r, fname, pattern);
353 if (!p) {
354 continue;
355 }
356
357 /* Add this entry to the linked list */
358 if (head == NULL) {
359 head = p;
360 p->next = NULL;
361 current = p;
362 }
363 else {
364 current->next = p;
365 current = p;
366 }
367 /*
368 * We are only going to support single recursive listings this means
369 * that requests such as 'ls m*' will print out all files that match,
370 * and recurse a single level into directories.
371 */
372
373 if (search && (search[0] != '*') && (p->modestring[0] == 'd')) {
374 const char *newpattern = apr_pstrcat(r->pool, fname,
375 "/*", NULL);
376 p->child = ftp_direntry_get(r, newpattern);
377 }
378 else {
379 p->child = NULL;
380 }
381 num++;
382 }
383
384 apr_dir_close(dir);
385
386 /* Sort this mess */
387 if (num > 0) {
388 a = (struct ftp_direntry **) apr_pcalloc(r->pool,
389 num *
390 sizeof(ftp_direntry));
391 p = head;
392 i = 0;
393 while (p) {
394 a[i++] = p;
395 p = p->next;
396 }
397 num = i;
398 qsort((void *) a, num, sizeof(struct ftp_direntry *),
399 (int (*) (const void *, const void *)) ftp_dsortf);
400
401 /* Re-construct the list from the sorted list */
402 head = a[0];
403 current = head;
404 for (i = 1; i < num; i++) {
405 current->next = a[i];
406 current = current->next;
407 }
408 current->next = NULL;
409 }
410
411 return head;
412 }
413
414
415 /* ftp_set_authorization: set the r->headers_in Authorization header
416 *
417 * Arguments: r - The request
418 *
419 * Returns: nada
420 */
421
422 void ftp_set_authorization(request_rec *r)
423 {
424 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
425 if (fc->user == ftp_unknown_username)
426 return;
427 r->hostname = apr_pstrdup(r->pool, fc->host);
428 r->user = apr_pstrdup(r->pool, fc->user);
429 apr_table_setn(r->headers_in, "Host", r->hostname);
430 apr_table_setn(r->headers_in, "Authorization", fc->authorization);
431 }
432
433 /* ftp_set_uri: Setup r->uri based on a file argument and user's
434 * current working directory. We also run the translate
435 * name phase here to set r->filename.
436 *
437 * Arguments: r - The request
438 * arg - The file argument
439 *
440 * Returns: nothing
441 */
442 int ftp_set_uri(request_rec *r, const char *arg)
443 {
444 ftp_connection *fc = ftp_get_module_config(r->connection->conn_config);
445 apr_status_t res;
446
447 if (arg[0] == '/') {
448 ap_parse_uri(r, arg);
449 }
450 else {
451 ap_parse_uri(r, ap_make_full_path(r->pool, fc->cwd, arg));
452 }
453 ap_getparents(r->uri);
454
455 /*
456 * If the path ended in /., ap_getparents converts it to /, but we really
457 * don't want the trailing / there, so remove it.
458 */
459 if (r->uri[strlen(r->uri) - 1] == '/') {
460 r->uri[strlen(r->uri) - 1] = '\0';
461 }
462
463 /*
464 * Parsed uri is empty if we try a path outside the document
465 * root. For now, just set the uri to /, but I'm sure there
466 * is a better way around this.
467 *
468 * - rpm
469 */
470 if (r->uri[0] == '\0') {
471 r->uri = apr_pstrdup(r->pool, "/");
472 }
473 res = ap_run_translate_name(r);
474 if (res) {
475 fc->response_notes = apr_psprintf(r->pool, FTP_MSG_PERM_DENIED,
476 ftp_escape_control_text(r->parsed_uri.path,
477 r->pool));
478 return FTP_REPLY_LOCAL_ERROR;
479 }
480 r->uri = ap_escape_uri(r->pool, r->uri);
481
482 return OK;
483 }
484
485 /* ftp_unix_perms2mode: Translate apr_fileperms_t to mode_t.
486 *
487 * Arguments: perms - apr_fileperms_t
488 *
489 * Returns: mode_t
490 */
491 mode_t ftp_unix_perms2mode(apr_fileperms_t perms)
492 {
493 mode_t mode = 0;
494
495 #ifndef WIN32
496 if (perms & APR_UREAD) {
497 mode |= S_IRUSR;
498 }
499 if (perms & APR_UWRITE) {
500 mode |= S_IWUSR;
501 }
502 if (perms & APR_UEXECUTE) {
503 mode |= S_IXUSR;
504 }
505 if (perms & APR_GREAD) {
506 mode |= S_IRGRP;
507 }
508 if (perms & APR_GWRITE) {
509 mode |= S_IWGRP;
510 }
511 if (perms & APR_GEXECUTE) {
512 mode |= S_IXGRP;
513 }
514 if (perms & APR_WREAD) {
515 mode |= S_IROTH;
516 }
517 if (perms & APR_WWRITE) {
518 mode |= S_IWOTH;
519 }
520 if (perms & APR_WEXECUTE) {
521 mode |= S_IXOTH;
522 }
523 #endif /* WIN32 */
524 return mode;
525 }
526
527 /* ftp_unix_mode2perms: Translate mode_t to apr_fileperms_t.
528 *
529 * Arguments: mode - mode_t
530 *
531 * Returns: apr_fileperms_t
532 */
533 apr_fileperms_t ftp_unix_mode2perms(mode_t mode)
534 {
535 apr_fileperms_t perms = 0;
536 #ifdef WIN32
537 perms |= APR_UREAD;
538 perms |= APR_UWRITE;
539 perms |= APR_UEXECUTE;
540 perms |= APR_GREAD;
541 perms |= APR_GEXECUTE;
542 perms |= APR_WREAD;
543 perms |= APR_WEXECUTE;
544
545 #else
546 if (mode & S_IRUSR) {
547 perms |= APR_UREAD;
548 }
549 if (mode & S_IWUSR) {
550 perms |= APR_UWRITE;
551 }
552 if (mode & S_IXUSR) {
553 perms |= APR_UEXECUTE;
554 }
555 if (mode & S_IRGRP) {
556 perms |= APR_GREAD;
557 }
558 if (mode & S_IWGRP) {
559 perms |= APR_GWRITE;
560 }
561 if (mode & S_IXGRP) {
562 perms |= APR_GEXECUTE;
563 }
564 if (mode & S_IROTH) {
565 perms |= APR_WREAD;
566 }
567 if (mode & S_IWOTH) {
568 perms |= APR_WWRITE;
569 }
570 if (mode & S_IXOTH) {
571 perms |= APR_WEXECUTE;
572 }
573 #endif /* WIN32 */
574 return perms;
575 }
576
577 /* ftp_toupper: Convert a string to uppercase
578 *
579 * Arguments: s - The string
580 *
581 * Returns: The capitialized string.
582 */
583 char *ftp_toupper(apr_pool_t *p, const char *s)
584 {
585 char *upper = apr_pstrdup(p, s);
586 char *pos = upper;
587
588 while (*pos != '\0') {
589 *pos = apr_toupper(*pos);
590 pos++;
591 }
592
593 return upper;
594 }
595
596 /* ftp_escape_control_text:
597 * Expand <CR> to <CR> <IAC> <NOP> (because it is impossible to
598 * distinguish <CR> <NUL> from end of string while retaining
599 * multiline C strings) and a single 0xFF to <IAC> <IAC>
600 * as documented in RFC854 and clarified by RFC2640 and RFC3659
601 *
602 * Arguments: s - The string (not deliberately multiline)
603 * pool - The pool *if required*
604 *
605 * Returns: The escaped string, which may be the origin string.
606 */
607 FTP_DECLARE(const char *) ftp_escape_control_text(const char *s,
608 apr_pool_t *pool)
609 {
610 int i, j;
611 char *d;
612
613 for (i = 0, j = 0; s[i]; ++i, ++j)
614 {
615 if (s[i] == APR_ASCII_CR)
616 j += 2;
617 else if (s[i] == '\xFF')
618 ++j;
619 }
620
621 if (i == j)
622 return s;
623
624 d = apr_palloc(pool, j + 1);
625
626 for (i = 0, j = 0; (d[j] = s[i]); ++i, ++j)
627 {
628 if (s[i] == APR_ASCII_CR) {
629 d[++j] = '\xFF'; /* IAC */
630 d[++j] = '\xF1'; /* NOP */
631 }
632 else if (s[i] == '\xFF')
633 d[++j] = '\xFF'; /* IAC */
634 }
635
636 return d;
637 }
638
639 /* ftp_check_maxclients: Check the scoreboard for other available servers.
640 *
641 * Arguments: r - The current request
642 *
643 * Returns: 0 if we find a server, 1 otherwise.
644 */
645 int ftp_check_maxclients(request_rec *r)
646 {
647 int hard_server_limit, hard_thread_limit;
648 int i, j;
649 worker_score *scoreboard;
650
651 ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &hard_server_limit);
652 ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &hard_thread_limit);
653
654 for (i = 0; i < hard_server_limit; i++) {
655 for (j = 0; j < hard_thread_limit; j++) {
656 #if ((AP_SERVER_MAJORVERSION_NUMBER < 3) && (AP_SERVER_MINORVERSION_NUMBER < 3))
657 scoreboard = ap_get_scoreboard_worker(i, j);
658 #else
659 scoreboard = ap_get_scoreboard_worker_from_indexes(i, j);
660 #endif
661 if (scoreboard->status == SERVER_READY)
662 return 0;
663 }
664 }
665
666 /*
667 * We are the only available server, so go ahead. Maybe this should be
668 * optimized out so it's only in debug builds?
669 */
670 if (ap_exists_config_define("ONE_PROCESS")) {
671 return 0;
672 }
673
674 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, 0, r,
675 "Maximum number of FTP sessions reached.");
676 return 1;
677 }