#include <errno.h>
 
 #include <globus_common.h>
+
 #include <gssapi.h>
 #include <openssl/err.h>
 #include <openssl/ssl.h>
 
 #include "glite_gss.h"
 
+#ifndef GSS_C_GLOBUS_SSL_COMPATIBLE
+#define GSS_C_GLOBUS_SSL_COMPATIBLE    16384
+#endif
+
 #define tv_sub(a,b) {\
        (a).tv_usec -= (b).tv_usec;\
        (a).tv_sec -= (b).tv_sec;\
 
 static int globus_common_activated = 0;
 
-static void free_hostent(struct hostent *h);
-static int try_conn_and_auth (edg_wll_GssCred cred, char const *hostname, char *addr,  int addrtype, int port, struct timeval *timeout, edg_wll_GssConnection *connection,
-                                edg_wll_GssStatus* gss_code);
+typedef struct gssapi_mech {
+    const char* name;
+    gss_OID_desc oid;
+} gssapi_mech;
+
+static
+struct gssapi_mech gssapi_mechs[] = {
+    { "Kerberos", {9, "\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"} },
+    { "GSI", {9, "\x2b\x06\x01\x04\x01\x9b\x50\x01\x01"} },
+    { "EAP", {9, "\x2B\x06\x01\x04\x01\xA9\x4A\x16\x01"} },
+};
+
+static gss_OID
+get_oid(const char *mech);
+
+static void
+free_hostent(struct hostent *h);
+
+static int
+try_conn_and_auth(edg_wll_GssCred cred, char const *hostname,
+                 const char *service, gss_OID mech,
+                 char *addr,  int addrtype, int port, struct timeval *timeout,
+                 edg_wll_GssConnection *connection, edg_wll_GssStatus* gss_code);
+
+static int
+edg_wll_gss_oid_equal(const gss_OID a, const gss_OID b);
+
 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);
    return 0;
 }
 
+static gss_OID
+get_oid(const char *mech)
+{
+    unsigned int i;
+
+    for (i = 0; i < sizeof(gssapi_mechs)/sizeof(gssapi_mechs[0]); i++)
+       if (strcasecmp(mech, gssapi_mechs[i].name) == 0)
+           return &gssapi_mechs[i].oid;
+
+    return GSS_C_NO_OID;
+}
+
 static int
 send_token(int sock, void *token, size_t token_length, struct timeval *to)
 {
 }
 
 static int
-recv_token(int sock, void **token, size_t *token_length, struct timeval *to)
+send_gss_token(int sock, gss_OID mech, void *token, size_t token_length, struct timeval *to)
+{
+    int ret;
+    uint32_t net_len = htonl(token_length);
+
+    /* Don't use the usual message framing when using Globus GSI, in order to be
+       compatible with SSL on the wire. */
+    if (!edg_wll_gss_oid_equal(mech, get_oid("GSI"))) {
+       ret = send_token(sock, &net_len, 4, to);
+       if (ret)
+           return ret;
+    }
+
+    return send_token(sock, token, token_length, to);
+}
+
+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;
       gettimeofday(&before,NULL);
    }
 
-   ret = 0;
    do {
       FD_ZERO(&fds);
       FD_SET(sock,&fds);
            break;
       }
       
-      count = read(sock, buf, sizeof(buf));
+      count = read(sock, token, token_length);
       if (count < 0) {
         if (errno == EINTR)
            continue;
       }
 
       if (count==0) {
-         if (tl==0) {
-            free(t);
-            return EDG_WLL_GSS_ERROR_EOF;
-         } else goto end;
-      }
-      tmp=realloc(t, tl + count);
-      if (tmp == NULL) {
-       errno = ENOMEM;
-       free(t);
-       return EDG_WLL_GSS_ERROR_ERRNO;
+         ret = EDG_WLL_GSS_ERROR_EOF;
+         goto end;
       }
-      t = tmp;
-      memcpy(t + tl, buf, count);
-      tl += count;
 
    } while (count < 0); /* restart on EINTR */
 
+   ret = count;
+
 end:
    if (to) {
       gettimeofday(&after,NULL);
       }
    }
 
