/**********
 * Copyright (c) 2004 Greg Parker.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY GREG PARKER ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **********/

#include "includes.h"
#include "publickeys.h"
#include "recordlist.h"
#include "rsrc/rsrc.h"
#include "ssh/openssh/buffer.h"
#include "ssh/openssh/bufaux.h"
#include "ssh/openssh/key.h"
#include "ssh/keyimport.h"


#define PublicKeyDBName "pssh Public Keys"
#define PublicKeyDBType 'PKey'
static DmOpenRef PublicKeyDB = 0;
static RecordList *PublicKeyList = NULL;

/*
  public key record format:
  1 byte encrypted
  2 bytes length
  n bytes comment\0
  4 bytes length
  n bytes public keyblob
  4 bytes length
  n bytes private key\0 (base64 text, maybe with encrypted body)
*/

static Boolean ReadPublicKeyRecord(uint8_t *recordP, Boolean *encrypted, char **comment, uint8_t **pubkey, uint16_t *pubkeylen, uint8_t **privkey, uint16_t *privkeylen) PUBLICKEYS_SEGMENT;
static queue_t *PublicKeysWithEncryption(Boolean wantEncrypted) PUBLICKEYS_SEGMENT;
static void DrawPublicKeyRecord(MemPtr recordP, UInt16 index, RectanglePtr bounds) PUBLICKEYS_SEGMENT;
static Boolean SavePublicKey(Boolean encrypted, char *comment, uint16_t commentlen, char *pubkey, uint16_t pubkeylen, char *privkey, uint16_t privkeylen) PUBLICKEYS_SEGMENT;
static queue_t *PublicKeysWithEncryption(Boolean encrypted) PUBLICKEYS_SEGMENT;


extern DmOpenRef OpenDB(UInt32 type, char *name, Boolean resDB, Boolean create);

Boolean PublicKeysInit(void)
{
    PublicKeyDB = OpenDB(PublicKeyDBType, PublicKeyDBName, false, true);
    if (!PublicKeyDB) return false;

    PublicKeyList = 
        RecordListNew(PublicKeyDB, PublicKeysFormID, PublicKeysFormKeyTableID, 
                      PublicKeysFormKeyScrollbarID, DrawPublicKeyRecord);
    if (!PublicKeyList) return false;

    return true;
}



void PublicKeysFree(void)
{
    RecordListFree(PublicKeyList);
    DmCloseDatabase(PublicKeyDB);
}


void PublicKeysUpdate(void)
{
    return RecordListUpdate(PublicKeyList);
}


Boolean PublicKeysHandleEvent(EventPtr event)
{
    return RecordListHandleEvent(PublicKeyList, event);
}


UInt16 PublicKeysSelectedIndex(void)
{
    return RecordListSelectedIndex(PublicKeyList);
}


void PublicKeysDeleteSelectedRecord(void)
{
    return RecordListDeleteSelectedRecord(PublicKeyList);
}


static Boolean ReadPublicKeyRecord(uint8_t *recordP, Boolean *encrypted, 
                                   char **comment, 
                                   uint8_t **pubkey, uint16_t *pubkeylen, 
                                   uint8_t **privkey, uint16_t *privkeylen)
{
#define CHECK_SPACE(n) do { if (p+(n)>end) goto bad; } while (0)

    uint8_t c;
    uint16_t len;
    uint32_t len32;
    uint8_t *p = recordP;
    uint8_t *end = p + MemPtrSize(p);
    
    CHECK_SPACE(1);
    c = *(uint8_t *)p;
    if (encrypted) *encrypted = (c == 1);
    p++;

    CHECK_SPACE(2);
    len = *(uint16_t *)p;
    p += 2;
    CHECK_SPACE(len);
    if (comment) *comment = p;
    p += len;

    CHECK_SPACE(4);
    len32 = *(uint32_t *)p;
    p += 4;
    CHECK_SPACE(len32);
    if (len32 == 0) goto bad;
    if (pubkey) *pubkey = p;
    if (pubkeylen) *pubkeylen = len32;
    p += len32;

    CHECK_SPACE(4);
    len32 = *(uint32_t *)p;
    p += 4;
    CHECK_SPACE(len32);
    if (len32 == 0) goto bad;
    if (privkey) *privkey = p;
    if (privkeylen) *privkeylen = len32;
    p += len32;

    // allow trailing data for forward compatibility

    return true;

 bad:
    return false;
}


