/* -----------------------------------------------------------------------------

D'Bridge Queue support library for the Brake!(tm) Mailer - Public domain module.

Version 1.0 from September, 8, 1996.

Copyright (c) 1996 by Bad Data, INC. The Author is John Gladkih.

FIDO netmail to 2:5051/16
mailto:john@simcb.ru
http://access.MVC.net/~john

линковать следует с многопоточным рантаймом т.к. SCAN??? и SEND??? вызываются
из разных потоков.

необходимо экспортировать из DLL по имени следующие функции:

1. int _System _Export INIT( char *params, LOGFunc log );

  вызывается сразу после загрузки DLL (DosLoadModule), params берется
    из Brake!.Ctl, log - функция записи в Brake!.Log
  если INIT вернет не 0 - то данная DLL считается проинициализированной
    неуспешно и не будет использоваться.

2. void _System _Export DONE();

  вызывается сразу перед выгрузкой DLL из памяти (DosFreeModule).

3. void _System _Export SCANINIT();

  вызывается перед SCAN (инициализация процесса сканирования).

4. void _System _Export SCANDONE();

  вызывается после SCAN (завершение процесса сканирования).

5. void _System _Export SCAN( void *que, AddInQueueCallBackNodeFound *back );

  сканирование очереди, передавайте que в back неизменным! если back вернет
  не 0 то необходимо завершить сканирование.

6. void _System _Export SENDINIT( Address *addr );

  вызывается перед SEND (инициализация процесса передачи для addr).

7. void _System _Export SENDDONE();

  вызывается после SEND (завершение процесса передачи).

8. int _System _Export SEND( Address *addr, Flavour fl, FileType type,
                             AddInQueueCallBackSend *back );

  передача файла ремоте addr с
   флавором fl (immed/crash/direct/normal/hold/request)
   типа type (Mail, Compressed mail, File, Request);
   если у вас попросили нечто опpеделенного типа и флавоpа то и
   следует передавать ТОЛЬКО это!!!
  back возвращает следующие коды возврата:
                  0 - файл передан,
                 -1 - ошибка передачи,
                  1 - отказ от приема (больше этот файл передавать не нужно),
                  2 - отказ от приема (этот файл следует передать позже);
   если back вернула -1 то следует прекратить передачу.
  SEND должна возвращать 0 если back не вернула -1 или не 0 если back вернула
   -1 (в этом случае сессия будет считаться неуспешной).


----------------------------------------------------------------------------- */

#include <stdio.h>
#include <io.h>
#include <share.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <ctype.h>
#include <memory.h>
#include <locale.h>
#include <builtin.h>

#include "../language.h"
#include "../direct.h"
#include "../filetype.h"

#define newStr( s ) ( (s)==NULL ? \
                           (char *)NULL : \
                           (char *)strcpy( new char [ strlen(s)+1 ], s ) )

#define tmpStr( s ) ( (s)==NULL ? \
                           (char *)NULL : \
                           (char *)strcpy( (char *)_alloca( strlen(s)+1 ), s ) )

typedef unsigned short uShort;
typedef unsigned long uLong;

struct Address
{
  uShort zone, net, node, point;
  char *domain;
};

enum Flavour
{
  unknownFlavour=0, holdFlavour=1,
  normalFlavour=2, directFlavour=4,
  crashFlavour=8, immedFlavour=16,
  onlineFlavour=32, requestFlavour=64
};

struct Traffic
{
  uLong
	holdMail, crashMail, normalMail,
	immedMail, directMail,
	holdXMail, crashXMail, normalXMail,
	immedXMail, directXMail,
	holdFiles, crashFiles, normalFiles,
	immedFiles, directFiles,
	request;
  long age;
  Flavour poll;
  unsigned long holdTo;
};

typedef int _System AddInQueueCallBackNodeFound( void    *que, 
                                                 Address *addr,
                                                 Traffic *traf );
typedef void LOGFunc( char* ... );
typedef int _System AddInQueueCallBackSend( char *path, char *alias, FileProcess mode );
typedef int _System AddInQueueInit( char *params, LOGFunc log );
typedef void _System AddInQueueDone();
typedef void _System AddInQueueScanInit();
typedef void _System AddInQueueScanDone();
typedef void _System AddInQueueScan( void *, AddInQueueCallBackNodeFound * );
typedef void _System AddInQueueSendInit( Address * );
typedef void _System AddInQueueSendDone();
typedef void _System AddInQueueSend( Address *, Flavour, FileType, AddInQueueCallBackSend * );

