"Fossies" - the Fresh Open Source Software Archive

Member "mod_ftp-0.9.6/modules/ftp/ftp_util.c" (17 Sep 2009, 19056 Bytes) of package /linux/www/apache_httpd_modules/old/mod_ftp-0.9.6-beta.tar.gz:


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 }