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

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

#include "gq-utilities.h"

#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>

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

#include <lber.h>
#ifdef HAVE_LDAP_STR2OBJECTCLASS
#    include <ldap_schema.h>
#endif
#ifdef LDAP_OPT_NETWORK_TIMEOUT
#include <sys/time.h>
#endif

#include <glade/glade.h>

#include "debug.h"
#include "configfile.h"
#include "common.h"
#include "encode.h"
#include "errorchain.h"
#include "gq-input-form.h"
#include "gq-ldap.h"
#include "gq-server-list.h"
#include "gq-window.h"		/* message_log_append */
#include "schema.h"
#include "template.h"

/*
 * clear cached server schema
 */
void clear_server_schema(GqServer *server)
{
#ifdef HAVE_LDAP_STR2OBJECTCLASS
     GList *list;
     struct server_schema *ss;

     if(server->ss) {
	  ss = server->ss;

	  /* objectclasses */
	  list = ss->oc;
	  if(list) {
	       while(list) {
		    ldap_objectclass_free(list->data);
		    list = list->next;
	       }
	       g_list_free(ss->oc);
	  }

	  /* attribute types */
	  list = ss->at;
	  if(list) {
	       while(list) {
		    ldap_attributetype_free(list->data);
		    list = list->next;
	       }
	       g_list_free(ss->at);
	  }

	  /* matching rules */
	  list = ss->mr;
	  if(list) {
	       while(list) {
		    ldap_matchingrule_free(list->data);
		    list = list->next;
	       }
	       g_list_free(ss->mr);
	  }

	  /* syntaxes */
	  list = ss->s;
	  if(list) {
	       while(list) {
		    ldap_syntax_free(list->data);
		    list = list->next;
	       }
	       g_list_free(ss->s);
	  }

	  FREE(server->ss, "struct server_schema");
	  server->ss = NULL;
     }
     else
	  server->flags &= ~SERVER_HAS_NO_SCHEMA;
#endif

}


/*
 * delete entry
 */
static gboolean
delete_entry_recursive(int          delete_context,
		       GqServer   * server,
		       gchar const* dn,
		       gboolean     recursive)
{
     LDAP *ld;
     int msg;
     gboolean rc = TRUE;
     LDAPControl c;
     LDAPControl *ctrls[2] = { NULL, NULL } ;
     LDAPMessage *res = NULL, *e;

     c.ldctl_oid		= LDAP_CONTROL_MANAGEDSAIT;
     c.ldctl_value.bv_val	= NULL;
     c.ldctl_value.bv_len	= 0;
     c.ldctl_iscritical	= 1;
     
     ctrls[0] = &c;

     /* FIXME confirm-mod check here */

     set_busycursor();

     if( (ld = open_connection(delete_context, server) ) == NULL) {
	  set_normalcursor();
	  return(FALSE);
     }

     if (recursive) {
	  int something_to_do = 1;
	  static char* attrs [] = {"dn", NULL};

	  while (something_to_do) {
	       something_to_do = 0;
	       msg = ldap_search_ext_s(ld, dn,
				       LDAP_SCOPE_ONELEVEL,
				       "(objectClass=*)",
				       (char **)attrs,	/* attrs */
				       1,		/* attrsonly */
				       ctrls,		/* serverctrls */
				       NULL,		/* clientctrls */
				       NULL,		/* timeout */
				       LDAP_NO_LIMIT,	/* sizelimit */
				       &res);

	       if(msg == LDAP_NOT_SUPPORTED) {
		    msg = ldap_search_s(ld, dn, LDAP_SCOPE_ONELEVEL,
					"(objectClass=*)",
					(char **) attrs, 1, &res);
	       }

	       if(msg == LDAP_SUCCESS) {
		    for (e = ldap_first_entry(ld, res); e ;
			 e = ldap_next_entry(ld, e)) {
			 char *dn2 = ldap_get_dn(ld, e);
			 gboolean delok = delete_entry_full(delete_context,
							    server, dn2);
			 if (dn2) ldap_memfree(dn2);

			 if (delok) {
			      something_to_do = 1;
			 } else {
			      goto done;
			 }
		    }
	       } else if (msg == LDAP_SERVER_DOWN) {
		    server->server_down++;
		    goto done;
	       }
	       if (res) ldap_msgfree(res);
	       res = NULL;
	  }
     }

     statusbar_msg(_("Deleting: %s"), dn);

     msg = ldap_delete_ext_s(ld, dn, ctrls, NULL);

     if(msg == LDAP_NOT_SUPPORTED) {
	  msg = ldap_delete_s(ld, dn);
     }

#if HAVE_LDAP_CLIENT_CACHE
     ldap_uncache_entry(ld, dn);
#endif

     if(msg != LDAP_SUCCESS) {
	  error_push(delete_context, 
		     "Error deleting DN '%1$s' on '%2$s': %3$s", 
		     dn, gq_server_get_name(server), ldap_err2string(msg));
	  rc = FALSE;
     }
     else {
	  statusbar_msg(_("Deleted %s"), dn);
     }

 done:
     if (res) ldap_msgfree(res);
     set_normalcursor();
     close_connection(server, FALSE);

     return(rc);
}