LOGFunc *log;

class FailAddress {};

static void checkAddress( char *addr, uShort& zone, uShort& net,
                          uShort& node, uShort& point,
                          char **domain )
{
  uShort num;
  char *term( NULL );
  FailAddress error;

  zone = net = node = point = 0;
  *domain = NULL;

  char state( 0 ),
       zof( 0 ),
       nef( 0 ),
       nof( 0 );

  for( ;; addr = term+1 )
  {
    num = uShort( strtoul( addr, &term, 10 ) );

    switch( *term )
    {
     case ':':
       zone = num;
       state = 1;
       zof = 1;
       break;

     case '/':
       net = num;
       state = 2;
       nef = 1;
       break;

     case '.':
       node = num;
       state = 3;
       nof = 1;
       break;

     case 0:
     case '@':
       switch( state )
       {
       case 0:
	 node = num;
	 nof = 1;
	 break;

       case 1:
	 net = num;
	 nef = 1;
	 break;

       case 2:
	 node = num;
	 nof = 1;
	 break;

       case 3:
	 point = num;
	 break;
       }

       if( *term=='@' )
	 *domain = term+1;

       if( (point && (!zone && !node && !net)) ||
           !zof || !nof || !nef ||
           zone>4095 )
         throw error;
       
       return;

     default:
       throw error;
    }
  }
}

static uShort zone, net, node, point;
static char *domain;
static char *queuePath;

int _System _Export INIT( char *params, LOGFunc logIni )
{
  log = logIni;
  log( "DBRIDGE INIT: Begin D'Bridge Queue support", params );

  if( setlocale( LC_ALL, localeName )==NULL )
    log( Msg_ErrNoLocale, localeName );

  if( getenv( "TZ" )==NULL )
  {
    static char tz[]="TZ=MSK-3MSD";

    log( Msg_InfoTZ, tz );

    if( putenv( tz ) )
    {
      log( Msg_ErrUnableAdd, tz );
      return 1;
    }
  }

  tzset();

  register char *p = new char [ strlen(params)+1 ];
  char *pStart = p;

  for( register char blank=1; *params; )
  {
    register char ch = *params;

    if( isspace(ch) )
    {
      if( !blank )
      {
        blank = 1;
        *(p++) = ' ';
      }

      params++;
    }
    else
    {
      blank = 0;
      *(p++) = *(params++);
    }
  }

  --p;

  while( isspace(*p) )
    --p;

  p[1] = 0;

  p = strchr( pStart, ' ' );

  if( p==NULL )
  {
    log( "DBRIDGE INIT: Two params need" );
    delete pStart;
    return 1;
  }

  *(p++) = 0;

  try
  {
    checkAddress( pStart, zone, net, node, point, &domain );
  }
  catch( FailAddress )
  {
    log( "DBRIDGE INIT: Illegal address `%s'", pStart );
    delete pStart;
    return 1;
  }

  char *ppp = newStr( domain==NULL ? "FidoNet" : domain );
  domain = ppp;

  queuePath = new char[ strlen(p)+2 ];
  strcpy( queuePath, p );

  if( queuePath[ strlen(queuePath)-1 ]!='\\' )
    strcat( queuePath, "\\" );

  delete pStart;
  log( "DBRIDGE INIT: Our address is %d:%d/%d.%d@%s", zone, net, node, point,
                                                      domain );
  log( "DBRIDGE INIT: Queue location is %s", queuePath );

  return 0;
}

void _System _Export DONE()
{
  log( "DBRIDGE DONE: Operation complete." );
  delete domain;
  delete queuePath;
}

void _System _Export SCANINIT()
{
}

void _System _Export SCANDONE()
{
}

