/*
 *  config.c
 *
 *  Written on 30-Jul-90 by jim nutt.  Changes on 10-Jul-94 by John Dennis.
 *  Released to the public domain.
 *
 *  Finds & reads configuration files, initializes everything.
 */

#ifndef DEFAULT_CONFIG_FILE
#ifdef UNIX
#define DEFAULT_CONFIG_FILE "~/.msged"
#else
#define DEFAULT_CONFIG_FILE "msged.cfg"
#endif
#endif

#if defined(OS2) || defined(UNIX) || defined(WINNT)
#define TEXTLEN    1024
#else
#define TEXTLEN    512
#endif
#define AREAS_BBS  0

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <smapi/msgapi.h>
#include "addr.h"
#include "areas.h"
#include "dirute.h"
#include "nedit.h"
#include "msged.h"
#include "winsys.h"
#include "menu.h"
#include "main.h"
#include "strextra.h"
#include "memextra.h"
#include "bmg.h"
#include "fecfg145.h"
#include "gestr120.h"
#include "version.h"
#include "nshow.h"
#include "readmail.h"
#include "screen.h"
#include "wrap.h"
#include "config.h"
#include "environ.h"
#include "charset.h"
#include "fconf.h"
#include "patmat.h"
#include "group.h"


int areas_type;

void applyflags(AREA *a, char *flagstring);

/*
 *  This structure is used to cache the "Group" statements until after
 *  the configuration file has been processed.
 *
 */

typedef struct _group
{
    int handle;
    char *pattern;
}
GROUP;


static char **tags2skip;
static char *mntdirunix = NULL, *mntdirdos = NULL;
char *areafileflags = NULL;
static GROUP *group = NULL;
static int num_groups = 0, check_area_files = 0;


static char *cfgverbs[] =
{
    "Name",
    "Address",
    "PrivateNet",
    "Alias",
    "OutFile",
    "Lastread",
    "TossLog",
    "UserList",
    "SwapPath",
    "NodePath",
    "Nodelist",
    "HelpFile",
    "AreaFile",
    "UserOffset",
    "Quote",
    "AlterFunc",
    "Switch",
    "Color",
    "Right",
    "QuoteRight",
    "TabSize",
    "CurStart",
    "CurEnd",
    "Fido",
    "Squish",
    "UUCP",
    "Domain",
    "Gate",
    "Origin",
    "ReadKey",
    "EditKey",
    "Function",
    "Include",
    "MaxX",
    "MaxY",
    "Template",
    "UucpName",
    "Group",
    "Colour",
    "Editor",
    "RobotName",
    "QuickBBS",
    "Quick",
    "Scan",
    "MountDir",
    "SoftCrXlat",
    "AreaExcl",
    "OutputCharset",
    "SortAreas",
    "EnableSC",
    "AreaFileFlags",
    "FreqArea",
    "AssumeCharset",
    "If",
    "Else",
    "Elif",
    "Elseif",
    "Endif",
    "UucpReplyTo",
    "FreqFlags",
    "Printer",
    "Jam",
    "ReadMap",
    "WriteMap",
    "CharsetAlias",
    "AreaDesc",
    "GroupSettings",
    NULL
};

#define CFG_NAME           0
#define CFG_ADDRESS        1
#define CFG_PRIVATENET     2
#define CFG_ALIAS          3
#define CFG_OUTFILE        4
#define CFG_LASTREAD       5
#define CFG_TOSSLOG        6
#define CFG_USERLIST       7
#define CFG_SWAPPATH       8
#define CFG_NODEPATH       9
#define CFG_NODELIST       10
#define CFG_HELPFILE       11
#define CFG_AREAFILE       12
#define CFG_USEROFFSET     13
#define CFG_QUOTE          14
#define CFG_ALTERFUNC      15
#define CFG_SWITCH         16
#define CFG_COLOR          17
#define CFG_RIGHT          18
#define CFG_QUOTERIGHT     19
#define CFG_TABSIZE        20
#define CFG_CURSTART       21
#define CFG_CUREND         22
#define CFG_FIDO           23
#define CFG_SQUISH         24
#define CFG_UUCP           25
#define CFG_DOMAIN         26
#define CFG_GATE           27
#define CFG_ORIGIN         28
#define CFG_READKEY        29
#define CFG_EDITKEY        30
#define CFG_FUNCTION       31
#define CFG_INCLUDE        32
#define CFG_MAXX           33
#define CFG_MAXY           34
#define CFG_TEMPLATE       35
#define CFG_UUCPNAME       36
#define CFG_GROUP          37
#define CFG_COLOUR         38
#define CFG_EDITOR         39
#define CFG_ROBOTNAME      40
#define CFG_QUICKBBS       41
#define CFG_QUICK          42
#define CFG_SCAN           43
#define CFG_MOUNTDIR       44
#define CFG_SOFTCRXLAT     45
#define CFG_AREAEXCL       46
#define CFG_OUTPUTCHARSET  47
#define CFG_SORTAREAS      48
#define CFG_ENABLESC       49
#define CFG_AREAFILEFLAGS  50
#define CFG_FREQAREA       51
#define CFG_ASSUMECHARSET  52
#define CFG_IF             53
#define CFG_ELSE           54
#define CFG_ELIF           55
#define CFG_ELSEIF         56
#define CFG_ENDIF          57
#define CFG_UUCPREPLYTO    58
#define CFG_FREQFLAGS      59
#define CFG_PRINTER        60
#define CFG_JAM            61
#define CFG_READMAP        62
#define CFG_WRITEMAP       63
#define CFG_CHARSETALIAS   64
#define CFG_AREADESC       65
#define CFG_GROUPSETTINGS  66

static struct colorverb colortable[] =
{
    {"Black", 0x00},
    {"Blue", 0x01},
    {"Green", 0x02},
    {"Cyan", 0x03},
    {"Red", 0x04},
    {"Magenta", 0x05},
    {"Brown", 0x06},
    {"LGrey", 0x07},
    {"DGrey", 0x08},
    {"LBlue", 0x09},
    {"LGreen", 0x0A},
    {"LCyan", 0x0B},
    {"LRed", 0x0C},
    {"LMagenta", 0x0D},
    {"Yellow", 0x0E},
    {"White", 0x0F},
    {"Intense", 0x08},
    {"_Black", 0x00},
    {"_Blue", 0x10},
    {"_Green", 0x20},
    {"_Cyan", 0x30},
    {"_Red", 0x40},
    {"_Magenta", 0x50},
    {"_Brown", 0x60},
    {"_LGrey", 0x70},
    {"_DGrey", 0x80},
    {"_LBlue", 0x90},
    {"_LGreen", 0xA0},
    {"_LCyan", 0xB0},
    {"_LRed", 0xC0},
    {"_LMagenta", 0xD0},
    {"_Yellow", 0xE0},
    {"_White", 0xF0},
    {"_Blink", 0x80},
    {NULL, 0}
};

static char *cfgcolors[] =
{
    "MainNorm",
    "MainQuote",
    "MainKludge",
    "MainTempl",
    "MainInfo",
    "MainDivide",
    "MainHeader",
    "MainHdrTxt",
    "MainBlock",
    "MainEdit",
    "MainWarn",
    "MainNetInf",
    "MenuBorder",
    "MenuTitle",
    "MenuNorm",
    "MenuSelect",
    "HelpBorder",
    "HelpTitle",
    "HelpNorm",
    "HelpHilight",
    "InfoBorder",
    "InfoNorm",
    "InputBorder",
    "InputNorm",
    "InputEdit",
    "DlgBorder",
    "DlgNorm",
    "DlgChNorm",
    "DlgChSel",
    "DlgEnNorm",
    "DlgEnSel",
    "DlgButSel",
    "DlgButNorm",
    "DlgButShadow",
    "ListBdr",
    "ListTitle",
    "ListNorm",
    "ListInfo",
    "ListSelect",
    NULL
};

static char *cfgswitches[] =
{
    "HardQuote",
    "SaveCC",
    "RawCC",
    "SEEN-BYs",
    "ShowNotes",
    "Confirm",
    "ShowAddr",
    "MSGIDs",
    "OpusDate",
    "DateArvd",
    "ChopQuote",
    "QQuotes",
    "UseLastr",
    "ShowCR",
    "ShowEOL",
    "RealMsgN",
    "UseMouse",
    "EditCROnly",
    "UsePID",
    "SOTEOT",
    "ShowTime",
    "ImportFN",
    "DMore",
    "StatBar",
    "ShowSystem",
    "ExtFormat",
    "AreaListExactMatch",
    "EchoFlags",
    "NetmailVia",
    "DomainOrigin",
    "RightNextUnreadArea",
    "ShowOrigins",
    "ShowTearLines",
    "TearLines",
    "OriginLines",
    "ShowSeenBys",
    "EditTearLines",
    "EditOriginLines",
    "Colors",
    "Shadows",
    "BS127",
    "LowerCase",
    "AdaptiveCase",
    "ReceiveAllNames",
    "ReceiveAllAddresses",
    "SquishLock",
    "Carthy",
    "DirectList",
    "PseudoGraphics",
    "GroupSeparators",
    "UseTosserGroups",
    "DomainMsgid",
    "TZUTC",
    "xxltearline",
    NULL
};

#define CFG_SW_HARDQUOTE            0
#define CFG_SW_SAVECC               1
#define CFG_SW_RAWCC                2
#define CFG_SW_SEENBYS              3
#define CFG_SW_SHOWNOTES            4
#define CFG_SW_CONFIRM              5
#define CFG_SW_SHOWADDR             6
#define CFG_SW_MSGIDS               7
#define CFG_SW_OPUSDATE             8
#define CFG_SW_DATEARVD             9
#define CFG_SW_CHOPQUOTE            10
#define CFG_SW_QQUOTES              11
#define CFG_SW_USELASTR             12
#define CFG_SW_SHOWCR               13
#define CFG_SW_SHOWEOL              14
#define CFG_SW_REALMSGN             15
#define CFG_SW_USEMOUSE             16
#define CFG_SW_EDITCRONLY           17
#define CFG_SW_USEPID               18
#define CFG_SW_SOTEOT               19
#define CFG_SW_SHOWTIME             20
#define CFG_SW_IMPORTFN             21
#define CFG_SW_DMORE                22
#define CFG_SW_STATBAR              23
#define CFG_SW_SHOWSYSTEM           24
#define CFG_SW_EXTFORMAT            25
#define CFG_SW_AREALISTEXACTMATCH   26
#define CFG_SW_ECHOFLAGS            27
#define CFG_SW_NETMAILVIA           28
#define CFG_SW_DOMAINORIGIN         29
#define CFG_SW_RIGHTNEXTUNREADAREA  30
#define CFG_SW_SHOWORIGINS          31
#define CFG_SW_SHOWTEARLINES        32
#define CFG_SW_TEARLINES            33
#define CFG_SW_ORIGINLINES          34
#define CFG_SW_SHOWSEENBYS          35
#define CFG_SW_EDITTEARLINES        36
#define CFG_SW_EDITORIGINLINES      37
#define CFG_SW_COLORS               38
#define CFG_SW_SHADOWS              39
#define CFG_SW_BS127                40
#define CFG_SW_LOWERCASE            41
#define CFG_SW_ADAPTIVECASE         42
#define CFG_SW_RECEIVEALLNAMES      43
#define CFG_SW_RECEIVEALLADDR       44
#define CFG_SW_SQUISH_LOCK          45
#define CFG_SW_CARTHY               46
#define CFG_SW_DIRECTLIST           47
#define CFG_SW_PSEUDOGRAPHICS       48
#define CFG_SW_GROUPSEPARATORS      49
#define CFG_SW_USETOSSERGROUPS      50
#define CFG_SW_DOMAINMSGID          51
#define CFG_SW_TZUTC                52
#define CFG_SW_XXLTEARLINE          53

#ifdef UNIX
#include <sys/types.h>
#include <pwd.h>
#include <unistd.h>
#endif

