/*
    GQ -- a GTK-based LDAP client
    Copyright (C) 1998-2003 Bert Vermeulen
    Copyright (C) 2002-2003 Peter Stamfest

    This program is released under the Gnu General Public License with
    the additional exemption that compiling, linking, and/or using
    OpenSSL is allowed.

    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 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
*/

/* $Id$ */

#include <stdlib.h>
#include <string.h>  /* strlen */
#include <ctype.h>
#include <time.h>

#include <glib.h>
#include <glib/gi18n.h>

#include <lber.h>

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

#include "common.h"
#include "errorchain.h"
#include "gq-formfill.h" /* isInternalAttr() */
#include "gq-ldap.h"
#include "gq-server-dn.h"
#include "gq-utilities.h"
#include "ldif.h"

void prepend_ldif_header(GString *out, GList *to_export)
{
     time_t stamp;
     GString *tmp;
     char *username;
     GqServer *last = NULL;

     tmp = g_string_sized_new(256);
     username = get_username();
     time(&stamp);
     g_string_sprintf(tmp,
		      "#\n"
		      "# This file was generated by %s %s   (http://biot.com/gq/)\n"
		      "# run by %s %s#\n",
		      PACKAGE, VERSION, username ? username : "NULL",
		      ctime(&stamp));  /* f*** ctime - stupid trailing \n */

     while(to_export) {
	  GqServerDn* dos = to_export->data;
	  GqServer  * server = gq_server_dn_get_server(dos);
	  if (server != last) {
	       if (dos->flags == LDAP_SCOPE_SUBTREE) {
		    g_string_sprintfa(tmp, "# subtree search on server: %s\n",
				      server->canon_name);
	       } else {
		    g_string_sprintfa(tmp, "#    base search on server: %s\n",
				      server->canon_name);
	       }
	       g_string_sprintfa(tmp, "#                   binddn: %s\n",
				 (gq_server_get_bind_dn(server) && *gq_server_get_bind_dn(server)) ? gq_server_get_bind_dn(server) : "(anonymous)");
	       g_string_sprintfa(tmp, "#          searching below: ");

	       last = server;
	  } else {
	       g_string_sprintfa(tmp, "#                           ");
	  }
	  g_string_sprintfa(tmp, "%s\n", gq_server_dn_get_dn(dos));

	  to_export = to_export->next;
     }
     g_string_sprintfa(tmp, "# version: 1\n#\n");
     g_string_prepend(out, tmp->str);
     if (username) free(username);
     g_string_free(tmp, TRUE);
}


gboolean ldif_entry_out(GString *out, LDAP *ld, LDAPMessage *msg,
			int error_context)
{
     BerElement *berptr;
     int i;
     char *dn, *attr;
     struct berval **vals, *value;
     

     dn = ldap_get_dn(ld, msg);

     if (!dn) {
	  /* an error occurred */

	  error_push(error_context, _("Cannot retrieve DN of entry."));
	  push_ldap_addl_error(ld, error_context);
	  return FALSE;
     }

     ldif_line_out(out, "dn", dn, strlen(dn), error_context);
     g_string_append(out, "\n");
     ldap_memfree(dn);

     for(attr = ldap_first_attribute(ld, msg, &berptr); attr != NULL;
	 attr = ldap_next_attribute(ld, msg, berptr)) {

	  if (!isInternalAttr(attr)) {
	       vals = ldap_get_values_len(ld, msg, attr);
	       if(vals) {
		    for(i = 0; vals[i]; i++) {
			 /* should work for ;binary as-is */
			 value = vals[i];
			 ldif_line_out(out, attr,
				       value->bv_val, value->bv_len,
				       error_context);
			 g_string_append(out, "\n");
		    }
		    ldap_value_free_len(vals);
	       }
	  }
	  ldap_memfree(attr);
     }
#ifndef HAVE_OPENLDAP12
     if(berptr)
	  ber_free(berptr, 0);
#endif
     g_string_append(out, "\n");

     return(TRUE);
}


gboolean ldif_line_out(GString *out, char *attr, char *value, 
		       unsigned int vlen,
		       int error_context)
{
     GString *tmp;
     int i;
     int w, do_base64;
/*       char message[256]; */

     if (attr == NULL) {
	  /* NULL attributes make no sense */
	  error_push(error_context, 
		     _("NULL attribute name - cannot write such an attribute..."));
	  return FALSE;
     }
     
     if (value == NULL) {
	  /* NULL seems to be normal, at least with Netscape LDAP
             servers, tested with help from Cord Beermann
             <cord.beermann@mediaways.net> */

	  /* 
	     g_snprintf(message, sizeof(message),
	     _("NULL value for attribute %s - writing empty string instead..."),
	     attr);
	     error_push(error_context, message);
	  */
	  value = "";
	  vlen = 0;
     }

     g_string_append(out, attr);
     tmp = g_string_sized_new(64);

     do_base64 = 0;
     for(i = 0; i < (int) vlen && !do_base64; i++) {
	  if(!isascii( (int) value[i]) || !isprint( (int) value[i]))
	       do_base64 = 1;
     }

     if(do_base64) {
	  g_string_append_c(out, ':');
	  b64_encode(tmp, value, vlen);
     }
     else {
	  g_string_append(tmp, value);
     }

     g_string_append(out, ": ");

     w = strlen(attr) + do_base64 + 2;
     for(i = 0; i < (int) tmp->len; i++, w++) {
	  if(w > 76) {
	       g_string_append(out, "\n ");
	       w = 1;
	  }
	  g_string_append_c(out, tmp->str[i]);
     }

     g_string_free(tmp, TRUE);

     return(TRUE);
}