FILE *_fsopen( const char *filename, const char *orimode, int shflag )
{
  int open_mode = 0;
  int mode = 0;
  const char *fmode = orimode;

  switch( *fmode )
  {
  case 'w':
    open_mode = O_CREAT | O_WRONLY | O_TRUNC;
    mode = S_IWRITE;
    break;

  case 'r':
    open_mode = O_RDONLY;
    break;

  case 'a':
    open_mode = O_WRONLY | O_CREAT | O_APPEND;
    mode = S_IWRITE;
    break;

  default:
Illegal:
    errno = 255;
    return (FILE *)NULL;
  }

  for( fmode++; *fmode; fmode++ )
    switch( *fmode )
    {
    case 't':
      open_mode = (open_mode & ~O_BINARY) | O_TEXT;
      break;

    case 'b':
      open_mode = (open_mode & ~O_TEXT) | O_BINARY;
      break;

    case '+':
      open_mode = (open_mode & ~(O_WRONLY | O_RDONLY)) | O_RDWR;
      mode = S_IREAD | S_IWRITE;
      break;

    default:
      goto Illegal;
    }

  int fd = sopen( filename, open_mode|O_NOINHERIT, shflag, mode );

  if( fd==-1 )
    return (FILE *) NULL;

  FILE *stream = fdopen( fd, orimode );

  if( stream==NULL )
    close( fd );

  return stream;
}

void static inline addTraff( uLong& Mail, uLong& XMail, uLong& Files,
                             uLong size, FileType type )
{
  switch( type )
  {
  case FileMail:
    Mail += size;
    break;

  case FileXMail:
    XMail += size;
    break;

  default:
    Files += size;
    break;
  }
}

