Initial commit of GSSAPI Kerberos support.
authorowen <owen@cda61777-01e9-0310-a592-d414129be87e>
Sun, 10 Aug 2008 13:10:31 +0000 (13:10 +0000)
committerowen <owen@cda61777-01e9-0310-a592-d414129be87e>
Sun, 10 Aug 2008 13:10:31 +0000 (13:10 +0000)
git-svn-id: svn://svn.tartarus.org/sgt/putty@8138 cda61777-01e9-0310-a592-d414129be87e

Recipe
config.c
mkfiles.pl
putty.h
settings.c
ssh.c
sshgss.h [new file with mode: 0644]
unix/uxgss.c [new file with mode: 0644]
unix/uxplink.c
windows/wingss.c [new file with mode: 0644]

diff --git a/Recipe b/Recipe
index 8854c40..f957288 100644 (file)
--- a/Recipe
+++ b/Recipe
 #      it to compile under development environments which do not
 #      support IPv6 in their header files.
 #
+#  - COMPAT=/DNO_GSSAPI
+#      Disables PuTTY's ability to use GSSAPI functions for
+#      authentication and key exchange.
+#
 #  - COMPAT=/DMSVC4 (Windows only)
 #  - RCFL=/DMSVC4
 #      Makes a couple of minor changes so that PuTTY compiles using
@@ -166,6 +170,7 @@ version.o: FORCE
 # Add VER to Windows resource targets, and force them to be rebuilt every
 # time, on the assumption that they will contain version information.
 !begin vc vars
+CFLAGS = $(CFLAGS) /DHAS_GSSAPI
 RCFLAGS = $(RCFLAGS) $(VER)
 !end
 !begin cygwin vars
@@ -255,8 +260,8 @@ NONSSH   = telnet raw rlogin ldisc pinger
 SSH      = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf
          + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd
          + sshaes sshsh256 sshsh512 sshbn wildcard pinger ssharcf
-WINSSH   = SSH winnoise winpgntc
-UXSSH    = SSH uxnoise uxagentc
+WINSSH   = SSH winnoise winpgntc wingss
+UXSSH    = SSH uxnoise uxagentc uxgss
 MACSSH   = SSH macnoise
 
 # SFTP implementation (pscp, psftp).
index 85e13c2..60d9836 100644 (file)
--- a/config.c
+++ b/config.c
@@ -2051,6 +2051,13 @@ void setup_config_box(struct controlbox *b, int midsession,
                          dlg_stdcheckbox_handler,
                          I(offsetof(Config,try_ki_auth)));
 