static const char ch[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

void b64_encode(GString *out, char *value, unsigned int vlen)
{
     unsigned int i, j;
     char chin[4], chout[5];

     j = 0;
     chin[0] = chin[1] = chin[2] = 0;
     chout[0] = chout[1] = chout[2] = chout[3] = chout[4] = 0;
     for(i = 0; i < vlen; i++) {
	  chin[j++] = value[i];

	  chout[0] = ch[(chin[0] >> 2) & 0x3f];
	  chout[1] = ch[((chin[0] << 4) & 0x30) | ((chin[1] >> 4) & 0x0f)];
	  chout[2] = ch[((chin[1] << 2) & 0x3c) | ((chin[2] >> 6) & 0x03)];
	  if(j == 3) {
	       chout[3] = ch[chin[2] & 0x3f];
	       g_string_append(out, chout);
	       chin[0] = chin[1] = chin[2] = 0;
	       chout[0] = chout[1] = chout[2] = chout[3] = 0;
	       j = 0;
	  }
     }

     if(j == 1)
	  chout[2] = chout[3] = '=';
     else if(j == 2)
	  chout[3] = '=';
     g_string_append(out, chout);
}


static const signed char decode[] = {
     -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
     -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
     -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
     -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
     
     -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
     -1,   -1,   -1, /* + */ 0x3e,   -1,   -1,   -1, /* / */ 0x3f,
     
     /* 0-9 */
     0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
     0x3c, 0x3d,   -1,   -1,   -1,   -1, -1, -1,
     
     /* A-Z */
     -1, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
     0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
     0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
     0x17, 0x18, 0x19,   -1,   -1,   -1,   -1,   -1,
     
     /* a-z */
     -1, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
     0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
     0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
     0x31, 0x32, 0x33,   -1,   -1,   -1,   -1,   -1,
};


void b64_decode(GByteArray *out, const char *value, unsigned int vlen) {
/*       int cnt = 0; */
     int eq = 0;
     
/*       for (i = 0 ; i < vlen ; i++) { */
/*  	  char c = buf[i]; */
/*  	  int d = decode[c]; */
/*  	  if (c == '=') { */
/*  	       eq++; */
/*  	       cnt++; */
/*  	  } */
/*  	  if (d != -1) cnt++; */
/*       } */
     
/*       int n = (cnt / 4) * 3; */
/*       if (cnt % 4 != 0) n++; */
/*       n -= eq; */
     
/*       byte result[] = new byte[n]; */
/*       int N = 0; */
     
     char tmp[] = { 0, 0, 0};
     unsigned int i, j = 0;
     signed int d;
     char c;

     for (i = 0 ; i < vlen ; i++) {
	  if (value[i] & 0x80) continue;
	  c = value[i] & 0x7f;
	  if (c == '=') eq++;
	  
	  d = decode[(int)c];
	  if (d == -1) continue;
	  
	  switch (j) {
	  case 0: {
	       tmp[0] |= (d << 2) & 0xfc;
	       j++;
	       break;
	  }
	  case 1: {
	       tmp[0] |= (d >> 4) & 0x03;
	       tmp[1] |= (d << 4) & 0xf0;
	       j++;
	       break;
	  }
	  case 2: {
	       tmp[1] |= (d >> 2) & 0x0f;
	       tmp[2] |= (d << 6) & 0xc0;
	       j++;
	       break;
	  }
	  case 3: {
	       tmp[2] |= d & 0x3f;
	       g_byte_array_append(out, (guchar*)tmp, 3);
	       tmp[0] = tmp[1] = tmp[2] = 0;
	       j = 0;
	       break;
	  }
	  }
     }
     
     if (j != 0) {
	  g_byte_array_append(out, (guchar*)tmp, 1);
	  if (eq < 2) g_byte_array_append(out, (guchar*)tmp + 1, 1);
	  if (eq < 1) g_byte_array_append(out, (guchar*)tmp + 2, 1);
     }
}

/* 
   Local Variables:
   c-basic-offset: 5
   End:
*/