/*
 * Function: shell_expand
 *
 * This function expands pathnames that begin with ~ according to the
 * UNIX shell expansion rules: A ~ followed by a slash / is the home
 * directory (which will be fetched from the HOME environment
 * variable, which even works on OS/2 or NT), a ~ followed by
 * something different is the home directory of the user named
 * "something different", eg ~tobi/ will expand to the home directory
 * of the user "tobi". The latter only works on Unix.
 *
 */

#ifndef USE_FIDOCONFIG
char *shell_expand(char *str)
{
    char *slash = NULL, *ret = NULL, c;
#ifdef UNIX
    struct passwd *pw = NULL;
#endif
    char *pfix = NULL;

    if (str == NULL)
    {
        return str;
    }
    if (*str == '\0' || str[0] != '~')
    {
        return str;
    }
    for (slash = str; *slash != '/' && *slash != '\0'
#ifndef UNIX
                     && *slash != '\\'
#endif
         ; slash++);
    c = *slash;
    *slash = 0;

    if (str[1] == '\0')
    {
        pfix = getenv("HOME");
#ifdef UNIX
        if (pfix == NULL)
        {
            pw = getpwuid(getuid());
            if (pw != NULL)
            {
                pfix = pw->pw_dir;
            }
        }
#endif
    }
#ifdef UNIX
    else
    {
        pw = getpwnam(str + 1);
        if (pw != NULL)
        {
            pfix = pw->pw_dir;
        }
    }
#endif
    *slash = c;

    if (pfix == NULL)  /* could not find an expansion */
    {
        return str;
    }

    ret = xmalloc(strlen(slash) + strlen(pfix) + 1);
    strcpy(ret, pfix);
    strcat(ret, slash);
    xfree(str);
    return ret;
}
#endif

/*
 * Function: pathcvt
 *
 * This function should be applied to all filenames that are read from
 * a configuration file (but not those entered as command line
 * arguments or interactively). It handles network drive or mount
 * point filenme translation.
 *
 */

char *pathcvt(char *path)
{
    int i;
#ifdef UNIX
    int dospathlen, unixpathlen;
#endif
    path = shell_expand(path);
#ifdef UNIX
    if (mntdirdos != NULL)
    {
        dospathlen = strlen(mntdirdos);
    }
    else
    {
        dospathlen = 0;
    }
    if (mntdirunix != NULL)
    {
        unixpathlen = strlen(mntdirunix);
    }
    else
    {
        unixpathlen = 0;
    }
    if (mntdirdos != NULL && strncmp(path, mntdirdos, dospathlen) == 0)
    {
        char *temppath;
        temppath = xmalloc(unixpathlen + strlen(path) - dospathlen + 1);
        strcpy(temppath, mntdirunix);
        strcpy(temppath + unixpathlen, path + dospathlen);
        release(path);
        path = temppath;
    }
#endif
    for (i = strlen(path) - 1; i >= 0; i--)
    {
#ifdef UNIX
        if (path[i] == '\\')
        {
            path[i] = '/';
        }
#endif
        if (SW->lowercase)
        {
            path[i] = tolower(path[i]);
        }
    }
    if (SW->adaptivecase)
    {
        adaptcase(path);
    }

    return path;
}

static char wildcmp(char *s1, char *s2)
{
    while (*s1 != '\0' && *s2 != '\0')
    {
        if (tolower(*s1) == tolower(*s2) || *s1 == '?')
        {
            s1++;
            s2++;
        }
        else if (*s1 == '*')
        {
            while (*s1 && (*s1 == '*' || *s1 == '?'))
            {
                if (*s1 == '?')
                {
                    s2++;
                }
                s1++;
            }
            if (*s1 != '\0')
            {
                char *p;
                p = s1;
                while (*p != '\0' && *p != '*' && *p != '?')
                {
                    p++;
                }
                while (*s2 != '\0' && strncmpi(s2, s1, (size_t) (p - s1)))
                {
                    s2++;
                }
                if (*s2 != '\0')
                {
                    s2 += (size_t) (p - s1);
                    s1 = p;
                }
                else
                {
                    return 1;
                }
            }
            else
            {
                while (*s2 != '\0')
                {
                    s2++;
                }
            }
        }
        else
        {
            return 1;
        }
    }
    if (*s2 == '\0')
    {
        return 0;
    }
    else
    {
        return 1;
    }
}


/* Strips leading whitespaces from a string. */

char *striplwhite(char *str)
{
    while (*str && isspace((unsigned char)*str))
    {
        str++;
    }
    return str;
}


/* Strips trailing spaces from a string. */
#ifndef USE_FIDOCONFIG
char *striptwhite(char *str)
{
    char *p;

    if (str == NULL)
    {
        return str;
    }
    if (*str == 0)
    {
        return str;   /* strend is undefined for zero-length string! */
    }
    p = strend(str);
    while (p > str && *p && isspace((unsigned char)*p))
    {
        *p = '\0';
        p--;
    }
    return str;
}
#endif

/* Strips quotation marks ("Gaensefuesschen") from a string */

char *strip_geese_feet(char *str)
{
    char *p;

    if (str == NULL)
    {
        return str;
    }
    if (*str == 0)
    {
        return str;
    }
    p = str;
    while (*p && (isspace((unsigned char)*p) || *p == '\"') )
    {
       p++;
    }
    memmove(str, p, strlen(p) + 1);
    if (*str == 0)
    {
        return str;   /* strend is undefined for zero-length string! */
    }
    p = strend(str);
    while (p > str && *p && (isspace((unsigned char)*p) || *p == '\"'))
    {
        *p = '\0';
        p--;
    }
    return str;
}


/* Skips from current position to first blank found. */

char *skip_to_blank(char *str)
{
    if (str == NULL)
    {
        return str;
    }
    while (*str && !isspace((unsigned char)*str))
    {
        str++;
    }
    return str;
}


/* Kills a trailing slash from a path. */

void kill_trail_slash(char *str)
{
    char *p;

    if (str == NULL)
    {
        return;
    }
    if (*str == 0)
    {
        return;
    }
    p = strend(str);
    if (*p == '\\' || *p == '/')
    {
        *p = '\0';
    }
}


/*
 *  Returns the number of the passed verb in relation to the cfgverbs
 *  array, or -1 if the passed verb is not in the array.
 */

int GetVerbNum(char *verb)
{
    int i = 0;

    while (cfgverbs[i] != NULL)
    {
        if (stricmp(verb, cfgverbs[i]) == 0)
        {
            return i;
        }
        i++;
    }
    return -1;
}


/* Assigns the passed named area to the global colour array. */

void AssignColor(char *name, int color)
{
    int i = 0;

    while (cfgcolors[i] != NULL)
    {
        if (stricmp(name, cfgcolors[i]) == 0)
        {
            cm[i] = color;
            return;
        }
        i++;
    }
    printf("\r\aUnknown colour argument: '%s'\n", name);
}


/* Assigns the passed switch name to on or off. */

void AssignSwitch(char *swtch, int OnOff)
{
    int i = 0;

    while (cfgswitches[i] != NULL)
    {
        if (stricmp(swtch, cfgswitches[i]) == 0)
        {
            break;
        }
        i++;
    }

    if (cfgswitches[i] == NULL)
    {
        printf("\r\aUnknown switch: '%s'\n", swtch);
        return;
    }

    switch (i)
    {
    case CFG_SW_HARDQUOTE:
        SW->hardquote = OnOff;
        break;

    case CFG_SW_SAVECC:
        SW->savecc = OnOff;
        break;

    case CFG_SW_RAWCC:
        SW->rawcc = OnOff;
        break;

    case CFG_SW_SEENBYS:
    case CFG_SW_SHOWSEENBYS:
        SW->showseenbys = OnOff;
        break;

    case CFG_SW_SHOWORIGINS:
        SW->showorigins = OnOff;
        break;

    case CFG_SW_SHOWTEARLINES:
        SW->showtearlines = OnOff;
        break;

    case CFG_SW_SHOWNOTES:
        SW->shownotes = OnOff;
        break;

    case CFG_SW_CONFIRM:
        SW->confirmations = OnOff;
        break;

    case CFG_SW_SHOWADDR:
        SW->showaddr = OnOff;
        break;

    case CFG_SW_MSGIDS:
        SW->msgids = OnOff;
        break;

    case CFG_SW_OPUSDATE:
        SW->opusdate = OnOff;
        break;

    case CFG_SW_DATEARVD:
        SW->datearrived = OnOff;
        break;

    case CFG_SW_CHOPQUOTE:
        SW->chopquote = OnOff;
        break;

    case CFG_SW_QQUOTES:
        SW->qquote = OnOff;
        break;

    case CFG_SW_USELASTR:
        SW->use_lastr = OnOff;
        break;

    case CFG_SW_SHOWCR:
        SW->showcr = OnOff;
        break;

    case CFG_SW_SHOWEOL:
        SW->showeol = OnOff;
        break;

    case CFG_SW_REALMSGN:
        SW->showrealmsgn = OnOff;
        break;

    case CFG_SW_USEMOUSE:
        SW->usemouse = OnOff;
        break;

    case CFG_SW_EDITCRONLY:
        SW->editcronly = OnOff;
        break;

    case CFG_SW_USEPID:
        SW->usepid = OnOff;
        break;

    case CFG_SW_SOTEOT:
        SW->soteot = OnOff;
        break;

    case CFG_SW_SHOWTIME:
        SW->showtime = OnOff;
        break;

    case CFG_SW_IMPORTFN:
        SW->importfn = OnOff;
        break;

    case CFG_SW_DMORE:
        SW->dmore = OnOff;
        break;

    case CFG_SW_STATBAR:
        SW->statbar = OnOff;
        break;

    case CFG_SW_SHOWSYSTEM:
        SW->showsystem = OnOff;
        break;

    case CFG_SW_EXTFORMAT:
        SW->extformat = OnOff;
        break;

    case CFG_SW_AREALISTEXACTMATCH:
        SW->arealistexactmatch = OnOff;
        break;

    case CFG_SW_ECHOFLAGS:
        SW->echoflags = OnOff;
        break;

    case CFG_SW_NETMAILVIA:
        SW->netmailvia = OnOff;
        break;

    case CFG_SW_DOMAINORIGIN:
        SW->domainorigin = OnOff;
        break;

    case CFG_SW_RIGHTNEXTUNREADAREA:
        SW->rightnextunreadarea = OnOff;
        break;

    case CFG_SW_TEARLINES:
        SW->usetearlines = OnOff;
        break;

    case CFG_SW_ORIGINLINES:
        SW->useoriginlines = OnOff;
        break;

    case CFG_SW_EDITTEARLINES:
        SW->edittearlines = OnOff;
        break;

    case CFG_SW_EDITORIGINLINES:
        SW->editoriginlines = OnOff;
        break;

    case CFG_SW_COLORS:
        wnd_force_monochrome = !(OnOff);
        break;

    case CFG_SW_SHADOWS:
        wnd_suppress_shadows = !(OnOff);
        break;

    case CFG_SW_BS127:
        wnd_bs_127 = OnOff;
        break;

    case CFG_SW_LOWERCASE:
        SW->lowercase = OnOff;
        break;

    case CFG_SW_ADAPTIVECASE:
        SW->adaptivecase = OnOff;
        break;

    case CFG_SW_RECEIVEALLNAMES:
    	SW->receiveallnames = OnOff;
        break;

    case CFG_SW_RECEIVEALLADDR:
        SW->receivealladdr = OnOff;
        break;

    case CFG_SW_SQUISH_LOCK:
        SW->squish_lock = OnOff;
        break;

    case CFG_SW_CARTHY:
        SW->carthy = OnOff;
        break;

    case CFG_SW_DIRECTLIST:
        SW->direct_list = OnOff;
        break;

    case CFG_SW_PSEUDOGRAPHICS:
        TTconfigure("pseudographics", OnOff?"1":"0");
        break;

    case CFG_SW_GROUPSEPARATORS:
        SW->groupseparators = OnOff;
        break;

    case CFG_SW_USETOSSERGROUPS:
        SW->areafilegroups = OnOff;
	break;

    case CFG_SW_DOMAINMSGID:
	SW->domainmsgid = OnOff;
	break;

    case CFG_SW_TZUTC:
	SW->tzutc = OnOff;
	break;

    case CFG_SW_XXLTEARLINE:
        SW->xxltearline = OnOff;
        break;

    default:
        printf("\r\aUnknown switch: '%s'\n", swtch);
        break;
    }
}


