You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bforce/source/bforce/prot_binkp.c

699 lines
17 KiB
C

/*
* 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 "util.h"
#include "io.h"
#include "session.h"
#include "outbound.h"
#include "prot_common.h"
#include "prot_binkp.h"
typedef enum {
BPI_SendSysInfo,
BPI_WaitADR,
BPI_WaitPWD,
BPI_Auth
} binkp_incoming_state;
typedef enum {
BPO_SendSysInfo,
BPO_WaitNUL,
BPO_SendPWD,
BPO_WaitADR,
BPO_Auth,
BPO_WaitOK
} binkp_outgoing_state;
#define GOTO(label,newrc) { rc = (newrc); goto label; }
void binkp_process_NUL(s_binkp_sysinfo *remote_data, char *buffer)
{
char *p, *q;
if( strncmp(buffer, "SYS ", 4) == 0 )
strnxcpy(remote_data->systname, buffer+4, sizeof(remote_data->systname));
else if( strncmp(buffer, "ZYZ ", 4) == 0 )
strnxcpy(remote_data->sysop, buffer+4, sizeof(remote_data->sysop));
else if( strncmp(buffer, "LOC ", 4) == 0 )
strnxcpy(remote_data->location, buffer+4, sizeof(remote_data->location));
else if( strncmp(buffer, "PHN ", 4) == 0 )
strnxcpy(remote_data->phone, buffer+4, sizeof(remote_data->phone));
else if( strncmp(buffer, "NDL ", 4) == 0 )
strnxcpy(remote_data->flags, buffer+4, sizeof(remote_data->flags));
else if( strncmp(buffer, "TIME ", 5) == 0 )
strnxcpy(remote_data->timestr, buffer+5, sizeof(remote_data->timestr));
else if( strncmp(buffer, "OPT ", 4) == 0 )
{
if( *remote_data->opt )
{
strnxcat(remote_data->opt, " ", sizeof(remote_data->opt));
strnxcat(remote_data->opt, buffer+4, sizeof(remote_data->opt));
}
else
strnxcpy(remote_data->opt, buffer+4, sizeof(remote_data->opt));
binkp_parse_options(remote_data, buffer+4);
}
else if( strncmp(buffer, "VER ", 4) == 0 )
{
/* <mailer> [<protocol>/<vermaj>.<vermin>] */
if( (p = strchr(buffer+4, ' ')) )
{
strnxcpy(remote_data->progname, buffer+4,
MIN(sizeof(remote_data->progname), p - (buffer+4) + 1));
++p;
if( (q = strchr(p, '/')) )
{
strnxcpy(remote_data->protname, p,
MIN(sizeof(remote_data->protname), q - p + 1));
sscanf(q+1, "%d.%d",
&remote_data->majorver,
&remote_data->minorver);
}
}
else
strnxcpy(remote_data->progname, buffer+4, sizeof(remote_data->progname));
}
else
log("BinkP got invalid NUL: \"%s\"", string_printable(buffer));
}
void binkp_process_ADR(s_binkp_sysinfo *remote_data, char *buffer)
{
s_faddr addr;
char *p, *q;
for( p = string_token(buffer, &q, NULL, 0); p;
p = string_token(NULL, &q, NULL, 0) )
{
if( ftn_addrparse(&addr, p, FALSE) )
log("BinkP got unparsable address \"%s\"", string_printable(p));
else
session_addrs_add(&remote_data->addrs, &remote_data->anum, addr);
}
}
int binkp_outgoing(s_binkp_sysinfo *local_data, s_binkp_sysinfo *remote_data)
{
s_bpinfo bpi;
binkp_outgoing_state binkp_state = BPO_SendSysInfo;
int rc = HRC_OK;
int recv_rc = 0;
int send_rc = 0;
bool send_ready = FALSE;
bool recv_ready = FALSE;
binkp_init_bpinfo(&bpi);
while(1)
{
switch(binkp_state) {
case BPO_SendSysInfo:
binkp_queue_sysinfo(&bpi, local_data);
binkp_state = BPO_WaitNUL;
break;
case BPO_SendPWD:
if( *local_data->passwd == '\0' )
{
binkp_queuemsg(&bpi, BPMSG_PWD, NULL, "-");
}
else if( remote_data->options & BINKP_OPT_MD5 )
{
char digest_bin[16];
char digest_hex[33];
md5_cram_get(local_data->passwd, remote_data->challenge,
remote_data->challenge_length, digest_bin);
/* Encode digest to the hex string */
string_bin_to_hex(digest_hex, digest_bin, 16);
binkp_queuemsg(&bpi, BPMSG_PWD, "CRAM-MD5-", digest_hex);
}
else
binkp_queuemsg(&bpi, BPMSG_PWD, NULL, local_data->passwd);
binkp_state = BPO_WaitADR;
break;
case BPO_Auth:
/* Set remote password same as local */
strncpy(remote_data->passwd, local_data->passwd, BINKP_MAXPASSWD);
remote_data->passwd[BINKP_MAXPASSWD] = '\0';
if( !remote_data->anum )
{
binkp_queuemsg(&bpi, BPMSG_BSY, NULL, "No addresses was presented");
GOTO(Exit, HRC_BUSY);
}
else if( session_addrs_check_genuine(remote_data->addrs, remote_data->anum,
state.node.addr) )
{
binkp_queuemsg(&bpi, BPMSG_ERR, NULL, "Sorry, you are not who I need");
GOTO(Exit, HRC_NO_ADDRESS);
}
else if( session_addrs_check(remote_data->addrs, remote_data->anum,
remote_data->passwd, NULL, 0) )
{
binkp_queuemsg(&bpi, BPMSG_ERR, NULL, "Security violation");
GOTO(Exit, HRC_BAD_PASSWD);
}
else if( session_addrs_lock(remote_data->addrs, remote_data->anum) )
{
binkp_queuemsg(&bpi, BPMSG_BSY, NULL, "All addresses are busy");
GOTO(Exit, HRC_BUSY);
}
binkp_state = BPO_WaitOK;
break;
default:
break;
}
/*
* Receive/Send next data block
*/
send_ready = recv_ready = FALSE;
if( tty_select(&recv_ready, (bpi.opos || bpi.n_msgs) ?
&send_ready : NULL, bpi.timeout) < 0 )
GOTO(Abort, HRC_OTHER_ERR);
recv_rc = BPMSG_NONE;
send_rc = 0;
if( recv_ready && (recv_rc = binkp_recv(&bpi)) == BPMSG_EXIT )
GOTO(Abort, HRC_OTHER_ERR);
if( send_ready && (send_rc = binkp_send(&bpi)) < 0 )
GOTO(Abort, HRC_OTHER_ERR);
/*
* Handle received message
*/
switch(recv_rc) {
case BPMSG_NONE:
break;
case BPMSG_NUL:
if( binkp_state == BPO_WaitNUL || binkp_state == BPO_WaitADR )
{
binkp_process_NUL(remote_data, bpi.ibuf+1);
if( binkp_state == BPO_WaitNUL )
binkp_state = BPO_SendPWD;
}
break;
case BPMSG_ADR:
if( binkp_state == BPO_WaitADR )
{
binkp_process_ADR(remote_data, bpi.ibuf+1);
binkp_state = BPO_Auth;
}
break;
case BPMSG_OK:
if( binkp_state == BPO_WaitOK )
GOTO(Exit, HRC_OK);
break;
case BPMSG_ERR:
log("BinkP error: \"%s\"", string_printable(bpi.ibuf+1));
GOTO(Abort, HRC_FATAL_ERR);
case BPMSG_BSY:
log("BinkP busy: \"%s\"", string_printable(bpi.ibuf+1));
GOTO(Abort, HRC_TEMP_ERR);
}
}
Exit:
if( binkp_flush_queue(&bpi, bpi.timeout) && rc == HRC_OK )
rc = HRC_OTHER_ERR;
Abort:
binkp_deinit_bpinfo(&bpi);
return rc;
}
int binkp_auth_incoming(s_binkp_sysinfo *remote_data)
{
if( remote_data->challenge_length > 0
&& strncmp(remote_data->passwd, "CRAM-MD5-", 9) == 0 )
{
return session_addrs_check(remote_data->addrs,
remote_data->anum,
remote_data->passwd + 9,
remote_data->challenge,
remote_data->challenge_length);
}
return session_addrs_check(remote_data->addrs, remote_data->anum,
remote_data->passwd, NULL, 0);
}
int binkp_incoming(s_binkp_sysinfo *local_data, s_binkp_sysinfo *remote_data)
{
s_bpinfo bpi;
binkp_incoming_state binkp_state = BPI_SendSysInfo;
int rc = HRC_OK;
int recv_rc = 0;
int send_rc = 0;
bool send_ready = FALSE;
bool recv_ready = FALSE;
binkp_init_bpinfo(&bpi);
while(1)
{
switch(binkp_state) {
case BPI_SendSysInfo:
binkp_queue_sysinfo(&bpi, local_data);
binkp_state = BPI_WaitADR;
break;
case BPI_Auth:
/* Set challenge string same as local */
memcpy(remote_data->challenge, local_data->challenge,
sizeof(remote_data->challenge));
remote_data->challenge_length = local_data->challenge_length;
/* Do authorization */
if( !remote_data->anum )
{
binkp_queuemsg(&bpi, BPMSG_BSY, NULL, "No addresses was presented");
GOTO(Exit, HRC_BUSY);
}
else if( binkp_auth_incoming(remote_data) )
{
binkp_queuemsg(&bpi, BPMSG_ERR, NULL, "Security violation");
GOTO(Exit, HRC_BAD_PASSWD);
}
else if( session_addrs_lock(remote_data->addrs, remote_data->anum) )
{
binkp_queuemsg(&bpi, BPMSG_BSY, NULL, "All addresses are busy");
GOTO(Exit, HRC_BUSY);
}
else
{
binkp_queuemsg(&bpi, BPMSG_OK, NULL, NULL);
GOTO(Exit, HRC_OK);
}
break;
default:
break;
}
/*
* Receive/Send next data block
*/
send_ready = recv_ready = FALSE;
if( tty_select(&recv_ready, (bpi.opos || bpi.n_msgs) ?
&send_ready : NULL, bpi.timeout) < 0 )
GOTO(Abort, HRC_OTHER_ERR);
recv_rc = BPMSG_NONE;
send_rc = 0;
if( recv_ready && (recv_rc = binkp_recv(&bpi)) == BPMSG_EXIT )
GOTO(Abort, HRC_OTHER_ERR);
if( send_ready && (send_rc = binkp_send(&bpi)) < 0 )
GOTO(Abort, HRC_OTHER_ERR);
/*
* Handle received message
*/
switch(recv_rc) {
case BPMSG_NONE:
break;
case BPMSG_NUL:
if( binkp_state == BPI_WaitADR )
binkp_process_NUL(remote_data, bpi.ibuf+1);
break;
case BPMSG_ADR:
if( binkp_state == BPI_WaitADR )
{
binkp_process_ADR(remote_data, bpi.ibuf+1);
binkp_state = BPI_WaitPWD;
}
break;
case BPMSG_PWD:
if( binkp_state == BPI_WaitPWD )
{
strnxcpy(remote_data->passwd, bpi.ibuf+1, sizeof(remote_data->passwd));
binkp_state = BPI_Auth;
}
break;
case BPMSG_ERR:
log("BinkP error: \"%s\"", string_printable(bpi.ibuf+1));
GOTO(Abort, HRC_FATAL_ERR);
case BPMSG_BSY:
log("BinkP busy: \"%s\"", string_printable(bpi.ibuf+1));
GOTO(Abort, HRC_TEMP_ERR);
}
}
Exit:
if( binkp_flush_queue(&bpi, bpi.timeout) && rc == HRC_OK )
rc = HRC_OTHER_ERR;
Abort:
binkp_deinit_bpinfo(&bpi);
return rc;
}
int binkp_transfer(s_protinfo *pi)
{
int i, n, rc = PRC_NOERROR;
bool recv_ready = FALSE;
bool send_ready = FALSE;
bool sent_EOB = FALSE;
bool rcvd_EOB = FALSE;
bool send_file = FALSE;
bool recv_file = FALSE;
bool wait_got = FALSE;
bool nofiles = FALSE;
int recv_rc = 0;
int send_rc = 0;
char *fname = NULL;
size_t fsize = 0;
time_t ftime = 0;
size_t foffs = 0;
s_bpinfo bpi;
binkp_init_bpinfo(&bpi);
while( !sent_EOB || !rcvd_EOB )
{
if( !send_file && !sent_EOB && bpi.opos == 0 && bpi.n_msgs == 0 )
{
if( !p_tx_fopen(pi) )
{
send_file = TRUE;
binkp_queuemsgf(&bpi, BPMSG_FILE, "%s %ld %ld 0",
pi->send->net_name, (long)pi->send->bytes_total,
(long)pi->send->mod_time);
}
else
/* No more files */
nofiles = TRUE;
}
if( send_file && bpi.opos == 0 && bpi.n_msgs == 0 )
{
if( (n = p_tx_readfile(bpi.obuf + BINKP_BLK_HDRSIZE,
4096, pi)) < 0 )
{
p_tx_fclose(pi);
send_file = FALSE;
}
else
{
binkp_puthdr(bpi.obuf, (unsigned)(n & 0x7fff));
bpi.opos = n + BINKP_BLK_HDRSIZE;
pi->send->bytes_sent += n;
if( pi->send->eofseen )
{
wait_got = TRUE; send_file = FALSE;
pi->send->status = FSTAT_WAITACK;
}
}
}
if( nofiles && !wait_got && !sent_EOB )
{
sent_EOB = TRUE;
binkp_queuemsg(&bpi, BPMSG_EOB, NULL, NULL);
}
recv_ready = send_ready = FALSE;
if( tty_select(&recv_ready, (bpi.opos || bpi.n_msgs) ?
&send_ready : NULL, bpi.timeout) < 0 )
gotoexit(PRC_ERROR);
recv_rc = BPMSG_NONE;
send_rc = 0;
if( recv_ready && (recv_rc = binkp_recv(&bpi)) == BPMSG_EXIT )
gotoexit(PRC_ERROR);
if( send_ready && (send_rc = binkp_send(&bpi)) < 0 )
gotoexit(PRC_ERROR);
switch(recv_rc) {
case BPMSG_NONE:
break;
case BPMSG_DATA: /* Got new data block */
if( recv_file )
{
if( (n = p_rx_writefile(bpi.ibuf, bpi.isize, pi)) < 0 )
{
/* error writing file */
if( n == -2 )
{
binkp_queuemsgf(&bpi, BPMSG_GOT, "%s %ld %ld",
pi->recv->net_name, (long)pi->recv->bytes_total,
(long)pi->recv->mod_time);
}
else
{
binkp_queuemsgf(&bpi, BPMSG_SKIP, "%s %ld %ld",
pi->recv->net_name, (long)pi->recv->bytes_total,
(long)pi->recv->mod_time);
}
recv_file = FALSE;
p_rx_fclose(pi);
}
else
{
pi->recv->bytes_received += bpi.isize;
/* Was it the last data block? */
if( pi->recv->bytes_received > pi->recv->bytes_total )
{
log("binkp got too many data (%ld, %ld expected)",
(long)pi->recv->bytes_received,
(long)pi->recv->bytes_total);
recv_file = FALSE;
pi->recv->status = FSTAT_REFUSED;
(void)p_rx_fclose(pi);
binkp_queuemsgf(&bpi, BPMSG_SKIP, "%s %ld %ld",
pi->recv->net_name, (long)pi->recv->bytes_total,
(long)pi->recv->mod_time);
}
else if( pi->recv->bytes_received == pi->recv->bytes_total )
{
recv_file = FALSE;
pi->recv->status = FSTAT_SUCCESS;
if( !p_rx_fclose(pi) )
{
binkp_queuemsgf(&bpi, BPMSG_GOT, "%s %ld %ld",
pi->recv->net_name, (long)pi->recv->bytes_total,
(long)pi->recv->mod_time);
}
else
{
binkp_queuemsgf(&bpi, BPMSG_SKIP, "%s %ld %ld",
pi->recv->net_name, (long)pi->recv->bytes_total,
(long)pi->recv->mod_time);
}
}
}
}
#ifdef DEBUG
else
DEB((D_PROT, "ignore received data block"));
#endif
break;
case BPMSG_FILE:
if( recv_file )
{ p_rx_fclose(pi); recv_file = FALSE; }
if( binkp_parsfinfo(bpi.ibuf+1, &fname,
&fsize, &ftime, &foffs) )
{
binkp_queuemsg(&bpi, BPMSG_ERR, "FILE: ", "unparsable arguments");
goto FinishSession;
}
if( pi->recv && !p_compfinfo(pi->recv, fname, fsize, ftime)
&& pi->recv->bytes_skipped == foffs )
{
recv_file = TRUE;
break;
}
switch(p_rx_fopen(pi, fname, fsize, ftime, 0)) {
case 0:
if( pi->recv->bytes_skipped == 0 )
{
recv_file = TRUE;
break;
}
binkp_queuemsgf(&bpi, BPMSG_GET, "%s %ld %ld %ld",
pi->recv->net_name, (long)pi->recv->bytes_total,
(long)pi->recv->mod_time,
(long)pi->recv->bytes_skipped);
break;
case 1:
/* SKIP (non-destructive) */
binkp_queuemsgf(&bpi, BPMSG_SKIP, "%s %ld %ld",
pi->recv->net_name, (long)pi->recv->bytes_total,
(long)pi->recv->mod_time);
break;
case 2:
/* SKIP (destructive) */
binkp_queuemsgf(&bpi, BPMSG_GOT, "%s %ld %ld",
pi->recv->net_name, (long)pi->recv->bytes_total,
(long)pi->recv->mod_time);
break;
default:
ASSERT_MSG();
}
break;
case BPMSG_EOB: /* End Of Batch */
if( recv_file )
{
p_rx_fclose(pi);
recv_file = FALSE;
}
rcvd_EOB = TRUE;
break;
case BPMSG_GOT:
case BPMSG_SKIP:
if( binkp_parsfinfo(bpi.ibuf+1, &fname, &fsize, &ftime, NULL) == 0 )
{
if( send_file && !p_compfinfo(pi->send, fname, fsize, ftime) )
{
/* We got GOT/SKIP for current file! */
if( recv_rc == BPMSG_GOT )
pi->send->status = FSTAT_SKIPPED;
else
pi->send->status = FSTAT_REFUSED;
p_tx_fclose(pi);
send_file = FALSE;
break;
}
wait_got = FALSE;
for( i = 0; i < pi->n_sentfiles; i++ )
{
if( pi->sentfiles[i].status == FSTAT_WAITACK
&& !p_compfinfo(&pi->sentfiles[i], fname, fsize, ftime) )
{
s_finfo *tmp = pi->send;
pi->send = &pi->sentfiles[i];
if( recv_rc == BPMSG_GOT )
pi->send->status = FSTAT_SUCCESS;
else
pi->send->status = FSTAT_REFUSED;
p_tx_fclose(pi);
pi->send = tmp;
break;
}
else if( pi->sentfiles[i].status == FSTAT_WAITACK )
wait_got = TRUE;
}
/*
* Check, maybe there are files still
* waiting for the GOT/SKIP acknwoledge
*/
while( i < pi->n_sentfiles && !wait_got )
{
if( pi->sentfiles[i].status == FSTAT_WAITACK )
wait_got = TRUE;
++i;
}
}
break;
case BPMSG_ERR:
log("remote report error: \"%s\"", bpi.ibuf+1);
gotoexit(PRC_ERROR);
break;
case BPMSG_BSY:
log("remote busy error: \"%s\"", bpi.ibuf+1);
gotoexit(PRC_ERROR);
break;
case BPMSG_GET:
if( binkp_parsfinfo(bpi.ibuf+1, &fname, &fsize, &ftime, &foffs) == 0 )
{
if( send_file && !p_compfinfo(pi->send, fname, fsize, ftime) )
{
if( fseek(pi->send->fp, foffs, SEEK_SET) == -1 )
{
log("cannot send file from requested offset %ld", (long)foffs);
p_tx_fclose(pi);
send_file = FALSE;
}
else
{
log("sending \"%s\" from %ld offset",
pi->send->fname, (long)foffs);
pi->send->bytes_skipped = foffs;
pi->send->bytes_sent = foffs;
binkp_queuemsgf(&bpi, BPMSG_FILE, "%s %ld %ld %ld",
pi->send->net_name, (long)pi->send->bytes_total,
(long)pi->send->mod_time, (long)foffs);
}
}
}
break;
default:
log("binkp got unhandled msg #%d", recv_rc);
break;
}
} /* end of while( !sent_EOB || !rcvd_EOB ) */
FinishSession:
if( binkp_flush_queue(&bpi, bpi.timeout) && rc == PRC_NOERROR )
rc = PRC_ERROR;
exit:
if( pi->send && pi->send->fp ) p_tx_fclose(pi);
if( pi->recv && pi->recv->fp ) p_rx_fclose(pi);
binkp_deinit_bpinfo(&bpi);
DEB((D_PROT, "binkp: BINKP exit = %d", rc));
return rc;
}