/*
* lib/krb5/os/sendto_kdc.c
*
- * Copyright 1990,1991 by the Massachusetts Institute of Technology.
+ * Copyright 1990,1991,2001,2002 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
#include <time.h>
#endif
#include "os-proto.h"
+#ifdef _WIN32
+#include <sys/timeb.h>
+#endif
#ifdef _AIX
#include <sys/select.h>
#endif
-#ifdef _WIN32
-#define ENABLE_TCP 0 /* for now */
-#else
-#define ENABLE_TCP 1
-#endif
-
-#if defined(ENABLE_TCP) && !defined(_WIN32)
+#ifndef _WIN32
/* For FIONBIO. */
#include <sys/ioctl.h>
#ifdef HAVE_SYS_FILIO_H
* should probably be returned as well.
*/
+#ifdef DEBUG
+ fprintf(stderr, "krb5_sendto_kdc(%d@%p, \"",
+ message->length, message->data);
+ /* realm need not be nul-terminated */
+ fwrite(realm->data, 1, realm->length, stderr);
+ fprintf(stderr, "\", use_master=%d, tcp_only=%d)\n", use_master, tcp_only);
+#endif
+
retval = (use_master ? KRB5_KDC_UNREACH : KRB5_REALM_UNKNOWN);
if (!tcp_only
+ && message->length < 1500
&& ! krb5_locate_kdc(context, realm, &addrs, use_master, SOCK_DGRAM)) {
if (addrs.naddrs > 0) {
retval = krb5int_sendto_udp (context, message, &addrs, reply);
return 0;
}
}
-
-#if ENABLE_TCP
if (! krb5_locate_kdc(context, realm, &addrs, use_master, SOCK_STREAM)) {
if (addrs.naddrs > 0) {
retval = krb5int_sendto_tcp (context, message, &addrs, reply);
return 0;
}
}
-#endif
return retval;
}
#endif
}
+#if defined(_WIN32) && defined(DEBUG)
+static char *bogus_strerror (int xerr)
+{
+ static char buf[30];
+ sprintf(buf, "[err%d]", xerr);
+ return buf;
+}
+#define strerror(S) bogus_strerror(S)
+#endif
+
#ifdef DEBUG
+
+#ifdef _WIN32
+#define dperror(MSG) \
+ fprintf(stderr, \
+ "%s: an error occurred ... wouldn't you like to know more?\n" \
+ "\tline=%d errno=%d socketerrno=%d\n", \
+ (MSG), __LINE__, errno, SOCKET_ERRNO)
+#else
#define dperror(MSG) perror(MSG)
+#endif
#define dfprintf(ARGLIST) fprintf ARGLIST
-#else
+
+static void print_fdsets (FILE *, fd_set *, fd_set *, fd_set *, int);
+
+#else /* ! DEBUG */
+
#define dperror(MSG) ((void)(MSG))
#define dfprintf(ARGLIST) ((void)0)
+
#endif
krb5_error_code
krb5int_sendto_udp (krb5_context context, const krb5_data *message,
const struct addrlist *addrs, krb5_data *reply)
{
- int timeout, host, i;
+ int host, i;
+ unsigned int timeout;
int sent, nready;
krb5_error_code retval;
SOCKET *socklist;
socklist[host] = socket(ai->ai_family, SOCK_DGRAM, 0);
if (socklist[host] == INVALID_SOCKET) {
dfprintf((stderr, "socket: %s\naf was %d\n",
- strerror(errno), ai->ai_family));
+ strerror(SOCKET_ERRNO), ai->ai_family));
continue; /* try other hosts */
}
debug_log_connect(socklist[host], ai);
dperror ("connect");
continue;
}
+ } else {
+ dfprintf((stderr, "fd %d...", socklist[host]));
}
dfprintf((stderr, "sending..."));
- if (send(socklist[host],
- message->data, message->length, 0)
+ if (send(socklist[host], message->data, message->length, 0)
!= message->length) {
dperror ("sendto");
continue;
waitlen.tv_sec = timeout;
FD_ZERO(&readable);
FD_SET(socklist[host], &readable);
+#ifdef DEBUG
+ fprintf(stderr, "selecting on one socket [");
+ print_fdsets(stderr, &readable, 0, 0, SOCKET_NFDS(socklist[host]));
+ fprintf(stderr, " ] timeout %ld.%06ld\n",
+ waitlen.tv_sec, waitlen.tv_usec);
+#endif
if ((nready = select(SOCKET_NFDS(socklist[host]),
&readable,
0,
}
if ((cc = recv(socklist[host],
reply->data, reply->length,
- 0)) == SOCKET_ERROR)
- {
+ 0)) == SOCKET_ERROR) {
/* man page says error could be:
EBADF: won't happen
ENOTSOCK: it's a socket.
server (i.e. don't set sent = 1).
*/
- if (SOCKET_ERRNO == SOCKET_EINTR)
+ int e = SOCKET_ERRNO;
+#ifdef DEBUG
+ dperror("recv");
+#endif
+ if (e == SOCKET_EINTR)
sent = 1;
continue;
- }
+ }
/* We might consider here verifying that the reply
came from one of the KDC's listed for that address type,
return retval;
}
-#if ENABLE_TCP
/*
+ * Notes:
+ *
+ * Getting "connection refused" on a connected UDP socket causes
+ * select to indicate write capability on UNIX, but only shows up
+ * as an exception on Windows. (I don't think any UNIX system flags
+ * the error as an exception.) So we check for both, or make it
+ * system-specific.
+ *
+ * Always watch for responses from *any* of the servers. Eventually
+ * fix the UDP code to do the same.
+ *
* To do:
* - TCP NOPUSH/CORK socket options?
* - error codes that don't suck
SOCKET fd;
krb5_error_code err;
enum { CONNECTING, WRITING, READING, FAILED } state;
- char *buf;
- size_t bufsize;
unsigned char bufsizebytes[4];
- size_t bufsizebytes_read;
- struct iovec iov[2];
- struct iovec *iovp;
- int iov_count;
+ union {
+ /* When state is CONNECTING or WRITING, 'out' is valid. When
+ state is READING, 'in' is valid. When state is FAILED,
+ both should be ignored. */
+ struct {
+ sg_buf sgbuf[2];
+ sg_buf *sgp;
+ int sg_count;
+ } out;
+ struct {
+ size_t bufsizebytes_read;
+ size_t bufsize;
+ char *buf;
+ char *pos;
+ size_t n_left;
+ } in;
+ } x;
};
struct select_state {
int max, nfds;
- fd_set rfds, wfds;
+ fd_set rfds, wfds, xfds;
struct timeval end_time;
};
#ifdef DEBUG
static void
-print_fdsets (FILE *f, fd_set *rfds, fd_set *wfds, int maxfd)
+print_fdsets (FILE *f, fd_set *rfds, fd_set *wfds, fd_set *xfds, int maxfd)
{
int i;
for (i = 0; i < maxfd; i++) {
int r = FD_ISSET(i, rfds);
- int w = FD_ISSET(i, wfds);
- if (r || w) {
+ int w = wfds && FD_ISSET(i, wfds);
+ int x = xfds && FD_ISSET(i, xfds);
+ if (r || w || x) {
fprintf(f, " %d", i);
if (r)
fprintf(f, "r");
if (w)
fprintf(f, "w");
+ if (x)
+ fprintf(f, "x");
}
}
}
#endif
+static int getcurtime (struct timeval *tvp)
+{
+#ifdef _WIN32
+ struct _timeb tb;
+ _ftime(&tb);
+ tvp->tv_sec = tb.time;
+ tvp->tv_usec = tb.millitm * 1000;
+ /* Can _ftime fail? */
+ return 0;
+#else
+ if (gettimeofday(tvp, 0)) {
+ dperror("gettimeofday");
+ return errno;
+ }
+ return 0;
+#endif
+}
+
/*
* Call select and return results.
* Input: interesting file descriptors and absolute timeout
krb5_error_code e;
*out = *in;
- if (gettimeofday(&now, 0)) {
- e = errno;
- dperror("gettimeofday");
+ e = getcurtime(&now);
+ if (e)
return e;
- }
out->end_time.tv_sec -= now.tv_sec;
out->end_time.tv_usec -= now.tv_usec;
if (out->end_time.tv_usec < 0) {
}
#ifdef DEBUG
fprintf(stderr, "selecting on %d sockets [", out->nfds);
- print_fdsets(stderr, &out->rfds, &out->wfds, out->max);
+ print_fdsets(stderr, &out->rfds, &out->wfds, &out->xfds, out->max);
fprintf(stderr, " ] timeout %ld.%06ld\n", (long) out->end_time.tv_sec,
(long) out->end_time.tv_usec);
#endif
- *sret = select(out->max, &out->rfds, &out->wfds, 0, &out->end_time);
- e = errno;
+ *sret = select(out->max, &out->rfds, &out->wfds, &out->xfds,
+ &out->end_time);
+ e = SOCKET_ERRNO;
#ifdef DEBUG
fprintf(stderr, "select returns %d", *sret);
if (*sret < 0)
- fprintf(stderr, ", errno = %d/%s\n", e, strerror(e));
+ fprintf(stderr, ", error = %d/%s\n", e, strerror(e));
else if (*sret == 0)
fprintf(stderr, " (timeout)\n");
else {
fprintf(stderr, ":");
- print_fdsets(stderr, &out->rfds, &out->wfds, out->max);
+ print_fdsets(stderr, &out->rfds, &out->wfds, &out->xfds, out->max);
fprintf(stderr, "\n");
}
#endif
if (*sret < 0)
- return errno;
- return 0;
-}
-
-static int
-make_nonblocking (int fd)
-{
- static const int one = 1;
-
- if (ioctlsocket (fd, FIONBIO, &one)) {
- dperror("sendto_kdc_tcp: ioctl(FIONBIO)");
- return errno;
- }
+ return e;
return 0;
}
int fd, e;
state->err = 0;
- state->buf = 0;
- state->bufsizebytes_read = 0;
- state->iovp = state->iov;
- state->iov[0].iov_base = (char *) state->bufsizebytes;
- state->iov[0].iov_len = 4;
- state->iov[1].iov_base = 0;
- state->iov[1].iov_len = 0;
- state->iov_count = 2;
+ state->x.out.sgp = state->x.out.sgbuf;
+ SG_SET(&state->x.out.sgbuf[0], state->bufsizebytes, 4);
+ SG_SET(&state->x.out.sgbuf[1], 0, 0);
+ state->x.out.sg_count = 2;
dfprintf((stderr, "getting stream socket in family %d...", ai->ai_family));
fd = socket(ai->ai_family, SOCK_STREAM, 0);
if (fd == INVALID_SOCKET) {
- state->err = errno;
+ state->err = SOCKET_ERRNO;
dfprintf((stderr, "socket: %s connecting with af %d\n",
strerror (state->err), ai->ai_family));
return -1; /* try other hosts */
}
- debug_log_connect(fd, ai);
/* Make it non-blocking. */
- make_nonblocking(fd);
+ {
+ static const int one = 1;
+ static const struct linger lopt = { 0, 0 };
+
+ if (ioctlsocket(fd, FIONBIO, (const void *) &one))
+ dperror("sendto_kdc_tcp: ioctl(FIONBIO)");
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &lopt, sizeof(lopt)))
+ dperror("sendto_kdc_tcp: setsockopt(SO_LINGER)");
+ }
/* Start connecting to KDC. */
+ debug_log_connect(fd, ai);
e = connect(fd, ai->ai_addr, ai->ai_addrlen);
if (e != 0) {
/*
state->state = CONNECTING;
} else {
state->err = SOCKET_ERRNO;
- dfprintf((stderr, "socket error %d in connect()\n", state->err));
state->state = FAILED;
return -2;
}
return 0;
}
-static void
-iov_advance (struct iovec *iov, int how_far)
-{
- if (iov->iov_len < how_far)
- abort();
- /* Can't do math on void pointers. */
- iov->iov_base = (char *) iov->iov_base + how_far;
- iov->iov_len -= how_far;
-}
-
/* Return nonzero only if we're finished and the caller should exit
its loop. This happens in two cases: We have a complete message,
or the socket has closed and no others are open. */
static int
service_tcp_fd (struct conn_state *conn, struct select_state *selstate,
- int can_read, int can_write)
+ int can_read, int can_write, int exception)
{
krb5_error_code e = 0;
int nwritten, nread;
#ifdef DEBUG
- /* index with can_read + 2 * can_write */
- static const char *const select_states[] = {
- "cannot_read_or_write??",
- "can_read", "can_write", "can_read/can_write",
- };
- fprintf(stderr, "handling %s on fd %d in state %s\n",
- select_states[can_read + 2 * can_write],
- conn->fd, state_strings[(int) conn->state]);
+ {
+ int sep = ' ';
+ fprintf(stderr, "handling");
+ if (can_read)
+ fprintf(stderr, "%cread", sep), sep = '/';
+ if (can_write)
+ fprintf(stderr, "%cwrite", sep), sep = '/';
+ if (exception)
+ fprintf(stderr, "%cexception", sep), sep = '/';
+ if (sep == ' ')
+ fprintf(stderr, " no_flags?!");
+ fprintf(stderr, " on fd %d in state %s\n",
+ conn->fd, state_strings[(int) conn->state]);
+ }
#endif
- if (!can_read && !can_write)
+ if (!can_read && !can_write && !exception)
abort();
switch (conn->state) {
+ SOCKET_WRITEV_TEMP tmp;
case CONNECTING:
if (can_read) {
e = EINVAL /* ?? */;
kill_conn:
conn->state = FAILED;
- shutdown(conn->fd, 2);
+ shutdown(conn->fd, SHUTDOWN_BOTH);
FD_CLR(conn->fd, &selstate->rfds);
FD_CLR(conn->fd, &selstate->wfds);
+ FD_CLR(conn->fd, &selstate->xfds);
conn->err = e;
dfprintf((stderr, "abandoning connection %d: %s\n",
conn->fd, strerror(e)));
if (selstate->max == 1 + conn->fd) {
while (selstate->max > 0
&& ! FD_ISSET(selstate->max-1, &selstate->rfds)
- && ! FD_ISSET(selstate->max-1, &selstate->wfds))
+ && ! FD_ISSET(selstate->max-1, &selstate->wfds)
+ && ! FD_ISSET(selstate->max-1, &selstate->xfds))
selstate->max--;
dfprintf((stderr, "new max_fd + 1 is %d\n", selstate->max));
}
}
return e == 0;
}
+ if (exception) {
+#ifdef DEBUG
+ int sockerr;
+ size_t sockerrlen = sizeof(sockerr);
+#endif
+ handle_exception:
+#ifdef DEBUG
+ e = getsockopt(conn->fd, SOL_SOCKET, SO_ERROR,
+ &sockerr, &sockerrlen);
+ if (e != 0) {
+ /* What to do now? */
+ e = SOCKET_ERRNO;
+ dfprintf((stderr,
+ "getsockopt(SO_ERROR) on exception fd failed: %d\n",
+ e));
+ goto kill_conn;
+ }
+ /* Okay, got the error back. Either way, kill the
+ connection. */
+ e = sockerr;
+#else
+ e = 1; /* need only be non-zero */
+#endif
+ goto kill_conn;
+ }
/*
* Connect finished -- but did it succeed or fail?
+ * UNIX sets can_write if failed.
* Try writing, I guess, and find out.
*/
conn->state = WRITING;
/* Bad -- the KDC shouldn't be sending anything yet. */
goto kill_conn;
}
+ if (exception)
+ goto handle_exception;
- try_writing:
+ try_writing:
dfprintf((stderr, "trying to writev %d (%d bytes) to fd %d\n",
- conn->iov_count,
- ((conn->iov_count == 2 ? conn->iovp[1].iov_len : 0)
- + conn->iovp[0].iov_len),
+ conn->x.out.sg_count,
+ ((conn->x.out.sg_count == 2 ? SG_LEN(&conn->x.out.sgp[1]) : 0)
+ + SG_LEN(&conn->x.out.sgp[0])),
conn->fd));
- nwritten = writev(conn->fd, conn->iovp,
- conn->iov_count);
+ nwritten = SOCKET_WRITEV(conn->fd, conn->x.out.sgp,
+ conn->x.out.sg_count, tmp);
if (nwritten < 0) {
e = SOCKET_ERRNO;
dfprintf((stderr, "failed: %s\n", strerror(e)));
}
dfprintf((stderr, "wrote %d bytes\n", nwritten));
while (nwritten) {
- struct iovec *iovp = conn->iovp;
- if (nwritten < iovp->iov_len) {
- iov_advance(iovp, nwritten);
+ sg_buf *sgp = conn->x.out.sgp;
+ if (nwritten < SG_LEN(sgp)) {
+ SG_ADVANCE(sgp, nwritten);
nwritten = 0;
} else {
- nwritten -= conn->iovp->iov_len;
- conn->iovp++;
- conn->iov_count--;
- if (conn->iov_count == 0 && nwritten != 0)
+ nwritten -= SG_LEN(conn->x.out.sgp);
+ conn->x.out.sgp++;
+ conn->x.out.sg_count--;
+ if (conn->x.out.sg_count == 0 && nwritten != 0)
/* Wrote more than we wanted to? */
abort();
}
}
- if (conn->iov_count == 0) {
+ if (conn->x.out.sg_count == 0) {
/* Done writing, switch to reading. */
- shutdown(conn->fd, 1);
+ shutdown(conn->fd, SHUTDOWN_WRITE);
FD_CLR(conn->fd, &selstate->wfds);
/* Q: How do we detect failures to send the remaining data
to the remote side, since we're in non-blocking mode?
Will we always get errors on the reading side? */
dfprintf((stderr, "switching fd %d to READING\n", conn->fd));
conn->state = READING;
- conn->bufsizebytes_read = 0;
+ conn->x.in.bufsizebytes_read = 0;
+ conn->x.in.bufsize = 0;
+ conn->x.in.buf = 0;
+ conn->x.in.pos = 0;
+ conn->x.in.n_left = 0;
}
return 0;
case READING:
- if (conn->bufsizebytes_read == 4) {
+ if (exception) {
+ if (conn->x.in.buf) {
+ free(conn->x.in.buf);
+ conn->x.in.buf = 0;
+ }
+ goto handle_exception;
+ }
+
+ if (conn->x.in.bufsizebytes_read == 4) {
/* Reading data. */
- dfprintf((stderr, "reading %ld bytes of data from fd %d\n",
- (long) conn->iov[0].iov_len, conn->fd));
- nread = read(conn->fd, conn->iov[0].iov_base,
- conn->iov[0].iov_len);
+ dfprintf((stderr, "reading %lu bytes of data from fd %d\n",
+ (unsigned long) conn->x.in.n_left, conn->fd));
+ nread = SOCKET_READ(conn->fd, conn->x.in.pos, conn->x.in.n_left);
if (nread <= 0) {
e = nread ? SOCKET_ERRNO : ECONNRESET;
- free(conn->buf);
- conn->buf = 0;
+ free(conn->x.in.buf);
+ conn->x.in.buf = 0;
goto kill_conn;
}
- iov_advance(conn->iov, nread);
- if (conn->iov[0].iov_len == 0) {
+ conn->x.in.n_left -= nread;
+ conn->x.in.pos += nread;
+ if (conn->x.in.n_left <= 0) {
/* We win! */
return 1;
}
} else {
/* Reading length. */
- nread = read(conn->fd,
- conn->bufsizebytes + conn->bufsizebytes_read,
- 4 - conn->bufsizebytes_read);
+ nread = SOCKET_READ(conn->fd,
+ conn->bufsizebytes + conn->x.in.bufsizebytes_read,
+ 4 - conn->x.in.bufsizebytes_read);
if (nread < 0) {
e = SOCKET_ERRNO;
goto kill_conn;
}
- conn->bufsizebytes_read += nread;
- if (conn->bufsizebytes_read == 4) {
+ conn->x.in.bufsizebytes_read += nread;
+ if (conn->x.in.bufsizebytes_read == 4) {
unsigned long len;
len = conn->bufsizebytes[0];
len = (len << 8) + conn->bufsizebytes[1];
e = E2BIG;
goto kill_conn;
}
- conn->bufsize = conn->iov[0].iov_len = len;
- conn->buf = conn->iov[0].iov_base = malloc(len);
+ conn->x.in.bufsize = conn->x.in.n_left = len;
+ conn->x.in.buf = conn->x.in.pos = malloc(len);
dfprintf((stderr, "allocated %lu byte buffer at %p\n",
- len, conn->buf));
- if (conn->iov[0].iov_base == 0) {
+ len, conn->x.in.buf));
+ if (conn->x.in.buf == 0) {
+ /* allocation failure */
e = errno;
goto kill_conn;
}
/* Got something on a socket, process it. */
for (i = 0; i <= selstate->max && selret > 0; i++) {
- int can_read, can_write;
+ int can_read, can_write, exception;
if (conns[i].fd == INVALID_SOCKET)
continue;
can_read = FD_ISSET(conns[i].fd, &sel_results.rfds);
can_write = FD_ISSET(conns[i].fd, &sel_results.wfds);
- if (!can_read && !can_write)
+ exception = FD_ISSET(conns[i].fd, &sel_results.xfds);
+ if (!can_read && !can_write && !exception)
continue;
selret--;
- if (service_tcp_fd(&conns[i], selstate, can_read, can_write)) {
+ if (service_tcp_fd(&conns[i], selstate, can_read, can_write, exception)) {
dfprintf((stderr, "service_tcp_fd says we're done\n"));
*winning_conn = i;
return 1;
if (conns == NULL) {
return ENOMEM;
}
+ memset(conns, 0, n_conns * sizeof(conns[i]));
for (i = 0; i < n_conns; i++) {
- memset(&conns[i], 0, sizeof(conns[i]));
conns[i].fd = INVALID_SOCKET;
}
select_state.nfds = 0;
FD_ZERO(&select_state.rfds);
FD_ZERO(&select_state.wfds);
+ FD_ZERO(&select_state.xfds);
message_len_buf[0] = (message->length >> 24) & 0xff;
message_len_buf[1] = (message->length >> 16) & 0xff;
continue;
/* Send to the host, wait timeout seconds for a response,
then move on. */
- if (start_tcp_connection (&conns[host], addrs->addrs[host]))
+ if (start_tcp_connection (&conns[host], addrs->addrs[host])) {
continue;
- conns[host].iov[0].iov_base = (char *) message_len_buf;
- conns[host].iov[0].iov_len = 4;
- conns[host].iov[1].iov_base = message->data;
- conns[host].iov[1].iov_len = message->length;
-
+ }
+ SG_SET(&conns[host].x.out.sgbuf[0], message_len_buf, 4);
+ SG_SET(&conns[host].x.out.sgbuf[1], message->data, message->length);
+
FD_SET(conns[host].fd, &select_state.rfds);
FD_SET(conns[host].fd, &select_state.wfds);
+ FD_SET(conns[host].fd, &select_state.xfds);
if (select_state.max <= conns[host].fd)
select_state.max = conns[host].fd + 1;
select_state.nfds++;
- if (gettimeofday(&now, 0)) {
- retval = errno;
- dperror("gettimeofday");
+ retval = getcurtime(&now);
+ if (retval)
goto egress;
- }
select_state.end_time = now;
select_state.end_time.tv_sec++;
e = service_fds(&select_state, conns, host+1, &winning_conn);
return KRB5_KDC_UNREACH;
}
if (e == 0) {
- if (gettimeofday(&now, 0)) {
- retval = errno;
- dperror("gettimeofday");
+ retval = getcurtime(&now);
+ if (retval)
goto egress;
- }
select_state.end_time = now;
select_state.end_time.tv_sec += 30;
e = service_fds(&select_state, conns, n_conns, &winning_conn);
goto egress;
}
/* Success! */
- reply->data = conns[winning_conn].buf;
- reply->length = conns[winning_conn].bufsize;
+ reply->data = conns[winning_conn].x.in.buf;
+ reply->length = conns[winning_conn].x.in.bufsize;
dfprintf((stderr, "returning %lu bytes in buffer %p\n",
(unsigned long) reply->length, reply->data));
retval = 0;
- conns[winning_conn].buf = 0;
+ conns[winning_conn].x.in.buf = 0;
egress:
for (i = 0; i < n_conns; i++) {
if (conns[i].fd != INVALID_SOCKET)
close(conns[i].fd);
- if (conns[i].buf != 0)
- free(conns[i].buf);
+ if (conns[i].state == READING && conns[i].x.in.buf != 0)
+ free(conns[i].x.in.buf);
}
free(conns);
return retval;
}
-#endif