/*
 *  Searches through the colortable for the matching colorname and
 *  returns the color, or -1 if not found.
 */

int GetColor(char *color)
{
    int i = 0;

    while (colortable[i].name != NULL)
    {
        if (stricmp(color, colortable[i].name) == 0)
            return colortable[i].color;
        i++;
    }
    printf("\r\aUnknown color: '%s'\n", color);
    return -1;
}


/*
 *  Determines if the passed char is a token seperator.
 */

int toksep(char ch)
{
    return strchr(" ,\r\t\n", ch) != NULL ? TRUE : FALSE;
}


/*
 *  Gets num tokens from a string, seperated by a default set of tokens.
 *  Handles stuff bewtween quotes as one token.
 */

void parse_tokens(char *str, char *tokens[], int num)
{
    int i = 0;
    char *s = str;
    int done = 0;

    if (str == NULL)
    {
        tokens[0] = NULL;
        return;
    }

    while (!done && i < num)
    {
        if (toksep(*s))
        {
            while (*s && toksep(*s))
            {
                s++;
            }
        }

        if (*s == ';')
        {
            tokens[i] = NULL;
            break;
        }

        if (*s == '\"')
        {
            tokens[i++] = ++s;

            while (*s && *s != '\"')
            {
                s++;
            }
            if (*s == '\"')
            {
                *s++ = '\0';
            }
        }
        else
        {
            tokens[i++] = s;
            while (*s && !toksep(*s))
            {
                s++;
            }
        }
        if (*s == '\0')
        {
            tokens[i] = NULL;
            done = TRUE;
        }
        else
        {
            *s++ = '\0';
        }
    }
    tokens[i] = NULL;
}

/*
 *  Opens cfn first, and returns if successful, else it tries env.
 *  env can be considered the default name.
 */

static FILE *fileopen(char *env, char *cfn)
{
    FILE *fp;
    char *fenv = NULL;
    char *fcfn = NULL;

    if (env != NULL)
    {
        fenv = shell_expand(xstrdup(env));
    }

    if (cfn != NULL)
    {
        fcfn = shell_expand(xstrdup(cfn));
    }

    if (fcfn != NULL)
    {
        fp = fopen(fcfn, "r");
        if (fp != NULL)
        {
            release(fcfn);
            release(fenv);
            return fp;
        }
    }
    fp = fopen(fenv, "r");
    release(fcfn);
    release(fenv);
    return fp;
}

/*
 *  Parses a macro.
 */

static unsigned int *parse_macro(char *macro)
{
    unsigned int *m, *t;
    int l;
    char tmp[5];

    t = xcalloc(strlen(macro) * 2, sizeof *t);
    m = t;

    if (t == NULL || macro == NULL)
    {
        return NULL;
    }

    while (*macro)
    {
        if (*macro == '^')
        {
            *t++ = (unsigned int)(*(macro + 1) == '^') ? '^' : (toupper(*(macro + 1)) - 64);
            macro += 2;
        }
        else if (*macro == '\\')
        {
            if (*(macro + 1) == '\\')
            {
                *t++ = (unsigned int)'\\';
                macro += 2;
            }
            else
            {
                memset(tmp, 0, sizeof tmp);
                strncpy(tmp, macro + 1, 4);
                *t++ = (unsigned int)strtol(tmp, NULL, 0) << 8;
                macro += 5;
            }
        }
        else
        {
            *t++ = (unsigned int)*macro++;
        }
    }

    *t = 0;
    l = (int)(t - m) + 1;

    t = xrealloc(m, l * sizeof(int));

    if (t != NULL)
    {
        return t;
    }

    return m;
}

static void SkipArea(char * a)
{
    int i;
    if (tags2skip == NULL)
    {
         tags2skip = xmalloc(sizeof *tags2skip);
         tags2skip[0] = NULL;
    }
    i = 0;
    while (tags2skip[i] != NULL && stricmp(tags2skip[i], a) != 0)
    {
        i++;
    }
    if (tags2skip[i] == NULL)
    {
        tags2skip = xrealloc(tags2skip, sizeof *tags2skip * (i + 2));
        tags2skip[i] = xstrdup(a);
        tags2skip[i + 1] = NULL;
    }
}

/*
 *  Sets the username and the template for an area.
 *  Adds areas to their respective groups based on the search pattern.
 */

static void SetAreaGroupInfo(AREA * a)
{
    int i;

    for (i = 0; i < num_groups; i++)
    {
        if (patmat(a->tag, group[i].pattern))
        {
            a->group=group[i].handle;
            break;
        }
    }


    if (a->group);
    {
        a->username = group_getusername(a->group);
        a->template = group_getusername(a->group);


        if ((a->username >= MAXUSERS) ||
            (a->username < 0) ||
            (user_list[a->username].name == NULL))
        {
            a->username = 0;
        }

        if ((a->template < 0) ||
            (a->template >= SW->numtemplates))
        {
            a->template = 0;
        }

        if (a->username != 0)
        {
            SW->areadefinesuser = 1;
        }

           /* Currently, the user cannot set this switch. Our logic is this: if
              there is at least one group setting which actually gets applied,
              then the user probably wants that the username is selected based
              on the area that he enters. If, however, there is no such group
              statement, then entering an area does not change the user
              name. Handy for pseudo multiuser installations. */
    }
}

/*
 *  Adds a new area to the area-list.
 */

void AddArea(AREA * a)
{
    int i, g, l;
    char *d, *t, *p, *converted_path;

    if (tags2skip != NULL)
    {
        i = 0;
        while (tags2skip[i] != NULL && wildcmp(tags2skip[i], a->tag) != 0)
        {
            i++;
        }
        if (tags2skip[i] != NULL)
        {
            return;
        }
    }

    if (a->msgtype != QUICK)
    {
        converted_path = xmalloc((l = strlen(a->path)) + 5);
        memcpy(converted_path, a->path, l + 1);
        kill_trail_slash(converted_path);
        if (a->msgtype == SQUISH)
        {
            /* we add the .sqd temporarily so that the case
               adaption on Unix works correctly, because it can
               only work if an existing file is referenced */
            strcat(converted_path, ".sqd");
        }
        converted_path = pathcvt(converted_path);
        if (a->msgtype == SQUISH && (l=strlen(converted_path)) > 4)
        {
            converted_path[l - 4] = '\0';
        }
    }
    else
    {
        converted_path = NULL;
    }


    if (a->msgtype == QUICK && ST->quickbbs == NULL)
    {
        printf("\r\aFor QuickBBS areas, set QuickBBS path!\n");
        return;
    }

    for (i = 0; i < SW->areas; i++)
    {
        if (((a->msgtype == QUICK) && (arealist[i].msgtype == QUICK) &&
             (a->board ==arealist[i].board)) ||
            ((a->msgtype != QUICK) && (arealist[i].msgtype != QUICK) &&
             (arealist[i].path != NULL) && (converted_path != NULL) &&
             (stricmp(arealist[i].path, converted_path) == 0)))
        {
            break;
        }
    }

    if (i == SW->areas)
    {
        /* It's a new area, so we add it to the end of the list. */

        SW->areas++;
        SW->area = SW->areas - 1;
        if (SW->areas > SW->maxareas)
        {
            SW->maxareas += 50;
                /* Reallocating in greater blocks dramatically reduces */
                /* memory requirements of the DOS version. At least    */
                /* Borlands realloc does not seem to be very "intelligent".. */

            arealist = xrealloc(arealist, SW->maxareas * sizeof(struct _area));
        }

        memset(&CurArea, 0, sizeof(struct _area));

        CurArea = *a;

        if (a->addr.domain)
        {
            CurArea.addr.domain = xstrdup(a->addr.domain);
        }

        CurArea.description = xstrdup(a->description);
        CurArea.tag = xstrdup(a->tag);
        CurArea.path = converted_path;
    }
    else
    {
        /*
         *  We redefine the area to the new defaults, with the
         *  exception of the path, tag, desc and group. */

        release(converted_path);
        release(arealist[i].addr.domain);
        p = arealist[i].path;
        t = arealist[i].tag;
        d = arealist[i].description;
        g = arealist[i].group;

        arealist[i] = *a;
        arealist[i].path = p;
        arealist[i].tag = t;
        arealist[i].description = d;
        arealist[i].group = g;

        if (a->addr.domain)
        {
            arealist[i].addr.domain = xstrdup(a->addr.domain);
        }
    }

    release(a->tag);
    release(a->description);
    release(a->path);
    release(a->addr.domain);
}

/*
 *  Checks a file in the areas.bbs format and gets all the area
 *  definitions. First attempts to open the file specified on the
 *  command line, and if that fails, tries for the default name
 *  "areas.bbs".
 */

static void checkareas(char *areafile)
{
    static AREA a;                /* current area */
    static char *buffer=NULL;     /* line buffer */
    FILE *fp;                     /* file handle */
    char *s;                      /* pointer */
    int flag = 1;                 /* found host name? */
    char *tokens[12];

#ifdef USE_MSGAPI
    int sq = 0;                   /* a squish base? */
#endif

    if (buffer == NULL) buffer = xmalloc(TEXTLEN);

    fp = fileopen("areas.bbs", areafile);
    if (fp == NULL)
    {
        return;
    }

    while (fgets(buffer, TEXTLEN, fp) != NULL)
    {
        if (*buffer == '\r' || *buffer == '\n')
        {
            continue;
        }

        s = striplwhite(buffer);

        if (*s == ';' || *s == '-' || *s == '#')
        {
            continue;
        }

        if (strchr(buffer, '!') && flag)
        {
            char *p;
            p = strrchr(buffer, '!');
            if (p != NULL)
            {
                *p = '\0';
                release(ST->origin);
                ST->origin = xstrdup(buffer);
            }
            flag = 0;
            continue;
        }

#ifdef USE_MSGAPI
        if (*s == '$')
        {
            sq = 1;
            s++;
        }
        else if (*s == '!')
        {
#if MSGAPI_VERSION < 2
            continue;
#else
            sq = 2;
            s++;
#endif
        }
        else
        {
            sq = 0;
        }
#endif

        memset(tokens, 0, sizeof tokens);
        parse_tokens(s, tokens, 4);

        if (tokens[0] == NULL || tokens[1] == NULL)
        {
            continue;
        }

        memset(&a, 0, sizeof a);

        a.echomail = 1;
        a.msgtype = FIDO;

        sscanf(tokens[0], " %d ", &a.board);
        if (a.board)
        {
            a.msgtype = QUICK;
        }
        else
        {
            a.path = xstrdup(tokens[0]);
            kill_trail_slash(a.path);
        }

        a.tag = xstrdup(tokens[1]);
        a.description = xstrdup(tokens[1]);
        a.addr.notfound = 1;

        if (tokens[2] != NULL && strlen(tokens[2]) >= 7)
        {
            s = tokens[2];
            if (*s == '-' && toupper(*(s + 1)) == 'P')
            {
                a.addr = parsenode(s + 2);
            }
        }

        if (a.addr.notfound)
        {
            a.addr = thisnode;
            if (thisnode.domain)
            {
                a.addr.domain = xstrdup(thisnode.domain);
            }
        }

        strupr(a.tag);

#ifdef USE_MSGAPI
        switch  (sq)
        {
        case 1:
            a.msgtype = SQUISH;
            break;
#if MSGAPI_VERSION >= 2
        case 2:
            a.msgtype = JAM;
            break;
#endif
        }
#endif

        applyflags(&a, areafileflags);
        AddArea(&a);
    }

    fclose(fp);
}

/* Recode the area tags / descriptions. This is needed when reading a
   fastecho.cfg or gecho.cfg config file on a Unix machine. */

