/***************************************************************************/
/*                                                                         */
/* Project:     OpenSLP - OpenSource implementation of Service Location    */
/*              Protocol Version 2                                         */
/*                                                                         */
/* File:        slp_spi.h                                                  */
/*                                                                         */
/* Abstract:    Functions for fetching SPI information from the filesystem */
/*              Current implementation uses OpenSSL. For details see       */
/*              (see http://www.openssl.org                                */
/*                                                                         */
/*-------------------------------------------------------------------------*/
/*                                                                         */
/*     Please submit patches to http://www.openslp.org                     */
/*                                                                         */
/*-------------------------------------------------------------------------*/
/*                                                                         */
/* Copyright (C) 2000 Caldera Systems, Inc                                 */
/* All rights reserved.                                                    */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions are  */
/* met:                                                                    */ 
/*                                                                         */
/*      Redistributions of source code must retain the above copyright     */
/*      notice, this list of conditions and the following disclaimer.      */
/*                                                                         */
/*      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.                                                      */
/*                                                                         */
/*      Neither the name of Caldera Systems nor the names of its           */
/*      contributors may be used to endorse or promote products derived    */
/*      from this software without specific prior written permission.      */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS     */
/* `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 CALDERA      */
/* SYSTEMS OR CONTRIBUTORS 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 "slp_spi.h"
#include "slp_xmalloc.h"

#include <string.h>
#include <stdio.h>
#include <openssl/pem.h>
#include <openssl/bn.h>


#define MAX_SPI_ENTRY_LEN   1024
#define PUBLIC_TOKEN        "PUBLIC"
#define PRIVATE_TOKEN       "PRIVATE"


/*-------------------------------------------------------------------------*/
void SLPSpiEntryFree(SLPSpiEntry* victim)
/*-------------------------------------------------------------------------*/
{
    if(victim->keyfilename) xfree(victim->keyfilename);
    if(victim->spistr) xfree(victim->spistr);
    if(victim->key) SLPCryptoDSAKeyDestroy(victim->key);
    if(victim) xfree(victim);
}

/*-------------------------------------------------------------------------*/
SLPSpiEntry* SLPSpiEntryFind(SLPList* cache,
                             int keytype,
                             int spistrlen,
                             const char* spistr)
/* pass in null spistr to find the first Cached entry                      */
/*-------------------------------------------------------------------------*/
{
    SLPSpiEntry* entry = (SLPSpiEntry*)cache->head;
    while(entry)
    {
        if(spistr)
        {
            if (entry->spistrlen == spistrlen &&
                memcmp(entry->spistr,spistr,spistrlen) == 0 &&
                        entry->keytype == keytype)
            {
                return entry;
            }
        }
        else
        {
            if(keytype == SLPSPI_KEY_TYPE_ANY || entry->keytype == keytype)
            {
                return entry;
            }
        }
        entry = (SLPSpiEntry*)entry->listitem.next;
    }

    return 0;
}

/*-------------------------------------------------------------------------*/
SLPCryptoDSAKey* SLPSpiReadKeyFile(const char* keyfile, int keytype)
/*-------------------------------------------------------------------------*/
{
    FILE*            fp;
    SLPCryptoDSAKey* result = 0;

    fp = fopen(keyfile,"r");
    if(fp)
    {
        if(keytype == SLPSPI_KEY_TYPE_PUBLIC)
        {
            result = PEM_read_DSA_PUBKEY(fp, &result, NULL, NULL);
        }
        else if (keytype == SLPSPI_KEY_TYPE_PRIVATE)
        {
            result =  PEM_read_DSAPrivateKey(fp, &result, NULL, NULL);
        }

	    fclose(fp);
    }

    return result;
}