gboolean
delete_entry(int          delete_context,
	     GqServer   * server,
	     gchar const* dn)
{
	return delete_entry_recursive(delete_context, server, dn, FALSE);
}

gboolean
delete_entry_full(int          delete_context,
		  GqServer   * server,
		  gchar const* dn)
{
	return delete_entry_recursive(delete_context, server, dn, TRUE);
}

/*
 * display hourglass cursor on mainwin
 */
void set_busycursor(void)
{
     GdkCursor *busycursor;

     busycursor = gdk_cursor_new(GDK_WATCH);
     gdk_window_set_cursor(mainwin.mainwin->window, busycursor);
     gdk_cursor_destroy(busycursor);

}


/*
 * set mainwin cursor to default
 */
void set_normalcursor(void)
{

     gdk_window_set_cursor(mainwin.mainwin->window, NULL);

}

/*
 * callback for key_press_event on a widget, destroys obj if key was esc
 */
int close_on_esc(GtkWidget *widget, GdkEventKey *event, gpointer obj)
{
     if(event && event->type == GDK_KEY_PRESS && event->keyval == GDK_Escape) {
	  gtk_widget_destroy(GTK_WIDGET(obj));
	  g_signal_stop_emission_by_name(widget, "key_press_event");
	  return(TRUE);
     }

     return(FALSE);
}


/*
 * callback for key_press_event on a widget, calls func if key was esc
 */
int func_on_esc(GtkWidget *widget, GdkEventKey *event, GtkWidget *window)
{
     void (*func)(GtkWidget *);

     if(event && event->type == GDK_KEY_PRESS && event->keyval == GDK_Escape) {
	  func = gtk_object_get_data(GTK_OBJECT(window), "close_func");
	  func(widget);
	  g_signal_stop_emission_by_name(widget, "key_press_event");
	  return(TRUE);
     }

     return(FALSE);
}


int tokenize(const struct tokenlist *list, const char *keyword)
{
     int i;

     for(i = 0; list[i].keyword && strlen(list[i].keyword); i++)
	  if(!strcasecmp(list[i].keyword, keyword))
	       return(list[i].token);

     return(0);
}


const char *detokenize(const struct tokenlist *list, int token)
{
     int i;

     for(i = 0; list[i].keyword && strlen(list[i].keyword); i++)
	  if(list[i].token == token)
	       return(list[i].keyword);

     return(list[0].keyword);
}


const void *detokenize_data(const struct tokenlist *list, int token)
{
     int i;

     for(i = 0; strlen(list[i].keyword); i++)
	  if(list[i].token == token)
	       return(list[i].data);

     return(list[0].data);
}

/*
 * return pointer to username (must be freed)
 */
char *get_username(void)
{
     struct passwd *pwent;
     char *username;

     username = NULL;
     pwent = getpwuid(getuid());
     if(pwent && pwent->pw_name)
	  username = strdup(pwent->pw_name);
     endpwent();

     return(username);
}


/* these should probably belong to GqWindow */
#warning "FIXME: move into GqWindow"
static guint context = 0, msgid = 0;


/*
 * display message in main window's statusbar, and flushes the
 * GTK event queue
 */
void
statusbar_msg(gchar const* fmt, ...)
{
     /* do not use g_string_sprintf, as it usually does not support
	numbered arguments */
     int len = strlen(fmt) * 2;
     char *buf;
     int n;

     if (len > 0) {
	  for (;;) {
	       va_list ap;
	       buf = g_malloc(len);
	       *buf = 0;
	       
	       va_start(ap, fmt);
	       n = g_vsnprintf(buf, len, fmt, ap);
	       va_end(ap);
	       
	       if (n > len || n == -1) {
		    g_free(buf);
		    len *= 2;
		    continue;
	       }
	       break;
	  } 
     } else {
	  buf = g_strdup("");
     }

     statusbar_msg_clear();

     msgid = gtk_statusbar_push(GTK_STATUSBAR(mainwin.statusbar), 
				context, buf);
     message_log_append(buf);
     g_free(buf);

     /* make sure statusbar gets updated right away */
	gq_main_loop_flush();
}

