mod_xfer.c
上传用户:pycemail
上传日期:2007-01-04
资源大小:329k
文件大小:26k
源码类别:

Ftp客户端

开发平台:

Unix_Linux

  1. /*
  2.  * ProFTPD - FTP server daemon
  3.  * Copyright (c) 1997, 1998 Public Flood Software
  4.  * Copyright (C) 1999, MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
  5.  *  
  6.  * This program is free software; you can redistribute it and/or modify
  7.  * it under the terms of the GNU General Public License as published by
  8.  * the Free Software Foundation; either version 2 of the License, or
  9.  * (at your option) any later version.
  10.  *
  11.  * This program is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.  * GNU General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with this program; if not, write to the Free Software
  18.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
  19.  */
  20. /*
  21.  * Data transfer module for ProFTPD
  22.  * $Id: mod_xfer.c,v 1.15 1999/10/01 07:57:31 macgyver Exp $
  23.  */
  24. /* History Log:
  25.  *
  26.  * 8/15/99
  27.  *   - rate control <grin@tolna.net>
  28.  * 4/24/97 0.99.0pl1
  29.  *   _translate_ascii was returning a buffer larger than the max buffer
  30.  *   size causing memory overrun and all sorts of neat corruption.
  31.  *   Status: Stomped
  32.  *
  33.  */
  34. #include "conf.h"
  35. #ifdef HAVE_REGEX_H
  36. #include <regex.h>
  37. #endif
  38. extern module auth_module;
  39. extern pid_t mpid;
  40. /* From the auth module */
  41. char *auth_map_uid(int);
  42. char *auth_map_gid(int);
  43. void xfer_abort(IOFILE*,int);
  44. /* Variables for this module */
  45. static fsdir_t *retr_file = NULL;
  46. static fsdir_t *stor_file = NULL;
  47. static int stor_fd;
  48. static int retr_fd;
  49. module xfer_module;
  50. static void _log_transfer(char direction)
  51. {
  52.   struct timeval end_time;
  53.   char *fullpath;
  54.   gettimeofday(&end_time,NULL);
  55.   end_time.tv_sec -= session.xfer.start_time.tv_sec;
  56.   if(end_time.tv_usec >= session.xfer.start_time.tv_usec)
  57.     end_time.tv_usec -= session.xfer.start_time.tv_usec;
  58.   else {
  59.     end_time.tv_usec = 1000000L - (session.xfer.start_time.tv_usec -
  60.                        end_time.tv_usec);
  61.     end_time.tv_sec--;
  62.   }
  63.   fullpath = dir_abs_path(session.xfer.p,session.xfer.path,TRUE);
  64.   if((session.flags & SF_ANON) != 0) {
  65.     log_xfer(end_time.tv_sec,session.c->remote_name,session.xfer.total_bytes,
  66.              fullpath,(session.flags & SF_ASCII ? 'a' : 'b'),
  67.              direction,'a',session.anon_user);
  68.   } else
  69.     log_xfer(end_time.tv_sec,session.c->remote_name,session.xfer.total_bytes,
  70.              fullpath,(session.flags & SF_ASCII ? 'a' : 'b'),
  71.              direction,'r',session.user);
  72.   log_debug(DEBUG1,"Transfer completed: %d bytes in %d.%02d seconds.",
  73.                  session.xfer.total_bytes,end_time.tv_sec,
  74.                  (end_time.tv_usec / 10000));
  75.   data_cleanup();
  76. }
  77. /* This routine counts the difference in usec between timeval's
  78.  */
  79. static float _rate_diffusec(struct timeval tlast, struct timeval t) {
  80.     float diffsec, diffusec;
  81.     diffsec = t.tv_sec - tlast.tv_sec;
  82.     diffusec= t.tv_usec- tlast.tv_usec;
  83.     log_debug(DEBUG5,"_rate_diffusec: last=%ld %ld  now=%ld %ld  diff=%f %f  res=%f",
  84.       tlast.tv_sec,tlast.tv_usec,
  85.       t.tv_sec,t.tv_usec,
  86.       diffsec, diffusec,
  87.       ( diffsec * 10e5 + diffusec ) );
  88.     return( diffsec * 10e5 + diffusec );
  89. }
  90. /* Bandwidth Throttling. <grin@tolna.net>
  91.  *
  92.  * If the rate sent were too high usleeps the required amount (max 100 sec).
  93.  * No throttling for the first FreeBytes bytes (but this includes REST as well).
  94.  * If HardBPS then forces BPS throughout FreeBytes as well.
  95.  *
  96.  * input:  rate_pos: position in file
  97.  *              rate_bytes:     bytes xferred (same as rate_pos if !REST)
  98.  *              rate_tvlast:    when the transfer was started
  99.  *              rate_freebytes: no throttling unless that many xferred
  100.  *              rate_bps:       max byte / sec bandwidth allowed
  101.  *              rate_hardbps:   if FALSE then forces BPS only after FreeBytes
  102.  */
  103. static void _rate_throttle(unsigned long rate_pos, long rate_bytes,
  104.    struct timeval rate_tvlast,
  105.    long rate_freebytes, long rate_bps,
  106.    int rate_hardbps)
  107. {
  108.   /* rate_tv:        now (the diff of those gives the time spent)
  109.    */
  110.   struct timeval rate_tv;
  111.   float dtime, wtime;
  112.   gettimeofday(&rate_tv, NULL);
  113.   
  114.   /* no rate control unless more than free bytes DL'ed */
  115.   log_debug(DEBUG5,
  116.     "_rate_throttle: rate_bytes=%ld  rate_pos=%ld  rate_freebytes=%ld  rate_bps=%ld  rate_hardbps=%i",
  117.     rate_bytes, rate_pos,
  118.     rate_freebytes, rate_bps, rate_hardbps);
  119.   if(rate_pos < rate_freebytes)
  120.     return;
  121.   
  122.   if(!rate_hardbps)
  123.     rate_bytes -= rate_freebytes;
  124.   
  125.   dtime = _rate_diffusec(rate_tvlast, rate_tv);
  126.   wtime = 10e5 * rate_bytes / rate_bps;
  127.   
  128.   if(wtime>dtime) {
  129.     /* too fast, doze a little */
  130.     log_debug(DEBUG5,"_rate_throttle: wtime=%f  dtime=%f",wtime,dtime);
  131.     if(wtime-dtime > 10e7) {
  132.       /* >100sec, umm that'd timeout */
  133.       log_debug(DEBUG5, "Sleeping 100 seconds.");
  134.       usleep( 10e7 );
  135.       log_debug(DEBUG5, "Sleeping 100 seconds done!");
  136.     } else {
  137.       log_debug(DEBUG5, "Sleeping %f sec.", (wtime - dtime) / 10e5);
  138.       usleep(wtime-dtime);
  139.       log_debug(DEBUG5, "Sleeping %f sec done!", (wtime - dtime) / 10e5);
  140.     }
  141.   }
  142. }
  143. static
  144. void _stor_done()
  145. {
  146.   fs_close(stor_file,stor_fd);
  147.   stor_file = NULL;
  148.   if(session.fsgid && session.xfer.path) {
  149.     struct stat sbuf;
  150.     fs_stat(session.xfer.path,&sbuf);
  151.     if(chown(session.xfer.path,(uid_t)-1,(gid_t)session.fsgid) == -1)
  152.       log_pri(LOG_WARNING,"chown(%s) failed: %s",
  153.               session.xfer.path,strerror(errno));
  154.     else
  155.       fs_chmod(session.xfer.path,sbuf.st_mode);
  156.   }
  157. }
  158. static
  159. void _retr_done()
  160. {
  161.   fs_close(retr_file,retr_fd);
  162.   retr_file = NULL;
  163. }
  164. static void _stor_abort() {
  165.   fs_close(stor_file,stor_fd);
  166.   stor_file = NULL;
  167.   if(session.xfer.xfer_type == STOR_HIDDEN) {
  168.     /* If hidden stor aborted, remove only hidden file, not real one */
  169.     if(session.xfer.path_hidden)
  170.       unlink(session.xfer.path_hidden);
  171.   } else if(session.xfer.path) {
  172.     unlink(session.xfer.path);
  173.   }
  174. }
  175. static void _retr_abort() {
  176.   /* Isn't necessary to send anything here, just cleanup */
  177.   fs_close(retr_file,retr_fd);
  178.   retr_file = NULL;
  179. }
  180. /* cmd_pre_stor is a PRE_CMD handler which checks security, etc, and
  181.  * places the full filename to receive in cmd->private [note that we CANNOT
  182.  * use cmd->tmp_pool for this, as tmp_pool only lasts for the duration
  183.  * of this function.
  184.  */
  185. MODRET pre_cmd_stor(cmd_rec *cmd) {
  186.   char *dir;
  187.   mode_t fmode;
  188.   privdata_t *p, *p_hidden;
  189.   if(cmd->argc < 2) {
  190.     add_response_err(R_500,"'%s' not understood.",get_full_cmd(cmd));
  191.     return ERROR(cmd);
  192.   }
  193.   dir = dir_best_path(cmd->tmp_pool,cmd->arg);
  194.   if(!dir || !dir_check(cmd->tmp_pool,cmd->argv[0],cmd->group,dir,NULL)) {
  195.     add_response_err(R_550,"%s: %s",cmd->arg,strerror(errno));
  196.     return ERROR(cmd);
  197.   }
  198.   fmode = file_mode(dir);
  199.   if(fmode && (session.xfer.xfer_type != STOR_APPEND) && 
  200.        get_param_int(CURRENT_CONF,"AllowOverwrite",FALSE) != 1) {
  201.     add_response_err(R_550,"%s: Overwrite permission denied",cmd->arg);
  202.     return ERROR(cmd);
  203.   }
  204.   if(fmode && !S_ISREG(fmode)) {
  205.     add_response_err(R_550,"%s: Not a regular file",cmd->arg);
  206.     return ERROR(cmd);
  207.   }
  208.   /* If restarting, check permissions on this directory, if
  209.    * AllowStoreRestart is set, permit it
  210.    */
  211.   if(fmode &&
  212.      (session.restart_pos || (session.xfer.xfer_type == STOR_APPEND)) &&
  213.      get_param_int(CURRENT_CONF,"AllowStoreRestart",FALSE) != TRUE) {
  214.     add_response_err(R_451,"%s: Append/Restart not permitted, try again.",
  215.                   cmd->arg);
  216.     session.restart_pos = 0L;
  217.     session.xfer.xfer_type = STOR_DEFAULT;
  218.     return ERROR(cmd);
  219.   }
  220.   /* otherwise everthing is good */
  221.   p = mod_privdata_alloc(cmd, "stor_filename", strlen(dir) + 1);
  222.   sstrncpy(p->value.str_val, dir, strlen(dir) + 1);
  223.   if(get_param_int(CURRENT_CONF,"HiddenStor",FALSE) == 1) {
  224.     /* We have to also figure out the temporary hidden file name for
  225.      * receiving this transfer.
  226.      * Length is +5 due to .in. prepended and "." at end.
  227.      */
  228.     char *c;
  229.     int dotcount, foundslash, basenamestart, maxlen;
  230.     dotcount = foundslash = basenamestart = 0;
  231.     /* Figure out where the basename starts */
  232.     for (c=dir; *c; ++c) {
  233.       if (*c == '/') {
  234. foundslash = 1;
  235. basenamestart = dotcount = 0;
  236.       } else if (*c == '.') {
  237. ++ dotcount;
  238. /* Keep track of leading dots, ... is normal, . and .. are special.
  239.  * So if we exceed ".." it becomes a normal file, retroactively consider
  240.  * this the possible start of the basename
  241.  */
  242. if ((dotcount > 2) && (!basenamestart))
  243.   basenamestart = ((unsigned long)c - (unsigned long)dir) - dotcount;
  244.       } else {
  245. /* We found a nonslash, nondot character; if this is the first time
  246.  * we found one since the last slash, remember this as the possible
  247.  * start of the basename.
  248.  */
  249. if (!basenamestart)
  250.   basenamestart = ((unsigned long)c - (unsigned long)dir) - dotcount;
  251.       }
  252.     }
  253.     if (! basenamestart) {
  254.       /* This probably shouldn't happen */
  255.       add_response_err(R_451,"%s: Bad file name.", dir);
  256.       return ERROR(cmd);
  257.     }
  258.     maxlen = strlen(dir) + 1 + 5;
  259.     if (maxlen > MAXPATHLEN) {
  260.       /* This probably shouldn't happen */
  261.       add_response_err(R_451,"%s: File name too long.", dir);
  262.       return ERROR(cmd);
  263.     }
  264.     
  265.     p_hidden = mod_privdata_alloc(cmd, "stor_hidden_filename", maxlen);
  266.     if (! foundslash) {
  267.       /* Simple local file name */
  268.       sstrncpy(p_hidden->value.str_val, ".in.", maxlen);
  269.       sstrcat(p_hidden->value.str_val, dir, maxlen);
  270.       sstrcat(p_hidden->value.str_val, ".", maxlen);
  271.       log_pri(LOG_DEBUG,"Local path, will rename %s to %s",
  272. p_hidden->value.str_val, p->value.str_val);
  273.     } else {
  274.       /* Complex relative path or absolute path */
  275.       sstrncpy(p_hidden->value.str_val, dir, maxlen);
  276.       p_hidden->value.str_val[basenamestart] = '';
  277.       sstrcat(p_hidden->value.str_val, ".in.", maxlen);
  278.       sstrcat(p_hidden->value.str_val, dir + basenamestart, maxlen);
  279.       sstrcat(p_hidden->value.str_val, ".", maxlen);
  280.       log_pri(LOG_DEBUG,"Complex path, will rename %s to %s",
  281. p_hidden->value.str_val, p->value.str_val);
  282.       if(file_mode(p_hidden->value.str_val)) {
  283.         add_response_err(R_550,"%s: Temporary hidden file %s already exists",
  284. cmd->arg, p_hidden->value.str_val);
  285.         return ERROR(cmd);
  286.       }
  287.     }
  288.     session.xfer.xfer_type = STOR_HIDDEN;
  289.   }
  290.   return HANDLED(cmd);
  291. }
  292. /* cmd_pre_appe is the PRE_CMD handler for the APPEnd command, which
  293.  * simply sets xfer_type to STOR_APPEND and calls pre_cmd_stor
  294.  */
  295. MODRET pre_cmd_appe(cmd_rec *cmd)
  296. {
  297.   session.xfer.xfer_type = STOR_APPEND;
  298.   session.restart_pos = 0L;
  299.   
  300.   return pre_cmd_stor(cmd);
  301. }
  302. MODRET cmd_stor(cmd_rec *cmd)
  303. {
  304.   char *dir;
  305.   char *lbuf;
  306.   int bufsize,len;
  307.   unsigned long respos = 0;
  308.   privdata_t *p, *p_hidden;
  309. #if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP)
  310.   regex_t *preg;
  311.   int ret;
  312. #endif
  313.   long rate_pos=0, rate_bytes=0, rate_freebytes=0, rate_bps=0;
  314.   int rate_hardbps=0;
  315.   struct timeval rate_tvstart;
  316.   if( (rate_bps = get_param_int(CURRENT_CONF,"RateWriteBPS",FALSE)) == -1 ) { rate_bps=0; }
  317.   if( (rate_freebytes = get_param_int(CURRENT_CONF,"RateWriteFreeBytes",FALSE)) == -1 ) { rate_freebytes=0; }
  318.   rate_hardbps = get_param_int(CURRENT_CONF,"RateWriteHardBPS",FALSE) == 1;
  319.       
  320.   if( rate_bps != 0 ) {
  321.       /* I am not sure this _is_ allowed in ftp protocol... ideas, anyone?
  322.        add_response(R_211,"Allowed bandwidth is %ld bps (starting at %ld).",rate_bps,rate_freebytes);
  323.        */
  324.       log_debug(DEBUG2,"Allowed bandwidth is %ld bps (starting at %ld).",rate_bps,rate_freebytes);
  325.   }
  326.   
  327.   p_hidden = NULL;
  328.   p = mod_privdata_find(cmd,"stor_filename",NULL);
  329.   if(!p) {
  330.     add_response_err(R_550,"%s: internal error, stor_filename not set by cmd_pre_retr",cmd->arg);
  331.     return ERROR(cmd);
  332.   }
  333.   dir = p->value.str_val;
  334.   if(session.xfer.xfer_type == STOR_HIDDEN) {
  335.     p_hidden = mod_privdata_find(cmd,"stor_hidden_filename",NULL);
  336.     if(!p_hidden) {
  337.       add_response_err(R_550,"%s: internal error, stor_hidden_filename not set by cmd_pre_retr",cmd->arg);
  338.       return ERROR(cmd);
  339.     }
  340.   }
  341. #if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP)
  342.   preg = (regex_t*)get_param_ptr(TOPLEVEL_CONF,"PathAllowFilter",FALSE);
  343.   if(preg && ((ret = regexec(preg,cmd->arg,0,NULL,0)) != 0)) {
  344.     char errmsg[200];
  345.     regerror(ret,preg,errmsg,200);
  346.     log_debug(DEBUG2,"'%s' didn't pass regex: %s",cmd->arg,errmsg);
  347.     add_response_err(R_550,"%s: Forbidden filename", cmd->arg);
  348.     return ERROR(cmd);
  349.   }
  350.   preg = (regex_t*)get_param_ptr(TOPLEVEL_CONF,"PathDenyFilter",FALSE);
  351.   if(preg && ((ret = regexec(preg,cmd->arg,0,NULL,0)) == 0)) {
  352.     add_response_err(R_550,"%s: Forbidden filename", cmd->arg);
  353.     return ERROR(cmd);
  354.   }
  355. #endif
  356.   if(session.xfer.xfer_type == STOR_HIDDEN)
  357.     stor_file = fs_open(p_hidden->value.str_val,
  358. O_WRONLY|(session.restart_pos ? 0 : O_CREAT|O_EXCL),&stor_fd);
  359.   else if(session.xfer.xfer_type == STOR_APPEND)
  360.     stor_file = fs_open(dir, O_WRONLY|O_CREAT|O_APPEND,&stor_fd);
  361.   else /* Normal session */
  362.     stor_file = fs_open(dir,
  363. O_WRONLY|(session.restart_pos ? 0 : O_TRUNC|O_CREAT),&stor_fd);
  364.   if(stor_file && session.restart_pos) {
  365.     if(fs_lseek(stor_file,stor_fd,session.restart_pos,SEEK_SET) == -1) {
  366.       int _errno = errno;
  367.       fs_close(stor_file,stor_fd);
  368.       errno = _errno;
  369.       stor_file = NULL;
  370.     }
  371.     respos = session.restart_pos;
  372.     rate_pos = respos;
  373.     session.restart_pos = 0L;
  374.   }
  375.   if(!stor_file) {
  376.     add_response_err(R_550,"%s: %s",cmd->arg,strerror(errno));
  377.     return ERROR(cmd);
  378.   } else {
  379.     /* perform the actual transfer now */
  380.     data_init(cmd->arg,IO_READ);
  381.     session.xfer.path = pstrdup(session.xfer.p,dir);
  382.     if(session.xfer.xfer_type == STOR_HIDDEN)
  383.       session.xfer.path_hidden = pstrdup(session.xfer.p, p_hidden->value.str_val);
  384.     else
  385.       session.xfer.path_hidden = NULL;
  386.       
  387.     session.xfer.file_size = respos;
  388.     if(data_open(cmd->arg,NULL,IO_READ,0) < 0) {
  389.       data_abort(0,TRUE);
  390.       return HANDLED(cmd);
  391.     }
  392.     bufsize = (main_server->tcp_rwin > 0 ? main_server->tcp_rwin : 1024);
  393.     lbuf = (char*) palloc(cmd->tmp_pool, bufsize);
  394.     
  395.     gettimeofday(&rate_tvstart, NULL);
  396.     while((len = data_xfer(lbuf, bufsize)) > 0) {
  397.       if(XFER_ABORTED)
  398.         break;
  399.       len = fs_write(stor_file, stor_fd, lbuf, len);
  400.       if(len < 0) {
  401.         int s_errno = errno;
  402.         _stor_abort();
  403.         data_abort(s_errno, FALSE);
  404.         return ERROR(cmd);
  405.       }
  406.       if(rate_bps) {
  407.           rate_pos += len;
  408.           rate_bytes += len;
  409.   _rate_throttle(rate_pos, rate_bytes, rate_tvstart, rate_freebytes,
  410.  rate_bps, rate_hardbps);
  411.       }
  412.     }
  413.     if(XFER_ABORTED) {
  414.       _stor_abort();
  415.       data_abort(0,0);
  416.       return ERROR(cmd);
  417.     } else if(len < 0) {
  418.       _stor_abort();
  419.       data_abort(session.d->inf->xerrno,FALSE);
  420.       return ERROR(cmd);
  421.     } else {
  422.       if(session.xfer.path && session.xfer.path_hidden) {
  423.         if(fs_rename(session.xfer.path_hidden,session.xfer.path) != 0) {
  424.           /* This should only fail on a race condition with a chmod/chown
  425.            * or if STOR_APPEND is on and the permissions are squirrely.
  426.            * The poor user will have to re-upload, but we've got more important
  427.            * problems to worry about and this failure should be fairly rare.
  428.            */
  429.            log_pri(LOG_WARNING,"Rename of %s to %s failed: %s",
  430.              session.xfer.path_hidden, session.xfer.path, strerror(errno));
  431.            add_response_err(R_550,"%s: rename of hidden file %s failed: %s",
  432.              session.xfer.path, session.xfer.path_hidden, strerror(errno));
  433.            unlink(session.xfer.path_hidden);
  434.            return ERROR(cmd);
  435.          }
  436.       }
  437.       _stor_done();
  438.       data_close(FALSE);
  439.     }
  440.   }
  441.   return HANDLED(cmd);
  442. }
  443. MODRET cmd_rest(cmd_rec *cmd)
  444. {
  445.   long int pos;
  446.   char *endp;
  447.   if(cmd->argc != 2) {
  448.     add_response_err(R_500,"'%s': command not understood.",get_full_cmd(cmd));
  449.     return ERROR(cmd);
  450.   }
  451.   pos = strtol(cmd->argv[1],&endp,10);
  452.   if((endp && *endp) || pos < 0) {
  453.     add_response_err(R_501,"REST requires a value greater than or equal to 0.");
  454.     return ERROR(cmd);
  455.   }
  456.   session.restart_pos = pos;
  457.   add_response(R_350,"Restarting at %ld. Send STORE or RETRIEVE to initiate transfer.",
  458.                 pos);
  459.   return HANDLED(cmd);
  460. }
  461. /* cmd_pre_retr is a PRE_CMD handler which checks security, etc, and
  462.  * places the full filename to send in cmd->private [note that we CANNOT
  463.  * use cmd->tmp_pool for this, as tmp_pool only lasts for the duration
  464.  * of this function.
  465.  */
  466. MODRET pre_cmd_retr(cmd_rec *cmd)
  467. {
  468.   char *dir;
  469.   mode_t fmode;
  470.   privdata_t *p;
  471.   if(cmd->argc < 2) {
  472.     add_response_err(R_500,"'%s' not understood.",get_full_cmd(cmd));
  473.     return ERROR(cmd);
  474.   }
  475.   dir = dir_realpath(cmd->tmp_pool,cmd->arg);
  476.   if(!dir || !dir_check(cmd->tmp_pool,cmd->argv[0],cmd->group,dir,NULL)) {
  477.     add_response_err(R_550,"%s: %s",cmd->arg,strerror(errno));
  478.     return ERROR(cmd);
  479.   }
  480.   fmode = file_mode(dir);
  481.   if(!S_ISREG(fmode)) {
  482.     if(!fmode)
  483.       add_response_err(R_550,"%s: %s",cmd->arg,strerror(errno));
  484.     else
  485.       add_response_err(R_550,"%s: Not a regular file",cmd->arg);
  486.     return ERROR(cmd);
  487.   }
  488.   /* If restart is on, check to see if AllowRestartRetrieve
  489.    * is off, in which case we disallow the transfer and
  490.    * clear restart_pos
  491.    */
  492.   if(session.restart_pos &&
  493.      get_param_int(CURRENT_CONF,"AllowRetrieveRestart",FALSE) == 0) {
  494.     add_response_err(R_451,"%s: Restart not permitted, try again.",
  495.                   cmd->arg);
  496.     session.restart_pos = 0L;
  497.     return ERROR(cmd);
  498.   }
  499.   /* otherwise everthing is good */
  500.   p = mod_privdata_alloc(cmd,"retr_filename",strlen(dir)+1);
  501.   sstrncpy(p->value.str_val, dir, strlen(dir) + 1);
  502.   return HANDLED(cmd);
  503. }
  504. MODRET cmd_retr(cmd_rec *cmd)
  505. {
  506.   char *dir, *lbuf;
  507.   struct stat sbuf;
  508.   struct timeval rate_tvstart;
  509.   privdata_t *p;
  510.   int bufsize, len = 0;
  511.   int rate_hardbps = 0;
  512.   unsigned long respos = 0,cnt = 0,cnt_steps = 0,cnt_next = 0;
  513.   long rate_bytes = 0, rate_freebytes = 0, rate_bps = 0;
  514.   off_t off;
  515.   if((rate_bps = get_param_int(CURRENT_CONF, "RateReadBPS", FALSE)) == -1)
  516.     rate_bps = 0;
  517.   if((rate_freebytes = get_param_int(CURRENT_CONF,
  518.      "RateReadFreeBytes", FALSE)) == -1)
  519.     rate_freebytes = 0;
  520.   
  521.   rate_hardbps = get_param_int(CURRENT_CONF,"RateReadHardBPS",FALSE) == 1;
  522.   
  523.   if(rate_bps != 0) {
  524.       /* I am not sure this _is_ allowed in ftp protocol... ideas, anyone?
  525.  add_response(R_211,"Allowed bandwidth is %ld bps (starting at %ld).",rate_bps,rate_freebytes);
  526.       */
  527.     log_debug(DEBUG2,"Allowed bandwidth is %ld bps (starting at %ld).",rate_bps,rate_freebytes);
  528.   }
  529.   
  530.   p = mod_privdata_find(cmd,"retr_filename",NULL);
  531.   
  532.   if(!p) {
  533.     add_response_err(R_550, "%s: internal error, what happened to "
  534.      "cmd_pre_retr?!?", cmd->arg);
  535.     return ERROR(cmd);
  536.   }
  537.   
  538.   dir = p->value.str_val;
  539.   retr_file = fs_open(dir,O_RDONLY,&retr_fd);
  540.   if(session.restart_pos) {
  541.     if(fs_lseek(retr_file,retr_fd,session.restart_pos,SEEK_SET) == -1) {
  542.       int _errno = errno;
  543.       fs_close(retr_file,retr_fd);
  544.       errno = _errno;
  545.       retr_file = NULL;
  546.     }
  547.     respos = session.restart_pos;
  548.     session.restart_pos = 0L;
  549.   }
  550.   if(!retr_file || fs_stat(dir,&sbuf) == -1) {
  551.     add_response_err(R_550,"%s: %s",cmd->arg,strerror(errno));
  552.     return ERROR(cmd);
  553.   } else {
  554.     /* send the data */
  555.     data_init(cmd->arg,IO_WRITE);
  556.     session.xfer.path = pstrdup(session.xfer.p,dir);
  557.     session.xfer.file_size = (unsigned long)sbuf.st_size;
  558.     cnt_steps = session.xfer.file_size / 100;
  559.     if(cnt_steps == 0)
  560.       cnt_steps = 1;
  561.     if(data_open(cmd->arg,NULL,IO_WRITE,sbuf.st_size - respos) < 0) {
  562.       data_abort(0,TRUE);
  563.       return ERROR(cmd);
  564.     }
  565.     bufsize = (main_server->tcp_swin > 0 ? main_server->tcp_swin : 1024);
  566.     lbuf = (char*)palloc(cmd->tmp_pool,bufsize);
  567.     cnt = respos;
  568.     log_add_run(mpid, NULL, session.user,
  569. (session.class && session.class->name) ? session.class->name :
  570. "",
  571. NULL, 0, session.xfer.file_size, 0, NULL);
  572.     gettimeofday(&rate_tvstart, NULL);
  573.     off = respos;
  574.     
  575.     while(cnt != session.xfer.file_size) {
  576.       if((len = fs_read(retr_file, retr_fd, lbuf, bufsize)) <= 0)
  577. break;
  578.       
  579.       if(XFER_ABORTED)
  580.         break;
  581.       
  582. #ifdef HAVE_SENDFILE
  583.       if(session.flags & (SF_ASCII | SF_ASCII_OVERRIDE)) {
  584. len = data_xfer(lbuf, len);
  585. goto done;
  586.       }
  587.       
  588.       len = data_sendfile(retr_fd, &off, session.xfer.file_size - cnt);
  589.       if(len == -1) {
  590. log_pri(LOG_ERR, "data_sendfile error: %d:%s",
  591. errno, strerror(errno));
  592. switch (errno) {
  593. case EAGAIN:
  594.   continue;
  595. default:
  596.   log_pri(LOG_ERR, "unknown data_sendfile() error %d: %s",
  597.   errno, strerror(errno));
  598. }
  599.       }
  600.     done:
  601. #else
  602.       len = data_xfer(lbuf, len);
  603. #endif /* HAVE_SENDFILE */
  604.       
  605.       if(len < 0) {
  606.         _retr_abort();
  607.         data_abort(session.d->outf->xerrno,FALSE);
  608.         return ERROR(cmd);
  609.       }
  610.       cnt += len;
  611.       rate_bytes += len;
  612.       
  613.       if((cnt / cnt_steps) != cnt_next) {
  614. cnt_next = cnt / cnt_steps;
  615. log_add_run(mpid, NULL, session.user,
  616.     (session.class && session.class->name) ?
  617.     session.class->name : "",
  618.     NULL, 0, session.xfer.file_size, cnt, NULL);
  619.       }
  620.       
  621.       if(rate_bps) {
  622. _rate_throttle(cnt, rate_bytes, rate_tvstart, rate_freebytes,
  623.        rate_bps, rate_hardbps);
  624.       }
  625.     }
  626.     
  627.     if(XFER_ABORTED) {
  628.       _retr_abort();
  629.       data_abort(0,0);
  630.       return ERROR(cmd);
  631.     } else if(len < 0) {
  632.       _retr_abort();
  633.       data_abort(errno,FALSE);
  634.       return ERROR(cmd);
  635.     } else {
  636.       _retr_done();
  637.       data_close(FALSE);
  638.     }
  639.   }
  640.   
  641.   return HANDLED(cmd);
  642. }
  643. MODRET cmd_abor(cmd_rec *cmd)
  644. {
  645.   if(cmd->argc != 1) {
  646.     add_response_err(R_500,"'%s' not understood.",get_full_cmd(cmd));
  647.     return ERROR(cmd);
  648.   }
  649.   if(session.flags & (SF_POST_ABORT|SF_ABORT)) {
  650.     session.flags &= ~(SF_POST_ABORT|SF_ABORT);
  651.     add_response(R_226,"Abort successful");
  652.     return HANDLED(cmd);
  653.   }
  654.   add_response_err(R_500,"No command to abort.");
  655.   return ERROR(cmd);
  656. }
  657. MODRET cmd_type(cmd_rec *cmd)
  658. {
  659.   if(cmd->argc < 2 || cmd->argc > 3) {
  660.     add_response_err(R_500,"'%s' not understood.",get_full_cmd(cmd));
  661.     return ERROR(cmd);
  662.   }
  663.   cmd->argv[1][0] = toupper(cmd->argv[1][0]);
  664.   if(!strcmp(cmd->argv[1],"A"))
  665.     session.flags |= SF_ASCII;
  666.   else if(!strcmp(cmd->argv[1],"I"))
  667.     session.flags &= (SF_ALL^SF_ASCII);
  668.   else {
  669.     add_response_err(R_500,"'%s' not understood.",get_full_cmd(cmd));
  670.     return ERROR(cmd);
  671.   }
  672.   add_response(R_200,"Type set to %s.",cmd->argv[1]);
  673.   return HANDLED(cmd);
  674. }
  675. MODRET log_stor(cmd_rec *cmd)
  676. {
  677.   _log_transfer('i');
  678.   return DECLINED(cmd);
  679. }
  680. MODRET log_retr(cmd_rec *cmd)
  681. {
  682.   _log_transfer('o');
  683.   return DECLINED(cmd);
  684. }
  685. static int _noxfer_timeout(CALLBACK_FRAME)
  686. {
  687.   if(session.flags & SF_XFER)
  688.     return TRUE; /* Transfer in progress, ignore timeout */
  689.   send_response_async(R_421,
  690.            "No Transfer Timeout (%d seconds): closing control connection.",
  691.            TimeoutNoXfer);
  692. #if 0 /* no longer needed */
  693.   schedule(main_exit,0,(void*)LOG_NOTICE,"FTP no transfer time out, disconnected.",
  694.            (void*)0,NULL);
  695. #endif
  696.   
  697.   remove_timer(TIMER_IDLE,ANY_MODULE);
  698.   remove_timer(TIMER_LOGIN,ANY_MODULE);
  699.   main_exit((void*)LOG_NOTICE,"FTP no transfer timeout, disconnected.",
  700.   (void*)0,NULL);
  701.   return 0;
  702. }
  703. int xfer_init_child()
  704. {
  705.   /* Setup TimeoutNoXfer timer */
  706.   if(TimeoutNoXfer)
  707.     add_timer(TimeoutNoXfer,TIMER_NOXFER,&xfer_module,_noxfer_timeout);
  708.   return 0;
  709. }
  710. MODRET add_ratenum(cmd_rec *cmd)
  711. {
  712.   config_rec *c;
  713.   long ratenum;
  714.   char *endp;
  715.   CHECK_ARGS(cmd,1);
  716.   CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_ANON | CONF_DIR | CONF_GLOBAL);
  717.   if(cmd->argc != 2 )
  718.       CONF_ERROR(cmd,"invalid number of arguments");
  719.   if(!strcasecmp(cmd->argv[1],"none"))
  720.     ratenum = 0;
  721.   else {
  722.     ratenum = (int)strtol(cmd->argv[1],&endp,10);
  723.     if((endp && *endp) || ratenum < 0)
  724.       CONF_ERROR(cmd,"argument must be 'none' or a positive integer.");
  725.   }
  726.   log_debug(DEBUG5,"add_ratenum: %s %ld",cmd->argv[0],ratenum);
  727.   c = add_config_param( cmd->argv[0], 1, (void*)ratenum );
  728.   c->flags |= CF_MERGEDOWN;
  729.   return HANDLED(cmd);
  730. }
  731. MODRET add_ratebool(cmd_rec *cmd)
  732. {
  733.   config_rec *c;
  734.   int b;
  735.   CHECK_ARGS(cmd,1);
  736.   CHECK_CONF(cmd, CONF_ROOT | CONF_VIRTUAL | CONF_ANON | CONF_DIR | CONF_GLOBAL);
  737.   if((b = get_boolean(cmd,1)) == -1)
  738.     CONF_ERROR(cmd,"expected boolean argument.");
  739.   log_debug(DEBUG5,"add_ratebool: %s %d",cmd->argv[0],b);
  740.   c = add_config_param( cmd->argv[0], 1, (void*)b );
  741.   c->flags |= CF_MERGEDOWN;
  742.   return HANDLED(cmd);
  743. }
  744. conftable xfer_config[] = {
  745.   { "RateReadBPS", add_ratenum,                 },
  746.   { "RateReadFreeBytes", add_ratenum,              },
  747.   { "RateReadHardBPS", add_ratebool,                },
  748.   { "RateWriteBPS", add_ratenum,                 },
  749.   { "RateWriteFreeBytes", add_ratenum,              },
  750.   { "RateWriteHardBPS", add_ratebool,                },
  751.   { NULL }
  752. };
  753. cmdtable xfer_commands[] = {
  754.   { CMD,     C_TYPE, G_NONE,  cmd_type, TRUE, FALSE, CL_MISC },
  755.   { PRE_CMD, C_RETR, G_READ,  pre_cmd_retr, TRUE, FALSE },
  756.   { CMD,     C_RETR, G_READ,  cmd_retr, TRUE, FALSE, CL_READ },
  757.   { LOG_CMD, C_RETR, G_NONE,  log_retr, FALSE,  FALSE },
  758.   { PRE_CMD, C_STOR, G_WRITE, pre_cmd_stor, TRUE, FALSE },
  759.   { CMD,     C_STOR, G_WRITE, cmd_stor, TRUE, FALSE, CL_WRITE },
  760.   { LOG_CMD, C_STOR,    G_NONE,  log_stor, FALSE,  FALSE },
  761.   { PRE_CMD, C_APPE, G_WRITE, pre_cmd_appe, TRUE, FALSE },
  762.   { CMD,     C_APPE, G_WRITE, cmd_stor, TRUE, FALSE, CL_WRITE },
  763.   { LOG_CMD, C_APPE, G_NONE,  log_stor, FALSE,  FALSE },
  764.   { CMD,     C_ABOR, G_NONE,  cmd_abor, TRUE, TRUE,  CL_MISC  },
  765.   { CMD,     C_REST, G_NONE,  cmd_rest, TRUE, FALSE, CL_MISC  },
  766.   { 0,NULL }
  767. };
  768. module xfer_module = {
  769.   NULL,NULL, /* Always NULL */
  770.   0x20, /* API Version */
  771.   "xfer", /* Module name */
  772.   xfer_config,
  773.   xfer_commands,
  774.   NULL,
  775.   NULL,xfer_init_child
  776. };