//  This may look like C code, but it is really -*- C++ -*-

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License as
//  published by the Free Software Foundation; either version 2 of the
//  License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Synchronet message base
//  ------------------------------------------------------------------

#include <cerrno>
#include <gcrcall.h>
#include <gstrall.h>
#include <gmemall.h>
#include <gmemdbg.h>
#include <gdbgerr.h>
#include <gdbgtrk.h>
#include <gmosmb.h>


//  ------------------------------------------------------------------

smb_t* smbdata = NULL;
int smbdatano = 0;

//  ------------------------------------------------------------------
//  Decode Synchronet SMB 3.10+ when_written timestamps.
//
//  Synchronet changed when_written.time from a plain Unix time_t to a packed
//  local wall-clock format for SMB v3.10+. In that format, the 16 bits that
//  used to be the upper half of netattr now carry the year, while the low
//  26 bits of when_written.time carry month/day/hour/minute/second.
//
//  Older GoldED+ code treated when_written.time as a raw Unix timestamp,
//  which causes imported Synchronet messages to display as 1970-era dates.
//
//  Existing/legacy SMB messages are still stored as normal Unix timestamps.
//  Synchronet detects those by the presence of bits outside the packed-date
//  mask. We mirror that logic here.
//  ------------------------------------------------------------------

static const uint32_t SMB_DATE_MON_SHIFT = 22;
static const uint32_t SMB_DATE_DAY_SHIFT = 17;
static const uint32_t SMB_DATE_HR_SHIFT  = 12;
static const uint32_t SMB_DATE_MIN_SHIFT = 6;
static const uint32_t SMB_DATE_SEC_SHIFT = 0;

static const uint32_t SMB_DATE_MON_MASK = 0x03C00000UL;
static const uint32_t SMB_DATE_DAY_MASK = 0x003E0000UL;
static const uint32_t SMB_DATE_HR_MASK  = 0x0001F000UL;
static const uint32_t SMB_DATE_MIN_MASK = 0x00000FC0UL;
static const uint32_t SMB_DATE_SEC_MASK = 0x0000003FUL;
static const uint32_t SMB_DATE_MASK     = (SMB_DATE_MON_MASK | SMB_DATE_DAY_MASK | SMB_DATE_HR_MASK | SMB_DATE_MIN_MASK | SMB_DATE_SEC_MASK);

static time32_t smb_written_time_to_golded(const msghdr_t& hdr)
{
    const time32_t raw = hdr.when_written.time;

    // Legacy SMB messages store a normal Unix timestamp directly.
    if(raw & ~SMB_DATE_MASK)
        return raw;

    // Synchronet SMB 3.10+ stores the year in the upper 16 bits of netattr.
    // GoldED's older headers still see netattr as a 32-bit field, so we can
    // recover the year from the high word.
    const uint16_t year = (uint16_t)((hdr.netattr >> 16) & 0xFFFFU);
    if(year < 1970 || year > 9999)
        return raw;

    struct tm tp;
    memset(&tp, 0, sizeof(tp));
    tp.tm_year = (int)year - 1900;
    tp.tm_mon  = (int)((raw & SMB_DATE_MON_MASK) >> SMB_DATE_MON_SHIFT) - 1;
    tp.tm_mday = (int)((raw & SMB_DATE_DAY_MASK) >> SMB_DATE_DAY_SHIFT);
    tp.tm_hour = (int)((raw & SMB_DATE_HR_MASK)  >> SMB_DATE_HR_SHIFT);
    tp.tm_min  = (int)((raw & SMB_DATE_MIN_MASK) >> SMB_DATE_MIN_SHIFT);
    tp.tm_sec  = (int)((raw & SMB_DATE_SEC_MASK) >> SMB_DATE_SEC_SHIFT);
    tp.tm_isdst = -1;

    return gmktime(&tp);
}

static time32_t smb_imported_time_to_golded(const msghdr_t& hdr)
{
    return hdr.when_imported.time;
}



//  ------------------------------------------------------------------

void SMBExit()
{

    throw_release(smbdata);
}


//  ------------------------------------------------------------------

void SMBInit()
{

    smbdata = (smb_t *)throw_calloc(3, sizeof(smb_t));
}


//  ------------------------------------------------------------------

void SMBArea::data_open()
{

    data = smbdata + (smbdatano++);
    strxcpy(data->file, real_path(), sizeof(data->file) - 3);
    data->sdt_fp = data->shd_fp = data->sid_fp = data->sda_fp = data->sha_fp = NULL;
    data->retry_time = 1;
    data->last_error[0] = NUL;
}


//  ------------------------------------------------------------------

void SMBArea::data_close()
{

    smbdatano--;
    data = NULL;
}


//  ------------------------------------------------------------------
//  Open the Synchronet message base

void SMBArea::open()
{

    GFTRK("SMBOpen");

    isopen++;
    if(isopen > 2)
    {
        WideLog->ErrTest();
        WideLog->printf("! Trying to open a Synchronet msgbase more than twice.");
        WideLog->printf(": %s, %s.", echoid(), path());
        WideLog->printf("+ Info: This indicates a serious bug.");
        WideLog->printf("+ Advice: Report to the Author immediately.");
        TestErrorExit();
    }
    if(isopen == 1)
    {
        if(ispacked())
        {
            isopen--;
            const char* newpath = Unpack(path());
            if(newpath == NULL)
                packed(false);
            set_real_path(newpath ? newpath : path());
            isopen++;
        }
        data_open();

        int _tries = 0;

        for(;;)
        {
            if(smb_open(data) != 0)
            {

                if((errno != EACCES) or (PopupLocked(++_tries, false, data->file) == false))
                {

                    // User requested to exit
                    WideLog->ErrOpen();
                    WideLog->printf("! Synchronet message base could not be opened (%s).", data->last_error);
                    WideLog->printf(": %s", real_path());
                    WideLog->ErrOSInfo();
                    OpenErrorExit();
                }
            }
            else
                break;
        };

        // Remove the popup window
        if(_tries)
            PopupLocked(0, 0, NULL);

        if(not fsize(data->shd_fp))
        {
            data->status.max_crcs = 0;
            data->status.max_age = 0;
            data->status.max_msgs = 1000;
            data->status.attr = 0;
            if(smb_create(data) != 0)
            {
                smb_close(data);

                WideLog->ErrOpen();
                WideLog->printf("! Synchronet message base could not be created (%s).", data->last_error);
                WideLog->printf(": %s", real_path());
                WideLog->ErrOSInfo();
                OpenErrorExit();
            }
        }
        scan();
    }

    GFTRK(0);
}


