/*
  axgetmail - read messages from FBB BBS using compressed download
  (c) 1999-2002 by Radek Burget OK2JBG <radkovo@centrum.cz>

  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.
*/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <sys/stat.h>
#include "common.h"
#include "getmsg.h"

/* program name */
#define PRGNAME "axgetmail"
#ifndef VERSION
#define VERSION "???"
#endif

/* verbosity level */
#define MSG_LEVEL MSG_RESULT

#define MAX_BLTNS    64                           /* max. num of bbs */
#define DEFAULT_GID  100                          /* default GID for messages */
#define CONFIG_FILE  "/etc/ax25/axgetmail.conf"   /* config file name */
#define AXPORTS      "/etc/ax25/axports"          /* axports file */
#define AX25_CALLS   "/etc/ax25/logins"           /* list of users */

#define MAIL_PATH "/var/ax25/mail"                /* directory for storing msgs */
#define LIST_PATH "/var/ax25/ulistd"              /* directory for storing lists */

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

char bbscall[256];          /* BBS callsign (from cmdline) */
char user_home[256];        /* user's home dir ($HOME) */

/* config */
char bltcall[20];           /* callsign for bulletins */
int perssid;                /* personal SSID */
int mail_group;             /* group of message files */
char homedir[256];          /* home directory of LinPac ($HOME/homedir) */
char port_name[256];        /* port name */
char bbspath[256];          /* connect path to BBS */
char bulletin[MAX_BLTNS][20]; /* bulletin names */
int num_bulletins;          /* number of bulletins */

/* read the home directory of user */
int get_home_dir(char *login, char *home)
{
  struct passwd *pwdentry = getpwnam(login);
  if (pwdentry)
  {
    strcpy(home, pwdentry->pw_dir);
    return 1;
  }
  return 0;
}

/* does the destination directory exist ? */
int dest_exist(char *home, char *homedir, char *bbs)
{
  char name[256];
  struct stat s;
  sprintf(name, "%s/%s/%s", user_home, homedir, bbs);
  if (stat(name, &s) != -1 && S_ISDIR(s.st_mode)) return 1;
  else return 0;
}

/* returns true when bulletin specification 'bname' matches to dest@fwd */
int match_dnload(char *dest, char *fwd, char *bname)
{
  char bcall[35];
  char bfwd[20];
  char *p = strchr(bname, '@');

  if (p == NULL) {strcpy(bcall, bname); strcpy(bfwd, "*");}
  else
  {
    *p = '\0';
    strcpy(bcall, bname);
    strcpy(bfwd, p+1);
    *p = '@';
  }

  if (*bcall && strcasecmp(bcall, dest) != 0) return 0;
  if (bfwd[0] != '*' && strcasecmp(bfwd, fwd) != 0) return 0;
  return 1;
}

/*========================================================================*/

/* Load configuration for one BBS */
void load_config(char *call)
{
  FILE *f;
  char pattern[256];
  char line[256];
  char pname[256];
  int found = 0;
  char field[30];
  char value[64];
  char *p, *q;

  strcpy(bltcall, "");
  strcpy(homedir, "");
  perssid = -1;
  mail_group = DEFAULT_GID;

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

  /* Find the BBS entry */
  sprintf(pattern, "[%s", call);
  while (!feof(f))
  {
    strcpy(line, "");
    fgets(line, 255, f);
    EX_EOLN(line);

    if (line[0] != '[' && line[0] != '#') /* BBS entries haven't started */
    {
      sscanf(line, "%s %s", field, value); /* configuration entry */
      if (strcmp(field, "HOMEDIR") == 0) strcpy(homedir, value);
      if (strcmp(field, "BLTCALL") == 0) strcpy(bltcall, value);
      if (strcmp(field, "PERSSID") == 0)
      {
        char *endptr;
        perssid = strtol(value, &endptr, 10);
        if (*endptr != '\0') perssid = -1;
      }
      if (strcmp(field, "MAILGROUP") == 0)
      {
        struct group *grentry = getgrnam(value);
        if (grentry) mail_group = grentry->gr_gid;
        else
        {
          message(MSG_ERROR, "%s: error: MAILGROUP is incorrect in config file\n", PRGNAME);
          exit(1);
        }
      }
    }
    else if (strncasecmp(line, pattern, strlen(pattern)) == 0 &&
             (line[strlen(pattern)] == '-' || line[strlen(pattern)] == ']'))
    {
      found = 1; /* BBS entry found */
      break;
    }
  }

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

  if (strlen(bltcall) == 0)
  {
    message(MSG_ERROR, "%s: error: BLTCALL is not set in config file\n", PRGNAME);
    exit(1);
  }
  
  if (perssid == -1)
  {
    message(MSG_ERROR, "%s: error: PERSSID is not set in config file or invalid value\n", PRGNAME);
    exit(1);
  }

  if (!found)
  {
    message(MSG_ERROR, "%s: %s has no entry in %s\n", PRGNAME, call, CONFIG_FILE);
    exit(1);
  }

  /* read path */
  fgets(bbspath, 255, f);
  EX_EOLN(bbspath);
  p = bbspath;
  q = pname;
  while (*p && !isspace(*p)) {*q = *p; p++; q++;}
  *q = '\0';
  while (*p && isspace(*p)) p++;
  strcpy(bbspath, p);
  strcpy(port_name, pname);
  
  /* read bulletin names */
  num_bulletins = 0;
  while (!feof(f))
  {
    strcpy(bulletin[num_bulletins], "");
    fgets(bulletin[num_bulletins], 19, f);
    EX_EOLN(bulletin[num_bulletins]);
    if (strlen(bulletin[num_bulletins]) > 0 &&
        bulletin[num_bulletins][0] != '[' &&
        num_bulletins < MAX_BLTNS) num_bulletins++;
    else break;
  }

  fclose(f);
}