static void recode_area_descriptions(void)
{
    LOOKUPTABLE *ltable;        /* for description charset conversion */
    int i;
    char *tempdsc = NULL;

    ltable = get_readtable("IBMPC", 2);
                                /* we assume that umlauts in the
                                   fastecho.cfg area descriptions are
                                   always in the IBMPC charset. */

    for (i = 0; i < SW->areas; i++)
    {
        if (arealist[i].recodedsc)
        {
            tempdsc = translate_text(arealist[i].description, ltable);
            strip_control_chars(tempdsc);
            release(arealist[i].description);
            arealist[i].description = tempdsc;
        }
    }
}

/* check_fastecho - reads the areas in a FastEcho 1.45 configuration file */

static void check_fastecho(char *areafile)
{
    static AREA a;              /* current area */
    FILE *fp;                   /* file handle */

#ifdef USE_MSGAPI
    int sq = 0;                 /* a squish base? */
#endif
    CONFIG feconfig;
    Area fearea;
    ExtensionHeader feexthdr;
    SysAddress feakas[MAX_AKAS];
    int i, cnt, found;
    unsigned char atype, storage;
    static char progress_indicators[4] =
    {'-', '\\', '|', '/'};
    dword curofs;
    GroupNames *fegroups = NULL;
    int *fegrouphandles = NULL;

    if (alias == NULL)
    {
        printf("\r\aError! Primary address must be defined.\n");
        exit(-1);
    }

    if (user_list[0].name == NULL)
    {
        printf("\r\aError! Name must be defined.\n");
        exit(-1);
    }

    fp = fopen(areafile, "rb");
    if (fp == NULL)
    {
        return;
    }

    ST->fecfgpath = xstrdup(areafile);

    if (read_fe_config(&feconfig, fp) == -1)
    {
        return;
    }

    /* preload the AKAs and groups*/

    fseek(fp, FE_CONFIG_SIZE, SEEK_SET);
    curofs = (dword) FE_CONFIG_SIZE; found=0;

    if (feconfig.GroupCnt)
    {
        fegroups = xmalloc(FE_GROUPNAMES_LEN * feconfig.GroupCnt);
        fegrouphandles = xmalloc(sizeof(int) * feconfig.GroupCnt);
        memset(fegrouphandles, 0, sizeof(int) * feconfig.GroupCnt);
    }

    while (curofs < FE_CONFIG_SIZE + feconfig.offset)
    {
        if (read_fe_extension_header(&feexthdr, fp) == -1)
        {
            return;
        }
        curofs += sizeof feexthdr;
        if (feexthdr.type == EH_AKAS)
        {
            cnt = 0; found = 1;
            do
            {
                if (read_fe_sysaddress(feakas + cnt, fp) == -1)
                {
                    return;
                }
                curofs += FE_SYS_ADDRESS_SIZE; cnt++;
            }
            while (cnt < feconfig.AkaCnt);
        }
        else if ((feexthdr.type == EH_GROUPS) && (fegroups != NULL))
        {
            cnt = 0;
            do
            {
                if (fread(fegroups + cnt, FE_GROUPNAMES_LEN, 1, fp) != 1)
                {
                    xfree(fegroups); xfree(fegrouphandles);
                    return;
                }
                curofs += FE_GROUPNAMES_LEN; cnt++;
            }
            while (cnt < feconfig.GroupCnt);
        }
        else
        {
            fseek(fp, feexthdr.offset, SEEK_CUR);
            curofs += feexthdr.offset;
        }
    }
    if (!found)  /* no EH_AKAS header !? */
    {
        xfree(fegroups); xfree(fegrouphandles);
        return;
    }
    for (i = 0; i < feconfig.GroupCnt; i++)
    {
        if (!((fegroups + i)->name)[0])
        {
            sprintf ((fegroups + i)->name, "Fastecho Group %c", i + 'a');
        }
    }


    /* set up the fastecho primary netmail path */
    memset(&a, 0, sizeof a);
    a.msgtype = FIDO;
    a.group = (SW->areafilegroups) ?
        group_gethandle("Primary Netmail Folders", 1) : 0;
    a.echomail = 0;
    a.netmail = 1; a.priv = 1;
    a.addr.notfound = 0;
    a.addr.fidonet = 1;
    a.addr.zone = feakas[0].main.zone;
    a.addr.net = feakas[0].main.net;
    a.addr.node = feakas[0].main.node;
    a.addr.point = feakas[0].main.point;
    if (feakas[0].domain)
    {
        if (*feakas[0].domain)
        {
            a.addr.domain = xstrdup(feakas[0].domain);
        }
    }
    a.path = env_expand(feconfig.NetMPath);
    a.tag = xstrdup("NETMAIL");
    a.description = makeareadesc(a.tag, "FastEcho Primary Netmail Folder");
    kill_trail_slash(a.path);
    strlwr(a.path);
    applyflags (&a, areafileflags);
    AddArea(&a);


    /* Scan for echomail and secondary netmail areas */

    fseek(fp, (dword)FE_CONFIG_SIZE + feconfig.offset + feconfig.NodeCnt *
      feconfig.NodeRecSize, SEEK_SET);

    for (i = 0; i < feconfig.AreaCnt; i++)
    {
        /* printing an indicator after EVERY area slows down the system
           unnecessary when running in an OS/2 or Windows windowed session,
           now that reading a single area is done in almost zero time */
        if (i%50)
        {
            printf("%c\b", progress_indicators[(i / 50) % 4]);
            fflush(stdout);
        }

        if(read_fe_area(&fearea, fp) == -1)
        {
            xfree(fegroups); xfree(fegrouphandles);
            return;
        }

        cnt = 0;
        found = 0;

        /* AKA index is  (fearea.info.akagroup & 0xff) */

        /* set things up */

        memset(&a, 0, sizeof a);
        a.msgtype = FIDO;

        /* We do not yet have Msged grouphandles for the Fastecho groups. This
           is because we only want to register those Fastecho groups that
           actually contain at least one area.

           Now, we mark the group of this area as used: */

        fegrouphandles[fearea.info.group] = 1;

        /* ... and set an invalid (negative) group handle. Later, we will
           register all Fastecho groups that have been marked as used, and then
           iterate the area array to set the correct group handle for all areas
           that have a negative group handle. */

        if (SW->areafilegroups)
            a.group = -(fearea.info.group + 1);
        else
            a.group = 0;

        a.echomail = 1;
        a.netmail = 0;
        a.addr.notfound = 0;
        a.addr.fidonet = 1;
        a.addr.zone = feakas[fearea.info.aka].main.zone;
        a.addr.net = feakas[fearea.info.aka].main.net;
        a.addr.node = feakas[fearea.info.aka].main.node;
        a.addr.point = feakas[fearea.info.aka].main.point;
        if (feakas[fearea.info.aka].domain)
        {
            if (*feakas[fearea.info.aka].domain)
            {
                a.addr.domain =
                  xstrdup(feakas[fearea.info.aka].domain);
            }
        }

        /* storage type: QBBS, etc... */
        storage = fearea.flags.storage;
        atype = fearea.flags.atype;

        if (atype == AREA_NETMAIL)
        {
            a.echomail = 0;
            a.netmail = 1;
            a.priv = 1;
        }
        else
        if (atype == AREA_LOCAL)
        {
            a.echomail = 0;
            a.local = 1;
            a.priv = 0;
        }

#ifdef USE_MSGAPI
        if (storage == FE_SQUISH)
        {
            sq = 1;
        }
        else if (storage == FE_JAM)
        {
#if MSGAPI_VERSION < 2
            continue;
#else
            sq = 2;
#endif
        }
        else
        {
            sq = 0;
        }
#endif

        if (storage == FE_QBBS)
        {
            a.msgtype = QUICK;
        }
        else if (storage == FE_PASSTHRU)
        {
            continue;
        }

        if (a.msgtype == QUICK)
        {
            a.board = fearea.board;
        }
        else
        {
            a.path = env_expand(fearea.path);
        }

        a.tag = xstrdup(fearea.name);
        strupr(a.tag);

        a.recodedsc   = 1; /* fastecho.cfg always has IBMPC type characters */
        a.description = makeareadesc(fearea.name, fearea.desc);

        if (a.path)
        {
            kill_trail_slash(a.path);
        }

#ifdef USE_MSGAPI
        switch (sq)
        {
        case 1:
            a.msgtype = SQUISH;
            break;
#if MSGAPI_VERSION >= 2
        case 2:
            a.msgtype = JAM;
            break;
#endif
        }
#endif
        applyflags(&a, areafileflags);
        AddArea(&a);
    }

    fclose(fp);

    /* Now, we must fix the invalid group handles that we have set previously
    */

    if (SW->areafilegroups)
    {
        for (i = 0; i < feconfig.GroupCnt; i++)
        {
            if (fegrouphandles[i])
            {
                fegrouphandles[i] = group_gethandle(fegroups[i].name, 1);
            }
        }

        for (i = 0; i < SW->areas; i++)
        {
            if (arealist[i].group < 0)
            {
                arealist[i].group = fegrouphandles[(-arealist[i].group) - 1];
            }
        }
    }

    xfree(fegroups); xfree(fegrouphandles);
}

/* check_squish - reads the areas in a squish configuration file */

static void check_squish(char *areafile)
{
    static AREA a;
    static char progress_indicators[4] =
    {'-', '\\', '|', '/'};
    static char *raw_buffer = NULL;
    char *buffer = NULL;
    char *tokens[20];
    FILE *fp;
    int i, line_num = 0;
    char *s;

    if (raw_buffer == NULL) raw_buffer = xmalloc(TEXTLEN);

    if (alias == NULL)
    {
        printf("\r\aError! Primary address must be defined.\n");
        exit(-1);
    }

    if (user_list[0].name == NULL)
    {
        printf("\r\aError! Name must be defined.\n");
        exit(-1);
    }

    fp = fileopen("squish.cfg", areafile);
    if (fp == NULL)
    {
        return;
    }

    memset(raw_buffer, 0, TEXTLEN);

    while (fgets(raw_buffer, TEXTLEN, fp) != NULL)
    {
        line_num++;
        release(buffer);
        buffer = env_expand(raw_buffer);

        printf("%c\b", progress_indicators[line_num % 2]);
        fflush(stdout);

        if (*buffer == '\r' || *buffer == '\n')
        {
            continue;
        }

        s = striplwhite(buffer);

        if (*s == ';' || *s == '-' || *s == '#')
        {
            continue;
        }

        memset(tokens, 0, sizeof(tokens));
        parse_tokens(s, tokens, 15);

        if (tokens[0] == NULL)
        {
            continue;
        }

        if (!stricmp(tokens[0], "EchoArea") ||
          !stricmp(tokens[0], "LocaLarea") ||
          !stricmp(tokens[0], "NetArea") ||
          !stricmp(tokens[0], "BadArea") ||
          !stricmp(tokens[0], "DupeArea"))
        {
            if (tokens[1] == NULL || tokens[2] == NULL)
            {
                continue;
            }

            memset(&a, 0, sizeof a);

            if (!stricmp(tokens[0], "EchoArea"))
            {
                a.echomail = 1;
            }
            else if (!stricmp(tokens[0], "LocalArea"))
            {
                a.local = 1;
            }
            else
            {
                a.netmail = 1;
                a.priv = 1;
            }

            a.msgtype = FIDO;
            a.addr.notfound = 1;
            a.tag = xstrdup(tokens[1]);
            a.description = xstrdup(tokens[1]);
            a.path = xstrdup(tokens[2]);

            strupr(a.tag);

            i = 3;
            while ((s = tokens[i]) != NULL)
            {
                if (*s == ';')
                {
                    i = 0;
                    break;
                }
#ifdef USE_MSGAPI
                else if (*s == '-' && *(s + 1) && *(s + 1) == '$')
                {
                    a.msgtype = SQUISH;
                }
#endif
                else if (*s == '-' && *(s + 1) && toupper(*(s + 1)) == 'P')
                {
                    /* an address was specified */

                    release(a.addr.domain);
                    a.addr = parsenode(s + 2);
                }
                else if (*s == '-' && *(s + 1) && *(s + 1) == '0')
                {
                    /* ignore passthru area */

                    release(a.tag);
                    release(a.description);
                    release(a.path);
                    release(a.addr.domain);
                    i = 0;
                    break;
                }
                i++;
            }

            if (i == 0)
            {
                continue;
            }

            if (a.addr.notfound)
            {
                a.addr = thisnode;
                if (thisnode.domain)
                {
                    a.addr.domain = xstrdup(thisnode.domain);
                }
            }
            applyflags(&a, areafileflags);
            AddArea(&a);
        }
    }

    release(buffer);
    fclose(fp);
}