-   if (ret == 0) {
-      *token = t;
-      *token_length = tl;
-   } else
-      free(t);
-
    return ret;
 }
 
+/* similar to recv_token() but never returns partial data */
+static int
+read_token(int sock, void *token, size_t length, struct timeval *to)
+{
+    size_t remain = length;
+    char *buf = token;
+    int count;
+
+    while (remain > 0) {
+       count = recv_token(sock, buf, remain, to);
+       if (count < 0)
+           return count;
+       buf += count;
+       remain -= count;
+    }
+
+    return length;
+}
+
+/*
+   SSL/TLS framing:
+   1st byte: SSL Handshake Record Type (\x16)
+   2nd-3rd bytes: SSL Version 3.0 (\x03 \x00)
+                  TLS Version 1.0 (\x03 \x01)
+   4th-5th bytes: Length
+*/
+#define SSLv3_HEADER "\x16\x03\x00"
+#define TLSv1_HEADER "\x16\x03\x01"
+
+static int
+is_ssl(unsigned char *header)
+{
+    if (memcmp(header, SSLv3_HEADER, 3) == 0)
+       return 1;
+    if (memcmp(header, TLSv1_HEADER, 3) == 0)
+       return 1;
+
+    return 0;
+    /* payload len == (size_t)(header[3]) << 8) | header[4]) + 5; */
+}
+
+
+static int
+recv_gss_token(int sock, gss_OID mech, void **token, size_t *token_length, struct timeval *to)
+{
+    int ret;
+    uint32_t len, net_len;
+    unsigned char *header;
+    char buf[4096];
+    char *b = NULL;
+
+    if (edg_wll_gss_oid_equal(mech, get_oid("GSI"))) {
+       ret = recv_token(sock, buf, sizeof(buf), to);
+       if (ret < 0)
+           return ret;
+
+       *token = malloc(ret);
+       if (*token == NULL)
+           return EDG_WLL_GSS_ERROR_ERRNO;
+
+       memcpy(*token, buf, ret);
+       *token_length = ret;
+
+       return 0;
+    }
+
+    ret = read_token(sock, &net_len, 4, to);
+    if (ret < 0)
+       return ret;
+
+    header = (unsigned char *)&net_len;
+    if (mech == GSS_C_NO_OID && is_ssl(header)) {
+       /* SSL detected. Let's return this fragment to the caller relying on
+          the Globus libs being able to cope with partial messages. */
+       *token = malloc(4);
+       if (*token == NULL)
+           return EDG_WLL_GSS_ERROR_ERRNO;
+
+       memcpy(*token, header, 4);
+       *token_length = 4;
+
+       return 0;
+    }
+
+    len =  ntohl(net_len);
+    b = malloc(len);
+    if (b == NULL)
+       return EDG_WLL_GSS_ERROR_ERRNO;
+
+    ret = read_token(sock, b, len, to);
+    if (ret < 0) {
+       free(b);
+       return ret;
+    }
+
+    *token = b;
+    *token_length = len;
+
+    return 0;
+}
+
 static int
 create_proxy(const char *cert_file, const char *key_file, char **proxy_file)
 {
 }
 
 /** Load or reload credentials. It should be called regularly (credential files can be changed).
+  This call works only for GSSAPI from Globus.
  @see edg_wll_gss_watch_creds
  */
 int
    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;