void statusbar_msg_clear()
{
     if(!context)
	  context =
	       gtk_statusbar_get_context_id(GTK_STATUSBAR(mainwin.statusbar),
					    "mainwin");
     if(msgid)
	  gtk_statusbar_remove(GTK_STATUSBAR(mainwin.statusbar),
			       context, msgid);
}

/*
 * return pointer to GqServer matching the canonical name
 */
GqServer *server_by_canon_name(const char *name,
					gboolean include_transient)
{
     GqServer *server = NULL;
     GList *I;

     if(name == NULL || name[0] == '\0')
	  return NULL;

     server = gq_server_list_get_by_canon_name(gq_server_list_get(), name);
     if(!server && include_transient) {
	  for (I = transient_servers ; I ; I = g_list_next(I)) {
	       if(!strcmp(((GqServer*)I->data)->canon_name, name)) {
		    server = I->data;
		    break;
	       }
	  }
     }
     return server;
}

gboolean
is_transient_server(const GqServer *server) {
	g_return_val_if_fail(GQ_IS_SERVER(server), FALSE);
	return !gq_server_list_contains(gq_server_list_get(), server);
}

/*
 * check if entry has a subtree
 */
int is_leaf_entry(int error_context,
		  GqServer *server, char *dn)
{
     LDAP *ld;
     LDAPMessage *res;
     int msg, is_leaf, rc;
     char *attrs[] = {
	  LDAP_NO_ATTRS,
	  NULL
     };
     LDAPControl c;
     LDAPControl *ctrls[2] = { NULL, NULL } ;

     /*  ManageDSAit  */
     c.ldctl_oid		= LDAP_CONTROL_MANAGEDSAIT;
     c.ldctl_value.bv_val	= NULL;
     c.ldctl_value.bv_len	= 0;
     c.ldctl_iscritical	= 1;
     
     ctrls[0] = &c;

     is_leaf = 0;

     set_busycursor();

     if( (ld = open_connection(error_context, server) ) == NULL) {
	  set_normalcursor();
	  return(-1);
     }

     statusbar_msg(_("Checking subtree for %s"), dn);

     rc = ldap_search_ext(ld, dn, LDAP_SCOPE_ONELEVEL, "(objectclass=*)",
			   attrs,		/* attrs */
			   0,			/* attrsonly */
			   ctrls,		/* serverctrls */
			   NULL,		/* clientctrls */
			   NULL,		/* timeout */
			   LDAP_NO_LIMIT,	/* sizelimit */
			   &msg);

     if(rc != -1) {
	  if( (ldap_result(ld, msg, 0, NULL, &res) != LDAP_RES_SEARCH_ENTRY))
	       is_leaf = 1;
	  ldap_msgfree(res);
	  ldap_abandon(ld, msg);
     }

     close_connection(server, FALSE);
     set_normalcursor();
     statusbar_msg_clear();

     return(is_leaf);
}

/*
 * check if child is a direct subentry of possible_parent
 */
gboolean is_direct_parent(char *child, char *possible_parent) 
{
     /* SHOULD use gq_ldap_explode_dn for this */
     char *c = g_utf8_strchr(child, -1, ',');
     if (c && (strcasecmp(c + 1, possible_parent) == 0)) return TRUE;
     return FALSE;
}

/*
 * check if child is a (possibly indirect) subentry of possible_ancestor
 */
gboolean
is_ancestor(gchar const* child,
	    gchar const* possible_ancestor) 
{
     char **rdn = gq_ldap_explode_dn(child, FALSE);
     GString *s;
     int n;
     gboolean rc = FALSE;

     for (n = 0 ; rdn[n] ; n++) {
     }

     s = g_string_new("");
     for (n-- ; n >= 0 ; n--) {
	  g_string_insert(s, 0, rdn[n]);
	  if ((strcasecmp(s->str, possible_ancestor) == 0)) {
	       rc = TRUE;
	       break;
	  }
	  g_string_insert(s, 0, ",");
     }

     g_string_free(s, TRUE);
     gq_exploded_free(rdn);

     return rc;
}

GList *ar2glist(char *ar[])
{
     GList *tmp;
     int i;

     if(ar == NULL) {
	  /* gtk_combo_set_popdown_strings() can't handle an
	     empty list, so give it a list with an empty entry */
	  tmp = g_list_append(NULL, "");
	  return(tmp);
     }

     tmp = NULL;
     i = 0;
     while(ar[i])
	  tmp = g_list_append(tmp, ar[i++]);

     return(tmp);
}


/*
 * pops up a warning dialog (with hand icon), and displays all messages in
 * the GList , one per line. The GList is freed here.
 */
