#include <k5-int.h>
#include <kadm5/admin.h>
#include <adm_proto.h>
#include <com_err.h>
#include <stdarg.h>
#include <assert.h>
#include <stdio.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <netinet/in.h>
#include <krb.h>
#include "krb524d.h"
#if defined(NEED_DAEMON_PROTO)
extern int daemon(int, int);
#endif
#define TIMEOUT 60
#define TKT_BUFSIZ 2048
#define MSGSIZE 8192
char *whoami;
int signalled = 0;
static int debug = 0;
void *handle = NULL;
int use_keytab, use_master;
int allow_v4_crossrealm = 0;
char *keytab = NULL;
krb5_keytab kt;
void init_keytab(krb5_context),
init_master(krb5_context, kadm5_config_params *),
cleanup_and_exit(int, krb5_context);
krb5_error_code do_connection(int, krb5_context);
krb5_error_code lookup_service_key(krb5_context, krb5_principal,
krb5_enctype, krb5_kvno,
krb5_keyblock *, krb5_kvno *);
krb5_error_code kdc_get_server_key(krb5_context, krb5_principal,
krb5_keyblock *, krb5_kvno *,
krb5_enctype, krb5_kvno);
static krb5_error_code
handle_classic_v4 (krb5_context context, krb5_ticket *v5tkt,
struct sockaddr_in *saddr,
krb5_data *tktdata, krb5_kvno *v4kvno);
static krb5_error_code
afs_return_v4(krb5_context, const krb5_principal , int *use_v5);
static void usage(context)
krb5_context context;
{
fprintf(stderr, "Usage: %s [-k[eytab]] [-m[aster] [-r realm]] [-nofork] [-p portnum]\n", whoami);
cleanup_and_exit(1, context);
}
static RETSIGTYPE request_exit(signo)
int signo;
{
signalled = 1;
}
int (*encode_v4tkt)(KTEXT, char *, unsigned int *) = 0;
int main(argc, argv)
int argc;
char **argv;
{
struct servent *serv;
struct sockaddr_in saddr;
struct timeval timeout;
int ret, s, nofork;
fd_set rfds;
krb5_context context;
krb5_error_code retval;
kadm5_config_params config_params;
unsigned long port = 0;
whoami = ((whoami = strrchr(argv[0], '/')) ? whoami + 1 : argv[0]);
retval = krb5int_init_context_kdc(&context);
if (retval) {
com_err(whoami, retval, "while initializing krb5");
exit(1);
}
{
krb5int_access k5int;
retval = krb5int_accessor(&k5int, KRB5INT_ACCESS_VERSION);
if (retval != 0) {
com_err(whoami, retval,
"while accessing krb5 library internal support");
exit(1);
}
encode_v4tkt = k5int.krb524_encode_v4tkt;
if (encode_v4tkt == NULL) {
com_err(whoami, 0,
"krb4 support disabled in krb5 support library");
exit(1);
}
}
argv++; argc--;
use_master = use_keytab = nofork = 0;
config_params.mask = 0;
while (argc) {
if (strncmp(*argv, "-X", 2) == 0) {
allow_v4_crossrealm = 1;
}
else if (strncmp(*argv, "-k", 2) == 0)
use_keytab = 1;
else if (strncmp(*argv, "-m", 2) == 0)
use_master = 1;
else if (strcmp(*argv, "-nofork") == 0)
nofork = 1;
else if (strcmp(*argv, "-r") == 0) {
argv++; argc--;
if (argc == 0 || !use_master)
usage(context);
config_params.mask |= KADM5_CONFIG_REALM;
config_params.realm = *argv;
}
else if (strcmp(*argv, "-p") == 0) {
char *endptr = 0;
argv++; argc--;
if (argc == 0)
usage (context);
if (port != 0) {
com_err (whoami, 0,
"port number may only be specified once");
exit (1);
}
port = strtoul (*argv, &endptr, 0);
if (*endptr != '\0' || port > 65535 || port == 0) {
com_err (whoami, 0,
"invalid port number %s, must be 1..65535\n",
*argv);
exit (1);
}
}
else
break;
argv++; argc--;
}
if (argc || use_keytab + use_master > 1 ||
use_keytab + use_master == 0) {
use_keytab = use_master = 0;
usage(context);
}
signal(SIGINT, request_exit);
signal(SIGHUP, SIG_IGN);
signal(SIGTERM, request_exit);
krb5_klog_init(context, "krb524d", whoami, !nofork);
if (use_keytab)
init_keytab(context);
if (use_master)
init_master(context, &config_params);
memset((char *) &saddr, 0, sizeof(struct sockaddr_in));
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
if (port == 0) {
serv = getservbyname(KRB524_SERVICE, "udp");
if (serv == NULL) {
com_err(whoami, 0, "service entry `%s' not found, using %d",
KRB524_SERVICE, KRB524_PORT);
saddr.sin_port = htons(KRB524_PORT);
} else
saddr.sin_port = serv->s_port;
} else
saddr.sin_port = htons(port);
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
com_err(whoami, errno, "creating main socket");
cleanup_and_exit(1, context);
}
if ((ret = bind(s, (struct sockaddr *) &saddr,
sizeof(struct sockaddr_in))) < 0) {
com_err(whoami, errno, "binding main socket");
cleanup_and_exit(1, context);
}
if (!nofork && daemon(0, 0)) {
com_err(whoami, errno, "while detaching from tty");
cleanup_and_exit(1, context);
}
while (1) {
FD_ZERO(&rfds);
FD_SET(s, &rfds);
timeout.tv_sec = TIMEOUT;
timeout.tv_usec = 0;
ret = select(s+1, &rfds, NULL, NULL, &timeout);
if (signalled)
cleanup_and_exit(0, context);
else if (ret == 0) {
if (use_master) {
ret = kadm5_flush(handle);
if (ret && ret != KRB5_KDB_DBNOTINITED) {
com_err(whoami, ret, "closing kerberos database");
cleanup_and_exit(1, context);
}
}
} else if (ret < 0 && errno != EINTR) {
com_err(whoami, errno, "in select");
cleanup_and_exit(1, context);
} else if (FD_ISSET(s, &rfds)) {
if (debug)
printf("received packet\n");
if ((ret = do_connection(s, context))) {
com_err(whoami, ret, "handling packet");
}
} else
com_err(whoami, 0, "impossible situation occurred!");
}
cleanup_and_exit(0, context);
}
void cleanup_and_exit(ret, context)
int ret;
krb5_context context;
{
if (use_master && handle) {
(void) kadm5_destroy(handle);
}
if (use_keytab && kt) krb5_kt_close(context, kt);
krb5_klog_close(context);
krb5_free_context(context);
exit(ret);
}
void init_keytab(context)
krb5_context context;
{
int ret;
use_keytab = 0;
if (keytab == NULL) {
if ((ret = krb5_kt_default(context, &kt))) {
com_err(whoami, ret, "while opening default keytab");
cleanup_and_exit(1, context);
}
} else {
if ((ret = krb5_kt_resolve(context, keytab, &kt))) {
com_err(whoami, ret, "while resolving keytab %s",
keytab);
cleanup_and_exit(1, context);
}
}
use_keytab = 1;
}
void init_master(context, params)
krb5_context context;
kadm5_config_params *params;
{
int ret;
use_master = 0;
if ((ret = kadm5_init(whoami, NULL, KADM5_ADMIN_SERVICE, params,
KADM5_STRUCT_VERSION, KADM5_API_VERSION_2, NULL,
&handle))) {
com_err(whoami, ret, "initializing kadm5 library");
cleanup_and_exit(1, context);
}
use_master = 1;
}
krb5_error_code do_connection(s, context)
int s;
krb5_context context;
{
struct sockaddr saddr;
krb5_ticket *v5tkt = 0;
krb5_data msgdata, tktdata;
char msgbuf[MSGSIZE], tktbuf[TKT_BUFSIZ], *p;
int ret;
socklen_t saddrlen;
krb5_int32 n;
krb5_kvno v4kvno;
msgdata.data = msgbuf;
msgdata.length = MSGSIZE;
tktdata.data = tktbuf;
tktdata.length = TKT_BUFSIZ;
saddrlen = sizeof(struct sockaddr);
ret = recvfrom(s, msgdata.data, (int) msgdata.length, 0, &saddr, &saddrlen);
if (ret < 0) {
return errno;
}
if (debug)
printf("message received\n");
if ((ret = decode_krb5_ticket(&msgdata, &v5tkt))) {
switch (ret) {
case KRB5KDC_ERR_BAD_PVNO:
case ASN1_MISPLACED_FIELD:
case ASN1_MISSING_FIELD:
case ASN1_BAD_ID:
case KRB5_BADMSGTYPE:
return ret;
break;
default:
if (msgdata.length == sizeof(krb5_int32))
return KRB5_BADMSGTYPE;
else
goto error;
}
}
if (debug)
printf("V5 ticket decoded\n");
if (krb5_princ_size(context, v5tkt->server) >= 1
&& krb5_princ_component(context, v5tkt->server, 0)->length == 3
&& strncmp(krb5_princ_component(context, v5tkt->server, 0)->data,
"afs", 3) == 0) {
krb5_data *enc_part;
int use_v5;
if ((ret = afs_return_v4(context, v5tkt->server,
&use_v5)) != 0)
goto error;
if ((ret = encode_krb5_enc_data(&v5tkt->enc_part, &enc_part)) != 0)
goto error;
if (!(use_v5)|| enc_part->length >= 344) {
krb5_free_data(context, enc_part);
if ((ret = handle_classic_v4(context, v5tkt,
(struct sockaddr_in *) &saddr, &tktdata,
&v4kvno)) != 0)
goto error;
} else {
KTEXT_ST fake_v4tkt;
memset(&fake_v4tkt, 0x11, sizeof(fake_v4tkt));
fake_v4tkt.mbz = 0;
fake_v4tkt.length = enc_part->length;
memcpy(fake_v4tkt.dat, enc_part->data, enc_part->length);
v4kvno = (0x100-0x2b);
krb5_free_data(context, enc_part);
ret = encode_v4tkt(&fake_v4tkt, tktdata.data, &tktdata.length);
}
} else {
if ((ret = handle_classic_v4(context, v5tkt,
(struct sockaddr_in *) &saddr, &tktdata,
&v4kvno)) != 0)
goto error;
}
error:
p = msgdata.data;
msgdata.length = 0;
n = htonl(ret);
memcpy(p, (char *) &n, sizeof(krb5_int32));
p += sizeof(krb5_int32);
msgdata.length += sizeof(krb5_int32);
if (ret)
goto write_msg;
n = htonl(v4kvno);
memcpy(p, (char *) &n, sizeof(krb5_int32));
p += sizeof(krb5_int32);
msgdata.length += sizeof(krb5_int32);
memcpy(p, tktdata.data, tktdata.length);
p += tktdata.length;
msgdata.length += tktdata.length;
write_msg:
if (ret)
(void) sendto(s, msgdata.data, (int) msgdata.length, 0, &saddr, saddrlen);
else
if (sendto(s, msgdata.data, msgdata.length, 0, &saddr, saddrlen)<0)
ret = errno;
if (debug)
printf("reply written\n");
if (v5tkt)
krb5_free_ticket(context, v5tkt);
return ret;
}
krb5_error_code lookup_service_key(context, p, ktype, kvno, key, kvnop)
krb5_context context;
krb5_principal p;
krb5_enctype ktype;
krb5_kvno kvno;
krb5_keyblock *key;
krb5_kvno *kvnop;
{
int ret;
krb5_keytab_entry entry;
if (use_keytab) {
if ((ret = krb5_kt_get_entry(context, kt, p, kvno, ktype, &entry)))
return ret;
*key = entry.key;
key->contents = malloc(key->length);
if (key->contents)
memcpy(key->contents, entry.key.contents, key->length);
else if (key->length) {
ret = errno;
memset (key, 0, sizeof (*key));
return ret;
}
krb5_kt_free_entry(context, &entry);
return 0;
} else if (use_master) {
return kdc_get_server_key(context, p, key, kvnop, ktype, kvno);
}
return 0;
}
krb5_error_code kdc_get_server_key(context, service, key, kvnop, ktype, kvno)
krb5_context context;
krb5_principal service;
krb5_keyblock *key;
krb5_kvno *kvnop;
krb5_enctype ktype;
krb5_kvno kvno;
{
krb5_error_code ret;
kadm5_principal_ent_rec server;
if ((ret = kadm5_get_principal(handle, service, &server,
KADM5_KEY_DATA|KADM5_ATTRIBUTES)))
return ret;
if (server.attributes & KRB5_KDB_DISALLOW_ALL_TIX
|| server.attributes & KRB5_KDB_DISALLOW_SVR) {
kadm5_free_principal_ent(handle, &server);
return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
}
if ((ret = kadm5_decrypt_key(handle,
&server,
ktype,
(ktype == ENCTYPE_DES_CBC_CRC) ?
KRB5_KDB_SALTTYPE_V4 : -1,
kvno,
key, NULL, kvnop)) &&
(ret = kadm5_decrypt_key(handle,
&server,
ktype,
-1,
kvno,
key, NULL, kvnop))) {
kadm5_free_principal_ent(handle, &server);
return (KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN);
}
kadm5_free_principal_ent(handle, &server);
return ret;
}
static krb5_error_code
handle_classic_v4 (krb5_context context, krb5_ticket *v5tkt,
struct sockaddr_in *saddr,
krb5_data *tktdata, krb5_kvno *v4kvno)
{
krb5_error_code ret;
krb5_keyblock v5_service_key, v4_service_key;
KTEXT_ST v4tkt;
v5_service_key.contents = NULL;
v4_service_key.contents = NULL;
if ((ret = lookup_service_key(context, v5tkt->server,
v5tkt->enc_part.enctype,
v5tkt->enc_part.kvno,
&v5_service_key, NULL)))
goto error;
if ((ret = lookup_service_key(context, v5tkt->server,
ENCTYPE_DES_CBC_CRC,
0,
&v4_service_key, v4kvno)))
goto error;
if (debug)
printf("service key retrieved\n");
if ((ret = krb5_decrypt_tkt_part(context, &v5_service_key, v5tkt))) {
goto error;
}
if (!(allow_v4_crossrealm || krb5_realm_compare(context, v5tkt->server,
v5tkt->enc_part2->client))) {
ret = KRB5KDC_ERR_POLICY;
goto error;
}
krb5_free_enc_tkt_part(context, v5tkt->enc_part2);
v5tkt->enc_part2= NULL;
memset(&v4tkt, 0x33, sizeof(v4tkt));
ret = krb524_convert_tkt_skey(context, v5tkt, &v4tkt, &v5_service_key,
&v4_service_key,
(struct sockaddr_in *)saddr);
if (ret)
goto error;
if (debug)
printf("credentials converted\n");
ret = encode_v4tkt(&v4tkt, tktdata->data, &tktdata->length);
if (ret)
goto error;
if (debug)
printf("v4 credentials encoded\n");
error:
if (v5tkt->enc_part2) {
krb5_free_enc_tkt_part(context, v5tkt->enc_part2);
v5tkt->enc_part2 = NULL;
}
if (v5_service_key.contents)
krb5_free_keyblock_contents(context, &v5_service_key);
if (v4_service_key.contents)
krb5_free_keyblock_contents(context, &v4_service_key);
return ret;
}
static krb5_error_code
afs_return_v4 (krb5_context context, const krb5_principal princ,
int *use_v5)
{
krb5_error_code ret;
char *unparsed_name;
char *cp;
krb5_data realm;
assert(use_v5 != NULL);
ret = krb5_unparse_name(context, princ, &unparsed_name);
if (ret != 0)
return ret;
for (cp = unparsed_name; *cp != '\0'; cp++) {
if (*cp == '\\') {
cp++;
continue;
}
if (*cp == '@') {
*cp = '\0';
realm.data = cp+1;
realm.length = strlen((char *) realm.data);
break;
}
}
krb5_appdefault_boolean(context, "afs_krb5",
&realm, unparsed_name, 1,
use_v5);
krb5_free_unparsed_name(context, unparsed_name);
return ret;
}