+static int
+sk_fb_refill(struct fastbuf *f)
+{
+ struct conn *c = SKIP_BACK(struct conn, rx_fb, f);
+ int cnt = read(c->sk, f->buffer, f->bufend - f->buffer);
+ if (cnt < 0)
+ client_error("Read error: %m");
+ f->bptr = f->buffer;
+ f->bstop = f->buffer + cnt;
+ return cnt;
+}
+
+static void
+sk_fb_spout(struct fastbuf *f)
+{
+ struct conn *c = SKIP_BACK(struct conn, tx_fb, f);
+ int len = f->bptr - f->buffer;
+ if (!len)
+ return;
+ int cnt = careful_write(c->sk, f->buffer, len);
+ if (cnt <= 0)
+ client_error("Write error");
+ f->bptr = f->buffer;
+}
+
+static void
+init_sk_fastbufs(struct conn *c)
+{
+ struct fastbuf *rf = &c->rx_fb, *tf = &c->tx_fb;
+
+ rf->buffer = xmalloc(1024);
+ rf->bufend = rf->buffer + 1024;
+ rf->bptr = rf->bstop = rf->buffer;
+ rf->name = "socket";
+ rf->refill = sk_fb_refill;
+
+ tf->buffer = xmalloc(1024);
+ tf->bufend = tf->buffer + 1024;
+ tf->bptr = tf->bstop = tf->buffer;
+ tf->name = rf->name;
+ tf->spout = sk_fb_spout;
+}
+
+static int
+tls_fb_refill(struct fastbuf *f)
+{
+ struct conn *c = SKIP_BACK(struct conn, rx_fb, f);
+ DBG("TLS: Refill");
+ int cnt = gnutls_record_recv(c->tls, f->buffer, f->bufend - f->buffer);
+ DBG("TLS: Received %d bytes", cnt);
+ if (cnt < 0)
+ client_error("TLS read error: %s", gnutls_strerror(cnt));
+ f->bptr = f->buffer;
+ f->bstop = f->buffer + cnt;
+ return cnt;
+}
+
+static void
+tls_fb_spout(struct fastbuf *f)
+{
+ struct conn *c = SKIP_BACK(struct conn, tx_fb, f);
+ int len = f->bptr - f->buffer;
+ if (!len)
+ return;
+ int cnt = gnutls_record_send(c->tls, f->buffer, len);
+ DBG("TLS: Sent %d bytes", cnt);
+ if (cnt <= 0)
+ client_error("TLS write error: %s", gnutls_strerror(cnt));
+ f->bptr = f->buffer;
+}
+
+static void
+init_tls_fastbufs(struct conn *c)
+{
+ struct fastbuf *rf = &c->rx_fb, *tf = &c->tx_fb;
+
+ ASSERT(rf->buffer && tf->buffer); // Already set up for the plaintext connection
+ rf->refill = tls_fb_refill;
+ tf->spout = tls_fb_spout;
+}
+
+/*** CLIENT LOOP (runs in a child process) ***/
+
+static void
+sigalrm_handler(int sig UNUSED)
+{
+ // We do not try to do any gracious shutdown to avoid races
+ client_error("Timed out");
+}
+
+static void
+client_loop(struct conn *c)
+{
+ setproctitle("submitd: client %s", c->ip_string);
+ log_pid = c->id;
+ init_sk_fastbufs(c);
+
+ signal(SIGPIPE, SIG_IGN);
+ struct sigaction sa = {
+ .sa_handler = sigalrm_handler
+ };
+ if (sigaction(SIGALRM, &sa, NULL) < 0)
+ die("Cannot setup SIGALRM handler: %m");
+
+ if (c->rule->plain_text)
+ {
+ bputsn(&c->tx_fb, "+OK");
+ bflush(&c->tx_fb);
+ }
+ else
+ {
+ bputsn(&c->tx_fb, "+TLS");
+ bflush(&c->tx_fb);
+ c->tls = tls_new_session(c->sk);
+ int err = gnutls_handshake(c->tls);
+ if (err < 0)
+ client_error("TLS handshake failed: %s", gnutls_strerror(err));
+ tls_log_params(c);
+ const char *cert_err = tls_verify_cert(c);
+ if (cert_err)
+ client_error("TLS certificate failure: %s", cert_err);
+ init_tls_fastbufs(c);
+ }
+
+ alarm(session_timeout);
+ if (!process_init(c))
+ msg(L_ERROR, "Protocol handshake failed");
+ else
+ {
+ setproctitle("submitd: client %s (%s)", c->ip_string, c->user);
+ for (;;)
+ {
+ alarm(session_timeout);
+ if (!process_command(c))
+ break;
+ }
+ }
+
+ if (c->tls)
+ gnutls_bye(c->tls, GNUTLS_SHUT_WR);
+ close(c->sk);
+ if (c->tls)
+ gnutls_deinit(c->tls);
+}
+
+/*** MAIN LOOP ***/
+
+static void
+sigchld_handler(int sig UNUSED)
+{
+ /* We do not need to do anything, just interrupt the accept syscall */
+}
+
+static void
+reap_child(pid_t pid, int status)
+{
+ char buf[EXIT_STATUS_MSG_SIZE];
+ if (format_exit_status(buf, status))
+ msg(L_ERROR, "Child %d %s", (int)pid, buf);
+
+ CLIST_FOR_EACH(struct conn *, c, connections)
+ if (c->pid == pid)
+ {
+ msg(L_INFO, "Connection %d closed", c->id);
+ conn_free(c);
+ return;
+ }
+ msg(L_ERROR, "Cannot find connection for child process %d", (int)pid);
+}
+
+static int listen_sk;
+
+static void
+sk_init(void)
+{
+ listen_sk = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (listen_sk < 0)