Forgot to add main part of GSSAPI support
authorDaniel Kouřil <kouril@ics.muni.cz>
Fri, 20 Aug 2004 13:31:47 +0000 (13:31 +0000)
committerDaniel Kouřil <kouril@ics.muni.cz>
Fri, 20 Aug 2004 13:31:47 +0000 (13:31 +0000)
org.glite.lb.common/interface/lb_gss.h [new file with mode: 0644]
org.glite.lb.common/src/lb_gss.c [new file with mode: 0644]

diff --git a/org.glite.lb.common/interface/lb_gss.h b/org.glite.lb.common/interface/lb_gss.h
new file mode 100644 (file)
index 0000000..49dd2c2
--- /dev/null
@@ -0,0 +1,108 @@
+#ifndef __EDG_WORKLOAD_LOGGING_COMMON_LB_GSS_H__
+#define __EDG_WORKLOAD_LOGGING_COMMON_LB_GSS_H__
+
+#ident "$Header$"
+
+#include <gssapi.h>
+
+enum {
+  EDG_WLL_GSS_OK               =  0,  /* no GSS errors */
+  EDG_WLL_GSS_ERROR_GSS                = -1,  /* GSS specific error, call edg_wll_get_gss_error() for details */
+  EDG_WLL_GSS_ERROR_TIMEOUT    = -2,  /* Timeout */
+  EDG_WLL_GSS_ERROR_EOF                = -3,  /* EOF occured */
+  EDG_WLL_GSS_ERROR_ERRNO      = -4,  /* System error. See errno */
+  EDG_WLL_GSS_ERROR_HERRNO     = -5   /* Resolver error. See h_errno */
+};
+
+typedef struct _edg_wll_GssConnection {
+  gss_ctx_id_t context;
+  int sock;
+  char buffer[BUFSIZ];
+  size_t bufsize;
+} edg_wll_GssConnection;
+
+typedef struct _edg_wll_GssStatus {
+  OM_uint32 major_status;
+  OM_uint32 minor_status;
+} edg_wll_GssStatus;
+
+/* XXX Support anonymous connections. Are we able/required to support
+ * anonymous servers as well. */
+
+int
+edg_wll_gss_acquire_cred_gsi(char *proxy_file,
+                            gss_cred_id_t *cred,
+                            char **name,
+                            edg_wll_GssStatus* gss_code);
+
+int 
+edg_wll_gss_connect(gss_cred_id_t cred,
+                   char const *hostname,
+                   int port,
+                   struct timeval *timeout,
+                   edg_wll_GssConnection *connection,
+                   edg_wll_GssStatus* gss_code);
+
+int
+edg_wll_gss_accept(gss_cred_id_t cred,
+                  int sock,
+                  struct timeval *timeout,
+                  edg_wll_GssConnection *connection,
+                  edg_wll_GssStatus* gss_code);
+
+int
+edg_wll_gss_read(edg_wll_GssConnection *connection,
+                void *buf,
+                size_t bufsize,
+                struct timeval *timeout,
+                edg_wll_GssStatus* gss_code);
+
+int
+edg_wll_gss_write(edg_wll_GssConnection *connection,
+                 const void *buf,
+                 size_t bufsize,
+                 struct timeval *timeout,
+                 edg_wll_GssStatus* gss_code);
+
+int
+edg_wll_gss_read_full(edg_wll_GssConnection *connection,
+                     void *buf,
+                     size_t bufsize,
+                     struct timeval *timeout,
+                     size_t *total,
+                     edg_wll_GssStatus* gss_code);
+
+int
+edg_wll_gss_write_full(edg_wll_GssConnection *connection,
+                      const void *buf,
+                      size_t bufsize,
+                      struct timeval *timeout,
+                      size_t *total,
+                      edg_wll_GssStatus* gss_code);
+
+int
+edg_wll_gss_watch_creds(const char * proxy_file,
+                       time_t * proxy_mtime);
+
+int
+edg_wll_gss_get_error(edg_wll_GssStatus* gss_code,
+                     const char *prefix,
+                     char **errmsg);
+
+int
+edg_wll_gss_close(edg_wll_GssConnection *connection,
+                 struct timeval *timeout);
+
+int 
+edg_wll_gss_reject(int sock);
+
+int
+edg_wll_gss_oid_equal(const gss_OID a,
+                     const gss_OID b);
+
+/*
+int
+edg_wll_gss_get_name(gss_cred_id_t cred, char **name);
+*/
+
+#endif /* __EDG_WORKLOAD_LOGGING_COMMON_LB_GSS_H__ */
diff --git a/org.glite.lb.common/src/lb_gss.c b/org.glite.lb.common/src/lb_gss.c
new file mode 100644 (file)
index 0000000..7b2054f
--- /dev/null
@@ -0,0 +1,877 @@
+#ident "$Header$"
+
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <time.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <ares.h>
+#include <errno.h>
+
+#include "lb_gss.h"
+
+#define tv_sub(a,b) {\
+       (a).tv_usec -= (b).tv_usec;\
+       (a).tv_sec -= (b).tv_sec;\
+       if ((a).tv_usec < 0) {\
+               (a).tv_sec--;\
+               (a).tv_usec += 1000000;\
+       }\
+}
+
+struct asyn_result {
+       struct hostent *ent;
+       int             err;
+};
+
+static int decrement_timeout(struct timeval *timeout, struct timeval before, struct timeval after)
+{
+        (*timeout).tv_sec = (*timeout).tv_sec - (after.tv_sec - before.tv_sec);
+        (*timeout).tv_usec = (*timeout).tv_usec - (after.tv_usec - before.tv_usec);
+        while ( (*timeout).tv_usec < 0) {
+                (*timeout).tv_sec--;
+                (*timeout).tv_usec += 1000000;
+        }
+        if ( ((*timeout).tv_sec < 0) || (((*timeout).tv_sec == 0) && ((*timeout).tv_usec == 0)) ) return(1);
+        else return(0);
+}
+
+/* ares callback handler for ares_gethostbyname()       */
+static void callback_handler(void *arg, int status, struct hostent *h) {
+       struct asyn_result *arp = (struct asyn_result *) arg;
+
+       switch (status) {
+          case ARES_SUCCESS:
+               if (h && h->h_addr_list[0]) {
+                       arp->ent->h_addr_list =
+                               (char **) malloc(2 * sizeof(char *));
+                       if (arp->ent->h_addr_list == NULL) {
+                               arp->err = NETDB_INTERNAL;
+                               break;
+                       }
+                       arp->ent->h_addr_list[0] =
+                               malloc(sizeof(struct in_addr));
+                       if (arp->ent->h_addr_list[0] == NULL) {
+                               free(arp->ent->h_addr_list);
+                               arp->err = NETDB_INTERNAL;
+                               break;
+                       }
+                       memcpy(arp->ent->h_addr_list[0], h->h_addr_list[0],
+                               sizeof(struct in_addr));
+                       arp->ent->h_addr_list[1] = NULL;
+                       arp->err = NETDB_SUCCESS;
+               } else {
+                       arp->err = NO_DATA;
+               }
+               break;
+           case ARES_EBADNAME:
+           case ARES_ENOTFOUND:
+               arp->err = HOST_NOT_FOUND;
+               break;
+           case ARES_ENOTIMP:
+               arp->err = NO_RECOVERY;
+               break;
+           case ARES_ENOMEM:
+           case ARES_EDESTRUCTION:
+           default:
+               arp->err = NETDB_INTERNAL;
+               break;
+       }
+}
+
+static void free_hostent(struct hostent *h){
+        int i;
+
+        if (h) {
+                if (h->h_name) free(h->h_name);
+               if (h->h_aliases) {
+                       for (i=0; h->h_aliases[i]; i++) free(h->h_aliases[i]);
+                       free(h->h_aliases);
+               }
+                if (h->h_addr_list) {
+                        for (i=0; h->h_addr_list[i]; i++) free(h->h_addr_list[i]);
+                        free(h->h_addr_list);
+                }
+                free(h);
+        }
+}
+
+static int asyn_gethostbyname(char **addrOut, char const *name, struct timeval *timeout) {
+       struct asyn_result ar;
+       ares_channel channel;
+       int nfds;
+       fd_set readers, writers;
+       struct timeval tv, *tvp;
+       struct timeval start_time,check_time;
+
+/* start timer */
+       gettimeofday(&start_time,0);
+
+/* ares init */
+       if ( ares_init(&channel) != ARES_SUCCESS ) return(NETDB_INTERNAL);
+       ar.ent = (struct hostent *) calloc (sizeof(*ar.ent),1);
+
+/* query DNS server asynchronously */
+       ares_gethostbyname(channel, name, AF_INET, callback_handler,
+                          (void *) &ar);
+
+/* wait for result */
+       while (1) {
+               FD_ZERO(&readers);
+               FD_ZERO(&writers);
+               nfds = ares_fds(channel, &readers, &writers);
+               if (nfds == 0)
+                       break;
+
+               gettimeofday(&check_time,0);
+               if (decrement_timeout(timeout, start_time, check_time)) {
+                       ares_destroy(channel);
+                       free_hostent(ar.ent);
+                       return(TRY_AGAIN);
+               }
+               start_time = check_time;
+
+               tvp = ares_timeout(channel, timeout, &tv);
+
+               switch ( select(nfds, &readers, &writers, NULL, tvp) ) {
+                       case -1: if (errno != EINTR) {
+                                       ares_destroy(channel);
+                                       free_hostent(ar.ent);
+                                       return NETDB_INTERNAL;
+                                } else
+                                       continue;
+                       case 0: 
+                               FD_ZERO(&readers);
+                               FD_ZERO(&writers);
+                               /* fallthrough */
+                       default: ares_process(channel, &readers, &writers);
+               }
+       }
+
+       ares_destroy(channel);
+
+       if (ar.err == NETDB_SUCCESS) {
+               *addrOut = malloc(sizeof(struct in_addr));
+               memcpy(*addrOut,ar.ent->h_addr_list[0], sizeof(struct in_addr));
+               free_hostent(ar.ent);
+       }
+       return(ar.err);
+}
+
+static int
+do_connect(int *s, char const *hostname, int port, struct timeval *timeout)
+{
+   int sock;
+   struct timeval before,after,to;
+   struct sockaddr_in a;
+   int sock_err;
+   socklen_t err_len;
+   char *addr;
+   int h_errno;
+
+   sock = socket(AF_INET, SOCK_STREAM, 0);
+   if (sock < 0) return EDG_WLL_GSS_ERROR_ERRNO;
+
+   if (timeout) {
+            int        flags = fcntl(sock, F_GETFL, 0);
+            if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0)
+                    return EDG_WLL_GSS_ERROR_ERRNO;
+            gettimeofday(&before,NULL);
+   }
+   
+   if (timeout) {
+      switch (h_errno = asyn_gethostbyname(&addr, hostname, timeout)) {
+               case NETDB_SUCCESS:
+                       memset(&a,0,sizeof a);
+                       a.sin_family = AF_INET;
+                       memcpy(&a.sin_addr.s_addr,addr,sizeof a.sin_addr.s_addr);
+                       a.sin_port = htons(port);
+                       free(addr);
+                       break;
+               case TRY_AGAIN:
+                       close(sock);
+                       return EDG_WLL_GSS_ERROR_TIMEOUT;
+               case NETDB_INTERNAL: 
+                       /* fall through */
+               default:
+                       close(sock);
+                       /* h_errno may be thread safe with Linux pthread libs,
+                        * but such an assumption is not portable
+                        */
+                       errno = h_errno;
+                       return EDG_WLL_GSS_ERROR_HERRNO;
+      }
+   } else {
+      struct hostent *hp;
+
+      hp = gethostbyname(hostname);
+      if (hp == NULL) {
+        close(sock);
+        errno = h_errno;
+        return EDG_WLL_GSS_ERROR_HERRNO;
+      }
+
+      memset(&a,0,sizeof a);
+      a.sin_family = AF_INET;
+      memcpy(&a.sin_addr.s_addr, hp->h_addr_list[0], sizeof(a.sin_addr.s_addr));
+      a.sin_port = htons(port);
+   }
+
+   if (connect(sock,(struct sockaddr *) &a,sizeof a) < 0) {
+            if (timeout && errno == EINPROGRESS) {
+                    fd_set     fds;
+                    FD_ZERO(&fds);
+                    FD_SET(sock,&fds);
+                    memcpy(&to,timeout,sizeof to);
+                    gettimeofday(&before,NULL);
+                    switch (select(sock+1,NULL,&fds,NULL,&to)) {
+                            case -1: close(sock);
+                                     return EDG_WLL_GSS_ERROR_ERRNO;
+                            case 0: close(sock);
+                                    return EDG_WLL_GSS_ERROR_TIMEOUT;
+                    }
+                    gettimeofday(&after,NULL);
+                    tv_sub(after,before);
+                    tv_sub(*timeout,after);
+
+                    err_len = sizeof sock_err;
+                    if (getsockopt(sock,SOL_SOCKET,SO_ERROR,&sock_err,&err_len)) {
+                            close(sock);
+                            return EDG_WLL_GSS_ERROR_ERRNO;
+                    }
+                    if (sock_err) {
+                            close(sock);
+                            errno = sock_err;
+                            return EDG_WLL_GSS_ERROR_ERRNO;
+                    }
+            }
+            else {
+                    close(sock);
+                    return EDG_WLL_GSS_ERROR_ERRNO;
+            }
+   }
+
+   *s = sock;
+   return 0;
+}
+
+static int
+send_token(int sock, void *token, size_t token_length, struct timeval *to)
+{
+   size_t num_written = 0;
+   ssize_t count;
+   fd_set fds;
+   struct timeval timeout,before,after;
+   int ret;
+
+   if (to) {
+       memcpy(&timeout,to,sizeof(timeout));
+       gettimeofday(&before,NULL);
+   }
+
+
+   ret = 0;
+   while(num_written < token_length) {
+      FD_ZERO(&fds);
+      FD_SET(sock,&fds);
+      switch (select(sock+1, NULL, &fds, NULL, to ? &timeout : NULL)) {
+        case 0: ret = EDG_WLL_GSS_ERROR_TIMEOUT;
+                goto end;
+                break;
+        case -1: ret = EDG_WLL_GSS_ERROR_ERRNO;
+                 goto end;
+                 break;
+      }
+
+      count = write(sock, ((char *)token) + num_written,
+                   token_length - num_written);
+      if(count < 0) {
+        if(errno == EINTR)
+           continue;
+        else {
+           ret = EDG_WLL_GSS_ERROR_ERRNO;
+           goto end;
+        }
+      }
+      num_written += count;
+   }
+
+end:
+   if (to) {
+      gettimeofday(&after,NULL);
+      tv_sub(after,before);
+      tv_sub(*to,after);
+      if (to->tv_sec < 0) {
+        to->tv_sec = 0;
+        to->tv_usec = 0;
+      }
+   }
+
+   return ret;
+}
+
+static int
+recv_token(int sock, void **token, size_t *token_length, struct timeval *to)
+{
+   ssize_t count;
+   char buf[4098];
+   char *t = NULL;
+   char *tmp;
+   size_t tl = 0;
+   fd_set fds;
+   struct timeval timeout,before,after;
+   int ret;
+
+   if (to) {
+      memcpy(&timeout,to,sizeof(timeout));
+      gettimeofday(&before,NULL);
+   }
+
+   ret = 0;
+   do {
+      FD_ZERO(&fds);
+      FD_SET(sock,&fds);
+      switch (select(sock+1, &fds, NULL, NULL, to ? &timeout : NULL)) {
+        case 0: 
+           ret = EDG_WLL_GSS_ERROR_TIMEOUT;
+           goto end;
+           break;
+        case -1:
+           ret = EDG_WLL_GSS_ERROR_ERRNO;
+           goto end;
+           break;
+      }
+      
+      count = read(sock, buf, sizeof(buf));
+      if (count < 0) {
+        if (errno == EINTR)
+           continue;
+        else {
+           ret = EDG_WLL_GSS_ERROR_ERRNO;
+           goto end;
+        }
+      }
+      if (count == 0 && tl == 0 && errno == 0)
+        return EDG_WLL_GSS_ERROR_EOF; 
+      tmp=realloc(t, tl + count);
+      if (tmp == NULL) {
+        errno = ENOMEM;
+        return EDG_WLL_GSS_ERROR_ERRNO;
+      }
+      t = tmp;
+      memcpy(t + tl, buf, count);
+      tl += count;
+   } while (count == sizeof(buf));
+
+end:
+   if (to) {
+      gettimeofday(&after,NULL);
+      tv_sub(after,before);
+      tv_sub(*to,after);
+      if (to->tv_sec < 0) {
+        to->tv_sec = 0;
+        to->tv_usec = 0;
+      }
+   }
+
+   if (ret == 0) {
+      *token = t;
+      *token_length = tl;
+   } else
+      free(t);
+
+   return ret;
+}
+
+int
+edg_wll_gss_acquire_cred_gsi(char *proxy_file, gss_cred_id_t *cred,
+                            char **name, edg_wll_GssStatus* gss_code)
+{
+   OM_uint32 major_status, minor_status, minor_status2;
+   gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
+   gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER;
+   gss_name_t gss_name = GSS_C_NO_NAME;
+   OM_uint32 lifetime;
+
+   if (proxy_file == NULL) {
+      major_status = gss_acquire_cred(&minor_status, GSS_C_NO_NAME, 0,
+                                     GSS_C_NO_OID_SET, GSS_C_BOTH,
+                                     &gss_cred, NULL, NULL);
+      if (GSS_ERROR(major_status))
+        goto end;
+   } else {
+      asprintf((char**)&buffer.value, "X509_USER_PROXY=%s", proxy_file);
+      if (buffer.value == NULL) {
+        errno = ENOMEM;
+        return EDG_WLL_GSS_ERROR_ERRNO;
+      }
+      buffer.length = strlen(proxy_file);
+
+      major_status = gss_import_cred(&minor_status, &gss_cred, GSS_C_NO_OID, 1,
+                                    &buffer, 0, NULL);
+      free(buffer.value);
+      if (GSS_ERROR(major_status))
+        goto end;
+   }
+
+   /* gss_import_cred() doesn't check validity of credential loaded, so let's
+    * verify it now */
+    major_status = gss_inquire_cred(&minor_status, gss_cred, &gss_name,
+                                   &lifetime, NULL, NULL);
+    if (GSS_ERROR(major_status))
+       goto end;
+
+    if (lifetime == 0) {
+       major_status = GSS_S_CREDENTIALS_EXPIRED;
+       minor_status = 0; /* XXX */
+       goto end;
+    }
+
+    if (name) {
+       major_status = gss_display_name(&minor_status, gss_name, &buffer, NULL);
+       if (GSS_ERROR(major_status))
+         goto end;
+       *name = buffer.value;
+       memset(&buffer, 0, sizeof(buffer));
+    }
+    
+   *cred = gss_cred;
+   gss_cred = GSS_C_NO_CREDENTIAL;
+
+end:
+   if (gss_name != GSS_C_NO_NAME)
+      gss_release_name(&minor_status2, &gss_name);
+
+   if (gss_cred != GSS_C_NO_CREDENTIAL)
+      gss_release_cred(&minor_status2, &gss_cred);
+
+   if (GSS_ERROR(major_status)) {
+      if (gss_code) {
+        gss_code->major_status = major_status;
+        gss_code->minor_status = minor_status;
+      }
+      return EDG_WLL_GSS_ERROR_GSS;
+   }
+
+   return 0;
+}
+
+int 
+edg_wll_gss_connect(gss_cred_id_t cred, char const *hostname, int port,
+                   struct timeval *timeout, edg_wll_GssConnection *connection,
+                   edg_wll_GssStatus* gss_code)
+{
+   int sock, ret;
+   OM_uint32 maj_stat, min_stat, min_stat2, req_flags;
+   int context_established = 0;
+   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+   gss_name_t server = GSS_C_NO_NAME;
+   gss_ctx_id_t context = GSS_C_NO_CONTEXT;
+   char *servername = NULL;
+
+   maj_stat = min_stat = min_stat2 = req_flags = 0;
+
+   /* GSI specific */
+   req_flags = GSS_C_GLOBUS_SSL_COMPATIBLE;
+
+   ret = do_connect(&sock, hostname, port, timeout);
+   if (ret)
+      return ret;
+
+   /* XXX find appropriate fqdn */
+   asprintf (&servername, "host@%s", hostname);
+   if (servername == NULL) {
+      errno = ENOMEM;
+      ret = EDG_WLL_GSS_ERROR_ERRNO;
+      goto end;
+   }
+   input_token.value = servername;
+   input_token.length = strlen(servername) + 1;
+
+   maj_stat = gss_import_name(&min_stat, &input_token,
+                             GSS_C_NT_HOSTBASED_SERVICE, &server);
+   if (GSS_ERROR(maj_stat)) {
+      ret = EDG_WLL_GSS_ERROR_GSS;
+      goto end;
+   }
+
+   free(servername);
+   memset(&input_token, 0, sizeof(input_token));
+
+   /* XXX if cred == GSS_C_NO_CREDENTIAL set the ANONYMOUS flag */
+
+   /* XXX prepsat na do {} while (maj_stat == CONT) a osetrit chyby*/
+   while (!context_established) {
+      /* XXX verify ret_flags match what was requested */
+      maj_stat = gss_init_sec_context(&min_stat, cred, &context,
+                                     GSS_C_NO_NAME, GSS_C_NO_OID,
+                                     req_flags | GSS_C_MUTUAL_FLAG,
+                                     0, GSS_C_NO_CHANNEL_BINDINGS,
+                                     &input_token, NULL, &output_token,
+                                     NULL, NULL);
+      if (input_token.length > 0) {
+        free(input_token.value);
+        input_token.length = 0;
+      }
+
+      if (output_token.length != 0) {
+        ret = send_token(sock, output_token.value, output_token.length, timeout);
+        gss_release_buffer(&min_stat2, &output_token);
+        if (ret)
+           goto end;
+      }
+
+      if (GSS_ERROR(maj_stat)) {
+        if (context != GSS_C_NO_CONTEXT) {
+           /* XXX send closing token to the friend */
+           gss_delete_sec_context(&min_stat2, &context, GSS_C_NO_BUFFER);
+           context = GSS_C_NO_CONTEXT;
+        }
+        ret = EDG_WLL_GSS_ERROR_GSS;
+        goto end;
+      }
+
+      if(maj_stat & GSS_S_CONTINUE_NEEDED) {
+        ret = recv_token(sock, &input_token.value, &input_token.length, timeout);
+        if (ret)
+           goto end;
+      } else
+        context_established = 1;
+   }
+
+   /* XXX check ret_flags matches to what was requested */
+
+   memset(connection, 0, sizeof(*connection));
+   connection->sock = sock;
+   connection->context = context;
+   servername = NULL;
+   ret = 0;
+
+end:
+   if (ret == EDG_WLL_GSS_ERROR_GSS && gss_code) {
+      gss_code->major_status = maj_stat;
+      gss_code->minor_status = min_stat;
+   }
+   if (server != GSS_C_NO_NAME)
+      gss_release_name(&min_stat2, &server);
+   if (servername == NULL)
+      free(servername);
+   if (ret)
+      close(sock);
+
+   return ret;
+}
+
+int
+edg_wll_gss_accept(gss_cred_id_t cred, int sock, struct timeval *timeout,
+                  edg_wll_GssConnection *connection, edg_wll_GssStatus* gss_code)
+{
+   OM_uint32 maj_stat, min_stat, min_stat2;
+   OM_uint32 ret_flags = 0;
+   gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
+   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
+   gss_name_t client_name = GSS_C_NO_NAME;
+   gss_ctx_id_t context = GSS_C_NO_CONTEXT;
+   int ret;
+
+   maj_stat = min_stat = min_stat2 = 0;
+
+   /* GSI specific */
+   ret_flags = GSS_C_GLOBUS_SSL_COMPATIBLE;
+
+   do {
+      ret = recv_token(sock, &input_token.value, &input_token.length, timeout);
+      if (ret)
+        goto end;
+
+      maj_stat = gss_accept_sec_context(&min_stat, &context,
+                                       cred, &input_token,
+                                       GSS_C_NO_CHANNEL_BINDINGS,
+                                       &client_name, NULL, &output_token,
+                                       &ret_flags, NULL, NULL);
+      if (input_token.length > 0) {
+        free(input_token.value);
+        input_token.length = 0;
+      }
+
+      if (output_token.length) {
+        ret = send_token(sock, output_token.value, output_token.length, timeout);
+        gss_release_buffer(&min_stat2, &output_token);
+        if (ret)
+           goto end;
+      }
+   } while(maj_stat & GSS_S_CONTINUE_NEEDED);
+
+   if (GSS_ERROR(maj_stat)) {
+      if (context != GSS_C_NO_CONTEXT) {
+        /* XXX send closing token to the friend */
+        gss_delete_sec_context(&min_stat2, &context, GSS_C_NO_BUFFER);
+        context = GSS_C_NO_CONTEXT;
+      }
+      ret = EDG_WLL_GSS_ERROR_GSS;
+      goto end;
+   }
+
+   maj_stat = gss_display_name(&min_stat, client_name, &output_token, NULL);
+   if (GSS_ERROR(maj_stat)) {
+      /* XXX close context ??? */
+      ret = EDG_WLL_GSS_ERROR_GSS;
+      goto end;
+   }
+
+   memset(connection, 0, sizeof(*connection));
+   connection->sock = sock;
+   connection->context = context;
+   memset(&output_token, 0, sizeof(output_token.value));
+   ret = 0;
+
+end:
+   if (ret == EDG_WLL_GSS_ERROR_GSS && gss_code) {
+      gss_code->major_status = maj_stat;
+      gss_code->minor_status = min_stat;
+   }
+   if (client_name != GSS_C_NO_NAME)
+      gss_release_name(&min_stat2, &client_name);
+
+   return ret;
+}
+
+int
+edg_wll_gss_write(edg_wll_GssConnection *connection, const void *buf, size_t bufsize,
+                 struct timeval *timeout, edg_wll_GssStatus* gss_code)
+{
+   OM_uint32  maj_stat, min_stat;
+   gss_buffer_desc  input_token;
+   gss_buffer_desc  output_token;
+   int  ret;
+
+   input_token.value = (void*)buf;
+   input_token.length = bufsize;
+
+   maj_stat = gss_wrap (&min_stat, connection->context, 0, GSS_C_QOP_DEFAULT,
+                       &input_token, NULL, &output_token);
+   if (GSS_ERROR(maj_stat)) {
+      if (gss_code) {
+        gss_code->minor_status = min_stat;
+        gss_code->major_status = maj_stat;
+      }
+
+      return EDG_WLL_GSS_ERROR_GSS;
+   }
+
+   ret = send_token(connection->sock, output_token.value, output_token.length,
+                   timeout);
+   gss_release_buffer(&min_stat, &output_token);
+
+   return ret;
+}
+
+
+int
+edg_wll_gss_read(edg_wll_GssConnection *connection, void *buf, size_t bufsize,
+                struct timeval *timeout, edg_wll_GssStatus* gss_code)
+{
+   OM_uint32 maj_stat, min_stat;
+   gss_buffer_desc input_token;
+   gss_buffer_desc output_token;
+   int ret, i;
+
+   if (connection->bufsize > 0) {
+      size_t len;
+      
+      len = (connection->bufsize < bufsize) ? connection->bufsize : bufsize;
+      memcpy(buf, connection->buffer, len);
+      connection->bufsize -= len;
+      if (connection->bufsize > 0) {
+        for (i = 0; i < sizeof(connection->buffer) - len; i++)
+           connection->buffer[i] = connection->buffer[i+len];
+      }
+
+      return len;
+   }
+
+   do {
+      ret = recv_token(connection->sock, &input_token.value, &input_token.length,
+                      timeout);
+      if (ret)
+        /* XXX cleanup */
+        return ret;
+
+      maj_stat = gss_unwrap(&min_stat, connection->context, &input_token,
+                           &output_token, NULL, NULL);
+      if (GSS_ERROR(maj_stat)) {
+        /* XXX cleanup */
+        return EDG_WLL_GSS_ERROR_GSS;
+      }
+   } while (maj_stat == 0 && output_token.length == 0 && output_token.value == NULL);
+
+   if (output_token.length > bufsize) {
+      if (output_token.length - bufsize > sizeof(connection->buffer))
+        return EINVAL;
+      connection->bufsize = output_token.length - bufsize;
+      memcpy(connection->buffer, output_token.value + bufsize, connection->bufsize);
+      output_token.length = bufsize;
+   }
+   memcpy(buf, output_token.value, output_token.length);
+
+   return output_token.length;
+}
+
+int
+edg_wll_gss_read_full(edg_wll_GssConnection *connection, void *buf, 
+                             size_t bufsize, struct timeval *timeout, size_t *total,
+                     edg_wll_GssStatus* gss_code)
+{
+   int     len,i;
+   *total = 0;
+
+   if (connection->bufsize > 0) {
+      size_t len;
+
+
+      len = (connection->bufsize < bufsize) ? connection->bufsize : bufsize;
+      memcpy(buf, connection->buffer, len);
+      connection->bufsize -= len;
+      if (connection->bufsize > 0) {
+         for (i = 0; i < sizeof(connection->buffer) - len; i++)
+            connection->buffer[i] = connection->buffer[i+len];
+      }
+      *total = len;
+   }
+
+   while (*total < bufsize) {
+      len = edg_wll_gss_read(connection, buf+*total, bufsize-*total,
+                            timeout, gss_code);
+      if (len < 0) return len;
+      *total += len;
+   }
+
+   return 0;
+}
+
+int
+edg_wll_gss_write_full(edg_wll_GssConnection *connection, const void *buf,
+                       size_t bufsize, struct timeval *timeout, size_t *total,
+                      edg_wll_GssStatus* gss_code)
+{
+   return edg_wll_gss_write(connection, buf, bufsize, timeout, gss_code);
+}
+
+/* XXX: I'm afraid the contents of stuct stat is somewhat OS dependent */
+int
+edg_wll_gss_watch_creds(const char *proxy_file, time_t *proxy_mtime)
+{
+       struct stat     pstat;
+       int     reload = 0;
+
+       if (!proxy_file) return 0;
+       if (stat(proxy_file,&pstat)) return -1;
+
+       if (!*proxy_mtime) *proxy_mtime = pstat.st_mtime;
+
+       if (*proxy_mtime != pstat.st_mtime) {
+               *proxy_mtime = pstat.st_mtime;
+               reload = 1;
+       }
+
+       return reload;
+}
+
+int
+edg_wll_gss_close(edg_wll_GssConnection *con, struct timeval *timeout)
+{
+   OM_uint32 min_stat;
+
+   /* XXX if timeout is NULL use value of 120 secs */
+
+   if (con->context != GSS_C_NO_CONTEXT) {
+      gss_delete_sec_context(&min_stat, &con->context, GSS_C_NO_BUFFER);
+      /* XXX send the buffer (if any) to the peer. GSSAPI specs doesn't
+       * recommend sending it, though */
+
+      /* XXX can socket be open even if context == GSS_C_NO_CONTEXT) ? */
+      /* XXX ensure that edg_wll_GssConnection is created with sock set to -1 */
+      if (con->sock >= 0)
+        close(con->sock);
+   }
+   memset(con, 0, sizeof(*con));
+   con->context = GSS_C_NO_CONTEXT;
+   con->sock = -1;
+   return 0;
+}
+
+int
+edg_wll_gss_get_error(edg_wll_GssStatus *gss_err, const char *prefix, char **msg)
+{
+   OM_uint32 maj_stat, min_stat;
+   OM_uint32 msg_ctx = 0;
+   gss_buffer_desc maj_status_string = GSS_C_EMPTY_BUFFER;
+   gss_buffer_desc min_status_string = GSS_C_EMPTY_BUFFER;
+   char *str = NULL;
+   char *line, *tmp;
+
+   str = strdup(prefix);
+   do {
+      maj_stat = gss_display_status(&min_stat, gss_err->major_status,
+                                   GSS_C_GSS_CODE, GSS_C_NO_OID,
+                                   &msg_ctx, &maj_status_string);
+      if (GSS_ERROR(maj_stat))
+        break;
+
+      maj_stat = gss_display_status(&min_stat, gss_err->minor_status,
+                                   GSS_C_MECH_CODE, GSS_C_NULL_OID,
+                                   &msg_ctx, &min_status_string);
+      if (GSS_ERROR(maj_stat)) {
+        gss_release_buffer(&min_stat, &maj_status_string);
+        break;
+      }
+
+      asprintf(&line, ": %s (%s)", (char *)maj_status_string.value,
+              (char *)min_status_string.value);
+      gss_release_buffer(&min_stat, &maj_status_string);
+      gss_release_buffer(&min_stat, &min_status_string);
+
+      tmp = realloc(str, strlen(str) + strlen(line) + 1);
+      if (tmp == NULL) {
+         /* abort() ? */
+        free(line);
+        free(str);
+        str = "WARNING: Not enough memory to produce error message";
+        break;
+      }
+      str = tmp;
+      strcat(str, line);
+      free(line);
+   } while (!GSS_ERROR(maj_stat) && msg_ctx != 0);
+
+   *msg = str;
+   return 0;
+}
+
+int
+edg_wll_gss_oid_equal(const gss_OID a, const gss_OID b)
+{
+   if (a == b)
+      return 1;
+   else {
+      if (a == GSS_C_NO_OID || b == GSS_C_NO_OID || a->length != b->length)
+        return 0;
+      else
+        return (memcmp(a->elements, b->elements, a->length) == 0);
+   }
+}
+
+int 
+edg_wll_gss_reject(int sock)
+{
+   /* XXX is it possible to cut & paste edg_wll_ssl_reject() ? */
+   return 0;
+}