/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* e-passwords.c
*
* Copyright (C) 2001 Ximian, Inc.
*/
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* 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.
*/
/*
* This looks a lot more complicated than it is, and than you'd think
* it would need to be. There is however, method to the madness.
*
* The code most cope with being called from any thread at any time,
* recursively from the main thread, and then serialising every
* request so that sane and correct values are always returned, and
* duplicate requests are never made.
*
* To this end, every call is marshalled and queued and a dispatch
* method invoked until that request is satisfied. If mainloop
* recursion occurs, then the sub-call will necessarily return out of
* order, but will not be processed out of order.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <libgnome/gnome-config.h>
#include <libgnome/gnome-i18n.h>
#include <gtk/gtkversion.h>
#include <gtk/gtkentry.h>
#include <gtk/gtkvbox.h>
#include <gtk/gtkcheckbutton.h>
#include <gtk/gtkmessagedialog.h>
#include "e-passwords.h"
#include "e-msgport.h"
#include "widgets/misc/e-error.h"
#ifndef ENABLE_THREADS
#define ENABLE_THREADS (1)
#endif
#ifdef ENABLE_THREADS
#include <pthread.h>
static pthread_t main_thread;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
#define LOCK() pthread_mutex_lock(&lock)
#define UNLOCK() pthread_mutex_unlock(&lock)
#else
#define LOCK()
#define UNLOCK()
#endif
struct _EPassMsg {
EMsg msg;
void (*dispatch)(struct _EPassMsg *);
/* input */
struct _GtkWindow *parent;
const char *component;
const char *key;
const char *title;
const char *prompt;
const char *oldpass;
guint32 flags;
/* output */
gboolean *remember;
char *password;
/* work variables */
GtkWidget *entry;
GtkWidget *check;
int ismain:1;
int noreply:1; /* supress replies; when calling
* dispatch functions from others */
};
typedef struct _EPassMsg EPassMsg;
static GHashTable *passwords = NULL;
static GtkDialog *password_dialog;
static EDList request_list = E_DLIST_INITIALISER(request_list);
static int idle_id;
static int ep_online_state = TRUE;
static char *decode_base64 (char *base64);
static int base64_encode_close(unsigned char *in, int inlen, gboolean break_lines, unsigned char *out, int *state, int *save);
static int base64_encode_step(unsigned char *in, int len, gboolean break_lines, unsigned char *out, int *state, int *save);
static gboolean
ep_idle_dispatch(void *data)
{
EPassMsg *msg;
/* As soon as a password window is up we stop; it will
re-invoke us when it has been closed down */
LOCK();
while (password_dialog == NULL && (msg = (EPassMsg *)e_dlist_remhead(&request_list))) {
UNLOCK();
msg->dispatch(msg);
LOCK();
}
idle_id = 0;
UNLOCK();
return FALSE;
}
static EPassMsg *
ep_msg_new(void (*dispatch)(EPassMsg *))
{
EPassMsg *msg;
e_passwords_init();
msg = g_malloc0(sizeof(*msg));
msg->dispatch = dispatch;
msg->msg.reply_port = e_msgport_new();
#ifdef ENABLE_THREADS
msg->ismain = pthread_self() == main_thread;
#else
msg->ismain = TRUE;
#endif
return msg;
}
static void
ep_msg_free(EPassMsg *msg)
{
e_msgport_destroy(msg->msg.reply_port);
g_free(msg->password);
g_free(msg);
}
static void
ep_msg_send(EPassMsg *msg)
{
int needidle = 0;
LOCK();
e_dlist_addtail(&request_list, (EDListNode *)&msg->msg);
if (!idle_id) {
if (!msg->ismain)
idle_id = g_idle_add(ep_idle_dispatch, NULL);
else
needidle = 1;
}
UNLOCK();
if (msg->ismain) {
EPassMsg *m;
if (needidle)
ep_idle_dispatch(NULL);
while ((m = (EPassMsg *)e_msgport_get(msg->msg.reply_port)) == NULL)
g_main_context_iteration(NULL, TRUE);
g_assert(m == msg);
} else {
e_msgport_wait(msg->msg.reply_port);
g_assert(e_msgport_get(msg->msg.reply_port) == &msg->msg);
}
}
/* the functions that actually do the work */
static void
ep_clear_passwords(EPassMsg *msg)
{
char *path;
path = g_strdup_printf ("/Evolution/Passwords-%s", msg->component);
gnome_config_private_clean_section (path);
gnome_config_private_sync_file ("/Evolution");
g_free (path);
if (!msg->noreply)
e_msgport_reply(&msg->msg);
}
static gboolean
free_entry (gpointer key, gpointer value, gpointer user_data)
{
g_free (key);
memset (value, 0, strlen (value));
g_free (value);
return TRUE;
}
static void
ep_forget_passwords(EPassMsg *msg)
{
void *it;
char *key;
it = gnome_config_private_init_iterator_sections("/Evolution");
while ( (it = gnome_config_iterator_next(it, &key, NULL)) ) {
if (0 == strncmp(key, "Passwords-", 10)) {
char *section = g_strdup_printf("/Evolution/%s", key);
gnome_config_private_clean_section (section);
g_free(section);
}
g_free(key);
}
gnome_config_private_sync_file ("/Evolution");
/* free up the session passwords */
g_hash_table_foreach_remove (passwords, free_entry, NULL);
if (!msg->noreply)
e_msgport_reply(&msg->msg);
}
static char *
password_path (const char *component_name, const char *key)
{
char *keycopy, *path;
int i;
keycopy = g_strdup (key);
for (i = 0; i < strlen (keycopy); i ++)
if (keycopy[i] == '/' || keycopy[i] =='=')
keycopy[i] = '_';
path = g_strdup_printf ("/Evolution/Passwords-%s/%s", component_name, keycopy);
g_free (keycopy);
return path;
}
static void
ep_remember_password(EPassMsg *msg)
{
gpointer okey, value;
char *path, *pass64;
int len, state, save;
if (g_hash_table_lookup_extended (passwords, msg->key, &okey, &value)) {
/* add it to the on-disk cache of passwords */
path = password_path (msg->component, okey);
len = strlen (value);
pass64 = g_malloc0 ((len + 2) * 4 / 3 + 1);
state = save = 0;
base64_encode_close (value, len, FALSE, pass64, &state, &save);
gnome_config_private_set_string (path, pass64);
g_free (path);
g_free (pass64);
/* now remove it from our session hash */
g_hash_table_remove (passwords, msg->key);
g_free (okey);
g_free (value);
gnome_config_private_sync_file ("/Evolution");
}
if (!msg->noreply)
e_msgport_reply(&msg->msg);
}
static void
ep_forget_password (EPassMsg *msg)
{
gpointer okey, value;
char *path;
if (g_hash_table_lookup_extended (passwords, msg->key, &okey, &value)) {
g_hash_table_remove (passwords, msg->key);
memset (value, 0, strlen (value));
g_free (okey);
g_free (value);
}
/* clear it in the on disk db */
path = password_path (msg->component, msg->key);
gnome_config_private_clean_key (path);
gnome_config_private_sync_file ("/Evolution");
g_free (path);
if (!msg->noreply)
e_msgport_reply(&msg->msg);
}
static void
ep_get_password (EPassMsg *msg)
{
char *path, *passwd;
char *encoded = NULL;
passwd = g_hash_table_lookup (passwords, msg->key);
if (passwd) {
msg->password = g_strdup(passwd);
} else {
/* not part of the session hash, look it up in the on disk db */
path = password_path (msg->component, msg->key);
encoded = gnome_config_private_get_string_with_default (path, NULL);
g_free (path);
if (encoded) {
msg->password = decode_base64 (encoded);
g_free (encoded);
}
}
if (!msg->noreply)
e_msgport_reply(&msg->msg);
}
static void
ep_add_password (EPassMsg *msg)
{
gpointer okey, value;
if (g_hash_table_lookup_extended (passwords, msg->key, &okey, &value)) {
g_hash_table_remove (passwords, msg->key);
g_free (okey);
g_free (value);
}
g_hash_table_insert (passwords, g_strdup (msg->key), g_strdup (msg->oldpass));
if (!msg->noreply)
e_msgport_reply(&msg->msg);
}
static void ep_ask_password(EPassMsg *msg);
static void
pass_response(GtkDialog *dialog, int response, void *data)
{
EPassMsg *msg = data;
int type = msg->flags & E_PASSWORDS_REMEMBER_MASK;
EDList pending = E_DLIST_INITIALISER(pending);
EPassMsg *mw, *mn;
if (response == GTK_RESPONSE_OK) {
msg->password = g_strdup(gtk_entry_get_text((GtkEntry *)msg->entry));
if (type != E_PASSWORDS_REMEMBER_NEVER) {
int noreply = msg->noreply;
*msg->remember = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (msg->check));
msg->noreply = 1;
if (*msg->remember || type == E_PASSWORDS_REMEMBER_FOREVER) {
msg->oldpass = msg->password;
ep_add_password(msg);
}
if (*msg->remember && type == E_PASSWORDS_REMEMBER_FOREVER)
ep_remember_password(msg);
msg->noreply = noreply;
}
}
gtk_widget_destroy((GtkWidget *)dialog);
password_dialog = NULL;
/* ok, here things get interesting, we suck up any pending
* operations on this specific password, and return the same
* result or ignore other operations */
LOCK();
mw = (EPassMsg *)request_list.head;
mn = (EPassMsg *)mw->msg.ln.next;
while (mn) {
if ((mw->dispatch == ep_forget_password
|| mw->dispatch == ep_get_password
|| mw->dispatch == ep_ask_password)
&& (strcmp(mw->component, msg->component) == 0
&& strcmp(mw->key, msg->key) == 0)) {
e_dlist_remove((EDListNode *)mw);
mw->password = g_strdup(msg->password);
e_msgport_reply(&mw->msg);
}
mw = mn;
mn = (EPassMsg *)mn->msg.ln.next;
}
UNLOCK();
if (!msg->noreply)
e_msgport_reply(&msg->msg);
ep_idle_dispatch(NULL);
}
static void
ep_ask_password(EPassMsg *msg)
{
GtkWidget *vbox;
int type = msg->flags & E_PASSWORDS_REMEMBER_MASK;
int noreply = msg->noreply;
msg->noreply = 1;
/*password_dialog = (GtkDialog *)e_error_new(msg->parent, "mail:ask-session-password", msg->prompt, NULL);*/
password_dialog = (GtkDialog *)gtk_message_dialog_new (msg->parent,
0,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_OK_CANCEL,
"%s", msg->prompt);
gtk_window_set_title(GTK_WINDOW(password_dialog), msg->title);
#if !GTK_CHECK_VERSION (2,4,0)
gtk_dialog_set_has_separator(password_dialog, FALSE);
#endif
gtk_dialog_set_default_response(password_dialog, GTK_RESPONSE_OK);
vbox = gtk_vbox_new (FALSE, 6);
gtk_widget_show (vbox);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (password_dialog)->vbox), vbox, TRUE, FALSE, 0);
gtk_container_set_border_width((GtkContainer *)vbox, 6);
msg->entry = gtk_entry_new ();
gtk_entry_set_visibility ((GtkEntry *)msg->entry, !(msg->flags & E_PASSWORDS_SECRET));
gtk_entry_set_activates_default((GtkEntry *)msg->entry, TRUE);
gtk_box_pack_start (GTK_BOX (vbox), msg->entry, TRUE, FALSE, 3);
gtk_widget_show (msg->entry);
gtk_widget_grab_focus (msg->entry);
if ((msg->flags & E_PASSWORDS_REPROMPT)) {
ep_get_password(msg);
if (msg->password) {
gtk_entry_set_text ((GtkEntry *) msg->entry, msg->password);
g_free (msg->password);
msg->password = NULL;
}
}
/* static password, shouldn't be remembered between sessions,
but will be remembered within the session beyond our control */
if (type != E_PASSWORDS_REMEMBER_NEVER) {
msg->check = gtk_check_button_new_with_mnemonic(type == E_PASSWORDS_REMEMBER_FOREVER
? _("_Remember this password")
: _("_Remember this password for the remainder of this session"));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (msg->check), *msg->remember);
gtk_box_pack_start (GTK_BOX (vbox), msg->check, TRUE, FALSE, 3);
gtk_widget_show (msg->check);
}
msg->noreply = noreply;
g_signal_connect(password_dialog, "response", G_CALLBACK (pass_response), msg);
gtk_widget_show((GtkWidget *)password_dialog);
}
/**
* e_passwords_init:
*
* Initializes the e_passwords routines. Must be called before any other
* e_passwords_* function.
**/
void
e_passwords_init (void)
{
LOCK();
if (!passwords) {
/* create the per-session hash table */
passwords = g_hash_table_new (g_str_hash, g_str_equal);
#ifdef ENABLE_THREADS
main_thread = pthread_self();
#endif
}
UNLOCK();
}
/**
* e_passwords_cancel:
*
* Cancel any outstanding password operations and close any dialogues
* currently being shown.
**/
void
e_passwords_cancel(void)
{
EPassMsg *msg;
LOCK();
while ((msg = (EPassMsg *)e_dlist_remhead(&request_list)))
e_msgport_reply(&msg->msg);
UNLOCK();
if (password_dialog)
gtk_widget_destroy((GtkWidget *)password_dialog);
}
/**
* e_passwords_shutdown:
*
* Cleanup routine to call before exiting.
**/
void
e_passwords_shutdown (void)
{
/* shouldn't need this really - everything is synchronous */
gnome_config_private_sync_file ("/Evolution");
e_passwords_cancel();
if (passwords) {
/* and destroy our per session hash */
g_hash_table_foreach_remove (passwords, free_entry, NULL);
g_hash_table_destroy (passwords);
passwords = NULL;
}
}
/**
* e_passwords_set_online:
* @state:
*
* Set the offline-state of the application. This is a work-around
* for having the backends fully offline aware, and returns a
* cancellation response instead of prompting for passwords.
*
* FIXME: This is not a permanent api, review post 2.0.
**/
void
e_passwords_set_online(int state)
{
ep_online_state = state;
/* TODO: we could check that a request is open and close it, or maybe who cares */
}
/**
* e_passwords_forget_passwords:
*
* Forgets all cached passwords, in memory and on disk.
**/
void
e_passwords_forget_passwords (void)
{
EPassMsg *msg = ep_msg_new(ep_forget_passwords);
ep_msg_send(msg);
ep_msg_free(msg);
}
/**
* e_passwords_clear_passwords:
*
* Forgets all disk cached passwords for the component.
**/
void
e_passwords_clear_passwords (const char *component_name)
{
EPassMsg *msg = ep_msg_new(ep_clear_passwords);
msg->component = component_name;
ep_msg_send(msg);
ep_msg_free(msg);
}
/**
* e_passwords_remember_password:
* @key: the key
*
* Saves the password associated with @key to disk.
**/
void
e_passwords_remember_password (const char *component_name, const char *key)
{
EPassMsg *msg;
g_return_if_fail(component_name != NULL);
g_return_if_fail(key != NULL);
msg = ep_msg_new(ep_remember_password);
msg->component = component_name;
msg->key = key;
ep_msg_send(msg);
ep_msg_free(msg);
}
/**
* e_passwords_forget_password:
* @key: the key
*
* Forgets the password associated with @key, in memory and on disk.
**/
void
e_passwords_forget_password (const char *component_name, const char *key)
{
EPassMsg *msg;
g_return_if_fail(component_name != NULL);
g_return_if_fail(key != NULL);
msg = ep_msg_new(ep_forget_password);
msg->component = component_name;
msg->key = key;
ep_msg_send(msg);
ep_msg_free(msg);
}
/**
* e_passwords_get_password:
* @key: the key
*
* Return value: the password associated with @key, or %NULL. Caller
* must free the returned password.
**/
char *
e_passwords_get_password (const char *component_name, const char *key)
{
EPassMsg *msg;
char *passwd;
g_return_val_if_fail(component_name != NULL, NULL);
g_return_val_if_fail(key != NULL, NULL);
msg = ep_msg_new(ep_get_password);
msg->component = component_name;
msg->key = key;
ep_msg_send(msg);
passwd = msg->password;
msg->password = NULL;
ep_msg_free(msg);
return passwd;
}
/**
* e_passwords_add_password:
* @key: a key
* @passwd: the password for @key
*
* This stores the @key/@passwd pair in the current session's password
* hash.
**/
void
e_passwords_add_password (const char *key, const char *passwd)
{
EPassMsg *msg;
g_return_if_fail(key != NULL);
g_return_if_fail(passwd != NULL);
msg = ep_msg_new(ep_add_password);
msg->key = key;
msg->oldpass = passwd;
ep_msg_send(msg);
ep_msg_free(msg);
}
/**
* e_passwords_ask_password:
* @title: title for the password dialog
* @component_name: the name of the component for which we're storing
* the password (e.g. Mail, Addressbook, etc.)
* @key: key to store the password under
* @prompt: prompt string
* @secret: whether or not the password text should be ***ed out
* @remember_type: whether or not to offer to remember the password,
* and for how long.
* @remember: on input, the default state of the remember checkbox.
* on output, the state of the checkbox when the dialog was closed.
* @parent: parent window of the dialog, or %NULL
*
* Asks the user for a password.
*
* Return value: the password, which the caller must free, or %NULL if
* the user cancelled the operation. *@remember will be set if the
* return value is non-%NULL and @remember_type is not
* E_PASSWORDS_DO_NOT_REMEMBER.
**/
char *
e_passwords_ask_password (const char *title, const char *component_name,
const char *key,
const char *prompt,
EPasswordsRememberType type,
gboolean *remember,
GtkWindow *parent)
{
char *passwd;
EPassMsg *msg = ep_msg_new(ep_ask_password);
if ((type & E_PASSWORDS_ONLINE) && !ep_online_state)
return NULL;
msg->title = title;
msg->component = component_name;
msg->key = key;
msg->prompt = prompt;
msg->flags = type;
msg->remember = remember;
msg->parent = parent;
ep_msg_send(msg);
passwd = msg->password;
msg->password = NULL;
ep_msg_free(msg);
return passwd;
}
static char *base64_alphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static unsigned char camel_mime_base64_rank[256] = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255,
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
};
/* call this when finished encoding everything, to
flush off the last little bit */
static int
base64_encode_close(unsigned char *in, int inlen, gboolean break_lines, unsigned char *out, int *state, int *save)
{
int c1, c2;
unsigned char *outptr = out;
if (inlen>0)
outptr += base64_encode_step(in, inlen, break_lines, outptr, state, save);
c1 = ((unsigned char *)save)[1];
c2 = ((unsigned char *)save)[2];
switch (((char *)save)[0]) {
case 2:
outptr[2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
g_assert(outptr[2] != 0);
goto skip;
case 1:
outptr[2] = '=';
skip:
outptr[0] = base64_alphabet[ c1 >> 2 ];
outptr[1] = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )];
outptr[3] = '=';
outptr += 4;
break;
}
if (break_lines)
*outptr++ = '\n';
*save = 0;
*state = 0;
return outptr-out;
}
/*
performs an 'encode step', only encodes blocks of 3 characters to the
output at a time, saves left-over state in state and save (initialise to
0 on first invocation).
*/
static int
base64_encode_step(unsigned char *in, int len, gboolean break_lines, unsigned char *out, int *state, int *save)
{
register unsigned char *inptr, *outptr;
if (len<=0)
return 0;
inptr = in;
outptr = out;
if (len + ((char *)save)[0] > 2) {
unsigned char *inend = in+len-2;
register int c1, c2, c3;
register int already;
already = *state;
switch (((char *)save)[0]) {
case 1: c1 = ((unsigned char *)save)[1]; goto skip1;
case 2: c1 = ((unsigned char *)save)[1];
c2 = ((unsigned char *)save)[2]; goto skip2;
}
/* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */
while (inptr < inend) {
c1 = *inptr++;
skip1:
c2 = *inptr++;
skip2:
c3 = *inptr++;
*outptr++ = base64_alphabet[ c1 >> 2 ];
*outptr++ = base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ];
*outptr++ = base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ];
*outptr++ = base64_alphabet[ c3 & 0x3f ];
/* this is a bit ugly ... */
if (break_lines && (++already)>=19) {
*outptr++='\n';
already = 0;
}
}
((char *)save)[0] = 0;
len = 2-(inptr-inend);
*state = already;
}
if (len>0) {
register char *saveout;
/* points to the slot for the next char to save */
saveout = & (((char *)save)[1]) + ((char *)save)[0];
/* len can only be 0 1 or 2 */
switch(len) {
case 2: *saveout++ = *inptr++;
case 1: *saveout++ = *inptr++;
}
((char *)save)[0]+=len;
}
return outptr-out;
}
/**
* base64_decode_step: decode a chunk of base64 encoded data
* @in: input stream
* @len: max length of data to decode
* @out: output stream
* @state: holds the number of bits that are stored in @save
* @save: leftover bits that have not yet been decoded
*
* Decodes a chunk of base64 encoded data
**/
static int
base64_decode_step(unsigned char *in, int len, unsigned char *out, int *state, unsigned int *save)
{
register unsigned char *inptr, *outptr;
unsigned char *inend, c;
register unsigned int v;
int i;
inend = in+len;
outptr = out;
/* convert 4 base64 bytes to 3 normal bytes */
v=*save;
i=*state;
inptr = in;
while (inptr<inend) {
c = camel_mime_base64_rank[*inptr++];
if (c != 0xff) {
v = (v<<6) | c;
i++;
if (i==4) {
*outptr++ = v>>16;
*outptr++ = v>>8;
*outptr++ = v;
i=0;
}
}
}
*save = v;
*state = i;
/* quick scan back for '=' on the end somewhere */
/* fortunately we can drop 1 output char for each trailing = (upto 2) */
i=2;
while (inptr>in && i) {
inptr--;
if (camel_mime_base64_rank[*inptr] != 0xff) {
if (*inptr == '=')
outptr--;
i--;
}
}
/* if i!= 0 then there is a truncation error! */
return outptr-out;
}
static char *
decode_base64 (char *base64)
{
char *plain, *pad = "==";
int len, out, state, save;
len = strlen (base64);
plain = g_malloc0 (len);
state = save = 0;
out = base64_decode_step (base64, len, plain, &state, &save);
if (len % 4) {
base64_decode_step (pad, 4 - len % 4, plain + out,
&state, &save);
}
return plain;
}