/*
   axgetlist
   (c) 2000-2002 by Radek Burget OK2JBG

   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.

   Version 0.10 - started
   Version 0.11 - 20 jan 02
      Added options for collaboration with ulistd (CMD_LIST_FROM etc)
      Added config file options in order to be able parse month names
        and fixed position fields
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <getopt.h>
#include <errno.h>
#include "common.h"
#include "conbbs.h"
#include "auth.h"

#ifndef VERSION
#define VERSION "???"
#endif

#define PRGNAME "axgetlist"
#define CONFIG_FILE "/etc/ax25/axgetlist.conf"
#define LISTPATH "/var/ax25/ulistd"

/* strip eoln from string */
#define EX_EOLN(x) if (x[strlen(x)-1] == '\n') x[strlen(x)-1] = '\0'
#define EX_EOLNCR(x) if (x[strlen(x)-1] == '\r') x[strlen(x)-1] = '\0'

/* default month names */
char *def_month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

/* configuration */
char mycall[32];
char bbscall[32];
char bbsport[32];
char cmd_list[256];
char cmd_from[256];
char cmd_disc[256];
int header_lines;
char format[256];
char datefmt[16];
char *month[12];
int bpfpos;

int lastnum = 1;
FILE *list; /* message list */

/* Load configuration for one BBS */
void load_config(const char *bbsname)
{
  FILE *f;
  char line[256];
  char field[30];
  char *section;
  int i;

  strcpy(mycall, "");
  strcpy(bbscall, "");
  strcpy(bbsport, "");
  strcpy(cmd_list, "");
  strcpy(cmd_from, "");
  strcpy(cmd_disc, "");
  header_lines = -1;
  strcpy(format, "");
  strcpy(datefmt, "");
  for (i = 0; i < 12; i++) month[i] = NULL;
  bpfpos = -1;

  f = fopen(CONFIG_FILE, "r");
  if (f == NULL)
  {
    message(MSG_ERROR, "%s: Cannot open config file %s\n", PRGNAME, CONFIG_FILE);
    exit(1);
  }

  /* Find apropriate section */
  if (bbsname)
  {
     section = (char *) malloc((strlen(bbsname)+3)*sizeof(char));
     sprintf(section, "[%s]", bbsname);
  }
  while (!feof(f))
  {
     strcpy(line, "");
     fgets(line, 255, f);
     EX_EOLN(line);
     if (bbsname)
     {
        if (strcasecmp(section, line) == 0) break;
     }
     else
     {
        if (line[0] == '[') break;
     }
  }
  free(section);

  while (!feof(f))
  {
    strcpy(line, "");
    fgets(line, 255, f);
    EX_EOLN(line);

    if (line[0] == '[') break; /* start of next section found */
    
    if (line[0] != '#')
    {
      char *p;

      strcpy(field, "");
      p = line;
      while (*p && !isspace(*p)) { strncat(field, p, 1); p++; }
      while (*p && isspace(*p)) p++;

      if (strcmp(field, "MYCALL") == 0) strncpy(mycall, p, 31);
      if (strcmp(field, "BBSCALL") == 0) strncpy(bbscall, p, 31);
      if (strcmp(field, "BBSPORT") == 0) strncpy(bbsport, p, 31);
      if (strcmp(field, "CMD_LIST") == 0) strncpy(cmd_list, p, 255);
      if (strcmp(field, "CMD_LIST_FROM") == 0) strncpy(cmd_from, p, 255);
      if (strcmp(field, "CMD_DISC") == 0) strncpy(cmd_disc, p, 255);
      if (strcmp(field, "HEADER_LINES") == 0)
      {
        char *endptr;
        header_lines = strtol(p, &endptr, 10);
        if (*endptr != '\0') header_lines = -1;
      }
      if (strcmp(field, "FORMAT") == 0) strncpy(format, p, 255);
      if (strcmp(field, "DATEFMT") == 0) strncpy(datefmt, p, 15);
      if (strcmp(field, "MONTH") == 0)
      {
        char *endptr;
        int m;
        m = strtol(p, &endptr, 10);
        if (*endptr == ' ' && m >= 1 && m <= 12)
        {
           endptr++;
           month[m-1] = strdup(endptr);
        }
        else
        {
           message(MSG_ERROR, "%s: warning: illegal MONTH setting in config file (ignored)", PRGNAME);
        }
      }
      if (strcmp(field, "BPFPOS") == 0)
      {
        char *endptr;
        bpfpos = strtol(p, &endptr, 10);
        if (*endptr != '\0') bpfpos = -1;
      }
    }
  }

  /* check configuration */
  if (strlen(mycall) == 0)
  {
    message(MSG_ERROR, "%s: error: MYCALL is not set in config file\n", PRGNAME);
    exit(1);
  }

  if (strlen(bbscall) == 0)
  {
    message(MSG_ERROR, "%s: error: BBSCALL is not set in config file\n", PRGNAME);
    exit(1);
  }

  if (strlen(bbscall) == 0)
  {
    message(MSG_ERROR, "%s: error: BBSPORT is not set in config file\n", PRGNAME);
    exit(1);
  }

  if (strlen(cmd_list) == 0)
  {
    message(MSG_ERROR, "%s: error: CMD_LIST is not set in config file\n", PRGNAME);
    exit(1);
  }

  if (strlen(cmd_from) == 0)
  {
    message(MSG_ERROR, "%s: error: CMD_LIST_FROM is not set in config file\n", PRGNAME);
    exit(1);
  }

  if (strlen(cmd_disc) == 0)
  {
    message(MSG_ERROR, "%s: error: CMD_DISC is not set in config file\n", PRGNAME);
    exit(1);
  }

  if (header_lines == -1)
  {
    message(MSG_ERROR, "%s: error: HEADER_LINES is not set in config file or invalid value\n", PRGNAME);
    exit(1);
  }

  if (strlen(format) == 0)
  {
    message(MSG_ERROR, "%s: error: FORMAT is not set in config file\n", PRGNAME);
    exit(1);
  }

  if (strlen(datefmt) == 0)
  {
    message(MSG_ERROR, "%s: error: DATEFMT is not set in config file\n", PRGNAME);
    exit(1);
  }

  if (bpfpos == -1)
  {
    message(MSG_ERROR, "%s: error: BPFPOS is not set in config file or invalid value\n", PRGNAME);
    exit(1);
  }

  for (i = 0; i < 12; i++)
     if (month[i] == NULL) month[i] = def_month[i];

  fclose(f);
}


