/* pggsign.c - functions to sign data
 *      Copyright (C) 1999 Michael Roth <mroth@gnupg.org>
 *
 * This file is part of PGG (Privacy Guard Glue).
 *
 * PGG 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.
 *
 * PGG 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */


#include <includes.h>
#include <pgg.h>
#include <pggsign.h>
#include <pggdebug.h>
#include <pggstate.h>


#define sign		((PggSignPtr)(_sign))
#define old_sign	((PggSignPtr)(_old_sign))


PggSign pgg_sign_new(PggErrenv errenv)
{
    PggSignPtr		new_sign;
    
    PGG_CHECK_SKIP_ARG(NULL);
    
    if (!( new_sign = _malloc(PggSign) ))
        PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
    
    memset(new_sign, 0, _size(PggSign));
    
    new_sign->magic      = PggSignMAGIC;
    new_sign->refcounter = 1;
    
    return _hd(PggSign, new_sign);
}


PggSign pgg_sign_clone(PggSign _old_sign, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    PggSignPtr		new_sign;
    
    PGG_STD_ASSERT_ARG(PggSign, old_sign, NULL);
    
    pgg_errenv_reset(local_errenv);
    
    if (!( new_sign = _malloc(PggSign) ))
        PGG_RETURN_ERR_ARG(RESOURCE, MEMORY, NULL);
    
    memcpy(new_sign, old_sign, _size(PggSign));
    
    new_sign->magic      = PggSignMAGIC;
    new_sign->refcounter = 1;
    
    if (old_sign->stdio)
        new_sign->stdio = pgg_stdio_clone(old_sign->stdio, local_errenv);
    
    if (old_sign->passcache)
        new_sign->passcache = pgg_passcache_clone(old_sign->passcache, local_errenv);
    
    if (old_sign->algo)
        new_sign->algo = pgg_algo_clone(old_sign->algo, local_errenv);
    
    if (old_sign->config)
        new_sign->config = pgg_config_clone(old_sign->config, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv)) {
        PGG_DEBUG(("problems during cloning!?!?"));
        
        if (new_sign->passcache)
            pgg_passcache_release(new_sign->passcache, NULL);
        
        if (new_sign->stdio)
            pgg_stdio_release(new_sign->stdio, NULL);
        
        if (new_sign->algo)
            pgg_algo_release(new_sign->algo, NULL);
        
        if (new_sign->config)
            pgg_config_release(new_sign->config, NULL);
        
        free(new_sign);
        pgg_errenv_copy(errenv, local_errenv);
        return NULL;
    }
    
    return _hd(PggSign, new_sign);
}


void pgg_sign_addref(PggSign _sign, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggSign, sign);
    sign->refcounter++;
}


void pgg_sign_release(PggSign _sign, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    
    PGG_STD_ASSERT(PggSign, sign);
    
    if (!--sign->refcounter) {
        pgg_errenv_reset(local_errenv);
        
        if (sign->passcache)
            pgg_passcache_release(sign->passcache, local_errenv);
        
        if (sign->stdio)
            pgg_stdio_release(sign->stdio, local_errenv);
        
        if (sign->algo)
            pgg_algo_release(sign->algo, local_errenv);
        
        if (sign->config)
            pgg_config_release(sign->config, local_errenv);
        
        free(sign);
        
        if (pgg_errenv_is_set(local_errenv)) {
            PGG_DEBUG(("An error occured during releasing various resources"));
            pgg_errenv_copy(errenv, local_errenv);
        }
    }
}


void pgg_sign_set_signer(PggSign _sign, const char *signer, PggErrenv errenv)
{
    char *		tmp = NULL;
    
    PGG_STD_ASSERT(PggSign, sign);
    
    if (signer && !(tmp = strdup(signer)) )
        PGG_RETURN_ERR(RESOURCE, MEMORY);
    
    if (sign->signer)
        free(sign->signer);
    
    sign->signer = tmp;
}


const char * pgg_sign_get_signer(PggSign _sign, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggSign, sign, NULL);
    PGG_ASSERT_ARG(sign->signer, REQUEST, NOTSET, NULL);
    return sign->signer;
}


void pgg_sign_set_escape(PggSign _sign, PggSign_Escape escape, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggSign, sign);
    PGG_ASSERT(escape >= PGG_SIGN_ESCAPE_NONE, RESOURCE, VALUE);
    PGG_ASSERT(escape <= PGG_SIGN_ESCAPE_NOTDASH, RESOURCE, VALUE);
    sign->escape = escape;
}


PggSign_Escape pgg_sign_get_escape(PggSign _sign, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggSign, sign, PGG_SIGN_ESCAPE_ERROR);
    return sign->escape;
}


void pgg_sign_set_mode(PggSign _sign, PggSign_Mode mode, PggErrenv errenv)
{
    PGG_STD_ASSERT(PggSign, sign);
    PGG_ASSERT(mode >= PGG_SIGN_MODE_NORMAL, RESOURCE, VALUE);
    PGG_ASSERT(mode <= PGG_SIGN_MODE_CLEARSIGN, RESOURCE, VALUE);
    sign->mode = mode;
}