void warning_popup(GList *messages)
{
     GList *list;
     GtkWidget *window, *vbox1, *vbox2, *vbox3, *label, *hbox0;
     GtkWidget *hbox1, *ok_button;
     GtkWidget *pixmap;

     window = gtk_dialog_new();
     gtk_container_border_width(GTK_CONTAINER(window), CONTAINER_BORDER_WIDTH);
     gtk_window_set_title(GTK_WINDOW(window), _("Warning"));
     gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE);
     vbox1 = GTK_DIALOG(window)->vbox;
     gtk_widget_show(vbox1);

     hbox1 = gtk_hbox_new(FALSE, 0);
     gtk_widget_show(hbox1);
     gtk_box_pack_start(GTK_BOX(vbox1), hbox1, FALSE, FALSE, 10);
     pixmap = gtk_image_new_from_file(PACKAGE_PREFIX "/share/pixmaps/gq/warning.xpm");
     gtk_widget_show(pixmap);
     gtk_box_pack_start(GTK_BOX(hbox1), pixmap, TRUE, TRUE, 10);

     vbox2 = gtk_vbox_new(FALSE, 0);
     gtk_widget_show(vbox2);
     gtk_box_pack_start(GTK_BOX(hbox1), vbox2, TRUE, TRUE, 10);

     list = messages;
     while(list) {
	  label = gtk_label_new((char *) list->data);
	  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
	  gtk_widget_show(label);
	  gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, TRUE, 0);
	  list = list->next;
     }

     /* OK button */
     vbox3 = GTK_DIALOG(window)->action_area;
     gtk_widget_show(vbox3);

     hbox0 = gtk_hbutton_box_new();
     gtk_widget_show(hbox0);
     gtk_box_pack_start(GTK_BOX(vbox3), hbox0, TRUE, TRUE, 0);

     ok_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);

     g_signal_connect_swapped(ok_button, "clicked",
			       G_CALLBACK(gtk_widget_destroy),
			       window);
     gtk_box_pack_start(GTK_BOX(hbox0), ok_button, FALSE, FALSE, 0);
     GTK_WIDGET_SET_FLAGS(ok_button, GTK_CAN_DEFAULT);
     gtk_widget_grab_default(ok_button);
     gtk_widget_show(ok_button);

     /* what does this mean? PS: 20030928 - FIXME */
     g_signal_connect_swapped(window, "destroy",
			       G_CALLBACK(gtk_widget_destroy),
			       window);

     g_signal_connect_swapped(window, "key_press_event",
                               G_CALLBACK(close_on_esc),
                               window);

     gtk_widget_show(window);

     g_list_free(messages);

}


void single_warning_popup(char *message)
{
     GList *msglist;

     msglist = g_list_append(NULL, message);
     warning_popup(msglist);

}


#ifdef HAVE_LDAP_STR2OBJECTCLASS
GList *find_at_by_mr_oid(GqServer *server, const char *oid)
{
     GList *list, *srvlist;
     LDAPAttributeType *at;

     list = NULL;
     srvlist = server->ss->at;
     while(srvlist) {
	  at = (LDAPAttributeType *) srvlist->data;

	  if( (at->at_equality_oid && !strcasecmp(oid, at->at_equality_oid)) ||
	      (at->at_ordering_oid && !strcasecmp(oid, at->at_ordering_oid)) ||
	      (at->at_substr_oid && !strcasecmp(oid, at->at_substr_oid)) ) {
	       if(at->at_names && at->at_names[0])
		    list = g_list_append(list, at->at_names[0]);
	       else
		    list = g_list_append(list, at->at_oid);
	  }
	  srvlist = srvlist->next;
     }

     return(list);
}

LDAPAttributeType *find_canonical_at_by_at(struct server_schema *schema,
					   const char *attr)
{
     GList *atlist;
     char **n;
     
     if (!schema) return NULL;
     
     for ( atlist = schema->at ; atlist ; atlist = atlist->next ) {
	  LDAPAttributeType *at = (LDAPAttributeType *) atlist->data;
	  if (!at) continue;
	  
	  for (n = at->at_names ; n && *n ; n++) {
/*  	       printf("%s ", *n); */
	       if (strcasecmp(*n, attr) == 0) {
		    /* found! */
		    return at;
	       }
	  }
     }
     return NULL;
}

GList *find_at_by_s_oid(GqServer *server, const char *oid)
{
     GList *list, *srvlist;
     LDAPAttributeType *at;

     list = NULL;
     srvlist = server->ss->at;
     while(srvlist) {
	  at = (LDAPAttributeType *) srvlist->data;

	  if(at->at_syntax_oid && !strcasecmp(oid, at->at_syntax_oid)) {
	       if(at->at_names && at->at_names[0])
		    list = g_list_append(list, at->at_names[0]);
	       else
		    list = g_list_append(list, at->at_oid);
	  }
	  srvlist = srvlist->next;
     }

     return(list);
}