/* check_gecho - reads the areas in a GEcho configuration file.
   tested with GEcho 1.20/Pro (might work with other versions). */

static void check_gecho(char *areafile)
{
    static AREA a;              /* current area */
    char *fn;                   /* file name */
    FILE *fp;                   /* file handle */

    static char progress_indicators[4] =
    {'-', '\\', '|', '/'};

    SETUP_GE     Setup;
    AREAFILE_HDR AreaHdr;
    AREAFILE_GE  Area;
    word         arearecsize, i;

    if (alias == NULL)
    {
        printf("\r\aError! Primary address must be defined.\n");
        exit(-1);
    }

    if (user_list[0].name == NULL)
    {
        printf("\r\aError! Name must be defined.\n");
        exit(-1);
    }

    /* printf("Reading GEcho configuration... "); */


    /* read SETUP.GE (we need AKAs and netmail path) */

    kill_trail_slash(areafile);
    fn = xmalloc(strlen(areafile) + 10);
    sprintf(fn, "%s\\setup.ge", areafile);
    fn = pathcvt(fn);
    fp = fopen(fn, "rb");
    release(fn);
    if (fp == NULL)
    {
        return;
    }

    if (read_setup_ge(&Setup, fp) == -1)
    {
        /* cannot read setup */
        return;
    }

    fclose(fp);

    if (Setup.sysrev != GE_THISREV)
    {
        /* bad setup file version */
        return;
    }


    /* set up GEcho netmail folder (if it exists) */

    if (*Setup.mailpath)
    {
        memset(&a, 0, sizeof a);
        a.msgtype = FIDO;
        a.group = 0;
        a.netmail = 1;
        a.priv = 1;
        a.addr.fidonet = 1;
        a.addr.zone = Setup.aka[0].zone;
        a.addr.net = Setup.aka[0].net;
        a.addr.node = Setup.aka[0].node;
        a.addr.point = Setup.aka[0].point;
        a.tag = xstrdup("NETMAIL");
        a.description = makeareadesc(a.tag, "GEcho Netmail Folder");
        a.path = xstrdup(Setup.mailpath);
        kill_trail_slash(a.path);
        applyflags (&a, areafileflags);
        AddArea(&a);
    }


    /* read AREAFILE.GE (scan for echomail and secondary netmail areas) */

    fn = xmalloc(strlen(areafile) + 13);
    sprintf(fn, "%s\\areafile.ge", areafile);
    fn = pathcvt(fn);
    fp = fopen(fn, "rb");
    release(fn);
    if (fp == NULL)
    {
        return;
    }

    if (read_areafile_hdr(&AreaHdr, fp) == -1)
    {
        /* cannot read areafile header */
        return;
    }

    if ((AreaHdr.hdrsize < AREAFILE_HDR_SIZE) ||
        (AreaHdr.recsize < AREAFILE_GE_SIZE))
    {
        /* incompatible areafile format */
        return;
    }

    arearecsize = AreaHdr.recsize + (AreaHdr.maxconnections * CONNECTION_SIZE);

    for (i = 0; 1 == 1; i++) /* repeat until break */
    {
        /* print an indicator every sixteen areas */
        if ((i & 15) == 0)
        {
            printf("%c\b", progress_indicators[(i >> 4) & 3]);
            fflush(stdout);
        }

        fseek(fp, AreaHdr.hdrsize + arearecsize * i, SEEK_SET);

        if (read_areafile_ge(&Area, fp) == -1)
        {
            /* no more areas */
            break;
        }

        if ((Area.options & REMOVED) == 0)
        {
            memset(&a, 0, sizeof a);

            switch (Area.areaformat)
            {
            case FORMAT_SDM:    /* *.MSG */
                {
                    a.msgtype = FIDO;
                    break;
                }
            case FORMAT_HMB:    /* Hudson Message Base */
                {
                    a.msgtype = QUICK;
                    break;
                }
#ifdef USE_MSGAPI
            case FORMAT_SQUISH: /* Squish 2.0 */
                {
                    a.msgtype = SQUISH;
                    break;
                }
#if MSGAPI_VERSION >= 2
            case FORMAT_JAM:    /* Joaquim-Andrew-Mats message base proposal */
                {
                    a.msgtype = JAM;
                    break;
                }
#endif
#endif
            case FORMAT_PCB:    /* PCBoard 15.0 */
            case FORMAT_WC:     /* Wildcat! 4.0 */
            default:
                {
                    continue;   /* not supported */
                }
            }

            switch (Area.areatype)
            {
            case NETMAIL:
                {
                    a.netmail = 1;
                    a.priv = 1;
                    break;
                }
            case LOCAL:
                {
                    a.local = 1;
                    break;
                }
            case ECHOMAIL:
            case PERSONAL:
            case BADECHO:
            default:
                {
                    a.echomail = 1;
                    break;
                }
            }

            if (a.msgtype == QUICK)
            {
                a.board = Area.areanumber;
            }
            else
            {
                a.path = xstrdup(Area.path);
                kill_trail_slash(a.path);
            }

            a.addr.fidonet = 1;
            a.addr.zone = Setup.aka[Area.pkt_origin].zone;
            a.addr.net = Setup.aka[Area.pkt_origin].net;
            a.addr.node = Setup.aka[Area.pkt_origin].node;
            a.addr.point = Setup.aka[Area.pkt_origin].point;

            a.group = 0; /* Area.group; commented out until fixed by tobi */

            a.tag = xstrdup(Area.name);
            strupr(a.tag);

            a.recodedsc = 1; /* gecho.cfg always has IBMPC type characters */

            a.description = makeareadesc(Area.name, Area.comment);

            applyflags(&a, areafileflags);
            AddArea(&a);
        }
    }

    fclose(fp);

    /* printf("Done.\n"); */
}


/*
 *  Applies a string of user-given flags to an area definition
 */

void applyflags(AREA *a, char *flagstring)
{
    char *s = flagstring;

    if (s == NULL)
    {
        return;
    }

    while (*s)
    {
        switch (tolower(*s))  /* one letter is enough */
        {
        case 'n':
            a->news = 1;
            break;
        case 'u':
            a->uucp = 1;
            break;
        case 'm':
            a->netmail = 1;
            break;
        case 'e':
            a->echomail = 1;
            break;
        case 'p':
            a->priv = 1;
            break;
        case 'h':
            a->hold = 1;
            break;
        case 'd':
            a->direct = 1;
            break;
        case 'c':
            a->crash = 1;
            break;
        case 'k':
            a->killsent = 1;
            break;
        case 'l':
            a->local = 1;
            break;
        case '8':
            a->eightbits = 1;
            break;
        default:
            /* unknown */
            break;
        }
        s++;
    }

    if (!a->echomail && !a->netmail && !a->uucp && !a->news)
    {
        a->local = 1;
    }
}


/*
 *  Reads in an area definition in the msged.cfg format.
 */

static void parsemail(char *keyword, char *value)
{
    char *tokens[20];
    static AREA a;

    memset(&a, 0, sizeof a);
    memset(tokens, 0, sizeof tokens);

    if (alias == NULL)
    {
        printf("\r\aError! Primary address must be defined.\n");
        exit(-1);
    }

    if (user_list[0].name == NULL)
    {
        printf("\r\aError! Name must be defined.\n");
        exit(-1);
    }

    switch (tolower(*keyword))  /* one letter is enough for now */
    {
    default:
    case 'f':
        a.msgtype = FIDO;
        break;
    case 'q':
        a.msgtype = QUICK;
        break;
#ifdef USE_MSGAPI
    case 's':
        a.msgtype = SQUISH;
        break;
#if MSGAPI_VERSION >= 2
    case 'j':
        a.msgtype = JAM;
        break;
#endif
#endif
    }

    parse_tokens(value, tokens, 5);

    if (tokens[2] == NULL)
    {
        return;
    }

    applyflags(&a, tokens[0]);

    a.description = xstrdup(tokens[1]);
    switch (a.msgtype)
    {
    case QUICK:
        a.board = atoi(tokens[2]);
        break;
    default:
        a.path = xstrdup(tokens[2]);
        break;
    }

    a.addr.notfound = 1;

    if (a.echomail)
    {
        if (tokens[3] != NULL)
        {
            a.tag = xstrdup(tokens[3]);
            if (tokens[4] != NULL)
            {
                a.addr = parsenode(tokens[4]);
            }
        }
        else
        {
            a.tag = xstrdup(a.description);
        }
    }
    else
    {
        a.tag = xstrdup(a.description);
        if (tokens[3] != NULL)
        {
            a.addr = parsenode(tokens[3]);
        }
    }

    if (a.addr.notfound)
    {
        a.addr = thisnode;
        if (thisnode.domain)
        {
            a.addr.domain = xstrdup(thisnode.domain);
        }
    }
    AddArea(&a);
}

/*
 *  Parses an alias.
 */

static void parse_alias(char *value)
{
    static ALIAS a;
    char *tokens[6];
    char *s;

    memset(&a, 0, sizeof a);
    memset(tokens, 0, sizeof tokens);

    parse_tokens(value, tokens, 5);

    if (tokens[2] == NULL)
    {
        return;
    }

    a.alias = xstrdup(tokens[0]);
    a.name = xstrdup(tokens[1]);

    if (!stricmp(tokens[2], "uucp"))
    {
     /*   a.addr.internet = 1; */
    }
    else
    {
        a.addr = parsenode(tokens[2]);
    }

    if ((s = tokens[3]) != NULL)
    {
        a.attrib.local = 1;
        a.attr = 1;
        while (*s)
        {
            switch (tolower(*s))
            {
            case 'p':
                a.attrib.priv = 1;
                break;
            case 'h':
                a.attrib.hold = 1;
                break;
            case 'd':
                a.attrib.direct = 1;
                break;
            case 'c':
                a.attrib.crash = 1;
                break;
            case 'k':
                a.attrib.killsent = 1;
                break;
            case 'n':
                a.attr = 0;
                break;
            default:
                /* unknown */
                break;
            }
            s++;
        }

        if (tokens[4] != NULL)
        {
            a.subj = xstrdup(tokens[4]);
        }
    }


    SW->otheraliases++;
    if (SW->otheraliases > SW->maxotheraliases)
    {
        SW->maxotheraliases++;
        SW->maxotheraliases*=3;

        /* Reallocating in greater blocks dramatically reduces */
        /* memory requirements of the DOS version. At least    */
        /* Borlands realloc does not seem to be very "intelligent".. */

        aliaslist = xrealloc(aliaslist,
                             SW->maxotheraliases * sizeof(struct _alias));
    }
    aliaslist[SW->otheraliases - 1] = a;
}

/*
 *  Parses the AREADESC keyword
 */


static char *areadesctokens[] =
{
    "asis",
    "upper",
    "lower",
    "tag",
    "areatag",
    "desc",
    "dsc",
    "areadesc",
    NULL
};

#define AREADESC_ASIS 0
#define AREADESC_UPPER 1
#define AREADESC_LOWER 2
#define AREADESC_TAG 3
#define AREADESC_AREATAG 4
#define AREADESC_DESC 5
#define AREADESC_DSC 6
#define AREADESC_AREADESC 7

