/***************************************************************************** * * $Id$ * Purpose ...............: Fidonet mailer - Hydra protocol driver * Remark ................: See below for more copyright details and credits. * ***************************************************************************** * Copyright (C) 1997-2001 * * Michiel Broek FIDO: 2:280/2802 * Beekmansbos 10 * 1971 BV IJmuiden * the Netherlands * * This file is part of MBSE BBS. * * This BBS 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, or (at your option) any * later version. * * MBSE BBS is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MBSE BBS; see the file COPYING. If not, write to the Free * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. *****************************************************************************/ /* * ifcico v3.0.cm - hydra protocol module * Copyright (C) 1996-98 Christof Meerwald. */ /* * The HYDRA protocol was designed by * Arjen G. Lentz, LENTZ SOFTWARE-DEVELOPMENT and * Joaquim H. Homrighausen * COPYRIGHT (C) 1991-1993; ALL RIGHTS RESERVED */ #include "../config.h" #include "../lib/libs.h" #include "../lib/memwatch.h" #include "../lib/structs.h" #include "../lib/common.h" #include "../lib/clcomm.h" #include "session.h" #include "filelist.h" #include "filetime.h" #include "ttyio.h" #include "statetbl.h" #include "config.h" #include "emsi.h" #include "openfile.h" #include "lutil.h" #include "respfreq.h" #include "mbcico.h" #include "hydra.h" #define H_RXWINDOW 0L #define H_TXWINDOW 0L static int put_binbyte(char *outbuf, char c); static int put_hexbyte(char *outbuf, char c); static enum HyPktTypes hyrxpkt(char *rxbuf, int *rxlen, int tot); static void hytxpkt(enum HyPktTypes pkttype, char *txbuf, int txlen); static int put_flags(char *buf, unsigned long Flags); static unsigned long get_flags(char *buf); static int resync(off_t off); static int hydra_batch(int role, file_list *to_send); extern unsigned long sentbytes; extern unsigned long rcvdbytes; static struct h_flags_struct { char *str; unsigned long val; } h_flags[] = { { (char *)"XON", HOPT_XONXOFF }, { (char *)"TLN", HOPT_TELENET }, { (char *)"CTL", HOPT_CTLCHRS }, { (char *)"HIC", HOPT_HIGHCTL }, { (char *)"HI8", HOPT_HIGHBIT }, { (char *)"BRK", HOPT_CANBRK }, { (char *)"ASC", HOPT_CANASC }, { (char *)"UUE", HOPT_CANUUE }, { (char *)"C32", HOPT_CRC32 }, { (char *)"DEV", HOPT_DEVICE }, { (char *)"FPT", HOPT_FPT }, { NULL , 0x0L } }; static int txoptions, rxoptions; static char *put_long(char *buffer, long val) { #if defined(__i386__) *(unsigned long *) buffer = (unsigned long) val; #else buffer[0] = (unsigned long) val & 0xff; buffer[1] = ((unsigned long) val >> 8) & 0xff; buffer[2] = ((unsigned long) val >> 16) & 0xff; buffer[3] = ((unsigned long) val >> 24) & 0xff; #endif return buffer; } static long get_long(char *buffer) { #if defined(__i386__) return *(long *) buffer; #else return ((unsigned long) ((unsigned char) buffer[0])) | ((unsigned long) ((unsigned char) buffer[1]) << 8) | ((unsigned long) ((unsigned char) buffer[2]) << 16) | ((unsigned long) ((unsigned char) buffer[3]) << 24); #endif } char *PktS(int); char *PktS(int c) { switch (c) { case 'A' : return (char *)"START"; case 'B' : return (char *)"INIT"; case 'C' : return (char *)"INITACK"; case 'D' : return (char *)"FINFO"; case 'E' : return (char *)"FINFOACK"; case 'F' : return (char *)"DATA"; case 'G' : return (char *)"DATAACK"; case 'H' : return (char *)"RPOS"; case 'I' : return (char *)"EOF"; case 'J' : return (char *)"EOFACK"; case 'K' : return (char *)"END"; case 'L' : return (char *)"IDLE"; case 'M' : return (char *)"DEVDATA"; case 'N' : return (char *)"DEVDACK"; case 'a' : return (char *)"PKTEND"; case 'b' : return (char *)"BINPKT"; case 'c' : return (char *)"HEXPKT"; case 'd' : return (char *)"ASCPKT"; case 'e' : return (char *)"UUEPKT"; default: break; } return (char *)""; } int put_binbyte(char *outbuf, char c) { static int lastc = -1; register int count = 0; register char n; n = c; if (txoptions & HOPT_HIGHCTL) { n &= 0x7f; } if ((n == H_DLE) || ((txoptions & HOPT_XONXOFF) && ((n == XON) || (n == XOFF))) || ((txoptions & HOPT_TELENET) && (n == '\r') && (lastc == '@')) || ((txoptions & HOPT_CTLCHRS) && ((n < 32) || (n == 127)))) { *outbuf++ = H_DLE; c ^= 0x40; count++; } *outbuf++ = c; lastc = n; return count + 1; } int put_hexbyte(char *outbuf, char c) { static const char hexdigit[] = "0123456789abcdef"; register int count = 0; if (c & 0x80) { *outbuf++ = '\\'; *outbuf++ = hexdigit[(c >> 4) & 0x0f]; *outbuf++ = hexdigit[c & 0x0f]; count = 3; } else if ((c < 32) || (c == 127)) { *outbuf++ = H_DLE; *outbuf++ = c ^ 0x40; count = 2; } else if (c == '\\') { *outbuf++ = '\\'; *outbuf++ = '\\'; count = 2; } else { *outbuf++ = c; count = 1; } return count; } /* TODO: code cleanup */ /* TODO: error handling */ enum HyPktTypes hyrxpkt(char *rxbuf, int *rxlen, int tot) { static char rxencbuf[H_BUFLEN]; static enum HyPktTypes pkttype = H_NOPKT; static char *inbuf = rxencbuf, *outbuf; int c, i, n; static int rxdle = 0; static enum HyPktFormats format; while ((c = GETCHAR(tot)) >= 0) { if (rxoptions & HOPT_HIGHBIT) c &= 0x7f; n = c; if (rxoptions & HOPT_HIGHCTL) n &= 0x7f; if ((n != H_DLE) && (((rxoptions & HOPT_XONXOFF) && ((n == XON) || (n == XOFF))) || ((rxoptions & HOPT_CTLCHRS) && ((n < 32) || (n == 127))))) continue; if ((rxdle) || (c == H_DLE)) { switch (c) { case H_DLE: rxdle++; if (rxdle >= 5) { inbuf = rxencbuf; return H_CANCEL; } break; case HCHR_PKTEND: switch (format) { case HCHR_BINPKT: *rxlen = inbuf - rxencbuf; memcpy(rxbuf, rxencbuf, *rxlen); break; case HCHR_HEXPKT: outbuf = rxencbuf; *rxlen = 0; while (outbuf < inbuf) { if ((*outbuf == '\\') && (*++outbuf != '\\')) { i = *outbuf++; n = *outbuf++; if ((i -= '0') > 9) i -= ('a' - ':'); if ((n -= '0') > 9) n -= ('a' - ':'); if ((i & ~0x0f) || (n & ~ 0x0f)) { Syslog('+', "Hydra: RXPKT assert"); die(1); break; } rxbuf[*rxlen] = (i << 4) | n; *rxlen += 1; } else { rxbuf[*rxlen] = *outbuf++; *rxlen += 1; } } break; case HCHR_ASCPKT: case HCHR_UUEPKT: default: Syslog('+', "Hydra: RXPKT assert"); die(1); } if ((format != HCHR_HEXPKT) && (rxoptions & HOPT_CRC32)) { n = h_crc32test(crc32ccitt(rxbuf, *rxlen)); *rxlen -= 4; /* remove CRC-32 */ } else { n = h_crc16test(crc16ccitt(rxbuf, *rxlen)); *rxlen -= 2; /* remove CRC-16 */ } *rxlen -= 1; /* remove packet type */ pkttype = rxbuf[*rxlen]; /* check if CRC test succeeded */ if (n) { inbuf = rxencbuf; Syslog('h', "Hydra: RXPKT rcvd %s", PktS(pkttype)); return pkttype; } else { Syslog('+', "Hydra: RXPKT CRC test failed"); } break; case HCHR_BINPKT: case HCHR_HEXPKT: case HCHR_ASCPKT: case HCHR_UUEPKT: format = c; inbuf = rxencbuf; rxdle = 0; break; default: *inbuf++ = c ^ 0x40; rxdle = 0; break; } } else { *inbuf++ = c; } } Syslog('h', "GETCHAR returned %i", c); if ((c == TERROR) || (c == EOFILE) || (c == HANGUP)) { return H_CARRIER; } return H_NOPKT; } /* TODO: support packet prefix string */ void hytxpkt(enum HyPktTypes pkttype, char *txbuf, int txlen) { static char txencbuf[H_BUFLEN]; char *outbuf, *inbuf; enum HyPktFormats format; if (pkttype == 'G') Syslog('h', "ACK 0x%02x%02x%02x%02x", txbuf[0], txbuf[1], txbuf[2], txbuf[3]); /* * some packets have to be transferred in HEX mode */ if ((pkttype == HPKT_START) || (pkttype == HPKT_INIT) || (pkttype == HPKT_INITACK) || (pkttype == HPKT_END) || (pkttype == HPKT_IDLE)) { format = HCHR_HEXPKT; } else { /* do we need to strip high bit */ if (txoptions & HOPT_HIGHBIT) { if ((txoptions & HOPT_CTLCHRS) && (txoptions & HOPT_CANUUE)) { format = HCHR_UUEPKT; /* use UUE packet encoding */ } else if (txoptions & HOPT_CANASC) { format = HCHR_ASCPKT; /* we can use ASCII packet encoding */ } else { format = HCHR_HEXPKT; /* fall back to hex packet encoding */ } } else { format = HCHR_BINPKT; /* we can use binary packet encoding */ } } /* * Append format byte to data */ txbuf[txlen] = pkttype; txlen++; /* * check if we can use 32-bit CRC's */ if ((format != HCHR_HEXPKT) && (txoptions & HOPT_CRC32)) { unsigned long crc; /* * Calc CRC-32 of data + pkttype */ crc = ~crc32ccitt(txbuf, txlen); /* * Append one's complement of CRC to data, lowbyte first */ txbuf[txlen++] = crc; txbuf[txlen++] = crc >> 8; txbuf[txlen++] = crc >> 16; txbuf[txlen++] = crc >> 24; } else { unsigned short crc; /* * Calc CRC-16 of data + pkttype */ crc = ~crc16ccitt(txbuf, txlen); /* * Append one's complement of CRC to data, lowbyte first */ txbuf[txlen++] = crc; txbuf[txlen++] = crc >> 8; } inbuf = txbuf; outbuf = txencbuf; *outbuf++ = H_DLE; *outbuf++ = format; /* encode packet data */ switch (format) { case HCHR_BINPKT: while (txlen > 0) { outbuf += put_binbyte(outbuf, *inbuf); inbuf++; txlen--; } break; case HCHR_HEXPKT: while (txlen > 0) { outbuf += put_hexbyte(outbuf, *inbuf); inbuf++; txlen--; } break; /* ASCII and UUE packets are not yet supported */ case HCHR_ASCPKT: case HCHR_UUEPKT: default: Syslog('+', "Hydra: TXPKT assert"); die(1); } *outbuf++ = H_DLE; *outbuf++ = HCHR_PKTEND; if ((pkttype != HPKT_DATA) && (format != HCHR_BINPKT)) { *outbuf++ = '\r'; *outbuf++ = '\n'; } Syslog('h', "Hydra: TXPKT send %s", PktS(pkttype)); PUT(txencbuf, outbuf - txencbuf); return; } int put_flags(char *buf, unsigned long Flags) { int i, count = 0; for (i = 0; h_flags[i].val; i++) { if (Flags & h_flags[i].val) { if (count > 0) { *buf++ = ','; count++; } strcpy(buf, h_flags[i].str); buf += H_FLAGLEN; count += H_FLAGLEN; } } *buf = 0; return count; } unsigned long get_flags(char *buf) { unsigned long Flags = 0L; char *p; int i; for (p = strtok(buf, ","); p != NULL; p = strtok(NULL, ",")) { for (i = 0; h_flags[i].val; i++) { if (!strcmp(p, h_flags[i].str)) { Flags |= h_flags[i].val; break; } } } return Flags; } int resync(off_t off) { return 0; } int hydra_batch(int role, file_list *to_send) { static char txbuf[H_BUFLEN], rxbuf[H_BUFLEN]; struct stat txstat; /* file stat being transmitted */ FILE *txfp = NULL; /* file currently being transmitted */ FILE *rxfp = NULL; /* file currently being received */ char *inbuf, *outbuf; int rxlen, txlen; /* length of receive/transmit buffer */ long txwindow, rxwindow; /* window sizes */ long txpos; off_t rxpos; /* file positions */ long stxpos, srxpos; long longnum; int hdxlink = FALSE; int txretries, rxretries; int txlastack, txsyncid; int rxlastsync, rxsyncid; int rxlastdatalen; int blksize; int goodbytes; int goodneeded; enum HyTxStates txstate; enum HyRxStates rxstate; int txwaitpkt, rxwaitpkt; enum HyPktTypes pkttype; int waitputget = 0; struct timeval txstarttime, txendtime; struct timeval rxstarttime, rxendtime; struct timezone tz; int sverr; Syslog('h', "Hydra: resettimers"); RESETTIMERS(); txpos = rxpos = 0; stxpos = srxpos = 0; txretries = rxretries = 0; txlastack = txsyncid = 0; rxlastsync = rxsyncid = 0; rxlastdatalen = 0; blksize = 512; goodbytes = 0; goodneeded = 1024; Syslog('h', "Hydra: set BRAIN timer %d", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); txstate = HTX_START; txoptions = HTXI_OPTIONS; txstarttime.tv_sec = txstarttime.tv_usec = txendtime.tv_sec = txendtime.tv_usec = 0; rxstarttime.tv_sec = rxstarttime.tv_usec = rxendtime.tv_sec = rxendtime.tv_usec = 0; tz.tz_minuteswest = tz.tz_dsttime = 0; rxstate = HRX_INIT; rxoptions = HRXI_OPTIONS; while ((txstate != HTX_DONE) && (txstate != HTX_Abort)) { /* * Is transmitter waiting for packet? */ txwaitpkt = ((txstate == HTX_SWAIT) || (txstate == HTX_INITACK) || ((txstate == HTX_RINIT) && (rxstate == HRX_INIT)) || (txstate == HTX_FINFOACK) || (txstate == HTX_DATA) || (txstate == HTX_DATAACK) || ((txstate == HTX_XWAIT) && (rxstate != HRX_DONE)) || (txstate == HTX_EOFACK) || (txstate == HTX_ENDACK) || (txstate == HTX_REND)); /* * Is receiver waiting for packet? */ rxwaitpkt = ((rxstate == HRX_INIT) || (rxstate == HRX_FINFO) || (rxstate == HRX_DATA) || (rxstate == HRX_DONE)); /* * Do we have to wait for a packet? */ if (txwaitpkt && rxwaitpkt) { /* * Don't wait for a packet if transmitter is in DATA state */ if ((txstate == HTX_DATA) || ((txstate == HTX_REND) && (rxstate == HRX_DONE))) { if (txstate == HTX_DATA) { waitputget = WAITPUTGET(-1); if (waitputget & 1) { pkttype = hyrxpkt(rxbuf, &rxlen, 0); } else { pkttype = H_NOPKT; } } else { pkttype = hyrxpkt(rxbuf, &rxlen, 0); } } else { pkttype = hyrxpkt(rxbuf, &rxlen, -1); } if ((pkttype == H_CARRIER) || (EXPIRED(TIMERNO_BRAIN))) { Syslog('h', "Hydra: BRAIN timer expired"); txstate = HTX_Abort; break; } } else { pkttype = H_NOPKT; } /* * Special handling for RPOS packet */ if ((pkttype == HPKT_RPOS) && (rxlen == 12) && ((txstate == HTX_DATA) || (txstate == HTX_DATAACK) || (txstate == HTX_XWAIT) || (txstate == HTX_EOFACK))) { long rpos_pos = get_long(rxbuf); long rpos_blksize = get_long(rxbuf + 4); long rpos_id = get_long(rxbuf + 8); if (rpos_pos < 0) { /* * this differs from the protocol definition: txpos is used * instead of rxpos (I think it's wrong in the protocol * definition as in the example source also txpos is used) */ if ((rpos_pos == -2) && (txpos != -2) && (txstate == HTX_EOFACK)) { txpos = -2; txstate = HTX_EOF; } else { Syslog('h', "Hydra: set BRAIN timer", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); txstate = HTX_SkipFile; } } if (rpos_id == txsyncid) { txretries++; if (txretries >= 10) { Syslog('+', "Hydra: too many errors"); txstate = HTX_Abort; break; } } else { if (rpos_pos >= 0) { txpos = rpos_pos; txsyncid = rpos_id; txretries = 1; blksize = rpos_blksize; goodbytes = 0; goodneeded += 1024; if (goodneeded > 8192) goodneeded = 8192; /* if we receive an RPOS packet in EOFACK-state we have to change back to DATA-state */ if (txstate == HTX_EOFACK) txstate = HTX_DATA; Syslog('h', "Hydra: set BRAIN timer", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); } } pkttype = H_NOPKT; /* packet has already been processed */ } switch (txstate) { /* * initiate Hydra session */ case HTX_START: Syslog('h', "SM 'HTX' entering 'START'"); if (txretries < 10) { PUT((char *)"hydra\r", 6); /* send AutoStart string */ hytxpkt(HPKT_START, txbuf, 0); Syslog('h', "Hydra: set TX timer %d", H_START); SETTIMER(TIMERNO_TX, H_START); txstate = HTX_SWAIT; } else { Syslog('+', "Hydra: transmitter start TIMEOUT"); txstate = HTX_Abort; break; } break; /* * wait for START packet */ case HTX_SWAIT: Syslog('h', "SM 'HTX' entering 'SWAIT'"); if (((pkttype == HPKT_START) && (rxlen == 0)) || (pkttype == HPKT_INIT)) { txretries = 0; Syslog('h', "Hydra: reset TX timer"); RESETTIMER(TIMERNO_TX); Syslog('h', "Hydra: set BRAIN timer %d", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); txstate = HTX_INIT; pkttype = H_NOPKT; /* packet has already been processed */ } else if (EXPIRED(TIMERNO_TX)) { Syslog('+', "Hydra: transmitter timeout (HTX_SWAIT)"); txretries++; txstate = HTX_START; } break; /* * send INIT packet */ case HTX_INIT: Syslog('h', "SM 'HTX' entering 'INIT'"); if (txretries < 10) { outbuf = txbuf; /* Application ID string */ outbuf += sprintf(outbuf, "%08lx%s,%s", H_REVSTAMP, "mbcico", VERSION) + 1; /* Supported options */ outbuf += put_flags(outbuf, HCAN_OPTIONS) + 1; Syslog('h', "Hydra: supported options: %08lx", HCAN_OPTIONS); /* Desired options */ outbuf += put_flags(outbuf, HDEF_OPTIONS & HCAN_OPTIONS & ~HUNN_OPTIONS) + 1; Syslog('h', "Hydra: desired options : %08lx", HDEF_OPTIONS & HCAN_OPTIONS & ~HUNN_OPTIONS); /* Desired transmitter and receiver window size */ outbuf += sprintf(outbuf, "%08lx%08lx", H_TXWINDOW, H_RXWINDOW) + 1; /* Packet prefix string */ *outbuf++ = 0; hytxpkt(HPKT_INIT, txbuf, outbuf - txbuf); Syslog('h', "Hydra: set TX timer %d", H_MINTIMER/2); SETTIMER(TIMERNO_TX, H_MINTIMER/2); txstate = HTX_INITACK; } else { Syslog('+', "Hydra: too many errors waiting for INITACK"); txstate = HTX_Abort; break; } break; /* * wait for an INIT acknowledge packet */ case HTX_INITACK: Syslog('h', "SM 'HTX' entering 'INITACK'"); if ((pkttype == HPKT_INITACK) && (rxlen == 0)) { txretries = 0; Syslog('h', "Hydra: reset TX timer"); RESETTIMER(TIMERNO_TX); Syslog('h', "Hydra: set BRAIN timer %d", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); txstate = HTX_RINIT; pkttype = H_NOPKT; /* packet has already been processed */ } else if (EXPIRED(TIMERNO_TX)) { Syslog('+', "Hydra: tx timeout"); txretries++; txstate = HTX_INIT; } break; /* * wait for receiver to leave INIT state */ case HTX_RINIT: Syslog('h', "SM 'HTX' entering 'RINIT'"); if (rxstate != HRX_INIT) txstate = HTX_NextFile; break; /* * prepare next file for transmitting */ case HTX_NextFile: Syslog('h', "SM 'HTX' entering 'NextFile'"); /* * skip file with NULL remote name */ while (to_send && (to_send->remote == NULL)) { execute_disposition(to_send); to_send = to_send->next; } if (to_send) { struct flock txflock; if (to_send->remote == NULL) break; txflock.l_type=F_RDLCK; txflock.l_whence=0; txflock.l_start=0L; txflock.l_len=0L; txfp = fopen(to_send->local, "r"); if (txfp == NULL) { sverr = errno; if ((sverr == ENOENT) || (sverr == EINVAL)) { Syslog('+', "File %s doesn't exist, removing", MBSE_SS(to_send->local)); execute_disposition(to_send); // Added 18-10-99 MB. } else { WriteError("$Hydra: cannot open file %s, skipping", MBSE_SS(to_send->local)); } to_send = to_send->next; break; } if (fcntl(fileno(txfp), F_SETLK, &txflock) != 0) { WriteError("$Hydra: cannot lock file %s, skipping", MBSE_SS(to_send->local)); fclose(txfp); to_send = to_send->next; break; } if (stat(to_send->local, &txstat) != 0) { WriteError("$Hydra: cannot access \"%s\", skipping",MBSE_SS(to_send->local)); fclose(txfp); to_send = to_send->next; break; } Syslog('+', "Hydra: send \"%s\" as \"%s\"", MBSE_SS(to_send->local), MBSE_SS(to_send->remote)); Syslog('+', "Hydra: size %lu bytes, dated %s",(unsigned long)txstat.st_size, date(txstat.st_mtime)); gettimeofday(&txstarttime, &tz); } txstate = HTX_ToFName; break; /* TODO: fallthrough */ case HTX_ToFName: Syslog('h', "SM 'HTX' entering 'ToFName'"); txsyncid = 0; txretries = 0; Syslog('h', "Hydra: reset TX timer"); RESETTIMER(TIMERNO_TX); txstate = HTX_FINFO; break; /* TODO: fallthrough */ /* * transmit File Information packet */ case HTX_FINFO: Syslog('h', "SM 'HTX' entering 'FINFO'"); if (txretries >= 10) { txstate = HTX_Abort; break; } else { if (to_send) { txlen = sprintf(txbuf, "%08lx%08lx%08lx%08lx%08lx", mtime2sl(txstat.st_mtime+(txstat.st_mtime%2)), (long)(txstat.st_size), 0UL, 0UL, 0UL); /* * convert file name to DOS-format */ outbuf = xstrcpy(to_send->remote); name_mangle(outbuf); strcpy(txbuf + txlen, outbuf); free(outbuf); for(; txbuf[txlen]; txlen++) { txbuf[txlen] = tolower(txbuf[txlen]); } txlen++; strcpy(txbuf + txlen, to_send->remote); txlen += strlen(to_send->remote) + 1; } else { txbuf[0] = 0; txlen = 1; } hytxpkt(HPKT_FINFO, txbuf, txlen); if (txretries > 0) { Syslog('h', "Hydra: set TX timer %d", H_MINTIMER/2); SETTIMER(TIMERNO_TX, H_MINTIMER/2); } else { Syslog('h', "Hydra: set TX timer %d", H_MINTIMER); SETTIMER(TIMERNO_TX, H_MINTIMER); } txstate = HTX_FINFOACK; } break; /* * get FINFOACK packet */ case HTX_FINFOACK: Syslog('h', "SM 'HTX' entering 'FINFOACK'"); if ((pkttype == HPKT_FINFOACK) && (rxlen == 4)) { txpos = get_long(rxbuf); Syslog('h', "Hydra: set BRAIN timer", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); if (to_send == NULL) { Syslog('h', "Hydra: set TX timer %d", H_MINTIMER); SETTIMER(TIMERNO_TX, H_MINTIMER); txstate = HTX_REND; } else if (txpos >= 0L) { if (txpos > 0L) Syslog('+', "Hydra: restart from %lu", txpos); stxpos = txpos; txretries = 0; txlastack = 0; Syslog('h', "Hydra: reset TX timer"); RESETTIMER(TIMERNO_TX); txstate = HTX_DATA; } else if (txpos == -1) { Syslog('+', "Hydra: Receiver already has this file, skipping"); fclose(txfp); execute_disposition(to_send); to_send = to_send->next; txstate = HTX_NextFile; } else if (txpos == -2) { Syslog('+', "Hydra: receiver requested to skip this file for now"); fclose(txfp); to_send = to_send->next; txstate = HTX_NextFile; } pkttype = H_NOPKT; /* packet has already been processed */ } else if (EXPIRED(TIMERNO_TX)) { Syslog('+', "Hydra: transmitter timeout (HTX_FINFOACK)"); txretries++; txstate = HTX_FINFO; } break; case HTX_DATA: Syslog('h', "SM 'HTX' entering 'DATA'"); Syslog('h', "txwindow=%d txpos=%d txlastack=%d", txwindow, txpos, txlastack); if ((rxstate != HRX_DONE) && (hdxlink)) { Syslog('h', "Hydra: set TX timer %d", H_MINTIMER); SETTIMER(TIMERNO_TX, H_MINTIMER); txstate = HTX_XWAIT; } else if ((pkttype == HPKT_DATAACK) && (rxlen == 4)) { longnum = get_long(rxbuf); Syslog('h', "received 'DATAACK' (0x%08lx)", longnum); if (longnum > txlastack) { txlastack = longnum; } pkttype = H_NOPKT; /* packet has already been processed */ } else if ((txwindow) && (txpos >= txlastack + txwindow)) { /* * We have to wait for an ACK before we can continue */ if (txretries > 0) { Syslog('h', "Hydra: set TX timer %d", H_MINTIMER/2); SETTIMER(TIMERNO_TX, H_MINTIMER/2); } else { Syslog('H', "HYDRA: SET TX TIMER %D", H_MINTIMER); SETTIMER(TIMERNO_TX, H_MINTIMER); } txstate = HTX_DATAACK; break; } else { /* * check if there is enough room in output */ if ((waitputget & 2) == 0) { break; } fseek(txfp, txpos, SEEK_SET); put_long(txbuf, txpos); Nopper(); Syslog('h', "Check for more frames here?"); txlen = fread(txbuf + 4, 1, blksize, txfp); Syslog('h', "Hydra: send DATA (0x%08lx) %lu", txpos, txlen); if (txlen == 0) { if (ferror(txfp)) { WriteError("$Hydra: error reading from file"); txstate = HTX_SkipFile; } else { txstate = HTX_EOF; } } else { txpos += txlen; sentbytes += txlen; goodbytes += txlen; txlen += 4; hytxpkt(HPKT_DATA, txbuf, txlen); if (goodbytes > goodneeded) { blksize *= 2; if (blksize > H_MAXBLKLEN) { blksize = H_MAXBLKLEN; } } } } break; case HTX_SkipFile: Syslog('h', "SM 'HTX' entering 'SkipFile'"); /* * this differs from the protocol definition: -2 is used * instead of -1 (I think it's wrong in the protocol * definition as in the example source also -2 is used) * MB: No, I don't think this is wrong, -1 means file already * there, -2 means skip for now, try another time. */ txpos = -2; txretries = 0; txstate = HTX_EOF; break; case HTX_DATAACK: Syslog('h', "SM 'HTX' entering 'DATAACK'"); if ((pkttype == HPKT_DATAACK) && (rxlen == 4)) { longnum = get_long(rxbuf); if ((longnum > txlastack) && (txpos < longnum + txwindow)) { txlastack = longnum; txretries = 0; Syslog('h', "Hydra: reset TX timer"); RESETTIMER(TIMERNO_TX); txstate = HTX_DATA; } pkttype = H_NOPKT; /* packet has already been processed */ } else if (txretries >= 10) { Syslog('+', "Hydra: too many errors"); txstate = HTX_Abort; } else if (EXPIRED(TIMERNO_TX)) { Syslog('+', "Hydra: tx timeout"); txretries++; txstate = HTX_DATA; } break; case HTX_XWAIT: Syslog('h', "SM 'HTX' entering 'XWAIT'"); if (rxstate == HRX_DONE) { Syslog('h', "Hydra: reset RX timer"); RESETTIMER(TIMERNO_RX); txstate = HTX_DATA; } else if ((pkttype == HPKT_DATAACK) && (rxlen == 4)) { longnum = get_long(rxbuf); if (longnum > txlastack) { txlastack = longnum; } pkttype = H_NOPKT; /* packet has already been processed */ } else if (pkttype == HPKT_RPOS) { /* * Handle RPOS in state DATA but stay in this state */ pkttype = H_NOPKT; /* packet has already been processed */ } else if ((pkttype == HPKT_IDLE) && (rxlen == 0)) { hdxlink = FALSE; Syslog('h', "Hydra: reset TX timer"); RESETTIMER(TIMERNO_TX); txstate = HTX_DATA; pkttype = H_NOPKT; /* packet has already been processed */ } else if (EXPIRED(TIMERNO_TX)) { Syslog('h', "Hydra: TX timer expired"); hytxpkt(HPKT_IDLE, txbuf, 0); Syslog('h', "Hydra: set TX timer %d", H_MINTIMER); SETTIMER(TIMERNO_TX, H_MINTIMER); } break; case HTX_EOF: Syslog('h', "SM 'HTX' entering 'EOF'"); if (txretries >= 10) { txstate = HTX_Abort; break; } else { put_long(txbuf, txpos); hytxpkt(HPKT_EOF, txbuf, 4); if (txretries > 0) { Syslog('h', "Hydra: set TX timer %d", H_MINTIMER/2); SETTIMER(TIMERNO_TX, H_MINTIMER/2); } else { Syslog('h', "Hydra: set TX timer %d", H_MINTIMER); SETTIMER(TIMERNO_TX, H_MINTIMER); } Syslog('h', "Hydra: entering TX EOFACK"); txstate = HTX_EOFACK; } break; case HTX_EOFACK: Syslog('h', "SM 'HTX' entering 'EOFACK'"); if ((pkttype == HPKT_EOFACK) && (rxlen == 0)) { Syslog('h', "Hydra: set BRAIN timer", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); /* * calculate time needed and bytes transferred */ gettimeofday(&txendtime, &tz); /* close transmitter file */ fclose(txfp); if (txpos >= 0) { stxpos = txpos - stxpos; Syslog('+', "Hydra: OK %s", transfertime(txstarttime, txendtime, stxpos, TRUE)); execute_disposition(to_send); } else { Syslog('+', "Hydra: transmitter skipped file after %ld seconds", txendtime.tv_sec - txstarttime.tv_sec); } to_send = to_send->next; txstate = HTX_NextFile; pkttype = H_NOPKT; /* packet has already been processed */ } else if ((pkttype == HPKT_DATAACK) && (rxlen == 4)) { longnum = get_long(rxbuf); txlastack = longnum; pkttype = H_NOPKT; /* packet has already been processed */ } else if (EXPIRED(TIMERNO_TX)) { Syslog('+', "Hydra: transmitter timeout (HTX_EOFACK)"); txretries++; txstate = HTX_EOF; } break; case HTX_REND: Syslog('h', "SM 'HTX' entering 'REND'"); if (rxstate == HRX_DONE) { txretries = 0; txstate = HTX_END; } else if (EXPIRED(TIMERNO_TX)) { Syslog('h', "Hydra: TX timer expired"); hytxpkt(HPKT_IDLE, txbuf, 0); Syslog('h', "Hydra: set TX timer %d", H_MINTIMER/2); SETTIMER(TIMERNO_TX, H_MINTIMER/2); } break; case HTX_END: Syslog('h', "SM 'HTX' entering 'END'"); if (txretries >= 10) { txstate = HTX_Abort; break; } else { hytxpkt(HPKT_END, txbuf, 0); hytxpkt(HPKT_END, txbuf, 0); Syslog('h', "Hydra: set TX timer %d", H_MINTIMER/2); SETTIMER(TIMERNO_TX, H_MINTIMER/2); txstate = HTX_ENDACK; } break; case HTX_ENDACK: Syslog('h', "SM 'HTX' entering 'ENDACK'"); if ((pkttype == HPKT_END) && (rxlen == 0)) { hytxpkt(HPKT_END, txbuf, 0); hytxpkt(HPKT_END, txbuf, 0); hytxpkt(HPKT_END, txbuf, 0); txstate = HTX_DONE; pkttype = H_NOPKT; /* packet has already been processed */ } else if (EXPIRED(TIMERNO_TX)) { Syslog('+', "Hydra: transmitter timeout (HTX_ENDACK)"); txretries++; txstate = HTX_END; } break; case HTX_DONE: case HTX_Abort: /* nothing to do */ break; default: die(1); } /* switch (txstate) */ switch (rxstate) { case HRX_INIT: Syslog('h', "SM 'HRX' entering 'INIT'"); if (pkttype == HPKT_INIT) { /* TODO: some checking, error handling */ /* Skip application ID */ Syslog('+', "Hydra: remote \"%s\"", rxbuf+8); inbuf = rxbuf + strlen(rxbuf) + 1; // Syslog('+', "Hydra: inbuf=\"%s\"", inbuf); inbuf += strlen(inbuf) + 1; rxoptions = (HDEF_OPTIONS & HCAN_OPTIONS) | HUNN_OPTIONS; /* * get supported options */ rxoptions |= get_flags(inbuf); inbuf += strlen(inbuf) + 1; /* * get desired options */ rxoptions &= get_flags(rxbuf + strlen(rxbuf) + 1); rxoptions &= HCAN_OPTIONS; /* * set options */ txoptions = rxoptions; put_flags(txbuf, rxoptions); Syslog('h', "Hydra: options: %s (%08lx)", txbuf, rxoptions); /* * get desired window sizes */ txwindow = rxwindow = 0; sscanf(inbuf, "%08lx%08lx", &rxwindow, &txwindow); if (rxwindow < 0) rxwindow = 0; if (H_RXWINDOW && ((rxwindow == 0) || (rxwindow > H_RXWINDOW))) rxwindow = H_RXWINDOW; if (txwindow < 0) txwindow = 0; if (H_TXWINDOW && ((txwindow == 0) || (txwindow > H_TXWINDOW))) txwindow = H_TXWINDOW; Syslog('h', "Hydra: txwindow=%d, rxwindow=%d", txwindow, rxwindow); hytxpkt(HPKT_INITACK, txbuf, 0); Syslog('h', "Hydra: set BRAIN timer", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); rxstate = HRX_FINFO; pkttype = H_NOPKT; /* packet has already been processed */ } break; case HRX_FINFO: Syslog('h', "SM 'HRX' entering 'FINFO'"); if (pkttype == HPKT_FINFO) { /* * check if we have received an `End of batch' FINFO packet */ if ((rxlen == 1) && (rxbuf[0] == 0)) { put_long(txbuf, 0); hytxpkt(HPKT_FINFOACK, txbuf, 4); Syslog('h', "Hydra: set BRAIN timer", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); rxstate = HRX_DONE; } /* * check if FINFO packet is at least long enough to contain * file information */ else if ((rxlen > 41) && (rxbuf[rxlen - 1] == 0)) { time_t timestamp; time_t orgstamp; long filesize; char dosname[8 + 1 + 3 + 1], *Name; sscanf(rxbuf, "%08lx%08lx%*08x%*08x%*08x", ×tamp, &filesize); /* convert timestamp to UNIX time */ orgstamp = timestamp; timestamp = sl2mtime(timestamp); /* * check if DOS conforming file name is max. 8+1+3 chars long */ if (strlen(rxbuf + 40) <= 12) { strcpy(dosname, rxbuf + 40); } else { strcpy(dosname, "BadWazoo"); } /* * check if real file name is specified */ if ((strlen(rxbuf + 40) + 41) < rxlen) { Name = rxbuf + strlen(rxbuf + 40) + 41; /* * use DOS-filename if real- and DOS-name only * differ in case sensitivity */ if (strcasecmp(Name, dosname) == 0) { Name = dosname; } } else { Name = dosname; } Syslog('+', "Hydra: receive \"%s\" (%ld bytes) dated %s", Name, filesize, date(timestamp)); rxfp = openfile(Name, timestamp, filesize, &rxpos, resync); gettimeofday(&rxstarttime, &tz); /* check for error opening file */ if (rxfp) { srxpos = rxpos; rxstate = HRX_ToData; } else { if (filesize == rxpos) { /* Skip this file, it's already here */ Syslog('+', "Hydra: Skipping file %s", Name); put_long(txbuf, -1); } else { /* Skip this file for now if error opening file */ put_long(txbuf, -2); } hytxpkt(HPKT_FINFOACK, txbuf, 4); Syslog('h', "Hydra: set BRAIN timer", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); } } pkttype = H_NOPKT; /* packet has already been processed */ } else if (pkttype == HPKT_INIT) { hytxpkt(HPKT_INITACK, txbuf, 0); pkttype = H_NOPKT; /* packet has already been processed */ } else if ((pkttype == HPKT_EOF) && (rxlen == 4)) { hytxpkt(HPKT_EOFACK, txbuf, 0); pkttype = H_NOPKT; /* packet has already been processed */ } break; case HRX_ToData: Syslog('h', "SM 'HRX' entering 'ToData'"); put_long(txbuf, rxpos); hytxpkt(HPKT_FINFOACK, txbuf, 4); rxsyncid = 0; rxlastsync = 0; rxretries = 0; Syslog('h', "Hydra: reset RX timer"); RESETTIMER(TIMERNO_RX); Syslog('h', "Hydra: set BRAIN timer %d", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); rxstate = HRX_DATA; break; case HRX_DATA: Syslog('h', "SM 'HRX' entering 'DATA'"); if ((pkttype == HPKT_DATA) && (rxlen > 4)) { longnum = get_long(rxbuf); Syslog('h', "Hydra: rcvd DATA (0x%08lx, 0x%08lx) %lu", longnum, rxpos, rxlen-4); Nopper(); if (longnum == rxpos) { if (fwrite(rxbuf + 4, 1, rxlen - 4, rxfp) != (rxlen - 4)) { WriteError("$Hydra: error writing to file"); rxpos = -2; } else { rxlastdatalen = rxlen - 4; rxpos += rxlen - 4; rcvdbytes += rxlen - 4; rxretries = 0; rxlastsync = rxpos; Syslog('h', "Hydra: reset RX timer"); RESETTIMER(TIMERNO_RX); Syslog('h', "Hydra: set BRAIN timer %d", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); if (rxwindow) { put_long(txbuf, rxpos); hytxpkt(HPKT_DATAACK, txbuf, 4); } } } else { if (rxpos >= 0) { Syslog('+', "Hydra: received bad rxpos"); } rxstate = HRX_BadPos; } pkttype = H_NOPKT; /* packet has already been processed */ } else if ((pkttype == HPKT_EOF) && (rxlen == 4)) { longnum = get_long(rxbuf); if (longnum == rxpos) { /* * calculate time and CPU usage needed */ gettimeofday(&rxendtime, &tz); if (rxpos >= 0) { rxfp = NULL; if (!closefile(1)) { srxpos = rxpos - srxpos; Syslog('+', "Hydra: OK %s", transfertime(rxstarttime, rxendtime, srxpos, FALSE)); rxstate = HRX_OkEOF; } else { Syslog('+', "Hydra: error closing file"); rxpos = -2; /* * Note: the following state change isn't 100 % * conformant with the Hydra protocol specification. */ rxstate = HRX_BadPos; } } else { Syslog('+', "Hydra: receiver skipped file after %ld seconds", rxendtime.tv_sec - rxstarttime.tv_sec); if (rxfp) { closefile(0); rxfp = NULL; } rxstate = HRX_OkEOF; } } else if (longnum == -2) { if (rxfp) { closefile(0); rxfp = NULL; } rxstate = HRX_OkEOF; } else { if (longnum >= 0) { Syslog('+', "Hydra: received bad rxpos"); } rxstate = HRX_BadPos; /* TODO: fallthrough */ } pkttype = H_NOPKT; /* packet has already been processed */ } else if (pkttype == HPKT_FINFO) { put_long(txbuf, rxpos); hytxpkt(HPKT_FINFOACK, txbuf, 4); pkttype = H_NOPKT; /* packet has already been processed */ } else if ((pkttype == HPKT_IDLE) && (rxlen == 0) && (hdxlink == FALSE)) { Syslog('h', "Hydra: set BRAIN timer", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); pkttype = H_NOPKT; /* packet has already been processed */ } else if (EXPIRED(TIMERNO_RX)) { Syslog('h', "Hydra: RX timer expired"); rxstate = HRX_HdxLink; } break; case HRX_BadPos: Syslog('h', "SM 'HRX' entering 'BadPos'"); longnum = get_long(rxbuf); if (longnum <= rxlastsync) { rxretries = 0; Syslog('h', "Hydra: reset RX timer"); RESETTIMER(TIMERNO_RX); } rxlastsync = longnum; rxstate = HRX_Timer; break; case HRX_Timer: Syslog('h', "SM 'HRX' entering 'Timer'"); if ((!RUNNING(TIMERNO_RX)) || (EXPIRED(TIMERNO_RX))) { Syslog('h', "Hydra: RX timer expired"); rxstate = HRX_HdxLink; } else { rxstate = HRX_DATA; } break; case HRX_HdxLink: Syslog('h', "SM 'HRX' entering 'HdxLink'"); if ((rxretries > 4) && (txstate != HTX_REND) && (role == 0) && (hdxlink == FALSE)) { rxretries = 0; hdxlink = TRUE; } rxstate = HRX_Retries; break; /* TODO: fallthrough */ case HRX_Retries: Syslog('h', "SM 'HRX' entering 'Retries'"); rxretries++; if (rxretries >= 10) { Syslog('+', "Hydra: too many errors"); txstate = HTX_Abort; } else if (rxretries == 1) { rxsyncid++; } rxstate = HRX_RPos; break; /* TODO: fallthrough */ case HRX_RPos: Syslog('h', "SM 'HRX' entering 'RPos'"); rxlastdatalen /= 2; if (rxlastdatalen < 64) rxlastdatalen = 64; put_long(txbuf, rxpos); put_long(txbuf + 4, rxlastdatalen); put_long(txbuf + 8, rxsyncid); hytxpkt(HPKT_RPOS, txbuf, 12); Syslog('h', "Hydra: set TX timer %d", H_MINTIMER); SETTIMER(TIMERNO_RX, H_MINTIMER); rxstate = HRX_DATA; break; case HRX_OkEOF: Syslog('h', "SM 'HRX' entering 'OkEOF'"); hytxpkt(HPKT_EOFACK, txbuf, 0); Syslog('h', "Hydra: reset RX timer"); RESETTIMER(TIMERNO_RX); Syslog('h', "Hydra: set BRAIN timer %d", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); rxstate = HRX_FINFO; break; case HRX_DONE: Syslog('h', "SM 'HRX' entering 'DONE'"); if (pkttype == HPKT_FINFO) { put_long(txbuf, -2); hytxpkt(HPKT_FINFOACK, txbuf, 4); pkttype = H_NOPKT; /* packet has already been processed */ } else if ((pkttype == HPKT_IDLE) && (rxlen == 0)) { Syslog('h', "Hydra: set BRAIN timer", H_BRAINDEAD); SETTIMER(TIMERNO_BRAIN, H_BRAINDEAD); pkttype = H_NOPKT; /* packet has already been processed */ } break; } /* switch(rxstate) */ if (pkttype != H_NOPKT) { Syslog('h', "Hydra: rcvd packet %s - ignored", PktS(pkttype)); pkttype = H_NOPKT; /* ignore received packet */ } } /* while() */ Syslog('h', "Hydra: resettimers"); RESETTIMERS(); if (txstate == HTX_Abort) { /* check if file is still open */ if (rxfp) { rxfp = NULL; closefile(0); } Syslog('+', "Hydra: signal CAN to remote"); FLUSHOUT(); /* 8 times CAN and 10 times BS */ PUT((char *)"\030\030\030\030\030\030\030\030\010\010\010\010\010\010\010\010\010\010", 18); sleep(4); /* wait a few seconds... */ FLUSHIN(); return 2; } return 0; } int hydra(int role) { int rc; fa_list *eff_remote, tmpl; file_list *tosend = NULL, *request = NULL, *respond = NULL, *tmpfl; char *nonhold_mail; Syslog('+', "Hydra: start transfer"); session_flags |= SESSION_HYDRA; /* Hydra special file requests */ if (emsi_remote_lcodes & LCODE_NPU) { Syslog('+', "Hydra: remote requested \"no pickup\", no send"); eff_remote = NULL; } else if (emsi_remote_lcodes & LCODE_PUP) { Syslog('+', "Hydra: remote requested \"pickup primary\""); tmpl.addr = remote->addr; tmpl.next = NULL; eff_remote = &tmpl; } else { eff_remote = remote; } if (role) { // if (localoptions & NOHOLD) nonhold_mail = (char *)ALL_MAIL; // else // nonhold_mail = (char *)NONHOLD_MAIL; } else { nonhold_mail = (char *)ALL_MAIL; } if (emsi_remote_lcodes & LCODE_HAT) { Syslog('+', "Hydra: remote requested \"hold all traffic\", no send"); tosend = NULL; } else { tosend = create_filelist(eff_remote, nonhold_mail, 0); } if (session_flags & SESSION_WAZOO) request = create_freqlist(remote); /* * Send only file requests during first batch if remote supports * "RH1" flag. */ if (emsi_remote_lcodes & LCODE_RH1) { rc = hydra_batch(role, request); } else { if (request != NULL) { tmpfl = tosend; tosend = request; for (; request->next; request = request->next); request->next = tmpfl; request = NULL; } rc = hydra_batch(role, tosend); } Syslog('+', "Hydra: start second batch"); if (rc == 0) { if ((emsi_local_opts & OPT_NRQ) == 0) respond = respond_wazoo(); if (emsi_remote_lcodes & LCODE_RH1) { for (tmpfl = tosend; tmpfl->next; tmpfl = tmpfl->next); tmpfl->next = respond; rc = hydra_batch(role, tosend); tmpfl->next = NULL; /* split filelist into tosend and respond again */ } else { rc = hydra_batch(role, respond); } } tidy_filelist(request, (rc == 0)); tidy_filelist(tosend, (rc == 0)); tidy_filelist(respond, 0); Syslog('+', "Hydra: end transfer"); return rc; }