/**********
 * Copyright (c) 2003-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 "data/hostkeys.h"
#include "openssh/key.h"
#include "formutils.h"
#include "rsrc/rsrc.h"

#include "keyfile.h"


typedef enum {
    trustNever = 0,
    trustOnce,
    trustAlways
} key_trust_t;

#define warnstart "The credentials offered by the ser- ver "
#define warnend   ". This connection may be insecure."
// fixme write security problem info text
// #define warnend   ". This connection may be insecure. Tap (i) for details "
//                   "about this possible security problem."


static UInt16 WarningDialog(UInt16 formID, char *content, char *address) SSH_SEGMENT;
static key_trust_t ask_trust_for_new(const char *hostname, const char *hostaddr) SSH_SEGMENT;
static key_trust_t ask_trust_for_change(const char *hostname, const char *hostaddr) SSH_SEGMENT;


static UInt16 WarningDialog(UInt16 formID, char *content, char *address)
{
    UInt16 result;
    FormPtr frm;
    FieldPtr fld;

    frm = FrmInitForm(formID);
    fld = FrmGetObjectPtr(frm, FrmGetObjectIndex(frm, HostKeyWarningFormContentFieldID));
    PrvSetFieldToValue(fld, content);
    fld = FrmGetObjectPtr(frm, FrmGetObjectIndex(frm, HostKeyWarningFormAddressFieldID));
    PrvSetFieldToValue(fld, address);
    result = FrmDoDialog(frm);
    FrmDeleteForm(frm);
    return result;
}


static key_trust_t ask_trust_for_new(const char *hostname, const char *hostaddr)
{
    char address[16];
    char content[241];
    UInt16 buttonID;

    strcpy(address, hostaddr);
    
    strcpy(content, warnstart);
    strcat(content, "are UNKNOWN");
    strcat(content, warnend);

    buttonID = WarningDialog(HostKeyWarningFormID, content, address);

    switch (buttonID) {
    case HostKeyWarningFormTrustOnceButtonID:
        return trustOnce;
    case HostKeyWarningFormTrustForeverButtonID:
        return trustAlways;
    case HostKeyWarningFormRejectButtonID:
        return trustNever;
    default:
        return trustNever;
    }
}


static key_trust_t ask_trust_for_change(const char *hostname, const char *hostaddr)
{
    char address[16];
    char content[241];
    UInt16 buttonID;

    strcpy(address, hostaddr);
    
    strcpy(content, warnstart);
    strcat(content, "have CHANGED");
    strcat(content, warnend);

    buttonID = WarningDialog(HostKeyWarningFormID, content, address);

    switch (buttonID) {
    case HostKeyWarningFormTrustOnceButtonID:
        return trustOnce;
    case HostKeyWarningFormTrustForeverButtonID:
        return trustAlways;
    case HostKeyWarningFormRejectButtonID:
        return trustNever;
    default:
        return trustNever;
    }
}


Boolean check_host_key(const char *hostname, const char *hostaddr, Key *hostkey)
{
    UInt16 keyIndex;
    UInt16 hostIndex;
    key_trust_t trust;

    keyIndex = HostKeysFindRecordForKey(hostkey);
    hostIndex = HostKeysFindRecordForHostname(hostname);

    if (hostIndex != noRecord  &&  
        keyIndex != noRecord  &&  
        hostIndex == keyIndex) 
    {
        // This host and this key have been seen before, 
        // and they match (i.e. host key has not changed) - OK
        return true;
    }

    // Ask user whether to trust this key.

    if (hostIndex == noRecord) {
        // no history for this hostname - NEW
        trust = ask_trust_for_new(hostname, hostaddr);
    } 
    else {
        // history for this hostname has a differnt key - CHANGED
        // fixme emit weaker warning if key is known for some other host?
        trust = ask_trust_for_change(hostname, hostaddr);
    }

    // Fix up key history (or not), and return whether to 
    // trust this key for this connection.

    if (trust == trustAlways) {
        // Save this hostname/key in key history:
        // 1. add new record for this key, if needed
        // 2. add hostname to key's record
        // 3. remove previous history for this hostname, if any
        if (keyIndex == noRecord) {
            HostKeysAddRecord(hostname, hostkey);
        } else {
            HostKeysAddHostnameToRecord(hostname, keyIndex);
        }
        if (hostIndex != noRecord) {
            HostKeysRemoveHostnameFromRecord(hostname, hostIndex);
        }
    }

    if (trust == trustAlways  ||  trust == trustOnce) {
        // user wants to trust this key for this connection
        return true;
    } else {
        // user doesn't trust this key
        return false;
    }
}


/*
  fixme check hostname and IP together (OpenSSH options.check_host_ip)

       | addr
  host | NEW | BAD | GOOD
   NEW |  1  |  2  |  3
   BAD |  4  |  5  |  6
  GOOD |  7  |  8  |  9

1: new host key, new addr key - totally unknown host
openssh: NEW  pssh: NEW

2: new host key, BAD addr key - dyndns? route spoof?
openssh: NEW with warning  pssh: NEW

3: new host key, KNOWN addr key - new alias? 
openssh: NEW  pssh: NEW (qualified by new alias?)

4: BAD host key, new addr key - DNS spoof; MITM; key&addr simultaneous change
openssh: spoof & MITM; disable some auth  pssh: spoof & MITM (strong warning)

5a: BAD host key, BAD addr key, same key - MITM; key change
openssh: MITM; disable some auth  pssh: MITM (strong warning)

5b: BAD host key, BAD addr key, different key - same as 4

6: BAD host key, KNOWN addr key - same as 4 (with same vs. diff key qualifiers)

7: KNOWN host key, NEW addr key - rr/dyn DNS;
openssh: adds IP with warning  pssh: silently add IP? warn?

8: KNOWN host key, BAD addr key - dyn DNS;
openssh: warn  pssh: warn (weak warning)

9: KNOWN host key, KNOWN addr key
openssh: ok  pssh: ok
*/

