/* 
   elmo - ELectronic Mail Operator

   Copyright (C) 2004 rzyjontko

   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; version 2.

   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  

   ----------------------------------------------------------------------

*/
/****************************************************************************
 *    IMPLEMENTATION HEADERS
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif

#ifdef GPG_SUPPORT
# ifdef HAVE_GPGME4 
#  include <gpgme4/gpgme.h>
# else
#  include <gpgme.h>
# endif
#endif

#include <string.h>
#include <unistd.h>

#include "pgp.h"
#include "read.h"
#include "gettext.h"
#include "ask.h"
#include "mime.h"
#include "xmalloc.h"
#include "file.h"
#include "mlex.h"
#include "rmime.h"
#include "hash.h"
#include "error.h"
#include "gettext.h"

/****************************************************************************
 *    IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS
 ****************************************************************************/

#define PREAMBLE do { if (! initialized) return; } while (0)

/****************************************************************************
 *    IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
 ****************************************************************************/

struct pass_list {
        struct pass_list *next;
        char             *hint;
        char             *pass;
};

/****************************************************************************
 *    IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE DATA
 ****************************************************************************/

#ifdef GPG_SUPPORT
/* GPGME context */
static gpgme_ctx_t ctx;
#endif

#ifdef GPG_SUPPORT
/* How should we hide the password. */
static hide_mode_t hide = HIDE_ASTERISK;
#endif

static int initialized = 0;

/* List of passwords read by user. */
static struct pass_list *passwords = NULL;

/****************************************************************************
 *    INTERFACE DATA
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
 ****************************************************************************/
/****************************************************************************
 *    IMPLEMENTATION PRIVATE FUNCTIONS
 ****************************************************************************/

static void
destroy_passwords (struct pass_list *list)
{
        if (list == NULL)
                return;

        destroy_passwords (list->next);

        xfree (list->hint);
        xfree (list->pass);
        xfree (list);
}


#ifdef GPG_SUPPORT

static void
add_password (const char *hint, const char *pass)
{
        struct pass_list *list = xmalloc (sizeof (struct pass_list));

        list->hint = xstrdup (hint);
        list->pass = xstrdup (pass);

        list->next = passwords;
        passwords  = list;
}


static char *
find_password (struct pass_list *list, const char *hint)
{
        if (list == NULL)
                return NULL;

        if (strcmp (list->hint, hint) == 0)
                return list->pass;

        return find_password (list->next, hint);
}


static gpgme_error_t
passphrase_cb (void *hook, const char *hint, const char *info,
               int prev_bad, int fd)
{
        char *answer = NULL;

        if (! prev_bad){
                answer = find_password (passwords, hint);
        }

        if (answer == NULL){
                answer = read_argument (_("Password: "), NULL, COMPLETE_NONE,
                                        hide);
        }
               
        if (answer == NULL){
                write (fd, "\n", 1);
                return GPG_ERR_CANCELED;
        }

        write (fd, answer, strlen (answer));
        write (fd, "\n", 1);

        add_password (hint, answer);

        return GPG_ERR_NO_ERROR;
}


static gpgme_data_t
data_from_mime (const char *fname, mime_t *mime)
{
        gpgme_data_t result;

        gpgme_data_new_from_filepart (& result, fname, NULL, mime->off_start,
                                      mime->off_end - mime->off_start);
        return result;
}


static gpgme_data_t
data_from_mime_with_header (const char *fname, mime_t *mime)
{
        gpgme_data_t result;

        gpgme_data_new_from_filepart (& result, fname, NULL, mime->off_header,
                                      mime->off_end - mime->off_header);
        return result;
}



static gpgme_data_t
data_from_tmp_file (FILE **fp, char **fname)
{
        gpgme_data_t result;
        
        *fp = file_temp_file (fname);
        gpgme_data_new_from_stream (& result, *fp);

        return result;
}


static mime_t *
parse_file (FILE *fp)
{
        mime_t *mime;
        
        rewind (fp);
        yyin = fp;

        if (mlex_scan_file (0) != NEXT_MAIL)
                return NULL;

        mime                = newmail->mime->mime;
        newmail->mime->mime = NULL;

        mail_destroy (newmail, BOX_INVALID);
        return mime;
}