static void parseareadesc(char *value)
{
    char *tokens[16];
    int casemode = 0, i, j;
    int areadesc = 0;

    memset(tokens, 0, sizeof tokens);

    parse_tokens(value, tokens, 15);

    for (i = 0; tokens[i] != NULL; i++)
    {
        for (j = 0; areadesctokens[j] != NULL; j++)
        {
            if (!stricmp(tokens[i], areadesctokens[j]))
            {
                break;
            }
        }

        switch (j)
        {
        case AREADESC_ASIS:
            casemode = 0;
            break;
        case AREADESC_UPPER:
            casemode = 1;
            break;
        case AREADESC_LOWER:
            casemode = 2;
            break;
        case AREADESC_TAG:
        case AREADESC_AREATAG:
            switch (casemode)
            {
            case 0:
                areadesc |= DSCTAGASIS;
                break;
            case 1:
                areadesc |= DSCTAGUPPER;
                break;
            case 2:
                areadesc |= DSCTAGLOWER;
                break;
            default:
                abort();
            }
            break;
        case AREADESC_DESC:
        case AREADESC_DSC:
        case AREADESC_AREADESC:
            switch (casemode)
            {
            case 0:
                areadesc |= DSCDESCASIS;
                break;
            case 1:
                areadesc |= DSCDESCUPPER;
                break;
            case 2:
                areadesc |= DSCDESCLOWER;
                break;
            default:
                abort();
            }
            break;
        default:
            printf ("\r\aNot a valid option for the AreaDesc keyword: %s\n",
                    tokens[i]);
        }
    }

    if (areadesc != 0)
    {
        SW->areadesc = areadesc;
    }
}

/*
 * This routine takes data read form araefile and makes area description
 * from it based on the user's wishes (SW->areadesc).
 */

char *makeareadesc(char *tag, char *desc)
{
    int length = 1; /* zero byte */
    int taglen = 0, desclen = 0;
    char *r, *p;

    if (SW->areadesc & (DSCDESCASIS | DSCDESCUPPER | DSCDESCLOWER))
    {
        if (desc != NULL)
        {
            length += (desclen = strlen(desc));
        }
    }

    if (!desclen || (SW->areadesc & (DSCTAGASIS | DSCTAGUPPER | DSCTAGLOWER)))
    {
        /* even if TAG is not configured, TAG is used if no DESC is present */
        if (tag != NULL)
        {
            length += (taglen = strlen(tag));
        }
    }

    if (desclen && taglen)
    {
        length += 3; /* " - " */
    }

    r = p = xmalloc(length);

    if (r != NULL)
    {
        if (taglen)
        {
            if (SW->areadesc & DSCTAGLOWER)
            {
                while (*tag)  *(p++) = (char)tolower((int)(*(tag++)));
            }
            else if (SW->areadesc & DSCTAGUPPER)
            {
                while (*tag) *(p++) = (char)toupper((int)(*(tag++)));
            }
            else
            {
                while (*tag) *(p++) = *(tag++);
            }
        }
        if (taglen && desclen)
        {
            *(p++) = ' ';
            *(p++) = '-';
            *(p++) = ' ';
        }
        if (desclen)
        {
            if (SW->areadesc & DSCDESCLOWER)
            {
                while (*desc)  *(p++) = (char)tolower((int)(*(desc++)));
            }
            else if (SW->areadesc & DSCDESCUPPER)
            {
                while (*desc) *(p++) = (char)toupper((int)(*(desc++)));
            }
            else
            {
                while (*desc) *(p++) = *(desc++);
            }
        }
        *p = '\0';
    }
    return r;
}

/*
 *  Handles the areafiles.
 */

void do_areafile(char *value)
{
    char *tokens[3];

    memset(tokens, 0, sizeof tokens);
    parse_tokens(value, tokens, 2);

    if (tokens[0] == NULL)
    {
        return;
    }

    areas_type = AREAS_BBS;

    if (toupper(*tokens[0]) == 'S')
    {
        areas_type = SQUISH;
    }

    if (toupper(*tokens[0]) == 'F')
    {
        areas_type = FASTECHO;
    }

    if (toupper(*tokens[0]) == 'G')
    {
        areas_type = GECHO;
    }

    if (toupper(*tokens[0]) == 'F' && toupper(tokens[0][1]) == 'I')
    {
        areas_type = FIDOCONFIG;
    }

    if (tokens[1] || areas_type == FIDOCONFIG)
    {
        char *temp = NULL;
        if (tokens[1])
        {
            temp = pathcvt(xstrdup(tokens[1]));
        }
        if (areas_type == AREAS_BBS)
        {
            checkareas(temp);
        }
        else if (areas_type == FASTECHO)
        {
            check_fastecho(temp);
        }
        else if (areas_type == GECHO)
        {
            check_gecho(temp);
        }
        else if (areas_type == FIDOCONFIG)
        {
            check_fidoconfig(tokens[1]);
        }
        else
        {
            check_squish(temp);
        }
        release(temp);
    }
    else
    {
        /* no area-file was specified, we want to check later */
        check_area_files = 1;
    }
}

/*
 *  Compares two areas. Qsort helper function for areasort.
 */

#define compare(x,y) ( (x) < (y) ? -1 : ((x) == (y) ? 0 : 1) )

static int compare_areas(const void *x1, const void *x2)
{
  const AREA *a1 = (const AREA *)x1;
  const AREA *a2 = (const AREA *)x2;
  const char *cp;
  int retval = 0;


  for (cp = ST->sort_criteria; *cp; cp++)
  {
      switch(toupper(*cp))
      {
      case 'N':   /* sort netmail first, then local, then echomail */
          retval = compare(a1->netmail + a1->local * 2 + a1->echomail * 4,
                           a2->netmail + a2->local * 2 + a2->echomail * 4);
          break;
      case 'T':   /* sort by area tag */
          retval = strcmp(a1->tag, a2->tag);
          break;
      case 'D':   /* sort by area description */
          retval = strcmp(a1->description, a2->description);
          break;
      case 'G':   /* sort by group */
          retval = compare(a1->group, a2->group);
          break;
      }
      if (retval)
      {
         return retval;
      }
  }
  /* No differences found using the specified criteria. Maintain the
     original insertion order. */
  return compare(a1->areanumber, a2->areanumber);
}


/*
 *  Iterates all areas and sees if they match any of the group patterns.
 */

static void applygroups(void)
{
    int i;

    for (i=0; i<SW->areas; i++)
    {
        SetAreaGroupInfo(arealist + i);
    }
}



/*
 *  Sorts the areas in the defined manner and sets the arenumber field.
 */

static void areasort(void)
{
    int i;

    for (i=0; i<SW->areas; i++)
    {
        arealist[i].areanumber=i;
    }

    if (ST->sort_criteria == NULL || SW->areas == 0)
    {
        return;
    }

    qsort(arealist, SW->areas, sizeof(AREA), compare_areas);

    for (i=0; i<SW->areas; i++)
    {
        arealist[i].areanumber=i;
    }
}


 /*
  * The struct s_conditional saves information about the IF conditional
  * block that is currently being parsed.
  */

#define TRUECOND 1
#define FALSECOND 0
#define NOCOND -1    /* generally disabled IF block because of
                        surrounding false condition */

struct s_conditional
{
    int condition;  /* true or false? */
    int branch; /* 0: then, 1: else */
    int line_no;
    struct s_conditional *last;
} *cur_cond = NULL;

  /*
   * cond_deleted is set to 1 if we are reading a block that is not to
   * be parsed (except for IF, ELSE, ELIF, ENDIF) because the
   * introducing conditional was false.
   */

static int cond_deleted = 0;

  /*
   * evaluate a IF or ELSEIF condition
   */

int evaluate_condition(char *condition, int line_no)
{
    char *firstword;
    char *secondword;
    char *equal;
    char *eval;

    if (condition == NULL)
    {
        goto syntax_error;
    }

    equal = strchr(condition, '=');
    firstword = strtok(condition, "= \t\n\r");
    secondword = strtok(NULL, "= \t\n\r");

    if (equal == NULL && secondword != NULL)
    {
        goto syntax_error;
    }

    if (secondword == NULL)
    {
        secondword = "";
    }

    if (equal)  /* comparison of env variables */
    {
        eval = getenv(firstword);
        if (eval == NULL)
        {
            eval = "";
        }
        if (!stricmp(eval, secondword))
        {
            return TRUECOND;
        }
        else
        {
            return FALSECOND;
        }
    }
    else        /* test for operating system */
    {
        if (!stricmp(firstword, "0"))
        {
            return 0;
        }
        else if (!stricmp(firstword, "1"))
        {
            return 1;
        }
        else if (!stricmp(firstword, "LINUX"))
        {
            firstword = "LNX"; /* GoldEd compatibility */
        }

        return (stristr(OSID, firstword) != NULL);
    }

 syntax_error:
    printf ("\r\aLine %d: Syntax error in IF/ELIF condition.\n", line_no);
    return FALSECOND;
}

  /*
   * functions for handling conditions
   */

static void new_condition(void)
{
    if ((cur_cond == NULL) ||
        (cur_cond->condition == TRUECOND && cur_cond->branch == 0) ||
        (cur_cond->condition == FALSECOND && cur_cond->branch == 1))
    {
        cond_deleted = 0;
    }
    else
    {
        cond_deleted = 1;
    }
}

static void func_if(int condition, int line_no)
{
    struct s_conditional *pc = xmalloc(sizeof(struct s_conditional));

    pc->branch = 0;
    pc->condition = cond_deleted ? NOCOND: condition;
    pc->line_no = line_no;
    pc->last = cur_cond;
    cur_cond = pc;

    new_condition();
}

static void func_else(int line_no)
{
    if (cur_cond == NULL)
    {
        printf ("\r\aLine %d: ELSE without preceding IF keyword found.\n",
                line_no);
    }
    else
    {
        cur_cond->branch = 1;
        new_condition();
    }
}

static void func_elif(int condition, int line_no)
{
    if (cur_cond == NULL)
    {
        printf ("\r\aLine %d: ELSEIF without preceding IF keyword found.\n",
                line_no);
    }
    else
    {
        if (cur_cond->branch == 0)
        {
            if (cur_cond->condition == FALSECOND)
            {
                cur_cond->condition = condition;
            }
            else
            {
                cur_cond->branch = 1;
            }
            new_condition();
        }
    }
}

static void func_endif(int line_no)
{
    struct s_conditional *pc = cur_cond;

    if (cur_cond == NULL)
    {
        printf ("\r\aLine %d: ENDIF without preceding IF found.\n",
                line_no);
    }
    else
    {
        cur_cond = pc->last;
        xfree(pc);
        new_condition();
    }
}


/*
 *  Handles an entire config file.
 */

