static void kill_conn(krb5_context context, struct conn_state *conn, struct select_state *selstate) { free_http_tls_data(context, conn); if (socktype_for_transport(conn->addr.transport) == SOCK_STREAM) TRACE_SENDTO_KDC_TCP_DISCONNECT(context, &conn->addr); cm_remove_fd(selstate, conn->fd); closesocket(conn->fd); conn->fd = INVALID_SOCKET; conn->state = FAILED; }
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; }
static int service_tcp_fd(krb5_context context, struct conn_state *conn, struct select_state *selstate, int ssflags) { int e = 0; ssize_t nwritten, nread; if (!(ssflags & (SSF_READ|SSF_WRITE|SSF_EXCEPTION))) abort(); switch (conn->state) { SOCKET_WRITEV_TEMP tmp; case CONNECTING: if (ssflags & SSF_READ) { /* Bad -- the KDC shouldn't be sending to us first. */ e = EINVAL /* ?? */; kill_conn: TRACE_SENDTO_KDC_TCP_DISCONNECT(context, conn); kill_conn(conn, selstate, e); if (e == EINVAL) { closesocket(conn->fd); conn->fd = INVALID_SOCKET; } return e == 0; } if (ssflags & SSF_EXCEPTION) { handle_exception: e = get_so_error(conn->fd); 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.) */ e = get_so_error(conn->fd); if (e) { TRACE_SENDTO_KDC_TCP_ERROR_CONNECT(context, conn, e); dprint("socket error on write fd: %m", e); goto kill_conn; } conn->state = WRITING; goto try_writing; case WRITING: if (ssflags & SSF_READ) { e = E2BIG; /* Bad -- the KDC shouldn't be sending anything yet. */ goto kill_conn; } if (ssflags & SSF_EXCEPTION) goto handle_exception; try_writing: dprint("trying to writev %d (%d bytes) to fd %d\n", 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); TRACE_SENDTO_KDC_TCP_SEND(context, conn); nwritten = SOCKET_WRITEV(conn->fd, conn->x.out.sgp, conn->x.out.sg_count, tmp); if (nwritten < 0) { e = SOCKET_ERRNO; TRACE_SENDTO_KDC_TCP_ERROR_SEND(context, conn, e); dprint("failed: %m\n", e); goto kill_conn; } dprint("wrote %d bytes\n", nwritten); while (nwritten) { sg_buf *sgp = conn->x.out.sgp; if ((size_t) nwritten < SG_LEN(sgp)) { SG_ADVANCE(sgp, (size_t) nwritten); nwritten = 0; } else { nwritten -= SG_LEN(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->x.out.sg_count == 0) { /* Done writing, switch to reading. */ /* Don't call shutdown at this point because * some implementations cannot deal with half-closed connections.*/ 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? */ dprint("switching fd %d to READING\n", conn->fd); conn->state = READING; 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 (ssflags & SSF_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. */ dprint("reading %d bytes of data from fd %d\n", (int) 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->x.in.buf); conn->x.in.buf = 0; TRACE_SENDTO_KDC_TCP_ERROR_RECV(context, conn, e); goto kill_conn; } 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 = SOCKET_READ(conn->fd, conn->x.in.bufsizebytes + conn->x.in.bufsizebytes_read, 4 - conn->x.in.bufsizebytes_read); if (nread < 0) { TRACE_SENDTO_KDC_TCP_ERROR_RECV_LEN(context, conn, e); e = SOCKET_ERRNO; goto kill_conn; } conn->x.in.bufsizebytes_read += nread; if (conn->x.in.bufsizebytes_read == 4) { unsigned long len = load_32_be (conn->x.in.bufsizebytes); dprint("received length on fd %d is %d\n", conn->fd, (int)len); /* Arbitrary 1M cap. */ if (len > 1 * 1024 * 1024) { e = E2BIG; goto kill_conn; } conn->x.in.bufsize = conn->x.in.n_left = len; conn->x.in.buf = conn->x.in.pos = malloc(len); dprint("allocated %d byte buffer at %p\n", (int) len, conn->x.in.buf); if (conn->x.in.buf == 0) { /* allocation failure */ e = ENOMEM; goto kill_conn; } } } break; default: abort(); } return 0; }