PggSign_Mode pgg_sign_get_mode(PggSign _sign, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggSign, sign, PGG_SIGN_MODE_ERROR);
    return sign->mode;
}


void pgg_sign_set_stdio(PggSign _sign, PggStdio stdio, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    
    PGG_STD_ASSERT(PggSign, sign);
    
    pgg_errenv_reset(local_errenv);
    
    if (stdio)
        pgg_stdio_addref(stdio, local_errenv);
    
    if (sign->stdio)
        pgg_stdio_release(sign->stdio, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv)) 
        pgg_errenv_copy(errenv, local_errenv);
    else
        sign->stdio = stdio;
}


PggStdio pgg_sign_get_stdio(PggSign _sign, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggSign, sign, NULL);
    PGG_ASSERT_ARG(sign->stdio, REQUEST, NOTSET, NULL);
    return sign->stdio;
}


void pgg_sign_set_passcache(PggSign _sign, PggPasscache passcache, PggErrenv errenv)
{
    PggErrenv           local_errenv;
    
    PGG_STD_ASSERT(PggSign, sign);
    
    pgg_errenv_reset(local_errenv);
    
    if (passcache)
        pgg_passcache_addref(passcache, local_errenv);
    
    if (sign->passcache)
        pgg_passcache_release(sign->passcache, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv))  
        pgg_errenv_copy(errenv, local_errenv);
    else
        sign->passcache = passcache;
}


PggPasscache pgg_sign_get_passcache(PggSign _sign, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggSign, sign, NULL);
    PGG_ASSERT_ARG(sign->passcache, REQUEST, NOTSET, NULL);
    return sign->passcache;
}


void pgg_sign_set_algo(PggSign _sign, PggAlgo algo, PggErrenv errenv)
{
    PggErrenv           local_errenv;
    
    PGG_STD_ASSERT(PggSign, sign);
    
    pgg_errenv_reset(local_errenv);
    
    if (algo)
        pgg_algo_addref(algo, local_errenv);
    
    if (sign->algo)
        pgg_algo_release(sign->algo, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv))  
        pgg_errenv_copy(errenv, local_errenv);
    else
        sign->algo = algo;
}


PggAlgo pgg_sign_get_algo(PggSign _sign, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggSign, sign, NULL);
    PGG_ASSERT_ARG(sign->algo, REQUEST, NOTSET, NULL);
    return sign->algo;
}


void pgg_sign_set_config(PggSign _sign, PggConfig config, PggErrenv errenv)
{
    PggErrenv           local_errenv;
    
    PGG_STD_ASSERT(PggSign, sign);
    
    pgg_errenv_reset(local_errenv);
    
    if (config)
        pgg_config_addref(config, local_errenv);
    
    if (sign->config)
        pgg_config_release(sign->config, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv))  
        pgg_errenv_copy(errenv, local_errenv);
    else
        sign->config = config;
}


PggConfig pgg_sign_get_config(PggSign _sign, PggErrenv errenv)
{
    PGG_STD_ASSERT_ARG(PggSign, sign, NULL);
    PGG_ASSERT_ARG(sign->config, REQUEST, NOTSET, NULL);
    return sign->config;
}