MemHandle PublicKeysQuerySelectedRecord(Boolean *encrypted, char **comment, 
                                        uint8_t **pubkey, uint16_t *pubkeylen, 
                                        uint8_t **privkey,uint16_t *privkeylen)
{
    return PublicKeysQueryIndexedRecord(RecordListSelectedIndex(PublicKeyList),
                                        encrypted, comment, 
                                        pubkey, pubkeylen, 
                                        privkey, privkeylen);
}


MemHandle PublicKeysQueryIndexedRecord(UInt16 index, 
                                       Boolean *encrypted, char **comment, 
                                       uint8_t **pubkey, uint16_t *pubkeylen, 
                                       uint8_t **privkey, uint16_t *privkeylen)
{
    MemHandle recordH;
    MemPtr recordP;
    Boolean ok;

    recordH = RecordListQueryIndexedRecord(PublicKeyList, index);
    if (!recordH) return NULL;

    recordP = MemHandleLock(recordH);
    ok = ReadPublicKeyRecord(recordP, encrypted, comment, 
                             pubkey, pubkeylen, 
                             privkey, privkeylen);
    
    if (!ok) {
        MemHandleUnlock(recordH);
        return NULL;
    } else {
        return recordH;
    }
}


Key *PublicKeyForRecord(MemPtr recordP)
{
    uint8_t *pubkeyblob;
    uint16_t pubkeylen;

    Boolean ok = ReadPublicKeyRecord(recordP, NULL, NULL, 
                                     &pubkeyblob, &pubkeylen, 
                                     NULL, NULL);

    if (!ok) return NULL;
    else return key_from_blob(pubkeyblob, pubkeylen);
}


Key *PrivateKeyForRecord(MemPtr recordP, char *passphrase)
{
    uint8_t *privkeytext;
    uint16_t privkeylen;

    Boolean ok = ReadPublicKeyRecord(recordP, NULL, NULL, 
                                     NULL, NULL, 
                                     &privkeytext, &privkeylen);

    if (!ok) return NULL;
    return openssh_read(privkeytext, privkeylen, passphrase ? passphrase : "");
}


static queue_t *PublicKeysWithEncryption(Boolean wantEncrypted)
{
    MemHandle h;
    MemPtr p;
    UInt16 i;
    Boolean encrypted;
    UInt16 count = RecordListCount(PublicKeyList);
    queue_t *result = queue_new();
    
    for (i = 0; i < count; i++) {
        h = RecordListQueryIndexedRecord(PublicKeyList, i);
        if (h  &&  MemHandleSize(h) >= 1) {
            p = MemHandleLock(h);
            encrypted = (*(uint8_t *)p) == 1;
            if (encrypted == wantEncrypted) {
                queue_enqueue(result, h);
            }
            MemHandleUnlock(h);
        }
    }
    
    return result;
}

queue_t *PhraselessPublicKeys(void)
{
    return PublicKeysWithEncryption(false);
}

queue_t *PhrasefulPublicKeys(void)
{
    return PublicKeysWithEncryption(true);
}