//  ------------------------------------------------------------------

void SMBArea::close()
{
    GFTRK("SMBClose");

    if(isopen)
    {
        if(isopen == 1)
        {
            smb_close(data);
            data_close();
            if(ispacked())
            {
                CleanUnpacked(real_path());
            }
        }
        isopen--;
    }
    else
    {
        WideLog->ErrTest();
        WideLog->printf("! Trying to close an already closed Synchronet msgbase.");
        WideLog->printf(": %s, %s.", echoid(), path());
        WideLog->printf("+ Info: This indicates a potentially serious bug.");
        WideLog->printf("+ Advice: Report to the Author immediately.");
        TestErrorExit();
    }

    GFTRK(0);
}


//  ------------------------------------------------------------------

void SMBArea::suspend()
{
    smb_close(data);
}


//  ------------------------------------------------------------------

void SMBArea::resume()
{
    int _tries = 0;

    for(;;)
    {
        if(smb_open(data) != 0)
        {

            if((errno != EACCES) or (PopupLocked(++_tries, false, data->file) == false))
            {

                // User requested to exit
                WideLog->ErrOpen();
                WideLog->printf("! Synchronet message base could not be opened (%s).", data->last_error);
                WideLog->printf(": %s", real_path());
                WideLog->ErrOSInfo();
                OpenErrorExit();
            }
        }
        else
            break;
    };

    // Remove the popup window
    if(_tries)
        PopupLocked(0, 0, NULL);

    if(not fsize(data->shd_fp))
    {
        data->status.max_crcs = 0;
        data->status.max_age = 0;
        data->status.max_msgs = 1000;
        data->status.attr = 0;
        if(smb_create(data) != 0)
        {
            smb_close(data);

            WideLog->ErrOpen();
            WideLog->printf("! Synchronet message base could not be created (%s).", data->last_error);
            WideLog->printf(": %s", real_path());
            WideLog->ErrOSInfo();
            OpenErrorExit();
        }
    }
}


//  ------------------------------------------------------------------

void SMBArea::lock()
{
}


//  ------------------------------------------------------------------

void SMBArea::unlock()
{
}


//  ------------------------------------------------------------------

int SMBArea::load_hdr(gmsg* __msg, smbmsg_t *smsg)
{
    GFTRK("SMBLoadHdr");

    smbmsg_t local_smsg, *smsgp;
    smsgp = smsg ? smsg : &local_smsg;
    uint32_t reln = Msgn->ToReln(__msg->msgno);
    if(reln == 0)
    {
        GFTRK(0);
        return false;
    }
    fseek(data->sid_fp, (reln - 1L) * sizeof(idxrec_t), SEEK_SET);
    if(not fread(&smsgp->idx, 1, sizeof(idxrec_t), data->sid_fp) or (smb_lockmsghdr(data, smsgp) != 0))
    {
        GFTRK(0);
        return false;
    }
    int rv = smb_getmsghdr(data, smsgp);
    smb_unlockmsghdr(data, smsgp);
    if(rv != 0)
    {
        GFTRK(0);
        return false;
    }
    __msg->link.to_set(smsgp->hdr.thread_orig);
    __msg->link.next_set(smsgp->hdr.thread_next);
    __msg->link.first_set(smsgp->hdr.thread_first);

    strxcpy(__msg->by, (char *)smsgp->from, 36);
    strxcpy(__msg->to, (char *)smsgp->to, 36);
    strxcpy(__msg->re, (char *)smsgp->subj, 72);

    if(smsgp->from_net.type == NET_FIDO)
    {
        __msg->orig.zone  = __msg->oorig.zone  = ((fidoaddr_t *)smsgp->from_net.addr)->zone;
        __msg->orig.net   = __msg->oorig.net   = ((fidoaddr_t *)smsgp->from_net.addr)->net;
        __msg->orig.node  = __msg->oorig.node  = ((fidoaddr_t *)smsgp->from_net.addr)->node;
        __msg->orig.point = __msg->oorig.point = ((fidoaddr_t *)smsgp->from_net.addr)->point;
    }
    else
    {
        __msg->orig.zone  = __msg->oorig.zone  = 0;
        __msg->orig.net   = __msg->oorig.net   = 0;
        __msg->orig.node  = __msg->oorig.node  = 0;
        __msg->orig.point = __msg->oorig.point = 0;
    }
    if(smsgp->to_net.type == NET_FIDO)
    {
        __msg->dest.zone  = __msg->odest.zone  = ((fidoaddr_t *)smsgp->to_net.addr)->zone;
        __msg->dest.net   = __msg->odest.net   = ((fidoaddr_t *)smsgp->to_net.addr)->net;
        __msg->dest.node  = __msg->odest.node  = ((fidoaddr_t *)smsgp->to_net.addr)->node;
        __msg->dest.point = __msg->odest.point = ((fidoaddr_t *)smsgp->to_net.addr)->point;
    }
    else
    {
        __msg->dest.zone  = __msg->odest.zone  = 0;
        __msg->dest.net   = __msg->odest.net   = 0;
        __msg->dest.node  = __msg->odest.node  = 0;
        __msg->dest.point = __msg->odest.point = 0;
    }

    // Transfer attributes
    __msg->attr.pvt(smsgp->hdr.attr & MSG_PRIVATE);
    __msg->attr.rcv(smsgp->hdr.attr & MSG_READ);
    __msg->attr.lok(smsgp->hdr.attr & MSG_LOCKED);
    __msg->attr.del(smsgp->hdr.attr & MSG_DELETE);
    __msg->attr.r_o(smsgp->hdr.attr & MSG_PERMANENT);
    __msg->attr.loc(smsgp->hdr.netattr & MSG_LOCAL);
    __msg->attr.cra(smsgp->hdr.netattr & MSG_CRASH);
    __msg->attr.imm(smsgp->hdr.netattr & MSG_IMMEDIATE);
    __msg->attr.dir(smsgp->hdr.netattr & MSG_DIRECT);
    __msg->attr.hld(smsgp->hdr.netattr & MSG_HOLD);
    __msg->attr.snt(smsgp->hdr.netattr & MSG_SENT);
    __msg->attr.trs(smsgp->hdr.netattr & MSG_INTRANSIT);
    __msg->attr.orp(smsgp->hdr.netattr & MSG_ORPHAN);
    __msg->attr.k_s(smsgp->hdr.netattr & MSG_KILLSENT);
    __msg->attr.a_s(smsgp->hdr.netattr & MSG_ARCHIVESENT);
    __msg->attr.zon(smsgp->hdr.netattr & MSG_GATE);
    __msg->attr.att(smsgp->hdr.auxattr & MSG_FILEATTACH);
    __msg->attr.frq(smsgp->hdr.auxattr & MSG_FILEREQUEST);
    __msg->attr.rrq(smsgp->hdr.auxattr & MSG_RECEIPTREQ);
    __msg->attr.cfm(smsgp->hdr.auxattr & MSG_CONFIRMREQ);
    __msg->attr.tfs(smsgp->hdr.auxattr & MSG_TRUNCFILE);

    __msg->written = smb_written_time_to_golded(smsgp->hdr);
    __msg->arrived = smb_imported_time_to_golded(smsgp->hdr);
    __msg->received = 0;

    if(not smsg)
        smb_freemsgmem(smsgp);
    GFTRK(0);
    return true;
}