GList *find_mr_by_s_oid(GqServer *server, const char *oid)
{
     GList *list, *srvlist;
     LDAPMatchingRule *mr;

     list = NULL;
     srvlist = server->ss->mr;
     while(srvlist) {
	  mr = (LDAPMatchingRule *) srvlist->data;

	  if(mr->mr_syntax_oid && !strcasecmp(oid, mr->mr_syntax_oid)) {
	       if(mr->mr_names && mr->mr_names[0])
		    list = g_list_append(list, mr->mr_names[0]);
	       else
		    list = g_list_append(list, mr->mr_oid);
	  }
	  srvlist = srvlist->next;
     }

     return(list);
}


GList *find_oc_by_at(int error_context,
		     GqServer *server, const char *atname)
{
     GList *list, *oclist;
     LDAPObjectClass *oc;
     int i, found;
     struct server_schema *ss = NULL;

     list = NULL;

     if (server == NULL) return NULL;
     ss = get_schema(error_context, server);
     if (ss == NULL) return NULL;

     oclist = ss->oc;
     while(oclist) {
	  oc = (LDAPObjectClass *) oclist->data;

	  found = 0;

	  if(oc->oc_at_oids_must) {
	       i = 0;
	       while(oc->oc_at_oids_must[i] && !found) {
		    if(!strcasecmp(atname, oc->oc_at_oids_must[i])) {
			 found = 1;
			 if(oc->oc_names && oc->oc_names[0])
			      list = g_list_append(list, oc->oc_names[0]);
			 else
			      list = g_list_append(list, oc->oc_oid);
		    }
		    i++;
	       }
	  }

	  if(oc->oc_at_oids_may) {
	       i = 0;
	       while(oc->oc_at_oids_may[i] && !found) {
		    if(!strcasecmp(atname, oc->oc_at_oids_may[i])) {
			 found = 1;
			 if(oc->oc_names && oc->oc_names[0])
			      list = g_list_append(list, oc->oc_names[0]);
			 else
			      list = g_list_append(list, oc->oc_oid);
		    }
		    i++;
	       }
	  }

	  oclist = oclist->next;
     }

     return(list);
}

const char *find_s_by_at_oid(int error_context, GqServer *server,
			     const char *oid)
{
     GList *srvlist;
     LDAPAttributeType *at;
     char **c;
     struct server_schema *ss = NULL;

     if (server == NULL) return NULL;
     ss = get_schema(error_context, server);

     if(ss == NULL || ss->at == NULL)
	  return(NULL);

     srvlist = ss->at;
     while(srvlist) {
	  at = (LDAPAttributeType *) srvlist->data;

	  for (c = at->at_names ; c && *c ; c++) {
	      if (!strcasecmp(oid, *c)) {
		  return at->at_syntax_oid;
	      }
	  }
	  srvlist = srvlist->next;
     }

     return NULL;
}

#else /* HAVE_LDAP_STR2OBJECTCLASS */


/* fall back to attributeName to find syntax. */

struct oid2syntax_t {
     const char *oid;
     const char *syntax;
};

static struct oid2syntax_t oid2syntax[] = {
     { "userPassword", "1.3.6.1.4.1.1466.115.121.1.40" },
     { "jpegPhoto", "1.3.6.1.4.1.1466.115.121.1.28" },
     { "audio", "1.3.6.1.4.1.1466.115.121.1.4" },
     { "photo", "1.3.6.1.4.1.1466.115.121.1.4" },
     { NULL, NULL },
};

const char *find_s_by_at_oid(GqServer *server, const char *oid)
{
     struct oid2syntax_t *os;
     for (os = oid2syntax ; os->oid ; os++) {
	  if (strcasecmp(os->oid, oid) == 0) return os->syntax;
     }
     
     return "1.3.6.1.4.1.1466.115.121.1.3";
}


#endif /* HAVE_LDAP_STR2OBJECTCLASS */


struct gq_template *find_template_by_name(const char *templatename)
{
     GList *I;

     for(I = config->templates ; I ; I = g_list_next(I)) {
	  struct gq_template *template = 
	       (struct gq_template *) I->data;
	  if(!strcasecmp(templatename, template->name))
	       return template;
     }
     return NULL;
}