void pgg_sign_execute(PggSign _sign, PggErrenv errenv)
{
    PggErrenv		local_errenv;
    PggExe		exe;
    char		keyid_buffer[32];
    const char *	passphrase;
    STM_DECLS;
    
    PGG_STD_ASSERT(PggSign, sign);
    PGG_ASSERT(sign->stdio, REQUEST, STATE);
    PGG_ASSERT(sign->passcache, REQUEST, STATE);
    
    pgg_errenv_reset(local_errenv);
    
    exe = pgg_exe_new(local_errenv);
    
    if (sign->config)
        pgg_config_setup_exe(sign->config, exe, local_errenv);
    
    if (sign->algo)
        pgg_algo_setup_exe(sign->algo, exe, local_errenv);
    else {
        PggConfig config = pgg_config_new(local_errenv);
        pgg_config_setup_exe(config, exe, local_errenv);
        pgg_config_release(config, local_errenv);
    }
    
    switch (sign->escape) {
        case PGG_SIGN_ESCAPE_NONE:
            break;
        
        case PGG_SIGN_ESCAPE_FROM:
            pgg_exe_add_arg(exe, "--escape-from-lines", local_errenv);
            break;
        
        case PGG_SIGN_ESCAPE_NOTDASH:
            pgg_exe_add_arg(exe, "--not-dash-escaped", local_errenv);
            break;
        
        default:
            PGG_DEBUG(("unknown internal escape value"));
            pgg_exe_release(exe, NULL);
            PGG_RETURN_ERR(INTERNAL, NONE);
    }
    
    if (sign->signer) {
        pgg_exe_add_arg(exe, "--local-user", local_errenv);
        pgg_exe_add_arg(exe, sign->signer, local_errenv);
    }
    
    switch (sign->mode) {
        case PGG_SIGN_MODE_NORMAL:
            pgg_exe_add_arg(exe, "--sign", local_errenv);
            break;
        
        case PGG_SIGN_MODE_DETACH:
            pgg_exe_add_arg(exe, "--detach-sign", local_errenv);
            break;
        
        case PGG_SIGN_MODE_CLEARSIGN:
            pgg_exe_add_arg(exe, "--clearsign", local_errenv);
            break;
        
        default:
            PGG_DEBUG(("unknown internal mode state!"));
            pgg_exe_release(exe, NULL);
            PGG_RETURN_ERR(INTERNAL, NONE);
    }
    
    pgg_stdio_setup_exe(sign->stdio, exe, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv)) {
        PGG_DEBUG(("problems during initializing PggExe"));
        pgg_exe_release(exe, NULL);
        pgg_errenv_copy(errenv, local_errenv);
        return;
    }
    else
        PGG_DEBUG(("setup PggExe ok"));
    
    pgg_exe_request_shm(exe, local_errenv);
    pgg_exe_request_status(exe, local_errenv);
    pgg_exe_start(exe, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv)) {
        PGG_DEBUG(("starting GnuPG failed"));
        pgg_exe_release(exe, NULL);
        pgg_errenv_copy(errenv, local_errenv);
        return;
    }
    else
        PGG_DEBUG(("GnuPG started"));
    
    
    STM_BEGIN()
        
        IF_STATUS(0, NEED_PASSPHRASE) {
            /* grab password */
            PGG_DEBUG(("passphrase for keyid %s requested", status_arg));
            if (strlen(status_arg) != 16) {
                PGG_DEBUG(("wrong length of keyid!?!?"));
                pgg_exe_release(exe, NULL);
                PGG_RETURN_ERR(GNUPG, UNEXPECTED);
            }
            strcpy(keyid_buffer, status_arg);
            SWITCH_TO_STATE(1);
        }
        IF_STATUS(0, GOOD_PASSPHRASE) {
            PGG_DEBUG(("used key has no password"));
            SWITCH_TO_STATE(3);
        }
        IF_STATUS(0, NO_SECKEY) {			/* FIXME: could this occur? */
            PGG_DEBUG(("Tried to use a non existing secret key"));
            pgg_exe_release(exe, NULL);
            PGG_RETURN_ERR(GNUPG, UNEXPECTED);
        }
        IF_EVENT_FINISHED(0) {				/* FIXME: we must update GnuPG to supply a valid status line */
            PGG_DEBUG(("Possible non existant key specified"));
            pgg_exe_release(exe, NULL);
            PGG_RETURN_ERR(CRYPT, NOSECKEY);
        }
        
        
        IF_STATUS_ARG(1, SHM_GET_HIDDEN, "passphrase.enter") {
            if (!( passphrase = pgg_passcache_get_passphrase(sign->passcache, keyid_buffer, local_errenv) )) {
                PGG_DEBUG(("no passphrase available for keyid %s", keyid_buffer));
                pgg_exe_release(exe, NULL);
                PGG_RETURN_ERR(CRYPT, PASSPHRASE);	/* FIXME: should we add a new or change suberr codes? */
            }
            else {
                PGG_DEBUG(("sending passphrase for keyid %s", keyid_buffer));
                pgg_exe_reply_str(exe, passphrase, local_errenv);
                if (pgg_errenv_is_set(local_errenv)) {
                    pgg_errenv_copy(errenv, local_errenv);
                    pgg_exe_release(exe, NULL);
                    return;
                }
                SWITCH_TO_STATE(2);
            }
        }
        
        
        IF_STATUS(2, GOOD_PASSPHRASE) {
            /* yes, passphrase is ok */
            PGG_DEBUG(("passphrase was all right"));
            SWITCH_TO_STATE(3);
        }
        IF_STATUS(2, BAD_PASSPHRASE) {
            /* passphrase was wrong. update passcache */
            PGG_DEBUG(("wrong passphrase"));
            pgg_exe_release(exe, NULL);
            PGG_RETURN_ERR(CRYPT, PASSPHRASE);
        }
        
        
        IF_STATUS_ARG(3, SHM_GET_BOOL, "openfile.overwrite.okay") {
            pgg_exe_reply_bool(exe, 1, local_errenv);
            if (pgg_errenv_is_set(local_errenv)) {
                pgg_errenv_copy(errenv, local_errenv);
                pgg_exe_release(exe, NULL);
                return;
            }
        }
        IF_EVENT_FINISHED(3) {
            /* all gone right */
            STM_LEAVE();
        }
        
        
        IF_UNHANDLED() {
            PGG_DEBUG(("unhandled event %d in state %d (status %d: \"%s\")", event, state, status, status_arg));
            pgg_exe_release(exe, NULL);
            PGG_RETURN_ERR(GNUPG, UNEXPECTED);
        }
        
        
        if (pgg_errenv_is_set(local_errenv)) {
            PGG_DEBUG(("unhandled error in state: %d", state));
        }
        
    STM_END
    
    pgg_exe_release(exe, local_errenv);
    
    if (pgg_errenv_is_set(local_errenv)) {
        PGG_DEBUG(("pgg_exe_release failed..."));
        pgg_errenv_copy(errenv, local_errenv);
    }
}