//  ------------------------------------------------------------------

int SMBArea::load_msg(gmsg* msg)
{
    smbmsg_t smsg;
    uint16_t xlat;
    uint8_t  *inbuf;
    int32_t outlen;
    char buf[512];
    int i;
    bool lzh;
    bool tail = true;
    uint32_t l;
    uint32_t txt_len = 1;

    GFTRK("SMBLoadMsg");

    if(not load_hdr(msg, &smsg))
    {
        GFTRK(0);
        return false;
    }

    msg->txt = throw_strdup("");

    if(smsg.from_net.type == NET_INTERNET)
    {
        sprintf(buf, "\001From: %s (%s)\r", (char *)smsg.from_net.addr, smsg.from);
        outlen = strlen(buf);
        msg->txt = (char *)throw_realloc(msg->txt, txt_len+outlen);
        strcpy(msg->txt+txt_len-1, buf);
        txt_len+=outlen;
    }
    if(smsg.to_net.type == NET_INTERNET)
    {
        sprintf(buf, "\001To: %s (%s)\r", (char *)smsg.to_net.addr, smsg.to);
        outlen = strlen(buf);
        msg->txt = (char *)throw_realloc(msg->txt, txt_len+outlen);
        strcpy(msg->txt+txt_len-1, buf);
        txt_len+=outlen;
    }

    for(i = 0; i < smsg.total_hfields; i++)
        switch(smsg.hfield[i].type)
        {
        case RFC822MSGID:
            sprintf(buf, "\001Message-ID: %s\r", (char *)smsg.hfield_dat[i]);
            goto add;
        case FIDOMSGID:
            sprintf(buf, "\001MSGID: %s\r", (char *)smsg.hfield_dat[i]);
            goto add;
        case RFC822REPLYID:
            sprintf(buf, "\001In-Reply-To: %s\r", (char *)smsg.hfield_dat[i]);
            goto add;
        case FIDOREPLYID:
            sprintf(buf, "\001REPLY: %s\r", (char *)smsg.hfield_dat[i]);
            goto add;
        case FIDOPID:
            sprintf(buf, "\001PID: %s\r", (char *)smsg.hfield_dat[i]);
            strcpy(msg->pid, (char *)smsg.hfield_dat[i]);
            goto add;
        case FIDOAREA:
            sprintf(buf, "\001AREA: %s\r", (char *)smsg.hfield_dat[i]);
            goto add;
        case FIDOFLAGS:
            sprintf(buf, "\001FLAGS: %s\r", (char *)smsg.hfield_dat[i]);
            goto add;
        case FIDOCTRL:
            if(not strncmp((char *)smsg.hfield_dat[i], "Via ", 4) or not strncmp((char *)smsg.hfield_dat[i], "Recd ", 5))
                break;
        case RFC822HEADER:
            if(not strncmp((char *)smsg.hfield_dat[i], "Via:", 4))
                break;
            sprintf(buf, "\001%s\r", (char *)smsg.hfield_dat[i]);
            goto add;
        case FIDOCHARSET:
            sprintf(buf, "\001CHRS: %s\r", (char *)smsg.hfield_dat[i]);
add:
            outlen = strlen(buf);
            msg->txt = (char *)throw_realloc(msg->txt, txt_len+outlen);
            strcpy(msg->txt+txt_len-1, buf);
            txt_len+=outlen;
            break;
        default:
            break;
        }

    for(i = 0; i < smsg.hdr.total_dfields; i++)
        switch(smsg.dfield[i].type)
        {
        case TEXT_BODY:
            if(tail)
                tail = false;
            goto common;
        case TEXT_TAIL:
            if(not tail)
            {
                tail = true;
                msg->txt[txt_len-1] = '\r';
                txt_len++;
            }
common:
            fseek(data->sdt_fp, smsg.hdr.offset + smsg.dfield[i].offset, SEEK_SET);
            l = sizeof(xlat);
            fread(&xlat, sizeof(xlat), 1, data->sdt_fp);
            lzh = false;
            if(xlat == XLAT_LZH)
            {
                lzh = true;
                fread(&xlat, sizeof(xlat), 1, data->sdt_fp);
                l += sizeof(xlat);
            }
            if(xlat != XLAT_NONE) /* no other translations currently supported */
                continue;
            if(lzh)
            {
                inbuf = (uint8_t *)throw_xmalloc(smsg.dfield[i].length);
                fread(inbuf, smsg.dfield[i].length - l, 1, data->sdt_fp);
                outlen = *(int32_t *)inbuf;
                msg->txt = (char *)throw_realloc(msg->txt, txt_len+outlen);
                lzh_decode(inbuf, smsg.dfield[i].length - l, (uint8_t *)(msg->txt+txt_len-1));
                throw_xfree(inbuf);
            }
            else if(l < smsg.dfield[i].length)
            {
                outlen = smsg.dfield[i].length - l;
                msg->txt = (char *)throw_realloc(msg->txt, txt_len+outlen);
                fread(msg->txt+txt_len-1, smsg.dfield[i].length - l, 1, data->sdt_fp);
            }
            else
                outlen = 0;
            txt_len += outlen;
            msg->txt[txt_len-1] = NUL;
            break;
        }

    for(i = 0; i < smsg.total_hfields; i++)
        switch(smsg.hfield[i].type)
        {
        case FIDOPATH:
            sprintf(buf, "\r\001PATH: %s", (char *)smsg.hfield_dat[i]);
            goto add2;
        case FIDOSEENBY:
            sprintf(buf, "\rSEEN-BY: %s", (char *)smsg.hfield_dat[i]);
            goto add2;
        case FIDOCTRL:
            if(strncmp((char *)smsg.hfield_dat[i], "Via ", 4) and strncmp((char *)smsg.hfield_dat[i], "Recd ", 5))
                break;
            sprintf(buf, "\r\001%s", (char *)smsg.hfield_dat[i]);
add2:
            outlen = strlen(buf);
            msg->txt = (char *)throw_realloc(msg->txt, txt_len+outlen);
            strcpy(msg->txt+txt_len-1, buf);
            txt_len+=outlen;
            break;
        default:
            break;
        }


    smb_freemsgmem(&smsg);

    GFTRK(0);
    return true;
}

