krb5_error_code k5_sendto(krb5_context context, const krb5_data *message, const krb5_data *realm, const struct serverlist *servers, k5_transport_strategy strategy, struct sendto_callback_info* callback_info, krb5_data *reply, struct sockaddr *remoteaddr, socklen_t *remoteaddrlen, int *server_used, /* return 0 -> keep going, 1 -> quit */ int (*msg_handler)(krb5_context, const krb5_data *, void *), void *msg_handler_data) { int pass; time_ms delay; krb5_error_code retval; struct conn_state *conns = NULL, *state, **tailptr, *next, *winner; size_t s; struct select_state *sel_state = NULL, *seltemp; char *udpbuf = NULL; krb5_boolean done = FALSE; *reply = empty_data(); /* One for use here, listing all our fds in use, and one for * temporary use in service_fds, for the fds of interest. */ sel_state = malloc(2 * sizeof(*sel_state)); if (sel_state == NULL) { retval = ENOMEM; goto cleanup; } seltemp = &sel_state[1]; cm_init_selstate(sel_state); /* First pass: resolve server hosts, communicate with resulting addresses * of the preferred transport, and wait 1s for an answer from each. */ for (s = 0; s < servers->nservers && !done; s++) { /* Find the current tail pointer. */ for (tailptr = &conns; *tailptr != NULL; tailptr = &(*tailptr)->next); retval = resolve_server(context, realm, servers, s, strategy, message, &udpbuf, &conns); if (retval) goto cleanup; for (state = *tailptr; state != NULL && !done; state = state->next) { /* Contact each new connection, deferring those which use the * non-preferred RFC 4120 transport. */ if (state->defer) continue; if (maybe_send(context, state, message, sel_state, realm, callback_info)) continue; done = service_fds(context, sel_state, 1000, conns, seltemp, realm, msg_handler, msg_handler_data, &winner); } } /* Complete the first pass by contacting servers of the non-preferred RFC * 4120 transport (if given), waiting 1s for an answer from each. */ for (state = conns; state != NULL && !done; state = state->next) { if (!state->defer) continue; if (maybe_send(context, state, message, sel_state, realm, callback_info)) continue; done = service_fds(context, sel_state, 1000, conns, seltemp, realm, msg_handler, msg_handler_data, &winner); } /* Wait for two seconds at the end of the first pass. */ if (!done) { done = service_fds(context, sel_state, 2000, conns, seltemp, realm, msg_handler, msg_handler_data, &winner); } /* Make remaining passes over all of the connections. */ delay = 4000; for (pass = 1; pass < MAX_PASS && !done; pass++) { for (state = conns; state != NULL && !done; state = state->next) { if (maybe_send(context, state, message, sel_state, realm, callback_info)) continue; done = service_fds(context, sel_state, 1000, conns, seltemp, realm, msg_handler, msg_handler_data, &winner); if (sel_state->nfds == 0) break; } /* Wait for the delay backoff at the end of this pass. */ if (!done) { done = service_fds(context, sel_state, delay, conns, seltemp, realm, msg_handler, msg_handler_data, &winner); } if (sel_state->nfds == 0) break; delay *= 2; } if (sel_state->nfds == 0 || !done || winner == NULL) { retval = KRB5_KDC_UNREACH; goto cleanup; } /* Success! */ *reply = make_data(winner->in.buf, winner->in.pos); retval = 0; winner->in.buf = NULL; if (server_used != NULL) *server_used = winner->server_index; if (remoteaddr != NULL && remoteaddrlen != 0 && *remoteaddrlen > 0) (void)getpeername(winner->fd, remoteaddr, remoteaddrlen); TRACE_SENDTO_KDC_RESPONSE(context, reply->length, &winner->addr); cleanup: for (state = conns; state != NULL; state = next) { next = state->next; if (state->fd != INVALID_SOCKET) { if (socktype_for_transport(state->addr.transport) == SOCK_STREAM) TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &state->addr); closesocket(state->fd); free_http_tls_data(context, state); } if (state->state == READING && state->in.buf != udpbuf) free(state->in.buf); if (callback_info) { callback_info->pfn_cleanup(callback_info->data, &state->callback_buffer); } free(state); } if (reply->data != udpbuf) free(udpbuf); free(sel_state); return retval; }
krb5_error_code k5_sendto(krb5_context context, const krb5_data *message, const struct serverlist *servers, int socktype1, int socktype2, struct sendto_callback_info* callback_info, krb5_data *reply, struct sockaddr *remoteaddr, socklen_t *remoteaddrlen, int *server_used, /* return 0 -> keep going, 1 -> quit */ int (*msg_handler)(krb5_context, const krb5_data *, void *), void *msg_handler_data) { int pass, delay; krb5_error_code retval; struct conn_state *conns = NULL, *state, **tailptr, *next, *winner; size_t s; struct select_state *sel_state = NULL, *seltemp; char *udpbuf = NULL; krb5_boolean done = FALSE; reply->data = 0; reply->length = 0; /* One for use here, listing all our fds in use, and one for * temporary use in service_fds, for the fds of interest. */ sel_state = malloc(2 * sizeof(*sel_state)); if (sel_state == NULL) { retval = ENOMEM; goto cleanup; } seltemp = &sel_state[1]; sel_state->max = 0; sel_state->nfds = 0; sel_state->end_time.tv_sec = sel_state->end_time.tv_usec = 0; FD_ZERO(&sel_state->rfds); FD_ZERO(&sel_state->wfds); FD_ZERO(&sel_state->xfds); /* First pass: resolve server hosts, communicate with resulting addresses * of the preferred socktype, and wait 1s for an answer from each. */ for (s = 0; s < servers->nservers && !done; s++) { /* Find the current tail pointer. */ for (tailptr = &conns; *tailptr != NULL; tailptr = &(*tailptr)->next); retval = resolve_server(context, servers, s, socktype1, socktype2, message, &udpbuf, &conns); if (retval) goto cleanup; for (state = *tailptr; state != NULL && !done; state = state->next) { /* Contact each new connection whose socktype matches socktype1. */ if (state->socktype != socktype1) continue; if (maybe_send(context, state, sel_state, callback_info)) continue; done = service_fds(context, sel_state, 1, conns, seltemp, msg_handler, msg_handler_data, &winner); } } /* Complete the first pass by contacting servers of the non-preferred * socktype (if given), waiting 1s for an answer from each. */ for (state = conns; state != NULL && !done; state = state->next) { if (state->socktype != socktype2) continue; if (maybe_send(context, state, sel_state, callback_info)) continue; done = service_fds(context, sel_state, 1, state, seltemp, msg_handler, msg_handler_data, &winner); } /* Wait for two seconds at the end of the first pass. */ if (!done) { done = service_fds(context, sel_state, 2, conns, seltemp, msg_handler, msg_handler_data, &winner); } /* Make remaining passes over all of the connections. */ delay = 4; for (pass = 1; pass < MAX_PASS && !done; pass++) { for (state = conns; state != NULL && !done; state = state->next) { if (maybe_send(context, state, sel_state, callback_info)) continue; done = service_fds(context, sel_state, 1, conns, seltemp, msg_handler, msg_handler_data, &winner); if (sel_state->nfds == 0) break; } /* Wait for the delay backoff at the end of this pass. */ if (!done) { done = service_fds(context, sel_state, delay, conns, seltemp, msg_handler, msg_handler_data, &winner); } if (sel_state->nfds == 0) break; delay *= 2; } if (sel_state->nfds == 0 || !done || winner == NULL) { retval = KRB5_KDC_UNREACH; goto cleanup; } /* Success! */ TRACE_SENDTO_KDC_RESPONSE(context, winner); reply->data = winner->x.in.buf; reply->length = winner->x.in.pos - winner->x.in.buf; retval = 0; winner->x.in.buf = NULL; if (server_used != NULL) *server_used = winner->server_index; if (remoteaddr != NULL && remoteaddrlen != 0 && *remoteaddrlen > 0) (void)getpeername(winner->fd, remoteaddr, remoteaddrlen); cleanup: for (state = conns; state != NULL; state = next) { next = state->next; if (state->fd != INVALID_SOCKET) closesocket(state->fd); if (state->state == READING && state->x.in.buf != udpbuf) free(state->x.in.buf); if (callback_info) { callback_info->pfn_cleanup(callback_info->context, &state->callback_buffer); } free(state); } if (reply->data != udpbuf) free(udpbuf); free(sel_state); return retval; }