Files
inetutils/libtelnet/shishi.c
2025-01-01 18:21:25 +01:00

731 lines
16 KiB
C

/*
Copyright (C) 2003-2025 Free Software Foundation, Inc.
This file is part of GNU Inetutils.
GNU Inetutils is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
GNU Inetutils is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see `http://www.gnu.org/licenses/'. */
/* Written by Simon Josefsson and Nicolas Pouvesle, based on
kerberos5.c from GNU Inetutils. */
#include <config.h>
#ifdef SHISHI
# include <stdlib.h>
# include <stdio.h>
# include <arpa/telnet.h>
# include <shishi.h>
# include <netdb.h>
# include <ctype.h>
# include <syslog.h>
# include <string.h>
# include <attribute.h>
# include "auth.h"
# include "misc.h"
# ifdef ENCRYPTION
# include "encrypt.h"
# endif
char *dest_realm = NULL;
Shishi_key *enckey = NULL;
static unsigned char str_data[2048] = { IAC, SB, TELOPT_AUTHENTICATION, 0,
AUTHTYPE_KERBEROS_V5,
};
# define KRB_AUTH 0 /* Authentication data follows */
# define KRB_REJECT 1 /* Rejected (reason might follow) */
# define KRB_ACCEPT 2 /* Accepted */
# define KRB_RESPONSE 3 /* Response for mutual auth. */
Shishi *shishi_handle = NULL;
Shishi_ap *auth_handle;
# define DEBUG(c) if (auth_debug_mode) printf c
/* Callback from consumer. */
extern void printsub (char, unsigned char *, int);
static int
Data (TN_Authenticator *ap, int type, void *d, int c)
{
unsigned char *p = str_data + 4;
unsigned char *cd = (unsigned char *) d;
/* Submitted as test data. */
if (c == -1)
c = strlen ((char *) cd);
if (auth_debug_mode)
{
printf ("%s:%d: [%d] (%d)",
str_data[3] == TELQUAL_IS ? ">>>IS" : ">>>REPLY",
str_data[3], type, c);
printd (d, c);
printf ("\r\n");
}
*p++ = ap->type;
*p++ = ap->way;
*p++ = type;
while (c-- > 0)
{
if ((*p++ = *cd++) == IAC)
*p++ = IAC;
}
*p++ = IAC;
*p++ = SE;
if (str_data[3] == TELQUAL_IS)
printsub ('>', &str_data[2], p - &str_data[2]);
return (net_write (str_data, p - str_data));
}
Shishi *shishi_telnet = NULL;
/* FIXME: Reverse return code! */
int
krb5shishi_init (TN_Authenticator *ap MAYBE_UNUSED, int server)
{
if (server)
str_data[3] = TELQUAL_REPLY;
else
str_data[3] = TELQUAL_IS;
if (!shishi_check_version (SHISHI_VERSION))
return 0;
return 1;
}
static int
delayed_shishi_init (void)
{
if (shishi_handle)
return 1;
if (str_data[3] == TELQUAL_REPLY)
{
if (!shishi_handle && shishi_init_server (&shishi_handle) != SHISHI_OK)
return 0;
}
else
{
if (!shishi_handle && shishi_init (&shishi_handle) != SHISHI_OK)
return 0;
}
return 1;
}
void
krb5shishi_cleanup (TN_Authenticator *ap MAYBE_UNUSED)
{
if (shishi_handle == NULL)
return;
shishi_done (shishi_handle);
shishi_handle = NULL;
}
int
krb5shishi_send (TN_Authenticator *ap)
{
int ap_opts;
char type_check[2];
Shishi_tkt *tkt;
Shishi_tkts_hint hint;
int rc;
char *tmp;
char *apreq;
size_t apreq_len;
if (!UserNameRequested)
{
DEBUG (("telnet: Kerberos V5: no user name supplied\r\n"));
return 0;
}
if (!delayed_shishi_init ())
{
DEBUG (("telnet: Kerberos V5: shishi initialization failed\r\n"));
return 0;
}
tmp = malloc (strlen ("host/") + strlen (RemoteHostName) + 1);
if (tmp == NULL)
{
DEBUG (("telnet: Kerberos V5: shishi memory allocation failed\r\n"));
return 0;
}
/* Check for Kerberos prefix in principal name. */
if (strchr (RemoteHostName, '/'))
strcpy (tmp, RemoteHostName);
else
sprintf (tmp, "host/%s", RemoteHostName);
memset (&hint, 0, sizeof (hint));
hint.server = tmp;
hint.client = UserNameRequested;
if (dest_realm && *dest_realm)
shishi_realm_default_set (shishi_handle, dest_realm);
else
{
/* Retrieve realm assigned to this server as per configuration. */
char *p = strchr (RemoteHostName, '/');
if (p)
++p;
else
p = RemoteHostName;
shishi_realm_default_set (shishi_handle,
shishi_realm_for_server (shishi_handle, p));
}
tkt = shishi_tkts_get (shishi_tkts_default (shishi_handle), &hint);
free (tmp);
if (!tkt)
{
DEBUG (("telnet: Kerberos V5: no shishi ticket for server\r\n"));
return 0;
}
if (auth_debug_mode)
shishi_tkt_pretty_print (tkt, stdout);
if ((ap->way & AUTH_HOW_MASK) == AUTH_HOW_MUTUAL)
ap_opts = SHISHI_APOPTIONS_MUTUAL_REQUIRED;
else
ap_opts = 0;
type_check[0] = ap->type;
type_check[1] = ap->way;
# ifndef DONT_ALWAYS_USE_DES
/* Even if we are not using a DES key, we can still try a DES
session-key. Then we can support DES_?FB64 encryption with 3DES
or AES keys against non-RFC 2952 implementations. Of course, it
would be better to follow RFC 2952, but enc_des.c does not
support this (see comment in that file). */
if (shishi_tkt_keytype_fast (tkt) != SHISHI_DES_CBC_CRC &&
shishi_tkt_keytype_fast (tkt) != SHISHI_DES_CBC_MD4 &&
shishi_tkt_keytype_fast (tkt) != SHISHI_DES_CBC_MD5)
rc = shishi_ap_etype_tktoptionsdata (shishi_handle, &auth_handle,
SHISHI_DES_CBC_MD5,
tkt, ap_opts,
(char *) &type_check, 2);
else
# endif
rc = shishi_ap_tktoptionsdata (shishi_handle, &auth_handle,
tkt, ap_opts, (char *) &type_check, 2);
if (rc != SHISHI_OK)
{
DEBUG (("telnet: Kerberos V5: Could not make AP-REQ (%s)\r\n",
shishi_strerror (rc)));
return 0;
}
# ifdef ENCRYPTION
if (enckey)
{
shishi_key_done (enckey);
enckey = NULL;
}
rc = shishi_authenticator_get_subkey
(shishi_handle, shishi_ap_authenticator (auth_handle), &enckey);
if (rc != SHISHI_OK)
{
DEBUG (("telnet: Kerberos V5: could not get encryption key (%s)\r\n",
shishi_strerror (rc)));
return 0;
}
# endif
rc = shishi_ap_req_der (auth_handle, &apreq, &apreq_len);
if (rc != SHISHI_OK)
{
DEBUG (("telnet: Kerberos V5: could not DER encode (%s)\r\n",
shishi_strerror (rc)));
return 0;
}
if (auth_debug_mode)
{
shishi_authenticator_print (shishi_handle, stdout,
shishi_ap_authenticator (auth_handle));
shishi_apreq_print (shishi_handle, stdout, shishi_ap_req (auth_handle));
}
if (!auth_sendname (UserNameRequested, strlen (UserNameRequested)))
{
DEBUG (("telnet: Not enough room for user name\r\n"));
return 0;
}
if (!Data (ap, KRB_AUTH, apreq, apreq_len))
{
DEBUG (("telnet: Not enough room for authentication data\r\n"));
return 0;
}
DEBUG (("telnet: Sent Kerberos V5 credentials to server\r\n"));
return 1;
}
# ifdef ENCRYPTION
static void
shishi_init_key (Session_Key *skey, int type)
{
int32_t etype = shishi_key_type (enckey);
if (etype == SHISHI_DES_CBC_CRC || etype == SHISHI_DES_CBC_MD4
|| etype == SHISHI_DES_CBC_MD5)
skey->type = SK_DES;
else
skey->type = SK_OTHER;
skey->length = shishi_key_length (enckey);
skey->data = (unsigned char *) shishi_key_value (enckey);
encrypt_session_key (skey, type);
}
# endif
void
krb5shishi_reply (TN_Authenticator *ap, unsigned char *data, int cnt)
{
static int mutual_complete = 0;
# ifdef ENCRYPTION
Session_Key skey;
# endif
if (cnt-- < 1)
return;
switch (*data++)
{
case KRB_REJECT:
if (cnt > 0)
printf ("[ Kerberos V5 rejects authentication: %.*s ]\r\n",
cnt, data);
else
printf ("[ Kerberos V5 refuses authentication ]\r\n");
auth_send_retry ();
return;
case KRB_ACCEPT:
if (!mutual_complete)
{
if ((ap->way & AUTH_HOW_MASK) == AUTH_HOW_MUTUAL)
{
printf ("[ Kerberos V5 accepted you, "
"but didn't provide mutual authentication! ]\r\n");
auth_send_retry ();
break;
}
# ifdef ENCRYPTION
shishi_init_key (&skey, 0);
# endif
}
if (cnt)
printf ("[ Kerberos V5 accepts you as ``%.*s''%s ]\r\n", cnt, data,
mutual_complete ?
" (server authenticated)" : " (server NOT authenticated)");
else
printf ("[ Kerberos V5 accepts you ]\r\n");
auth_finished (ap, AUTH_USER);
/* This was last access to handle on behalf of the client. */
shishi_done (shishi_handle);
shishi_handle = NULL;
break;
case KRB_RESPONSE:
if ((ap->way & AUTH_HOW_MASK) == AUTH_HOW_MUTUAL)
{
if (shishi_ap_rep_verify_der (auth_handle, (char *) data, cnt)
!= SHISHI_OK)
{
printf ("[ Mutual authentication failed ]\r\n");
auth_send_retry ();
break;
}
if (auth_debug_mode)
{
shishi_aprep_print (shishi_handle, stdout,
shishi_ap_rep (auth_handle));
shishi_encapreppart_print (shishi_handle, stdout,
shishi_ap_encapreppart
(auth_handle));
}
# ifdef ENCRYPTION
shishi_init_key (&skey, 0);
# endif
mutual_complete = 1;
}
break;
default:
DEBUG (("Unknown Kerberos option %d\r\n", data[-1]));
}
}
int
krb5shishi_status (TN_Authenticator *ap MAYBE_UNUSED,
char *name, size_t len, int level)
{
int status;
if (level < AUTH_USER)
return level;
if (UserNameRequested
&& shishi_authorized_p (shishi_handle,
shishi_ap_tkt (auth_handle), UserNameRequested))
{
/* FIXME: Check buffer length */
strncpy (name, UserNameRequested, len);
status = AUTH_VALID;
}
else
status = AUTH_USER;
return status;
}
static int
krb5shishi_is_auth (TN_Authenticator *a, unsigned char *data, int cnt,
char *errbuf, int errbuflen)
{
Shishi_key *key;
int rc;
char *cnamerealm, *server = NULL, *realm = NULL;
size_t cnamerealmlen;
# ifdef ENCRYPTION
Session_Key skey;
# endif
if (!delayed_shishi_init ())
{
DEBUG (("telnet: Kerberos V5: shishi initialization failed\r\n"));
return 0;
}
/* Enable use of `~/.k5login'. */
if (shishi_check_version ("1.0.2")) /* Faulty in version 1.0.1. */
{
rc = shishi_cfg_authorizationtype_set (shishi_handle, "k5login basic");
if (rc != SHISHI_OK)
{
snprintf (errbuf, errbuflen,
"Cannot initiate authorization types: %s",
shishi_error (shishi_handle));
return rc;
}
}
if (ServerPrincipal && *ServerPrincipal)
{
rc = shishi_parse_name (shishi_handle, ServerPrincipal,
&server, &realm);
if (rc != SHISHI_OK)
{
snprintf (errbuf, errbuflen,
"Cannot parse server principal name: %s",
shishi_strerror (rc));
return 1;
}
if (realm)
shishi_realm_default_set (shishi_handle, realm);
/* Reclaim an empty server part. */
if (server && !*server)
{
free (server);
server = NULL;
}
}
if (!server)
{
server = malloc (strlen ("host/") + strlen (LocalHostName) + 1);
if (server)
sprintf (server, "host/%s", LocalHostName);
}
if (server)
{
/* Two possible action on `server':
* "srv.local" : rewrite as "host/srv.local"
* "tn/srv.local" : accept as is
*/
char *p = strchr (server, '/');
if (!p)
{
p = server;
server = malloc (strlen ("host/") + strlen (p) + 1);
if (!server)
{
free (p); /* This old `server'. */
snprintf (errbuf, errbuflen,
"Cannot allocate memory for server name");
return 1;
}
sprintf (server, "host/%s", p);
}
if (realm)
key = shishi_hostkeys_for_serverrealm (shishi_handle, server, realm);
else
/* Enforce a search with the known default realm. */
key = shishi_hostkeys_for_serverrealm (shishi_handle,
server,
shishi_realm_default
(shishi_handle));
free (server);
}
else
key = shishi_hostkeys_for_localservicerealm (shishi_handle,
"host", realm);
free (realm);
if (key == NULL)
{
snprintf (errbuf, errbuflen, "Could not find key: %s",
shishi_error (shishi_handle));
return 1;
}
rc = shishi_ap (shishi_handle, &auth_handle);
if (rc != SHISHI_OK)
{
snprintf (errbuf, errbuflen,
"Cannot allocate authentication structures: %s",
shishi_strerror (rc));
return 1;
}
rc = shishi_ap_req_der_set (auth_handle, (char *) data, cnt);
if (rc != SHISHI_OK)
{
snprintf (errbuf, errbuflen,
"Cannot parse authentication information: %s",
shishi_strerror (rc));
return 1;
}
rc = shishi_ap_req_process (auth_handle, key);
if (rc != SHISHI_OK)
{
snprintf (errbuf, errbuflen, "Could not process AP-REQ: %s",
shishi_strerror (rc));
return 1;
}
if (shishi_apreq_mutual_required_p (shishi_handle,
shishi_ap_req (auth_handle)))
{
char *der;
size_t derlen;
rc = shishi_ap_rep_der (auth_handle, &der, &derlen);
if (rc != SHISHI_OK)
{
snprintf (errbuf, errbuflen, "Error DER encoding aprep: %s",
shishi_strerror (rc));
return 1;
}
Data (a, KRB_RESPONSE, der, derlen);
free (der);
}
rc = shishi_encticketpart_clientrealm (shishi_handle,
shishi_tkt_encticketpart
(shishi_ap_tkt (auth_handle)),
&cnamerealm, &cnamerealmlen);
if (rc != SHISHI_OK)
{
snprintf (errbuf, errbuflen, "Error getting authenticator name: %s",
shishi_strerror (rc));
return 1;
}
Data (a, KRB_ACCEPT, cnamerealm, cnamerealm ? -1 : 0);
DEBUG (("telnetd: Kerberos5 identifies him as ``%s''\r\n",
cnamerealm ? cnamerealm : ""));
free (cnamerealm);
auth_finished (a, AUTH_USER);
/* Make sure that shishi_handle is still valid,
* it must not be released in auth_finish()!
* The server side will make reference to it
* later on. */
# ifdef ENCRYPTION
if (enckey)
{
shishi_key_done (enckey);
enckey = NULL;
}
rc = shishi_authenticator_get_subkey (shishi_handle,
shishi_ap_authenticator (auth_handle),
&enckey);
if (rc != SHISHI_OK)
{
Shishi_tkt *tkt;
tkt = shishi_ap_tkt (auth_handle);
if (tkt)
{
rc = shishi_encticketpart_get_key (shishi_handle,
shishi_tkt_encticketpart (tkt),
&enckey);
if (rc != SHISHI_OK)
enckey = NULL;
shishi_tkt_done (tkt);
}
}
if (enckey == NULL)
{
snprintf (errbuf, errbuflen,
"telnet: Kerberos V5: could not get encryption key (%s)\r\n",
shishi_strerror (rc));
return 1;
}
shishi_init_key (&skey, 1);
# endif
return 0;
}
void
krb5shishi_is (TN_Authenticator *ap, unsigned char *data, int cnt)
{
int r = 0;
char errbuf[512];
if (cnt-- < 1)
return;
errbuf[0] = 0;
switch (*data++)
{
case KRB_AUTH:
r = krb5shishi_is_auth (ap, data, cnt, errbuf, sizeof errbuf);
break;
default:
DEBUG (("Unknown Kerberos option %d\r\n", data[-1]));
Data (ap, KRB_REJECT, 0, 0);
break;
}
if (r)
{
if (!errbuf[0])
snprintf (errbuf, sizeof errbuf, "kerberos_is: error");
Data (ap, KRB_REJECT, errbuf, -1);
DEBUG (("%s\r\n", errbuf));
syslog (LOG_ERR, "%s", errbuf);
}
}
static char *
req_type_str (int type)
{
switch (type)
{
case KRB_REJECT:
return "REJECT";
case KRB_ACCEPT:
return "ACCEPT";
case KRB_AUTH:
return "AUTH";
case KRB_RESPONSE:
return "RESPONSE";
}
return NULL;
}
# define ADDC(p,l,c) if ((l) > 0) {*(p)++ = (c); --(l);}
void
krb5shishi_printsub (unsigned char *data, int cnt, char *buf, int buflen)
{
char *p;
int i;
buf[buflen - 1] = '\0'; /* make sure its NULL terminated */
buflen -= 1;
p = req_type_str (data[3]);
if (!p)
{
int l = snprintf (buf, buflen, " %d (unknown)", data[3]);
buf += l;
buflen -= l;
}
else
{
while (buflen > 0 && (*buf++ = *p++) != 0)
buflen--;
}
switch (data[3])
{
case KRB_REJECT: /* Rejected (reason might follow) */
case KRB_ACCEPT: /* Accepted (username might follow) */
if (cnt <= 4)
break;
ADDC (buf, buflen, '"');
for (i = 4; i < cnt; i++)
ADDC (buf, buflen, data[i]);
ADDC (buf, buflen, '"');
ADDC (buf, buflen, '\0');
break;
case KRB_AUTH:
case KRB_RESPONSE:
for (i = 4; buflen > 0 && i < cnt; i++)
{
int l = snprintf (buf, buflen, " %d", data[i]);
buf += l;
buflen -= l;
}
}
}
#endif /* SHISHI */