/* * binkleyforce -- unix FTN mailer project * * Copyright (c) 1998-2000 Alexander Belkin, 2:5020/1398.11 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * $Id$ */ #include "includes.h" #include "confread.h" #include "logger.h" #include "io.h" #include "util.h" #include "session.h" #include "prot_common.h" #include "outbound.h" #include "freq.h" const char *Protocols[] = { "Unknown", "Xmodem", "Zmodem", "ZedZap", "DirZap", "Janus", "Hydra", "Tcp", "BinkP" }; /***************************************************************************** * Get first file from files queue not marked as sent. Extract files in the * order: netmail, arcmail, other files, file request. Files of equal type * extract in the order of flavor descending * * Arguments: * dest pointer to the pointer to put file information to * pi pointer to the protocol information structure * q pointer to the files queue * * Return value: * Zero value on success and non-zero if nothing to send */ static int prot_get_next_file(s_filelist **dest, s_protinfo *pi, s_fsqueue *q) { s_filelist *ptrl = NULL; s_filelist *best = NULL; *dest = NULL; for( ptrl = q->fslist; ptrl; ptrl = ptrl->next ) if( ptrl->status == STATUS_WILLSEND ) { if( pi->reqs_only ) { if( (ptrl->type & TYPE_REQUEST) ) { *dest = ptrl; return 0; } } else if( best ) { if( (ptrl->type & TYPE_NETMAIL) ) { if( (best->type & TYPE_NETMAIL) ) { if( ptrl->flavor > best->flavor ) best = ptrl; } else best = ptrl; } else if( (ptrl->type & TYPE_ARCMAIL) ) { if( (best->type & TYPE_ARCMAIL) && (ptrl->flavor > best->flavor) ) best = ptrl; else if( !(best->type & TYPE_NETMAIL) && !(best->type & TYPE_ARCMAIL) ) best = ptrl; } else if( !(ptrl->type & TYPE_REQUEST) && (best->type & TYPE_REQUEST) ) best = ptrl; } else best = ptrl; } if( best ) { *dest = best; return 0; } for( ptrl = pi->filelist; ptrl; ptrl = ptrl->next ) if( ptrl->status == STATUS_WILLSEND ) { *dest = ptrl; return 0; } return 1; } /***************************************************************************** * Calculate total files number and size we are going to send. It will be * used by some protocols that supports reporting of total traffic to the * remote side (e.g. Zmodem protocols could) * * Arguments: * pi pointer to the protocol information structure * q pointer to the files queue * * Return value: * None */ /* void prot_update_traffic(s_protinfo *pi, const s_fsqueue *q) { s_filelist *ptrl = NULL; pi->send_bytesleft = 0; pi->send_filesleft = 0; if( !state.sopts.holdall ) { for( ptrl = q->fslist; ptrl; ptrl = ptrl->next ) if( ptrl->status == STATUS_WILLSEND ) { pi->send_filesleft += 1; pi->send_bytesleft += ptrl->size; } for( ptrl = pi->filelist; ptrl; ptrl = ptrl->next ) if( ptrl->status == STATUS_WILLSEND ) { pi->send_filesleft += 1; pi->send_bytesleft += ptrl->size; } } } */ /***************************************************************************** * Open next file for sending. Set all necessary information. And do all * necessary checks. * * Arguments: * pi pointer to the protocol information structure * * Return value: * Zero value on success and non-zero if have nothing to send */ int p_tx_fopen(s_protinfo *pi) { FILE *fp; struct stat st; s_filelist *ptrl = NULL; if( state.sopts.holdall ) return 1; get_next_file: if( prot_get_next_file(&ptrl, pi, &state.queue) || !ptrl ) return 1; /* Mark this file as "processed" */ ptrl->status = STATUS_SENDING; pi->send_left_num -= 1; pi->send_left_size -= ptrl->size; if( pi->send_left_size < 0 ) pi->send_left_size = 0; if( pi->send_left_num < 0 ) pi->send_left_num = 0; /* Reset MinCPS time counter */ pi->tx_low_cps_time = 0; DEB((D_PROT, "p_tx_fopen: now opening \"%s\"", ptrl->fname)); if( stat(ptrl->fname, &st) ) { logerr("send: cannot stat file \"%s\"", ptrl->fname); goto get_next_file; } if( (fp = file_open(ptrl->fname, "r")) == NULL ) { logerr("send: cannot open file \"%s\"", ptrl->fname); goto get_next_file; } /* * Add new entry to the send files queue */ if( pi->sentfiles && pi->n_sentfiles > 0 ) { pi->sentfiles = (s_finfo *)xrealloc(pi->sentfiles, sizeof(s_finfo)*(pi->n_sentfiles+1)); memset(&pi->sentfiles[pi->n_sentfiles], '\0', sizeof(s_finfo)); pi->send = &pi->sentfiles[pi->n_sentfiles++]; } else { pi->sentfiles = (s_finfo *)xmalloc(sizeof(s_finfo)); memset(pi->sentfiles, '\0', sizeof(s_finfo)); pi->send = pi->sentfiles; pi->n_sentfiles = 1; } /* * Set file information */ pi->send->fp = fp; pi->send->local_name = (char*)xstrcpy(file_getname(ptrl->fname)); pi->send->net_name = recode_file_out(p_convfilename(pi->send->local_name, ptrl->type)); pi->send->fname = (char*)xstrcpy(ptrl->fname); pi->send->mod_time = (ptrl->type & TYPE_REQUEST) ? time(NULL) : st.st_mtime; pi->send->mod_time = localtogmt(pi->send->mod_time); pi->send->mod_time += pi->send->mod_time%2; pi->send->mode = st.st_mode; pi->send->bytes_total = st.st_size; pi->send->start_time = time(NULL); pi->send->eofseen = FALSE; pi->send->status = FSTAT_PROCESS; pi->send->type = ptrl->type; pi->send->action = ptrl->action; pi->send->flodsc = ptrl->flodsc; if( strcmp(pi->send->local_name, pi->send->net_name) == 0 ) { log("send: \"%s\" %d bytes", pi->send->local_name, pi->send->bytes_total); } else { log("send: \"%s\" %d bytes -> \"%s\"", pi->send->local_name, pi->send->bytes_total, pi->send->net_name); } return 0; } void prot_traffic_update(s_traffic *traf, size_t size, int xtime, int type) { if( type & TYPE_REQANSW ) { traf->freqed_size += size; traf->freqed_time += xtime; traf->freqed_num++; } else if( type & TYPE_NETMAIL ) { traf->netmail_size += size; traf->netmail_time += xtime; traf->netmail_num++; } else if( type & TYPE_ARCMAIL ) { traf->arcmail_size += size; traf->arcmail_time += xtime; traf->arcmail_num++; } else { traf->files_size += size; traf->files_time += xtime; traf->files_num++; } } size_t prot_traffic_total_size(const s_traffic *traff) { return traff->netmail_size + traff->arcmail_size + traff->files_size + traff->freqed_size; } int prot_traffic_total_num(const s_traffic *traff) { return traff->netmail_num + traff->arcmail_num + traff->files_num + traff->freqed_num; } /***************************************************************************** * Close sending file. If file was sent successfully than make appropriate * actions (e.g. unlink/truncate file) * * Arguments: * pi pointer to the protocol information structure * * Return value: * Zero value on success and non-zero to indicate error */ int p_tx_fclose(s_protinfo *pi) { long trans_time = 0; long cps = 0; if( pi->send->fp ) { (void)file_close(pi->send->fp); pi->send->fp = NULL; } /* * Calculate send time, CPS, etc */ trans_time = (long)time(NULL) - (long)pi->send->start_time; if( trans_time > 0 ) cps = (long)((pi->send->bytes_sent - pi->send->bytes_skipped) / trans_time); log("sent: \"%s\" %ld [%s]: %ld seconds, %ld CPS", pi->send->net_name, (long)(pi->send->bytes_sent - pi->send->bytes_skipped), (pi->send->status == FSTAT_SKIPPED) ? "skipped" : (pi->send->status == FSTAT_REFUSED) ? "refused" : (pi->send->status == FSTAT_SUCCESS) ? "ok" : "error", (long)(trans_time > 0) ? trans_time : 0, (long)(cps > 0) ? cps : 0); /* * Update outgoing traffic structure */ prot_traffic_update(&pi->traffic_sent, pi->send->bytes_sent - pi->send->bytes_skipped, trans_time, pi->send->type); /* * Calculate time of sending FREQ'ed files */ if( (pi->send->type & TYPE_REQANSW) == TYPE_REQANSW ) pi->send_freqtime += trans_time; /* * Do necessary actions */ if( pi->send->status == FSTAT_SUCCESS || pi->send->status == FSTAT_SKIPPED ) { FILE *tfp; if( (pi->send->type & TYPE_FROMFLO) ) out_flo_marksent(pi->send->fname, state.queue.flotab[pi->send->flodsc].fname); switch(pi->send->action) { case ACTION_NOTHING: DEB((D_PROT, "p_tx_fclose: do nothing with file \"%s\"", pi->send->fname)); break; case ACTION_UNLINK: case ACTION_FORCEUNLINK: DEB((D_PROT, "p_tx_fclose: unlinking file \"%s\"", pi->send->fname)); if( unlink(pi->send->fname) && errno != ENOENT ) logerr("send: cannot unlink file \"%s\"", pi->send->fname); break; case ACTION_TRUNCATE: DEB((D_PROT, "p_tx_fclose: truncating file \"%s\"", pi->send->fname)); if( (tfp = fopen(pi->send->fname, "w")) ) fclose(tfp); else if( errno != ENOENT ) logerr("send: cannot truncate file \"%s\"", pi->send->fname); break; } } return 0; } /***************************************************************************** * Read next part of file to the buffer. User by transmitter. * * Arguments: * buffer pointer to the buffer * buflen buffer size * pi pointer to the protocol information structure * * Return value: * >= 0 - number of bytes read * -1 - error reading file (must try to send file later) * -2 - file must be skipped (send never) */ int p_tx_readfile(char *buffer, size_t buflen, s_protinfo *pi) { int n; struct stat st; long ftell_pos = ftell(pi->send->fp); pi->send->eofseen = FALSE; /* clear EOF flag */ /* * Sanity check: sync our transmitter position * with the position returned by the ftell() */ if( ftell_pos < 0 ) { log("Error: ftell() return %ld for the file \"%s\"", ftell_pos, pi->send->fname); pi->send->status = FSTAT_REFUSED; return -1; } /* * Try receive file later, if such error occurs */ if( pi->send->bytes_sent != ftell_pos ) { log("internal error: invalid transmitting offset"); log("pi->send->bytes_sent = %ld, ftell() = %ld", (long)pi->send->bytes_sent, ftell_pos); pi->send->bytes_received = ftell_pos; return -1; } if( stat(pi->send->fname, &st) == -1 && errno == ENOENT ) { log("send: file not found! do you want to skip it?"); pi->send->status = FSTAT_SKIPPED; return -2; } else if( (n = fread(buffer, 1, buflen, pi->send->fp)) < buflen ) { if( feof(pi->send->fp) ) pi->send->eofseen = TRUE; else { log("send: error reading file from disk (ferror = %d)", ferror(pi->send->fp)); pi->send->status = FSTAT_REFUSED; return -1; } } return n; } /***************************************************************************** * Write buffer to the file. Used by receiver. * * Arguments: * buffer pointer to the buffer * buflen number of bytes in buffer * pi pointer to the protocol information structure * * Return value: * 0 - successfully written * -1 - error writing file (receive later) * -2 - file must be skipped (receive never) */ int p_rx_writefile(const char *buffer, size_t buflen, s_protinfo *pi) { struct stat st; long ftell_pos = ftell(pi->recv->fp); /* * Sanity check: sync our receiver position * with the position returned by the ftell() */ if( ftell_pos < 0 ) { log("Error: ftell() return %ld for the file \"%s\"", ftell_pos, pi->recv->fname); pi->recv->status = FSTAT_REFUSED; return -1; } /* * Try receive file later, if such error occurs */ if( pi->recv->bytes_received != ftell_pos ) { log("internal error: invalid receiving offset"); log("pi->recv->bytes_received = %ld, ftell() = %ld", (long)pi->recv->bytes_received, ftell_pos); pi->recv->bytes_received = ftell_pos; return -1; } if( stat(pi->recv->fname, &st) == -1 ) { fflush(pi->recv->fp); if( stat(pi->recv->fname, &st) == -1 && errno == ENOENT ) { log("recv: skip file \"%s\" (file not found)", pi->recv->fname); pi->recv->status = FSTAT_SKIPPED; return -2; } } if( buflen > 0 && fwrite(buffer, buflen, 1, pi->recv->fp) != 1 ) { log("recv: error writing file \"%s\" to disk (ferror = %d)", pi->recv->fname, ferror(pi->recv->fp)); pi->recv->status = FSTAT_REFUSED; return -1; } return 0; } /***************************************************************************** * Move file from temporary inbound to main * * Arguments: * pi pointer to the protocol information structure * * Return value: * 0 - successfully processed file * 1 - error occured (mailer must refuse this file (receive later)) */ static int p_move2inbound(s_protinfo *pi) { int rc = 0; char *realname = NULL; char *uniqname = NULL; char *destname = NULL; if( state.inbound && pi->recv->local_name ) { realname = (char *)xstrcpy(state.inbound); realname = (char *)xstrcat(realname, pi->recv->local_name); } else return 1; if( access(realname, F_OK) == -1 ) { destname = realname; } else { if( (uniqname = prot_unique_name(state.inbound, pi->recv->local_name, pi->recv->type)) == NULL ) { log("recv: cannot get unique name for \"%s\"", pi->recv->local_name); free(realname); return 1; } if( !strcmp(realname, uniqname) ) { log("recv: overwriting \"%s\"", file_getname(uniqname)); } else { log("recv: rename \"%s\" -> \"%s\"", pi->recv->local_name, file_getname(uniqname)); } destname = uniqname; } if( (rc = rename(pi->recv->fname, destname)) == -1 ) { logerr("recv: cannot rename \"%s\" -> \"%s\"", pi->recv->fname, destname); } else { mode_t fmode = 0; /* * Change new file permissions */ if( (pi->recv->type & TYPE_NETMAIL) == TYPE_NETMAIL ) fmode = conf_filemode(cf_mode_netmail); else if( (pi->recv->type & TYPE_ARCMAIL) == TYPE_ARCMAIL ) fmode = conf_filemode(cf_mode_arcmail); else if( (pi->recv->type & TYPE_REQUEST) == TYPE_REQUEST ) fmode = conf_filemode(cf_mode_request); else if( (pi->recv->type & TYPE_TICFILE) == TYPE_TICFILE ) fmode = conf_filemode(cf_mode_ticfile); if( fmode == 0 ) fmode = conf_filemode(cf_mode_default); if( chmod(destname, fmode ? fmode : (S_IRUSR|S_IWUSR)) == -1 ) logerr("recv: cannot set permissions for file \"%s\"", destname); } if( realname ) free(realname); if( uniqname ) free(uniqname); return rc ? 1 : 0; } /***************************************************************************** * Compare the two file informations. One specified by file information * structure (s_finfo) and another specified by other variables. * * Arguments: * finf pointer to the file information * fn file name * sz file size * tm file modification time * * Return value: * Zero value if two files with such parameters seems to be the same * file, and non-zero if it is different files */ int p_compfinfo(s_finfo *finf, const char *fn, size_t sz, time_t tm) { return ( sz == finf->bytes_total && tm == finf->mod_time && strcmp(fn, finf->net_name) == 0 ) ? 0 : 1; } /***************************************************************************** * Open file for receiving. If file with such name allready exist: * 1) if timestamp and size identical to existing one, then skip; * 2) if only timestamp is equal and size of existing file is shorter then * it possible was an unfinished transfer - try to recover; * 3) in all other cases rename receiving file. * * Arguments: * pi pointer to the protocol information structure * fn file name | * sz file size | \ as it was reported by * tm file last modification time | / remote side (need checks) * mode file permissions | * * Return value: * 0 - successful call * 1 - refuse this file (receive later) * 2 - skip this file (never receive it) */ int p_rx_fopen(s_protinfo *pi, char *fn, size_t sz, time_t tm, mode_t mode) { const char *openmode = "w"; /* Set open mode to overwrite */ struct stat st; const char *p_skiplist = NULL; const char *p_delaylist = NULL; size_t minfree = 0; size_t needed_bytes_total = 0; /* * Check. May be we are receiving this file allready */ if( pi->recv && p_compfinfo(pi->recv, fn, sz, tm) == 0 ) { log("recv: got duplicated file information"); if( pi->recv->fp ) return 0; else if( pi->recv->status == FSTAT_SKIPPED ) return 2; else if( pi->recv->status == FSTAT_REFUSED ) return 1; } /* Reset mincps time counter */ pi->rx_low_cps_time = 0; /* * Add new entry to the receive files queue */ if( pi->rcvdfiles && pi->n_rcvdfiles > 0 ) { pi->rcvdfiles = (s_finfo *)xrealloc(pi->rcvdfiles, sizeof(s_finfo)*(pi->n_rcvdfiles+1)); memset(&pi->rcvdfiles[pi->n_rcvdfiles], '\0', sizeof(s_finfo)); pi->recv = &pi->rcvdfiles[pi->n_rcvdfiles++]; } else { pi->rcvdfiles = (s_finfo *)xmalloc(sizeof(s_finfo)); memset(pi->rcvdfiles, '\0', sizeof(s_finfo)); pi->recv = pi->rcvdfiles; pi->n_rcvdfiles = 1; } /* * Set file information */ pi->recv->net_name = (char*)xstrcpy(fn); pi->recv->local_name = xstrcpy(file_getname(fn)); pi->recv->bytes_total = sz; pi->recv->mod_time = tm; pi->recv->mode = mode; pi->recv->start_time = time(NULL); pi->recv->eofseen = FALSE; pi->recv->status = FSTAT_PROCESS; pi->recv->type = out_filetype(pi->recv->net_name); pi->recv->action = ACTION_NOTHING; pi->recv->flodsc = 0; /* * Convert file name to local charset */ recode_file_in(pi->recv->local_name); /* * Remove undesirable characters from file name */ file_name_makesafe(pi->recv->local_name); /* * Upper case file names convert to lower case. */ if( string_isupper(pi->recv->local_name) ) string_tolower(pi->recv->local_name); if( strcmp(pi->recv->local_name, pi->recv->net_name) ) { log("recv: \"%s\" %d bytes -> \"%s\"", string_printable(pi->recv->net_name), pi->recv->bytes_total, string_printable(pi->recv->local_name)); } else { log("recv: \"%s\" %d bytes", string_printable(pi->recv->local_name), pi->recv->bytes_total); } /* * Get `DelayFilesIn', `Skipfiles' and `MinFree' values */ p_skiplist = conf_string(cf_skip_files_recv); p_delaylist = conf_string(cf_delay_files_recv); minfree = conf_number(cf_min_free_space); /* * Should we delay(refuse) this file NOW? */ if( p_delaylist && !checkmasks(p_delaylist, pi->recv->net_name) ) { log("recv: file from delaylist -> refuse"); pi->recv->status = FSTAT_REFUSED; return(1); } /* * Should we skip this file NOW? */ if( p_skiplist && !checkmasks(p_skiplist, pi->recv->net_name) ) { log("recv: file from skiplist -> skip"); pi->recv->status = FSTAT_SKIPPED; return(2); } /* * Check. May be we allready have this file */ if( (pi->recv->type & TYPE_REQUEST) || (pi->recv->type & TYPE_NETMAIL) ) { pi->recv->fname = p_gettmpname(state.tinbound, pi->recv->local_name, state.node.addr, pi->recv->bytes_total, pi->recv->mod_time); } else /* It is not netmail or filerequest */ { char *fname; fname = (char *)xstrcpy(state.inbound); fname = (char *)xstrcat(fname, pi->recv->local_name); if( stat(fname, &st ) == 0 ) { /* Skip file if we allready have it's copy */ if( pi->recv->mod_time == localtogmt(st.st_mtime) && pi->recv->bytes_total == st.st_size ) { log("recv: allready have \"%s\"", fname); pi->recv->status = FSTAT_SKIPPED; } } free(fname); fname = NULL; if( pi->recv->status == FSTAT_SKIPPED ) return 2; pi->recv->fname = p_gettmpname(state.tinbound, pi->recv->local_name, state.node.addr, pi->recv->bytes_total, pi->recv->mod_time); /* Now make same check for temporary inbound */ if( stat(pi->recv->fname, &st) == 0 ) { /* Skip if it is the same file */ if( pi->recv->mod_time == localtogmt(st.st_mtime) && pi->recv->bytes_total == st.st_size ) { /* * We will skip it and try to move it into inbound * directory, do you know who could left it here ? :) */ log("recv: allready have \"%s\"", pi->recv->fname); if( p_move2inbound(pi) == 0 ) pi->recv->status = FSTAT_SKIPPED; else pi->recv->status = FSTAT_REFUSED; free(pi->recv->fname); pi->recv->fname = NULL; return pi->recv->status == FSTAT_SKIPPED ? 2 : 1; } else if( pi->recv->mod_time == localtogmt(st.st_mtime) && pi->recv->bytes_total >= st.st_size ) { pi->recv->bytes_skipped = st.st_size; pi->recv->bytes_received = pi->recv->bytes_skipped; log("recv: receiving \"%s\" from offset %ld", pi->recv->fname, (long)pi->recv->bytes_skipped); openmode = "a"; } } } /* * Check, there is enough space in our inbound */ if (openmode == "a") needed_bytes_total = minfree + pi->recv->bytes_total - pi->recv->bytes_skipped; else needed_bytes_total = minfree + pi->recv->bytes_total; if( minfree > 0 && getfreespace(state.inbound) < needed_bytes_total ) { log("recv: not enough free space in inbound -> refuse"); pi->recv->status = FSTAT_REFUSED; return(1); } DEB((D_PROT, "p_rx_fopen: call fopen(\"%s\", \"%s\")", pi->recv->fname, openmode)); if( (pi->recv->fp = file_open(pi->recv->fname, openmode)) == NULL ) { logerr("recv: cannot open \"%s\" -> refuse", pi->recv->fname); pi->recv->status = FSTAT_REFUSED; free(pi->recv->fname); pi->recv->fname = NULL; return 1; } if( pi->buffer && pi->buflen > 0 ) { #ifdef SETVBUF_REVERSED setvbuf(pi->recv->fp, _IOFBF, pi->buffer, pi->buflen); #else setvbuf(pi->recv->fp, pi->buffer, _IOFBF, pi->buflen); #endif } return 0; } /***************************************************************************** * Close receiving file. If it was completly received - move it to the main * inbound directory. * * Arguments: * pi pointer to the protocol information structure * * Return value: * 0 - successfull call * 1 - error occured, refuse this file (receive later) */ int p_rx_fclose(s_protinfo *pi) { long trans_time = 0; long cps = 0; int rc = 0; struct stat st; DEB((D_PROT, "p_rx_fclose: close file \"%s\"", pi->recv->fname)); if( pi->recv->fp ) { rc = file_close(pi->recv->fp); pi->recv->fp = NULL; if( rc ) { /* * This may be any sort of errors, including * random data corruptions. Return error. */ logerr("recv: cannot close file \"%s\"", pi->recv->fname); return 1; } } if( pi->recv->status == FSTAT_SKIPPED ) { /* * Remove skipped file from temp. inbound */ if( unlink(pi->recv->fname) == -1 && errno != ENOENT ) logerr("recv: cannot remove skipped file \"%s\"", pi->recv->fname); } else { /* * Set original file modification time */ if( pi->recv->mod_time ) { struct utimbuf tvp; tvp.actime = time(NULL); tvp.modtime = gmttolocal(pi->recv->mod_time); utime(pi->recv->fname, &tvp); } /* * Set default file permission to 0600 */ if( chmod(pi->recv->fname, (S_IRUSR | S_IWUSR)) == -1 ) logerr("recv: cannot change mode for file \"%s\"", pi->recv->fname); } /* * Update incoming traffic structure */ prot_traffic_update(&pi->traffic_rcvd, pi->recv->bytes_received - pi->recv->bytes_skipped, trans_time, pi->recv->type); /* * If file was received successfuly */ if( pi->recv->status == FSTAT_SUCCESS ) { /* * Sanity check. Compare real file length with the * expected length (reported by the remote side) */ if( stat(pi->recv->fname, &st) == -1 ) { log("cannot stat received file \"%s\"", pi->recv->fname); pi->recv->status = FSTAT_REFUSED; } else if( st.st_size != pi->recv->bytes_total ) { log("received file has invalid size %ld (%ld expected)", (long)st.st_size, (long)pi->recv->bytes_total); pi->recv->status = FSTAT_REFUSED; } else if( state.reqstat == REQS_ALLOW && (pi->recv->type & TYPE_REQUEST) ) { /* Run our freq processor */ req_proc(pi->recv->fname, &pi->filelist); unlink(pi->recv->fname); } else if( p_move2inbound(pi) ) { log("recv: can't move file into main inbound -> refuse"); pi->recv->status = FSTAT_REFUSED; } } /* * Calculate receive time, CPS, etc */ trans_time = (long)time(NULL) - (long)pi->recv->start_time; if( trans_time > 0 ) cps = (long)((pi->recv->bytes_received - pi->recv->bytes_skipped) / trans_time); log("rcvd: \"%s\" %ld [%s]: %ld seconds, %ld CPS", pi->recv->local_name, (long)(pi->recv->bytes_received - pi->recv->bytes_skipped), (pi->recv->status == FSTAT_SKIPPED) ? "skipped" : (pi->recv->status == FSTAT_REFUSED) ? "refused" : (pi->recv->status == FSTAT_SUCCESS) ? "ok" : "error", (long)(trans_time > 0) ? trans_time : 0, (long)(cps > 0) ? cps : 0); return pi->recv->status == FSTAT_SUCCESS ? 0 : 1; } /***************************************************************************** * Check current receiving/sending speeds (CPS), check time limits. * * Arguments: * pi pointer to the protocol information structure * bidir if hydra, check only one CPS value * * Return value: * PRC_NOERROR - all ok * PRC_STOPTIME - abort session due to time limits * PRC_CPSTOOLOW - abort session due low speed */ int p_info(s_protinfo *pi, int bidir) { int rc = 0, rx_cps_low = 0, tx_cps_low = 0; time_t tx_now, rx_now, now; now = time(NULL); if( pi->send && pi->send->status == FSTAT_PROCESS ) { tx_now = (long)time(NULL) - (long)pi->send->start_time; pi->tx_cps = (pi->send->bytes_sent - pi->send->bytes_skipped) / (tx_now ? tx_now : 1); /* Is it FREQ answer? */ if( pi->freq_timelimit > 0 && (pi->send->type & TYPE_REQANSW) ) { /* * Check FREQ time limit not reached? */ if( pi->send_freqtime + tx_now > pi->freq_timelimit ) { log("reached FREQ time limit %d second(s)", pi->freq_timelimit); rc = PRC_STOPTIME; } } /* Check is transmiting CPS to low */ if( pi->min_tx_cps ) { if( pi->tx_low_cps_time ) { if( pi->tx_cps < pi->min_tx_cps ) { if( now - pi->tx_low_cps_time >= pi->min_cps_time ) { tx_cps_low = 1; } } else { pi->tx_low_cps_time = 0; } } else if( pi->tx_cps < pi->min_tx_cps ) { pi->tx_low_cps_time = now; } } } /* end of if( txi ) */ if( pi->recv && pi->recv->status == FSTAT_PROCESS ) { rx_now = (long)time(NULL) - (long)pi->recv->start_time; pi->rx_cps = (pi->recv->bytes_received - pi->recv->bytes_skipped) / (rx_now ? rx_now : 1); /* Check is receiving CPS to low */ if( pi->min_rx_cps ) { if( pi->rx_low_cps_time ) { if( pi->rx_cps < pi->min_rx_cps ) { if( now - pi->rx_low_cps_time >= pi->min_cps_time ) { rx_cps_low = 1; } } else { pi->rx_low_cps_time = 0; } } else if( pi->rx_cps < pi->min_rx_cps ) { pi->rx_low_cps_time = now; } } } /* end of if( rxi ) */ /* check maybe now is a good time to stop it ? =) */ if( pi->stop_time && now >= pi->stop_time ) { log("reached stop time"); rc = PRC_STOPTIME; } if ( tx_cps_low ) { if ( !bidir || rx_cps_low ) { log("transmitting speed below %d cps", pi->min_tx_cps); rc = PRC_CPSTOOLOW; } } if ( rx_cps_low ) { if ( !bidir || tx_cps_low ) { log("receiving speed below %d cps", pi->min_rx_cps); rc = PRC_CPSTOOLOW; } } return(rc); } /***************************************************************************** * Log short session statistic. Output overall Send/Received bytes, avreage * CPS, on-line time * * Arguments: * pi pointer to the protocol information structure * * Return value: * None */ void p_log_txrxstat(const s_protinfo *pi) { char abuf[BF_MAXADDRSTR+1]; s_faddr tmpaddr = state.node.addr; int total_time = time_elapsed(pi->start_time); int files_sent = prot_traffic_total_num(&pi->traffic_sent); int files_rcvd = prot_traffic_total_num(&pi->traffic_rcvd); size_t bytes_sent = prot_traffic_total_size(&pi->traffic_sent); size_t bytes_rcvd = prot_traffic_total_size(&pi->traffic_rcvd); size_t total_traff = bytes_sent + bytes_rcvd; size_t total_cps = (total_traff / (total_time ? total_time : 1)); tmpaddr.domain[0] = '\0'; /* remove domain from address */ log("%s, S/R %ld/%ld, %ld/%ld bytes in %ld seconds, %ld CPS", ftn_addrstr(abuf, tmpaddr), files_sent, files_rcvd, (long)bytes_sent, (long)bytes_rcvd, (long)((total_time > 0) ? total_time : 0), (long)((total_cps > 0) ? total_cps : 0)); } /***************************************************************************** * Do session cleanup. Remove temporary files, remove useless file requests * * Arguments: * pi pointer to the protocol information structure * * Return value: * None */ void p_session_cleanup(s_protinfo *pi, bool success) { int i; s_filelist *ptrl; if( success ) { /* * Remove sent file requests after successfull sessions */ for( i = 0; i < pi->n_sentfiles; i++ ) if( (pi->sentfiles[i].type & TYPE_REQUEST) && (pi->sentfiles[i].status == FSTAT_SUCCESS) ) { if( unlink(pi->sentfiles[i].fname) == -1 && errno != ENOENT ) { logerr("can't unlink '.req' file \"%s\"", pi->sentfiles[i].fname); } } } /* * Delete files with action ACTION_FORCEUNLINK */ for( ptrl = pi->filelist; ptrl; ptrl = ptrl->next ) { /* It must be allready deleted if it is sent */ if( ptrl->action == ACTION_FORCEUNLINK && ptrl->status != STATUS_SENT ) { if( unlink(ptrl->fname) && errno != ENOENT ) logerr("cannot unlink temporary file \"%s\"", ptrl->fname); } } } /***************************************************************************** * Do all necessary convertions and checks with file names before sending it * * Arguments: * origname pointer to the original file name * type file type (netmail, arcmail, etc.) * * Return value: * Pointer to the new file name (must be freed) */ char *p_convfilename(const char *origname, int type) { const char *p; char *dest = NULL; long crc = 0L; s_faddr addr; if( (type & TYPE_NETMAIL) ) { /* * Generate random file name for netmail packets */ dest = (char*)xmalloc(32); sprintf(dest, "%08lx.pkt", (long)rand()); } else if( (type & TYPE_ASONAME) == TYPE_ASONAME && (type & TYPE_ARCMAIL) == TYPE_ARCMAIL ) { /* * Generate new file name for arcmail from AmigaDos style * outbound, such files looks like "2.5020.1682.9.fr1". */ if( (p = strrchr(origname, '.')) ) crc = getcrc32ccitt(origname, p - origname - 1); else crc = getcrc32ccitt(origname, strlen(origname)); dest = (char*)xmalloc(32); sprintf(dest, "%08lx%s", (long)crc, p?p:".fr0"); } else if( (type & TYPE_ASONAME) == TYPE_ASONAME && (type & TYPE_REQUEST) == TYPE_REQUEST ) { /* * Generate new file name for file request from AmigaDos * style outbound, such files looks like "2.5020.1682.0.req". */ dest = (char*)xmalloc(32); if( out_parse_name_aso(&addr, origname) == 0 ) sprintf(dest, "%04x%04x.req", addr.net, addr.node); else sprintf(dest, "%08lx.req", (long)rand()); } else { /* * All other files send with their real name, but: * 1) remove "nonprintable" characters * 2) convert files to 8+3 format if remote has no long * file names support (indicated by $state.sopts.fnc) */ if( state.sopts.fnc ) { dest = (char*)xmalloc(13); file_get_dos_name(dest, origname); } else { dest = (char*)xstrcpy(origname); if( string_isupper(dest) ) string_tolower(dest); } } return dest; } /***************************************************************************** * Get temporary file name with full path. * * Arguments: * inbound path to temporary inbound directory * filename file name * addr remote address \ * sz file size > Used to generate unique file names * tm file time / * * Return value: * Pointer to the temporary file name (must be freed) */ char *p_gettmpname(const char *inbound, char *filename, s_faddr addr, size_t sz, time_t tm) { char *dest; char abuf[BF_MAXADDRSTR+1]; char bbuf[BF_MAXADDRSTR+30]; char cbuf[16]; sprintf(bbuf, "%s %lx %lx", ftn_addrstr(abuf, addr), (long)sz, (long)tm); sprintf(cbuf, "%lx", (long)getcrc32ccitt(bbuf, strlen(bbuf))); dest = xstrcpy(inbound); dest = xstrcat(dest, filename); dest = xstrcat(dest, "."); dest = xstrcat(dest, cbuf); return dest; } #define MAX_TRIES 1024 /***************************************************************************** * Get new file name for received file. Use it when we allready have * file with such name in inbound directory. * * Rename file with the next algorithm: * * - For file request. Overwrite existing file * * - For netmail or ticfile. Generate new unique * file name. (1234abcde.pkt -> ab04d51f.pkt) * * - For arcmail. Rotate extension chars to make * unique name. (057d63a6.su0 -> 057d63a6.su1) * * - For other files. Try to add a numeric value * as additional extension ("net5020.ndl.1") * * Arguments: * dirname pointer to the directory (inbound) * fname pointer to the file name * type file type (netmail, arcmail, etc.) * * Return value: * Pointer to the new file name with path (must be freed), and * NULL to indicate error */ char *prot_unique_name(char *dirname, char *fname, int type) { int try = 0; int offs = 0; char *result = NULL; char *p; if( (type & TYPE_REQUEST) ) { result = (char *)xstrcpy(dirname); result = (char *)xstrcat(result, fname); } else if( (type & TYPE_NETMAIL) || (type & TYPE_TICFILE) ) { char *ext = (type & TYPE_NETMAIL) ? "pkt" : "tic"; offs = strlen(dirname); result = (char *)xmalloc(offs + 36); strncpy(result, dirname, offs + 35); result[offs + 35] = '\0'; do { ++try; sprintf(result+offs, "%08lx.%s", (long)rand(), ext); } while( !access(result, F_OK) && try < MAX_TRIES ); } else if( (type & TYPE_ARCMAIL) ) { result = (char *)xstrcpy(dirname); result = (char *)xstrcat(result, fname); p = result + strlen(result) - 1; do { ++try; if( (*p >= '0') && (*p <= '8') ) ++*p; else if( (*p >= 'A') && (*p <= 'Y') ) ++*p; else if( (*p >= 'a') && (*p <= 'y') ) ++*p; else if( (*p == '9') ) *p = 'a'; else if( (*p == 'z') ) *p = 'A'; else if( --p < result || *p == '.' || *p == '/' ) { free(result); result = NULL; break; } } while( !access(result, F_OK) && try < MAX_TRIES ); } else { result = (char *)xstrcpy(dirname); result = (char *)xstrcat(result, fname); offs = strlen(result); result = (char *)xrealloc(result, offs + 16); if( result[offs-1] != '.' ) { result[offs++] = '.'; result[offs ] = '\0'; } do { ++try; sprintf(result+offs, "%d", try); } while( !access(result, F_OK) && try < MAX_TRIES ); } if( try >= MAX_TRIES ) { if( result ) free(result); return NULL; } return result; } /***************************************************************************** * Release memory used by file information structure * * Arguments: * fi pointer to the file information structure * * Return value: * None */ void deinit_finfo(s_finfo *fi) { if( fi->fp ) fclose(fi->fp); if( fi->local_name ) free(fi->local_name); if( fi->net_name ) free(fi->net_name); if( fi->fname ) free(fi->fname); memset(fi, '\0', sizeof(s_finfo)); } /***************************************************************************** * Init protocol information structure. Set all necessary options, such as * minimal CPSes, start/stop time, etc. * * Arguments: * pi pointer to the protocol information structure * caller are we caller? * * Return value: * None */ void init_protinfo(s_protinfo *pi, bool caller) { long tmp; long sesslimit; memset(pi, '\0', sizeof(s_protinfo)); pi->start_time = time(NULL); if( (pi->buflen = conf_number(cf_recv_buffer_size)) > 0 ) pi->buflen = (pi->buflen / 2048) * 2048; else pi->buflen = 32768; if( pi->buflen > 0 ) pi->buffer = (char*)xmalloc(pi->buflen); if( (tmp = conf_number(cf_min_cps_time)) > 0 ) pi->min_cps_time = tmp; else pi->min_cps_time = 60; /* * Set abort time if session limit was specified */ if( caller ) sesslimit = conf_number(cf_session_limit_out); else sesslimit = conf_number(cf_session_limit_in); if( sesslimit > 0 ) pi->stop_time = time(0) + sesslimit; /* * Set minimal transmit speed (CPS) */ if( (tmp = conf_number(cf_min_cps_send)) > 0 ) pi->min_tx_cps = tmp; else { if( state.handshake->protocol == PROT_ZMODEM || state.handshake->protocol == PROT_ZEDZAP || state.handshake->protocol == PROT_DIRZAP ) tmp = conf_connlist(cf_zmodem_mincps_send, state.connspeed); else if( state.handshake->protocol == PROT_HYDRA ) tmp = conf_connlist(cf_hydra_mincps_send, state.connspeed); else tmp = 0; if( tmp > 0 ) pi->min_tx_cps = tmp; } /* * Set minimal receive speed (CPS) */ if( (tmp = conf_number(cf_min_cps_recv)) > 0 ) pi->min_rx_cps = tmp; else { if( state.handshake->protocol == PROT_ZMODEM || state.handshake->protocol == PROT_ZEDZAP || state.handshake->protocol == PROT_DIRZAP ) tmp = conf_connlist(cf_zmodem_mincps_recv, state.connspeed); else if( state.handshake->protocol == PROT_HYDRA ) tmp = conf_connlist(cf_hydra_mincps_recv, state.connspeed); else tmp = 0; if( tmp > 0 ) pi->min_rx_cps = tmp; } /* * Report about selected limits */ if( sesslimit > 0 && (pi->min_tx_cps > 0 || pi->min_rx_cps > 0) ) log("use restrictions: %d/%d cps, %d seconds", pi->min_tx_cps, pi->min_rx_cps, sesslimit); else if( pi->min_tx_cps > 0 || pi->min_rx_cps > 0 ) log("use restrictions: %d/%d cps", pi->min_tx_cps, pi->min_rx_cps); else if( sesslimit > 0 ) log("use restrictions: %d seconds", sesslimit); DEB((D_PROT, "protinfo_init: min_tx_cps = %ld, min_rx_cps = %ld", (long)pi->min_tx_cps, (long)pi->min_rx_cps)); DEB((D_PROT, "protinfo_init: buffersize = %ld, stoptime = %ld", (long)pi->min_tx_cps, (long)pi->min_rx_cps)); } /***************************************************************************** * Release memory used by protocol information structure * * Arguments: * pi pointer to the protocol information structure * * Return value: * None */ void deinit_protinfo(s_protinfo *pi) { int i; for( i = 0; i < pi->n_sentfiles; i++ ) deinit_finfo(&pi->sentfiles[i]); for( i = 0; i < pi->n_rcvdfiles; i++ ) deinit_finfo(&pi->rcvdfiles[i]); if( pi->buffer ) free(pi->buffer); if( pi->sentfiles ) free(pi->sentfiles); if( pi->rcvdfiles ) free(pi->rcvdfiles); memset(pi, '\0', sizeof(s_protinfo)); }