static void DrawPublicKeyRecord(MemPtr recordP, UInt16 index, 
                                RectanglePtr bounds)
{
    Boolean encrypted;
    char *comment;

    if (ReadPublicKeyRecord(recordP, &encrypted, &comment, 
                            NULL, NULL, NULL, NULL))
    {
        // fixme draw something for encrypted keys
        int len;
        int x = bounds->topLeft.x + 1;
        int y = bounds->topLeft.y;

        len = StrLen(comment);
        WinDrawTruncChars(comment, len, x, y, 
                          bounds->topLeft.x + bounds->extent.x - x - 1);
        x += FntCharsWidth(comment, len);
    }
}



// comment MAY NOT be nul-terminated
// commentlen MUST NOT include nul char
// pubkey MAY NOT be nul-terminated
// pubkeylen MUST NOT include nul char
// privkey MAY NOT be nul-terminated
// privkeylen MUST NOT include nul char
// NOTHING overlaps the selected record, if any
static Boolean SavePublicKey(Boolean encrypted, 
                             char *comment, uint16_t commentlen, 
                             char *pubkey, uint16_t pubkeylen, 
                             char *privkey, uint16_t privkeylen)
{
    Boolean result = false;
    Buffer b;
    MemHandle recordH;
    MemPtr recordP;

    buffer_init(&b);

    // encrypted
    buffer_put_char(&b, encrypted ? 1 : 0);

    // comment\0
    buffer_put_short(&b, commentlen + 1);
    buffer_append(&b, comment, commentlen);
    buffer_put_char(&b, '\0');

    // public key (binary)
    buffer_put_int(&b, pubkeylen);
    buffer_append(&b, pubkey, pubkeylen);

    // private key\0 (text)
    buffer_put_int(&b, privkeylen + 1);
    buffer_append(&b, privkey, privkeylen);
    buffer_put_char(&b, '\0');


    // write to record (might be new)
    recordH = RecordListGetSelectedRecord(PublicKeyList, buffer_len(&b));
    if (!recordH) goto done;

    recordP = MemHandleLock(recordH);
    DmWrite(recordP, 0, buffer_ptr(&b), buffer_len(&b));
    MemHandleUnlock(recordH);
    RecordListReleaseRecord(PublicKeyList, recordH, true);
    result = true;

 done: 
    buffer_free(&b);
    return result;
}


// privkey MAY NOT be nul-terminated
// privkeylen MUST NOT include nul char
// comment MAY NOT be nul-terminated
// commentlen MUST NOT include nul char
Boolean SaveImportedPublicKey(Boolean encrypted, 
                              char *comment, uint16_t commentlen,
                              Key *pubkey, 
                              char *privkey, uint16_t privkeylen)
{
    Boolean ok = false;
    uint8_t *pubkeybytes;
    uint16_t pubkeylen;

    key_to_blob(pubkey, &pubkeybytes, &pubkeylen);

    RecordListClearSelection(PublicKeyList);
    ok = SavePublicKey(encrypted, comment, commentlen, pubkeybytes, pubkeylen, 
                       privkey, privkeylen);

    xfree(pubkeybytes);
    return ok;
}


// Mirrors PublicKeysQueryIndexedRecord()
// comment MUST be nul-terminated
// privkey MUST be nul-terminated
// privkeylen MUST include nul char
// NOTHING overlaps the selected record, if any
Boolean WritePublicKeyRecord(Boolean encrypted, 
                             char *comment, 
                             char *pubkey, uint16_t pubkeylen, 
                             char *privkey, uint16_t privkeylen)
{
    return SavePublicKey(encrypted, 
                         comment, strlen(comment), // no nul char
                         pubkey, pubkeylen, 
                         privkey, privkeylen - 1); // ignore nul char
}


char *NameForPublicKey(MemHandle h)
{
    char *comment;
    char *result = NULL;
    uint8_t *p = MemHandleLock(h);

    if (ReadPublicKeyRecord(p, NULL, &comment, NULL, NULL, NULL, NULL)) {
        result = arena_strdup(comment);
    }

    MemHandleUnlock(h);

    if (!result) return arena_strdup("<error>");
    else return result;
}