void dump_mods(LDAPMod **mods)
{
     LDAPMod *mod;
     int cmod, cval;

     cmod = 0;
     while(mods[cmod]) {
	  mod = mods[cmod];
	  switch(mod->mod_op) {
	  case LDAP_MOD_ADD: printf("LDAP_MOD_ADD"); break;
	  case LDAP_MOD_DELETE: printf("LDAP_MOD_DELETE"); break;
	  case LDAP_MOD_REPLACE: printf("LDAP_MOD_REPLACE"); break;
	  case LDAP_MOD_BVALUES: printf("LDAP_MOD_BVALUES"); break;
	  }
	  printf(" %s\n", mod->mod_type);
	  cval = 0;
	  while(mod->mod_values && mod->mod_values[cval]) {
	       printf("\t%s\n", mod->mod_values[cval]);
	       cval++;
	  }

	  cmod++;
     }


}

struct popup_comm {
     int destroyed;
     int ended;
     int rc;
};

static struct popup_comm *new_popup_comm() 
{
     struct popup_comm *n = g_malloc0(sizeof(struct popup_comm));
     n->ended = n->destroyed = n->rc = 0;
     return n;
}

static void free_popup_comm(struct popup_comm *c)
{
     g_assert(c);
     g_free(c);
}

/* gtk2 checked (multiple destroy callbacks safety), confidence 0.7 */
static void
query_destroy(struct popup_comm* comm)
{
     g_assert(comm);
     if (!comm) return;
     if (!comm->ended)
	  gtk_main_quit(); /* quits only nested main loop */
     comm->destroyed = 1;
}

static void
query_ok(struct popup_comm* comm)
{
     gtk_main_quit();
     comm->rc = 1;
}

static void
query_cancel(struct popup_comm* comm)
{
     gtk_main_quit();
     comm->rc = 0;
}

/* pops up a dialog to retrieve user data via a GtkEntry. This
   functions waits for the data and puts it into outbuf.

   inout_buf afterward points to allocate memory that has to be free'd
   using g_free. As an input parameter it points to the current value
   of the information to be entered (if not NULL). The passed-in
   information will not be changed.
*/

int query_popup(const char *title, gchar **inout_buf, gboolean is_password,
		GtkWidget *modal_for)
{
     GtkWidget *window, *vbox1, *vbox2, *label, *inputbox, *button, *hbox0;
     int rc;
     GtkWidget *f = gtk_grab_get_current();
     struct popup_comm *comm = new_popup_comm();

     /* This is a BAD hack - it solves a problem with the query popup
        dialog that locks up focus handling with all the
        window-managers I have been able to test this with. Actually,
        it should be sufficient to let go of the focus, but
        hiding/showing seems to work... (as I do not know how to
        release the focus in gtk) - Any gtk Hackers around? */
     if (f != NULL) {
	 gtk_widget_hide(f);
	 gtk_widget_show(f);
     }

     if (modal_for) {
	  modal_for = gtk_widget_get_toplevel(modal_for);
     }

     window = gtk_dialog_new();
     gtk_container_border_width(GTK_CONTAINER(window), CONTAINER_BORDER_WIDTH);
     gtk_window_set_title(GTK_WINDOW(window), title);
     gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE);
	g_signal_connect_swapped(window, "destroy",
				 G_CALLBACK(query_destroy), comm);
     g_signal_connect_swapped(window, "key_press_event",
                               G_CALLBACK(close_on_esc),
                               window);

     if (modal_for) {
	  gtk_window_set_modal(GTK_WINDOW(window), TRUE);
	  gtk_window_set_transient_for(GTK_WINDOW(window),
				       GTK_WINDOW(modal_for));
     }

     vbox1 = GTK_DIALOG(window)->vbox;
     gtk_widget_show(vbox1);

     label = gtk_label_new(title);
     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
     gtk_widget_show(label);
     gtk_box_pack_start(GTK_BOX(vbox1), label, TRUE, TRUE, 0);

     inputbox = gtk_entry_new();

     GTK_WIDGET_SET_FLAGS(inputbox, GTK_CAN_FOCUS);
     GTK_WIDGET_SET_FLAGS(inputbox, GTK_CAN_DEFAULT);
     if (is_password) {
	  gtk_entry_set_visibility(GTK_ENTRY(inputbox), FALSE);
     }

     if (inout_buf && *inout_buf) {
	  int pos = 0;
	  gtk_editable_insert_text(GTK_EDITABLE(inputbox),
				   *inout_buf, strlen(*inout_buf), &pos);
     }

     gtk_widget_show(inputbox);
	g_signal_connect_swapped(inputbox, "activate",
				 G_CALLBACK(query_ok), comm);
     gtk_box_pack_end(GTK_BOX(vbox1), inputbox, TRUE, TRUE, 5);

     vbox2 = GTK_DIALOG(window)->action_area;
     gtk_container_border_width(GTK_CONTAINER(vbox2), 0);
     gtk_widget_show(vbox2);

     hbox0 = gtk_hbutton_box_new();
     gtk_widget_show(hbox0);
     gtk_box_pack_start(GTK_BOX(vbox2), hbox0, TRUE, TRUE, 0);

     button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);

	g_signal_connect_swapped(button, "clicked",
				 G_CALLBACK(query_ok), comm);
     gtk_box_pack_end(GTK_BOX(hbox0), button, FALSE, FALSE, 0);
     GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
     GTK_WIDGET_SET_FLAGS(button, GTK_RECEIVES_DEFAULT);
     gtk_widget_grab_default(button);
     gtk_widget_show(button);

     button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);

	g_signal_connect_swapped(button, "clicked",
				 G_CALLBACK(query_cancel), comm);

     gtk_box_pack_end(GTK_BOX(hbox0), button, FALSE, FALSE, 0);
     gtk_widget_show(button);

