/*
*/
/*
*
* Copyright 1990,1991,2001,2002,2004,2005,2007 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
* require a specific license from the United States Government.
* It is the responsibility of any person or organization contemplating
* export to obtain such a license before exporting.
*
* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
* distribute this software and its documentation for any purpose and
* without fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright notice and
* this permission notice appear in supporting documentation, and that
* the name of M.I.T. not be used in advertising or publicity pertaining
* to distribution of the software without specific, written prior
* permission. Furthermore if you modify this software you must label
* your software as modified software and not distribute it in such a
* fashion that it might be confused with the original M.I.T. software.
* M.I.T. makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
*
* Send packet to KDC for realm; wait for response, retransmitting
* as necessary.
*/
#include "fake-addrinfo.h"
#include "k5-int.h"
/* Solaris Kerberos */
#include <syslog.h>
#include <locale.h>
#ifdef HAVE_SYS_TIME_H
#else
#include <time.h>
#endif
#include "os-proto.h"
#ifdef _WIN32
#endif
#ifdef _AIX
#endif
#ifndef _WIN32
/* For FIONBIO. */
#ifdef HAVE_SYS_FILIO_H
#endif
#endif
/* Solaris Kerberos: moved to k5-int.h */
/* #define DEFAULT_UDP_PREF_LIMIT 1465 */
#ifdef DEBUG
int krb5int_debug_sendto_kdc = 0;
{
#if 0
return;
#else
/* stderr is unbuffered */
#endif
}
/*
* Solaris Kerberos: only including the debug stuff if DEBUG defined outside
* this file.
*/
/* Solaris kerberos: removed put() since it isn't needed. */
#if 0
{
}
#endif
{
/* Solaris kerberos: build the string which will be passed to syslog later */
}
#else
#endif
void
{
#ifdef DEBUG
/* Temporaries for variable arguments, etc. */
int err;
int i;
int maxfd;
const krb5_data *d;
const char *p;
#ifndef max
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif
/*
* Solaris kerberos: modified this function to create a string to pass to
* syslog()
*/
global_err_str[0] = NULL;
if (*fmt != '%') {
/* Possible optimization: Look for % and print all chars
up to it in one call. */
continue;
}
/* After this, always processing a '%' sequence. */
fmt++;
switch (*fmt) {
case 0:
default:
abort();
case 'E':
/* %E => krb5_error_code */
p = error_message(kerr);
putstr(p);
break;
case 'm':
/* %m => errno value (int) */
/* Like syslog's %m except the errno value is passed in
rather than the current value. */
p = NULL;
#ifdef HAVE_STRERROR_R
p = tmpbuf;
#endif
if (p == NULL)
putstr(p);
break;
case 'F':
/* %F => fd_set *, fd_set *, fd_set *, int */
for (i = 0; i < maxfd; i++) {
if (r || w || x) {
putf(" %d", i);
if (r)
putstr("r");
if (w)
putstr("w");
if (x)
putstr("x");
}
}
putstr(" ");
break;
case 's':
/* %s => char * */
putstr(p);
break;
case 't':
/* %t => struct timeval * */
if (tv) {
} else
putstr("never");
break;
case 'd':
/* %d => int */
break;
case 'p':
/* %p => pointer */
break;
case 'A':
/* %A => addrinfo */
else
NI_NUMERICHOST | NI_NUMERICSERV)) {
else
} else
break;
case 'D':
/* %D => krb5_data * */
/* Solaris Kerberos */
p = d->data;
putstr("0x");
for (i = 0; i < d->length; i++) {
putf("%.2x", *p++);
}
break;
}
}
/* Solaris kerberos: use syslog() for debug output */
#endif
}
static void
{
int i;
for (i = 0; i < a->naddrs; i++)
dprint("}");
}
static int
{
/* Wouldn't it be nice if we could filter out duplicates? The
int err, i;
/* Solaris Kerberos */
#ifdef DEBUG
/*LINTED*/
dprint("merging addrlists:\n\tlist1: ");
/*LINTED*/
/*LINTED*/
dprint("\n\tlist2: ");
/*LINTED*/
/*LINTED*/
dprint("\n");
#endif
if (err)
return err;
}
/* Solaris Kerberos */
#ifdef DEBUG
/*LINTED*/
dprint("\tout: ");
/*LINTED*/
/*LINTED*/
dprint("\n");
#endif
return 0;
}
static int
{
int i;
return 1;
}
return 0;
}
static int
void *msg_handler_data)
{
*retval = 0;
if (krb5_is_krb_error(reply)) {
/* Returning 0 means continue to next KDC */
return (*retval != KDC_ERR_SVC_UNAVAILABLE);
}
}
return 1;
}
/*
* send the formatted request 'message' to a KDC for realm 'realm' and
* return the response (if any) in 'reply'.
*
* If the message is sent and a response is received, 0 is returned,
* otherwise an error code is returned.
*
* The storage for 'reply' is allocated and should be freed by the caller
* when finished.
*/
int *use_master, int tcp_only)
{
}
/*
* Solaris Kerberos
* Same as krb5_sendto_kdc plus an extra arg to return the FQDN
* of the KDC sent the request.
* Caller (at top of stack) needs to free hostname_used.
*/
{
/*
* find KDC location(s) for realm
*/
/*
* BUG: This code won't return "interesting" errors (e.g., out of mem,
* bad config file) from locate_kdc. KRB5_REALM_CANT_RESOLVE can be
* ignored from one query of two, but if only one query is done, or
* both return that error, it should be returned to the caller. Also,
* "interesting" errors (not KRB5_KDC_UNREACH) from sendto_{udp,tcp}
* should probably be returned as well.
*/
/*LINTED*/
dprint("krb5_sendto_kdc(%d@%p, \"%D\", use_master=%d, tcp_only=%d)\n",
/*LINTED*/
int tmp;
"libdefaults", "udp_preference_limit", 0,
if (retval)
return retval;
if (tmp < 0)
else if (tmp > HARD_UDP_LIMIT)
/* In the unlikely case that a *really* big value is
given, let 'em use as big as we think we can
support. */
}
if (tcp_only)
else
if (socktype2) {
socktype2, 0);
#if 0
if (retval2 == 0) {
retval = 0;
} else if (retval == KRB5_REALM_CANT_RESOLVE) {
}
#else
if (retval == 0) {
}
#endif
}
switch (retval) {
case 0:
/*
* Set use_master to 1 if we ended up talking to a master when
* we didn't explicitly request to
*/
if (*use_master == 0) {
if (retval == 0) {
*use_master = 1;
}
}
if (hostname_used) {
int err;
*hostname_used = NULL;
if (err)
sizeof (buf), 0, 0,
if (!err)
/* don't sweat strdup fail */
}
return 0;
default:
break;
/* Cases here are for constructing useful error messages. */
case KRB5_KDC_UNREACH:
if (err == KDC_ERR_SVC_UNAVAILABLE) {
} else {
"Cannot contact any KDC for realm '%.*s'"),
}
break;
}
}
return retval;
}
#ifdef DEBUG
#ifdef _WIN32
dprint("%s: an error occurred ... " \
"\tline=%d errno=%m socketerrno=%m\n", \
#else
#endif
#else /* ! DEBUG */
#endif
/*
* 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:
* - error codes that don't suck
* - getsockopt(SO_ERROR) to check connect status
* - handle error RESPONSE_TOO_BIG from UDP server and use TCP
* connections already in progress
*/
#include "cm.h"
{
#ifdef _WIN32
/* 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
* Output: select return value (-1 or num fds ready) and fd_sets
* Return: 0 (for i/o available or timeout) or error code.
*/
{
e = getcurtime(&now);
if (e)
return e;
timo = 0;
else {
}
*sret = 0;
return 0;
}
}
/*LINTED*/
dprint("selecting on max=%d sockets [%F] timeout %t\n",
/*LINTED*/
timo);
e = SOCKET_ERRNO;
/* Solaris Kerberos */
#ifdef DEBUG
/*LINTED*/
if (*sret < 0)
/*LINTED*/
dprint(", error = %E\n", e);
else if (*sret == 0)
/*LINTED*/
dprint(" (timeout)\n");
else
/*LINTED*/
#endif
if (*sret < 0)
return e;
return 0;
}
static void
{
return;
} else {
}
}
static int
{
/*
SG_SET(&state->x.out.sgbuf[0], message_len_buf, 4);
SG_SET(&state->x.out.sgbuf[1], message->data, message->length);
state->x.out.sg_count = 2;
*/
} else {
/*
SG_SET(&state->x.out.sgbuf[0], message->data, message->length);
SG_SET(&state->x.out.sgbuf[1], 0, 0);
state->x.out.sg_count = 1;
*/
if (*udpbufp == 0) {
if (*udpbufp == 0) {
dperror("malloc(krb5_max_dgram_size)");
return 1;
}
}
}
return 0;
}
static int
struct select_state *selstate,
struct sendto_callback_info* callback_info,
{
int fd, e;
/*LINTED*/
/*LINTED*/
if (fd == INVALID_SOCKET) {
/*LINTED*/
return -1; /* try other hosts */
}
/* Make it non-blocking. */
dperror("sendto_kdc: ioctl(FIONBIO)");
dperror("sendto_kdc: setsockopt(SO_LINGER)");
}
/* Start connecting to KDC. */
/*LINTED*/
if (e != 0) {
/*
* This is the path that should be followed for non-blocking
* connections.
*/
} else {
/*LINTED*/
(void) closesocket(fd);
return -2;
}
} else {
/*
* Connect returned zero even though we tried to make it
* non-blocking, which should have caused it to return before
* finishing the connection. Oh well. Someone's network
* stack is broken, but if they gave us a connection, use it.
*/
}
/*LINTED*/
/*
* Here's where KPASSWD callback gets the socket information it needs for
* a kpasswd request
*/
if (callback_info) {
if (e != 0) {
dprint("callback failed: %m\n", e);
(void) closesocket(fd);
return -3;
}
dprint("callback %p (message=%d@%p)\n",
}
/* Send it now. */
int ret;
/*LINTED*/
dperror("sendto");
return -4;
} else {
}
}
#ifdef DEBUG
if (debug) {
}
}
#endif
/*LINTED*/
dprint("new select vectors: %F\n",
/*LINTED*/
return 0;
}
/* Return 0 if we sent something, non-0 otherwise.
If 0 is returned, the caller should delay waiting for a response.
Otherwise, the caller should immediately move on to process the
next connection. */
static int
struct select_state *selstate,
struct sendto_callback_info* callback_info,
{
/*LINTED*/
/*LINTED*/
/* Did we already shut down this channel? */
dprint("connection already closed\n");
return -1;
}
dprint("skipping stream socket\n");
/* The select callback will handle flushing any data we
haven't written yet, and we only write it once. */
return -1;
}
/* UDP - Send message, possibly for the first time, possibly a
retransmit if a previous attempt timed out. */
/*LINTED*/
dperror("send");
/* Keep connection alive, we'll try again next pass.
Is this likely to catch any errors we didn't get from the
select callbacks? */
return -1;
}
/* Yay, it worked. */
return 0;
}
static void
{
/*LINTED*/
/* Fix up max fd for next select call. */
/*LINTED*/
}
}
/* Check socket for error. */
static int
{
int e, sockerr;
sockerr = 0;
sockerrlen = sizeof(sockerr);
if (e != 0) {
/* What to do now? */
e = SOCKET_ERRNO;
dprint("getsockopt(SO_ERROR) on fd failed: %m\n", e);
return e;
}
return sockerr;
}
/* 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
int ssflags)
{
krb5_error_code e = 0;
abort();
case CONNECTING:
/* Bad -- the KDC shouldn't be sending to us first. */
e = EINVAL /* ?? */;
if (e == EINVAL) {
}
return e == 0;
}
if (ssflags & SSF_EXCEPTION) {
if (e)
dprint("socket error on exception fd: %m", e);
else
dprint("no socket error info available on exception fd");
goto kill_conn;
}
/*
* Connect finished -- but did it succeed or fail?
* UNIX sets can_write if failed.
* Call getsockopt to see if error pending.
*
* (For most UNIX systems it works to just try writing the
* first time and detect an error. But Bill Dodd at IBM
* reports that some version of AIX, SIGPIPE can result.)
*/
if (e) {
dprint("socket error on write fd: %m", e);
goto kill_conn;
}
goto try_writing;
case WRITING:
e = E2BIG;
/* Bad -- the KDC shouldn't be sending anything yet. */
goto kill_conn;
}
if (ssflags & SSF_EXCEPTION)
goto handle_exception;
/*LINTED*/
dprint("trying to writev %d (%d bytes) to fd %d\n",
/*LINTED*/
/*LINTED*/
if (nwritten < 0) {
e = SOCKET_ERRNO;
/*LINTED*/
dprint("failed: %m\n", e);
goto kill_conn;
}
/*LINTED*/
while (nwritten) {
/*LINTED*/
nwritten = 0;
} else {
/* Wrote more than we wanted to? */
abort();
}
}
/* Done writing, switch to reading. */
/* Don't call shutdown at this point because
* some implementations cannot deal with half-closed connections.*/
/* 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? */
/*LINTED*/
}
return 0;
case READING:
if (ssflags & SSF_EXCEPTION) {
}
goto handle_exception;
}
/* Reading data. */
/*LINTED*/
dprint("reading %d bytes of data from fd %d\n",
if (nread <= 0) {
goto kill_conn;
}
/* Solaris Kerberos */
/* We win! */
return 1;
}
} else {
/* Reading length. */
if (nread < 0) {
e = SOCKET_ERRNO;
goto kill_conn;
}
unsigned long len;
/*LINTED*/
/* Arbitrary 1M cap. */
e = E2BIG;
goto kill_conn;
}
/*LINTED*/
/* allocation failure */
e = errno;
goto kill_conn;
}
}
}
break;
default:
abort();
}
return 0;
}
static int
int ssflags)
{
int nread;
abort();
abort();
if (nread < 0) {
return 0;
}
return 1;
}
static int
struct select_state *selstate,
struct select_state *seltemp,
void *msg_handler_data)
{
int e, selret;
e = 0;
int i;
/*LINTED*/
if (selret == 0)
/* Timeout, return to caller. */
return 0;
/* Got something on a socket, process it. */
int ssflags;
continue;
ssflags = 0;
if (!ssflags)
continue;
/*LINTED*/
dprint("handling flags '%s%s%s' on fd %d (%A) in state %s\n",
/*LINTED*/
/*LINTED*/
/*LINTED*/
/*LINTED*/
if (msg_handler != NULL) {
}
if (stop) {
dprint("fd service routine says we're done\n");
*winning_conn = i;
return 1;
}
}
}
}
if (e != 0) {
/*LINTED*/
dprint("select returned %m\n", e);
*winning_conn = -1;
return 1;
}
return 0;
}
/*
* Current worst-case timeout behavior:
*
* First pass, 1s per udp or tcp server, plus 2s at end.
* Second pass, 1s per udp server, plus 4s.
* Third pass, 1s per udp server, plus 8s.
* Fourth => 16s, etc.
*
* Restated:
* Per UDP server, 1s per pass.
* Per TCP server, 1s.
* Backoff delay, 2**(P+1) - 2, where P is total number of passes.
*
* Total = 2**(P+1) + U*P + T - 2.
*
* If P=3, Total = 3*U + T + 14.
* If P=4, Total = 4*U + T + 30.
*
* Note that if you try to reach two ports (e.g., both 88 and 750) on
* one server, it counts as two.
*/
/*ARGSUSED*/
int *addr_used,
/* return 0 -> keep going, 1 -> quit */
void *msg_handler_data)
{
int i, pass;
char *udpbuf = 0;
if (message)
else
dprint(")\n");
return ENOMEM;
}
if (callback_info) {
if (callback_data == NULL) {
return ENOMEM;
}
}
for (i = 0; i < n_conns; i++) {
}
/* One for use here, listing all our fds in use, and one for
temporary use in service_fds, for the fds of interest. */
return ENOMEM;
}
/* Set up connections. */
&udpbuf);
if (retval)
continue;
}
/* Possible optimization: Make only one pass if TCP only.
Stop making passes if all UDP ports are closed down. */
/*LINTED*/
/*LINTED*/
/* Send to the host, wait for a response, then move on. */
continue;
if (retval)
goto egress;
if (e)
break;
/*
* After the first pass, if we close all fds, break
* out right away. During the first pass, it's okay,
* we're probably about to open another connection.
*/
break;
}
if (e)
break;
if (retval)
goto egress;
/* Possible optimization: Find a way to integrate this select
call with the last one from the above loop, if the loop
actually calls select. */
if (e)
break;
break;
delay_this_pass *= 2;
}
/* No addresses? */
goto egress;
}
if (e == 0 || winning_conn < 0) {
goto egress;
}
/* Success! */
/*LINTED*/
dprint("returning %d bytes in buffer %p\n",
retval = 0;
if (addr_used)
for (i = 0; i < n_conns; i++) {
if (callback_info) {
}
}
if (callback_data)
return retval;
}