/* Set conn->state to READING when done; otherwise, call a cm_set_. */ static krb5_boolean service_https_write(krb5_context context, const krb5_data *realm, struct conn_state *conn, struct select_state *selstate) { k5_tls_status st; /* If this is our first time in here, set up the SSL context. */ if (conn->http.tls == NULL && !setup_tls(context, realm, conn, selstate)) { kill_conn(context, conn, selstate); return FALSE; } /* Try to transmit our request to the server. */ st = context->tls->write(context, conn->http.tls, SG_BUF(conn->out.sgp), SG_LEN(conn->out.sgbuf)); if (st == DONE) { TRACE_SENDTO_KDC_HTTPS_SEND(context, &conn->addr); cm_read(selstate, conn->fd); conn->state = READING; } else if (st == WANT_READ) { cm_read(selstate, conn->fd); } else if (st == WANT_WRITE) { cm_write(selstate, conn->fd); } else if (st == ERROR_TLS) { TRACE_SENDTO_KDC_HTTPS_ERROR_SEND(context, &conn->addr); kill_conn(context, conn, selstate); } return FALSE; }
/* 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 maybe_send(krb5_context context, struct conn_state *conn, struct select_state *selstate, struct sendto_callback_info *callback_info) { sg_buf *sg; ssize_t ret; dprint("maybe_send(@%p) state=%s type=%s\n", conn, state_strings[conn->state], conn->is_udp ? "udp" : "tcp"); if (conn->state == INITIALIZING) return start_connection(context, conn, selstate, callback_info); /* Did we already shut down this channel? */ if (conn->state == FAILED) { dprint("connection already closed\n"); return -1; } if (conn->socktype == SOCK_STREAM) { 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 - retransmit after a previous attempt timed out. */ sg = &conn->x.out.sgbuf[0]; TRACE_SENDTO_KDC_UDP_SEND_RETRY(context, conn); dprint("sending %d bytes on fd %d\n", SG_LEN(sg), conn->fd); ret = send(conn->fd, SG_BUF(sg), SG_LEN(sg), 0); if (ret < 0 || (size_t) ret != SG_LEN(sg)) { TRACE_SENDTO_KDC_UDP_ERROR_SEND_RETRY(context, conn, SOCKET_ERRNO); 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; }
/* 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 maybe_send(krb5_context context, struct conn_state *conn, const krb5_data *message, struct select_state *selstate, const krb5_data *realm, struct sendto_callback_info *callback_info) { sg_buf *sg; ssize_t ret; if (conn->state == INITIALIZING) { return start_connection(context, conn, message, selstate, realm, callback_info); } /* Did we already shut down this channel? */ if (conn->state == FAILED) { return -1; } if (conn->addr.transport != UDP) { /* The select callback will handle flushing any data we haven't written yet, and we only write it once. */ return -1; } /* UDP - retransmit after a previous attempt timed out. */ sg = &conn->out.sgbuf[0]; TRACE_SENDTO_KDC_UDP_SEND_RETRY(context, &conn->addr); ret = send(conn->fd, SG_BUF(sg), SG_LEN(sg), 0); if (ret < 0 || (size_t) ret != SG_LEN(sg)) { TRACE_SENDTO_KDC_UDP_ERROR_SEND_RETRY(context, &conn->addr, SOCKET_ERRNO); /* 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 int start_connection(krb5_context context, struct conn_state *state, const krb5_data *message, struct select_state *selstate, const krb5_data *realm, struct sendto_callback_info *callback_info) { int fd, e, type; static const int one = 1; static const struct linger lopt = { 0, 0 }; type = socktype_for_transport(state->addr.transport); fd = socket(state->addr.family, type, 0); if (fd == INVALID_SOCKET) return -1; /* try other hosts */ set_cloexec_fd(fd); /* Make it non-blocking. */ ioctlsocket(fd, FIONBIO, (const void *) &one); if (state->addr.transport == TCP) { setsockopt(fd, SOL_SOCKET, SO_LINGER, &lopt, sizeof(lopt)); TRACE_SENDTO_KDC_TCP_CONNECT(context, &state->addr); } /* Start connecting to KDC. */ e = connect(fd, (struct sockaddr *)&state->addr.saddr, state->addr.len); if (e != 0) { /* * This is the path that should be followed for non-blocking * connections. */ if (SOCKET_ERRNO == EINPROGRESS || SOCKET_ERRNO == EWOULDBLOCK) { state->state = CONNECTING; state->fd = fd; } else { (void) closesocket(fd); state->state = FAILED; return -2; } } else { /* * Connect returned zero even though we made it non-blocking. This * happens normally for UDP sockets, and can perhaps also happen for * TCP sockets connecting to localhost. */ state->state = WRITING; state->fd = fd; } /* * Here's where KPASSWD callback gets the socket information it needs for * a kpasswd request */ if (callback_info) { e = callback_info->pfn_callback(state->fd, callback_info->data, &state->callback_buffer); if (e != 0) { (void) closesocket(fd); state->fd = INVALID_SOCKET; state->state = FAILED; return -3; } message = &state->callback_buffer; } e = set_transport_message(state, realm, message); if (e != 0) { TRACE_SENDTO_KDC_ERROR_SET_MESSAGE(context, &state->addr, e); (void) closesocket(state->fd); state->fd = INVALID_SOCKET; state->state = FAILED; return -4; } if (state->addr.transport == UDP) { /* Send it now. */ ssize_t ret; sg_buf *sg = &state->out.sgbuf[0]; TRACE_SENDTO_KDC_UDP_SEND_INITIAL(context, &state->addr); ret = send(state->fd, SG_BUF(sg), SG_LEN(sg), 0); if (ret < 0 || (size_t) ret != SG_LEN(sg)) { TRACE_SENDTO_KDC_UDP_ERROR_SEND_INITIAL(context, &state->addr, SOCKET_ERRNO); (void) closesocket(state->fd); state->fd = INVALID_SOCKET; state->state = FAILED; return -5; } else { state->state = READING; } } if (!cm_add_fd(selstate, state->fd)) { (void) closesocket(state->fd); state->fd = INVALID_SOCKET; state->state = FAILED; return -1; } if (state->state == CONNECTING || state->state == WRITING) cm_write(selstate, state->fd); else cm_read(selstate, state->fd); return 0; }
static int start_connection(krb5_context context, struct conn_state *state, struct select_state *selstate, struct sendto_callback_info *callback_info) { int fd, e; dprint("start_connection(@%p)\ngetting %s socket in family %d...", state, state->socktype == SOCK_STREAM ? "stream" : "dgram", state->family); fd = socket(state->family, state->socktype, 0); if (fd == INVALID_SOCKET) { state->err = SOCKET_ERRNO; dprint("socket: %m creating with af %d\n", state->err, state->family); return -1; /* try other hosts */ } #ifndef _WIN32 /* On Windows FD_SETSIZE is a count, not a max value. */ if (fd >= FD_SETSIZE) { closesocket(fd); state->err = EMFILE; dprint("socket: fd %d too high\n", fd); return -1; } #endif set_cloexec_fd(fd); /* Make it non-blocking. */ if (state->socktype == SOCK_STREAM) { static const int one = 1; static const struct linger lopt = { 0, 0 }; if (ioctlsocket(fd, FIONBIO, (const void *) &one)) dperror("sendto_kdc: ioctl(FIONBIO)"); if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &lopt, sizeof(lopt))) dperror("sendto_kdc: setsockopt(SO_LINGER)"); TRACE_SENDTO_KDC_TCP_CONNECT(context, state); } /* Start connecting to KDC. */ e = connect(fd, (struct sockaddr *)&state->addr, state->addrlen); if (e != 0) { /* * This is the path that should be followed for non-blocking * connections. */ if (SOCKET_ERRNO == EINPROGRESS || SOCKET_ERRNO == EWOULDBLOCK) { state->state = CONNECTING; state->fd = fd; } else { dprint("connect failed: %m\n", SOCKET_ERRNO); (void) closesocket(fd); state->err = SOCKET_ERRNO; state->state = FAILED; return -2; } } else { /* * Connect returned zero even though we made it non-blocking. This * happens normally for UDP sockets, and can perhaps also happen for * TCP sockets connecting to localhost. */ state->state = WRITING; state->fd = fd; } dprint("new state = %s\n", state_strings[state->state]); /* * Here's where KPASSWD callback gets the socket information it needs for * a kpasswd request */ if (callback_info) { e = callback_info->pfn_callback(state, callback_info->context, &state->callback_buffer); if (e != 0) { dprint("callback failed: %m\n", e); (void) closesocket(fd); state->err = e; state->fd = INVALID_SOCKET; state->state = FAILED; return -3; } set_conn_state_msg_length(state, &state->callback_buffer); } if (state->socktype == SOCK_DGRAM) { /* Send it now. */ ssize_t ret; sg_buf *sg = &state->x.out.sgbuf[0]; TRACE_SENDTO_KDC_UDP_SEND_INITIAL(context, state); dprint("sending %d bytes on fd %d\n", SG_LEN(sg), state->fd); ret = send(state->fd, SG_BUF(sg), SG_LEN(sg), 0); if (ret < 0 || (size_t) ret != SG_LEN(sg)) { TRACE_SENDTO_KDC_UDP_ERROR_SEND_INITIAL(context, state, SOCKET_ERRNO); dperror("sendto"); (void) closesocket(state->fd); state->fd = INVALID_SOCKET; state->state = FAILED; return -4; } else { state->state = READING; } } FD_SET(state->fd, &selstate->rfds); if (state->state == CONNECTING || state->state == WRITING) FD_SET(state->fd, &selstate->wfds); FD_SET(state->fd, &selstate->xfds); if (selstate->max <= state->fd) selstate->max = state->fd + 1; selstate->nfds++; dprint("new select vectors: %F\n", &selstate->rfds, &selstate->wfds, &selstate->xfds, selstate->max); return 0; }