/*-------------------------------------------------------------------------*/
SLPSpiEntry* SLPSpiReadSpiFile(FILE* fp, int keytype)
/* Caller needs to free returned memory SLPSpiEntryFree()                  */
/*-------------------------------------------------------------------------*/
{
    SLPSpiEntry*    result;
    char            tmp;
    char*           line;
    char*           slider1;
    char*           slider2;

    /*----------------------------*/
    /* Allocate memory for result */
    /*----------------------------*/
    line = (char*) xmalloc(MAX_SPI_ENTRY_LEN);
    result = (SLPSpiEntry*) xmalloc(sizeof(SLPSpiEntry));
    if(result == 0 || line == 0)
    {
        return 0;
    }
    memset(result,0,sizeof(SLPSpiEntry));
    

    /*---------------------------*/
    /* Read the next valid entry */
    /*---------------------------*/
    while(fgets(line, MAX_SPI_ENTRY_LEN, fp))
    {
        /*----------------------*/
        /* read the first token */
        /*----------------------*/
        slider1 = line;
        /* skip leading whitespace */
        while(*slider1 && *slider1 <= 0x20) slider1++;
        /* skip all white lines */
        if(*slider1 == 0) continue;
        /* skip commented lines */
        if(*slider1 == '#') continue;
        /* PUBLIC|PRIVATE */
        slider2 = slider1;
        while(*slider2 && *slider2 > 0x20) slider2++;
        if(strncasecmp(PUBLIC_TOKEN,slider1,slider2-slider1) == 0)
        {
            if(keytype == SLPSPI_KEY_TYPE_PRIVATE) continue;
            result->keytype = SLPSPI_KEY_TYPE_PUBLIC;
        }
        else if(strncasecmp(PRIVATE_TOKEN,slider1,slider2-slider1) == 0)
        {
            if(keytype == SLPSPI_KEY_TYPE_PUBLIC) continue;
            result->keytype = SLPSPI_KEY_TYPE_PRIVATE;
        }
        else
        {
            /* unknown token */
            continue;
        }

        /*-----------------------*/
        /* read the second token */
        /*-----------------------*/
        slider1=slider2;
        /* skip leading whitespace */
        while(*slider1 && *slider1 <= 0x20) slider1++;
        /* SPI string */
        slider2 = slider1;
        while(*slider2 && *slider2 > 0x20) slider2++;
        /* SPI string is at slider1 length slider2 - slider1 */
    
        result->spistr = (char*)xmalloc(slider2-slider1);
        if(result->spistr)
        {
            memcpy(result->spistr,slider1,slider2-slider1);
            result->spistrlen = slider2-slider1;    
        }   
                
        /*----------------------*/
        /* read the third token */
        /*----------------------*/
        slider1=slider2;
        /* skip leading whitespace */
        while(*slider1 && *slider1 <= 0x20) slider1++;
        /* SPI string */
        slider2 = slider1;
        while(*slider2 && *slider2 > 0x20) slider2++;
        /* key file path is at slider1 length slider2 - slider1 */
        tmp = *slider2; 
        *slider2 = 0;
        result->keyfilename = xstrdup(slider1);
        result->key = 0; /* read it later */ 
        *slider2 = tmp;
        
        /*-----------------*/
        /* See what we got */
        /*-----------------*/
        if(result &&
           result->spistr &&
           result->keyfilename)
        {
            goto SUCCESS;
        }

        if(result->keyfilename) xfree(result->keyfilename);
        if(result->spistr) xfree(result->spistr);
    }

    if (result)
    { 
        xfree(result);
        result = 0;
    }
    
SUCCESS:

    if (line) xfree(line);

    return result;
}


/*=========================================================================*/
SLPSpiHandle SLPSpiOpen(const char* spifile, int cacheprivate)
/* Initializes SLP SPI data storage.                                       */
/*                                                                         */
/* Parameters: spifile      (IN) path of slp.spi file                      */
/*             cacheprivate (IN) should private keys be cached in handle   */
/*                                                                         */
/* Returns:  valid pointer.  NULL on failure                               */
/*=========================================================================*/
{
    FILE*           fp;
    SLPSpiHandle    result = 0;
    SLPSpiEntry*    spientry;      

    fp = fopen(spifile,"r");
    if(fp)
    {
        result = xmalloc(sizeof(struct _SLPSpiHandle));
        if(result == 0) return 0;
        memset(result, 0, sizeof(struct _SLPSpiHandle));
        
        result->spifile = xstrdup(spifile);
        result->cacheprivate = cacheprivate;
        while(1)
        {
            spientry = SLPSpiReadSpiFile(fp, SLPSPI_KEY_TYPE_ANY);
            if(spientry == 0) break;
            if(spientry->keytype == SLPSPI_KEY_TYPE_PRIVATE &&
               cacheprivate == 0)
            {
                /* destroy the key cause we're not suppose to cache it */
                SLPCryptoDSAKeyDestroy(spientry->key);
            }
            
            SLPListLinkHead(&(result->cache),(SLPListItem*)spientry);
        } 

        fclose(fp); 
    }

    return result;
}

/*=========================================================================*/
void SLPSpiClose(SLPSpiHandle hspi)
/* Release SLP SPI data storage associated with the specified SLPSpiHandle */
/*                                                                         */
/* Parameters: hspi (IN) SLPSpiHandle to deinitialize                      */
/*=========================================================================*/
{
    if(hspi)
    {
        if(hspi->spifile) xfree(hspi->spifile);
        while(hspi->cache.count)
        {
            SLPSpiEntryFree((SLPSpiEntry*)SLPListUnlink(&(hspi->cache),hspi->cache.head));
        }
        
        xfree(hspi);
    }
}


