1
0
mirror of https://github.com/samba-team/samba.git synced 2025-12-07 20:23:50 +03:00
Files
samba-mirror/source/heimdal/lib/krb5/send_to_kdc.c
Andrew Bartlett 3b09f9e8f9 r10561: This patch takes over KDC socket routines in Heimdal, and directs them
at the Samba4 socket layer.

The intention here is to ensure that other events may be processed while
heimdal is waiting on the KDC.  The interface is designed to be
sufficiently flexible, so that the plugin may choose how to time
communication with the KDC (ie multiple outstanding requests, looking
for a functional KDC).

I've hacked the socket layer out of cldap.c to handle this very
specific case of one udp packet and reply.  Likewise I also handle
TCP, stolen from the winbind code.

This same plugin system might also be useful for a self-contained
testing mode in Heimdal, in conjunction with libkdc.  I would suggest
using socket-wrapper instead however.

Andrew Bartlett
2007-10-10 13:39:04 -05:00

456 lines
11 KiB
C

/*
* Copyright (c) 1997 - 2002 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* 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.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``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 INSTITUTE OR CONTRIBUTORS 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 "krb5_locl.h"
RCSID("$Id: send_to_kdc.c,v 1.56 2005/06/17 04:33:11 lha Exp $");
struct send_and_recv {
krb5_send_and_recv_func_t func;
krb5_send_and_recv_close_func_t close;
void *data;
};
krb5_error_code KRB5_LIB_FUNCTION
krb5_set_send_recv_func(krb5_context context,
krb5_send_and_recv_func_t func,
krb5_send_and_recv_close_func_t close_fn,
void *data)
{
free(context->send_and_recv);
context->send_and_recv = malloc(sizeof(*context->send_and_recv));
if (!context->send_and_recv) {
return ENOMEM;
}
context->send_and_recv->func = func;
context->send_and_recv->close = close_fn;
context->send_and_recv->data = data;
return 0;
}
/*
* send the data in `req' on the socket `fd' (which is datagram iff udp)
* waiting `tmout' for a reply and returning the reply in `rep'.
* iff limit read up to this many bytes
* returns 0 and data in `rep' if succesful, otherwise -1
*/
static int
recv_loop (int fd,
time_t tmout,
int udp,
size_t limit,
krb5_data *rep)
{
fd_set fdset;
struct timeval timeout;
int ret;
int nbytes;
if (fd >= FD_SETSIZE) {
return -1;
}
krb5_data_zero(rep);
do {
FD_ZERO(&fdset);
FD_SET(fd, &fdset);
timeout.tv_sec = tmout;
timeout.tv_usec = 0;
ret = select (fd + 1, &fdset, NULL, NULL, &timeout);
if (ret < 0) {
if (errno == EINTR)
continue;
return -1;
} else if (ret == 0) {
return 0;
} else {
void *tmp;
if (ioctl (fd, FIONREAD, &nbytes) < 0) {
krb5_data_free (rep);
return -1;
}
if(nbytes == 0)
return 0;
if (limit)
nbytes = min(nbytes, limit - rep->length);
tmp = realloc (rep->data, rep->length + nbytes);
if (tmp == NULL) {
krb5_data_free (rep);
return -1;
}
rep->data = tmp;
ret = recv (fd, (char*)tmp + rep->length, nbytes, 0);
if (ret < 0) {
krb5_data_free (rep);
return -1;
}
rep->length += ret;
}
} while(!udp && (limit == 0 || rep->length < limit));
return 0;
}
/*
* Send kerberos requests and receive a reply on a udp or any other kind
* of a datagram socket. See `recv_loop'.
*/
static int
send_and_recv_udp(int fd,
time_t tmout,
const krb5_data *req,
krb5_data *rep)
{
if (send (fd, req->data, req->length, 0) < 0)
return -1;
return recv_loop(fd, tmout, 1, 0, rep);
}
/*
* `send_and_recv' for a TCP (or any other stream) socket.
* Since there are no record limits on a stream socket the protocol here
* is to prepend the request with 4 bytes of its length and the reply
* is similarly encoded.
*/
static int
send_and_recv_tcp(int fd,
time_t tmout,
const krb5_data *req,
krb5_data *rep)
{
unsigned char len[4];
unsigned long rep_len;
krb5_data len_data;
_krb5_put_int(len, req->length, 4);
if(net_write(fd, len, sizeof(len)) < 0)
return -1;
if(net_write(fd, req->data, req->length) < 0)
return -1;
if (recv_loop (fd, tmout, 0, 4, &len_data) < 0)
return -1;
if (len_data.length != 4) {
krb5_data_free (&len_data);
return -1;
}
_krb5_get_int(len_data.data, &rep_len, 4);
krb5_data_free (&len_data);
if (recv_loop (fd, tmout, 0, rep_len, rep) < 0)
return -1;
if(rep->length != rep_len) {
krb5_data_free (rep);
return -1;
}
return 0;
}
int
_krb5_send_and_recv_tcp(int fd,
time_t tmout,
const krb5_data *req,
krb5_data *rep)
{
return send_and_recv_tcp(fd, tmout, req, rep);
}
/*
* `send_and_recv' tailored for the HTTP protocol.
*/
static int
send_and_recv_http(int fd,
time_t tmout,
const char *prefix,
const krb5_data *req,
krb5_data *rep)
{
char *request;
char *str;
int ret;
int len = base64_encode(req->data, req->length, &str);
if(len < 0)
return -1;
asprintf(&request, "GET %s%s HTTP/1.0\r\n\r\n", prefix, str);
free(str);
if (request == NULL)
return -1;
ret = net_write (fd, request, strlen(request));
free (request);
if (ret < 0)
return ret;
ret = recv_loop(fd, tmout, 0, 0, rep);
if(ret)
return ret;
{
unsigned long rep_len;
char *s, *p;
s = realloc(rep->data, rep->length + 1);
if (s == NULL) {
krb5_data_free (rep);
return -1;
}
s[rep->length] = 0;
p = strstr(s, "\r\n\r\n");
if(p == NULL) {
free(s);
return -1;
}
p += 4;
rep->data = s;
rep->length -= p - s;
if(rep->length < 4) { /* remove length */
free(s);
return -1;
}
rep->length -= 4;
_krb5_get_int(p, &rep_len, 4);
if (rep_len != rep->length) {
free(s);
return -1;
}
memmove(rep->data, p + 4, rep->length);
}
return 0;
}
static int
init_port(const char *s, int fallback)
{
if (s) {
int tmp;
sscanf (s, "%d", &tmp);
return htons(tmp);
} else
return fallback;
}
/*
* Return 0 if succesful, otherwise 1
*/
static int
send_via_proxy (krb5_context context,
const krb5_krbhst_info *hi,
const krb5_data *send_data,
krb5_data *receive)
{
char *proxy2 = strdup(context->http_proxy);
char *proxy = proxy2;
char *prefix;
char *colon;
struct addrinfo hints;
struct addrinfo *ai, *a;
int ret;
int s = -1;
char portstr[NI_MAXSERV];
if (proxy == NULL)
return ENOMEM;
if (strncmp (proxy, "http://", 7) == 0)
proxy += 7;
colon = strchr(proxy, ':');
if(colon != NULL)
*colon++ = '\0';
memset (&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
snprintf (portstr, sizeof(portstr), "%d",
ntohs(init_port (colon, htons(80))));
ret = getaddrinfo (proxy, portstr, &hints, &ai);
free (proxy2);
if (ret)
return krb5_eai_to_heim_errno(ret, errno);
for (a = ai; a != NULL; a = a->ai_next) {
s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
if (s < 0)
continue;
if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
close (s);
continue;
}
break;
}
if (a == NULL) {
freeaddrinfo (ai);
return 1;
}
freeaddrinfo (ai);
asprintf(&prefix, "http://%s/", hi->hostname);
if(prefix == NULL) {
close(s);
return 1;
}
ret = send_and_recv_http(s, context->kdc_timeout,
prefix, send_data, receive);
close (s);
free(prefix);
if(ret == 0 && receive->length != 0)
return 0;
return 1;
}
/*
* Send the data `send' to one host from `handle` and get back the reply
* in `receive'.
*/
krb5_error_code KRB5_LIB_FUNCTION
krb5_sendto (krb5_context context,
const krb5_data *send_data,
krb5_krbhst_handle handle,
krb5_data *receive)
{
krb5_error_code ret = 0;
int fd;
int i;
for (i = 0; i < context->max_retries; ++i) {
krb5_krbhst_info *hi;
while (krb5_krbhst_next(context, handle, &hi) == 0) {
struct addrinfo *ai, *a;
if (context->send_and_recv) {
ret = context->send_and_recv->func(context,
context->send_and_recv->data,
hi, send_data, receive);
if (ret) {
continue;
} else if (receive->length != 0) {
return 0;
} else {
continue;
}
}
if(hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) {
if (send_via_proxy (context, hi, send_data, receive)) {
/* Try again, with next host */
continue;
} else {
/* Success */
return 0;
}
}
ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
if (ret)
continue;
for (a = ai; a != NULL; a = a->ai_next) {
fd = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
if (fd < 0)
continue;
if (connect (fd, a->ai_addr, a->ai_addrlen) < 0) {
close (fd);
continue;
}
switch (hi->proto) {
case KRB5_KRBHST_HTTP :
ret = send_and_recv_http(fd, context->kdc_timeout,
"", send_data, receive);
break;
case KRB5_KRBHST_TCP :
ret = send_and_recv_tcp (fd, context->kdc_timeout,
send_data, receive);
break;
case KRB5_KRBHST_UDP :
ret = send_and_recv_udp (fd, context->kdc_timeout,
send_data, receive);
break;
}
close (fd);
if(ret == 0 && receive->length != 0) {
return 0;
}
}
}
krb5_krbhst_reset(context, handle);
}
krb5_clear_error_string (context);
return KRB5_KDC_UNREACH;
}
krb5_error_code KRB5_LIB_FUNCTION
krb5_sendto_kdc(krb5_context context,
const krb5_data *send_data,
const krb5_realm *realm,
krb5_data *receive)
{
return krb5_sendto_kdc_flags(context, send_data, realm, receive, 0);
}
krb5_error_code KRB5_LIB_FUNCTION
krb5_sendto_kdc_flags(krb5_context context,
const krb5_data *send_data,
const krb5_realm *realm,
krb5_data *receive,
int flags)
{
krb5_error_code ret;
krb5_krbhst_handle handle;
int type;
if ((flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc)
type = KRB5_KRBHST_ADMIN;
else
type = KRB5_KRBHST_KDC;
if (send_data->length > context->large_msg_size)
flags |= KRB5_KRBHST_FLAGS_LARGE_MSG;
ret = krb5_krbhst_init_flags(context, *realm, type, flags, &handle);
if (ret)
return ret;
ret = krb5_sendto(context, send_data, handle, receive);
krb5_krbhst_free(context, handle);
if (ret == KRB5_KDC_UNREACH)
krb5_set_error_string(context,
"unable to reach any KDC in realm %s", *realm);
return ret;
}