/*       gtk_window_set_transient_for(GTK_WINDOW(window),  */
/*  				  GTK_WINDOW(getMainWin())); */
     gtk_widget_grab_focus(GTK_WIDGET(window));
     gtk_window_set_modal(GTK_WINDOW(window), TRUE);

     gtk_widget_show(window);
     gtk_widget_grab_focus(inputbox);

     gtk_main();
     comm->ended = 1;

     if (! comm->destroyed && comm->rc) {
	 *inout_buf = gtk_editable_get_chars(GTK_EDITABLE(inputbox), 0, -1);
     } else {
	 *inout_buf = NULL;
     }

     if (! comm->destroyed ) {
	 gtk_widget_destroy(window);
     }

     rc = comm->rc;
     free_popup_comm(comm);

     return rc;
}


/* pops up a question dialog to ask the user a simple question. This
   functions waits for the answer and returns it. */
gboolean
question_popup(GtkWindow  * parent,
	       gchar const* title,
	       gchar const* question)
{
	GtkWidget* window;
	gboolean   rc;
	GtkWidget* f = gtk_grab_get_current();

     /* This is a BAD hack - it solves a problem with the query popup
        dialog that locks up focus handling with all the
        window-managers I have been able to test this with. Actually,
        it should be sufficient to let go of the focus, but
        hiding/showing seems to work... (as I do not know how to
        release the focus in gtk) - Any gtk Hackers around? */
#warning "FIXME: we should be able to remove this one"
	if (f != NULL) {
		gtk_widget_hide(f);
		gtk_widget_show(f);
	}

	window = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL,
					GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
					"%s", title);
	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(window),
						 "%s", question);
	gtk_window_set_title(GTK_WINDOW(window), title);
	gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE);

	rc = gtk_dialog_run(GTK_DIALOG(window)) == GTK_RESPONSE_YES;
	gtk_widget_destroy(window);

	return rc;
}

/*
 * get all suffixes a server considers itself authorative for.
 */