/*=========================================================================*/
char* SLPSpiGetDefaultSPI(SLPSpiHandle hspi, 
                          int keytype,
                          int* spistrlen,
                          char** spistr)
/* Gets a reference to the default SPI string for the specified keytype    */
/*                                                                         */
/* Parameters: hspi      (IN) handle obtained from call to SLPSpiOpen()    */
/*             keytype   (IN) type of key                                  */
/*             spistrlen (OUT) length or the returned spistr               */
/*             spistr    (OUT) pointer to spistr.  MUST be freed by        */
/*                             caller!!                                    */
/*                                                                         */
/* Returns: Pointer to the default SPI string.  Pointer may *not* be NULL  */
/*          terminated                                                     */
/*=========================================================================*/
{
    SLPSpiEntry* entry;
    
    *spistr = 0;
    *spistrlen = 0;

    if(hspi)
    {
            
        entry = SLPSpiEntryFind(&(hspi->cache),keytype,0,0);
        if(entry)
        {
            *spistr = xmalloc(entry->spistrlen);
            if(*spistr)
            {
                memcpy(*spistr, entry->spistr, entry->spistrlen);
                *spistrlen = entry->spistrlen;
            }
        }
    }

    return *spistr;
}
    

/*=========================================================================*/
SLPCryptoDSAKey* SLPSpiGetDSAKey(SLPSpiHandle hspi,
                                 int keytype,
                                 int spistrlen,
                                 const char* spistr,
                                 SLPCryptoDSAKey **key)
/* Fetches a copy of the private key file used to sign SLP messages.       */
/*                                                                         */
/* Parameters: hspi      (IN)  handle obtained from call to SLPSpiOpen()   */
/*             keytype   (IN)  the type of key desired                     */
/*             spistrlen (IN)  the length of the spistr                    */
/*             spistr    (IN)  spistr associated with the key              */
/*             key       (OUT) the private key.  Caller should use         */
/*                             SLPCryptoDSAKeyDestroy() to free key memory */
/*                                                                         */
/* Returns: A valid pointer. NULL on failure. Caller should use            */
/*          SLPCryptoDSAKeyDestroy() to free key memory                    */ 
/*=========================================================================*/
{
    SLPSpiEntry*    tmp = 0;
        
    /* For safety NULL out the key from the beginning */
    *key = 0;

    if(hspi)
    {
        tmp = SLPSpiEntryFind(&(hspi->cache),
                              keytype,
                              spistrlen,
                              spistr);
        if(tmp)
        {
            if(tmp->key == 0)
            {
                if(keytype == SLPSPI_KEY_TYPE_PRIVATE && hspi->cacheprivate == 0)
                {
                    *key = SLPSpiReadKeyFile(tmp->keyfilename,SLPSPI_KEY_TYPE_PRIVATE);
                    return *key;
                }
                
                tmp->key = SLPSpiReadKeyFile(tmp->keyfilename,keytype);
		if (tmp->key == 0)
		    return 0;
            }

            *key = SLPCryptoDSAKeyDup(tmp->key);
        }
    }

    return *key;
}


/*=========================================================================*/
int SLPSpiCanVerify(SLPSpiHandle hspi,
                    int spistrlen,
                    const char* spistr)
/* Determine if we understand the specified SPI.  No SPI is always         */
/* returns true                                                            */
/*                                                                         */
/* Parameters: hspi      (IN)  handle obtained from call to SLPSpiOpen()   */
/*             spistrlen (IN)  the length of the spistr                    */
/*             spistr    (IN)  the SPI string                              */
/*                                                                         */
/* Returns     Non-zero if we verify specified the SPI                     */
/*=========================================================================*/
{
    if (hspi == 0)
    {
        return 0;
    }

    if(spistrlen == 0 || spistr == NULL)
    {
        return 1;
    }

    return (SLPSpiEntryFind(&(hspi->cache), 
                            SLPSPI_KEY_TYPE_PUBLIC,
                            spistrlen, 
                            spistr) != 0);
}


/*=========================================================================*/
int SLPSpiCanSign(SLPSpiHandle hspi,
                  int spistrlen,
                  const char* spistr)
/* Determine if we understand the specified SPI.  No SPI is always         */
/* return true                                                             */
/*                                                                         */
/* Parameters: hspi      (IN)  handle obtained from call to SLPSpiOpen()   */
/*             spistrlen (IN)  the length of the spistr                    */
/*             spistr    (IN)  the SPI string                              */
/*                                                                         */
/* Returns     Non-zero if we sign using the specified SPI                 */
/*=========================================================================*/
{
    return (SLPSpiEntryFind(&(hspi->cache), 
                            SLPSPI_KEY_TYPE_PRIVATE,
                            spistrlen, 
                            spistr) != 0);
}