+#ifndef NO_GSSAPI
+           ctrl_checkbox(s, "Attempt GSSAPI auth (SSH-2)",
+                         NO_SHORTCUT, HELPCTX(no_help),
+                         dlg_stdcheckbox_handler,
+                         I(offsetof(Config,try_gssapi_auth)));
+#endif
+
            s = ctrl_getset(b, "Connection/SSH/Auth", "params",
                            "Authentication parameters");
            ctrl_checkbox(s, "Allow agent forwarding", 'f',
@@ -2060,6 +2067,12 @@ void setup_config_box(struct controlbox *b, int midsession,
                          HELPCTX(ssh_auth_changeuser),
                          dlg_stdcheckbox_handler,
                          I(offsetof(Config,change_username)));
+#ifndef NO_GSSAPI
+           ctrl_checkbox(s, "Allow GSSAPI credential delegation in SSH-2", NO_SHORTCUT,
+                         HELPCTX(no_help),
+                         dlg_stdcheckbox_handler,
+                         I(offsetof(Config,gssapifwd)));
+#endif
            ctrl_filesel(s, "Private key file for authentication:", 'k',
                         FILTER_KEY_FILES, FALSE, "Select private key file",
                         HELPCTX(ssh_auth_privkey),
index d4b2bd1..c4760b8 100755 (executable)
@@ -924,6 +924,8 @@ if (defined $makefiles{'gtk'}) {
     "# You can define this path to point at your tools if you need to\n".
     "# TOOLPATH = /opt/gcc/bin\n".
     "CC = \$(TOOLPATH)cc\n".
+    "# If necessary set the path to krb5-config here\n".
+    "KRB5CONFIG=krb5-config\n".
     "# You can manually set this to `gtk-config' or `pkg-config gtk+-1.2'\n".
     "# (depending on what works on your system) if you want to enforce\n".
     "# building with GTK 1.2, or you can set it to `pkg-config gtk+-2.0'\n".
@@ -939,6 +941,11 @@ if (defined $makefiles{'gtk'}) {
                 " -D _FILE_OFFSET_BITS=64\n".
     "XLDFLAGS = \$(LDFLAGS) `\$(GTK_CONFIG) --libs`\n".
     "ULDFLAGS = \$(LDFLAGS)\n".
+    "ifeq (,\$(findstring NO_GSSAPI,\$(COMPAT)))\n".
+    "CFLAGS+= `\$(KRB5CONFIG) --cflags gssapi`\n".
+    "XLDFLAGS+= `\$(KRB5CONFIG) --libs gssapi`\n".
+    "ULDFLAGS = `\$(KRB5CONFIG) --libs gssapi`\n".
+    "endif\n";
     "INSTALL=install\n",
     "INSTALL_PROGRAM=\$(INSTALL)\n",
     "INSTALL_DATA=\$(INSTALL)\n",
diff --git a/putty.h b/putty.h
index 25758b2..a2f320c 100644 (file)
--- a/putty.h
+++ b/putty.h
@@ -459,6 +459,8 @@ struct config_tag {
     int ssh_no_userauth;              /* bypass "ssh-userauth" (SSH-2 only) */
     int try_tis_auth;
     int try_ki_auth;
+    int try_gssapi_auth;               /* attempt gssapi auth */
+    int gssapifwd;                     /* forward tgt via gss */
     int ssh_subsys;                   /* run a subsystem rather than a command */
     int ssh_subsys2;                  /* fallback to go with remote_cmd_ptr2 */
     int ssh_no_shell;                 /* avoid running a shell */
index a175e4d..cbd50d4 100644 (file)
@@ -321,6 +321,7 @@ void save_open_settings(void *sesskey, Config *cfg)
     write_setting_i(sesskey, "Compression", cfg->compression);
     write_setting_i(sesskey, "TryAgent", cfg->tryagent);
     write_setting_i(sesskey, "AgentFwd", cfg->agentfwd);
+    write_setting_i(sesskey, "GssapiFwd", cfg->gssapifwd);
     write_setting_i(sesskey, "ChangeUsername", cfg->change_username);
     wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX,
           cfg->ssh_cipherlist);
@@ -330,6 +331,7 @@ void save_open_settings(void *sesskey, Config *cfg)
     write_setting_i(sesskey, "SshNoAuth", cfg->ssh_no_userauth);
     write_setting_i(sesskey, "AuthTIS", cfg->try_tis_auth);
     write_setting_i(sesskey, "AuthKI", cfg->try_ki_auth);
+    write_setting_i(sesskey, "AuthGSSAPI", cfg->try_gssapi_auth);
     write_setting_i(sesskey, "SshNoShell", cfg->ssh_no_shell);
     write_setting_i(sesskey, "SshProt", cfg->sshprot);
     write_setting_s(sesskey, "LogHost", cfg->loghost);
@@ -581,7 +583,7 @@ void load_open_settings(void *sesskey, Config *cfg)
     gpps(sesskey, "ProxyTelnetCommand", "connect %host %port\\n",
         cfg->proxy_telnet_command, sizeof(cfg->proxy_telnet_command));
     gppmap(sesskey, "Environment", "", cfg->environmt, lenof(cfg->environmt));
-    gpps(sesskey, "UserName", "", cfg->username, sizeof(cfg->username));
+    gpps(sesskey, "UserName", get_username(), cfg->username, sizeof(cfg->username));
     gpps(sesskey, "LocalUserName", "", cfg->localusername,
         sizeof(cfg->localusername));
     gppi(sesskey, "NoPTY", 0, &cfg->nopty);
@@ -589,6 +591,7 @@ void load_open_settings(void *sesskey, Config *cfg)
     gppi(sesskey, "TryAgent", 1, &cfg->tryagent);
     gppi(sesskey, "AgentFwd", 0, &cfg->agentfwd);
     gppi(sesskey, "ChangeUsername", 0, &cfg->change_username);
+    gppi(sesskey, "GssapiFwd", 0, &cfg->gssapifwd);
     gprefs(sesskey, "Cipher", "\0",
           ciphernames, CIPHER_MAX, cfg->ssh_cipherlist);
     {
@@ -614,6 +617,7 @@ void load_open_settings(void *sesskey, Config *cfg)
     gppi(sesskey, "SshNoAuth", 0, &cfg->ssh_no_userauth);
     gppi(sesskey, "AuthTIS", 0, &cfg->try_tis_auth);
     gppi(sesskey, "AuthKI", 1, &cfg->try_ki_auth);
+    gppi(sesskey, "AuthGSSAPI", 1, &cfg->try_gssapi_auth);
     gppi(sesskey, "SshNoShell", 0, &cfg->ssh_no_shell);
     gppfile(sesskey, "PublicKeyFile", &cfg->keyfile);
     gpps(sesskey, "RemoteCommand", "", cfg->remote_cmd,
diff --git a/ssh.c b/ssh.c
index 5195776..84a66e0 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -12,6 +12,7 @@
 #include "putty.h"
 #include "tree234.h"
 #include "ssh.h"
+#include "sshgss.h"
 
 #ifndef FALSE
 #define FALSE 0
 #define SSH2_MSG_CHANNEL_REQUEST                  98   /* 0x62 */
 #define SSH2_MSG_CHANNEL_SUCCESS                  99   /* 0x63 */
 #define SSH2_MSG_CHANNEL_FAILURE                  100  /* 0x64 */
+#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE               60
+#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN                  61
+#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE      63
+#define SSH2_MSG_USERAUTH_GSSAPI_ERROR                  64
+#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK                 65
+#define SSH2_MSG_USERAUTH_GSSAPI_MIC                    66
 
 /*
  * Packet type contexts, so that ssh2_pkt_type can correctly decode
@@ -127,6 +134,7 @@ typedef enum {
     SSH2_PKTCTX_NOAUTH,
     SSH2_PKTCTX_PUBLICKEY,
     SSH2_PKTCTX_PASSWORD,
+    SSH2_PKTCTX_GSSAPI,
     SSH2_PKTCTX_KBDINTER
 } Pkt_ACtx;
 
@@ -339,6 +347,12 @@ static char *ssh1_pkt_type(int type)
 }
 static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
 {
+    translatea(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,SSH2_PKTCTX_GSSAPI);
+    translatea(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,SSH2_PKTCTX_GSSAPI);
+    translatea(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE,SSH2_PKTCTX_GSSAPI);
+    translatea(SSH2_MSG_USERAUTH_GSSAPI_ERROR,SSH2_PKTCTX_GSSAPI);
+    translatea(SSH2_MSG_USERAUTH_GSSAPI_ERRTOK,SSH2_PKTCTX_GSSAPI);
+    translatea(SSH2_MSG_USERAUTH_GSSAPI_MIC, SSH2_PKTCTX_GSSAPI);
     translate(SSH2_MSG_DISCONNECT);
     translate(SSH2_MSG_IGNORE);
     translate(SSH2_MSG_UNIMPLEMENTED);
@@ -896,6 +910,11 @@ struct ssh_tag {
     int kex_in_progress;
     long next_rekey, last_rekey;
     char *deferred_rekey_reason;    /* points to STATIC string; don't free */
+
+    /*
+     * Fully qualified host name, which we need if doing GSSAPI.
+     */
+    char *fullhostname;
 };
 
 #define logevent(s) logevent(ssh->frontend, s)
@@ -2875,6 +2894,7 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
        sk_addr_free(addr);
        return err;
     }
+    ssh->fullhostname = dupstr(*realhost);   /* save in case of GSSAPI */
 
     /*
      * Open socket.
@@ -7043,12 +7063,15 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
                AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
                AUTH_TYPE_PASSWORD,
+               AUTH_TYPE_GSSAPI,
                AUTH_TYPE_KEYBOARD_INTERACTIVE,
                AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
        } type;
        int done_service_req;
        int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
        int tried_pubkey_config, done_agent;
+       int can_gssapi;
+       int tried_gssapi;
        int kbd_inter_refused;
        int we_are_in;
        prompts_t *cur_prompt;
@@ -7072,6 +7095,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        int try_send;
        int num_env, env_left, env_ok;
        struct Packet *pktout;
+       Ssh_gss_ctx gss_ctx;
+       Ssh_gss_buf gss_buf;
+       Ssh_gss_buf gss_rcvtok, gss_sndtok;
+       Ssh_gss_name gss_srv_name;
+       Ssh_gss_stat gss_stat;
     };
     crState(do_ssh2_authconn_state);
 
@@ -7079,6 +7107,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 
     s->done_service_req = FALSE;
     s->we_are_in = FALSE;
+    s->tried_gssapi = FALSE;
+
     if (!ssh->cfg.ssh_no_userauth) {
        /*
         * Request userauth protocol, and await a response to it.
@@ -7366,7 +7396,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                break;
            }
 
-           if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) {
+           if (pktin->type != SSH2_MSG_USERAUTH_FAILURE && s->type != AUTH_TYPE_GSSAPI) {
                bombout(("Strange packet received during authentication: "
                         "type %d", pktin->type));
                crStopV;
@@ -7437,6 +7467,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    in_commasep_string("password", methods, methlen);
                s->can_keyb_inter = ssh->cfg.try_ki_auth &&
                    in_commasep_string("keyboard-interactive", methods, methlen);
+#ifndef NO_GSSAPI              
+               s->can_gssapi = ssh->cfg.try_gssapi_auth &&
+                 in_commasep_string("gssapi-with-mic", methods, methlen) &&
+                 ssh_gss_init();
+#endif
            }
 
            ssh->pkt_actx = SSH2_PKTCTX_NOAUTH;
@@ -7765,6 +7800,157 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    key->alg->freekey(key->data);
                }
 
+#ifndef NO_GSSAPI
+           } else if (s->can_gssapi && !s->tried_gssapi) {
+
+               /* GSSAPI Authentication */
+
+               int micoffset;
+               Ssh_gss_buf mic;
+               s->type = AUTH_TYPE_GSSAPI;
+               s->tried_gssapi = TRUE;
+               s->gotit = TRUE;
+               ssh->pkt_actx = SSH2_PKTCTX_GSSAPI;
+
+               /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */
+               s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+               ssh2_pkt_addstring(s->pktout, s->username);
+               ssh2_pkt_addstring(s->pktout, "ssh-connection");
+               ssh2_pkt_addstring(s->pktout, "gssapi-with-mic");
+
+               /* add mechanism info */
+               ssh_gss_indicate_mech(&s->gss_buf);
+
+               /* number of GSSAPI mechanisms */
+               ssh2_pkt_adduint32(s->pktout,1);
+
+               /* length of OID + 2 */
+               ssh2_pkt_adduint32(s->pktout, s->gss_buf.len + 2);
+               ssh2_pkt_addbyte(s->pktout, SSH2_GSS_OIDTYPE);
+
+               /* length of OID */
+               ssh2_pkt_addbyte(s->pktout, (unsigned char) s->gss_buf.len);
+
+               ssh_pkt_adddata(s->pktout, s->gss_buf.data, s->gss_buf.len);
+               ssh2_pkt_send(ssh, s->pktout);
+               crWaitUntilV(pktin);
+               if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) {
+                   logevent("GSSAPI authentication request refused");
+                   continue;
+               }
+
+               /* check returned packet ... */
+
+               ssh_pkt_getstring(pktin,&s->gss_rcvtok.data,&s->gss_rcvtok.len);
+               if (s->gss_rcvtok.len != s->gss_buf.len + 2 ||
+                   s->gss_rcvtok.data[0] != SSH2_GSS_OIDTYPE ||
+                   s->gss_rcvtok.data[1] != s->gss_buf.len ||
+                   memcmp(s->gss_rcvtok.data+2,s->gss_buf.data,s->gss_buf.len) ) {
+                   logevent("GSSAPI authentication - wrong response from server");
+                   continue;
+               }
+
+               /* now start running */
+               s->gss_stat = ssh_gss_import_name(ssh->fullhostname,
+                                                 &s->gss_srv_name);
+               if (s->gss_stat != SSH_GSS_OK) {
+                   if (s->gss_stat == SSH_GSS_BAD_HOST_NAME)
+                       logevent("GSSAPI import name failed - Bad service name");
+                   else
+                       logevent("GSSAPI import name failed");
+                   continue;
+               }
+
+               /* fetch TGT into GSS engine */
+               s->gss_stat = ssh_gss_acquire_cred(&s->gss_ctx);
+
+               if (s->gss_stat != SSH_GSS_OK) {
+                   logevent("GSSAPI authentication failed to get credentials");
+                   ssh_gss_release_name(&s->gss_srv_name);
+                   continue;
+               }
+
+               /* initial tokens are empty */
+               s->gss_rcvtok.len = s->gss_sndtok.len = 0;
+               s->gss_rcvtok.data = s->gss_sndtok.data = NULL;
+
+               /* now enter the loop */
+               do {
+                   s->gss_stat = ssh_gss_init_sec_context(&s->gss_ctx,
+                                                          s->gss_srv_name,
+                                                          ssh->cfg.gssapifwd,
+                                                          &s->gss_rcvtok,
+                                                          &s->gss_sndtok);
+
+                   if (s->gss_stat!=SSH_GSS_S_COMPLETE &&
+                       s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) {
+                       logevent("GSSAPI authentication initialisation failed");
+
+                       if (ssh_gss_display_status(s->gss_ctx,&s->gss_buf) == SSH_GSS_OK) {
+                           logevent(s->gss_buf.data);
+                           sfree(s->gss_buf.data);
+                       }
+
+                       break;
+                   }
+                   logevent("GSSAPI authentication initialised");
+
+                   /* Client and server now exchange tokens until GSSAPI
+                    * no longer says CONTINUE_NEEDED */
+
+                   if (s->gss_sndtok.len != 0) {
+                       s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
+                       ssh_pkt_addstring_start(s->pktout);
+                       ssh_pkt_addstring_data(s->pktout,s->gss_sndtok.data,s->gss_sndtok.len);
+                       ssh2_pkt_send(ssh, s->pktout);
+                       ssh_gss_free_tok(&s->gss_sndtok);
+                   }
+
+                   if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) {
+                       crWaitUntilV(pktin);
+                       if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_TOKEN) {
+                           logevent("GSSAPI authentication - bad server response");
+                           s->gss_stat = SSH_GSS_FAILURE;
+                           break;
+                       }
+                       ssh_pkt_getstring(pktin,&s->gss_rcvtok.data,&s->gss_rcvtok.len);
+                   }
+               } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
+
+               if (s->gss_stat != SSH_GSS_OK) {
+                   ssh_gss_release_name(&s->gss_srv_name);
+                   ssh_gss_release_cred(&s->gss_ctx);
+                   continue;
+               }
+               logevent("GSSAPI authentication loop finished OK");
+
+               /* Now send the MIC */
+
+               s->pktout = ssh2_pkt_init(0);
+               micoffset = s->pktout->length;
+               ssh_pkt_addstring_start(s->pktout);
+               ssh_pkt_addstring_data(s->pktout, (char *)ssh->v2_session_id, ssh->v2_session_id_len);
+               ssh_pkt_addbyte(s->pktout, SSH2_MSG_USERAUTH_REQUEST);
+               ssh_pkt_addstring(s->pktout, s->username);
+               ssh_pkt_addstring(s->pktout, "ssh-connection");
+               ssh_pkt_addstring(s->pktout, "gssapi-with-mic");
+
+               s->gss_buf.data = (char *)s->pktout->data + micoffset;
+               s->gss_buf.len = s->pktout->length - micoffset;
+
+               ssh_gss_get_mic(s->gss_ctx, &s->gss_buf, &mic);
+               s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC);
+               ssh_pkt_addstring_start(s->pktout);
+               ssh_pkt_addstring_data(s->pktout, mic.data, mic.len);
+               ssh2_pkt_send(ssh, s->pktout);
+               ssh_gss_free_mic(&mic);
+
+               s->gotit = FALSE;
+
+               ssh_gss_release_name(&s->gss_srv_name);
+               ssh_gss_release_cred(&s->gss_ctx);
+               continue;
+#endif
            } else if (s->can_keyb_inter && !s->kbd_inter_refused) {
 
                /*
@@ -8949,6 +9135,7 @@ static void ssh_free(void *handle)
     sfree(ssh->do_ssh2_authconn_state);
     sfree(ssh->v_c);
     sfree(ssh->v_s);
+    sfree(ssh->fullhostname);
     if (ssh->crcda_ctx) {
        crcda_free_context(ssh->crcda_ctx);
        ssh->crcda_ctx = NULL;
diff --git a/sshgss.h b/sshgss.h
new file mode 100644 (file)
index 0000000..dfd5e66
--- /dev/null
+++ b/sshgss.h
@@ -0,0 +1,112 @@
+#define SSH2_GSS_OIDTYPE 0x06
+typedef void *Ssh_gss_ctx;
+typedef void *Ssh_gss_name;
+
+typedef enum Ssh_gss_stat {
+    SSH_GSS_OK = 0,
+    SSH_GSS_S_CONTINUE_NEEDED,
+    SSH_GSS_NO_MEM,
+    SSH_GSS_BAD_HOST_NAME,
+    SSH_GSS_FAILURE
+} Ssh_gss_stat;
+
+#define SSH_GSS_S_COMPLETE SSH_GSS_OK
+
+typedef struct Ssh_gss_buf {
+    int len;
+    char *data;
+} Ssh_gss_buf;
+
+#define SSH_GSS_EMPTY_BUF (Ssh_gss_buf) {0,NULL}
+
+#define SSH_GSS_CLEAR_BUF(buf) do {            \
+    (*buf).len = 0;                            \
+    (*buf).data = NULL;                                \
+} while (0)
+
+/* Functions, provided by either wingss.c or uxgss.c */
+
+/*
+ * Do startup-time initialisation for using GSSAPI. (On Windows,
+ * for instance, this dynamically loads the GSSAPI DLL and
+ * retrieves some function pointers.)
+ *
+ * Return value is 1 on success, or 0 if initialisation failed.
+ *
+ * May be called multiple times (since the most convenient place
+ * to call it _from_ is the ssh.c setup code), and will harmlessly
+ * return success if already initialised.
+ */
+int ssh_gss_init(void);
+
+/*
+ * Fills in buf with a string describing the GSSAPI mechanism in
+ * use. buf->data is not dynamically allocated.
+ */
+Ssh_gss_stat ssh_gss_indicate_mech(Ssh_gss_buf *buf);
+
+/*
+ * Converts a name such as a hostname into a GSSAPI internal form,
+ * which is placed in "out". The result should be freed by
+ * ssh_gss_release_name().
+ */
+Ssh_gss_stat ssh_gss_import_name(char *in, Ssh_gss_name *out);
+
+/*
+ * Frees the contents of an Ssh_gss_name structure filled in by
+ * ssh_gss_import_name().
+ */
+Ssh_gss_stat ssh_gss_release_name(Ssh_gss_name *name);
+
+/*
+ * The main GSSAPI security context setup function. The "out"
+ * parameter will need to be freed by ssh_gss_free_tok.
+ */
+Ssh_gss_stat ssh_gss_init_sec_context(Ssh_gss_ctx *ctx, Ssh_gss_name name, int delegate,
+                                     Ssh_gss_buf *in, Ssh_gss_buf *out);
+
+/*
+ * Frees the contents of an Ssh_gss_buf filled in by
+ * ssh_gss_init_sec_context(). Do not accidentally call this on
+ * something filled in by ssh_gss_get_mic() (which requires a
+ * different free function) or something filled in by any other
+ * way.
+ */
+Ssh_gss_stat ssh_gss_free_tok(Ssh_gss_buf *);
+
+/*
+ * Acquires the credentials to perform authentication in the first
+ * place. Needs to be freed by ssh_gss_release_cred().
+ */
+Ssh_gss_stat ssh_gss_acquire_cred(Ssh_gss_ctx *);
+
+/*
+ * Frees the contents of an Ssh_gss_ctx filled in by
+ * ssh_gss_acquire_cred().
+ */
+Ssh_gss_stat ssh_gss_release_cred(Ssh_gss_ctx *);
+
+/*
+ * Gets a MIC for some input data. "out" needs to be freed by
+ * ssh_gss_free_mic().
+ */
+Ssh_gss_stat ssh_gss_get_mic(Ssh_gss_ctx ctx, Ssh_gss_buf *in,
+                            Ssh_gss_buf *out);
+
+/*
+ * Frees the contents of an Ssh_gss_buf filled in by
+ * ssh_gss_get_mic(). Do not accidentally call this on something
+ * filled in by ssh_gss_init_sec_context() (which requires a
+ * different free function) or something filled in by any other
+ * way.
+ */
+Ssh_gss_stat ssh_gss_free_mic(Ssh_gss_buf *);
+
+/*
+ * Return an error message after authentication failed. The
+ * message string is returned in "buf", with buf->len giving the
+ * number of characters of printable message text and buf->data
+ * containing one more character which is a trailing NUL.
+ * buf->data should be manually freed by the caller. 
+ */
+Ssh_gss_stat ssh_gss_display_status(Ssh_gss_ctx, Ssh_gss_buf *buf);
diff --git a/unix/uxgss.c b/unix/uxgss.c
new file mode 100644 (file)
index 0000000..5e59ed7
--- /dev/null
@@ -0,0 +1,196 @@
+#ifndef NO_GSSAPI
+
+#include <string.h>
+#include <gssapi/gssapi_krb5.h>
+#include "sshgss.h"
+#include "misc.h"
+
+typedef struct uxSsh_gss_ctx {
+    OM_uint32 maj_stat;
+    OM_uint32 min_stat;
+    gss_ctx_id_t ctx;
+} uxSsh_gss_ctx;
+
+int ssh_gss_init(void)
+{
+    /* On Windows this tries to load the SSPI library functions.  On
+       Unix we assume we have GSSAPI at runtime if we were linked with
+       it at compile time */
+    return 1;
+}
+
+Ssh_gss_stat ssh_gss_indicate_mech(Ssh_gss_buf *mech)
+{
+    /* Copy constant into mech */
+    mech->len  = gss_mech_krb5->length;
+    mech->data = gss_mech_krb5->elements;
+
+    return SSH_GSS_OK;
+}
+
+Ssh_gss_stat ssh_gss_import_name(char *host,
+                                 Ssh_gss_name *srv_name)
+{
+    OM_uint32 min_stat,maj_stat;
+    gss_buffer_desc host_buf;
+    char *pStr;
+
+    pStr = dupcat("host@", host, NULL);
+
+    host_buf.value = pStr;
+    host_buf.length = strlen(pStr);
+
+    maj_stat = gss_import_name(&min_stat, &host_buf,
+                              GSS_C_NT_HOSTBASED_SERVICE,
+                              (gss_name_t *)srv_name);
+    /* Release buffer */
+    sfree(pStr);
+    if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+    return SSH_GSS_FAILURE;
+}
+
+Ssh_gss_stat ssh_gss_acquire_cred(Ssh_gss_ctx *ctx)
+{
+    uxSsh_gss_ctx *uxctx = snew(uxSsh_gss_ctx);
+
+    uxctx->maj_stat =  uxctx->min_stat = GSS_S_COMPLETE;
+    uxctx->ctx = GSS_C_NO_CONTEXT;
+    *ctx = (Ssh_gss_ctx) uxctx;
+
+    return SSH_GSS_OK;
+}
+
+Ssh_gss_stat ssh_gss_init_sec_context(Ssh_gss_ctx *ctx,
+                                      Ssh_gss_name srv_name,
+                                      int to_deleg,
+                                     Ssh_gss_buf *recv_tok,
+                                     Ssh_gss_buf *send_tok)
+{
+    uxSsh_gss_ctx *uxctx = (uxSsh_gss_ctx*) *ctx;
+    OM_uint32 ret_flags;
+
+    if (to_deleg) to_deleg = GSS_C_DELEG_FLAG;
+    uxctx->maj_stat = gss_init_sec_context(&uxctx->min_stat,
+                                          GSS_C_NO_CREDENTIAL,
+                                          &uxctx->ctx,
+                                          (gss_name_t) srv_name,
+                                          (gss_OID) gss_mech_krb5,
+                                          GSS_C_MUTUAL_FLAG |
+                                          GSS_C_INTEG_FLAG | to_deleg,
+                                          0,
+                                          NULL,   /* no channel bindings */
+                                          (gss_buffer_desc *)recv_tok,
+                                          NULL,   /* ignore mech type */
+                                          (gss_buffer_desc *)send_tok,
+                                          &ret_flags,
+                                          NULL);  /* ignore time_rec */
+  
+    if (uxctx->maj_stat == GSS_S_COMPLETE) return SSH_GSS_S_COMPLETE;
+    if (uxctx->maj_stat == GSS_S_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED;
+    return SSH_GSS_FAILURE;
+}
+
+Ssh_gss_stat ssh_gss_display_status(Ssh_gss_ctx ctx, Ssh_gss_buf *buf)
+{
+    uxSsh_gss_ctx *uxctx = (uxSsh_gss_ctx *) ctx;
+    OM_uint32 lmin,lmax;
+    OM_uint32 ccc;
+    gss_buffer_desc msg_maj=GSS_C_EMPTY_BUFFER;
+    gss_buffer_desc msg_min=GSS_C_EMPTY_BUFFER;
+
+    /* Return empty buffer in case of failure */
+    SSH_GSS_CLEAR_BUF(buf);
+
+    /* get first mesg from GSS */
+    ccc=0;
+    lmax=gss_display_status(&lmin,uxctx->maj_stat,GSS_C_GSS_CODE,(gss_OID) gss_mech_krb5,&ccc,&msg_maj);
+
+    if (lmax != GSS_S_COMPLETE) return SSH_GSS_FAILURE;
+
+    /* get first mesg from Kerberos */
+    ccc=0;
+    lmax=gss_display_status(&lmin,uxctx->min_stat,GSS_C_MECH_CODE,(gss_OID) gss_mech_krb5,&ccc,&msg_min);
+
+    if (lmax != GSS_S_COMPLETE) {
+       gss_release_buffer(&lmin, &msg_maj);
+       return SSH_GSS_FAILURE;
+    }
+
+    /* copy data into buffer */
+    buf->len = msg_maj.length + msg_min.length + 1;
+    buf->data = snewn(buf->len + 1, char);
+  
+    /* copy mem */
+    memcpy(buf->data, msg_maj.value, msg_maj.length);
+    buf->data[msg_maj.length] = ' ';
+    memcpy(buf->data + msg_maj.length + 1, msg_min.value, msg_min.length);
+    buf->data[buf->len] = 0;
+    /* free mem & exit */
+    gss_release_buffer(&lmin, &msg_maj);
+    gss_release_buffer(&lmin, &msg_min);
+    return SSH_GSS_OK;
+}
+
+Ssh_gss_stat ssh_gss_free_tok(Ssh_gss_buf *send_tok)
+{
+    OM_uint32 min_stat,maj_stat;
+    maj_stat = gss_release_buffer(&min_stat, (gss_buffer_desc *)send_tok);
+  
+    if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+    return SSH_GSS_FAILURE;
+}
+
+Ssh_gss_stat ssh_gss_release_cred(Ssh_gss_ctx *ctx)
+{
+    uxSsh_gss_ctx *uxctx = (uxSsh_gss_ctx *) *ctx;
+    OM_uint32 min_stat;
+    OM_uint32 maj_stat=GSS_S_COMPLETE;
+  
+    if (uxctx == NULL) return SSH_GSS_FAILURE;
+    if (uxctx->ctx != GSS_C_NO_CONTEXT)
+       maj_stat = gss_delete_sec_context(&min_stat,&uxctx->ctx,GSS_C_NO_BUFFER);
+    sfree(uxctx);
+  
+    if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+    return SSH_GSS_FAILURE;
+}
+
+
+Ssh_gss_stat ssh_gss_release_name(Ssh_gss_name *srv_name)
+{
+    OM_uint32 min_stat,maj_stat;
+    maj_stat = gss_release_name(&min_stat, (gss_name_t) srv_name);
+  
+    if (maj_stat == GSS_S_COMPLETE) return SSH_GSS_OK;
+    return SSH_GSS_FAILURE;
+}
+
+Ssh_gss_stat ssh_gss_get_mic(Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
+                            Ssh_gss_buf *hash) 
+{
+    uxSsh_gss_ctx *uxctx = (uxSsh_gss_ctx *) ctx;
+    if (uxctx == NULL) return SSH_GSS_FAILURE;
+    return gss_get_mic(&(uxctx->min_stat),
+                      uxctx->ctx,
+                      0,
+                      (gss_buffer_desc *)buf,
+                      (gss_buffer_desc *)hash);
+}
+
+Ssh_gss_stat ssh_gss_free_mic(Ssh_gss_buf *hash)
+{
+    /* On Unix this is the same freeing process as ssh_gss_free_tok. */
+    return ssh_gss_free_tok(hash);
+}
+
+#else
+
+/* Dummy function so this source file defines something if NO_GSSAPI
+   is defined. */
+
+int ssh_gss_init(void)
+{
+    return 1;
+}
+
+#endif
index 0c34a29..6d9a9b7 100644 (file)
@@ -107,8 +107,8 @@ char *platform_default_s(const char *name)
 {
     if (!strcmp(name, "TermType"))
        return dupstr(getenv("TERM"));
-    if (!strcmp(name, "UserName"))
-       return get_username();
+     if (!strcmp(name, "UserName"))
+       return get_username();
     if (!strcmp(name, "SerialLine"))
        return dupstr("/dev/ttyS0");
     return NULL;
diff --git a/windows/wingss.c b/windows/wingss.c
new file mode 100644 (file)
index 0000000..5125a3a
--- /dev/null
@@ -0,0 +1,319 @@
+#ifndef NO_GSSAPI
+
+#include <windows.h>
+#define SECURITY_WIN32
+#include <security.h>
+#include "sshgss.h"
+#include "misc.h"
+
+#define NOTHING
+#define DECL_SSPI_FUNCTION(linkage, rettype, name, params)     \
+  typedef rettype (WINAPI *t_##name) params;                   \
+  linkage t_##name p_##name
+#define GET_SSPI_FUNCTION(module, name)                                        \
+  p_##name = module ? (t_##name) GetProcAddress(module, #name) : NULL
+
+DECL_SSPI_FUNCTION(static, SECURITY_STATUS,
+                  AcquireCredentialsHandleA,
+                  (SEC_CHAR *, SEC_CHAR *, ULONG, PLUID,
+                   PVOID, SEC_GET_KEY_FN, PVOID, PCredHandle, PTimeStamp));
+DECL_SSPI_FUNCTION(static, SECURITY_STATUS,
+                  InitializeSecurityContextA,
+                  (PCredHandle, PCtxtHandle, SEC_CHAR *, ULONG, ULONG,
+                  ULONG, PSecBufferDesc, ULONG, PCtxtHandle,
+                   PSecBufferDesc, PULONG, PTimeStamp));
+DECL_SSPI_FUNCTION(static, SECURITY_STATUS,
+                  FreeContextBuffer,
+                  (PVOID));
+DECL_SSPI_FUNCTION(static, SECURITY_STATUS,
+                  FreeCredentialsHandle,
+                  (PCredHandle));
+DECL_SSPI_FUNCTION(static, SECURITY_STATUS,
+                  DeleteSecurityContext,
+                  (PCtxtHandle));
+DECL_SSPI_FUNCTION(static, SECURITY_STATUS,
+                  QueryContextAttributesA,
+                  (PCtxtHandle, ULONG, PVOID));
+DECL_SSPI_FUNCTION(static, SECURITY_STATUS,
+                  MakeSignature,
+                  (PCtxtHandle, ULONG, PSecBufferDesc, ULONG));
+
+static HMODULE security_module = NULL;
+
+typedef struct winSsh_gss_ctx {
+    unsigned long maj_stat;
+    unsigned long min_stat;
+    CredHandle cred_handle;
+    CtxtHandle context;
+    PCtxtHandle context_handle;
+    TimeStamp expiry;
+} winSsh_gss_ctx;
+
+
+const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"};
+
+int ssh_gss_init(void)
+{
+    if (security_module)
+       return 1;                      /* already initialised */
+
+    security_module = LoadLibrary("secur32.dll");
+    if (security_module) {
+       GET_SSPI_FUNCTION(security_module, AcquireCredentialsHandleA);
+       GET_SSPI_FUNCTION(security_module, InitializeSecurityContextA);
+       GET_SSPI_FUNCTION(security_module, FreeContextBuffer);
+       GET_SSPI_FUNCTION(security_module, FreeCredentialsHandle);
+       GET_SSPI_FUNCTION(security_module, DeleteSecurityContext);
+       GET_SSPI_FUNCTION(security_module, QueryContextAttributesA);
+       GET_SSPI_FUNCTION(security_module, MakeSignature);
+       return 1;
+    }
+    return 0;
+}
+
+Ssh_gss_stat ssh_gss_indicate_mech(Ssh_gss_buf *mech)
+{
+    *mech = gss_mech_krb5;
+    return SSH_GSS_OK;
+}
+
+
+Ssh_gss_stat ssh_gss_import_name(char *host, Ssh_gss_name *srv_name)
+{
+    char *pStr;
+
+    /* Check hostname */
+    if (host == NULL) return SSH_GSS_FAILURE;
+    
+    /* copy it into form host/FQDN */
+    pStr = dupcat("host/", host, NULL);
+
+    *srv_name = (Ssh_gss_name) pStr;
+
+    return SSH_GSS_OK;
+}
+
+Ssh_gss_stat ssh_gss_acquire_cred(Ssh_gss_ctx *ctx)
+{
+    winSsh_gss_ctx *winctx = snew(winSsh_gss_ctx);
+
+    /* prepare our "wrapper" structure */
+    winctx->maj_stat =  winctx->min_stat = SEC_E_OK;
+    winctx->context_handle = NULL;
+
+    /* Specifying no principal name here means use the credentials of
+       the current logged-in user */
+
+    winctx->maj_stat = p_AcquireCredentialsHandleA(NULL,
+                                                  "Kerberos",
+                                                  SECPKG_CRED_OUTBOUND,
+                                                  NULL,
+                                                  NULL,
+                                                  NULL,
+                                                  NULL,
+                                                  &winctx->cred_handle,
+                                                  &winctx->expiry);
+
+    if (winctx->maj_stat != SEC_E_OK) return SSH_GSS_FAILURE;
+    
+    *ctx = (Ssh_gss_ctx) winctx;
+    return SSH_GSS_OK;
+}
+
+
+Ssh_gss_stat ssh_gss_init_sec_context(Ssh_gss_ctx *ctx,
+                                     Ssh_gss_name srv_name,
+                                     int to_deleg,
+                                     Ssh_gss_buf *recv_tok,
+                                     Ssh_gss_buf *send_tok)
+{
+    winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx;
+    SecBuffer wsend_tok = {send_tok->len,SECBUFFER_TOKEN,send_tok->data};
+    SecBuffer wrecv_tok = {recv_tok->len,SECBUFFER_TOKEN,recv_tok->data};
+    SecBufferDesc output_desc={SECBUFFER_VERSION,1,&wsend_tok};
+    SecBufferDesc input_desc ={SECBUFFER_VERSION,1,&wrecv_tok};
+    unsigned long flags=ISC_REQ_MUTUAL_AUTH|ISC_REQ_REPLAY_DETECT|
+       ISC_REQ_CONFIDENTIALITY|ISC_REQ_ALLOCATE_MEMORY;
+    unsigned long ret_flags=0;
+    
+    /* check if we have to delegate ... */
+    if (to_deleg) flags |= ISC_REQ_DELEGATE;
+    winctx->maj_stat = p_InitializeSecurityContextA(&winctx->cred_handle,
+                                                   winctx->context_handle,
+                                                   (char*) srv_name,
+                                                   flags,
+                                                   0,          /* reserved */
+                                                   SECURITY_NATIVE_DREP,
+                                                   &input_desc,
+                                                   0,          /* reserved */
+                                                   &winctx->context,
+                                                   &output_desc,
+                                                   &ret_flags,
+                                                   &winctx->expiry);
+  
+    /* prepare for the next round */
+    winctx->context_handle = &winctx->context;
+    send_tok->data = (char*) wsend_tok.pvBuffer;
+    send_tok->len  =  wsend_tok.cbBuffer;
+  
+    /* check & return our status */
+    if (winctx->maj_stat==SEC_E_OK) return SSH_GSS_S_COMPLETE;
+    if (winctx->maj_stat==SEC_I_CONTINUE_NEEDED) return SSH_GSS_S_CONTINUE_NEEDED;
+    
+    return SSH_GSS_FAILURE;
+}
+
+Ssh_gss_stat ssh_gss_free_tok(Ssh_gss_buf *send_tok)
+{
+    /* check input */
+    if (send_tok == NULL) return SSH_GSS_FAILURE;
+
+    /* free Windows buffer */
+    p_FreeContextBuffer(send_tok->data);
+    send_tok->len = 0; send_tok->data = NULL;
+    
+    return SSH_GSS_OK;
+}
+
+Ssh_gss_stat ssh_gss_release_cred(Ssh_gss_ctx *ctx)
+{
+    winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) *ctx;
+
+    /* check input */
+    if (winctx == NULL) return SSH_GSS_FAILURE;
+
+    /* free Windows data */
+    p_FreeCredentialsHandle(&winctx->cred_handle);
+    p_DeleteSecurityContext(&winctx->context);
+
+    /* delete our "wrapper" structure */
+    sfree(winctx);
+    *ctx = (Ssh_gss_ctx) NULL;
+
+    return SSH_GSS_OK;
+}
+
+
+Ssh_gss_stat ssh_gss_release_name(Ssh_gss_name *srv_name)
+{
+    char *pStr= (char *) *srv_name;
+
+    if (pStr == NULL) return SSH_GSS_FAILURE;
+    sfree(pStr);
+    *srv_name = (Ssh_gss_name) NULL;
+
+    return SSH_GSS_OK;
+}
+
+Ssh_gss_stat ssh_gss_display_status(Ssh_gss_ctx ctx, Ssh_gss_buf *buf)
+{
+    winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx;
+    char *msg;
+
+    if (winctx == NULL) return SSH_GSS_FAILURE;
+
+    /* decode the error code */
+    switch (winctx->maj_stat) {
+      case SEC_E_OK: msg="SSPI status OK"; break;
+      case SEC_E_INVALID_HANDLE: msg="The handle passed to the function"
+           " is invalid.";
+       break;
+      case SEC_E_TARGET_UNKNOWN: msg="The target was not recognized."; break;
+      case SEC_E_LOGON_DENIED: msg="The logon failed."; break;
+      case SEC_E_INTERNAL_ERROR: msg="The Local Security Authority cannot"
+           " be contacted.";
+       break;
+      case SEC_E_NO_CREDENTIALS: msg="No credentials are available in the"
+           " security package.";
+       break;
+      case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+       msg="No authority could be contacted for authentication."
+           "The domain name of the authenticating party could be wrong,"
+           " the domain could be unreachable, or there might have been"
+           " a trust relationship failure.";
+       break;
+      case SEC_E_INSUFFICIENT_MEMORY:
+       msg="One or more of the SecBufferDesc structures passed as"
+           " an OUT parameter has a buffer that is too small.";
+       break;
+      case SEC_E_INVALID_TOKEN:
+       msg="The error is due to a malformed input token, such as a"
+           " token corrupted in transit, a token"
+           " of incorrect size, or a token passed into the wrong"
+           " security package. Passing a token to"
+           " the wrong package can happen if client and server did not"
+           " negotiate the proper security package.";
+       break;
+      default:
+       msg = "Internal SSPI error";
+       break;
+    }
+
+    buf->data = dupstr(msg);
+    buf->len = strlen(buf->data);
+    
+    return SSH_GSS_OK;
+}
+
+Ssh_gss_stat ssh_gss_get_mic(Ssh_gss_ctx ctx, Ssh_gss_buf *buf,
+                            Ssh_gss_buf *hash)
+{
+    winSsh_gss_ctx *winctx= (winSsh_gss_ctx *) ctx;
+    SecPkgContext_Sizes ContextSizes;
+    SecBufferDesc InputBufferDescriptor;
+    SecBuffer InputSecurityToken[2];
+
+    if (winctx == NULL) return SSH_GSS_FAILURE;
+  
+    winctx->maj_stat = 0;
+
+    memset(&ContextSizes, 0, sizeof(ContextSizes));
+
+    winctx->maj_stat = p_QueryContextAttributesA(&winctx->context,
+                                                SECPKG_ATTR_SIZES,
+                                                &ContextSizes);
+    
+    if (winctx->maj_stat != SEC_E_OK ||
+       ContextSizes.cbMaxSignature == 0)
+       return winctx->maj_stat;
+
+    InputBufferDescriptor.cBuffers = 2;
+    InputBufferDescriptor.pBuffers = InputSecurityToken;
+    InputBufferDescriptor.ulVersion = SECBUFFER_VERSION;
+    InputSecurityToken[0].BufferType = SECBUFFER_DATA;
+    InputSecurityToken[0].cbBuffer = buf->len;
+    InputSecurityToken[0].pvBuffer = buf->data;
+    InputSecurityToken[1].BufferType = SECBUFFER_TOKEN;
+    InputSecurityToken[1].cbBuffer = ContextSizes.cbMaxSignature;
+    InputSecurityToken[1].pvBuffer = snewn(ContextSizes.cbMaxSignature, char);
+
+    winctx->maj_stat = p_MakeSignature(&winctx->context,
+                                      0,
+                                      &InputBufferDescriptor,
+                                      0);
+
+    if (winctx->maj_stat == SEC_E_OK) {
+       hash->len = InputSecurityToken[1].cbBuffer;
+       hash->data = InputSecurityToken[1].pvBuffer;
+    }
+
+    return winctx->maj_stat;
+}
+
+Ssh_gss_stat ssh_gss_free_mic(Ssh_gss_buf *hash)
+{
+    sfree(hash->data);
+    return SSH_GSS_OK;
+}
+
+#else
+
+/* Dummy function so this source file defines something if NO_GSSAPI
+   is defined. */
+
+int ssh_gss_init(void)
+{
+    return 0;
+}
+
+#endif