//  ------------------------------------------------------------------
//  Returns current timezone offset based on TZ environment variable.

int tzoffset_in_minutes()
{
    time32_t t1 = gtime(NULL);
    struct tm tp;
    ggmtime(&tp, &t1);
    tp.tm_isdst = -1;
    time32_t t2 = gmktime(&tp);
    int dt = (int)(t1 - t2);
    return dt / 60;
}

//  ------------------------------------------------------------------

void SMBArea::save_hdr(int mode, gmsg* msg)
{
    char ch;
    bool done, cr;
    uint32_t l, m, bodylen, taillen, crc;
    char *fbuf, *sbody, *stail;
    char buf[256];
    smbmsg_t smsg;
    fidoaddr_t destaddr;

    GFTRK("SMBSaveHdr");

    smb_getstatus(data);
    memset(&smsg, 0, sizeof(smbmsg_t));
    if(not (mode & GMSG_NEW))
    {
        uint32_t reln = Msgn->ToReln(msg->msgno);
        if(reln == 0)
        {
            GFTRK(0);
            return;
        }
        fseek(data->sid_fp, (reln - 1L) * sizeof(idxrec_t), SEEK_SET);
        if(not fread(&smsg.idx, 1, sizeof(idxrec_t), data->sid_fp) or (smb_lockmsghdr(data, &smsg) != 0))
        {
            GFTRK(0);
            return;
        }
        int rv = smb_getmsghdr(data, &smsg);
        smb_unlockmsghdr(data, &smsg);
        if(rv != 0)
        {
            GFTRK(0);
            return;
        }
        smsg.hdr.attr = 0;
        smsg.hdr.netattr = 0;
        smsg.hdr.auxattr = 0;
    }
    else
    {
        memcpy(smsg.hdr.id, "SHD\x1a", 4);
        smsg.hdr.version = smb_ver();
        struct tm tp;
        ggmtime(&tp, &msg->written);
        tp.tm_isdst = -1;
        smsg.hdr.when_written.time = gmktime(&tp);
        smsg.hdr.when_written.zone = tzoffset_in_minutes();
    }
    smsg.hdr.when_imported.time = gtime(NULL);
    smsg.hdr.when_imported.zone = tzoffset_in_minutes();

    // Transfer attributes
    if(msg->attr.pvt()) smsg.hdr.attr |= MSG_PRIVATE;
    if(msg->attr.rcv()) smsg.hdr.attr |= MSG_READ;
    if(msg->attr.lok()) smsg.hdr.attr |= MSG_LOCKED;
    if(msg->attr.del()) smsg.hdr.attr |= MSG_DELETE;
    if(msg->attr.r_o()) smsg.hdr.attr |= MSG_PERMANENT;
    if(msg->attr.loc()) smsg.hdr.netattr |= MSG_LOCAL;
    if(msg->attr.cra()) smsg.hdr.netattr |= MSG_CRASH;
    if(msg->attr.imm()) smsg.hdr.netattr |= MSG_IMMEDIATE;
    if(msg->attr.dir()) smsg.hdr.netattr |= MSG_DIRECT;
    if(msg->attr.hld()) smsg.hdr.netattr |= MSG_HOLD;
    if(msg->attr.snt()) smsg.hdr.netattr |= MSG_SENT;
    if(msg->attr.trs()) smsg.hdr.netattr |= MSG_INTRANSIT;
    if(msg->attr.orp()) smsg.hdr.netattr |= MSG_ORPHAN;
    if(msg->attr.k_s()) smsg.hdr.netattr |= MSG_KILLSENT;
    if(msg->attr.a_s()) smsg.hdr.netattr |= MSG_ARCHIVESENT;
    if(msg->attr.zon()) smsg.hdr.netattr |= MSG_GATE;
    if(msg->attr.att()) smsg.hdr.auxattr |= MSG_FILEATTACH;
    if(msg->attr.frq()) smsg.hdr.auxattr |= MSG_FILEREQUEST;
    if(msg->attr.rrq()) smsg.hdr.auxattr |= MSG_RECEIPTREQ;
    if(msg->attr.cfm()) smsg.hdr.auxattr |= MSG_CONFIRMREQ;
    if(msg->attr.tfs()) smsg.hdr.auxattr |= MSG_TRUNCFILE;

    smsg.hdr.thread_orig = msg->link.to();
    smsg.hdr.thread_next = msg->link.next();
    smsg.hdr.thread_first = msg->link.first();

    if((mode & GMSG_UPDATE) and not (mode & GMSG_TXT))
    {
        if(mode & GMSG_NEW)
            smb_addmsghdr(data, &smsg, data->status.attr & SMB_HYPERALLOC);
        else
        {
            smsg.idx.time = smsg.hdr.when_imported.time;
            smsg.idx.attr = smsg.hdr.attr;
            smsg.offset = Msgn->ToReln(msg->msgno) - 1;
            smb_putmsg(data, &smsg);
        }
        smb_freemsgmem(&smsg);
        GFTRK(0);
        return;
    }

    if(not (mode & GMSG_NEW))
    {
        if(not (data->status.attr & SMB_HYPERALLOC))
        {
            if(smb_open_da(data) == 0)
            {
                if(smb_open_ha(data) == 0)
                {
                    smb_freemsg(data, &smsg);
                    smb_close_ha(data);
                }
                smb_close_da(data);
            }
        }
        else
        {
            smb_freemsg(data, &smsg);
        }
        smb_freemsgmem(&smsg);
        smsg.dfield = NULL;
        smsg.hdr.total_dfields = 0;
        smsg.total_hfields = 0;
        smsg.hfield = NULL;
        smsg.hfield_dat = NULL;
    }

    uint16_t net = NET_FIDO;
    destaddr.zone = msg->dest.zone;
    destaddr.net = msg->dest.net;
    destaddr.node = msg->dest.node;
    destaddr.point = msg->dest.point;
    smb_hfield(&smsg, RECIPIENTNETTYPE, sizeof(uint16_t), &net);
    smb_hfield(&smsg, RECIPIENTNETADDR, sizeof(fidoaddr_t), &destaddr);

    smb_hfield(&smsg, SENDER, strlen(msg->by), msg->by);
    strcpy(buf, msg->by);
    strlwr(buf);
    smsg.idx.from = strCrc16c(buf);

    smb_hfield(&smsg, RECIPIENT, strlen(msg->to), msg->to);
    strcpy(buf, msg->to);
    strlwr(buf);
    smsg.idx.to = strCrc16c(buf);

    smb_hfield(&smsg, SUBJECT, strlen(msg->re), msg->re);
    strcpy(buf, msg->re);
    strlwr(buf);
    smsg.idx.subj = strCrc16c(buf);

    // calculate maximum possible size of sbody/stail
    for(l = 0, fbuf = msg->txt; *fbuf != NUL; l++, fbuf++)
        if(*fbuf == CR)
            ++l;

    l += 2; // reserve 2 bytes for the encoding type

    fbuf = msg->txt;
    sbody = (char *)throw_malloc(SDT_BLOCK_LEN*smb_datblocks(l))+2;
    stail = (char *)throw_malloc(SDT_BLOCK_LEN*smb_datblocks(l))+2;

    for(l = bodylen = taillen = done = 0, cr = true; (ch = fbuf[l]) != NUL; l++)
    {
        if(ch == CTRL_A and cr)
        {
            ++l;
            if(not strncmp(fbuf+l, "MSGID:", 6))
            {
                l += 6;
                while(fbuf[l] and fbuf[l] == ' ') l++;
                m = l;
                while(fbuf[m] and not iscntrl(fbuf[m])) m++;
                if(m != l)
                    smb_hfield(&smsg, FIDOMSGID, m-l, fbuf+l);
            }
            else if(not strncmp(fbuf+l, "REPLY:", 6))
            {
                l += 6;
                while(fbuf[l] and fbuf[l] == ' ') l++;
                m = l;
                while(fbuf[m] and not iscntrl(fbuf[m])) m++;
                if(m != l)
                    smb_hfield(&smsg, FIDOREPLYID, m-l, fbuf+l);
            }
            else if(not strncmp(fbuf+l, "FLAGS:", 6))
            {
                l += 6;
                while(fbuf[l] and fbuf[l] == ' ') l++;
                m = l;
                while(fbuf[m] and not iscntrl(fbuf[m])) m++;
                if(m != l)
                    smb_hfield(&smsg, FIDOFLAGS, m-l, fbuf+l);
            }
            else if(not strncmp(fbuf+l, "PATH:", 5))
            {
                l += 5;
                while(fbuf[l] and fbuf[l] == ' ') l++;
                m = l;
                while(fbuf[m] and not iscntrl(fbuf[m])) m++;
                if(m != l)
                    smb_hfield(&smsg, FIDOPATH, m-l, fbuf+l);
            }
            else if(not strncmp(fbuf+l, "PID:", 4))
            {
                l += 4;
                while(fbuf[l] and fbuf[l] == ' ') l++;
                m = l;
                while(fbuf[m] and not iscntrl(fbuf[m])) m++;
                if(m != l)
                    smb_hfield(&smsg, FIDOPID, m-l, fbuf+l);
            }
            else if(not strncmp(fbuf+l, "CHRS:", 5))
            {
                l += 5;
                while(fbuf[l] and fbuf[l] == ' ') l++;
                m = l;
                while(fbuf[m] and not iscntrl(fbuf[m])) m++;
                if(m != l)
                    smb_hfield(&smsg, FIDOCHARSET, m-l, fbuf+l);
            }
            else
            {
                if(strncmp(fbuf+l, "TOPT ", 5) and strncmp(fbuf+l, "FMPT ", 5) and
                        strncmp(fbuf+l, "INTL ", 5))
                {
                    while(fbuf[l] and fbuf[l] == ' ') l++;
                    m = l;
                    while(fbuf[m] and not iscntrl(fbuf[m])) m++;
                    if(m != l)
                        smb_hfield(&smsg, FIDOCTRL, m-l, fbuf+l);
                }
            }
            while(fbuf[l] and fbuf[l]!=CR) l++;
            continue;
        }
        if(ch != LF and ch != SOFTCR)
        {
            if(cr)
            {
                if(not done and not strncmp(fbuf+l, "--- ", 4) or not strncmp(fbuf+l, "---\r", 4))
                    done = true; // tear line and down go into tail
                else if(done and not strncmp(fbuf+l, "SEEN-BY:", 8))
                {
                    l += 8;
                    while(fbuf[l] and fbuf[l] == ' ') l++;
                    m = l;
                    while(fbuf[m] and not iscntrl(fbuf[m])) m++;
                    if(m != l)
                        smb_hfield(&smsg, FIDOSEENBY, m-l, fbuf+l);
                    while(fbuf[l] and fbuf[l]!=CR) l++;
                    continue;
                }
                else if(not done and not strncmp(fbuf+l, " * Origin: ", 11))
                    done = true;
            }
            if(done)
                stail[taillen++] = ch;
            else
                sbody[bodylen++] = ch;
            if(ch == CR)
            {
                cr = true;
                if(done)
                    stail[taillen++] = LF;
                else
                    sbody[bodylen++] = LF;
            }
            else
                cr = false;
        }
    }
    if(bodylen>=2 && sbody[bodylen-2]==CR && sbody[bodylen-1]==LF)
        bodylen-=2; // remove last CRLF if present

    crc = ~memCrc32(sbody, bodylen, false, CRC32_MASK_CCITT);
    smb_addcrc(data, crc);

    while(taillen and (iscntrl(stail[taillen-1]) or isspace(stail[taillen-1])))
        taillen--;

    l = bodylen+2;
    if(taillen)
        l += (taillen+2);

    if(not (data->status.attr & SMB_HYPERALLOC))
    {
        if(smb_open_da(data) == 0)
        {
            smsg.hdr.offset = smb_allocdat(data, l, 1);
            smb_close_da(data);
        }
        else
            smsg.hdr.offset = (uint32_t)-1L;
    }
    else
    {
        smsg.hdr.offset = smb_hallocdat(data);
    }

    if(smsg.hdr.offset != (uint32_t)-1L)
    {
        fseek(data->sdt_fp, smsg.hdr.offset, SEEK_SET);
        *(uint16_t *)(sbody-2) = XLAT_NONE;
        l = ftell(data->sdt_fp);
        fwrite(sbody-2, SDT_BLOCK_LEN, smb_datblocks(bodylen), data->sdt_fp);
        if(taillen)
        {
            fseek(data->sdt_fp, l+bodylen+2, SEEK_SET);
            *(uint16_t *)(stail-2) = XLAT_NONE;
            fwrite(stail-2, SDT_BLOCK_LEN, smb_datblocks(taillen), data->sdt_fp);
        }
        fflush(data->sdt_fp);
        smb_dfield(&smsg, TEXT_BODY, bodylen+2);
        if(taillen)
            smb_dfield(&smsg, TEXT_TAIL, taillen+2);

        int storage = data->status.attr & SMB_HYPERALLOC;

        if(mode & GMSG_NEW)
        {
            smb_addmsghdr(data, &smsg, storage);
            Msgn->Append(smsg.hdr.number);
        }
        else
        {
            // Changing message... It is always bad idea since it is usually
            // undescribed and not supported by the API
            int32_t l;

            if(data->locked or (smb_locksmbhdr(data) == 0))
            {
                if(smb_getstatus(data) == 0)
                {
                    if((storage == SMB_HYPERALLOC) or (smb_open_ha(data) == 0))
                    {
                        smsg.hdr.length = smb_getmsghdrlen(&smsg);
                        if(storage == SMB_HYPERALLOC)
                            l = smb_hallochdr(data);
                        else if(storage == SMB_FASTALLOC)
                            l = smb_fallochdr(data, smsg.hdr.length);
                        else
                            l = smb_allochdr(data, smsg.hdr.length);
                        if(storage != SMB_HYPERALLOC)
                            smb_close_ha(data);
                        if(l!=-1L)
                        {
                            smsg.idx.offset = data->status.header_offset+l;
                            smsg.idx.time = smsg.hdr.when_imported.time;
                            smsg.idx.attr = smsg.hdr.attr;
                            smsg.offset = Msgn->ToReln(msg->msgno) - 1;
                            smb_putmsg(data, &smsg);
                        }
                    }
                }
            }
            smb_unlocksmbhdr(data);
        }
    }
    throw_xfree(sbody-2);
    throw_xfree(stail-2);
    smb_freemsgmem(&smsg);

    GFTRK(0);
}