/* Decode the message entry
   Returns message number when succesfuly decoded, 0 or -1 when not */
int convert_entry(char *data, char *format, char *result)
{
  int msgnum = -1;
  char flag;
  int size;
  char dest[32];
  char bbs[32];
  char src[32];
  char date[8];
  char subj[256];
  char dd[8], dm[8], dy[8];
  struct tm *tim;
  time_t ttime;

  char *p, *q;
  char tag[32];
  char value[256];
  int in_macro = 0;
  
  /* scan format string and interpret data */
  p = format;
  q = data;
  while (*p)
  {
    if (in_macro) /* inside the <tag> */
    {
       if (*p == '>') /* end of the tag */
       {
         in_macro = 0;
         while(*q && isspace(*q)) q++;
         strcpy(value, "");
         if (*(p+1) == '\0') /* end of the format string */
            while(*q)
            {
              strncat(value, q, 1); /* copy the rest */
              q++;
            }
         else
            while(*q && !isspace(*q) && (!*p || *q != *(p+1)))
            {
              strncat(value, q, 1);
              q++;
            }

         if (strcasecmp(tag, "NUM") == 0) msgnum = atoi(value);
         else if (strcasecmp(tag, "FLAGS") == 0)
           if (strlen(value) >= bpfpos) flag = value[bpfpos-1]; else flag = '#';
         else if (strcasecmp(tag, "SIZE") == 0) size = atoi(value);
         else if (strcasecmp(tag, "TO") == 0) strcpy(dest, value);
         else if (strcasecmp(tag, "BBS") == 0) strcpy(bbs, value);
         else if (strcasecmp(tag, "FROM") == 0) strcpy(src, value);
         else if (strcasecmp(tag, "DATE") == 0) strcpy(date, value);
         else if (strcasecmp(tag, "TIME") == 0) ;
         else if (strcasecmp(tag, "SUBJ") == 0) strcpy(subj, value);
         else if (strcasecmp(tag, "OTHER") == 0) ;
         else
         {
           message(MSG_ERROR, "Unknown format field in config file (%s)\n", tag);
           exit(RET_ERR);
         }
       }
       else strncat(tag, p, 1);
    }
    else
    {
       while(*q && isspace(*q)) q++;
       while(*p && isspace(*p)) p++;
       if (*p ==  '<')
       {
         in_macro = 1;
         strcpy(tag, "");
       }
       else if (*p == '\\')
       {
         char pos[1024];
         char *pp = pos;
         int position;

         strcpy(pos, "");
         p++;
         while (*p && isdigit(*p) && strlen(pos) < 1022)
         {
           *pp = *p; p++; pp++;
         }
         *pp = '\0';
         position = atoi(pos);
         while (*q && q - data < position) q++;
         p--; /* step back not to miss following tag */
       }
       else
       {
         if (*q == *p) q++;
       }
    }
    p++;
  }

  /* scan the date format */
  sprintf(dd, "00"); sprintf(dm, "00");
  ttime = time(NULL);          /* use current year */
  tim = localtime(&ttime);
  strftime(dy, 7, "%y", tim);
  
  p = strchr(datefmt, 'd');   /* day */
  if (p != NULL)
  {
    strcpy(dd, "");
    q = date + (p - datefmt);
    while (*p && *q && *p == 'd' && (isdigit(*q) || *q == ' '))
       { strncat(dd, q, 1); q++; p++; }
  }

  p = strchr(datefmt, 'm');   /* month */
  if (p != NULL)
  {
    strcpy(dm, "");
    q = date + (p - datefmt);
    while (*p && *q && *p == 'm' && (isdigit(*q) || *q == ' '))
       { strncat(dm, q, 1); q++; p++; }
  }

  p = strchr(datefmt, 'M');   /* month in words */
  if (p != NULL)
  {
    int i;
    strcpy(dm, "");
    q = date + (p - datefmt);
    while (*p && *q && *p == 'M' && (isalnum(*q) || *q == ' '))
       { strncat(dm, q, 1); q++; p++; }
    for (i = 0; i < 12; i++)
       if (strcasecmp(month[i], dm) == 0)
       {
          sprintf(dm, "%i", i+1);
          break;
       }
  }

  p = strchr(datefmt, 'm');   /* year */
  if (p != NULL)
  {
    strcpy(dy, "");
    q = date + (p - datefmt);
    while (*p && *q && *p == 'y' && (isdigit(*q) || *q == ' '))
       { strncat(dy, q, 1); q++; p++; }
  }

  sprintf(result, "%i  %c %6i %-6s@%-6s %-6s %02i%02i%02i %s", msgnum,
                                                               flag,
                                                               size,
                                                               dest,
                                                               bbs,
                                                               src,
                                                               atoi(dy),
                                                               atoi(dm),
                                                               atoi(dd),
                                                               subj);
  return msgnum;
}

