1089 lines
26 KiB
C
1089 lines
26 KiB
C
/*
|
|
* Copyright 1985, 1986, 1987, 1988 by the Massachusetts Institute
|
|
* of Technology.
|
|
*
|
|
* For copying and distribution information, please see the file
|
|
* <mit-copyright.h>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "protos.h"
|
|
|
|
RCSID("$Id: kerberos.c,v 1.1.1.2 2000/12/29 01:44:08 assar Exp $");
|
|
|
|
/*
|
|
* If support for really large numbers of network interfaces is
|
|
* desired, define FD_SETSIZE to some suitable value.
|
|
*/
|
|
#define FD_SETSIZE (4*1024)
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
|
|
#ifdef TIME_WITH_SYS_TIME
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#elif defined(HAVE_SYS_TIME_H)
|
|
#include <sys/time.h>
|
|
#else
|
|
#include <time.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SELECT_H
|
|
#include <sys/select.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_SOCKET_H
|
|
#include <sys/socket.h>
|
|
#endif
|
|
#ifdef HAVE_NETINET_IN_H
|
|
#include <netinet/in.h>
|
|
#endif
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#if defined(HAVE_SYS_IOCTL_H) && SunOS != 40
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_FILIO_H
|
|
#include <sys/filio.h>
|
|
#endif /* HAVE_SYS_FILIO_H */
|
|
|
|
#ifdef HAVE_NETDB_H
|
|
#include <netdb.h>
|
|
#endif
|
|
#include <err.h>
|
|
|
|
#ifdef SOCKS
|
|
#include <socks.h>
|
|
#endif
|
|
|
|
#include <roken.h>
|
|
#include <base64.h>
|
|
|
|
#include <des.h>
|
|
#include <krb.h>
|
|
#include <krb_db.h>
|
|
#include <prot.h>
|
|
#include <klog.h>
|
|
|
|
#include <krb_log.h>
|
|
|
|
#include <kdc.h>
|
|
|
|
static des_key_schedule master_key_schedule;
|
|
static des_cblock master_key;
|
|
|
|
static struct timeval kerb_time;
|
|
static u_char master_key_version;
|
|
static char *lt;
|
|
static int more;
|
|
|
|
static int mflag; /* Are we invoked manually? */
|
|
static char *log_file = KRBLOG; /* name of alt. log file */
|
|
static int nflag; /* don't check max age */
|
|
static int rflag; /* alternate realm specified */
|
|
|
|
/* fields within the received request packet */
|
|
static char *req_name_ptr;
|
|
static char *req_inst_ptr;
|
|
static char *req_realm_ptr;
|
|
static u_int32_t req_time_ws;
|
|
|
|
static char local_realm[REALM_SZ];
|
|
|
|
/* options */
|
|
static int max_age = -1;
|
|
static int pause_int = -1;
|
|
|
|
/*
|
|
* Print usage message and exit.
|
|
*/
|
|
static void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: %s [-s] [-m] [-n] [-p pause_seconds]"
|
|
" [-a max_age] [-l log_file] [-i address_to_listen_on]"
|
|
" [-r realm] [database_pathname]\n",
|
|
__progname);
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* kerb_err_reply creates an error reply packet and sends it to the
|
|
* client.
|
|
*/
|
|
|
|
static void
|
|
kerb_err_reply(int f, struct sockaddr_in *client, int err, char *string)
|
|
{
|
|
static KTEXT_ST e_pkt_st;
|
|
KTEXT e_pkt = &e_pkt_st;
|
|
static char e_msg[128];
|
|
|
|
snprintf (e_msg, sizeof(e_msg),
|
|
"\nKerberos error -- %s", string);
|
|
cr_err_reply(e_pkt, req_name_ptr, req_inst_ptr, req_realm_ptr,
|
|
req_time_ws, err, e_msg);
|
|
sendto(f, (char*)e_pkt->dat, e_pkt->length, 0, (struct sockaddr *)client,
|
|
sizeof(*client));
|
|
}
|
|
|
|
static void
|
|
hang(void)
|
|
{
|
|
if (pause_int == -1) {
|
|
klog(L_KRB_PERR, "Kerberos will pause so as not to loop init");
|
|
for (;;)
|
|
pause();
|
|
} else {
|
|
char buf[256];
|
|
snprintf(buf, sizeof(buf),
|
|
"Kerberos will wait %d seconds before dying so as not to loop init",
|
|
pause_int);
|
|
klog(L_KRB_PERR, buf);
|
|
sleep(pause_int);
|
|
klog(L_KRB_PERR, "Do svedania....\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static int
|
|
check_princ(char *p_name, char *instance, unsigned int lifetime, Principal *p)
|
|
{
|
|
static int n;
|
|
static int more;
|
|
|
|
n = kerb_get_principal(p_name, instance, p, 1, &more);
|
|
|
|
if (n < 0) {
|
|
lt = klog(L_KRB_PERR, "Database unavailable!");
|
|
hang();
|
|
}
|
|
|
|
/*
|
|
* if more than one p_name, pick one, randomly create a session key,
|
|
* compute maximum lifetime, lookup authorizations if applicable,
|
|
* and stuff into cipher.
|
|
*/
|
|
if (n == 0) {
|
|
/* service unknown, log error, skip to next request */
|
|
lt = klog(L_ERR_UNK, "UNKNOWN %s.%s", p_name, instance);
|
|
return KERB_ERR_PRINCIPAL_UNKNOWN;
|
|
}
|
|
if (more) {
|
|
/* not unique, log error */
|
|
lt = klog(L_ERR_NUN, "Principal not unique %s.%s", p_name, instance);
|
|
return KERB_ERR_PRINCIPAL_NOT_UNIQUE;
|
|
}
|
|
/* If the user's key is null, we want to return an error */
|
|
if ((p->key_low == 0) && (p->key_high == 0)) {
|
|
/* User has a null key */
|
|
lt = klog(L_ERR_NKY, "Null key %s.%s", p_name, instance);
|
|
return KERB_ERR_NULL_KEY;
|
|
}
|
|
if (master_key_version != p->kdc_key_ver) {
|
|
/* log error reply */
|
|
lt = klog(L_ERR_MKV,
|
|
"Incorrect master key version for %s.%s: %d (should be %d)",
|
|
p->name, p->instance, p->kdc_key_ver, master_key_version);
|
|
return KERB_ERR_NAME_MAST_KEY_VER;
|
|
}
|
|
/* make sure the service hasn't expired */
|
|
if ((u_int32_t) p->exp_date < (u_int32_t) kerb_time.tv_sec) {
|
|
/* service did expire, log it */
|
|
time_t t = p->exp_date;
|
|
lt = klog(L_ERR_SEXP,
|
|
"Principal %s.%s expired at %s", p->name, p->instance,
|
|
krb_stime(&t));
|
|
return KERB_ERR_NAME_EXP;
|
|
}
|
|
/* ok is zero */
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
unseal(des_cblock *key)
|
|
{
|
|
kdb_encrypt_key(key, key, &master_key, master_key_schedule, DES_DECRYPT);
|
|
}
|
|
|
|
|
|
/* Set the key for krb_rd_req so we can check tgt */
|
|
static int
|
|
set_tgtkey(char *r)
|
|
/* Realm for desired key */
|
|
{
|
|
int n;
|
|
static char lastrealm[REALM_SZ];
|
|
Principal p_st;
|
|
Principal *p = &p_st;
|
|
des_cblock key;
|
|
|
|
if (!strcmp(lastrealm, r))
|
|
return (KSUCCESS);
|
|
|
|
klog(L_ALL_REQ, "Getting key for %s", r);
|
|
|
|
n = kerb_get_principal(KRB_TICKET_GRANTING_TICKET, r, p, 1, &more);
|
|
if (n == 0)
|
|
return (KFAILURE);
|
|
|
|
/* unseal tgt key from master key */
|
|
copy_to_key(&p->key_low, &p->key_high, key);
|
|
unseal(&key);
|
|
krb_set_key(key, 0);
|
|
strlcpy (lastrealm, r, REALM_SZ);
|
|
return (KSUCCESS);
|
|
}
|
|
|
|
|
|
static int
|
|
kerberos(unsigned char *buf, int len,
|
|
char *proto, struct sockaddr_in *client,
|
|
struct sockaddr_in *server,
|
|
KTEXT rpkt)
|
|
{
|
|
int pvno;
|
|
int msg_type;
|
|
int lsb;
|
|
int life;
|
|
int flags = 0;
|
|
char name[ANAME_SZ], inst[INST_SZ], realm[REALM_SZ];
|
|
char service[SNAME_SZ], sinst[INST_SZ];
|
|
u_int32_t req_time;
|
|
static KTEXT_ST ticket, cipher, adat;
|
|
KTEXT tk = &ticket, ciph = &cipher, auth = &adat;
|
|
AUTH_DAT ad;
|
|
des_cblock session, key;
|
|
int err;
|
|
Principal a_name, s_name;
|
|
|
|
char *msg;
|
|
|
|
|
|
unsigned char *p = buf;
|
|
if(len < 2){
|
|
strlcpy((char*)rpkt->dat,
|
|
"Packet too short",
|
|
sizeof(rpkt->dat));
|
|
return KFAILURE;
|
|
}
|
|
|
|
gettimeofday(&kerb_time, NULL);
|
|
|
|
pvno = *p++;
|
|
if(pvno != KRB_PROT_VERSION){
|
|
msg = klog(L_KRB_PERR, "KRB protocol version mismatch (%d)", pvno);
|
|
strlcpy((char*)rpkt->dat,
|
|
msg,
|
|
sizeof(rpkt->dat));
|
|
return KERB_ERR_PKT_VER;
|
|
}
|
|
msg_type = *p++;
|
|
lsb = msg_type & 1;
|
|
msg_type &= ~1;
|
|
switch(msg_type){
|
|
case AUTH_MSG_KDC_REQUEST:
|
|
/* XXX range check */
|
|
p += krb_get_nir(p, name, sizeof(name),
|
|
inst, sizeof(inst),
|
|
realm, sizeof(realm));
|
|
p += krb_get_int(p, &req_time, 4, lsb);
|
|
life = *p++;
|
|
p += krb_get_nir(p, service, sizeof(service),
|
|
sinst, sizeof(sinst), NULL, 0);
|
|
klog(L_INI_REQ,
|
|
"AS REQ %s.%s@%s for %s.%s from %s (%s/%u)",
|
|
name, inst, realm, service, sinst,
|
|
inet_ntoa(client->sin_addr),
|
|
proto, ntohs(server->sin_port));
|
|
if((err = check_princ(name, inst, 0, &a_name))){
|
|
strlcpy((char*)rpkt->dat,
|
|
krb_get_err_text(err),
|
|
sizeof(rpkt->dat));
|
|
return err;
|
|
}
|
|
tk->length = 0;
|
|
if((err = check_princ(service, sinst, 0, &s_name))){
|
|
strlcpy((char*)rpkt->dat,
|
|
krb_get_err_text(err),
|
|
sizeof(rpkt->dat));
|
|
return err;
|
|
}
|
|
life = min(life, s_name.max_life);
|
|
life = min(life, a_name.max_life);
|
|
|
|
des_new_random_key(&session);
|
|
copy_to_key(&s_name.key_low, &s_name.key_high, key);
|
|
unseal(&key);
|
|
krb_create_ticket(tk, flags, a_name.name, a_name.instance,
|
|
local_realm, client->sin_addr.s_addr,
|
|
session,
|
|
life, kerb_time.tv_sec,
|
|
s_name.name, s_name.instance, &key);
|
|
copy_to_key(&a_name.key_low, &a_name.key_high, key);
|
|
unseal(&key);
|
|
create_ciph(ciph, session, s_name.name, s_name.instance,
|
|
local_realm, life, s_name.key_version, tk,
|
|
kerb_time.tv_sec, &key);
|
|
memset(&session, 0, sizeof(session));
|
|
memset(&key, 0, sizeof(key));
|
|
{
|
|
KTEXT r;
|
|
r = create_auth_reply(name, inst, realm, req_time, 0,
|
|
a_name.exp_date, a_name.key_version, ciph);
|
|
memcpy(rpkt, r, sizeof(*rpkt));
|
|
}
|
|
return 0;
|
|
case AUTH_MSG_APPL_REQUEST:
|
|
strlcpy(realm, (char*)buf + 3, REALM_SZ);
|
|
if((err = set_tgtkey(realm))){
|
|
msg = klog(L_ERR_UNK,
|
|
"Unknown realm %s from %s (%s/%u)",
|
|
realm, inet_ntoa(client->sin_addr),
|
|
proto, ntohs(server->sin_port));
|
|
strlcpy((char*)rpkt->dat,
|
|
msg,
|
|
sizeof(rpkt->dat));
|
|
return err;
|
|
}
|
|
p = buf + strlen(realm) + 4;
|
|
p = p + p[0] + p[1] + 2;
|
|
auth->length = p - buf;
|
|
memcpy(auth->dat, buf, auth->length);
|
|
err = krb_rd_req(auth, KRB_TICKET_GRANTING_TICKET,
|
|
realm, client->sin_addr.s_addr, &ad, 0);
|
|
if(err){
|
|
msg = klog(L_ERR_UNK,
|
|
"krb_rd_req from %s (%s/%u): %s",
|
|
inet_ntoa(client->sin_addr),
|
|
proto,
|
|
ntohs(server->sin_port),
|
|
krb_get_err_text(err));
|
|
strlcpy((char*)rpkt->dat,
|
|
msg,
|
|
sizeof(rpkt->dat));
|
|
return err;
|
|
}
|
|
p += krb_get_int(p, &req_time, 4, lsb);
|
|
life = *p++;
|
|
p += krb_get_nir(p, service, sizeof(service),
|
|
sinst, sizeof(sinst), NULL, 0);
|
|
klog(L_APPL_REQ,
|
|
"APPL REQ %s.%s@%s for %s.%s from %s (%s/%u)",
|
|
ad.pname, ad.pinst, ad.prealm,
|
|
service, sinst,
|
|
inet_ntoa(client->sin_addr),
|
|
proto,
|
|
ntohs(server->sin_port));
|
|
|
|
if(strcmp(ad.prealm, realm)){
|
|
msg = klog(L_ERR_UNK, "Can't hop realms: %s -> %s",
|
|
realm, ad.prealm);
|
|
strlcpy((char*)rpkt->dat,
|
|
msg,
|
|
sizeof(rpkt->dat));
|
|
return KERB_ERR_PRINCIPAL_UNKNOWN;
|
|
}
|
|
|
|
if(!strcmp(service, "changepw")){
|
|
strlcpy((char*)rpkt->dat,
|
|
"Can't authorize password changed based on TGT",
|
|
sizeof(rpkt->dat));
|
|
return KERB_ERR_PRINCIPAL_UNKNOWN;
|
|
}
|
|
|
|
err = check_princ(service, sinst, life, &s_name);
|
|
if(err){
|
|
strlcpy((char*)rpkt->dat,
|
|
krb_get_err_text(err),
|
|
sizeof(rpkt->dat));
|
|
return err;
|
|
}
|
|
life = min(life,
|
|
krb_time_to_life(kerb_time.tv_sec,
|
|
krb_life_to_time(ad.time_sec,
|
|
ad.life)));
|
|
life = min(life, s_name.max_life);
|
|
copy_to_key(&s_name.key_low, &s_name.key_high, key);
|
|
unseal(&key);
|
|
des_new_random_key(&session);
|
|
krb_create_ticket(tk, flags, ad.pname, ad.pinst, ad.prealm,
|
|
client->sin_addr.s_addr, &session,
|
|
life, kerb_time.tv_sec,
|
|
s_name.name, s_name.instance,
|
|
&key);
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
|
|
create_ciph(ciph, session, service, sinst, local_realm,
|
|
life, s_name.key_version, tk,
|
|
kerb_time.tv_sec, &ad.session);
|
|
|
|
memset(&session, 0, sizeof(session));
|
|
memset(ad.session, 0, sizeof(ad.session));
|
|
{
|
|
KTEXT r;
|
|
r =create_auth_reply(ad.pname, ad.pinst, ad.prealm,
|
|
req_time, 0, 0, 0, ciph);
|
|
memcpy(rpkt, r, sizeof(*rpkt));
|
|
}
|
|
memset(&s_name, 0, sizeof(s_name));
|
|
return 0;
|
|
|
|
case AUTH_MSG_ERR_REPLY:
|
|
return -1;
|
|
default:
|
|
msg = klog(L_KRB_PERR,
|
|
"Unknown message type: %d from %s (%s/%u)",
|
|
msg_type,
|
|
inet_ntoa(client->sin_addr),
|
|
proto,
|
|
ntohs(server->sin_port));
|
|
strlcpy((char*)rpkt->dat,
|
|
msg,
|
|
sizeof(rpkt->dat));
|
|
return KFAILURE;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
kerberos_wrap(int s, KTEXT data, char *proto, struct sockaddr_in *client,
|
|
struct sockaddr_in *server)
|
|
{
|
|
KTEXT_ST pkt;
|
|
int http_flag = strcmp(proto, "http") == 0;
|
|
int err = kerberos(data->dat, data->length, proto, client, server, &pkt);
|
|
if(err == -1)
|
|
return;
|
|
if(http_flag){
|
|
const char *msg =
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Server: KTH-KRB/1\r\n"
|
|
"Content-type: application/octet-stream\r\n"
|
|
"Content-transfer-encoding: binary\r\n\r\n";
|
|
sendto(s, msg, strlen(msg), 0, (struct sockaddr *)client,
|
|
sizeof(*client));
|
|
}
|
|
if(err){
|
|
kerb_err_reply(s, client, err, (char*)pkt.dat);
|
|
return;
|
|
}
|
|
sendto(s, pkt.dat, pkt.length, 0, (struct sockaddr *)client,
|
|
sizeof(*client));
|
|
}
|
|
|
|
|
|
/*
|
|
* setup_disc
|
|
*
|
|
* disconnect all descriptors, remove ourself from the process
|
|
* group that spawned us.
|
|
*/
|
|
|
|
static void
|
|
setup_disc(void)
|
|
{
|
|
int s;
|
|
|
|
for (s = 0; s < 3; s++) {
|
|
close(s);
|
|
}
|
|
|
|
open("/dev/null", 0);
|
|
dup2(0, 1);
|
|
dup2(0, 2);
|
|
|
|
setsid();
|
|
|
|
chdir("/tmp");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Make sure that database isn't stale.
|
|
*
|
|
* Exit if it is; we don't want to tell lies.
|
|
*/
|
|
|
|
static void
|
|
check_db_age(void)
|
|
{
|
|
long age;
|
|
|
|
if (max_age != -1) {
|
|
/* Requires existance of kerb_get_db_age() */
|
|
gettimeofday(&kerb_time, 0);
|
|
age = kerb_get_db_age();
|
|
if (age == 0) {
|
|
klog(L_KRB_PERR, "Database currently being updated!");
|
|
hang();
|
|
}
|
|
if ((age + max_age) < kerb_time.tv_sec) {
|
|
klog(L_KRB_PERR, "Database out of date!");
|
|
hang();
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
}
|
|
|
|
struct descr{
|
|
int s;
|
|
KTEXT_ST buf;
|
|
int type;
|
|
int timeout;
|
|
struct sockaddr_in addr;
|
|
};
|
|
|
|
static void
|
|
mksocket(struct descr *d, struct in_addr addr, int type,
|
|
const char *service, int port)
|
|
{
|
|
int on = 1;
|
|
int sock;
|
|
|
|
memset(d, 0, sizeof(struct descr));
|
|
if ((sock = socket(AF_INET, type, 0)) < 0)
|
|
err (1, "socket");
|
|
if (sock >= FD_SETSIZE) {
|
|
errno = EMFILE;
|
|
errx(1, "Aborting: too many descriptors");
|
|
}
|
|
#if defined(SO_REUSEADDR) && defined(HAVE_SETSOCKOPT)
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on,
|
|
sizeof(on)) < 0)
|
|
warn ("setsockopt (SO_REUSEADDR)");
|
|
#endif
|
|
memset(&d->addr, 0, sizeof(d->addr));
|
|
d->addr.sin_family = AF_INET;
|
|
d->addr.sin_port = port;
|
|
d->addr.sin_addr = addr;
|
|
if (bind(sock, (struct sockaddr *)&d->addr, sizeof(d->addr)) < 0)
|
|
err (1, "bind '%s/%s' (%d)",
|
|
service, (type == SOCK_DGRAM) ? "udp" : "tcp",
|
|
ntohs(d->addr.sin_port));
|
|
|
|
if(type == SOCK_STREAM)
|
|
listen(sock, SOMAXCONN);
|
|
d->s = sock;
|
|
d->type = type;
|
|
}
|
|
|
|
|
|
static void loop(struct descr *fds, int maxfd);
|
|
|
|
struct port_spec {
|
|
int port;
|
|
int type;
|
|
};
|
|
|
|
static int
|
|
add_port(struct port_spec **ports, int *num_ports, int port, int type)
|
|
{
|
|
struct port_spec *tmp;
|
|
tmp = realloc(*ports, (*num_ports + 1) * sizeof(*tmp));
|
|
if(tmp == NULL)
|
|
return ENOMEM;
|
|
*ports = tmp;
|
|
tmp[*num_ports].port = port;
|
|
tmp[*num_ports].type = type;
|
|
(*num_ports)++;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
make_sockets(const char *port_spec, struct in_addr *i_addr,
|
|
struct descr **fds, int *nfds)
|
|
{
|
|
int tp;
|
|
struct in_addr *a;
|
|
char *p, *q, *pos = NULL;
|
|
struct servent *sp;
|
|
struct port_spec *ports = NULL;
|
|
int num_ports = 0;
|
|
int i, j;
|
|
char *port_spec_copy = strdup (port_spec);
|
|
|
|
if (port_spec_copy == NULL)
|
|
err (1, "strdup");
|
|
|
|
for(p = strtok_r(port_spec_copy, ", \t", &pos);
|
|
p;
|
|
p = strtok_r(NULL, ", \t", &pos)){
|
|
if(strcmp(p, "+") == 0){
|
|
add_port(&ports, &num_ports, 88, SOCK_DGRAM);
|
|
add_port(&ports, &num_ports, 88, SOCK_STREAM);
|
|
add_port(&ports, &num_ports, 750, SOCK_DGRAM);
|
|
add_port(&ports, &num_ports, 750, SOCK_STREAM);
|
|
}else{
|
|
q = strchr(p, '/');
|
|
if(q){
|
|
*q = 0;
|
|
q++;
|
|
}
|
|
sp = getservbyname(p, q);
|
|
if(sp)
|
|
tp = ntohs(sp->s_port);
|
|
else if(sscanf(p, "%d", &tp) != 1) {
|
|
warnx("Unknown port: %s%s%s", p, q ? "/" : "", q ? q : "");
|
|
continue;
|
|
}
|
|
if(q){
|
|
if(strcasecmp(q, "tcp") == 0)
|
|
add_port(&ports, &num_ports, tp, SOCK_STREAM);
|
|
else if(strcasecmp(q, "udp") == 0)
|
|
add_port(&ports, &num_ports, tp, SOCK_DGRAM);
|
|
else
|
|
warnx("Unknown protocol type: %s", q);
|
|
}else{
|
|
add_port(&ports, &num_ports, tp, SOCK_DGRAM);
|
|
add_port(&ports, &num_ports, tp, SOCK_STREAM);
|
|
}
|
|
}
|
|
}
|
|
free (port_spec_copy);
|
|
|
|
if(num_ports == 0)
|
|
errx(1, "No valid ports specified!");
|
|
|
|
if (i_addr) {
|
|
*nfds = 1;
|
|
a = malloc(sizeof(*a) * *nfds);
|
|
if (a == NULL)
|
|
errx (1, "Failed to allocate %lu bytes",
|
|
(unsigned long)(sizeof(*a) * *nfds));
|
|
memcpy(a, i_addr, sizeof(struct in_addr));
|
|
} else
|
|
*nfds = k_get_all_addrs (&a);
|
|
if (*nfds < 0) {
|
|
struct in_addr any;
|
|
|
|
any.s_addr = INADDR_ANY;
|
|
|
|
warnx ("Could not get local addresses, binding to INADDR_ANY");
|
|
*nfds = 1;
|
|
a = malloc(sizeof(*a) * *nfds);
|
|
if (a == NULL)
|
|
errx (1, "Failed to allocate %lu bytes",
|
|
(unsigned long)(sizeof(*a) * *nfds));
|
|
memcpy(a, &any, sizeof(struct in_addr));
|
|
}
|
|
*fds = malloc(*nfds * num_ports * sizeof(**fds));
|
|
if (*fds == NULL)
|
|
errx (1, "Failed to allocate %lu bytes",
|
|
(unsigned long)(*nfds * num_ports * sizeof(**fds)));
|
|
for (i = 0; i < *nfds; i++) {
|
|
for(j = 0; j < num_ports; j++) {
|
|
mksocket(*fds + num_ports * i + j, a[i],
|
|
ports[j].type, "", htons(ports[j].port));
|
|
}
|
|
}
|
|
*nfds *= num_ports;
|
|
free(ports);
|
|
free (a);
|
|
}
|
|
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int child;
|
|
int c;
|
|
struct descr *fds;
|
|
int nfds;
|
|
int n;
|
|
int kerror;
|
|
int i_flag = 0;
|
|
struct in_addr i_addr;
|
|
char *port_spec = "+";
|
|
|
|
umask(077); /* Create protected files */
|
|
|
|
set_progname (argv[0]);
|
|
|
|
while ((c = getopt(argc, argv, "snmp:P:a:l:r:i:")) != -1) {
|
|
switch(c) {
|
|
case 's':
|
|
/*
|
|
* Set parameters to slave server defaults.
|
|
*/
|
|
if (max_age == -1 && !nflag)
|
|
max_age = THREE_DAYS; /* Survive weekend */
|
|
if (pause_int == -1)
|
|
pause_int = FIVE_MINUTES; /* 5 minutes */
|
|
break;
|
|
case 'n':
|
|
max_age = -1; /* don't check max age. */
|
|
nflag++;
|
|
break;
|
|
case 'm':
|
|
mflag++; /* running manually; prompt for master key */
|
|
break;
|
|
case 'p': {
|
|
/* Set pause interval. */
|
|
char *tmp;
|
|
|
|
pause_int = strtol (optarg, &tmp, 0);
|
|
if (pause_int == 0 && tmp == optarg) {
|
|
fprintf(stderr, "pause_int `%s' not a number\n", optarg);
|
|
usage ();
|
|
}
|
|
|
|
if ((pause_int < 5) || (pause_int > ONE_HOUR)) {
|
|
fprintf(stderr, "pause_int must be between 5 and 3600 seconds.\n");
|
|
usage();
|
|
}
|
|
break;
|
|
}
|
|
case 'P':
|
|
port_spec = optarg;
|
|
break;
|
|
case 'a': {
|
|
/* Set max age. */
|
|
char *tmp;
|
|
|
|
max_age = strtol (optarg, &tmp, 0);
|
|
if (max_age == 0 && tmp == optarg) {
|
|
fprintf (stderr, "max_age `%s' not a number\n", optarg);
|
|
usage ();
|
|
}
|
|
if ((max_age < ONE_HOUR) || (max_age > THREE_DAYS)) {
|
|
fprintf(stderr, "max_age must be between one hour and "
|
|
"three days, in seconds\n");
|
|
usage();
|
|
}
|
|
break;
|
|
}
|
|
case 'l':
|
|
/* Set alternate log file */
|
|
log_file = optarg;
|
|
break;
|
|
case 'r':
|
|
/* Set realm name */
|
|
rflag++;
|
|
strlcpy(local_realm, optarg, sizeof(local_realm));
|
|
break;
|
|
case 'i':
|
|
/* Only listen on this address */
|
|
if(inet_aton (optarg, &i_addr) == 0) {
|
|
fprintf (stderr, "Bad address: %s\n", optarg);
|
|
exit (1);
|
|
}
|
|
++i_flag;
|
|
break;
|
|
default:
|
|
usage();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (optind == (argc-1)) {
|
|
if (kerb_db_set_name(argv[optind]) != 0) {
|
|
fprintf(stderr, "Could not set alternate database name\n");
|
|
exit(1);
|
|
}
|
|
optind++;
|
|
}
|
|
|
|
if (optind != argc)
|
|
usage();
|
|
|
|
printf("Kerberos server starting\n");
|
|
|
|
if ((!nflag) && (max_age != -1))
|
|
printf("\tMaximum database age: %d seconds\n", max_age);
|
|
if (pause_int != -1)
|
|
printf("\tSleep for %d seconds on error\n", pause_int);
|
|
else
|
|
printf("\tSleep forever on error\n");
|
|
if (mflag)
|
|
printf("\tMaster key will be entered manually\n");
|
|
|
|
printf("\tLog file is %s\n", log_file);
|
|
|
|
kset_logfile(log_file);
|
|
|
|
make_sockets(port_spec, i_flag ? &i_addr : NULL, &fds, &nfds);
|
|
|
|
/* do all the database and cache inits */
|
|
if ((n = kerb_init())) {
|
|
if (mflag) {
|
|
printf("Kerberos db and cache init ");
|
|
printf("failed = %d ...exiting\n", n);
|
|
exit (1);
|
|
} else {
|
|
klog(L_KRB_PERR,
|
|
"Kerberos db and cache init failed = %d ...exiting", n);
|
|
hang();
|
|
}
|
|
}
|
|
|
|
/* Make sure database isn't stale */
|
|
check_db_age();
|
|
|
|
/* setup master key */
|
|
if (kdb_get_master_key (mflag, &master_key, master_key_schedule) != 0) {
|
|
klog (L_KRB_PERR, "kerberos: couldn't get master key.");
|
|
exit (1);
|
|
}
|
|
kerror = kdb_verify_master_key (&master_key, master_key_schedule, stdout);
|
|
if (kerror < 0) {
|
|
klog (L_KRB_PERR, "Can't verify master key.");
|
|
memset(master_key, 0, sizeof (master_key));
|
|
memset (master_key_schedule, 0, sizeof (master_key_schedule));
|
|
exit (1);
|
|
}
|
|
|
|
master_key_version = (u_char) kerror;
|
|
|
|
fprintf(stdout, "\nCurrent Kerberos master key version is %d\n",
|
|
master_key_version);
|
|
des_init_random_number_generator(&master_key);
|
|
|
|
if (!rflag) {
|
|
/* Look up our local realm */
|
|
krb_get_lrealm(local_realm, 1);
|
|
}
|
|
fprintf(stdout, "Local realm: %s\n", local_realm);
|
|
fflush(stdout);
|
|
|
|
if (set_tgtkey(local_realm)) {
|
|
/* Ticket granting service unknown */
|
|
klog(L_KRB_PERR, "Ticket granting ticket service unknown");
|
|
fprintf(stderr, "Ticket granting ticket service unknown\n");
|
|
exit(1);
|
|
}
|
|
if (mflag) {
|
|
if ((child = fork()) != 0) {
|
|
printf("Kerberos started, PID=%d\n", child);
|
|
exit(0);
|
|
}
|
|
setup_disc();
|
|
}
|
|
|
|
klog(L_ALL_REQ, "Starting Kerberos for %s (kvno %d)",
|
|
local_realm, master_key_version);
|
|
|
|
/* receive loop */
|
|
loop(fds, nfds);
|
|
exit(1);
|
|
}
|
|
|
|
|
|
static void
|
|
read_socket(struct descr *n)
|
|
{
|
|
int b;
|
|
struct sockaddr_in from;
|
|
int fromlen = sizeof(from);
|
|
b = recvfrom(n->s, n->buf.dat + n->buf.length,
|
|
MAX_PKT_LEN - n->buf.length, 0,
|
|
(struct sockaddr *)&from, &fromlen);
|
|
if(b < 0){
|
|
if(n->type == SOCK_STREAM){
|
|
close(n->s);
|
|
n->s = -1;
|
|
}
|
|
n->buf.length = 0;
|
|
return;
|
|
}
|
|
n->buf.length += b;
|
|
if(n->type == SOCK_STREAM){
|
|
char *proto = "tcp";
|
|
if(n->buf.length > 4 &&
|
|
strncmp((char *)n->buf.dat, "GET ", 4) == 0 &&
|
|
strncmp((char *)n->buf.dat + n->buf.length - 4,
|
|
"\r\n\r\n", 4) == 0){
|
|
char *p;
|
|
char *save = NULL;
|
|
|
|
n->buf.dat[n->buf.length - 1] = 0;
|
|
strtok_r((char *)n->buf.dat, " \t\r\n", &save);
|
|
p = strtok_r(NULL, " \t\r\n", &save);
|
|
if(p == NULL)
|
|
p = "";
|
|
if(*p == '/') p++;
|
|
n->buf.length = base64_decode(p, n->buf.dat);
|
|
if(n->buf.length <= 0){
|
|
const char *msg =
|
|
"HTTP/1.1 404 Not found\r\n"
|
|
"Server: KTH-KRB/1\r\n"
|
|
"Content-type: text/html\r\n"
|
|
"Content-transfer-encoding: 8bit\r\n\r\n"
|
|
"<TITLE>404 Not found</TITLE>\r\n"
|
|
"<H1>404 Not found</H1>\r\n"
|
|
"That page does not exist. Information about "
|
|
"<A HREF=\"http://www.pdc.kth.se/kth-krb\">KTH-KRB</A> "
|
|
"is available elsewhere.\r\n";
|
|
fromlen = sizeof(from);
|
|
if(getpeername(n->s,(struct sockaddr*)&from, &fromlen) == 0)
|
|
klog(L_KRB_PERR, "Unknown HTTP request from %s",
|
|
inet_ntoa(from.sin_addr));
|
|
else
|
|
klog(L_KRB_PERR, "Unknown HTTP request from <unknown>");
|
|
write(n->s, msg, strlen(msg));
|
|
close(n->s);
|
|
n->s = -1;
|
|
n->buf.length = 0;
|
|
return;
|
|
}
|
|
proto = "http";
|
|
b = 0;
|
|
}
|
|
else if(n->buf.length >= 4 && n->buf.dat[0] == 0){
|
|
/* if this is a new type of packet (with
|
|
the length attached to the head of the
|
|
packet), and there is no more data to
|
|
be read, fake an old packet, so the
|
|
code below will work */
|
|
u_int32_t len;
|
|
krb_get_int(n->buf.dat, &len, 4, 0);
|
|
if(n->buf.length == len + 4){
|
|
memmove(n->buf.dat, n->buf.dat + 4, len);
|
|
b = 0;
|
|
}
|
|
}
|
|
if(b == 0){
|
|
/* handle request if there are
|
|
no more bytes to read */
|
|
fromlen = sizeof(from);
|
|
getpeername(n->s,(struct sockaddr*)&from, &fromlen);
|
|
kerberos_wrap(n->s, &n->buf, proto, &from,
|
|
&n->addr);
|
|
n->buf.length = 0;
|
|
close(n->s);
|
|
n->s = -1;
|
|
}
|
|
}else{
|
|
/* udp packets are atomic */
|
|
kerberos_wrap(n->s, &n->buf, "udp", &from,
|
|
&n->addr);
|
|
n->buf.length = 0;
|
|
}
|
|
}
|
|
|
|
static fd_set readfds;
|
|
|
|
static void
|
|
loop(struct descr *fds, int base_nfds)
|
|
{
|
|
int nfds = base_nfds;
|
|
int max_tcp = min(FD_SETSIZE, getdtablesize()) - fds[base_nfds - 1].s;
|
|
if (max_tcp <= 10) {
|
|
errno = EMFILE;
|
|
errx(1, "Aborting: too many descriptors");
|
|
}
|
|
max_tcp -= 10; /* We need a few extra for DB, logs, etc. */
|
|
if (max_tcp > 100) max_tcp = 100; /* Keep to some sane limit. */
|
|
|
|
for (;;) {
|
|
int ret;
|
|
struct timeval tv;
|
|
int next_timeout = 10; /* In seconds */
|
|
int maxfd = 0;
|
|
struct descr *n, *minfree;
|
|
int accepted; /* accept at most one socket per `round' */
|
|
|
|
FD_ZERO(&readfds);
|
|
gettimeofday(&tv, NULL);
|
|
maxfd = 0;
|
|
minfree = NULL;
|
|
/* Remove expired TCP sockets, and add all other
|
|
to the set we are selecting on */
|
|
for(n = fds; n < fds + nfds; n++){
|
|
if(n->s >= 0 && n->timeout && tv.tv_sec > n->timeout){
|
|
kerb_err_reply(n->s, NULL, KERB_ERR_TIMEOUT, "Timeout");
|
|
close(n->s);
|
|
n->s = -1;
|
|
}
|
|
if(n->s < 0){
|
|
if(minfree == NULL) minfree = n;
|
|
continue;
|
|
}
|
|
FD_SET(n->s, &readfds);
|
|
maxfd = max(maxfd, n->s);
|
|
next_timeout = min(next_timeout, tv.tv_sec - n->timeout);
|
|
}
|
|
/* add more space for sockets */
|
|
if (minfree == NULL && nfds < base_nfds + max_tcp) {
|
|
int i = nfds;
|
|
struct descr *new;
|
|
nfds *=2;
|
|
if (nfds > base_nfds + max_tcp)
|
|
nfds = base_nfds + max_tcp;
|
|
new = realloc(fds, sizeof(struct descr) * nfds);
|
|
if(new){
|
|
fds = new;
|
|
minfree = fds + i;
|
|
for(; i < nfds; i++) fds[i].s = -1;
|
|
}
|
|
}
|
|
if (minfree == NULL) {
|
|
/*
|
|
* We are possibly the subject of a DOS attack, pick a TCP
|
|
* connection at random and drop it.
|
|
*/
|
|
int r = rand() % (nfds - base_nfds);
|
|
r = r + base_nfds;
|
|
FD_CLR(fds[r].s, &readfds);
|
|
close(fds[r].s);
|
|
fds[r].s = -1;
|
|
minfree = &fds[r];
|
|
}
|
|
if (next_timeout < 0) next_timeout = 0;
|
|
tv.tv_sec = next_timeout;
|
|
tv.tv_usec = 0;
|
|
ret = select(maxfd + 1, &readfds, 0, 0, &tv);
|
|
if (ret < 0) {
|
|
if (errno != EINTR)
|
|
klog(L_KRB_PERR, "select: %s", strerror(errno));
|
|
continue;
|
|
}
|
|
accepted = 0;
|
|
for (n = fds; n < fds + nfds; n++){
|
|
if(n->s < 0) continue;
|
|
if (FD_ISSET(n->s, &readfds)){
|
|
if(n->type == SOCK_STREAM && n->timeout == 0){
|
|
/* add accepted socket to list of sockets we are
|
|
selecting on */
|
|
int s;
|
|
if(accepted) continue;
|
|
accepted = 1;
|
|
s = accept(n->s, NULL, 0);
|
|
if (minfree == NULL || s >= FD_SETSIZE) {
|
|
close(s);
|
|
}else{
|
|
minfree->s = s;
|
|
minfree->type = SOCK_STREAM;
|
|
gettimeofday(&tv, NULL);
|
|
minfree->timeout = tv.tv_sec + 4; /* XXX */
|
|
minfree->buf.length = 0;
|
|
memcpy(&minfree->addr, &n->addr, sizeof(minfree->addr));
|
|
}
|
|
}else
|
|
read_socket(n);
|
|
}
|
|
}
|
|
}
|
|
}
|