//  ------------------------------------------------------------------

void SMBArea::save_msg(int mode, gmsg* msg)
{
    GFTRK("SMBSaveMsg");

    save_hdr(mode | GMSG_HDRTXT, msg);

    GFTRK(0);
}


//  ------------------------------------------------------------------

void SMBArea::del_msg(gmsg* msg)
{
    GFTRK("SMBDelMsg");

    smbmsg_t smsg;
    uint32_t reln = Msgn->ToReln(msg->msgno);
    if(reln == 0)
    {
        GFTRK(0);
        return;
    }
    fseek(data->sid_fp, (reln - 1L) * sizeof(idxrec_t), SEEK_SET);
    if(not fread(&smsg.idx, 1, sizeof(idxrec_t), data->sid_fp) or (smb_lockmsghdr(data, &smsg) != 0))
    {
        GFTRK(0);
        return;
    }
    if(smb_getmsghdr(data, &smsg) == 0)
    {
        smsg.hdr.attr |= MSG_DELETE;
        smsg.idx.time = smsg.hdr.when_imported.time;
        smsg.idx.attr = smsg.hdr.attr;
        smsg.offset = reln - 1L;
        int rv = smb_putmsg(data, &smsg);
        smb_unlockmsghdr(data, &smsg);
        if(rv == 0)
            msg->attr.del1();
    }
    else
        smb_unlockmsghdr(data, &smsg);
    smb_freemsgmem(&smsg);

    GFTRK(0);
}