/* is message present ? */
int msg_present(int num, char *bbs, int priv, char *mycall)
{
  char name[256];
  struct stat s;
  if (priv)
    sprintf(name, "%s/%s/%s/%i", user_home, homedir, bbs, num);
  else
    sprintf(name, "%s/%s/%i", MAIL_PATH, bbs, num);
  if (stat(name, &s) != -1 && !S_ISDIR(s.st_mode)) return 1;
  else return 0;
}

/* check if the message should be downloaded and download it */
int check_download(int num, char *dest, char *fwd, int get_priv, char *mycall)
{
  int i;
  int dnload = 0;
  int priv;

  for (i = 0; i < num_bulletins; i++)
  {
    if (!get_priv) /* check in bulletins */
    {
      if (match_dnload(dest, fwd, bulletin[i]))
      {
        dnload = 1;
        priv = 0;
        break;
      }
    }
    else /* check private msgs */
    {
      if (strcasecmp(bulletin[i], "*pers*") == 0 &&
          compare_call(dest, mycall))
      {
        dnload = 1;
        priv = 1;
        break;
      }
    }
  }

  if (dnload &&
      !msg_present(num, bbscall, priv, mycall) &&
      (!priv || dest_exist(user_home, homedir, bbscall)))
  {
    int result;
    char dest_dir[256];
    if (priv)
      sprintf(dest_dir, "%s/%s/%s", user_home, homedir, bbscall);
    else
      sprintf(dest_dir, "%s/%s", MAIL_PATH, bbscall);

    result = get_msg(mycall, port_name, bbspath, num, priv, dest_dir);
    switch (result)
    {
      case RET_ERR: message(MSG_ERROR, "Unable to read message %i\n", num); break;
      case RET_CONN: message(MSG_ERROR, "Cannot connect BBS\n"); break;
      case RET_MSG: message(MSG_ERROR, "Transfer failed for message %i\n", num); break;
      case RET_WRITE: message(MSG_ERROR, "Unable to save message %i\n", num); break;
    }
    return (result == RET_OK);
  }

  return 1;
}
    
/* go through the list and try and download messages */
void scan_list(char *bbs, int priv, char *call, char *login)
{
  char name[256];
  char line[256];
  char snum[20];
  char flags[5];
  char ssize[20];
  char dest[35];
  char fwd[20];
  FILE *f;

  /* determine the home directory for personal messages */
  if (priv)
  {
    if (!get_home_dir(login, user_home)) return;
  }

  /* open the list file */
  sprintf(name, "%s/%s", LIST_PATH, bbs);
  f = fopen(name, "r");
  if (f == NULL)
  {
    message(MSG_ERROR, "%s: %s has no index file\n", PRGNAME, bbs);
    exit(1);
  }
  
  while(!feof(f))
  {
    strcpy(line, "");
    fgets(line, 255, f);
    if (sscanf(line, "%s %s %s %s %s", snum, flags, ssize, dest, fwd) == 5)
    {
      char *p;
      
      if (strchr(flags, 'B') == NULL && strchr(flags, 'P') == NULL) continue;
      p = strchr(dest, '@');
      if (p != NULL)
      {
        *p = '\0';
        strncpy(fwd, p+1, 9);
      }
      else
      {
        if (fwd[0] == '@') memmove(fwd, fwd+1, strlen(fwd));
        else strcpy(fwd, "");
      }
      if (!check_download(atoi(snum), dest, fwd, priv, call)) break; /* download problems - break */
    }
  }

  fclose(f);
}


int main(int argc, char **argv)
{
  FILE *f;

  set_prg_name("axgetmail");
  msg_level = MSG_LEVEL;

  if (argc != 2)
  {
    fprintf(stderr, "Usage: %s <BBS_call>\n", PRGNAME);
    return 1;
  }

  strncpy(bbscall, argv[1], 19);
  normalize_call(bbscall);
  strip_ssid(bbscall);

  message(MSG_INFO, "axgetmail started for BBS %s\n", bbscall);

  load_config(bbscall);
  message(MSG_DEBUG, "config loaded BBS:\n"
                     "bltcall=%s\n"
                     "perssid=%i\n"
                     "mail_group=%i\n"
                     "homedir=%s\n"
                     "portname=%s\n"
                     "bbspath=%s\n"
                     "bulletins=%i\n",
                     bltcall, perssid, mail_group, homedir, port_name,
                     bbspath, num_bulletins);

  /* check bulletins */
  message(MSG_INFO, "Scanning for interesting bulletins\n");
  scan_list(bbscall, 0, bltcall, 0);

  /* check private message for associated users */
  message(MSG_INFO, "Looking up users\n");
  f = fopen(AX25_CALLS, "r"); /* read callsigns and logins */
  if (f != NULL)
  {
    char line[256];
    char call[30];
    char ssid[5];
    char login[256];

    sprintf(ssid, "-%i", perssid);

    while (!feof(f))
    {
      strcpy(line, "");
      fgets(line, 255, f);
      if (is_empty(line) || line[0] == '#') continue;
      if (sscanf(line, "%s %s", login, call) == 2)
      {
        normalize_call(call);
        if (call_ssid(call) == 0) strcat(call, ssid);
        message(MSG_INFO, "Scanning for personal mail for %s (%s)\n", call, login);
        scan_list(bbscall, 1, call, login);
      }
      else message(MSG_ERROR, "Invalid line in %s\n", AX25_CALLS);
    }
    fclose(f);
  }

  finish_transfer();
  return 0;
}