static void parseconfig(FILE * fp)
{
    static char progress_indicators[4] =
    {'-', '\\', '|', '/'};
    static char *raw_buffer;
    static char *buffer = NULL;
    char *keyword;
    char *value = NULL;
    char *s = NULL;
    char *tokens[20];
    int i = 0, line_num = 0;
    int verb;

    if (raw_buffer == NULL) raw_buffer = xmalloc(TEXTLEN);
    memset(raw_buffer, 0, TEXTLEN);

    while (!feof(fp))
    {
        line_num++;

        printf("%c\b", progress_indicators[line_num % 2]);
        fflush(stdout);

        if (fgets(raw_buffer, TEXTLEN, fp) == NULL)
        {
            break;
        }
        buffer = env_expand(raw_buffer); /* expand %ENVIRONMENT% variables */

        keyword = strtok(buffer, " \t\n\r");

        if (keyword)
        {
            strlwr(keyword);
        }
        else
        {
            continue;
        }

        if (*keyword == ';')
        {
            continue;
        }

        value = strtok(NULL, ";\n\r");
        value = striptwhite(value);

        /* clear the pointers in the array */

        memset(tokens, 0, sizeof(tokens));

        while (value && *value && isspace((unsigned char)*value))
        {
            if (*value == '\n' || *value == ';')
            {
                break;
            }
            else
            {
                value++;
            }
        }

        verb = GetVerbNum(keyword);
        switch (verb)
        {
        case CFG_IF:
            parse_tokens(value, tokens, 3);
            func_if(evaluate_condition(value, line_num), line_num);
            verb = -2;  /* skip next switch block */
            break;

        case CFG_ELIF:
        case CFG_ELSEIF:
            parse_tokens(value, tokens, 3);
            func_elif(evaluate_condition(value, line_num), line_num);
            verb = -2;  /* skip next switch block */
            break;

        case CFG_ELSE:
            func_else(line_num);
            verb = -2;  /* skip next switch block */
            break;

        case CFG_ENDIF:
            func_endif(line_num);
            verb = -2;  /* skip next switch block */
            break;

        default:;
        }

        if (cond_deleted)
        {
            verb = -2;
        }

        switch (verb)
        {
        case CFG_NAME:
            parse_tokens(value, tokens, 3);
            if (tokens[0] != NULL)
            {
                for (i = 0; i < MAXUSERS; i++)
                {
                    if (user_list[i].name == NULL)
                    {
                        break;
                    }
                }
                if (i < MAXUSERS)
                {
                    user_list[i].name = xstrdup(tokens[0]);

                    if (tokens[1] != NULL)
                    {
                        user_list[i].lastread = xstrdup(tokens[1]);
                    }

                    if (tokens[2] != NULL)
                    {
                        user_list[i].offset = atol(tokens[2]);
                        user_list[i].offs_defined = 1;
                    }
                }
            }
            break;

        case CFG_ADDRESS:
            alias = xrealloc(alias, (++SW->aliascount) * sizeof(ADDRESS));
            alias[SW->aliascount - 1] = parsenode(value);
            break;

        case CFG_PRIVATENET:
            SW->pointnet = (int)strtol(value, NULL, 0);
            break;

        case CFG_ALIAS:
            parse_alias(value);
            break;

        case CFG_OUTFILE:
            release(ST->outfile);
            if (strchr("?+", value[0]) != NULL)
            {
                char *temp;
                temp = pathcvt(strdup(value + 1));
                ST->outfile = xmalloc(strlen(temp) + 2);
                ST->outfile[0] = value[0];
                ST->outfile[1] = '\0';
                strcat(ST->outfile, temp);
                release(temp);
            }
            else
            {
                ST->outfile = pathcvt(xstrdup(value));
            }
            break;

        case CFG_LASTREAD:
            release(ST->lastread);
            ST->lastread = xstrdup(value);
            break;

        case CFG_TOSSLOG:
            release(ST->echotoss);
            if (value!=NULL)
            {
                if (value[0] == '+')
                {
                    ST->echotoss = pathcvt(xstrdup(value + 1));
                }
                else
                {
                    ST->echotoss = pathcvt(xstrdup(value));
                }
            }
            break;

        case CFG_USERLIST:
            ST->fidolist = pathcvt(xstrdup(strtok(value, ",\n")));
            ST->userlist = strtok(NULL, ",\n");
            if (ST->userlist != NULL)
            {
                ST->userlist = pathcvt(xstrdup(ST->userlist));
            }
            break;

        case CFG_SWAPPATH:
            release(ST->swap_path);
            kill_trail_slash(value);
            ST->swap_path = pathcvt(xstrdup(value));
            break;

        case CFG_NODEPATH:
            release(ST->nodepath);
            kill_trail_slash(value);
            ST->nodepath = pathcvt(xstrdup(value));
            break;

        case CFG_NODELIST:
            parse_tokens(value, tokens, 3);
            if (tokens[2] != NULL)
            {
                node_lists = xrealloc(node_lists, (++SW->nodelists) * sizeof(D_LIST));
                node_lists[SW->nodelists - 1].name = xstrdup(tokens[0]);
                node_lists[SW->nodelists - 1].base_name = xstrdup(tokens[1]);
                node_lists[SW->nodelists - 1].sysop = xstrdup(tokens[2]);

                if (SW->nodelists == 1)
                {
                    ST->nodebase = node_lists[SW->nodelists - 1].base_name;
                    ST->sysop = node_lists[SW->nodelists - 1].sysop;
                }
            }
            break;

        case CFG_HELPFILE:
            release(ST->helpfile);
            ST->helpfile = pathcvt(xstrdup(value));
            break;

        case CFG_AREAFILE:
            do_areafile(value);
            break;

        case CFG_USEROFFSET:
            SW->useroffset = atoi(value);
            break;

        case CFG_QUOTE:
            release(ST->quotestr);
            striptwhite(value);
            ST->quotestr = xstrdup(value);
            s = ST->quotestr;
            while (*s)
            {
                if (*s == '_')
                {
                    *s = ' ';
                }
                s++;
            }
            break;

        case CFG_ALTERFUNC:
            parse_tokens(value, tokens, 2);
            if (tokens[0] && tokens[1])
            {
                int var = 0;
                char *c;

                c = striplwhite(tokens[0]);
                s = striplwhite(tokens[1]);

                if (*s != '\0' && *c != '\0')
                {
                    while (*c && !isspace((unsigned char)*c))
                    {
                        switch (toupper(*c))
                        {
                        case 'Q':
                            if (!(var & MT_REP) && !(var & MT_NEW))
                            {
                                var |= MT_QUO;
                            }
                            break;

                        case 'R':
                            if (!(var & MT_QUO) && !(var & MT_NEW))
                            {
                                var |= MT_REP;
                            }
                            break;

                        case 'N':
                            if (!(var & MT_QUO) && !(var & MT_REP))
                            {
                                var |= MT_NEW;
                            }
                            break;

                        case 'A':
                            var |= MT_ARC;
                            break;

                        case 'F':
                            var |= MT_FOL;
                            break;

                        default:
                            break;
                        }
                        c++;
                    }
                    if (!stricmp("ReplyQuote", s))
                    {
                        SW->rquote = var;
                    }
                    else if (!stricmp("ReplyOtherArea", s))
                    {
                        SW->rotharea = var;
                    }
                    else if (!stricmp("ReplyFollow", s))
                    {
                        SW->rfollow = var;
                    }
                    else if (!stricmp("ReplyExtra", s))
                    {
                        SW->rextra = var;
                    }
                }
            }
            break;

        case CFG_SWITCH:
            parse_tokens(value, tokens, 2);
            if (tokens[1] != NULL)
            {
                AssignSwitch(value, stricmp(tokens[1], "on") == 0 ? 1 : 0);
            }
            break;

        case CFG_COLOR:
        case CFG_COLOUR:
            parse_tokens(value, tokens, 3);
            if (tokens[2] != NULL)
            {
                int fcol, bcol;
                fcol = GetColor(tokens[1]);
                bcol = GetColor(tokens[2]);
                if (fcol == -1 || bcol == -1)
                {
                    continue;
                }
                AssignColor(tokens[0], fcol | bcol);
            }
            break;

        case CFG_RIGHT:
            SW->orgrm = (int)strtol(value, NULL, 0);
            break;

        case CFG_QUOTERIGHT:
            SW->orgqm = (int)strtol(value, NULL, 0);
            break;

        case CFG_TABSIZE:
            SW->tabsize = (int)strtol(value, NULL, 0);
            break;

        case CFG_CURSTART:
            cur_start = (int)strtol(value, NULL, 0);
            break;

        case CFG_CUREND:
            cur_end = (int)strtol(value, NULL, 0);
            break;

        case CFG_FIDO:
        case CFG_SQUISH:
        case CFG_QUICK:
#if MSGAPI_VERSION >= 2
        case CFG_JAM:
#endif
            parsemail(keyword, value);
            break;

        case CFG_UUCP:
            uucp_gate = parsenode(value);
            break;

        case CFG_DOMAIN:
            domain_list = xrealloc(domain_list, (++SW->domains) * sizeof(ADDRESS));
            domain_list[SW->domains - 1] = parsenode(value);
            break;

        case CFG_GATE:
            SW->gate = 0;
            switch (toupper(*value))
            {
            case 'Z':  /* Zones */
                SW->gate = GZONES;
                break;
            case 'D':  /* Domains */
                SW->gate = GDOMAINS;
                break;
            case 'B':  /* Both */
                SW->gate = BOTH;
                break;
            case 'A':  /* Ask */
                SW->gate = GASK;
                break;
            default:
                break;
            }
            break;

        case CFG_ORIGIN:
            if (value != NULL)
            {
                if (n_origins == 0)
                {
                    origins = xmalloc(sizeof(char*) * (n_origins = 1));
                }
                else
                {
                    origins = xrealloc(origins, sizeof(char*) * (++n_origins));
                }

                origins[n_origins - 1] = xstrdup(value);
                release(ST->origin);
                ST->origin = xstrdup(value);
            }
            break;

        case CFG_READKEY:
        case CFG_EDITKEY:
            if (value)
            {
                strlwr(value);
                i = (int)strtol(value, &value, 0);
                value = striplwhite(value);
                if (*keyword == 'e')
                {
                    e_assignkey(i, value);
                }
                else
                {
                    r_assignkey(i, value);
                }
            }
            break;

        case CFG_FUNCTION:
            i = (int)strtol(value, &s, 0);
            s = striplwhite(s);
            if (i >= 0 && i <= 40)
            {
                macros[i] = parse_macro(s);
            }
            break;

        case CFG_INCLUDE:
            if (value)
            {
                FILE *ifp;
                char *temp;
                temp = pathcvt(xstrdup(value));
                ifp = fopen(temp, "r");
                release(temp);
                if (ifp != NULL)
                {
                    parseconfig(ifp);
                    fclose(ifp);
                }
            }
            break;

        case CFG_MAXX:
            maxx_force = (int)strtol(value, NULL, 0);
            break;

        case CFG_MAXY:
            maxy_force = (int)strtol(value, NULL, 0);
            break;

        case CFG_TEMPLATE:
            templates = xrealloc(templates, (++SW->numtemplates) * sizeof(char *));
            templates[SW->numtemplates - 1] = pathcvt(xstrdup(value));
            if (SW->numtemplates == 1)
            {
                ST->template = xstrdup(templates[SW->numtemplates - 1]);
            }
            break;

        case CFG_UUCPNAME:
            release(ST->uucpgate);
            ST->uucpgate = xstrdup(value);
            break;

        case CFG_GROUP:
            parse_tokens(value, tokens, 3);
            if (tokens[2] != NULL || tokens[1] == NULL)
            {

                printf("\r\aInvalid Syntax for GROUP statement. The syntax and meaning of this keyword\n");
                printf ("have changed in Msged TE 06. See whatsnew.doc and the manual for more info.\n");
            }
            else
            {
                group = xrealloc(group, (++num_groups) * sizeof(GROUP));
                strip_geese_feet(tokens[0]);
                group[num_groups - 1].handle = group_gethandle(tokens[0], 1);
                group[num_groups - 1].pattern = xstrdup(tokens[1]);
            }
            break;

        case CFG_GROUPSETTINGS:
            parse_tokens(value, tokens, 3);
            if (tokens[2] != NULL)
            {
                strip_geese_feet(tokens[0]);
                i = group_gethandle(tokens[0], 1);
                group_setsettings(i, atoi(tokens[1]), atoi(tokens[2]));
            }
            break;

        case CFG_EDITOR:
            release(ST->editorName);
            ST->editorName = pathcvt(xstrdup(value));
            break;

        case CFG_ROBOTNAME:
            parse_tokens(value, tokens, 3);
            if (tokens[0] != NULL)
            {
                for (i = 0; i < MAXUSERS; i++)
                {
                    if (user_list[i].robotname == NULL)
                    {
                        break;
                    }
                }
                if (i < MAXUSERS)
                {
                    user_list[i].robotname = xstrdup(tokens[0]);
                }
            }
            break;

        case CFG_QUICKBBS:
            release(ST->quickbbs);
            kill_trail_slash(value);
            ST->quickbbs = pathcvt(xstrdup(value));
            break;

        case CFG_SCAN:
            scan = 1;
            break;

        case CFG_MOUNTDIR:
            parse_tokens(value, tokens, 2);
            if (tokens[0] != NULL && tokens[1] != NULL)
            {
                mntdirunix = xstrdup(tokens[0]);
                mntdirdos = xstrdup(tokens[1]);
            }
            break;

        case CFG_SOFTCRXLAT:
            softcrxlat = (int)strtol(value, NULL, 0);
            break;

        case CFG_AREAEXCL:
            SkipArea(value);
            break;

        case CFG_OUTPUTCHARSET:
            release(ST->output_charset);
            ST->output_charset = xstrdup(value);
            break;

        case CFG_SORTAREAS:
            release(ST->sort_criteria);
            ST->sort_criteria = xstrdup(value);
            break;

        case CFG_ENABLESC:
            if (value != NULL)
            {
                TTconfigure("highascii", value);
            }
            else
            {
                unsigned char buffer[129];
                int i;

                for (i = 0; i < 128; i++)
                {
                    buffer[i] = i + 128;
                }
                buffer[i] = '\0';

                TTconfigure("highascii", buffer);
            }
            break;

        case CFG_AREAFILEFLAGS:
            if (value != NULL)
            {
                if (areafileflags == NULL)
                {
                    areafileflags = xstrdup(value);
                }
                else
                {
                    areafileflags = xrealloc(areafileflags,
                                             strlen(areafileflags) +
                                             strlen(value) + 1 );
                    s = areafileflags + strlen(areafileflags);

                    for (; *value; value++)
                    {
                        if (strchr(areafileflags, *value) == NULL)
                        {
                            *s++ = *value;
                            *s = '\0';
                        }
                    }
                }
            }
            break;

        case CFG_FREQAREA:
            if (ST->freqarea != NULL)
            {
                xfree(ST->freqarea);
            }
            ST->freqarea = xstrdup(value);
            break;

        case CFG_ASSUMECHARSET:
            release(ST->input_charset);
            if (value != NULL)
            {
                ST->input_charset = xstrdup(value);
            }
            break;

        case CFG_UUCPREPLYTO:
            release(ST->uucpreplyto);
            ST->uucpreplyto = xstrdup(value);
            break;

        case CFG_FREQFLAGS:
            release (ST->freqflags);
            ST->freqflags = xstrdup(value);
            break;

        case CFG_PRINTER:
            release (ST->printer);
            ST->printer = xstrdup(value);
            break;

        case CFG_READMAP:
            release (ST->readmap);
            ST->readmap = xstrdup(value);
            break;

        case CFG_WRITEMAP:
            release (ST->writemap);
            ST->writemap = xstrdup(value);
            break;

        case CFG_CHARSETALIAS:
            parse_tokens(value, tokens, 2);
            if (tokens[0] != NULL && tokens[1] != NULL)
            {
                charset_alias(tokens[0], tokens[1]);
            }
            break;

        case CFG_AREADESC:
            parseareadesc(value);
            break;

        case -2:   /* skip */
            break;

        case -1:
        default:
            printf("\r\aLine %d: Unknown configuration keyword: '%s'\n",
                   line_num, keyword);
            break;
        }
        release(buffer);
        memset(raw_buffer, 0, TEXTLEN);
    }
}