GList *get_suffixes(int error_context, GqServer *server)
{
     LDAP *ld;
     LDAPMessage *res, *e;
     int msg, i;
     int num_suffixes = 0;
     char **vals;
     char *ldapv3_config[] = {
	  "namingcontexts",
	  NULL
     };
     
     GList *suffixes = NULL;
     
     set_busycursor();
     
     if( (ld = open_connection(error_context, server)) == NULL) {
	  set_normalcursor();
	  return NULL;
     }
     
     /* try LDAP V3 style config */
     statusbar_msg(_("Base search on NULL DN on server '%s'"), gq_server_get_name(server));
     msg = ldap_search_s(ld, "", LDAP_SCOPE_BASE, "(objectclass=*)",
			 ldapv3_config, 0, &res);
     if(msg == LDAP_SUCCESS) {
	  e = ldap_first_entry(ld, res);
	  while (e) {
	       vals = ldap_get_values(ld, e, "namingcontexts");
	       if (vals) {
		    for(i = 0; vals[i]; i++) {
			 suffixes = g_list_append(suffixes, g_strdup(vals[i]));
/*  			 add_suffix(entry, ctreeroot, node, vals[i]); */
			 num_suffixes++;
		    }
		    ldap_value_free(vals);
	       }
	       e = ldap_next_entry(ld, e);
	  }
	  ldap_msgfree(res);
     } else if (msg == LDAP_SERVER_DOWN) {
	  server->server_down++;
	  /* do not try V2 in case of server down problems */
     }

     if(num_suffixes == 0) {
	  /* try Umich style config */
	  statusbar_msg(_("Base search on cn=config"));
	  msg = ldap_search_s(ld, "cn=config", LDAP_SCOPE_BASE,
			      "(objectclass=*)", NULL, 0, &res);
	  
	  if(msg == LDAP_SUCCESS) {
	       e = ldap_first_entry(ld, res);
	       while (e) {
		    char **valptr;
		    
		    vals = ldap_get_values(ld, e, "database");
		    if (vals) {
			 for (valptr = vals; valptr && *valptr; valptr++) {
			      char *p = *valptr;
				   
			      i = 0;
			      while (p[i] && p[i] != ':')
				   i++;
			      while (p[i] && (p[i] == ':' || p[i] == ' '))
				   i++;
			      if (p[i]) {
				   int len = strlen(p + i);
					
				   while (p[i + len - 1] == ' ')
					len--;
				   p[i + len] = '\0';

				   suffixes = g_list_append(suffixes,
							    g_strdup(p + i));
/*				   add_suffix(entry, ctreeroot, node, p + i); */
				   num_suffixes++;
			      }
			 }
			 ldap_value_free(vals);
		    }
		    e = ldap_next_entry(ld, e);
	       }
	       ldap_msgfree(res);
	  } else if (msg == LDAP_SERVER_DOWN) {
	       server->server_down++;
	  }
     }


     /* add the configured base DN if it's a different one */
     if(strlen(server->basedn) && (g_list_find_custom(suffixes, server->basedn, (GCompareFunc) strcmp) == NULL)) {
		suffixes = g_list_append(suffixes, g_strdup(server->basedn));
	        num_suffixes++;
     }

     set_normalcursor();
     close_connection(server, FALSE);

     statusbar_msg(ngettext("One suffix found", "%d suffixes found",
			    num_suffixes),
		   num_suffixes);

     return g_list_first(suffixes);
}

#ifdef HAVE_LDAP_STR2DN

/* OpenLDAP 2.1 both deprecated and broke ldap_explode_dn in one go (I
   won't comment on this).

   NOTE: this is just a first try to adapt code from Pierangelo
   Masarati <masarati@aero.polimi.it>
*/

gchar**
gq_ldap_explode_dn(gchar const* dn,
		   int          dummy G_GNUC_UNUSED)
{
	int i, rc;
#if LDAP_API_VERSION > 2004
	LDAPDN parts;
#else
	LDAPDN *parts;
#endif
	GArray* array = NULL;
	gchar **retval = NULL;

	rc = ldap_str2dn(dn, &parts, LDAP_DN_FORMAT_LDAPV3);

	if (rc != LDAP_SUCCESS || parts == NULL) {
		return NULL;
	}

	array = g_array_new(TRUE, TRUE, sizeof(gchar*));
	for(i = 0; parts[i]; i++) {
		gchar* part = NULL;
		ldap_rdn2str(
#if LDAP_API_VERSION > 2004
			parts[i],
#else
			parts[0][i],
#endif
			&part, LDAP_DN_FORMAT_LDAPV3 | LDAP_DN_PRETTY );
		if(part && *part) {
			/* don't append the last (empty) part, to be compatible
			 * to ldap_explode_dn() */
			g_array_append_val(array, part);
		}
	}
	retval = (gchar**)array->data;
	g_array_free(array, FALSE);
	return retval;
}

void gq_exploded_free(char **exploded_dn)
{
     if (exploded_dn) free(exploded_dn);
}

#endif

static GtkWidget *enable_uline(GtkWidget *label) {
     gtk_label_set_use_underline(GTK_LABEL(label), TRUE);
     return label;
}

static GtkWidget *bin_enable_uline(GtkWidget *w) {
     GtkWidget *l;
     l = gtk_bin_get_child(GTK_BIN(w));
     enable_uline(l);
     return w;
}


GtkWidget *gq_label_new(const char *text) {
     return gtk_label_new_with_mnemonic(text);
}


GtkWidget *gq_radio_button_new_with_label(GSList *group,
					  const gchar *label) 
{
     return bin_enable_uline(gtk_radio_button_new_with_label(group, label));
}

GtkWidget *gq_menu_item_new_with_label(const gchar *text) {
     return bin_enable_uline(gtk_menu_item_new_with_label(text));
}

GtkWidget *gq_check_button_new_with_label(const gchar *text) {
     return bin_enable_uline(gtk_check_button_new_with_label(text));
}

GtkWidget *gq_button_new_with_label(const gchar *text) {
     return bin_enable_uline(gtk_button_new_with_label(text));
}

GtkWidget *gq_toggle_button_new_with_label(const gchar *text) {
     return bin_enable_uline(gtk_toggle_button_new_with_label(text));
}