/* Wait for prompt from BBS. Returns 0 when the connection has been broken */
int wait_prompt(FILE *bbs_stream)
{
  int old_char=0, new_char=0;
  message(MSG_DEBUG, "Waiting for BBS prompt\n");
  do
  {
    old_char = new_char;
    new_char = ffgetc(bbs_stream);
  } while ((old_char != '>' || new_char != '\r') && new_char != EOF);
  return (new_char != EOF);
}

/* fgets_cr - works like fgets, but uses CR as EOLN */
char *fgets_cr(char *s, int size, FILE *stream)
{
  int cnt = 0;
  int ch;
  while (cnt < size)
  {
    do ch = fgetc(stream); while (ch == EOF && (errno == EINTR || errno == EAGAIN));
    if (ch == EOF) {s[cnt++] = '\0'; break;}
    s[cnt++] = ch;
    if (ch == '\r') {s[cnt++] = '\0'; break;}
  }
  return s;
}

/*=========================== Mail list operations ========================*/

/* returns the number of last message on the list */
int last_message(char *bcall)
{
  int msgnum = 1;

  FILE *f;
  char s[256];
  int i, n;
    
  sprintf(s, "%s/%s", LISTPATH, bcall);
  f = fopen(s, "r");
  if (f != NULL)
  {
    while (!feof(f))
    {
      fgets(s, 255, f);
      n = sscanf(s, "%i", &i);
      if (n >= 1) msgnum = i;
    }
    fclose(f);
  }
  else
  {
    f = fopen(s, "w");
    if (f == NULL)
    {
        message(MSG_ERROR, "Cannot create file %s", s);
    }
    else fclose(f);
  }

  return msgnum;
}

