From 24d1860868682d33f60970119b1cff1bf088a497 Mon Sep 17 00:00:00 2001 From: Laurent Bercot Date: Sat, 11 Nov 2023 23:55:28 +0000 Subject: New and fixed version of sbearssl_run Signed-off-by: Laurent Bercot --- doc/s6-tlsc-io.html | 7 +- doc/s6-tlsc.html | 7 +- doc/s6-tlsd-io.html | 7 +- doc/s6-tlsd.html | 7 +- doc/upgrade.html | 2 + src/include/s6-networking/sbearssl.h | 2 +- src/sbearssl/sbearssl_client_init_and_run.c | 9 +- src/sbearssl/sbearssl_run.c | 258 ++++++++++++++-------------- src/sbearssl/sbearssl_server_init_and_run.c | 9 +- src/stls/stls_run.c | 6 +- src/tls/s6-tlsc-io.c | 6 +- src/tls/s6-tlsd-io.c | 6 +- 12 files changed, 162 insertions(+), 164 deletions(-) diff --git a/doc/s6-tlsc-io.html b/doc/s6-tlsc-io.html index b4bb154..f4a81a2 100644 --- a/doc/s6-tlsc-io.html +++ b/doc/s6-tlsc-io.html @@ -195,10 +195,9 @@ connection without using close_notify. This is the default.
  • -k servername : use Server Name Indication, and send servername. The default is not to use SNI, which may be a security risk.
  • -
  • -K kimeout : if the peer fails -to send data for kimeout milliseconds during the handshake, -close the connection. The default is 0, which means infinite timeout -(never kill the connection).
  • +
  • -K kimeout : if the handshake takes +more than kimeout milliseconds to complete, close the connection. +The default is 0, which means infinite timeout (never kill the connection).
  • -d notif : handshake notification. notif must be a file descriptor open for writing. When the TLS handshake has completed, some data (terminated by two null diff --git a/doc/s6-tlsc.html b/doc/s6-tlsc.html index 95cc44f..1d11c5b 100644 --- a/doc/s6-tlsc.html +++ b/doc/s6-tlsc.html @@ -131,10 +131,9 @@ connection without using close_notify. This is the default.
  • -k servername : use Server Name Indication, and send servername. The default is not to use SNI, which may be a security risk.
  • -
  • -K kimeout : if the peer fails -to send data for kimeout milliseconds during the handshake, -close the connection. The default is 0, which means infinite timeout -(never kill the connection).
  • +
  • -K kimeout : if the handshake takes +more than kimeout milliseconds to complete, close the connection. +The default is 0, which means infinite timeout (never kill the connection).
  • -6 fdr : expect an open file descriptor numbered fdr to read network (ciphertext) data from. Make sure prog also reads its data diff --git a/doc/s6-tlsd-io.html b/doc/s6-tlsd-io.html index 600690c..0f3b922 100644 --- a/doc/s6-tlsd-io.html +++ b/doc/s6-tlsd-io.html @@ -218,10 +218,9 @@ The certificate is mandatory: if the client gives none, the handshake fails. The default, with neither the -Y nor the -y option, is not to request a client certificate at all.
  • -
  • -K kimeout : if the peer fails -to send data for kimeout milliseconds during the handshake, -close the connection. The default is 0, which means infinite timeout -(never kill the connection).
  • +
  • -K kimeout : if the handshake takes +more than kimeout milliseconds to complete, close the connection. +The default is 0, which means infinite timeout (never kill the connection).
  • -k snilevel : support alternative certificate chains for SNI. If snilevel is nonzero, private key file names are read from every environment variable of the form diff --git a/doc/s6-tlsd.html b/doc/s6-tlsd.html index 658c0b9..883777b 100644 --- a/doc/s6-tlsd.html +++ b/doc/s6-tlsd.html @@ -143,10 +143,9 @@ connection without using close_notify. This is the default.
  • -y : Request a mandatory client certificate. The default, with neither the -Y nor the -y option, is not to request a client certificate at all.
  • -
  • -K kimeout : if the peer fails -to send data for kimeout milliseconds during the handshake, -close the connection. The default is 0, which means infinite timeout -(never kill the connection).
  • +
  • -K kimeout : if the handshake takes +more than kimeout milliseconds to complete, close the connection. +The default is 0, which means infinite timeout (never kill the connection).
  • -k snilevel : support alternative certificate chains for SNI. If snilevel is nonzero, private key file names are read from every environment variable of the form diff --git a/doc/upgrade.html b/doc/upgrade.html index 292030e..8ae492b 100644 --- a/doc/upgrade.html +++ b/doc/upgrade.html @@ -28,6 +28,8 @@ side. This allows users to invoke it directly when it is relevant.
  • Consequently, s6-tlsc and s6-ucspitlsc have changed how they invoke s6-tlsc-io.
  • +
  • The -K option to TLS programs has slightly changed semantics: +it now indicates a timeout for the whole handshake.
  • in 2.6.0.0

    diff --git a/src/include/s6-networking/sbearssl.h b/src/include/s6-networking/sbearssl.h index 6d68e1d..e8a4649 100644 --- a/src/include/s6-networking/sbearssl.h +++ b/src/include/s6-networking/sbearssl.h @@ -270,7 +270,7 @@ typedef int sbearssl_handshake_cbfunc (br_ssl_engine_context *, sbearssl_handsha typedef sbearssl_handshake_cbfunc *sbearssl_handshake_cbfunc_ref ; extern int sbearssl_send_environment (br_ssl_engine_context *, sbearssl_handshake_cbarg *) ; -extern void sbearssl_run (br_ssl_engine_context *, int *, tain const *, uint32_t, unsigned int, sbearssl_handshake_cbfunc_ref, sbearssl_handshake_cbarg *) gccattr_noreturn ; +extern void sbearssl_run (br_ssl_engine_context *, int const *, tain const *, uint32_t, unsigned int, sbearssl_handshake_cbfunc_ref, sbearssl_handshake_cbarg *) gccattr_noreturn ; /* Generic server policy class and server-side SNI implementation */ diff --git a/src/sbearssl/sbearssl_client_init_and_run.c b/src/sbearssl/sbearssl_client_init_and_run.c index 18ce86c..82c3ff3 100644 --- a/src/sbearssl/sbearssl_client_init_and_run.c +++ b/src/sbearssl/sbearssl_client_init_and_run.c @@ -29,9 +29,10 @@ void sbearssl_client_init_and_run (int *fds, tain const *tto, uint32_t preoption union br_skey_u key ; br_ssl_client_context cc ; sbearssl_x509_small_context xc ; + unsigned char bufi[BR_SSL_BUFSIZE_INPUT] ; + unsigned char bufo[BR_SSL_BUFSIZE_OUTPUT] ; br_x509_certificate chain[chainlen ? chainlen : 1] ; br_x509_trust_anchor btas[n] ; - unsigned char buf[BR_SSL_BUFSIZE_BIDI] ; for (size_t i = 0 ; i < chainlen ; i++) sbearssl_cert_to(genalloc_s(sbearssl_cert, &certs) + i, chain + i, storage.s) ; @@ -79,9 +80,9 @@ void sbearssl_client_init_and_run (int *fds, tain const *tto, uint32_t preoption } br_ssl_engine_add_flags(&cc.eng, BR_OPT_NO_RENEGOTIATION) ; - random_buf((char *)buf, 32) ; - br_ssl_engine_inject_entropy(&cc.eng, buf, 32) ; - br_ssl_engine_set_buffer(&cc.eng, buf, sizeof(buf), 1) ; + random_buf((char *)bufi, 32) ; + br_ssl_engine_inject_entropy(&cc.eng, bufi, 32) ; + br_ssl_engine_set_buffers_bidi(&cc.eng, bufi, sizeof(bufi), bufo, sizeof(bufo)) ; if (!br_ssl_client_reset(&cc, servername, 0)) strerr_diefu2x(97, "reset client context: ", sbearssl_error_str(br_ssl_engine_last_error(&cc.eng))) ; diff --git a/src/sbearssl/sbearssl_run.c b/src/sbearssl/sbearssl_run.c index a6204d4..a0eba96 100644 --- a/src/sbearssl/sbearssl_run.c +++ b/src/sbearssl/sbearssl_run.c @@ -15,176 +15,162 @@ #include #include "sbearssl-internal.h" -void sbearssl_run (br_ssl_engine_context *ctx, int *fds, tain const *tto, uint32_t options, unsigned int verbosity, sbearssl_handshake_cbfunc_ref cb, sbearssl_handshake_cbarg *cbarg) + + /* declared in bearssl's inner.h */ +extern void br_ssl_engine_fail (br_ssl_engine_context *, int) ; + + /* XXX: breaks encapsulation; see make_ready_in() in src/ssl/ssl_engine.c in bearssl */ +static int br_ssl_engine_in_isempty (br_ssl_engine_context *ctx) +{ + return !ctx->iomode || (ctx->iomode == 3 && !ctx->ixa && !ctx->ixb) ; +} + +static void close_sendrec (br_ssl_engine_context *ctx, int *fd, int closenotify) +{ + if (closenotify) br_ssl_engine_close(ctx) ; + else + { + fd_shutdown(*fd, 1) ; + fd_close(*fd) ; + *fd = -1 ; + } +} + +void sbearssl_run (br_ssl_engine_context *ctx, int const *fds, tain const *tto, uint32_t options, unsigned int verbosity, sbearssl_handshake_cbfunc_ref cb, sbearssl_handshake_cbarg *cbarg) { - iopause_fd x[4] ; - unsigned int xindex[4] ; - int markedforflush = 0 ; + iopause_fd x[4] = { { .fd = fds[0], .revents = 0 }, { .fd = fds[1], .revents = 0 }, { .fd = fds[2] }, { .fd = fds[3] } } ; + unsigned int state = br_ssl_engine_current_state(ctx) ; int handshake_done = 0 ; + tain deadline ; - if (ndelay_on(fds[0]) < 0 - || ndelay_on(fds[1]) < 0 - || ndelay_on(fds[2]) < 0 - || ndelay_on(fds[3]) < 0) + if (ndelay_on(x[0].fd) == -1 + || ndelay_on(x[1].fd) == -1 + || ndelay_on(x[2].fd) == -1 + || ndelay_on(x[3].fd) == -1) strerr_diefu1sys(111, "set fds non-blocking") ; + tain_add_g(&deadline, tto) ; - for (;;) + while (x[0].fd >= 0 || x[1].fd >= 0 || x[3].fd >= 0) { - tain deadline = TAIN_INFINITE ; - unsigned int j = 0 ; - unsigned int state = br_ssl_engine_current_state(ctx) ; - int r ; - if (state & BR_SSL_CLOSED) { - r = br_ssl_engine_last_error(ctx) ; + int r = br_ssl_engine_last_error(ctx) ; if (r) strerr_dief4x(98, "the TLS engine closed the connection ", handshake_done ? "after" : "during", " the handshake: ", sbearssl_error_str(r)) ; - break ; + else break ; } - if (fds[0] >= 0 && state & BR_SSL_SENDAPP) + + /* Preparation */ + + if (x[0].fd >= 0 && state & BR_SSL_SENDAPP) { - x[j].fd = fds[0] ; - x[j].events = IOPAUSE_READ ; - xindex[0] = j++ ; + x[0].events = IOPAUSE_READ ; if (!handshake_done) { if (!(*cb)(ctx, cbarg)) strerr_dief1sys(111, "post-handshake callback failed") ; handshake_done = 1 ; + deadline = tain_infinite ; } } - else xindex[0] = 4 ; - if (fds[1] >= 0 && state & BR_SSL_RECVAPP) - { - x[j].fd = fds[1] ; - x[j].events = IOPAUSE_WRITE ; - xindex[1] = j++ ; - } - else xindex[1] = 4 ; - if (fds[2] >= 0 && state & BR_SSL_RECVREC) - { - x[j].fd = fds[2] ; - x[j].events = IOPAUSE_READ ; - xindex[2] = j++ ; - } - else xindex[2] = 4 ; - if (fds[3] >= 0 && state & BR_SSL_SENDREC) - { - x[j].fd = fds[3] ; - x[j].events = IOPAUSE_WRITE ; - xindex[3] = j++ ; - } - else xindex[3] = 4 ; + else x[0].events = 0 ; - if (xindex[0] == 4 && xindex[1] == 4 && xindex[3] == 4) - { - if (!j || handshake_done) break ; - tain_add_g(&deadline, tto) ; - } + if (x[1].fd >= 0) + x[1].events = IOPAUSE_EXCEPT | (state & BR_SSL_RECVAPP ? IOPAUSE_WRITE : 0) ; - r = iopause_g(x, j, &deadline) ; - if (r < 0) strerr_diefu1sys(111, "iopause") ; - else if (!r) + if (x[2].fd >= 0 && state & BR_SSL_RECVREC) x[2].events = IOPAUSE_READ ; + else x[2].events = 0 ; + + if (x[3].fd >= 0) + x[3].events = IOPAUSE_EXCEPT | (state & BR_SSL_SENDREC ? IOPAUSE_WRITE : 0) ; + + + /* Wait for events */ + + switch (iopause_g(handshake_done ? x : x+2, handshake_done ? 4 : 2, &deadline)) { - if (verbosity) strerr_dief1x(98, "handshake timed out") ; - else _exit(98) ; + case -1 : strerr_diefu1sys(111, "iopause") ; + case 0 : + if (verbosity) strerr_dief1x(98, "handshake timed out") ; + else _exit(98) ; } - while (j--) - if (x[j].revents & IOPAUSE_EXCEPT) - x[j].revents |= IOPAUSE_READ | IOPAUSE_WRITE ; - /* Flush to local */ - if (state & BR_SSL_RECVAPP && xindex[1] < 4 && x[xindex[1]].events & x[xindex[1]].revents & IOPAUSE_WRITE) + if (x[1].revents && state & BR_SSL_RECVAPP) { size_t len ; - unsigned char const *s = br_ssl_engine_recvapp_buf(ctx, &len) ; - size_t w = allwrite(fds[1], (char const *)s, len) ; - if (!w) - { - if (!error_isagain(errno)) - strerr_diefu1sys(111, "write to application") ; - } - else + for (;;) { - br_ssl_engine_recvapp_ack(ctx, w) ; - if (fds[2] < 0 && w == len) + ssize_t w ; + unsigned char const *s = br_ssl_engine_recvapp_buf(ctx, &len) ; + if (!len) break ; + w = fd_write(x[1].fd, (char const *)s, len) ; + if (w == -1) { - fd_close(fds[1]) ; fds[1] = -1 ; + if (error_isagain(errno)) break ; + strerr_diefu1sys(111, "write to local") ; } - state = br_ssl_engine_current_state(ctx) ; + br_ssl_engine_recvapp_ack(ctx, w) ; } + if (x[2].fd == -1 && !len) + { + fd_close(x[1].fd) ; + x[1].fd = -1 ; + } + state = br_ssl_engine_current_state(ctx) ; } - /* Flush to remote */ - if (state & BR_SSL_SENDREC && xindex[3] < 4 && x[xindex[3]].events & x[xindex[3]].revents & IOPAUSE_WRITE) + if (x[3].revents && state & BR_SSL_SENDREC) { size_t len ; - unsigned char const *s = br_ssl_engine_sendrec_buf(ctx, &len) ; - size_t w = allwrite(fds[3], (char const *)s, len) ; - if (!w) + for (;;) { - if (!error_isagain(errno)) - strerr_diefu1sys(111, "write to peer") ; - } - else - { - br_ssl_engine_sendrec_ack(ctx, w) ; - if (fds[0] < 0 && w == len) + ssize_t w ; + unsigned char const *s = br_ssl_engine_sendrec_buf(ctx, &len) ; + if (!len) break ; + w = fd_write(x[3].fd, (char const *)s, len) ; + if (w == -1) { - if (options & 1) - { - fd_shutdown(fds[3], 1) ; - fd_close(fds[3]) ; fds[3] = -1 ; - } - else br_ssl_engine_close(ctx) ; + if (error_isagain(errno)) break ; + strerr_diefu1sys(111, "write to remote") ; } - state = br_ssl_engine_current_state(ctx) ; + br_ssl_engine_sendrec_ack(ctx, w) ; } + if (x[0].fd == -1 && !len) + close_sendrec(ctx, &x[3].fd, options & 1) ; + state = br_ssl_engine_current_state(ctx) ; } - /* Fill from local */ - if (state & BR_SSL_SENDAPP && xindex[0] < 4 && x[xindex[0]].events & IOPAUSE_READ && (markedforflush || x[xindex[0]].revents & IOPAUSE_READ)) + if (x[0].revents & IOPAUSE_READ && state & BR_SSL_SENDAPP) { - size_t len ; - unsigned char *s = br_ssl_engine_sendapp_buf(ctx, &len) ; - size_t w ; - errno = 0 ; - w = allread(fds[0], (char *)s, len) ; - if (!w) + for (;;) { - br_ssl_engine_flush(ctx, 0) ; - markedforflush = 0 ; - if (!error_isagain(errno)) + size_t len ; + ssize_t r ; + unsigned char *s = br_ssl_engine_sendapp_buf(ctx, &len) ; + if (!len) break ; + r = fd_read(x[0].fd, (char *)s, len) ; + if (r == -1 && !error_isagain(errno)) + strerr_diefu1sys(111, "read from local") ; + else if (r <= 0) { - fd_close(fds[0]) ; fds[0] = -1 ; - if (!br_ssl_engine_sendrec_buf(ctx, &len)) + br_ssl_engine_flush(ctx, 0) ; + if (!r) { - if (options & 1) - { - fd_shutdown(fds[3], 1) ; - fd_close(fds[3]) ; fds[3] = -1 ; - } - else br_ssl_engine_close(ctx) ; + fd_close(x[0].fd) ; + x[0].fd = -1 ; + if (!br_ssl_engine_sendrec_buf(ctx, &len)) + close_sendrec(ctx, &x[3].fd, options & 1) ; } + break ; } - } - else - { - br_ssl_engine_sendapp_ack(ctx, w) ; - if (w == len) markedforflush = 1 ; - else - { - br_ssl_engine_flush(ctx, 0) ; - markedforflush = 0 ; - } + br_ssl_engine_sendapp_ack(ctx, r) ; } state = br_ssl_engine_current_state(ctx) ; } @@ -192,27 +178,39 @@ void sbearssl_run (br_ssl_engine_context *ctx, int *fds, tain const *tto, uint32 /* Fill from remote */ - if (state & BR_SSL_RECVREC && xindex[2] < 4 && x[xindex[2]].events & x[xindex[2]].revents & IOPAUSE_READ) + if (x[2].revents & IOPAUSE_READ && state & BR_SSL_RECVREC) { - size_t len ; - unsigned char *s = br_ssl_engine_recvrec_buf(ctx, &len) ; - size_t w ; - errno = 0 ; - w = allread(fds[2], (char *)s, len) ; - if (!w) + for (;;) { - if (!error_isagain(errno)) + size_t len ; + ssize_t r ; + unsigned char *s = br_ssl_engine_recvrec_buf(ctx, &len) ; + if (!s) break ; + r = fd_read(x[2].fd, (char *)s, len) ; + if (r == -1) + { + if (error_isagain(errno)) break ; + else strerr_diefu1sys(111, "read from remote") ; + } + else if (!r) { - if (options & 1) fd_shutdown(fds[2], 0) ; - fd_close(fds[2]) ; fds[2] = -1 ; - if (fds[1] >= 0 && !br_ssl_engine_recvapp_buf(ctx, &len)) + fd_shutdown(x[2].fd, 0) ; + fd_close(x[2].fd) ; + x[2].fd = -1 ; + if (!br_ssl_engine_recvapp_buf(ctx, &len)) { - fd_close(fds[1]) ; fds[1] = -1 ; + fd_close(x[1].fd) ; + x[1].fd = -1 ; } + if (!br_ssl_engine_in_isempty(ctx)) + br_ssl_engine_fail(ctx, BR_ERR_IO) ; + break ; } + br_ssl_engine_recvrec_ack(ctx, r) ; } - else br_ssl_engine_recvrec_ack(ctx, w) ; + state = br_ssl_engine_current_state(ctx) ; } } + _exit(0) ; } diff --git a/src/sbearssl/sbearssl_server_init_and_run.c b/src/sbearssl/sbearssl_server_init_and_run.c index d7817f0..01abb32 100644 --- a/src/sbearssl/sbearssl_server_init_and_run.c +++ b/src/sbearssl/sbearssl_server_init_and_run.c @@ -77,14 +77,15 @@ void sbearssl_server_init_and_run (int *fds, tain const *tto, uint32_t preoption stralloc tastorage = STRALLOC_ZERO ; genalloc tas = GENALLOC_ZERO ; /* sbearssl_ta */ size_t n = preoptions & 1 ? sbearssl_get_tas(&tas, &tastorage) : 0 ; - unsigned char buf[BR_SSL_BUFSIZE_BIDI] ; + unsigned char bufi[BR_SSL_BUFSIZE_INPUT] ; + unsigned char bufo[BR_SSL_BUFSIZE_OUTPUT] ; br_x509_trust_anchor btas[n ? n : 1] ; sbearssl_sctx_init_full_generic(&sc) ; sbearssl_sctx_set_policy_sni(&sc, &pol) ; - random_buf((char *)buf, 32) ; - br_ssl_engine_inject_entropy(&sc.eng, buf, 32) ; - br_ssl_engine_set_buffer(&sc.eng, buf, sizeof(buf), 1) ; + random_buf((char *)bufi, 32) ; + br_ssl_engine_inject_entropy(&sc.eng, bufi, 32) ; + br_ssl_engine_set_buffers_bidi(&sc.eng, bufi, sizeof(bufi), bufo, sizeof(bufo)) ; { uint32_t flags = BR_OPT_ENFORCE_SERVER_PREFERENCES | BR_OPT_NO_RENEGOTIATION ; diff --git a/src/stls/stls_run.c b/src/stls/stls_run.c index 960260b..7385c4e 100644 --- a/src/stls/stls_run.c +++ b/src/stls/stls_run.c @@ -104,9 +104,9 @@ static void send_closenotify (struct tls *ctx, int const *fds) iopause_g(&x, 1, 0) ; } -static void closeit (struct tls *ctx, int *fds, int brutal) +static void closeit (struct tls *ctx, int *fds, int closenotify) { - if (brutal) fd_shutdown(fds[3], 1) ; + if (!closenotify) fd_shutdown(fds[3], 1) ; else if (fds[2] >= 0) send_closenotify(ctx, fds) ; fd_close(fds[3]) ; fds[3] = -1 ; } @@ -243,7 +243,7 @@ void stls_run (struct tls *ctx, int *fds, uint32_t options, unsigned int verbosi if (r < 0) { if (r == -1) strerr_warnwu2x("read from peer: ", tls_error(ctx)) ; - if (options & 1) fd_shutdown(fds[2], 0) ; + if (!(options & 1)) fd_shutdown(fds[2], 0) ; /* XXX: We need a way to detect when we've received a close_notify, because then we need to trigger a write and then shut the engine diff --git a/src/tls/s6-tlsc-io.c b/src/tls/s6-tlsc-io.c index 633d349..57b1442 100644 --- a/src/tls/s6-tlsc-io.c +++ b/src/tls/s6-tlsc-io.c @@ -73,7 +73,7 @@ int main (int argc, char const *const *argv, char const *const *envp) unsigned int verbosity = 1 ; unsigned int notif = 0 ; uint32_t preoptions = 0 ; - uint32_t options = 1 ; + uint32_t options = 2 ; PROG = "s6-tlsc-io" ; { @@ -87,8 +87,8 @@ int main (int argc, char const *const *argv, char const *const *envp) { case 'v' : if (!uint0_scan(l.arg, &verbosity)) dieusage() ; break ; case 'd' : if (!uint0_scan(l.arg, ¬if)) dieusage() ; break ; - case 'S' : options &= ~1 ; break ; - case 's' : options |= 1 ; break ; + case 'S' : options |= 1 ; break ; + case 's' : options &= ~1 ; break ; case 'Y' : preoptions &= ~1 ; break ; case 'y' : preoptions |= 1 ; break ; case 'K' : if (!uint0_scan(l.arg, &t)) dieusage() ; break ; diff --git a/src/tls/s6-tlsd-io.c b/src/tls/s6-tlsd-io.c index d58a259..0aa19c9 100644 --- a/src/tls/s6-tlsd-io.c +++ b/src/tls/s6-tlsd-io.c @@ -70,7 +70,7 @@ int main (int argc, char const *const *argv) unsigned int verbosity = 1 ; unsigned int notif = 0 ; uint32_t preoptions = 0 ; - uint32_t options = 1 ; + uint32_t options = 0 ; PROG = "s6-tlsd-io" ; { @@ -84,8 +84,8 @@ int main (int argc, char const *const *argv) { case 'v' : if (!uint0_scan(l.arg, &verbosity)) dieusage() ; break ; case 'd' : if (!uint0_scan(l.arg, ¬if)) dieusage() ; break ; - case 'S' : options &= ~1 ; break ; - case 's' : options |= 1 ; break ; + case 'S' : options |= 1 ; break ; + case 's' : options &= ~1 ; break ; case 'Y' : preoptions |= 1 ; preoptions &= ~2 ; break ; case 'y' : preoptions |= 3 ; break ; case 'K' : if (!uint0_scan(l.arg, &t)) dieusage() ; break ; -- cgit v1.2.3