static void
decrypt (mime_info_t *mime_info)
{
        gpgme_data_t  in, out;
        FILE         *fp;
        char         *fname;

        in  = data_from_mime (mime_info->file,
                              mime_info->mime->parts->array[1]);
        out = data_from_tmp_file (& fp, & fname);

        if (gpgme_op_decrypt (ctx, in, out) == GPG_ERR_NO_ERROR){
                mime_info->decrypted = parse_file (fp);
                mime_info->d_file    = fname;
        }
        else {
                xfree (fname);
                destroy_passwords (passwords);
        }

        fclose (fp);
        
        gpgme_data_release (in);
        gpgme_data_release (out);
}



static char *
sig_from_fpr (const char *fpr)
{
        gpgme_key_t   key;
        str_t        *str;
        char         *name;
        
        if (gpgme_get_key (ctx, fpr, & key, 0) != GPG_ERR_NO_ERROR)
                return NULL;

        name = mime_decode_utf8 (key->uids->name);
        str  = str_create ();
        str_sprintf (str, "%s <%s>", name, key->uids->email);
        xfree (name);

        gpgme_key_unref (key);
        return str_finished (str);
}


static void
get_sig_info (mime_info_t *mime)
{
        gpgme_verify_result_t result  = gpgme_op_verify_result (ctx);
        gpgme_error_t         error   = result->signatures->status;
        gpgme_sigsum_t        summary = result->signatures->summary;
        
        mime->sig_by = sig_from_fpr (result->signatures->fpr);
        
        if (summary & GPGME_SIGSUM_VALID){
                mime->sig = SIG_OK;
        }
        else if (summary & GPGME_SIGSUM_GREEN){
                mime->sig      = SIG_WARN;
                mime->sig_text = xstrdup (gpgme_strerror (error));
        }
        else if (summary == 0){
                mime->sig      = SIG_WARN;
                mime->sig_text = xstrdup (gpgme_strerror (error));
        }
        else {
                mime->sig      = SIG_FAIL;
                mime->sig_text = xstrdup (gpgme_strerror (error));
        }
}



static void
verify (mime_info_t *mime_info)
{
        gpgme_data_t  mail, sig;
        gpgme_error_t err;

        if (mime_info->sig != SIG_NOT_SIGNED)
                return;
        
        sig  = data_from_mime (mime_info->file,
                               mime_info->mime->parts->array[1]);
        mail = data_from_mime_with_header (mime_info->file,
                                           mime_info->mime->parts->array[0]);

        err = gpgme_op_verify (ctx, sig, mail, NULL);
        
        if (err == GPG_ERR_NO_ERROR){
                get_sig_info (mime_info);
        }
        else {
                error_ (0, _("couldn't verify signature: %s"),
                        gpgme_strerror (err));
        }
        
        gpgme_data_release (sig);
        gpgme_data_release (mail);
}

#else

static void
decrypt (mime_info_t *mime)
{
}


static void
verify (mime_info_t *mime)
{
}

#endif

/****************************************************************************
 *    INTERFACE FUNCTIONS
 ****************************************************************************/

void
pgp_init (void)
{
#if defined (GPG_SUPPORT) && defined (HAVE_LOCALE_H)
        char *shide;
        
        gpgme_check_version (NULL);
        gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
        gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL));

        shide = ask_for_default ("hide_password", NULL);
        
        if (shide && strcasecmp (shide, "yes") == 0)
                hide = HIDE_EMPTY;
        
        if (gpgme_new (& ctx) == GPG_ERR_NO_ERROR){
                gpgme_set_protocol (ctx, GPGME_PROTOCOL_OpenPGP);
                gpgme_set_armor (ctx, 1);
                
                gpgme_set_passphrase_cb (ctx, passphrase_cb, NULL);

                initialized = 1;
        }
#endif
}


void
pgp_free_resources (void)
{
        PREAMBLE;

#ifdef GPG_SUPPORT
        gpgme_release (ctx);
        destroy_passwords (passwords);
#endif
        initialized = 0;
        passwords   = NULL;
}



void
pgp_decrypt_verify (mime_info_t *mime_info)
{
        PREAMBLE;

        if (strcasecmp (mime_info->mime->type, "multipart/encrypted") == 0){
                decrypt (mime_info);
        }
        else if (strcasecmp (mime_info->mime->type, "multipart/signed") == 0){
                verify (mime_info);
        }
}



void
pgp_forget_passphrase (void)
{
        destroy_passwords (passwords);
        passwords = NULL;

        error_ (0, "%s", _("passwords have been forgotten"));
}

/****************************************************************************
 *    INTERFACE CLASS BODIES
 ****************************************************************************/
/****************************************************************************
 *
 *    END MODULE pgp.c
 *
 ****************************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1