//  ------------------------------------------------------------------

void SMBArea::new_msgno(gmsg* msg)
{
    int res = smb_getstatus(data);
    smb_unlocksmbhdr(data);
    msg->msgno = (res == 0) ? data->status.last_msg+1 : 0;
}


//  ------------------------------------------------------------------

char* SMBArea::user_lookup(char* lookfor)
{
    NW(lookfor);
    return NULL;
}


//  ------------------------------------------------------------------

int SMBArea::renumber()
{
    return false;
}


//  ------------------------------------------------------------------

void SMBArea::update_timesread(gmsg*)
{
}


//  ------------------------------------------------------------------

static char *binstr(char *buf, uint16_t length)
{
    static char str[128];
    char tmp[128];
    int i;

    str[0] = 0;
    for(i = 0; i < length; i++)
        if(buf[i] and not isprint(buf[i]))
            break;
    if(i == length)                /* not binary */
        return buf;

    if(length > 42)
        length = 42;

    for(i = 0; i < length; i++)
    {
        sprintf(tmp, "%02X ", buf[i]);
        strcat(str, tmp);
    }
    return str;
}


//  ------------------------------------------------------------------

static char *faddrtoa(fidoaddr_t addr)
{
    static char str[25];
    char point[25];

    sprintf(str, "%u:%u/%u", addr.zone, addr.net, addr.node);
    if (addr.point)
    {
        sprintf(point, ".%u", addr.point);
        strcat(str, point);
    }
    return str;
}