#define NP(x) ((x==NULL)?("<not set>"):(x))

void show_debuginfo(int macro_count)
{
    int i;
    char *szYes = "Yes";
    char *szNo = "No";

    MouseOFF();
    TTclose();

    printf(
      "\n"
      "%-30s; %s\n"
      "-------------------------------------------------------------------------------\n"
      "\n",
      PROG " " VERPROJECT " " VERNUM VERPATCH VERBRANCH "; Mail Reader",
      "Compiled on " __DATE__ " at " __TIME__
    );

    printf("Screen size       : %d columns, %d rows\n", maxx, maxy);
    printf("User              : \"%s\" (%s)\n", NP(ST->username), show_address(&thisnode));
    printf("Origin            : \"%s\"\n", NP(ST->origin));
    printf("Macros            : %d macros defined\n", macro_count);
    printf("Home directory    : %s\n", NP(ST->home));
    printf("Quote string      : \"%s\"\n", NP(ST->quotestr));
    printf("Export file       : %s (default)\n", NP(ST->outfile));
    printf("Config file       : %s\n", NP(ST->cfgfile));
    printf("Echotoss log      : %s\n", NP(ST->echotoss));
    printf("Template file     : %s\n", NP(ST->template));
#ifdef MSDOS
    printf("Swap path         : %s\n", NP(ST->swap_path));
#endif
    printf("Help file         : %s\n", NP(ST->helpfile));
    printf("Command processor : %s\n", NP(ST->comspec));
    printf("External editor   : %s\n", NP(ST->editorName));
    printf("QuickBBS path     : %s\n", NP(ST->quickbbs));
    printf("\n");

    printf("Addresses                 : %d addresses configured\n", SW->aliascount);
    for (i = 0; i < SW->aliascount; i++)
    {
        printf ("%s\n", show_address(alias + i));
    }
    printf("\n");

    printf("Areas                     : %d area%s configured\n", SW->areas, SW->areas == 1 ? "" : "s");
    printf("Generate MSGIDs           : %s\n", SW->msgids ? szYes : szNo);
    printf("Generate Opus time stamps : %s\n", SW->opusdate ? szYes : szNo);
    printf("Show hidden lines         : %s\n", SW->shownotes ? szYes : szNo);
    printf("Show SEEN-BY lines        : %s\n", SW->showseenbys ? szYes : szNo);
    printf("Show origin lines         : %s\n", SW->showorigins ? szYes : szNo);
    printf("Show tearlines            : %s\n", SW->showtearlines ? szYes : szNo);
    printf("Confirm deletes, aborts   : %s\n", SW->confirmations ? szYes : szNo);
    printf("Show date arrived         : %s\n", SW->datearrived ? szYes : szNo);
    printf("Show current address      : %s\n", SW->showaddr ? szYes : szNo);
    printf("Use lastread/current      : %s\n", SW->use_lastr ? szYes : szNo);
    printf("Quote quotes              : %s\n", SW->qquote ? szYes : szNo);
    printf("Save original carbon copy : %s\n", SW->savecc ? szYes : szNo);
    printf("Save raw carbon copy      : %s\n", SW->rawcc ? szYes : szNo);
    printf("Chop end of quoted msgs   : %s\n", SW->chopquote ? szYes : szNo);
    printf("Use \"HardQuotes\" feature  : %s\n", SW->hardquote ? szYes : szNo);
    printf("Show paragraph markers    : %s\n", SW->showcr ? szYes : szNo);
    printf("Show end-of-line markers  : %s\n", SW->showeol ? szYes : szNo);
    printf("Show real message numbers : %s\n", SW->showrealmsgn ? szYes : szNo);
    printf("Enable mouse support      : %s\n", SW->usemouse ? szYes : szNo);
    printf("Expand tab character      : %s\n", SW->tabexpand ? szYes : szNo);
    printf("Only show CRs in editor   : %s\n", SW->editcronly ? szYes : szNo);
    printf("Generate PID              : %s\n", SW->usepid ? szYes : szNo);
    printf("Generate echo tearlines   : %s\n", SW->usetearlines ? szYes : szNo);
    printf("Generate echo origins     : %s\n", SW->useoriginlines ? szYes : szNo);
    printf("Generate SOT/EOT          : %s\n", SW->soteot ? szYes : szNo);
    printf("Show system time          : %s\n", SW->showtime ? szYes : szNo);
    printf("Show file hdrs on import  : %s\n", SW->importfn ? szYes : szNo);
    printf("Msg num at top of screen  : %s\n", SW->dmore ? szYes : szNo);
    printf("Show status bar           : %s\n", SW->statbar ? szYes : szNo);
    printf("Perform address lookup    : %s\n", SW->showsystem ? szYes : szNo);
    printf("Format external messages  : %s\n", SW->extformat ? szYes : szNo);
    printf("Exact match in area list  : %s\n", SW->arealistexactmatch ? szYes : szNo);
    printf("Generate echomail FLAGS   : %s\n", SW->echoflags ? szYes : szNo);
    printf("Generate netmail Via      : %s\n", SW->netmailvia ? szYes : szNo);
    printf("Domain in origin line     : %s\n", SW->domainorigin ? szYes : szNo);
    printf("Key right to next unread  : %s\n", SW->rightnextunreadarea ? szYes : szNo);

    printf("-------------------------------------------------------------------------------\n");
    for (i = 0; i < SW->areas; i++)
    {
        printf("%s: %s (%s)\n", arealist[i].tag, arealist[i].path,
               (arealist[i].msgtype == SQUISH) ? "squish" :
               (arealist[i].msgtype == QUICK) ? "hudson" :
               (arealist[i].msgtype == FIDO) ? "fido" : "other");
    }

    exit(0);
}

/*
 *  Handles the entire configuration process.
 */

void opening(char *cfgfile, char *areafile)
{
    WND *hCurr, *hWnd;
    FILE *fp;
    int count = 0, i;
    static char cfnname[] = DEFAULT_CONFIG_FILE;
    char tmp[PATHLEN];

    InitVars();

    printf(PROG " " VERPROJECT " " VERNUM VERPATCH VERBRANCH " ... \n");
    fflush(stdout);

    fp = fileopen(cfnname, cfgfile);
    if (fp == NULL)
    {
        if (cfgfile == NULL)
        {
            cfgfile = cfnname;
        }
        printf("\nCannot open " PROG " configuration file '%s'.\n", cfgfile);
        exit(-1);
    }

    parseconfig(fp);
    fclose(fp);

                                /* initialise the FSP 1013  charset engine */
    read_charset_maps(ST->readmap, ST->writemap);
    recode_area_descriptions();
    if (!have_readtable ("IBMPC", 2))
    {
        printf ("\r\aWARNING: No readmap found for the IBMPC charset. "
                "Read in the manual about the\n"
                "CharsetAlias command!\n");
    }


    if (cur_cond != NULL)
    {
        printf ("\r\aLine %d: IF block has no matching ENDIF!\n",
                cur_cond->line_no);
    }

    if (alias == NULL)
    {
        printf("\r\aError! Primary address must be defined.\n");
        exit(-1);
    }

    if (user_list[0].name == NULL)
    {
        printf("\r\aError! Name must be defined.\n");
        exit(-1);
    }


    /* set default lastread pointer information for all users from the current
       lastread and user offset values, and after that, set the current values
       to those of the first user. */

    for (i = 0; i < MAXUSERS; i++)
    {
        if (user_list[i].name == NULL)
        {
            break;
        }

        if (user_list[i].lastread == NULL)
        {
            user_list[i].lastread = xstrdup(ST->lastread);
        }
        if (user_list[i].offs_defined == 0)
        {
            user_list[i].offset = SW->useroffset;
            user_list[i].offs_defined = 1;
        }
    }

    release(ST->username);
    ST->username = xstrdup(user_list[0].name);
    release(ST->lastread);
    ST->lastread = xstrdup(user_list[0].lastread);
    SW->useroffset = user_list[0].offset;



    /* count the number of macros */

    for (i = 0; i < 40; i++)
    {
        count += macros[i] != NULL ? 1 : 0;
    }


    /* now read the areafile. FIXME: What if user configures more than one? */

    if (check_area_files)
    {
        if (areas_type == AREAS_BBS)
        {
            checkareas(areafile);
        }
        else if (areas_type == FASTECHO)
        {
            check_fastecho(areafile);
        }
        else if (areas_type == GECHO)
        {
            check_gecho(areafile);
        }
        else
        {
            check_squish(areafile);
        }
    }

    if (arealist == NULL)
    {
        printf("\r\aError! At least one message area must be defined.\n");
        exit(-1);
    }

    if (SW->soteot && SW->domainorigin)
    {
        printf("\r\aError! "
               "The SOT/EOT specification does not permit domains in "
               "echomail origin\nlines. Please set either"
               "\"Switch DomainOrigin\" or "
               "\"Switch SOTEOT\" to \"Off\".\n");
        exit(-1);
    }

    applygroups();
    areasort();
    group_build_arealist();
    printf (" \n");
    InitScreen();
    mygetcwd(tmp, PATHLEN);
    ST->home = xstrdup(tmp);

    if (cmd_dbginfo)
    {
        show_debuginfo(count);
    }

    cursor(0);

    adapt_margins();

    hCurr = WndTop();

    hWnd = WndPopUp(42, 6, SBDR | SHADOW, cm[IN_BTXT], cm[IN_NTXT]);

    WndPutsCen(0, cm[IN_NTXT], PROG " Mail Reader");
    WndPutsCen(2, cm[IN_BTXT], "Version " VERPROJECT " " VERNUM VERPATCH VERBRANCH);
    WndPutsCen(4, cm[IN_NTXT], "Press a key to continue");

    GetKey();

    WndClose(hWnd);
    WndCurr(hCurr);
    TTScolor(cm[CM_NTXT]);
    TTClear(hMnScr->x1, hMnScr->y1, hMnScr->x2, hMnScr->y2);
}