static char tab36[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

inline char AtoN( char c )
{
  c = toupper(c);

  if( c<'A' )
    c -= '0';
  else
    c -= 'A'-10;

  return c;
}

static long _A36ToDec_( char* a36,int len )
{
  long n = AtoN( a36[len-1] );
  long p = 36L;

  for( int j=1; j<=len-1; j++ )
  {
    n += p*AtoN( a36[len-j-1] );
    p *= 36L;
  }

  return n;
}

static void _DecToA36_( long Dec, char *a36, int len )
{
  for( int j=0; j<len; j++ )
  {
    a36[len-j-1] = tab36[ Dec%36 ];
    Dec /= 36L;
  }
}

static void _DecToA36( long Dec, char *a36, int len )
{
  for( int j=0; j<len; j++ )
  {
    a36[len-j-1] = tab36[ Dec%36 ];
    Dec /= 36L;
  }

  a36[len]=0;
}

static long _A36ToDec( char *a36 )
{
  int i  = strlen( a36 )-1;
  long n = AtoN( a36[i] );
  long p = 36L;

  for( int j=1; j<=i; j++ )
  {
    n += p*AtoN( a36[i-j] );
    p *= 36L;
  }

  return n;
}

static void to36( char *box, int zone, int net, int node, int point )
{
  if( point )
  {
    					// POINT.XXX
    strcpy( box, "POINT." );
    _DecToA36( point, box+6, 3 );
  }
  else
  {
					// ZZZNNN.NNN
    _DecToA36_( zone, box, 3 );
    _DecToA36_( net,  box+3, 3 );
    strcpy( box+6, "." );
    _DecToA36( node, box+7, 3 );
  }

  strcat( box, "\\" );
}

static void process( uShort zone, uShort net, uShort node, uShort point,
                     char *path, char *name, AddInQueueCallBackNodeFound *back,
                     void *que )
{
  Address adr;
  Traffic traf;

  char *full = (char *)_alloca( strlen(name)+strlen(path)+1 );

  strcpy( full, path );
  strcat( full, name );

  FILE *q = _fsopen( full, "r", SH_DENYNO );

  if( q==NULL )
  {
    log( "DBRIDGE SCAN: Open Error" );
    log( Msg_ErrUnableOpen, full, strerror( errno ) );
    return;
  }

  adr.zone = zone;
  adr.net = net;
  adr.node = node;
  adr.point = point;
  adr.domain = domain;

  memset( &traf, 0, sizeof(traf) );
  traf.poll = unknownFlavour;

  for( ;; )
  {
    char buf[512];

    if( fgets( buf, sizeof(buf), q )==NULL )
      break;

    char *bl = strchr( buf+4, ' ' ),
         *alias = NULL;

    if( bl!=NULL )
    {
      *bl = 0;
      alias = bl+1;

      char *p = strchr( alias, ' ' );

      if( p!=NULL )
        *p = 0;
    }

    struct stat st;
    FileType type;
    Flavour fl;

    msgOfFileType( alias==NULL ? buf+4 : alias, &type );

    switch( toupper(buf[0]) )
    {
    case 'I':
      fl = immedFlavour;
      break;

    case 'C':
      fl = crashFlavour;
      break;

    case 'D':
      fl = directFlavour;
      break;

    case 'N':
      fl = normalFlavour;
      break;

    default:
      fl = holdFlavour;
    }

    buf[2] = toupper( buf[2] );

    switch( buf[2] )
    {
    case 'F':
      traf.request += strlen(buf+4)+2;

    // fall

    case 'P':

      if( fl>traf.poll )
        traf.poll = fl;

      break;

    case 'T':
      if( !stat( buf+4, &st ) )
      {
        if( !traf.age || (traf.age && traf.age>st.st_ctime) )
          if( type==FileMail || type==FileXMail )
            traf.age = st.st_ctime;

        switch( fl )
        {
        case immedFlavour:
          addTraff( traf.immedMail, traf.immedXMail, traf.immedFiles,
                    st.st_size, type );
          break;

        case crashFlavour:
          addTraff( traf.crashMail, traf.crashXMail, traf.crashFiles,
                    st.st_size, type );
          break;

        case directFlavour:
          addTraff( traf.directMail, traf.directXMail, traf.directFiles,
                    st.st_size, type );
          break;

        case normalFlavour:
          addTraff( traf.normalMail, traf.normalXMail, traf.normalFiles,
                    st.st_size, type );
          break;

        default:
          addTraff( traf.holdMail, traf.holdXMail, traf.holdFiles,
                    st.st_size, type );
        }					// switch( fl )
      }						// if( !stat() )
    }						// switch( buf[2] )
  }						// for( ;; )

  fclose( q );

  if( traf.poll==unknownFlavour &&
      !traf.crashMail  && !traf.crashXMail  && !traf.crashFiles  &&
      !traf.normalMail && !traf.normalXMail && !traf.normalFiles &&
      !traf.directMail && !traf.directXMail && !traf.directFiles &&
      !traf.immedMail  && !traf.immedXMail  && !traf.immedFiles     )
    traf.poll = holdFlavour;

  if( traf.age )
    traf.age = time( NULL ) - traf.age;

  (*back)( que, &adr, &traf );
}

class FailGet36 {};

static int get36( char *ptr, int how )
{
  register num = 0;
  FailGet36 error;

  for( ; how; how--, ptr++ )
  {
    if( !*ptr )
      throw error;

    char *p = strchr( tab36, toupper(*ptr) );

    if( p==NULL )
       throw error;

    num = num*36+(p-tab36);
  }

  return num;
}

static void processNode( char *path, char *name,
                         AddInQueueCallBackNodeFound *back,
                         void *que )
{
					// q-zzznnn.nnn
  uShort zone,
         net,
         node;

  try
  {
    zone = get36( name+2, 3 );
    net  = get36( name+5, 3 );
    node = get36( name+9, 3 );
  }
  catch( FailGet36 )
  {
    return;
  }

  process( zone, net, node, 0, path, name, back, que );
}

static void processPoint( char *path, char *name,
                          AddInQueueCallBackNodeFound *back,
                          void *que )
{
					// q-point.ppp
  uShort point;

  try
  {
    point = get36( name+8, 3 );
  }
  catch( FailGet36 )
  {
    return;
  }

  process( zone, net, node, point, path, name, back, que );
}

void _System _Export SCAN( void *que, AddInQueueCallBackNodeFound *back )
{
  char mask[]="Q-*",
       *search = (char *)_alloca( strlen(queuePath)+sizeof(mask) );

  strcpy( search, queuePath );
  strcat( search, mask );

  for( Directory dir( search, SearchAttributes( MayBeArchived ) ); dir; dir++ )
  {
    char *name = dir.getName(),
         *path = dir.getPath();
    int len = strlen( name );

    if( len==11 && !strnicmp( name, "Q-POINT.", 8 ) )
      processPoint( path, name, back, que );
    else if( len==12 && name[8]=='.' )
      processNode( path, name, back, que );
  }
}

void _System _Export SENDINIT( Address * )
{
}

void _System _Export SENDDONE()
{
}

int _System _Export SEND( Address *, Flavour, FileType, AddInQueueCallBackSend * )
{
  return 0;
}