//  ------------------------------------------------------------------

Line* SMBArea::make_dump_msg(Line*& lin, gmsg* msg, char* lng_head)
{

    GFTRK("SMBMakeDump");

    Line* line = lin =
                     AddLine (NULL, "Hexdump of Synchronet-style message header and text");
    AddLineF(line, "------------------------------------------------------------------------------");
    line = AddLine(line, "");

    smbmsg_t smsg;
    char buf[512];
    int i;

    GFTRK("SMBLoadMsg");

    if(not load_hdr(msg, &smsg))
    {
        line = AddLine(line, "Error loading header");
        GFTRK(0);
        return line;
    }

    line = AddLineF(line, "Subject           : %s", smsg.subj);
    line = AddLineF(line, "From              : %s", smsg.from);
    line = AddLineF(line, "To                : %s", smsg.to);
    line = AddLineF(line, "Type              : %04Xh", smsg.hdr.type);
    line = AddLineF(line, "Version           : %04Xh", smsg.hdr.version);
    line = AddLineF(line, "Length            : %u", smsg.hdr.length);
    line = AddLineF(line, "Attr              : %04Xh", smsg.hdr.attr);
    line = AddLineF(line, "AUXAttr           : %04Xh", smsg.hdr.auxattr);
    line = AddLineF(line, "NetAttr           : %04Xh", smsg.hdr.netattr);
    time32_t written = smb_written_time_to_golded(smsg.hdr);
    gctime(buf, ARRAYSIZE(buf), &written);
    line = AddLineF(line, "Written           : %s", buf);
    time32_t imported = smb_imported_time_to_golded(smsg.hdr);
    gctime(buf, ARRAYSIZE(buf), &imported);
    line = AddLineF(line, "Imported          : %s", buf);
    line = AddLineF(line, "Number            : %d (%d)", smsg.hdr.number, (int32_t)(ftell(data->sid_fp)/sizeof(idxrec_t)));
    line = AddLineF(line, "Thread orig       : %d", smsg.hdr.thread_orig);
    line = AddLineF(line, "Thread next       : %d", smsg.hdr.thread_next);
    line = AddLineF(line, "Thread first      : %d", smsg.hdr.thread_first);
    line = AddLineF(line, "Reserved          : %s", HexDump16(buf, (const char*)smsg.hdr.reserved, 16, "%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X"));
    line = AddLineF(line, "Offset            : %06Xh", smsg.hdr.offset);
    line = AddLineF(line, "Total dfields     : %u", smsg.hdr.total_dfields);

    if(smsg.from_net.type)
    {
        line = AddLineF(line, "From net type     : %02Xh", smsg.from_net.type);
        line = AddLineF(line, "From net addr     : %s", (char *)(smsg.from_net.type == NET_FIDO ? faddrtoa(*(fidoaddr_t *)smsg.from_net.addr) : smsg.from_net.addr));
    }

    if(smsg.to_net.type)
    {
        line = AddLineF(line, "To net type       : %02Xh", smsg.to_net.type);
        line = AddLineF(line, "To net addr       : %s", (char *)(smsg.to_net.type == NET_FIDO ? faddrtoa(*(fidoaddr_t *)smsg.to_net.addr) : smsg.to_net.addr));
    }

    if(smsg.replyto_net.type)
    {
        line = AddLineF(line, "Replyto net type  : %02Xh", smsg.replyto_net.type);
        line = AddLineF(line, "Replyto net addr  : %s", (char *)(smsg.replyto_net.type == NET_FIDO ? faddrtoa(*(fidoaddr_t *)smsg.replyto_net.addr) : smsg.replyto_net.addr));
    }

    line = AddLineF(line, "From agent        : %02Xh", smsg.from_agent);
    line = AddLineF(line, "To agent          : %02Xh", smsg.to_agent);
    line = AddLineF(line, "Replyto agent     : %02Xh", smsg.replyto_agent);

    line = AddLine(line, "");
    AddLineF(line, "dfields:");
    line = AddLine(line, "");

    for (i = 0; i < smsg.hdr.total_dfields; i++)
    {
        line = AddLineF(line, "dfield[%02u].type   : %02X", i, smsg.dfield[i].type);
        line = AddLineF(line, "dfield[%02u].offset : %u", i, smsg.dfield[i].offset);
        line = AddLineF(line, "dfield[%02u].length : %u", i, smsg.dfield[i].length);
    }

    line = AddLine(line, "");
    AddLineF(line, "hfields:");
    line = AddLine(line, "");

    for (i = 0; i < smsg.total_hfields; i++)
    {
        line = AddLineF(line, "hfield[%02u].type   : %02X", i, smsg.hfield[i].type);
        line = AddLineF(line, "hfield[%02u].length : %d", i, smsg.hfield[i].length);
        line = AddLineF(line, "hfield_dat[%02u]    : %s", i, binstr((char *)smsg.hfield_dat[i], smsg.hfield[i].length));
    }

    line = AddLine(line, "");
    AddLine(line, lng_head);
    line = AddLine(line, "");

    int _count = 0;
    char* _ptr = (char*)&smsg.hdr;
    while(_count < sizeof(msghdr_t))
    {
        sprintf(buf, "%04X   ", _count);
        HexDump16(buf+7, _ptr, 16, HEX_DUMP2);
        line = AddLine(line, buf);
        _count += 16;
        _ptr += 16;
    }
    sprintf(buf, "%04X   ", _count);
    HexDump16(buf+7, _ptr, sizeof(msghdr_t)%16, HEX_DUMP2);
    line = AddLine(line, buf);

    uint16_t xlat;
    uint8_t  *inbuf;
    int32_t  outlen;
    bool lzh;
    bool tail = true;
    uint32_t l;
    uint32_t txt_len = 1;

    msg->txt = throw_strdup("");

    for(i = 0; i < smsg.hdr.total_dfields; i++)
        switch(smsg.dfield[i].type)
        {
        case TEXT_BODY:
            if(tail)
                tail = false;
            goto common;
        case TEXT_TAIL:
            if(not tail)
            {
                tail = true;
                msg->txt[txt_len-1] = '\r';
                txt_len++;
            }
common:
            fseek(data->sdt_fp, smsg.hdr.offset + smsg.dfield[i].offset, SEEK_SET);
            l = sizeof(xlat);
            fread(&xlat, sizeof(xlat), 1, data->sdt_fp);
            lzh = false;
            if(xlat == XLAT_LZH)
            {
                lzh = true;
                fread(&xlat, sizeof(xlat), 1, data->sdt_fp);
                l += sizeof(xlat);
            }
            if(xlat != XLAT_NONE) /* no other translations currently supported */
                continue;
            if(lzh)
            {
                inbuf = (uint8_t *)throw_xmalloc(smsg.dfield[i].length);
                fread(inbuf, smsg.dfield[i].length - l, 1, data->sdt_fp);
                outlen = *(int32_t *)inbuf;
                msg->txt = (char *)throw_realloc(msg->txt, txt_len+outlen);
                lzh_decode(inbuf, smsg.dfield[i].length - l, (uint8_t *)(msg->txt+txt_len-1));
                throw_xfree(inbuf);
            }
            else if(l < smsg.dfield[i].length)
            {
                outlen = smsg.dfield[i].length - l;
                msg->txt = (char *)throw_realloc(msg->txt, txt_len+outlen);
                fread(msg->txt+txt_len-1, smsg.dfield[i].length - l, 1, data->sdt_fp);
            }
            else
                outlen = 0;
            txt_len+=outlen;
            msg->txt[txt_len-1] = NUL;
            break;
        }

    smb_freemsgmem(&smsg);

    GFTRK(0);

    return line;
}


//  ------------------------------------------------------------------
