/*
 *  Copyright (C) 2004 Morten Fjord-Larsen
 *
 *  $Id: passwordfile.c,v 1.2 2004/06/17 14:31:32 fjord Exp $
 *
 *  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; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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 Library 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.
 */

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

#include <gnome.h>

#include <errno.h>
#include <string.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "encstream.h"
#include "passwordfile.h"

#define MAGIC "GNOME Password Manager\n"


/* Opens a password file for reading */
encstream *password_file_openr(const gchar *filename,
			       const gchar *password,
			       GError **error)
{
    char buffer[128];
    char *p;
    encstream *es;

    g_return_val_if_fail(error == NULL || *error == NULL, NULL);

    es = encstream_open(filename, "r", password, 0);
    if (es == NULL) {
	g_set_error(error, 0, errno, g_strerror(errno));
	return NULL;
    }

    p = encstream_gets(buffer, sizeof(buffer), es);
    if (p == NULL) {
	g_set_error(error, 0, errno, g_strerror(errno));
	encstream_close(es);
	return NULL;
    }

    if (strcmp(p, MAGIC)) {
	g_set_error(error, 0, 0, _("Incorrect password!"));
	encstream_close(es);
	return NULL;
    }

    return es;
}

static gchar *password_file_get_line(encstream *es,
				     GError **error)
{
    static char buffer[1024];
    char *p;

    g_return_val_if_fail(error == NULL || *error == NULL, NULL);

    p = encstream_gets(buffer, sizeof(buffer), es);
    if (!p) {
	g_set_error(error, 0, errno, g_strerror(errno));
	return NULL;
    }
    p = strchr(buffer, '\n');
    if (!p) {
	if (!encstream_eof(es))
	    g_set_error(error, 0, 0, _("Too long line in password file"));
	return NULL;
    }
    else
	*p = '\0';

    //g_message("Got line: '%s'", buffer);

    return g_strdup(buffer);
}


static int password_file_get_val(encstream *es, 
				 const gchar *format,
				 gpointer val,
				 GError **error)
{
    int r;
    gchar *str;

    g_return_val_if_fail(error == NULL || *error == NULL, 0);

    str = password_file_get_line(es, error);
    if (!str) {
	g_assert(error == NULL && *error != NULL);
	return 0;
    }

    r = sscanf(str, format, val);
    if (r == 0) {
	g_set_error(error, 0, 0, _("Password file corrupted!"));
    }
    g_free(str);
    return r;
}


/* Destroy a password file entry. */
void password_file_entry_destroy(password_file_entry *entry)
{
    g_free(entry->title);
    g_free(entry->username);
    g_free(entry->password);
    g_free(entry->url);
    g_free(entry->description);
}


/* Reads an entry from the password file. */
int password_file_read_entry(encstream *es,
			      password_file_entry *entry,
			      GError **error)
{
    GError *t_error = NULL;
    size_t desclen;
    int r;
    gchar *buf;

    g_return_val_if_fail(error == NULL || *error == NULL, 2);

    if (error == NULL)
	error = &t_error;

    entry->title = password_file_get_line(es, error);
    if (entry->title == NULL && (error == NULL || *error == NULL)) {
	return EOF;
    }

    if (entry->title == NULL) 
	g_message((*error)->message);

    entry->username = password_file_get_line(es, error);
    entry->password = password_file_get_line(es, error);
    entry->url = password_file_get_line(es, error);

    password_file_get_val(es, "%u", &entry->creation_time, error);
    password_file_get_val(es, "%u", &entry->modification_time, error);
    password_file_get_val(es, "%u", &entry->expiration_time, error);
    password_file_get_val(es, "%u", &desclen, error);

    if (*error != NULL)
	return 1;

    buf = g_malloc(desclen);
    r = encstream_read(buf, desclen, es);
    if (r < desclen) {
	if (encstream_error(es))
	    g_set_error(error, 0, errno, g_strerror(errno));
	else
	    g_set_error(error, 0, 0, _("Premature end of file"));
	g_free(buf);
	return 3;
    }
    buf[r-1] = '\0';

    entry->description = buf;

    return 0;
}


/* Write a password entry to the given stream */
void password_file_write_entry(encstream *es, 
			       password_file_entry *entry,
			       GError **error)
{
    int rc;
    int len;

    g_return_if_fail(error == NULL || *error == NULL);

    g_return_if_fail(es != NULL);
    g_return_if_fail(entry->title != NULL);
    g_return_if_fail(entry->username != NULL);
    g_return_if_fail(entry->password != NULL);
    g_return_if_fail(entry->url != NULL);
    g_return_if_fail(entry->description != NULL);

    len = strlen(entry->description);
    rc = encstream_printf(es, "%s\n", entry->title) == 0 &&
	 encstream_printf(es, "%s\n", entry->username) == 0 &&
	 encstream_printf(es, "%s\n", entry->password) == 0 &&
	 encstream_printf(es, "%s\n", entry->url) == 0 &&
	 encstream_printf(es, "%u\n", entry->creation_time) == 0 &&
	 encstream_printf(es, "%u\n", entry->modification_time) == 0 &&
	 encstream_printf(es, "%u\n", entry->expiration_time) == 0 &&
	 encstream_printf(es, "%u\n", len+1) == 0 &&
	 encstream_printf(es, "%s\n", entry->description) == 0; 

    if (rc == 0) {
	g_set_error(error, 0, errno, g_strerror(errno));
    }
}


/* Closes a password file */
void password_file_close(encstream *es, GError **error)
{
    int rc;
    g_return_if_fail(error == NULL || *error != NULL);
    g_return_if_fail(es != NULL);
    rc = encstream_close(es);
    if (rc != 0) {
	g_set_error(error, 0, errno, g_strerror(errno));
    }
}


/* Opens a password file for writing. */
encstream *password_file_openw(const gchar *filename,
			       const gchar *password, 
			       GError **error)
{
    encstream *es;
    gchar *dirname;;

    g_return_val_if_fail(error == NULL || *error == NULL, NULL);
    g_return_val_if_fail(filename != NULL, NULL);
    g_return_val_if_fail(password != NULL, NULL);

    dirname = g_path_get_dirname(filename);
    if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
	if (mkdir(dirname, 0700)) {
	    g_free(dirname);
	    g_set_error(error,0, errno, g_strerror(errno));
	    return NULL;
	}
    }
    g_free(dirname);

    es = encstream_open(filename, "w", password, 1);
    if (es == NULL) {
	g_set_error(error, 0, errno, g_strerror(errno));
	return NULL;
    }

    if (chmod(filename, 0600)) {
	g_error(g_strerror(errno));
    }
    
    if (encstream_puts(MAGIC, es)) {
	g_set_error(error, 0, errno, g_strerror(errno));
	encstream_close(es);
	return NULL;
    }

    return es;
}


/* Creates a new password file. */
void password_file_create(const gchar *filename,
			  const gchar *password, 
			  GError **error) 
{

    encstream *es;
    g_return_if_fail(error == NULL || *error == NULL);
    es = password_file_openw(filename, password, error);
    password_file_close(es, error);
}


/* Does a backup of the password file. */
void password_file_backup(const gchar *filename, GError **error)
{
    gchar *backup = g_strdup_printf("%s.bak", filename);

    g_return_if_fail(error == NULL || *error == NULL);

    if (g_file_test(backup, G_FILE_TEST_EXISTS)) {
	if (unlink(backup) < 0) {
	    g_set_error(error, 0, errno, g_strerror(errno));
	    return;
	}
    }

    if (rename(filename, backup) == -1) {
	g_set_error(error, 0, errno, g_strerror(errno));
	return;
    }
}