/* add next line to list */
void add_line(char *bbs, int msgnum, char *line)
{
  FILE *f;
  char s[256];

  if (msgnum > lastnum)
  {
    sprintf(s, "%s/%s", LISTPATH, bbs);
    f = fopen(s, "a");
    if (f == NULL)
    {
      message(MSG_ERROR, "Cannot write to file %s\n", s);
    }
  
    fprintf(f, "%s\n", line);
    lastnum = msgnum;
    fclose(f);
  }
}

/* send the commands separated by semicolons to the BBS */
void send_commands(FILE *stream, char *cmds, int from)
{
  char *p = cmds;

  while (*p)
  {
    while(*p && *p != ';')
    {
       if (*p == '$')
       {
          char snum[256];
          sprintf(snum, "%i", from);
          fputs(snum, stream);
       }
       else
          fputc(*p, stream);
       p++;
    }
    fputc('\r', stream);
    if (*p == ';')
    {
      wait_prompt(stream);
      p++;
    }
  }
}

/* wait for disconnect */
void wait_disc(FILE *stream)
{
  int ch;
  do ch = fgetc(stream); while (ch != -1 || errno != ENOTCONN);
}

void help()
{
   fprintf(stderr, "Usage: axgetlist [-b BBS_name] [-s start]\n");
}

int main(int argc, char **argv)
{
  FILE *stream;
  char bcall[32];
  char s[256];
  char line[256];
  int i;
  int msgnum;
  int empty;
  int startfrom = -1;
  char *bbsname = NULL;

  char *endpos;

  while ((i = getopt(argc, argv, "b:s:hv")) != -1)
  {
     switch(i)
     {
       case 'b': bbsname = strdup(optarg); break;
       case 's': startfrom = strtol(optarg, &endpos, 10);
                 if (*endpos) { help(); exit(1); }
                 break;
       case 'v': printf("%s / ax25mail-utils %s", PRGNAME, VERSION);
                 exit(0);
                 break;
       case 'h':
       case '?':
       default : help(); exit(1);
     }
  }
  
  load_config(bbsname);
  strcpy(bcall, call_call(bbscall));
  normalize_call(bcall);
  normalize_call(mycall);
  
  stream = conbbs(mycall, bbsport, bbscall);
  if (stream == NULL)
  {
    message(MSG_ERROR, "Cannot connect BBS");
    exit(RET_CONN);
  }

  exec_auth_agent(stream, mycall, bcall);
  strip_ssid(bcall);

  lastnum = last_message(bcall);
  
  wait_prompt(stream);
  if (startfrom == -1) send_commands(stream, cmd_list, 0);
                  else send_commands(stream, cmd_from, startfrom);
  /* skip blank lines */
  empty = 0;
  for (i = 0; i < header_lines; i++)
  {
    fgets_cr(s, 255, stream);
    if (strstr(s, ">\r") != NULL) /* prompt detected - the list is empty */
    {
      empty = 1;
      break;
    }
  }
  /* read message lines */
  if (!empty)
  {
    do
    {
      fgets_cr(s, 255, stream);
      EX_EOLNCR(s);
      msgnum = convert_entry(s, format, line);
      if (msgnum > 0) add_line(bcall, msgnum, line);
      /*if (msgnum > 0) printf("%s\n", line);*/
    } while(msgnum > 0);
    wait_prompt(stream);
  }

  send_commands(stream, cmd_disc, 0);
  wait_disc(stream);
  disc_bbs(stream);
  
  return 0;
}