+   gss_OID_set_desc mechs;
    OM_uint32 lifetime;
    char *proxy_file = NULL;
    char *name = NULL;
       return EINVAL;
 
    if (cert_file == NULL) {
+      mechs.count = 1;
+      mechs.elements = get_oid("GSI");
+      
       major_status = gss_acquire_cred(&minor_status, GSS_C_NO_NAME, 0,
-                                     GSS_C_NO_OID_SET, GSS_C_BOTH,
+                                     &mechs, GSS_C_BOTH,
                                      &gss_cred, NULL, NULL);
       if (GSS_ERROR(major_status)) {
         ret = EDG_WLL_GSS_ERROR_GSS;
         goto end;
       }
    } else {
+#ifndef NO_GLOBUS_GSSAPI
       proxy_file = (char *)cert_file;
       if (strcmp(cert_file, key_file) != 0 &&
          (ret = create_proxy(cert_file, key_file, &proxy_file))) {
       }
       buffer.length = strlen(proxy_file);
 
-      major_status = gss_import_cred(&minor_status, &gss_cred, GSS_C_NO_OID, 1,
+      major_status = gss_import_cred(&minor_status, &gss_cred,
+                                    get_oid("GSI"), 1,
                                     &buffer, 0, NULL);
       free(buffer.value);
       if (GSS_ERROR(major_status)) {
         ret = EDG_WLL_GSS_ERROR_GSS;
         goto end;
       }
+#else
+      errno = EINVAL;
+      ret = EDG_WLL_GSS_ERROR_ERRNO;
+      goto end;
+#endif
    }
 
   /* gss_import_cred() doesn't check validity of credential loaded, so let's
 
 /** Create a socket and initiate secured connection. */
 int 
-edg_wll_gss_connect(edg_wll_GssCred cred, char const *hostname, int port,
-                   struct timeval *timeout, edg_wll_GssConnection *connection,
-                   edg_wll_GssStatus* gss_code)
+edg_wll_gss_connect_ext(edg_wll_GssCred cred, char const *hostname, int port,
+                       const char *service, gss_OID_set mechs, 
+                       struct timeval *timeout, edg_wll_GssConnection *connection,
+                       edg_wll_GssStatus* gss_code)
 {
    int ret;
    struct asyn_result ar;
    int h_errno;
    int addr_types[] = {AF_INET6, AF_INET};
    int ipver = AF_INET6; //def value; try IPv6 first
-   unsigned int j;
+   unsigned int j,k;
    int i;
+   gss_OID mech;
 
    memset(connection, 0, sizeof(*connection));
    for (j = 0; j< sizeof(addr_types)/sizeof(*addr_types); j++) {
        i = 0;
        while (ar.ent->h_addr_list[i])
        {
-               ret = try_conn_and_auth (cred, hostname, ar.ent->h_addr_list[i], 
+           k = 0;
+           do {
+               if (mechs == GSS_C_NO_OID_SET || mechs->count == 0)
+                   mech = GSS_C_NO_OID;
+               else
+                   mech = &mechs->elements[k];
+
+               ret = try_conn_and_auth (cred, hostname, service, mech,
+                                       ar.ent->h_addr_list[i], 
                                        ar.ent->h_addrtype, port, timeout, connection, gss_code);
                if (ret == 0)
                        goto end;
                if (timeout->tv_sec < 0 ||(timeout->tv_sec == 0 && timeout->tv_usec <= 0))
                        goto end;
-               i++;
+               k++;
+           } while (mechs != GSS_C_NO_OID_SET && k < mechs->count);
+           i++;
        }
        free_hostent(ar.ent);
        ar.ent = NULL;
    return ret;
 }
 
+int
+edg_wll_gss_connect(edg_wll_GssCred cred, char const *hostname, int port,
+                    struct timeval *timeout, edg_wll_GssConnection *connection,
+                    edg_wll_GssStatus* gss_code)
+{
+    gss_OID_set_desc mechs = {.count = 0, .elements = GSS_C_NO_OID};
+    gss_OID oid;
+    const char *mech;
+
+    mech = getenv("GLITE_GSS_MECH");
+    if (mech == NULL)
+       mech = "GSI";
+
+    oid = get_oid(mech);
+    if (oid != GSS_C_NO_OID) {
+       mechs.elements = oid;
+       mechs.count = 1;
+    }
+
+    return edg_wll_gss_connect_ext(cred, hostname, port,
+                                  NULL, &mechs,
+                                  timeout, connection, gss_code);
+}
+
 /* try connection and authentication for the given addr*/
-static int try_conn_and_auth (edg_wll_GssCred cred, char const *hostname, char *addr,  int addrtype, int port, struct timeval *timeout, edg_wll_GssConnection *connection,
-                               edg_wll_GssStatus* gss_code) 
+static int try_conn_and_auth (edg_wll_GssCred cred, char const *hostname,
+                             const char *service, gss_OID mech,
+                             char *addr,  int addrtype, int port,
+                             struct timeval *timeout, edg_wll_GssConnection *connection,
+                             edg_wll_GssStatus* gss_code) 
 {
-       int sock;
-       int ret = 0;
+   int sock;
+   int ret = 0;
    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;
+   gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
    char *servername = NULL;
    int retry = _EXPIRED_ALERT_RETRY_COUNT;
 
    maj_stat = min_stat = min_stat2 = req_flags = 0;
 
-   /* GSI specific */
-   req_flags = GSS_C_GLOBUS_SSL_COMPATIBLE;
+   if (edg_wll_gss_oid_equal(mech, get_oid("GSI"))) {
+       req_flags = GSS_C_GLOBUS_SSL_COMPATIBLE;
+       setenv("GLOBUS_GSSAPI_NAME_COMPATIBILITY", "STRICT_RFC2818", 0);
+   }
 
    ret = do_connect(&sock, addr, addrtype, port, timeout);
    if (ret)
       return ret;
 
-   /* XXX find appropriate fqdn */
-   asprintf (&servername, "host@%s", hostname);
+   asprintf(&servername, "%s@%s",
+           (service) ? service : "host", hostname);
    if (servername == NULL) {
       errno = ENOMEM;
       ret = EDG_WLL_GSS_ERROR_ERRNO;
    servername = NULL;
    memset(&input_token, 0, sizeof(input_token));
 
-   /* XXX if cred == GSS_C_NO_CREDENTIAL set the ANONYMOUS flag */
+   if (cred && cred->gss_cred)
+       gss_cred = cred->gss_cred;
 
    do { /* XXX: the black magic above */
 
-   /* 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->gss_cred, &context,
-                                     GSS_C_NO_NAME, GSS_C_NO_OID,
+      maj_stat = gss_init_sec_context(&min_stat, gss_cred, &context,
+                                     server, mech,
                                      req_flags | GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG,
                                      0, GSS_C_NO_CHANNEL_BINDINGS,
                                      &input_token, NULL, &output_token,
         input_token.length = 0;
       }
 
+      if (mech == GSS_C_NO_OID) {
+         gss_OID oid;
+
+         /* is it safe to inquire a partly-establised context? */
+         maj_stat = gss_inquire_context(&min_stat, context, NULL, NULL,
+                                        NULL, &oid, NULL, NULL, NULL);
+         if (!GSS_ERROR(maj_stat))
+             mech = oid;
+      }
+
       if (output_token.length != 0) {
-        ret = send_token(sock, output_token.value, output_token.length, timeout);
+        ret = send_gss_token(sock, mech, output_token.value, output_token.length, timeout);
         gss_release_buffer(&min_stat2, &output_token);
         if (ret)
            goto end;
            gss_delete_sec_context(&min_stat2, &context, &output_token);
            context = GSS_C_NO_CONTEXT;
            if (output_token.length) {
-               send_token(sock, output_token.value, output_token.length, timeout);
+               send_gss_token(sock, mech, output_token.value, output_token.length, timeout);
                gss_release_buffer(&min_stat2, &output_token);
            }
         }
       }
 
       if(maj_stat & GSS_S_CONTINUE_NEEDED) {
-        ret = recv_token(sock, &input_token.value, &input_token.length, timeout);
+        ret = recv_gss_token(sock, mech, &input_token.value, &input_token.length, timeout);
         if (ret)
            goto end;
       } else
 
    connection->sock = sock;
    connection->context = context;
+   connection->authn_mech = mech;
    ret = 0;
 
 end:
    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;
+   gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
+   gss_OID mech = GSS_C_NO_OID;
    int ret;
 
    maj_stat = min_stat = min_stat2 = 0;
    memset(connection, 0, sizeof(*connection));
 
-   if (cred == NULL)
-       return EINVAL;
+   if (cred && cred->gss_cred)
+       gss_cred = cred->gss_cred;
 
-   /* GSI specific */
    ret_flags = GSS_C_GLOBUS_SSL_COMPATIBLE;
 
    do {
-      ret = recv_token(sock, &input_token.value, &input_token.length, timeout);
+      ret = recv_gss_token(sock, mech, &input_token.value, &input_token.length, timeout);
       if (ret)
         goto end;
 
         gss_release_name(&min_stat2, &client_name);
 
       maj_stat = gss_accept_sec_context(&min_stat, &context,
-                                       cred->gss_cred, &input_token,
+                                       gss_cred, &input_token,
                                        GSS_C_NO_CHANNEL_BINDINGS,
-                                       &client_name, NULL, &output_token,
+                                       &client_name, &mech, &output_token,
                                        &ret_flags, NULL, NULL);
       if (input_token.length > 0) {
         free(input_token.value);
       }
 
       if (output_token.length) {
-        ret = send_token(sock, output_token.value, output_token.length, timeout);
+        ret = send_gss_token(sock, mech, output_token.value, output_token.length, timeout);
         gss_release_buffer(&min_stat2, &output_token);
         if (ret)
            goto end;
         gss_delete_sec_context(&min_stat2, &context, &output_token);
         context = GSS_C_NO_CONTEXT;
         if (output_token.length) {
-               send_token(sock, output_token.value, output_token.length, timeout);
+               send_gss_token(sock, mech, output_token.value, output_token.length, timeout);
                gss_release_buffer(&min_stat2, &output_token);
         }
       }
 
    connection->sock = sock;
    connection->context = context;
+   connection->authn_mech = mech;
    ret = 0;
 
 end:
       return EDG_WLL_GSS_ERROR_GSS;
    }
 
-   ret = send_token(connection->sock, output_token.value, output_token.length,
+   ret = send_gss_token(connection->sock, connection->authn_mech,
+                   output_token.value, output_token.length,
                    timeout);
    gss_release_buffer(&min_stat, &output_token);
 
    }
 
    do {
-      ret = recv_token(connection->sock, &input_token.value, &input_token.length,
+      ret = recv_gss_token(connection->sock, connection->authn_mech,
+                      &input_token.value, &input_token.length,
                       timeout);
       if (ret)
         return ret;
 
+      /* work around a Globus bug */
       ERR_clear_error();
+
       maj_stat = gss_unwrap(&min_stat, connection->context, &input_token,
                            &output_token, NULL, NULL);
       gss_release_buffer(&min_stat2, &input_token);
 
       now = time(NULL);
 
+      /* to work around a globus bug we enforce reloading credentials
+        quite often */
       if ( now >= (*last_time+GSS_CRED_WATCH_LIMIT) ) {
               *last_time = now;
               return 1;
       return 0;
 }
 
+int
+edg_wll_gss_watch_creds_gsi(const char *proxy_file, time_t *last_time)
+{
+    return edg_wll_gss_watch_creds(proxy_file, last_time);
+}
+
 /** Close the connection. */
 int
 edg_wll_gss_close(edg_wll_GssConnection *con, struct timeval *timeout)
    memset(con, 0, sizeof(*con));
    con->context = GSS_C_NO_CONTEXT;
    con->sock = -1;
+   con->authn_mech = NULL;
    return 0;
 }
 
 {
    int ret = 0;
 
+#ifndef NO_GLOBUS_GSSAPI
    if (globus_module_activate(GLOBUS_GSI_GSSAPI_MODULE) != GLOBUS_SUCCESS) {
       errno = EINVAL;
       ret = EDG_WLL_GSS_ERROR_ERRNO;
    }
+#endif
 
    if (globus_module_activate(GLOBUS_COMMON_MODULE) == GLOBUS_SUCCESS)
        globus_common_activated = 1;
 void
 edg_wll_gss_finalize(void)
 {
+#ifndef NO_GLOBUS_GSSAPI
    globus_module_deactivate(GLOBUS_GSI_GSSAPI_MODULE);
+#endif
    if (globus_common_activated) {
       globus_module_deactivate(GLOBUS_COMMON_MODULE);
       globus_common_activated = 0;
    X509 *p_cert;
    char *orig_cert = NULL, *orig_key = NULL;
 
+   if (!edg_wll_gss_oid_equal(gss->authn_mech, get_oid("GSI")))
+       return EINVAL;
+
    maj_stat = gss_export_sec_context(&min_stat, (gss_ctx_id_t *) &gss->context,
                                     &buffer);
    if (GSS_ERROR(maj_stat)) {