Skip to content

channel mode +M - #5

Merged
entrope merged 5 commits into
UndernetIRC:u2_10_12_branchfrom
empus:chanmode_M
Dec 23, 2019
Merged

channel mode +M#5
entrope merged 5 commits into
UndernetIRC:u2_10_12_branchfrom
empus:chanmode_M

Conversation

@empus

@empus empus commented May 4, 2019

Copy link
Copy Markdown
Contributor

New channel mode to prevent unauthenticated users from speaking or using part messages in a channel (unless opped or voiced).

Helpful to combat channel abuse (especially the recent troubles) where +r is too restrictive and the use of +Dm with bots is too complex for many users. This new mode could be used either permanently for many channels, or temporarily during periods of abuse.

empus added 2 commits May 4, 2019 08:10
channel moderation where only registered users can speak

@entrope entrope left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a fan of this kind of mode; it creates second-class members of a channel.

Comment thread include/channel.h
Comment thread ircd/channel.c
Comment thread ircd/channel.c Outdated
empus and others added 2 commits December 22, 2019 13:35
Move MODE_DELJOINS

Co-Authored-By: Michael Poole <mdpoole@troilus.org>
Comment thread ircd/channel.c Outdated
@entrope
entrope merged commit 47af138 into UndernetIRC:u2_10_12_branch Dec 23, 2019
Ratler added a commit that referenced this pull request Aug 28, 2026
…nd close semantics (#99)

* Fix TLS sendq accounting on partial writes (con_rexmit)

TLS short writes (SSL_MODE_ENABLE_PARTIAL_WRITE and the GnuTLS/libtls
equivalents) could leave con_rexmit pointing mid-message while
send_queued() advanced the MsgQ via msgq_delete(count_out).  Finishing
the remainder then called msgq_excise() with a mid-message pointer,
which only matches the start of a MsgBuf: default builds abort(), and
--disable-asserts builds leave the queue wrong and corrupt the TLS
stream.  Clients dropped with no IRC ERROR (after NOTICE AUTH, or under
large NAMES / sendq bursts).

Send path (all TLS backends + deliver_it):
- Credit successful rexmit bytes through count_out so msgq_delete()
  advances the queue's own Msg.sent offset; con_rexmit is then always
  head->msg + head->sent.  msgq_excise() is removed.
- Drain short TLS writes until the message finishes or the backend
  reports a real block (WANT_WRITE / EAGAIN); return IO_BLOCKED only then.
- Do not set FLAG_BLOCKED on TLS short IO_SUCCESS (that busy-loops
  ET_WRITE while POLLOUT stays ready).  Still set it for plain short
  writev.  Credit sendB on IO_BLOCKED when some bytes already went out.
- Clear con_rexmit beside every MsgQClear (dead_link / close_connection /
  dealloc_connection).

Tests: TLS auth-drop pipeline and NAMES-burst regressions (tls_stress
marker, pytest-timeout dependency).

* Fix AuthRequest freelist/timer lifecycle and double check_auth_finished

- destroy_auth_request() during timer_run() while the timeout timer is
  GEN_MARKED must not freelist the AuthRequest yet: timer_del() is a
  no-op while marked and timer_run() still owns the Timer.  Defer with
  AR_FREE_PENDING and freelist on ET_DESTROY.  Detach from the client
  first, and timer_del() before freelist reuse so a leftover queue link
  cannot form a self-loop that pegs timer_enqueue() at 100% CPU.
- Split start_dns_ident_queries() from start_dns_ident() so start_auth()
  finishes auth exactly once.  start_dns_ident() remains the deferred
  WebSocket resume path from s_bsd and finishes auth itself.  Previously
  start_auth() called check_auth_finished() twice on one AuthRequest,
  which could use a freelisted request (SIGSEGV after client pointer
  nulling) when the first call completed or exited the client.

* TLS: fail negotiation cleanly when the session is gone

ircd_tls_negotiate() returned 1 (success) when s_tls() was already NULL,
so FLAG_NEGOTIATING_TLS stayed set and start_auth() was invoked on every
subsequent ET_WRITE.  Clear the flag and return -1 in all three backends.

OpenSSL: return an explicit 1 on handshake success; the previous
'return res' at the end of the function returned X509_digest()'s result,
which reads as "still negotiating" when the digest fails.

* GnuTLS: align close and EOF semantics with OpenSSL and libtls

- gnutls_record_recv() == 0 is a peer close -> IO_FAILURE.  It was
  treated as IO_BLOCKED (gnutls_error_is_fatal(0) is false), leaving the
  session open until the client gave up on its SSL shutdown wait (30s
  for asyncio).
- ircd_tls_close(): only send close_notify after a completed handshake,
  matching OpenSSL SSL_is_init_finished() and libtls tls_close().
  ircd_tls_negotiate() marks success via gnutls_session_set_ptr();
  gnutls_protocol_get_version() cannot be used for this since it is
  already set before any ClientHello arrives.  A stalled handshake now
  yields TCP EOF instead of a TLS alert record.

Tests: test_stalled_handshake_times_out asserts a plain EOF (no alert
byte accepted); new test_stalled_handshake_after_clienthello_times_out
drives the client side in a MemoryBIO so an encrypted TLS 1.3 alert
after ServerHello is detected too.  Verified on openssl, gnutls, libtls.

* GnuTLS: accept peer certificate when fingerprint extraction fails

If gnutls_x509_crt_import() or gnutls_x509_crt_get_fingerprint() failed
after a successful handshake, ircd_tls_negotiate() returned 1 without
clearing FLAG_NEGOTIATING_TLS, so send_queued() never sent anything and
the connection wedged until ping timeout.  Log the error, leave the
fingerprint empty and complete the handshake normally, as the OpenSSL
and libtls backends do.

* Handle ircd_tls_negotiate() failure in completed_connection()

The outbound server-link path discarded the negotiate result and only
looked at FLAG_NEGOTIATING_TLS.  A handshake timeout (which returns -1
without clearing the flag) was read as "still negotiating" and the link
sat until ping timeout; a missing session (flag cleared, -1) fell
through to sending PASS/SERVER on a socket without TLS.  Handle the
result like tls_negotiate_client(): notify opers, close the TLS session
and fail the connection.

* tests: debug capture robustness and TLS container debug settings

- conftest: never abort the suite when the failure snapshot cannot be
  written (e.g. root-owned failures/ from a prior docker run); fall back
  under /tmp.
- debug_support: snapshot whichever hub container is running (tls-hub,
  hub, limits) instead of hard-coding ircu-hub.
- docker-compose: debug volume, ASAN options, seccomp:unconfined and
  core/nofile ulimits for ircd-tls-hub / ircd-tls-leaf, matching the
  other ircd services.

* TLS send path: remove drained rexmit by identity; no progress on fatal error

Follow-up to the TLS PR, addressing review findings #1 and #2.

#1 (correctness): the con_rexmit drain reported a raw byte count that
send_queued() fed to msgq_delete(), which deletes in (partial-normal,
prio, normal) order.  When a whole normal message was deferred (sent==0)
and a priority message (e.g. a check_pings PING) was enqueued before the
next ET_WRITE, msgq_delete() deleted the never-sent PING first and left
the normal message's tail to be re-sent as duplicate bytes -- desyncing
the P10 link and dropping the PING.  Restore identity-based removal: a
corrected msgq_excise() (matching the head message by buffer containment,
checking both queues) removes exactly the drained message, and its bytes
are no longer credited to *count_out.  The in-loop drain stays count-based
(it sends in mapiov order within one synchronous call).

#2 (disclosure): on a fatal mid-drain error the backend frees the TLS
session and returned IO_FAILURE with count_out > 0; deliver_it() reported
that as progress, so send_queued() looped back and flushed the rest of the
sendq -- private messages included -- as plaintext on the raw socket.  Zero
*count_out on every IO_FAILURE return so deliver_it() reports no progress
and the link dead-links immediately, matching pre-PR behaviour.

Applied consistently to the OpenSSL, GnuTLS and libtls backends.

* s_bsd: fix outbound TLS negotiation-failure handling

Follow-up to the TLS PR, addressing review findings #3 and #5.

#3: completed_connection()'s TLS-failure branch freed the session but did
not mark the client dead, so exit_client() flushed its ERROR line as
plaintext into the half-open handshake stream.  Set FLAG_DEADSOCKET so
can_send() rejects the write, matching tls_negotiate_client().

#5: tls_handshake_succeeded() discarded completed_connection()'s return,
so a link whose Connect block vanished during the handshake (rehash) was
left half-initialized until the ping timeout.  Exit the client on a 0
return, as the ET_CONNECT path already does.

* s_auth: assert freelisted AuthRequest timer invariant instead of repairing

Follow-up to the TLS PR, addressing review finding #6.

The defensive timer_del() in start_auth() was a no-op on a still-GEN_MARKED
timer, after which memset() zeroed t_header links timer_run() still owned --
recreating the timer-enqueue self-loop (100% CPU) it was meant to prevent.
AR_FREE_PENDING already guarantees a freelisted request's timer is fully
destroyed before reuse, so assert that invariant rather than silently
repairing a state that should never occur.

* tests: fix debug-snapshot exception guard and TLS-hub clone throttling

Follow-up to the TLS PR, addressing review findings #4 and #7.

#4: the failure-snapshot guard caught only OSError, but the docker
inspect/logs calls raise subprocess.TimeoutExpired (a SubprocessError) on
a hung daemon -- turning every test failure into a pytest internal error.
Catch subprocess.SubprocessError too.

#7: the TLS stress tests open many concurrent connections from the single
docker-network IP; add permissive IPCHECK_CLONE_LIMIT/PERIOD to the TLS
hub config (matching the plaintext hub) so clone throttling does not fail
them spuriously.

* tests: add msgq_excise unit test for the TLS rexmit prio-reorder fix

Covers review finding #1 at the C level.  A normal message deferred to
con_rexmit (sent == 0) with a priority message enqueued behind it must be
removed by identity, leaving the priority message intact; the test also
checks a mid-message con_rexmit pointer (multi-partial drain), excising a
priority message, and the lone-message case.  Wired into `make check` as
msgq_excise_t.

Verified to abort on the pre-fix count-based deletion, which removes the
priority message in place of the drained one.

* TLS: surface detailed handshake and verification failure reasons

ircd_tls_negotiate() collapsed every failure to a bare -1, so operator
notices and disconnect logs read only "TLS negotiation failed".  It now
fills a caller-provided reason buffer (TLS_REASON_LEN) with a specific,
human-readable cause, propagated to the SNO_OLDSNO operator notice and the
disconnect reason on both the inbound (tls_negotiate_client) and outbound
(completed_connection) paths.  A categorical ERROR line is still written to
the peer where the channel is plaintext; TLS-layer rejections reach the
peer as the backend's own TLS alert instead.

Reasons per backend:
- OpenSSL: X509_verify_cert_error_string() for a verification abort
  (SSL_get_verify_result() is set even when SSL_accept() fails), otherwise
  the OpenSSL error reason; plus certificate-required and handshake errors.
- GnuTLS: gnutls_certificate_verification_status_print() for a bad verdict,
  gnutls_strerror() for the verify call or handshake error.
- libtls: tls_error() for the handshake error.

A stalled handshake still closes with a plain TCP EOF (no peer write, which
would corrupt a mid-handshake peer's TLS stream).  The server-link
fingerprint-mismatch notice now reports the presented and configured
fingerprints (m_server.c).

Example operator notices:
  TLS negotiation failed from unknown server: self-signed certificate
  TLS negotiation failed from unknown server: certificate has expired
  TLS negotiation failed to leaf2.test.net: no shared cipher

* listener: enlarge show_ports flags buffer; add STATS p regression tests

show_ports() built its flag string in char flags[8], which was sized
for the original C/S + H + 4- + 6- letters (6 + NUL). The TLS ('E')
and Cloudflare ('F') additions push the worst case (hidden TLS
cloudflare listener with both address families unbound after a
rehash) to 8 characters plus NUL, one byte past the buffer. Bump it
to 16.

Add tests/stats_ports covering STATS p locally, hunted to a remote
server, after REHASH, and on a server with TLS/websocket/cloudflare
listeners.

* GnuTLS: keep count_out credit on non-fatal send errors

Both crediting error paths in ircd_tls_sendv() zeroed *count_out
before deciding whether the error was fatal, so a non-fatal error
(e.g. a warning alert) mid-batch returned IO_BLOCKED with zero
credit.  send_queued() then deleted nothing from the sendq and the
messages already handed to gnutls_record_send() were retransmitted
on the next ET_WRITE, duplicating lines mid-stream.

Zero the credit only on IO_FAILURE, matching the OpenSSL and libtls
backends.

---------

Co-authored-by: Stefan Wold <ratler@undernet.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants