mirror of
git://anongit.mindrot.org/openssh.git
synced 2024-11-23 18:23:25 +08:00
1629c07c07
[monitor_wrap.c servconf.c servconf.h monitor.c sshd_config.5] Teach Match how handle config directives that are used before authentication. This allows configurations such as permitting password authentication from the local net only while requiring pubkey from offsite. ok djm@, man page bits ok jmc@
1240 lines
29 KiB
C
1240 lines
29 KiB
C
/* $OpenBSD: monitor_wrap.c,v 1.55 2007/02/19 10:45:58 dtucker Exp $ */
|
|
/*
|
|
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
|
|
* Copyright 2002 Markus Friedl <markus@openbsd.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include <errno.h>
|
|
#include <pwd.h>
|
|
#include <signal.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <openssl/bn.h>
|
|
#include <openssl/dh.h>
|
|
|
|
#include "xmalloc.h"
|
|
#include "ssh.h"
|
|
#include "dh.h"
|
|
#include "buffer.h"
|
|
#include "key.h"
|
|
#include "cipher.h"
|
|
#include "kex.h"
|
|
#include "hostfile.h"
|
|
#include "auth.h"
|
|
#include "auth-options.h"
|
|
#include "packet.h"
|
|
#include "mac.h"
|
|
#include "log.h"
|
|
#ifdef TARGET_OS_MAC /* XXX Broken krb5 headers on Mac */
|
|
#undef TARGET_OS_MAC
|
|
#include "zlib.h"
|
|
#define TARGET_OS_MAC 1
|
|
#else
|
|
#include "zlib.h"
|
|
#endif
|
|
#include "monitor.h"
|
|
#ifdef GSSAPI
|
|
#include "ssh-gss.h"
|
|
#endif
|
|
#include "monitor_wrap.h"
|
|
#include "atomicio.h"
|
|
#include "monitor_fdpass.h"
|
|
#include "misc.h"
|
|
#include "servconf.h"
|
|
|
|
#include "channels.h"
|
|
#include "session.h"
|
|
#include "servconf.h"
|
|
|
|
/* Imports */
|
|
extern int compat20;
|
|
extern Newkeys *newkeys[];
|
|
extern z_stream incoming_stream;
|
|
extern z_stream outgoing_stream;
|
|
extern struct monitor *pmonitor;
|
|
extern Buffer input, output;
|
|
extern Buffer loginmsg;
|
|
extern ServerOptions options;
|
|
|
|
int
|
|
mm_is_monitor(void)
|
|
{
|
|
/*
|
|
* m_pid is only set in the privileged part, and
|
|
* points to the unprivileged child.
|
|
*/
|
|
return (pmonitor && pmonitor->m_pid > 0);
|
|
}
|
|
|
|
void
|
|
mm_request_send(int sock, enum monitor_reqtype type, Buffer *m)
|
|
{
|
|
u_int mlen = buffer_len(m);
|
|
u_char buf[5];
|
|
|
|
debug3("%s entering: type %d", __func__, type);
|
|
|
|
put_u32(buf, mlen + 1);
|
|
buf[4] = (u_char) type; /* 1st byte of payload is mesg-type */
|
|
if (atomicio(vwrite, sock, buf, sizeof(buf)) != sizeof(buf))
|
|
fatal("%s: write: %s", __func__, strerror(errno));
|
|
if (atomicio(vwrite, sock, buffer_ptr(m), mlen) != mlen)
|
|
fatal("%s: write: %s", __func__, strerror(errno));
|
|
}
|
|
|
|
void
|
|
mm_request_receive(int sock, Buffer *m)
|
|
{
|
|
u_char buf[4];
|
|
u_int msg_len;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
if (atomicio(read, sock, buf, sizeof(buf)) != sizeof(buf)) {
|
|
if (errno == EPIPE)
|
|
cleanup_exit(255);
|
|
fatal("%s: read: %s", __func__, strerror(errno));
|
|
}
|
|
msg_len = get_u32(buf);
|
|
if (msg_len > 256 * 1024)
|
|
fatal("%s: read: bad msg_len %d", __func__, msg_len);
|
|
buffer_clear(m);
|
|
buffer_append_space(m, msg_len);
|
|
if (atomicio(read, sock, buffer_ptr(m), msg_len) != msg_len)
|
|
fatal("%s: read: %s", __func__, strerror(errno));
|
|
}
|
|
|
|
void
|
|
mm_request_receive_expect(int sock, enum monitor_reqtype type, Buffer *m)
|
|
{
|
|
u_char rtype;
|
|
|
|
debug3("%s entering: type %d", __func__, type);
|
|
|
|
mm_request_receive(sock, m);
|
|
rtype = buffer_get_char(m);
|
|
if (rtype != type)
|
|
fatal("%s: read: rtype %d != type %d", __func__,
|
|
rtype, type);
|
|
}
|
|
|
|
DH *
|
|
mm_choose_dh(int min, int nbits, int max)
|
|
{
|
|
BIGNUM *p, *g;
|
|
int success = 0;
|
|
Buffer m;
|
|
|
|
buffer_init(&m);
|
|
buffer_put_int(&m, min);
|
|
buffer_put_int(&m, nbits);
|
|
buffer_put_int(&m, max);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_MODULI, &m);
|
|
|
|
debug3("%s: waiting for MONITOR_ANS_MODULI", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_MODULI, &m);
|
|
|
|
success = buffer_get_char(&m);
|
|
if (success == 0)
|
|
fatal("%s: MONITOR_ANS_MODULI failed", __func__);
|
|
|
|
if ((p = BN_new()) == NULL)
|
|
fatal("%s: BN_new failed", __func__);
|
|
if ((g = BN_new()) == NULL)
|
|
fatal("%s: BN_new failed", __func__);
|
|
buffer_get_bignum2(&m, p);
|
|
buffer_get_bignum2(&m, g);
|
|
|
|
debug3("%s: remaining %d", __func__, buffer_len(&m));
|
|
buffer_free(&m);
|
|
|
|
return (dh_new_group(g, p));
|
|
}
|
|
|
|
int
|
|
mm_key_sign(Key *key, u_char **sigp, u_int *lenp, u_char *data, u_int datalen)
|
|
{
|
|
Kex *kex = *pmonitor->m_pkex;
|
|
Buffer m;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_int(&m, kex->host_key_index(key));
|
|
buffer_put_string(&m, data, datalen);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SIGN, &m);
|
|
|
|
debug3("%s: waiting for MONITOR_ANS_SIGN", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_SIGN, &m);
|
|
*sigp = buffer_get_string(&m, lenp);
|
|
buffer_free(&m);
|
|
|
|
return (0);
|
|
}
|
|
|
|
struct passwd *
|
|
mm_getpwnamallow(const char *username)
|
|
{
|
|
Buffer m;
|
|
struct passwd *pw;
|
|
u_int len;
|
|
ServerOptions *newopts;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_cstring(&m, username);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PWNAM, &m);
|
|
|
|
debug3("%s: waiting for MONITOR_ANS_PWNAM", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_PWNAM, &m);
|
|
|
|
if (buffer_get_char(&m) == 0) {
|
|
buffer_free(&m);
|
|
return (NULL);
|
|
}
|
|
pw = buffer_get_string(&m, &len);
|
|
if (len != sizeof(struct passwd))
|
|
fatal("%s: struct passwd size mismatch", __func__);
|
|
pw->pw_name = buffer_get_string(&m, NULL);
|
|
pw->pw_passwd = buffer_get_string(&m, NULL);
|
|
pw->pw_gecos = buffer_get_string(&m, NULL);
|
|
#ifdef HAVE_PW_CLASS_IN_PASSWD
|
|
pw->pw_class = buffer_get_string(&m, NULL);
|
|
#endif
|
|
pw->pw_dir = buffer_get_string(&m, NULL);
|
|
pw->pw_shell = buffer_get_string(&m, NULL);
|
|
|
|
/* copy options block as a Match directive may have changed some */
|
|
newopts = buffer_get_string(&m, &len);
|
|
if (len != sizeof(*newopts))
|
|
fatal("%s: option block size mismatch", __func__);
|
|
if (newopts->banner != NULL)
|
|
newopts->banner = buffer_get_string(&m, NULL);
|
|
copy_set_server_options(&options, newopts, 1);
|
|
xfree(newopts);
|
|
|
|
buffer_free(&m);
|
|
|
|
return (pw);
|
|
}
|
|
|
|
char *
|
|
mm_auth2_read_banner(void)
|
|
{
|
|
Buffer m;
|
|
char *banner;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
buffer_init(&m);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTH2_READ_BANNER, &m);
|
|
buffer_clear(&m);
|
|
|
|
mm_request_receive_expect(pmonitor->m_recvfd,
|
|
MONITOR_ANS_AUTH2_READ_BANNER, &m);
|
|
banner = buffer_get_string(&m, NULL);
|
|
buffer_free(&m);
|
|
|
|
/* treat empty banner as missing banner */
|
|
if (strlen(banner) == 0) {
|
|
xfree(banner);
|
|
banner = NULL;
|
|
}
|
|
return (banner);
|
|
}
|
|
|
|
/* Inform the privileged process about service and style */
|
|
|
|
void
|
|
mm_inform_authserv(char *service, char *style)
|
|
{
|
|
Buffer m;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_cstring(&m, service);
|
|
buffer_put_cstring(&m, style ? style : "");
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHSERV, &m);
|
|
|
|
buffer_free(&m);
|
|
}
|
|
|
|
/* Do the password authentication */
|
|
int
|
|
mm_auth_password(Authctxt *authctxt, char *password)
|
|
{
|
|
Buffer m;
|
|
int authenticated = 0;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_cstring(&m, password);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHPASSWORD, &m);
|
|
|
|
debug3("%s: waiting for MONITOR_ANS_AUTHPASSWORD", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_AUTHPASSWORD, &m);
|
|
|
|
authenticated = buffer_get_int(&m);
|
|
|
|
buffer_free(&m);
|
|
|
|
debug3("%s: user %sauthenticated",
|
|
__func__, authenticated ? "" : "not ");
|
|
return (authenticated);
|
|
}
|
|
|
|
int
|
|
mm_user_key_allowed(struct passwd *pw, Key *key)
|
|
{
|
|
return (mm_key_allowed(MM_USERKEY, NULL, NULL, key));
|
|
}
|
|
|
|
int
|
|
mm_hostbased_key_allowed(struct passwd *pw, char *user, char *host,
|
|
Key *key)
|
|
{
|
|
return (mm_key_allowed(MM_HOSTKEY, user, host, key));
|
|
}
|
|
|
|
int
|
|
mm_auth_rhosts_rsa_key_allowed(struct passwd *pw, char *user,
|
|
char *host, Key *key)
|
|
{
|
|
int ret;
|
|
|
|
key->type = KEY_RSA; /* XXX hack for key_to_blob */
|
|
ret = mm_key_allowed(MM_RSAHOSTKEY, user, host, key);
|
|
key->type = KEY_RSA1;
|
|
return (ret);
|
|
}
|
|
|
|
static void
|
|
mm_send_debug(Buffer *m)
|
|
{
|
|
char *msg;
|
|
|
|
while (buffer_len(m)) {
|
|
msg = buffer_get_string(m, NULL);
|
|
debug3("%s: Sending debug: %s", __func__, msg);
|
|
packet_send_debug("%s", msg);
|
|
xfree(msg);
|
|
}
|
|
}
|
|
|
|
int
|
|
mm_key_allowed(enum mm_keytype type, char *user, char *host, Key *key)
|
|
{
|
|
Buffer m;
|
|
u_char *blob;
|
|
u_int len;
|
|
int allowed = 0, have_forced = 0;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
/* Convert the key to a blob and the pass it over */
|
|
if (!key_to_blob(key, &blob, &len))
|
|
return (0);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_int(&m, type);
|
|
buffer_put_cstring(&m, user ? user : "");
|
|
buffer_put_cstring(&m, host ? host : "");
|
|
buffer_put_string(&m, blob, len);
|
|
xfree(blob);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_KEYALLOWED, &m);
|
|
|
|
debug3("%s: waiting for MONITOR_ANS_KEYALLOWED", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_KEYALLOWED, &m);
|
|
|
|
allowed = buffer_get_int(&m);
|
|
|
|
/* fake forced command */
|
|
auth_clear_options();
|
|
have_forced = buffer_get_int(&m);
|
|
forced_command = have_forced ? xstrdup("true") : NULL;
|
|
|
|
/* Send potential debug messages */
|
|
mm_send_debug(&m);
|
|
|
|
buffer_free(&m);
|
|
|
|
return (allowed);
|
|
}
|
|
|
|
/*
|
|
* This key verify needs to send the key type along, because the
|
|
* privileged parent makes the decision if the key is allowed
|
|
* for authentication.
|
|
*/
|
|
|
|
int
|
|
mm_key_verify(Key *key, u_char *sig, u_int siglen, u_char *data, u_int datalen)
|
|
{
|
|
Buffer m;
|
|
u_char *blob;
|
|
u_int len;
|
|
int verified = 0;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
/* Convert the key to a blob and the pass it over */
|
|
if (!key_to_blob(key, &blob, &len))
|
|
return (0);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_string(&m, blob, len);
|
|
buffer_put_string(&m, sig, siglen);
|
|
buffer_put_string(&m, data, datalen);
|
|
xfree(blob);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_KEYVERIFY, &m);
|
|
|
|
debug3("%s: waiting for MONITOR_ANS_KEYVERIFY", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_KEYVERIFY, &m);
|
|
|
|
verified = buffer_get_int(&m);
|
|
|
|
buffer_free(&m);
|
|
|
|
return (verified);
|
|
}
|
|
|
|
/* Export key state after authentication */
|
|
Newkeys *
|
|
mm_newkeys_from_blob(u_char *blob, int blen)
|
|
{
|
|
Buffer b;
|
|
u_int len;
|
|
Newkeys *newkey = NULL;
|
|
Enc *enc;
|
|
Mac *mac;
|
|
Comp *comp;
|
|
|
|
debug3("%s: %p(%d)", __func__, blob, blen);
|
|
#ifdef DEBUG_PK
|
|
dump_base64(stderr, blob, blen);
|
|
#endif
|
|
buffer_init(&b);
|
|
buffer_append(&b, blob, blen);
|
|
|
|
newkey = xmalloc(sizeof(*newkey));
|
|
enc = &newkey->enc;
|
|
mac = &newkey->mac;
|
|
comp = &newkey->comp;
|
|
|
|
/* Enc structure */
|
|
enc->name = buffer_get_string(&b, NULL);
|
|
buffer_get(&b, &enc->cipher, sizeof(enc->cipher));
|
|
enc->enabled = buffer_get_int(&b);
|
|
enc->block_size = buffer_get_int(&b);
|
|
enc->key = buffer_get_string(&b, &enc->key_len);
|
|
enc->iv = buffer_get_string(&b, &len);
|
|
if (len != enc->block_size)
|
|
fatal("%s: bad ivlen: expected %u != %u", __func__,
|
|
enc->block_size, len);
|
|
|
|
if (enc->name == NULL || cipher_by_name(enc->name) != enc->cipher)
|
|
fatal("%s: bad cipher name %s or pointer %p", __func__,
|
|
enc->name, enc->cipher);
|
|
|
|
/* Mac structure */
|
|
mac->name = buffer_get_string(&b, NULL);
|
|
if (mac->name == NULL || mac_init(mac, mac->name) == -1)
|
|
fatal("%s: can not init mac %s", __func__, mac->name);
|
|
mac->enabled = buffer_get_int(&b);
|
|
mac->key = buffer_get_string(&b, &len);
|
|
if (len > mac->key_len)
|
|
fatal("%s: bad mac key length: %u > %d", __func__, len,
|
|
mac->key_len);
|
|
mac->key_len = len;
|
|
|
|
/* Comp structure */
|
|
comp->type = buffer_get_int(&b);
|
|
comp->enabled = buffer_get_int(&b);
|
|
comp->name = buffer_get_string(&b, NULL);
|
|
|
|
len = buffer_len(&b);
|
|
if (len != 0)
|
|
error("newkeys_from_blob: remaining bytes in blob %u", len);
|
|
buffer_free(&b);
|
|
return (newkey);
|
|
}
|
|
|
|
int
|
|
mm_newkeys_to_blob(int mode, u_char **blobp, u_int *lenp)
|
|
{
|
|
Buffer b;
|
|
int len;
|
|
Enc *enc;
|
|
Mac *mac;
|
|
Comp *comp;
|
|
Newkeys *newkey = newkeys[mode];
|
|
|
|
debug3("%s: converting %p", __func__, newkey);
|
|
|
|
if (newkey == NULL) {
|
|
error("%s: newkey == NULL", __func__);
|
|
return 0;
|
|
}
|
|
enc = &newkey->enc;
|
|
mac = &newkey->mac;
|
|
comp = &newkey->comp;
|
|
|
|
buffer_init(&b);
|
|
/* Enc structure */
|
|
buffer_put_cstring(&b, enc->name);
|
|
/* The cipher struct is constant and shared, you export pointer */
|
|
buffer_append(&b, &enc->cipher, sizeof(enc->cipher));
|
|
buffer_put_int(&b, enc->enabled);
|
|
buffer_put_int(&b, enc->block_size);
|
|
buffer_put_string(&b, enc->key, enc->key_len);
|
|
packet_get_keyiv(mode, enc->iv, enc->block_size);
|
|
buffer_put_string(&b, enc->iv, enc->block_size);
|
|
|
|
/* Mac structure */
|
|
buffer_put_cstring(&b, mac->name);
|
|
buffer_put_int(&b, mac->enabled);
|
|
buffer_put_string(&b, mac->key, mac->key_len);
|
|
|
|
/* Comp structure */
|
|
buffer_put_int(&b, comp->type);
|
|
buffer_put_int(&b, comp->enabled);
|
|
buffer_put_cstring(&b, comp->name);
|
|
|
|
len = buffer_len(&b);
|
|
if (lenp != NULL)
|
|
*lenp = len;
|
|
if (blobp != NULL) {
|
|
*blobp = xmalloc(len);
|
|
memcpy(*blobp, buffer_ptr(&b), len);
|
|
}
|
|
memset(buffer_ptr(&b), 0, len);
|
|
buffer_free(&b);
|
|
return len;
|
|
}
|
|
|
|
static void
|
|
mm_send_kex(Buffer *m, Kex *kex)
|
|
{
|
|
buffer_put_string(m, kex->session_id, kex->session_id_len);
|
|
buffer_put_int(m, kex->we_need);
|
|
buffer_put_int(m, kex->hostkey_type);
|
|
buffer_put_int(m, kex->kex_type);
|
|
buffer_put_string(m, buffer_ptr(&kex->my), buffer_len(&kex->my));
|
|
buffer_put_string(m, buffer_ptr(&kex->peer), buffer_len(&kex->peer));
|
|
buffer_put_int(m, kex->flags);
|
|
buffer_put_cstring(m, kex->client_version_string);
|
|
buffer_put_cstring(m, kex->server_version_string);
|
|
}
|
|
|
|
void
|
|
mm_send_keystate(struct monitor *monitor)
|
|
{
|
|
Buffer m;
|
|
u_char *blob, *p;
|
|
u_int bloblen, plen;
|
|
u_int32_t seqnr, packets;
|
|
u_int64_t blocks;
|
|
|
|
buffer_init(&m);
|
|
|
|
if (!compat20) {
|
|
u_char iv[24];
|
|
u_char *key;
|
|
u_int ivlen, keylen;
|
|
|
|
buffer_put_int(&m, packet_get_protocol_flags());
|
|
|
|
buffer_put_int(&m, packet_get_ssh1_cipher());
|
|
|
|
debug3("%s: Sending ssh1 KEY+IV", __func__);
|
|
keylen = packet_get_encryption_key(NULL);
|
|
key = xmalloc(keylen+1); /* add 1 if keylen == 0 */
|
|
keylen = packet_get_encryption_key(key);
|
|
buffer_put_string(&m, key, keylen);
|
|
memset(key, 0, keylen);
|
|
xfree(key);
|
|
|
|
ivlen = packet_get_keyiv_len(MODE_OUT);
|
|
packet_get_keyiv(MODE_OUT, iv, ivlen);
|
|
buffer_put_string(&m, iv, ivlen);
|
|
ivlen = packet_get_keyiv_len(MODE_OUT);
|
|
packet_get_keyiv(MODE_IN, iv, ivlen);
|
|
buffer_put_string(&m, iv, ivlen);
|
|
goto skip;
|
|
} else {
|
|
/* Kex for rekeying */
|
|
mm_send_kex(&m, *monitor->m_pkex);
|
|
}
|
|
|
|
debug3("%s: Sending new keys: %p %p",
|
|
__func__, newkeys[MODE_OUT], newkeys[MODE_IN]);
|
|
|
|
/* Keys from Kex */
|
|
if (!mm_newkeys_to_blob(MODE_OUT, &blob, &bloblen))
|
|
fatal("%s: conversion of newkeys failed", __func__);
|
|
|
|
buffer_put_string(&m, blob, bloblen);
|
|
xfree(blob);
|
|
|
|
if (!mm_newkeys_to_blob(MODE_IN, &blob, &bloblen))
|
|
fatal("%s: conversion of newkeys failed", __func__);
|
|
|
|
buffer_put_string(&m, blob, bloblen);
|
|
xfree(blob);
|
|
|
|
packet_get_state(MODE_OUT, &seqnr, &blocks, &packets);
|
|
buffer_put_int(&m, seqnr);
|
|
buffer_put_int64(&m, blocks);
|
|
buffer_put_int(&m, packets);
|
|
packet_get_state(MODE_IN, &seqnr, &blocks, &packets);
|
|
buffer_put_int(&m, seqnr);
|
|
buffer_put_int64(&m, blocks);
|
|
buffer_put_int(&m, packets);
|
|
|
|
debug3("%s: New keys have been sent", __func__);
|
|
skip:
|
|
/* More key context */
|
|
plen = packet_get_keycontext(MODE_OUT, NULL);
|
|
p = xmalloc(plen+1);
|
|
packet_get_keycontext(MODE_OUT, p);
|
|
buffer_put_string(&m, p, plen);
|
|
xfree(p);
|
|
|
|
plen = packet_get_keycontext(MODE_IN, NULL);
|
|
p = xmalloc(plen+1);
|
|
packet_get_keycontext(MODE_IN, p);
|
|
buffer_put_string(&m, p, plen);
|
|
xfree(p);
|
|
|
|
/* Compression state */
|
|
debug3("%s: Sending compression state", __func__);
|
|
buffer_put_string(&m, &outgoing_stream, sizeof(outgoing_stream));
|
|
buffer_put_string(&m, &incoming_stream, sizeof(incoming_stream));
|
|
|
|
/* Network I/O buffers */
|
|
buffer_put_string(&m, buffer_ptr(&input), buffer_len(&input));
|
|
buffer_put_string(&m, buffer_ptr(&output), buffer_len(&output));
|
|
|
|
mm_request_send(monitor->m_recvfd, MONITOR_REQ_KEYEXPORT, &m);
|
|
debug3("%s: Finished sending state", __func__);
|
|
|
|
buffer_free(&m);
|
|
}
|
|
|
|
int
|
|
mm_pty_allocate(int *ptyfd, int *ttyfd, char *namebuf, size_t namebuflen)
|
|
{
|
|
Buffer m;
|
|
char *p, *msg;
|
|
int success = 0;
|
|
|
|
buffer_init(&m);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PTY, &m);
|
|
|
|
debug3("%s: waiting for MONITOR_ANS_PTY", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_PTY, &m);
|
|
|
|
success = buffer_get_int(&m);
|
|
if (success == 0) {
|
|
debug3("%s: pty alloc failed", __func__);
|
|
buffer_free(&m);
|
|
return (0);
|
|
}
|
|
p = buffer_get_string(&m, NULL);
|
|
msg = buffer_get_string(&m, NULL);
|
|
buffer_free(&m);
|
|
|
|
strlcpy(namebuf, p, namebuflen); /* Possible truncation */
|
|
xfree(p);
|
|
|
|
buffer_append(&loginmsg, msg, strlen(msg));
|
|
xfree(msg);
|
|
|
|
*ptyfd = mm_receive_fd(pmonitor->m_recvfd);
|
|
*ttyfd = mm_receive_fd(pmonitor->m_recvfd);
|
|
|
|
/* Success */
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
mm_session_pty_cleanup2(Session *s)
|
|
{
|
|
Buffer m;
|
|
|
|
if (s->ttyfd == -1)
|
|
return;
|
|
buffer_init(&m);
|
|
buffer_put_cstring(&m, s->tty);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PTYCLEANUP, &m);
|
|
buffer_free(&m);
|
|
|
|
/* closed dup'ed master */
|
|
if (close(s->ptymaster) < 0)
|
|
error("close(s->ptymaster): %s", strerror(errno));
|
|
|
|
/* unlink pty from session */
|
|
s->ttyfd = -1;
|
|
}
|
|
|
|
#ifdef USE_PAM
|
|
void
|
|
mm_start_pam(Authctxt *authctxt)
|
|
{
|
|
Buffer m;
|
|
|
|
debug3("%s entering", __func__);
|
|
if (!options.use_pam)
|
|
fatal("UsePAM=no, but ended up in %s anyway", __func__);
|
|
|
|
buffer_init(&m);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PAM_START, &m);
|
|
|
|
buffer_free(&m);
|
|
}
|
|
|
|
u_int
|
|
mm_do_pam_account(void)
|
|
{
|
|
Buffer m;
|
|
u_int ret;
|
|
char *msg;
|
|
|
|
debug3("%s entering", __func__);
|
|
if (!options.use_pam)
|
|
fatal("UsePAM=no, but ended up in %s anyway", __func__);
|
|
|
|
buffer_init(&m);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PAM_ACCOUNT, &m);
|
|
|
|
mm_request_receive_expect(pmonitor->m_recvfd,
|
|
MONITOR_ANS_PAM_ACCOUNT, &m);
|
|
ret = buffer_get_int(&m);
|
|
msg = buffer_get_string(&m, NULL);
|
|
buffer_append(&loginmsg, msg, strlen(msg));
|
|
xfree(msg);
|
|
|
|
buffer_free(&m);
|
|
|
|
debug3("%s returning %d", __func__, ret);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
void *
|
|
mm_sshpam_init_ctx(Authctxt *authctxt)
|
|
{
|
|
Buffer m;
|
|
int success;
|
|
|
|
debug3("%s", __func__);
|
|
buffer_init(&m);
|
|
buffer_put_cstring(&m, authctxt->user);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PAM_INIT_CTX, &m);
|
|
debug3("%s: waiting for MONITOR_ANS_PAM_INIT_CTX", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_PAM_INIT_CTX, &m);
|
|
success = buffer_get_int(&m);
|
|
if (success == 0) {
|
|
debug3("%s: pam_init_ctx failed", __func__);
|
|
buffer_free(&m);
|
|
return (NULL);
|
|
}
|
|
buffer_free(&m);
|
|
return (authctxt);
|
|
}
|
|
|
|
int
|
|
mm_sshpam_query(void *ctx, char **name, char **info,
|
|
u_int *num, char ***prompts, u_int **echo_on)
|
|
{
|
|
Buffer m;
|
|
u_int i;
|
|
int ret;
|
|
|
|
debug3("%s", __func__);
|
|
buffer_init(&m);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PAM_QUERY, &m);
|
|
debug3("%s: waiting for MONITOR_ANS_PAM_QUERY", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_PAM_QUERY, &m);
|
|
ret = buffer_get_int(&m);
|
|
debug3("%s: pam_query returned %d", __func__, ret);
|
|
*name = buffer_get_string(&m, NULL);
|
|
*info = buffer_get_string(&m, NULL);
|
|
*num = buffer_get_int(&m);
|
|
if (*num > PAM_MAX_NUM_MSG)
|
|
fatal("%s: recieved %u PAM messages, expected <= %u",
|
|
__func__, *num, PAM_MAX_NUM_MSG);
|
|
*prompts = xcalloc((*num + 1), sizeof(char *));
|
|
*echo_on = xcalloc((*num + 1), sizeof(u_int));
|
|
for (i = 0; i < *num; ++i) {
|
|
(*prompts)[i] = buffer_get_string(&m, NULL);
|
|
(*echo_on)[i] = buffer_get_int(&m);
|
|
}
|
|
buffer_free(&m);
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
mm_sshpam_respond(void *ctx, u_int num, char **resp)
|
|
{
|
|
Buffer m;
|
|
u_int i;
|
|
int ret;
|
|
|
|
debug3("%s", __func__);
|
|
buffer_init(&m);
|
|
buffer_put_int(&m, num);
|
|
for (i = 0; i < num; ++i)
|
|
buffer_put_cstring(&m, resp[i]);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PAM_RESPOND, &m);
|
|
debug3("%s: waiting for MONITOR_ANS_PAM_RESPOND", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_PAM_RESPOND, &m);
|
|
ret = buffer_get_int(&m);
|
|
debug3("%s: pam_respond returned %d", __func__, ret);
|
|
buffer_free(&m);
|
|
return (ret);
|
|
}
|
|
|
|
void
|
|
mm_sshpam_free_ctx(void *ctxtp)
|
|
{
|
|
Buffer m;
|
|
|
|
debug3("%s", __func__);
|
|
buffer_init(&m);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_PAM_FREE_CTX, &m);
|
|
debug3("%s: waiting for MONITOR_ANS_PAM_FREE_CTX", __func__);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_PAM_FREE_CTX, &m);
|
|
buffer_free(&m);
|
|
}
|
|
#endif /* USE_PAM */
|
|
|
|
/* Request process termination */
|
|
|
|
void
|
|
mm_terminate(void)
|
|
{
|
|
Buffer m;
|
|
|
|
buffer_init(&m);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_TERM, &m);
|
|
buffer_free(&m);
|
|
}
|
|
|
|
int
|
|
mm_ssh1_session_key(BIGNUM *num)
|
|
{
|
|
int rsafail;
|
|
Buffer m;
|
|
|
|
buffer_init(&m);
|
|
buffer_put_bignum2(&m, num);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SESSKEY, &m);
|
|
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_SESSKEY, &m);
|
|
|
|
rsafail = buffer_get_int(&m);
|
|
buffer_get_bignum2(&m, num);
|
|
|
|
buffer_free(&m);
|
|
|
|
return (rsafail);
|
|
}
|
|
|
|
static void
|
|
mm_chall_setup(char **name, char **infotxt, u_int *numprompts,
|
|
char ***prompts, u_int **echo_on)
|
|
{
|
|
*name = xstrdup("");
|
|
*infotxt = xstrdup("");
|
|
*numprompts = 1;
|
|
*prompts = xcalloc(*numprompts, sizeof(char *));
|
|
*echo_on = xcalloc(*numprompts, sizeof(u_int));
|
|
(*echo_on)[0] = 0;
|
|
}
|
|
|
|
int
|
|
mm_bsdauth_query(void *ctx, char **name, char **infotxt,
|
|
u_int *numprompts, char ***prompts, u_int **echo_on)
|
|
{
|
|
Buffer m;
|
|
u_int success;
|
|
char *challenge;
|
|
|
|
debug3("%s: entering", __func__);
|
|
|
|
buffer_init(&m);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_BSDAUTHQUERY, &m);
|
|
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_BSDAUTHQUERY,
|
|
&m);
|
|
success = buffer_get_int(&m);
|
|
if (success == 0) {
|
|
debug3("%s: no challenge", __func__);
|
|
buffer_free(&m);
|
|
return (-1);
|
|
}
|
|
|
|
/* Get the challenge, and format the response */
|
|
challenge = buffer_get_string(&m, NULL);
|
|
buffer_free(&m);
|
|
|
|
mm_chall_setup(name, infotxt, numprompts, prompts, echo_on);
|
|
(*prompts)[0] = challenge;
|
|
|
|
debug3("%s: received challenge: %s", __func__, challenge);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
mm_bsdauth_respond(void *ctx, u_int numresponses, char **responses)
|
|
{
|
|
Buffer m;
|
|
int authok;
|
|
|
|
debug3("%s: entering", __func__);
|
|
if (numresponses != 1)
|
|
return (-1);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_cstring(&m, responses[0]);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_BSDAUTHRESPOND, &m);
|
|
|
|
mm_request_receive_expect(pmonitor->m_recvfd,
|
|
MONITOR_ANS_BSDAUTHRESPOND, &m);
|
|
|
|
authok = buffer_get_int(&m);
|
|
buffer_free(&m);
|
|
|
|
return ((authok == 0) ? -1 : 0);
|
|
}
|
|
|
|
#ifdef SKEY
|
|
int
|
|
mm_skey_query(void *ctx, char **name, char **infotxt,
|
|
u_int *numprompts, char ***prompts, u_int **echo_on)
|
|
{
|
|
Buffer m;
|
|
u_int success;
|
|
char *challenge;
|
|
|
|
debug3("%s: entering", __func__);
|
|
|
|
buffer_init(&m);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SKEYQUERY, &m);
|
|
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_SKEYQUERY,
|
|
&m);
|
|
success = buffer_get_int(&m);
|
|
if (success == 0) {
|
|
debug3("%s: no challenge", __func__);
|
|
buffer_free(&m);
|
|
return (-1);
|
|
}
|
|
|
|
/* Get the challenge, and format the response */
|
|
challenge = buffer_get_string(&m, NULL);
|
|
buffer_free(&m);
|
|
|
|
debug3("%s: received challenge: %s", __func__, challenge);
|
|
|
|
mm_chall_setup(name, infotxt, numprompts, prompts, echo_on);
|
|
|
|
xasprintf(*prompts, "%s%s", challenge, SKEY_PROMPT);
|
|
xfree(challenge);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
mm_skey_respond(void *ctx, u_int numresponses, char **responses)
|
|
{
|
|
Buffer m;
|
|
int authok;
|
|
|
|
debug3("%s: entering", __func__);
|
|
if (numresponses != 1)
|
|
return (-1);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_cstring(&m, responses[0]);
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SKEYRESPOND, &m);
|
|
|
|
mm_request_receive_expect(pmonitor->m_recvfd,
|
|
MONITOR_ANS_SKEYRESPOND, &m);
|
|
|
|
authok = buffer_get_int(&m);
|
|
buffer_free(&m);
|
|
|
|
return ((authok == 0) ? -1 : 0);
|
|
}
|
|
#endif /* SKEY */
|
|
|
|
void
|
|
mm_ssh1_session_id(u_char session_id[16])
|
|
{
|
|
Buffer m;
|
|
int i;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
buffer_init(&m);
|
|
for (i = 0; i < 16; i++)
|
|
buffer_put_char(&m, session_id[i]);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SESSID, &m);
|
|
buffer_free(&m);
|
|
}
|
|
|
|
int
|
|
mm_auth_rsa_key_allowed(struct passwd *pw, BIGNUM *client_n, Key **rkey)
|
|
{
|
|
Buffer m;
|
|
Key *key;
|
|
u_char *blob;
|
|
u_int blen;
|
|
int allowed = 0, have_forced = 0;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_bignum2(&m, client_n);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_RSAKEYALLOWED, &m);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_RSAKEYALLOWED, &m);
|
|
|
|
allowed = buffer_get_int(&m);
|
|
|
|
/* fake forced command */
|
|
auth_clear_options();
|
|
have_forced = buffer_get_int(&m);
|
|
forced_command = have_forced ? xstrdup("true") : NULL;
|
|
|
|
if (allowed && rkey != NULL) {
|
|
blob = buffer_get_string(&m, &blen);
|
|
if ((key = key_from_blob(blob, blen)) == NULL)
|
|
fatal("%s: key_from_blob failed", __func__);
|
|
*rkey = key;
|
|
xfree(blob);
|
|
}
|
|
mm_send_debug(&m);
|
|
buffer_free(&m);
|
|
|
|
return (allowed);
|
|
}
|
|
|
|
BIGNUM *
|
|
mm_auth_rsa_generate_challenge(Key *key)
|
|
{
|
|
Buffer m;
|
|
BIGNUM *challenge;
|
|
u_char *blob;
|
|
u_int blen;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
if ((challenge = BN_new()) == NULL)
|
|
fatal("%s: BN_new failed", __func__);
|
|
|
|
key->type = KEY_RSA; /* XXX cheat for key_to_blob */
|
|
if (key_to_blob(key, &blob, &blen) == 0)
|
|
fatal("%s: key_to_blob failed", __func__);
|
|
key->type = KEY_RSA1;
|
|
|
|
buffer_init(&m);
|
|
buffer_put_string(&m, blob, blen);
|
|
xfree(blob);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_RSACHALLENGE, &m);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_RSACHALLENGE, &m);
|
|
|
|
buffer_get_bignum2(&m, challenge);
|
|
buffer_free(&m);
|
|
|
|
return (challenge);
|
|
}
|
|
|
|
int
|
|
mm_auth_rsa_verify_response(Key *key, BIGNUM *p, u_char response[16])
|
|
{
|
|
Buffer m;
|
|
u_char *blob;
|
|
u_int blen;
|
|
int success = 0;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
key->type = KEY_RSA; /* XXX cheat for key_to_blob */
|
|
if (key_to_blob(key, &blob, &blen) == 0)
|
|
fatal("%s: key_to_blob failed", __func__);
|
|
key->type = KEY_RSA1;
|
|
|
|
buffer_init(&m);
|
|
buffer_put_string(&m, blob, blen);
|
|
buffer_put_string(&m, response, 16);
|
|
xfree(blob);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_RSARESPONSE, &m);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_RSARESPONSE, &m);
|
|
|
|
success = buffer_get_int(&m);
|
|
buffer_free(&m);
|
|
|
|
return (success);
|
|
}
|
|
|
|
#ifdef SSH_AUDIT_EVENTS
|
|
void
|
|
mm_audit_event(ssh_audit_event_t event)
|
|
{
|
|
Buffer m;
|
|
|
|
debug3("%s entering", __func__);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_int(&m, event);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_EVENT, &m);
|
|
buffer_free(&m);
|
|
}
|
|
|
|
void
|
|
mm_audit_run_command(const char *command)
|
|
{
|
|
Buffer m;
|
|
|
|
debug3("%s entering command %s", __func__, command);
|
|
|
|
buffer_init(&m);
|
|
buffer_put_cstring(&m, command);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUDIT_COMMAND, &m);
|
|
buffer_free(&m);
|
|
}
|
|
#endif /* SSH_AUDIT_EVENTS */
|
|
|
|
#ifdef GSSAPI
|
|
OM_uint32
|
|
mm_ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID goid)
|
|
{
|
|
Buffer m;
|
|
OM_uint32 major;
|
|
|
|
/* Client doesn't get to see the context */
|
|
*ctx = NULL;
|
|
|
|
buffer_init(&m);
|
|
buffer_put_string(&m, goid->elements, goid->length);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSETUP, &m);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSETUP, &m);
|
|
|
|
major = buffer_get_int(&m);
|
|
|
|
buffer_free(&m);
|
|
return (major);
|
|
}
|
|
|
|
OM_uint32
|
|
mm_ssh_gssapi_accept_ctx(Gssctxt *ctx, gss_buffer_desc *in,
|
|
gss_buffer_desc *out, OM_uint32 *flags)
|
|
{
|
|
Buffer m;
|
|
OM_uint32 major;
|
|
u_int len;
|
|
|
|
buffer_init(&m);
|
|
buffer_put_string(&m, in->value, in->length);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSTEP, &m);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSTEP, &m);
|
|
|
|
major = buffer_get_int(&m);
|
|
out->value = buffer_get_string(&m, &len);
|
|
out->length = len;
|
|
if (flags)
|
|
*flags = buffer_get_int(&m);
|
|
|
|
buffer_free(&m);
|
|
|
|
return (major);
|
|
}
|
|
|
|
OM_uint32
|
|
mm_ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
|
|
{
|
|
Buffer m;
|
|
OM_uint32 major;
|
|
|
|
buffer_init(&m);
|
|
buffer_put_string(&m, gssbuf->value, gssbuf->length);
|
|
buffer_put_string(&m, gssmic->value, gssmic->length);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSCHECKMIC, &m);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSCHECKMIC,
|
|
&m);
|
|
|
|
major = buffer_get_int(&m);
|
|
buffer_free(&m);
|
|
return(major);
|
|
}
|
|
|
|
int
|
|
mm_ssh_gssapi_userok(char *user)
|
|
{
|
|
Buffer m;
|
|
int authenticated = 0;
|
|
|
|
buffer_init(&m);
|
|
|
|
mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, &m);
|
|
mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUSEROK,
|
|
&m);
|
|
|
|
authenticated = buffer_get_int(&m);
|
|
|
|
buffer_free(&m);
|
|
debug3("%s: user %sauthenticated",__func__, authenticated ? "" : "not ");
|
|
return (authenticated);
|
|
}
|
|
#endif /* GSSAPI */
|