3
0
mirror of https://github.com/ergochat/ergo.git synced 2025-08-02 10:47:31 +02:00

Compare commits

...

1625 Commits

Author SHA1 Message Date
Shivaram Lingamneni
65295cbafa
Merge pull request #2283 from slingamn/makefile
refactor makefile to label individual targets phony
2025-06-26 22:17:06 -04:00
Shivaram Lingamneni
f0b1f34da7 refactor makefile to label individual targets phony 2025-06-26 22:14:19 -04:00
Shivaram Lingamneni
f918e28513 bump irctest 2025-06-26 01:31:51 -04:00
Shivaram Lingamneni
8798676ae9
update metadata corresponding to spec edits (#2282)
* spec update: metadata keys are lowercase

* add batch parameter to metadata batches

* fix: connecting clients receive METADATA, not RPL_KEYVALUE

* spec update: send RPL_METADATASUBS in a metadata-subs batch

* move some helpers

* bump irctest to forked hash

This is https://github.com/progval/irctest/pull/314 but I don't want to
couple the merges

* fix: empty value is valid

* fix: deleting a nonexistent key gets a FAIL
2025-06-22 18:59:42 -04:00
Shivaram Lingamneni
cca400de73 fix: actually broadcast prereg updates to subscribers
Missed in #2281, needs a test presumably :-)
2025-06-22 13:59:36 -04:00
Shivaram Lingamneni
73e51333ad
implement metadata before-connect (#2281)
* metadata spec update: disallow colon entirely

* refactor key validation

* implement metadata before-connect

* play the metadata in reg burst to all clients with the cap

* bump irctest

* remove all case normalization for keys

From spec discussion, we will most likely either require keys to be lowercase,
or else treat them as case-opaque, similar to message tag keys.
2025-06-22 13:57:46 -04:00
Shivaram Lingamneni
a5e435a26b bump irctest 2025-06-21 22:04:33 -04:00
Shivaram Lingamneni
17ed01c1ed
Merge pull request #2279 from slingamn/doc
fix some documentation
2025-06-19 13:29:08 -04:00
Shivaram Lingamneni
8f18454e8f fix help string for HISTORY 2025-06-19 13:25:34 -04:00
Shivaram Lingamneni
23844d4103 update documentation for globalUtf8EnforcementSetting 2025-06-19 13:22:07 -04:00
Shivaram Lingamneni
3b7db7fff7
round 1 of follow-up for metadata (#2277)
* refactoring
* send an empty batch if necessary, as per spec
* don't broadcast no-op updates
* don't trim spaces before validating the key
* bump irctest to cover metadata
* replay existing metadata to reattaching always-on clients
* use canonicalized name everywhere
* use utils.SafeErrorParam in FAIL lines
* validate key names for sub
* fix error for METADATA CLEAR
* max-keys is enforced for channels as well
* remove unlimited configurations
* maintain the limit exactly without off-by-one cases
* add final channel registration check
2025-06-18 00:22:49 -04:00
thatcher-gaming
4dcbc48159
metadata-2 (#2273)
Initial implementation of draft/metadata-2
2025-06-15 04:06:45 -04:00
Shivaram Lingamneni
0f5603eca2 bump irctest to upstream master 2025-06-09 02:20:49 -04:00
Shivaram Lingamneni
7d4f5e4adf
Merge pull request #2271 from slingamn/register
fix #2270
2025-06-09 02:19:55 -04:00
Shivaram Lingamneni
16568c5ab7 fix #2270
REGISTER should strip the guest format when applicable, same as NS REGISTER.
2025-06-08 16:50:34 -04:00
Shivaram Lingamneni
9a186f8e54
Fix invalid FAIL codes in REGISTER (#2269)
* nickserv.go: Update FAIL codes to match spec

* handlers.go: Fix FAIL code

* use ACCOUNT_EXISTS for errNameReserved

* bump irctest to development version

---------

Co-authored-by: Valerie Liu <79415174+ValwareIRC@users.noreply.github.com>
2025-06-08 01:43:43 -04:00
Shivaram Lingamneni
7828218bc7
Merge pull request #2264 from slingamn/cutprefix
fix #2147
2025-05-26 01:56:06 -04:00
Shivaram Lingamneni
7138e76151 fix #2147
use strings.CutPrefix when possible
2025-05-25 01:59:55 -04:00
Sarah Rose
e4aac56bda
API enhancements (#2261)
Fixes #2257 and #2260

* add `/v1/status` endpoint
* add `/v1/account_list` endpoint
* add fields to `/v1/account_details` response
2025-05-25 00:47:20 -04:00
Shivaram Lingamneni
4da6511674
Merge pull request #2262 from slingamn/constant
clean up constant redefinition
2025-05-23 00:36:46 -04:00
Shivaram Lingamneni
253972a9d2 clean up constant redefinition 2025-05-23 00:18:36 -04:00
Shivaram Lingamneni
a1c46a4be7 clarify channel registration instructions 2025-05-19 00:02:04 -04:00
Shivaram Lingamneni
7718081440
Merge pull request #2258 from slingamn/deps
upgrade dependencies for 2.17 release cycle
2025-05-18 02:07:40 -04:00
Shivaram Lingamneni
e7501ef847 upgrade go-msgauth 2025-05-18 01:28:48 -04:00
Shivaram Lingamneni
e404942d83 upgrade x dependencies 2025-05-18 01:27:52 -04:00
Shivaram Lingamneni
0a947115d6 set up new development version 2025-05-18 01:15:11 -04:00
Shivaram Lingamneni
9b9c39ddd4 changelog entry for API config 2025-05-18 01:09:13 -04:00
Shivaram Lingamneni
e200e9fd8f bump version and changelog for v2.16.0 2025-05-18 00:37:21 -04:00
Shivaram Lingamneni
66a7a488b7
bump version and changelog for v2.16.0-rc1 (#2255) 2025-05-11 01:07:13 -04:00
Shivaram Lingamneni
28ed16261c
Merge pull request #2254 from ergochat/shivaram_alwaysonbug.1
fix #2252
2025-05-08 00:14:51 -04:00
Shivaram Lingamneni
686ce4d5b2 fix #2252
Fix SAREGISTER creating always-on clients with no user modes.

Also fix UNREGISTER/ERASE not deleting the stored push subscriptions.
2025-05-07 22:09:08 -04:00
Shivaram Lingamneni
808799b100
Merge pull request #2253 from slingamn/batchname
fix isupport batch name
2025-05-01 22:18:08 -04:00
Shivaram Lingamneni
e382036ddb fix isupport batch name 2025-05-01 14:43:06 -04:00
Shivaram Lingamneni
43fe72f83e
clean up redundant caching (#2251) 2025-04-28 00:52:40 -04:00
Shivaram Lingamneni
4ab1a10eec clean up redundant caching 2025-04-28 00:29:40 -04:00
Shivaram Lingamneni
54b17b0700
improve robustness of timestamp parsing (#2250)
* Clamp CHATHISTORY timestamp selectors to be in [0, MaxInt64]
* Convert everything to UTC up front (probably a no-op)
2025-04-24 23:37:48 -04:00
Shivaram Lingamneni
2cf569c5d9
Merge pull request #2249 from slingamn/targetspanic
validate that CHATHISTORY limit parameters are nonnegative
2025-04-24 23:37:32 -04:00
Shivaram Lingamneni
a4194c38d8 validate that CHATHISTORY limit parameters are nonnegative
See #2248. Reported by @prdes
2025-04-24 12:33:53 -04:00
Shivaram Lingamneni
5bab190d33
fix #2244 (#2247)
Fix #2244

Produce an explicit error on receiving the UTF-8 BOM
2025-04-21 22:37:53 -04:00
Shivaram Lingamneni
68cee9e2cd
use emersion/go-msgauth for DKIM (#2242)
Fixes #1041 (support ed25519-sha256 for DKIM)
2025-04-07 00:24:08 -04:00
Shivaram Lingamneni
9c3173f573
safer 005 length limits (#2241)
* Limit the payload to 380 bytes instead of 400
* Don't translate the final parameter

This leaves about 60 bytes for the server name.
2025-04-06 02:59:03 -04:00
Shivaram Lingamneni
98e04c10a8
fix #2220 (#2240)
Allow publishing arbitrary ISUPPORT via the config file
2025-04-06 01:41:03 -04:00
Shivaram Lingamneni
a6df370bd9
block HTTP DoS attacks (#2239)
Block uses of the JS Fetch API to send HTTP message bodies that are also valid
IRC. The constraint on such messages is that they must begin with a valid HTTP
verb; we can detect this and reject them immediately.
2025-03-30 21:33:06 -04:00
Shivaram Lingamneni
9791606f62
allow customizing the NPC and SCENE nickmasks (#2237)
See #2229
2025-03-30 21:32:55 -04:00
Shivaram Lingamneni
7256d83ff0
implement command aliases (#2236)
See #2229
2025-03-30 21:32:37 -04:00
Shivaram Lingamneni
f5bb5afdd6
bump CI to noble (#2235) 2025-03-30 02:48:51 -04:00
Shivaram Lingamneni
d3eb787a1e bump irctest 2025-03-27 05:20:22 -04:00
Shivaram Lingamneni
19dbe10c99
fix panic on KILL (#2234)
Introduced by #2218, reported by knolle
2025-03-26 21:21:05 -04:00
Shivaram Lingamneni
467df24914
fix #2228 (#2233)
If the server is UTF8ONLY, validate that the MOTD is UTF8
2025-03-22 23:13:31 -04:00
Shivaram Lingamneni
9dc2fd52ed
Merge pull request #2232 from ergochat/dependabot/go_modules/github.com/golang-jwt/jwt/v5-5.2.2
Bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2
2025-03-22 20:52:14 -04:00
dependabot[bot]
a46732f6ab
Bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2
Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.1 to 5.2.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.1...v5.2.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 22:14:08 +00:00
Shivaram Lingamneni
ea81ec86e1
MVP for HTTP API (#2231)
Co-authored-by: Klaas Tammling <klaas@tammling.hamburg>
2025-03-18 23:13:03 -04:00
Shivaram Lingamneni
4bcd008416
fix CHATHISTORY TARGETS from MySQL backend using server local TZ (#2224)
time.Unix() returns a time.Time with the Location populated to the server's
timezone. Such times will format incorrectly with IRCv3TimestampFormat unless
they are manually converted to UTC.
2025-03-17 02:22:25 -04:00
Shivaram Lingamneni
aed216a62e update filenames in docker documentation 2025-03-16 16:18:16 -04:00
Shivaram Lingamneni
f3e24c7bdb
Merge pull request #2223 from slingamn/saferate
add SAFERATE to 005
2025-03-09 21:41:24 -04:00
Shivaram Lingamneni
23b65e225b add SAFERATE to 005
See discussion at https://github.com/ircv3/ircv3-specifications/pull/556
2025-03-06 13:30:17 -05:00
Shivaram Lingamneni
4ced4ef328
Merge pull request #2219 from slingamn/quit_tags
fix #2218
2025-02-16 02:20:13 -05:00
Shivaram Lingamneni
ec3417be79 fix #2218
The client's own QUIT line should respect server-time
2025-02-16 01:33:24 -05:00
Shivaram Lingamneni
7e18362d35
upgrade to go 1.24 (#2217) 2025-02-12 21:15:21 -05:00
Shivaram Lingamneni
eb84ede5f7
Merge pull request #2216 from slingamn/redact
fix #2215
2025-02-06 01:39:32 -05:00
Shivaram Lingamneni
d50f1471eb fix #2215
Hide the message-redaction capability if allow-individual-delete is disabled.
(Technically REDACT can still be used, but only by ircops, so advertising it
is misleading in the most common case).
2025-02-06 01:36:02 -05:00
Shivaram Lingamneni
d9f663c400
Merge pull request #2214 from slingamn/logline.2
minor refactoring
2025-02-06 00:21:15 -05:00
Shivaram Lingamneni
e1b5a05c27 refactor service help sorting 2025-02-05 00:47:23 -05:00
Shivaram Lingamneni
a850602bcc refactor 005 token generation 2025-02-05 00:47:23 -05:00
Shivaram Lingamneni
d1126b53eb return constant strings in 004/005 2025-02-05 00:47:23 -05:00
Shivaram Lingamneni
4851825d4f use slices.SortFunc for modes 2025-02-05 00:47:23 -05:00
Shivaram Lingamneni
8fa6e19c2e fix #2213
We intended to suppress 324 and 329 when there is no valid mode change, only
unknown modes. Fix these checks.
2025-02-03 21:35:51 -05:00
Shivaram Lingamneni
07669f9eb4 remove hashset from mode parsing 2025-02-03 21:29:34 -05:00
Shivaram Lingamneni
4dfb7cc7ae use slices.Contains in irc/modes 2025-02-03 21:29:34 -05:00
Shivaram Lingamneni
b6a8cc20c2 add conn ID to SASL auth logline 2025-02-03 21:29:34 -05:00
Shivaram Lingamneni
cf7db4bc2a
Merge pull request #2212 from slingamn/deps
upgrade dependencies for v2.16 release cycle
2025-01-26 13:07:34 -05:00
Shivaram Lingamneni
b6f6959acc upgrade buntdb 2025-01-26 04:17:34 -05:00
Shivaram Lingamneni
af124cd964 upgrade x dependencies 2025-01-26 00:58:13 -05:00
Shivaram Lingamneni
e60afda556 set up new development version 2025-01-26 00:55:31 -05:00
Shivaram Lingamneni
c92f23b0cb bump version and changelog for v2.15.0 2025-01-25 22:55:19 -05:00
Shivaram Lingamneni
656eea43e7 add more warnings about push notifications and tor 2025-01-25 18:25:59 -05:00
Shivaram Lingamneni
881f403164 bump webpush-go to release version 2025-01-16 01:04:37 -05:00
Shivaram Lingamneni
b38ca31ced
Merge pull request #2211 from slingamn/pushsync
fix buggy persistence of push timestamps
2025-01-15 21:22:57 -08:00
Shivaram Lingamneni
7b71839615 fix buggy persistence of push timestamps
getPushSubscriptions() could have a stale view of the latest subscription
renewal and successful push times. We don't want to rebuild on every renewal
or every push, so add a boolean refresh argument that controls rebuilding.
2025-01-16 00:06:11 -05:00
Shivaram Lingamneni
9dd7a2bbcb
Merge pull request #2200 from slingamn/download_url
fix download link
2025-01-15 20:50:01 -08:00
Shivaram Lingamneni
148d743eb1 update changelog 2025-01-15 23:27:33 -05:00
Shivaram Lingamneni
2a79f64f2d
Merge pull request #2210 from slingamn/rearrange
tweaks to logging and NS PUSH LIST
2025-01-15 20:22:23 -08:00
Shivaram Lingamneni
799e1b14f4 delete services debug line 2025-01-15 22:12:40 -05:00
Shivaram Lingamneni
2163d96348 add connID to another logline 2025-01-15 22:12:40 -05:00
Shivaram Lingamneni
e520ba7e0e list push subscription times as well 2025-01-15 22:12:40 -05:00
Shivaram Lingamneni
92e2aa987e move debug log ID within NS CLIENTS LIST output 2025-01-15 22:12:40 -05:00
Shivaram Lingamneni
ab2d842b27
changelog and version updates for v2.15.0-rc1 (#2209) 2025-01-13 22:57:04 -05:00
Shivaram Lingamneni
21ee867ebb
fix #2198 (#2199)
Add require-sasl support to KLINE / UBAN on NUH masks
2025-01-13 22:20:47 -05:00
Shivaram Lingamneni
36e5451aa5
implement draft/webpush (#2205) 2025-01-13 21:47:21 -05:00
Shivaram Lingamneni
efd3764337
add unique connection ID to debug logs (#2207)
Fixes #2206
2025-01-11 23:07:04 -05:00
Shivaram Lingamneni
375079e636
Merge pull request #2201 from donatj/patch-1
Improve Docker "Persisting Data" Section
2024-12-19 01:26:23 +01:00
Jesse Donat
38862b0529
Improve Docker "Persisting Data" Section
`pwd` is case sensitive on linux
Add missing `--name ergo`
2024-12-18 09:23:48 -06:00
Shivaram Lingamneni
2bb9980e56 fix download link 2024-12-11 22:50:24 -05:00
Shivaram Lingamneni
1bdc45ebb4
clarify role of database file (#2190) 2024-11-17 15:21:06 -05:00
Shivaram Lingamneni
eddd4cc723
fix incorrect batch parameter in draft/extended-isupport (#2197) 2024-10-26 22:11:20 -04:00
Shivaram Lingamneni
726d997d07
advertise SAFELIST (#2196)
LIST is implemented via blocking (*ResponseBuffer).Send, so it can never
exceed the sendq limit.
2024-10-06 12:11:34 -04:00
Shivaram Lingamneni
9577e87d9a
bump irc-go to v0.5.0-rc2 (#2194) 2024-09-27 00:42:09 -04:00
Shivaram Lingamneni
7586520032
implement draft/extended-isupport (#2184) 2024-09-27 00:40:56 -04:00
Shivaram Lingamneni
f68d32b4ee
remove GCStats.Pause initialization (#2189)
It's too small anyway so the runtime has to reallocate it.
2024-09-08 01:48:47 -04:00
Shivaram Lingamneni
796bc198ed
upgrade go to 1.23 (#2187) 2024-08-15 23:50:27 -04:00
Shivaram Lingamneni
df6aa4c34b
enable building for solaris (#2183) 2024-08-02 15:09:28 -04:00
Shivaram Lingamneni
30f47a9b22 bump irctest 2024-07-07 02:34:51 -04:00
Shivaram Lingamneni
92a23229f8
update to goreleaser v2 (#2169)
* update goreleaser config:

* --rm-dist is replaced by --clean
* `replacements` is removed:
   https://goreleaser.com/deprecations/#archivesreplacements

* update to goreleaser v2

* goreleaser version must be specified in .goreleaser.yml
* --skip-publish is replaced by --skip=publish
2024-07-05 17:18:08 -04:00
Shivaram Lingamneni
825b4298b8
Merge pull request #2175 from slingamn/deps.1
upgrade dependencies for new release cycle
2024-07-05 23:15:16 +02:00
Shivaram Lingamneni
eba6d532ea go mod tidy 2024-07-05 16:40:07 -04:00
Shivaram Lingamneni
7d3971835e upgrade x dependencies 2024-07-05 16:39:22 -04:00
Shivaram Lingamneni
99393d49bf upgrade golang-jwt 2024-07-05 16:37:44 -04:00
Shivaram Lingamneni
82c50cc497 upgrade buntdb 2024-07-05 16:36:53 -04:00
Shivaram Lingamneni
ce41f501c9 set up new development version 2024-07-01 01:07:21 -04:00
Shivaram Lingamneni
d25fc2a758 bump version and changelog for v2.14.0 2024-06-30 23:36:28 -04:00
Shivaram Lingamneni
f598da300d
add linux/riscv64 release (#2173)
* add riscv64 release

* undo Alpine upgrade

* exclude *bsd/riscv64 releases

---------

Co-authored-by: Meng Zhuo <mzh@golangcn.org>
2024-06-20 23:55:20 -04:00
Shivaram Lingamneni
bb6c7ee158
Merge pull request #2171 from slingamn/rc2
bump version and changelog for v2.14.0-rc2
2024-06-16 10:51:20 +02:00
Shivaram Lingamneni
958eb43393 bump version and changelog for v2.14.0-rc2 2024-06-16 04:40:00 -04:00
Shivaram Lingamneni
9b8562c211
Merge pull request #2170 from slingamn/validate.1
fix truncation check
2024-06-11 23:17:35 +02:00
Shivaram Lingamneni
2bb0a9c8e3 bump irctest to fork's master 2024-06-11 17:06:58 -04:00
Shivaram Lingamneni
0b333c7e72 fix truncation check
* The message target was not being counted :-(
* The additional character added to the target by STATUSMSG was not counted
2024-06-11 01:42:57 -04:00
Shivaram Lingamneni
2aec5e167c
Merge pull request #2168 from slingamn/fix_defaultconfig
don't require a config file for defaultconfig
2024-06-09 09:25:19 +02:00
Shivaram Lingamneni
3127353b84 don't require a config file for defaultconfig 2024-06-09 03:20:33 -04:00
Shivaram Lingamneni
654381071b
update version and changelog for v2.14.0-rc1 (#2164)
* changelog for v2.14.0-rc1

* bump version string for rc1

* bump irctest
2024-06-09 03:11:33 -04:00
Shivaram Lingamneni
71671405f3
Merge pull request #2167 from slingamn/lowerlimit
lower recommended ban list limit to 100
2024-06-09 06:08:04 +02:00
Shivaram Lingamneni
aa6be594b9 lower recommended ban list limit to 100
Insp and Libera use 100, seems a bit safer
2024-06-09 00:04:54 -04:00
Shivaram Lingamneni
6326982767
Merge pull request #2165 from slingamn/banlimit
fix #2081
2024-06-04 05:53:47 +02:00
Shivaram Lingamneni
0517b5571d fix #2081
Increase default/recommended mask list size limit to 150;
SAMODE overrides enforcement of the limit.
2024-06-03 23:39:08 -04:00
Shivaram Lingamneni
1117680fdd
clean up RPL_CHANNELMODEIS logic (#2163)
Don't send RPL_CHANNELMODEIS for no-op changes to channel-user modes
2024-06-03 23:28:08 -04:00
Shivaram Lingamneni
f44d902ce3
Merge pull request #2162 from slingamn/issue2043
fix #2043
2024-06-03 00:46:57 +02:00
Shivaram Lingamneni
7318e48629 fix #2043
Add human-readable description parameters to multiline fail messages,
since they are technically required by the standard-replies spec
(although the utility of showing them to users is dubious)
2024-06-02 03:34:11 -04:00
Shivaram Lingamneni
60f7d1122d
Merge pull request #2161 from slingamn/logerror
fix #2141
2024-05-29 08:25:42 +02:00
Shivaram Lingamneni
289b78d2fd fix #2141
Log errors from attempting to delete a unix domain socket path
2024-05-29 02:24:08 -04:00
Shivaram Lingamneni
ad0149be5e
Merge pull request #2160 from slingamn/embed
fix #2157
2024-05-29 08:04:25 +02:00
Shivaram Lingamneni
d81494ac09
Merge pull request #2159 from ergochat/casefolding.2
fix #2099
2024-05-29 08:04:14 +02:00
Shivaram Lingamneni
54ca659e57
Merge pull request #2158 from slingamn/ircv3bearer.2
remove draft/bearer in favor of IRCV3BEARER
2024-05-29 08:00:07 +02:00
Shivaram Lingamneni
794b4a2483 allow null bytes in bearer tokens
(Haven't decided what to do at the spec level yet)
2024-05-29 01:54:12 -04:00
Shivaram Lingamneni
af521c844f fix #2157
Embed a copy of the ergo default config in the binary;
add `ergo defaultconfig` to print it to stdout
2024-05-27 23:08:11 -04:00
Shivaram Lingamneni
7772b55cab fix #2099
Add optional support for rfc1459 and rfc1459-strict casemappings
2024-05-27 22:16:20 -04:00
Shivaram Lingamneni
ed683bff79 remove draft/bearer in favor of IRCV3BEARER 2024-05-27 20:40:04 -04:00
Shivaram Lingamneni
5ee32cda1c
Merge pull request #2156 from slingamn/throttle.1
fix login throttle handling
2024-05-28 02:25:11 +02:00
Shivaram Lingamneni
218f6f2454 fix login throttle handling
We were checking the login throttle at the beginning of every SASL
conversation. This had several problems:

1. Pidgin (on Windows?) tries every mechanism in order, regardless of
the CAP advertisement. It would use up the default throttle allowance
trying unsupported mechanisms like CRAM-MD5.
2. The throttle was actually checked twice for AUTHENTICATE PLAIN
(once at the start of the conversation and once in AuthenticateByPassphrase).

The general pattern here is that we should check the throttle every time we
do something "expensive" (bcrypt verification, send a reset email) or
"dangerous" (anything that could lead to a bruteforce attack on passwords).
Therefore, delete the check from the AUTHENTICATE handler, and add one at
the beginning of the SCRAM conversation to replace it.
2024-05-26 05:19:41 -04:00
Shivaram Lingamneni
ca4b9c15c5
Merge pull request #2151 from slingamn/modes_forwardport
fix deadlock on channel state mutex
2024-05-06 08:47:23 +02:00
Shivaram Lingamneni
6abb291290 fix deadlock on channel state mutex 2024-05-06 02:32:40 -04:00
Shivaram Lingamneni
ccc362be84
Merge pull request #2148 from slingamn/i2p
add i2pd b32 address directions
2024-05-06 07:00:19 +02:00
Shivaram Lingamneni
19b9867409 add i2pd b32 address directions
Fixes #1686
2024-05-05 21:18:29 -04:00
Shivaram Lingamneni
f6626ddb6e bump irctest 2024-05-01 11:40:19 -04:00
Shivaram Lingamneni
40ceb4956c
Merge pull request #2145 from slingamn/issue2144
fix #2144
2024-04-15 03:22:19 +02:00
Shivaram Lingamneni
74fa04c5ea
Merge pull request #2143 from slingamn/emailsending.1
fix #2142
2024-04-15 03:22:06 +02:00
Shivaram Lingamneni
15d686c593
Merge pull request #2146 from slingamn/webirc.1
add a config switch to accept hostnames from WEBIRC
2024-04-14 20:46:01 +02:00
Shivaram Lingamneni
f96f918ff1 fix #2144
RPL_NAMREPLY should send = for normal channels and @ for secret channels,
as per Modern docs.
2024-04-13 21:51:59 -04:00
Shivaram Lingamneni
7726160ec7 add a config switch to accept hostnames from WEBIRC
See #1686; this allows i2pd to pass the i2p address to Ergo, which may be
useful for moderation under some circumstances.
2024-04-13 21:43:41 -04:00
Shivaram Lingamneni
b426dd8f93 fix #2142
Allow specifying TCP4 or TCP6 for outgoing email sending, or choosing a
specific local address to send from.
2024-04-07 15:47:01 -04:00
Shivaram Lingamneni
1f4b5248a0
Merge pull request #2140 from slingamn/issue2139
fix #2139
2024-03-29 22:50:22 +01:00
Shivaram Lingamneni
0c804f8ea3 bump irctest 2024-03-29 13:34:52 -04:00
Shivaram Lingamneni
3d2f014d4c fix #2139
Database backup filenames contained a colon character, which is disallowed
on Windows; use period instead
2024-03-29 12:32:42 -04:00
Shivaram Lingamneni
d56e4ea301
Merge pull request #2136 from slingamn/issue2135_nicknameinuse
fix #2135
2024-03-20 10:48:27 -04:00
Shivaram Lingamneni
8d082865da
fix #2133 (#2137)
* fix #2133

Don't record NICK and QUIT in history for invisible auditorium members
2024-03-17 11:42:39 -04:00
Shivaram Lingamneni
837f6ac1a2 fix #2135
Handling of reserved nicknames is special-cased due to #1594, but we want to send
ERR_NICKNAMEINUSE if the nickname is actually in use, since that doesn't pose any
client compatibility problems.
2024-03-11 01:32:39 -04:00
Shivaram Lingamneni
681e8b1292
fix #2129 (#2132)
* fix #2129

Don't print the values of environment variable overrides, just the keys

* fix unit tests
2024-02-25 10:05:36 -05:00
Shivaram Lingamneni
432d4ea860
Merge pull request #2131 from slingamn/issue2130
fix #2130
2024-02-25 03:55:54 -05:00
Shivaram Lingamneni
78f342655d clean up dead code 2024-02-25 03:52:52 -05:00
Shivaram Lingamneni
cab192e2af fix #2130
We load registered channels unconditionally; reloading them again on rehash
is incorrect. This caused buggy behavior when channel registration was
disabled in the config, but some registered channels were already loaded.
2024-02-25 03:34:21 -05:00
Matt Hamilton
c67835ce5c
Gracefully handle NS cert add myself <fp> (#2128)
* Gracefully handle NS cert add myself <fp>

A non-operator with the nick "mynick" attempts to register
a fingerprint to their authenticated account.

They /msg NickServ cert add mynick <fingerprint>

NickServ responds with "Insufficient privileges" because
they've accidentally invoked the operator syntax (to action
other accounts).

This patch allows the user to add the fingerprint if the client's
account is identical to the target account.

Signed-off-by: Matt Hamilton <m@tthamilton.com>

* Update nickserv.go

Compare the case-normalized target to Account()

---------

Signed-off-by: Matt Hamilton <m@tthamilton.com>
Co-authored-by: Shivaram Lingamneni <slingamn@cs.stanford.edu>
2024-02-14 09:56:37 -05:00
Shivaram Lingamneni
7afd6dbc74 bearer: close open jwt key files 2024-02-13 21:32:37 -05:00
Shivaram Lingamneni
ee7f818674
implement SASL OAUTHBEARER and draft/bearer (#2122)
* implement SASL OAUTHBEARER and draft/bearer
* Upgrade JWT lib
* Fix an edge case in SASL EXTERNAL
* Accept longer SASL responses
* review fix: allow multiple token definitions
* enhance tests
* use SASL utilities from irc-go
* test expired tokens
2024-02-13 18:58:32 -05:00
Shivaram Lingamneni
8475b62da4 bump irctest 2024-02-12 23:43:28 -05:00
Shivaram Lingamneni
52d15a483c
Merge pull request #2127 from slingamn/isupport_thirteen
pull out max parameters constant in isupport impl
2024-02-12 23:40:54 -05:00
Shivaram Lingamneni
f691b8c058 pull out max parameters constant in isupport impl 2024-02-11 12:38:49 -05:00
Shivaram Lingamneni
6b7bfe0c09 set up new development version 2024-02-11 00:12:22 -05:00
Shivaram Lingamneni
2098cc9f2b
Merge pull request #2126 from slingamn/go122
upgrade to go 1.22
2024-02-11 00:02:28 -05:00
Shivaram Lingamneni
4b9aa725cb upgrade to go 1.22 2024-02-10 23:46:34 -05:00
Shivaram Lingamneni
24ac3b68b4
Merge pull request #2124 from slingamn/realnamelimit
fix #2123
2024-02-10 23:25:31 -05:00
Shivaram Lingamneni
0918564edc bump irctest 2024-02-08 00:46:26 -05:00
Shivaram Lingamneni
921651f664 fix #2123
Add a configurable limit on realname length
2024-02-08 00:03:12 -05:00
Shivaram Lingamneni
d97e964b35 v2.13.0: fix go release version in changelog 2024-01-14 17:42:34 -05:00
Shivaram Lingamneni
010875ec9a bump version and changelog for v2.13.0 2024-01-14 17:40:50 -05:00
Neale Pickett
7b525f8899
Add caddy reverse proxy websocket example (#2119)
* Add caddy reverse proxy websocket example

* Use consistent hostname for caddy reverse proxy
2024-01-12 13:30:53 -05:00
Neale Pickett
3839f8ae60
Explain reverse proxy setup for websockets (#2121)
* Explain reverse proxy setup for websockets

* Update MANUAL.md

Clarify that we only support `X-Forwarded-For`

---------

Co-authored-by: Shivaram Lingamneni <slingamn@cs.stanford.edu>
2024-01-11 23:20:26 -05:00
Shivaram Lingamneni
4e574b99f3 fix changelog typo 2024-01-07 01:07:36 -05:00
Shivaram Lingamneni
9d388d8cdb
Merge pull request #2118 from slingamn/changelog
bump version and changelog for 2.13.0-rc1
2024-01-07 00:42:46 -05:00
Shivaram Lingamneni
24cf5fac45 fix #2101 2024-01-07 00:38:10 -05:00
Shivaram Lingamneni
d238eaac67 bump version and changelog for 2.13.0-rc1 2024-01-07 00:30:39 -05:00
Shivaram Lingamneni
0f059ea2cc
Merge pull request #2117 from slingamn/handlepanic
add panic handler to async client/channel writes
2024-01-05 00:21:23 -05:00
Shivaram Lingamneni
dfe2a21b17 add panic handler to async client/channel writes
See #2113 for motivation
2024-01-05 00:18:46 -05:00
Shivaram Lingamneni
1d8bbde95c
Merge pull request #2115 from slingamn/issue2114_relaymsg
fix #2114
2024-01-04 01:03:57 -05:00
Shivaram Lingamneni
580fc7096d fix #2114
Channels with slashes (or other relaymsg separators) in their names
were being falsely detected as relaymsg identifiers.
2024-01-04 01:02:10 -05:00
Shivaram Lingamneni
15c074078a
Merge pull request #2116 from slingamn/issue2113_panic
fix #2113
2024-01-04 01:01:43 -05:00
Shivaram Lingamneni
4aa1aa371d fix #2113
Persisting always-on clients was panicking if client X believed it was
a member of channel Y, but channel Y didn't have a record of client X.
2024-01-03 10:52:34 -05:00
Shivaram Lingamneni
a4d160b76d bump irctest 2023-12-24 05:14:00 -05:00
Shivaram Lingamneni
430387dec6 bump irctest 2023-12-21 12:33:54 -05:00
Shivaram Lingamneni
ce162e9279
fix #2109 (#2111)
Remove numerics associated with the retired ACC spec
2023-12-21 01:10:50 -05:00
Shivaram Lingamneni
97d6f9eddb
Merge pull request #2110 from slingamn/msgid
fix #2108
2023-12-21 01:10:24 -05:00
Shivaram Lingamneni
6be1ec3ad6
Merge pull request #2107 from ergochat/dependabot/go_modules/golang.org/x/crypto-0.17.0
Bump golang.org/x/crypto from 0.5.0 to 0.17.0
2023-12-21 01:09:32 -05:00
dependabot[bot]
16ab0a67b5
Bump golang.org/x/crypto from 0.5.0 to 0.17.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.5.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.5.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-21 04:32:32 +00:00
Shivaram Lingamneni
cc1c491afe
Merge pull request #2112 from slingamn/testvector
include a fixed test vector in password tests
2023-12-20 23:30:40 -05:00
Shivaram Lingamneni
8d80cb52e6 include a fixed test vector in password tests 2023-12-20 23:28:55 -05:00
Shivaram Lingamneni
e11bda643e fix #2108
Send Message-ID even if DKIM is not enabled, for compatibility with Gmail:

* A workaround for Ergo 2.12.0 is to enable DKIM
* You need to enable either DKIM or SPF (preferably both) to send to Gmail anyway
* You also need forward-confirmed reverse DNS, which can be tricky for IPv6...
2023-12-20 22:18:48 -05:00
Shivaram Lingamneni
b1a0e7cc5c
bump docker base image to alpine 3.19 (#2104)
* bump docker base image to alpine 3.19

Fixes #2103
2023-12-17 23:20:55 -08:00
Shivaram Lingamneni
2d44ab1cbf
Merge pull request #2100 from slingamn/dockercomposeinit
add `init: true` to docker-compose.yml
2023-11-15 07:45:39 -08:00
Shivaram Lingamneni
3102babec8 add init: true to docker-compose.yml
Follows up from #2096, #2097
2023-11-15 10:32:56 -05:00
Shivaram Lingamneni
a5af245102
add --init to suggested docker run invocations (#2097)
* add --init to suggested docker run invocations

See #2096; this should fix unreaped zombies when using an auth-script or
ip-check-script that spawns its own subprocesses, then exits before reaping
them.

* add a note on why --init
2023-11-15 00:19:32 -05:00
Shivaram Lingamneni
4fabeed895 bump irctest 2023-11-11 18:37:04 -05:00
Shivaram Lingamneni
5671ee2a36 set up new development version 2023-10-11 11:20:45 -04:00
Shivaram Lingamneni
4d9e80fe5b bump version and changelog for v2.12.0 2023-10-10 22:11:15 -04:00
Shivaram Lingamneni
7b3778989e fix ergo invocation in readme 2023-09-28 14:03:12 -04:00
Shivaram Lingamneni
e3bcb9b8a0 fix ergo invocation in readme 2023-09-28 13:56:47 -04:00
Shivaram Lingamneni
70dfe9f594
Merge pull request #2094 from slingamn/bump_irctest
bump irctest
2023-09-24 12:05:38 -07:00
Shivaram Lingamneni
70a98ac2f1 upgrade CI image to jammy 2023-09-24 13:22:46 -04:00
Shivaram Lingamneni
046ef8ce94 bump irctest 2023-09-24 13:19:59 -04:00
Shivaram Lingamneni
baf5a8465d changelog entry for #2092 2023-09-24 10:48:27 -04:00
Shivaram Lingamneni
b33e1051f7
Merge pull request #2092 from progval/patch-5
Fix typo in ACCOUNT_NAME_MUST_BE_NICK code
2023-09-24 07:34:28 -07:00
Val Lorentz
ddb804b622
Fix typo in ACCOUNT_NAME_MUST_BE_NICK code 2023-09-24 14:16:49 +02:00
Shivaram Lingamneni
3ec7f0e5cc clarify address-blacklist syntax 2023-09-18 19:46:39 -04:00
Shivaram Lingamneni
48d139a532 bump irctest 2023-09-18 19:46:38 -04:00
Shivaram Lingamneni
556bcba465 bump irctest 2023-09-17 23:46:15 -04:00
Shivaram Lingamneni
20bfb285f0 changelog tweaks 2023-09-17 23:40:52 -04:00
Shivaram Lingamneni
29b4be83bc bump version for v2.12.0-rc1 2023-09-17 23:07:54 -04:00
Shivaram Lingamneni
399b0b3f39
changelog for v2.12.0-rc1 (#2090)
* changelog for v2.12.0-rc1

* bump date
2023-09-17 23:04:34 -04:00
Shivaram Lingamneni
e7597876d9
Merge pull request #2089 from slingamn/ident
upgrade go-ident
2023-09-11 22:44:31 -07:00
Shivaram Lingamneni
3bd3c6a88a upgrade go-ident
Fixes a socket leak (that doesn't seem to be affecting tilde.town?)
2023-09-12 01:39:49 -04:00
Shivaram Lingamneni
2013beb7c8
fix #1997 (#2088)
* Fix #1997 (allow the use of an external file for the email blacklist)
* Change config key names for blacklist (compatibility break)
* Accept globs rather than regexes for blacklist by default
* Blacklist comparison is now case-insensitive
2023-09-12 01:06:55 -04:00
Simon
6b386ce2ac Update MANUAL.md for Debian 12 syntax. 2023-09-10 01:52:38 -04:00
Shivaram Lingamneni
ee22bda09c
Merge pull request #2086 from slingamn/dockerio
explicit docker.io in Dockerfile
2023-09-09 21:35:58 -07:00
Shivaram Lingamneni
202de687df explicit docker.io in Dockerfile
See #2082
2023-09-10 00:13:48 -04:00
Shivaram Lingamneni
4b00c6c48e bump irctest 2023-09-05 03:07:35 -04:00
Shivaram Lingamneni
8ac488a1ff bump irctest 2023-08-28 13:17:42 -04:00
Shivaram Lingamneni
f07707dfbc
Merge pull request #2083 from slingamn/nonames.2
implement draft/no-implicit-names
2023-08-16 08:47:05 -07:00
Shivaram Lingamneni
3b3e8c0004
Merge pull request #2084 from slingamn/go_upgrade_121
bump go to v1.21
2023-08-16 08:46:34 -07:00
Shivaram Lingamneni
f77d430d25 use maps.Clone from go1.21 2023-08-15 20:57:52 -04:00
Shivaram Lingamneni
28d9a7ff63 use slices.Contains from go1.21 2023-08-15 20:55:09 -04:00
Shivaram Lingamneni
b3abd0bf1d use slices.Reverse from go1.21 2023-08-15 20:45:00 -04:00
Shivaram Lingamneni
cc873efd0f bump go to v1.21 2023-08-15 20:37:58 -04:00
Shivaram Lingamneni
3f74612e2b implement draft/no-implicit-names 2023-08-15 20:29:57 -04:00
Shivaram Lingamneni
24ba72cfd6 bump irctest 2023-08-11 17:18:57 -04:00
Shivaram Lingamneni
17b21c8521
Merge pull request #2079 from slingamn/autojoin.1
add channel autojoin feature
2023-07-16 10:12:19 -07:00
Shivaram Lingamneni
75bd63d0bc add channel autojoin feature
See discussion on #2077
2023-07-04 21:44:18 -04:00
Shivaram Lingamneni
3c4f83cf6e
Merge pull request #2078 from tacerus/apparmor
Import AppArmor profile
2023-07-02 08:16:08 -07:00
67d10bc63b
Import AppArmor profile
Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
2023-07-02 00:07:59 +02:00
Shivaram Lingamneni
6d642bfe93
Merge pull request #2074 from slingamn/ircgo_upgrade
upgrade to irc-go v0.4.0
2023-06-13 23:53:52 -07:00
Shivaram Lingamneni
ad3ad97047 upgrade to irc-go v0.4.0 2023-06-14 02:46:14 -04:00
Shivaram Lingamneni
d14ff9b3d5
Merge pull request #2073 from slingamn/issue2013.1
fix #2013
2023-06-08 07:06:44 -07:00
Shivaram Lingamneni
dfe84bc1c2 bump irctest 2023-06-05 04:22:40 -04:00
Shivaram Lingamneni
0f39fde647 remove insecure reattach check
See #2013; given that plaintext is deprecated now, it seems like there is no
added value from continuing to police this.
2023-06-05 04:22:40 -04:00
Shivaram Lingamneni
7d6e48ed2a bump irctest 2023-06-04 03:23:11 -04:00
Shivaram Lingamneni
e4c8f041f2
Merge pull request #2072 from csmith/misc/docker-alpine-upgrade
Dockerfile: `apk upgrade` before `add`
2023-06-02 16:38:51 -07:00
Chris Smith
783b579003 Dockerfile: apk upgrade before add
The base golang image ships with some packages pre-installed,
but they're not necessarily the latest. If we try to add a
package that (transitively) depends on one of the existing ones,
it'll fail if it's expecting a newer version.

To address this, simply `apk upgrade` before trying to `apk add`.

Closes #2071
2023-06-03 00:15:58 +01:00
Shivaram Lingamneni
07cc4f8354
Merge pull request #2070 from slingamn/batchfix
fix incorrect chathistory batch types
2023-06-02 04:00:34 -07:00
Shivaram Lingamneni
f100c1d0fa fix incorrect chathistory batch types
This was introduced in 38a6d17ee5ce6e1096c3dfd6d11f6f35d9a71ca6
2023-06-02 06:56:45 -04:00
Shivaram Lingamneni
2aded271c5
Merge pull request #2069 from slingamn/nestedbatch.1
some cleanups
2023-06-02 00:53:35 -07:00
Shivaram Lingamneni
3d4d8228aa bump irctest 2023-06-02 02:58:32 -04:00
Shivaram Lingamneni
60af8ee491 clean up force-trailing logic 2023-06-02 02:58:09 -04:00
Shivaram Lingamneni
38a6d17ee5 clean up nested batch logic 2023-06-01 06:29:22 -04:00
Shivaram Lingamneni
d082ec7ab9
don't send multiline responses to CAP LS 301 (#2068)
* don't send multiline responses to CAP LS 301

This is more or less explicitly prohibited by the spec:

https://ircv3.net/specs/extensions/capability-negotiation.html#multiline-replies-to-cap-ls-and-cap-list

* switch to whitelist model to be future-proof

* bump irctest to include test

* add a unit test
2023-05-31 23:22:16 -04:00
Shivaram Lingamneni
3e68694760
Merge pull request #2067 from slingamn/issue2066
fix #2066
2023-05-30 23:12:19 -07:00
Val Lorentz
48f8c341d7
Implement draft/message-redaction (#2065)
* Makefile: Add dependencies between targets

* Implement draft/message-redaction for channels

Permission to use REDACT mirrors permission for 'HistServ DELETE'

* Error when the given targetmsg does not exist

* gofmt

* Add CanDelete enum type

* gofmt

* Add support for PMs

* Fix documentation of allow-individual-delete.

* Remove 'TODO: add configurable fallback'

slingamn says it's probably not desirable, and I'm on the fence.
Out of laziness, let's omit it for now, as it's not a regression
compared to '/msg HistServ DELETE'.

* Revert "Makefile: Add dependencies between targets"

This reverts commit 2182b1da69ceaafad30859e45be0645d6c915b2c.

---------

Co-authored-by: Val Lorentz <progval+git+ergo@progval.net>
2023-05-31 01:16:14 -04:00
Shivaram Lingamneni
00cfe98461 fix #2066
CHATHISTORY TARGETS response should not be in a batch unless the client has
explicitly requested the batch cap.
2023-05-29 22:22:01 -04:00
Shivaram Lingamneni
bf33fba33a
Merge pull request #2064 from slingamn/issue2063
fix #2063
2023-05-22 22:27:33 -07:00
Shivaram Lingamneni
0710c7e12a bump irctest to include regression test for #2063 2023-05-23 01:19:36 -04:00
Shivaram Lingamneni
e84793d7ee fix #2063
In #2058 we introduced two bugs:

* A nil dereference when an outside user attempts to speak
* Ordinary copy of a modes.ModeSet (which should only be accessed via atomics)

This fixes both issues.
2023-05-22 12:29:55 -04:00
Shivaram Lingamneni
2c0928f94d
Merge pull request #2061 from slingamn/xterm.1
upgrade to x/term instead of crypto/ssh/terminal
2023-04-19 01:26:05 -07:00
Shivaram Lingamneni
0d8dcbecf6 upgrade to x/term instead of crypto/ssh/terminal
Simplify some of the password hashing logic. This requires a bump of irctest.
2023-04-19 02:58:50 -04:00
Shivaram Lingamneni
eeec481b8d
tweaks to NAMES implementation (#2058)
* tweaks to NAMES implementation

* tweak member caching

* add a benchmark for NAMES
2023-04-14 02:15:56 -04:00
Shivaram Lingamneni
378d88fee2
Merge pull request #2055 from slingamn/doc_update
add apache websocket example
2023-03-09 17:46:03 -08:00
Shivaram Lingamneni
c4db4984a6
Merge pull request #2056 from avollmerhaus/master
Add bsd-rc init script to distrib
2023-03-09 16:11:09 -08:00
Aljoscha Vollmerhaus
04f8791dd6
add sections + usage information to bsd-rc README 2023-03-09 11:14:29 +01:00
Aljoscha Vollmerhaus
37eb5f5804
Add bsd-rc init script 2023-03-09 11:09:03 +01:00
Shivaram Lingamneni
6e011cd536 add apache websocket example
Fixes #2050
2023-03-09 01:05:34 -05:00
Shivaram Lingamneni
295a567eda
Merge pull request #2041 from mogad0n/killresponseupdate
Update response string when killing always on clients
2023-03-04 23:31:11 -08:00
Shivaram Lingamneni
db0910d82d fix linter error
See #2052
2023-03-04 23:29:16 -08:00
Shivaram Lingamneni
374cf8ef97
Merge pull request #2053 from slingamn/killmsg
tweak KILL message
2023-02-28 19:08:46 -08:00
Shivaram Lingamneni
eb83df420b tweak KILL message
Remove `<no reason supplied>`, make default KILL anonymous
2023-02-27 03:34:38 -05:00
Shivaram Lingamneni
3fca52ba38
Merge pull request #2049 from slingamn/implicittls.1
support implicit TLS for mail submission agents
2023-02-18 19:12:27 -08:00
Shivaram Lingamneni
3d1412a898
Merge pull request #2051 from slingamn/tidy
go mod tidy
2023-02-18 16:34:37 -08:00
Shivaram Lingamneni
b155e5315b go mod tidy 2023-02-18 19:32:19 -05:00
Shivaram Lingamneni
7c53b9430a support implicit TLS for mail submission agents
Fixes #2048
2023-02-17 00:07:21 -05:00
Shivaram Lingamneni
3c59ce964d fix Dockerfile
This broke in #2047
2023-02-11 21:55:57 -05:00
Shivaram Lingamneni
ae04fb3d0a
Merge pull request #2047 from slingamn/make
change default make target to `build`
2023-02-11 18:49:59 -08:00
Shivaram Lingamneni
ba40d57afd bump irctest 2023-02-11 21:36:21 -05:00
Shivaram Lingamneni
697f34995b change default make target to build
Fixes #2046
2023-02-11 21:35:03 -05:00
Shivaram Lingamneni
19dbf3a531
Merge pull request #2045 from slingamn/go_upgrade
upgrade to go 1.20
2023-02-05 21:41:35 -08:00
Shivaram Lingamneni
8b6b2cabc3 upgrade to go 1.20 2023-02-06 00:37:51 -05:00
Shivaram Lingamneni
1da11ae8ae
implement draft/pre-away (#2044)
* implement draft/pre-away
* clean up some subtleties in auto-away aggregation.
* consistently apply auto-away only to always-on
* `AWAY *` should not produce user-visible changes wherever possible
2023-02-05 00:50:14 -05:00
Shivaram Lingamneni
12f7796933
Merge pull request #2042 from slingamn/msgreftypes
publish MSGREFTYPES 005 token
2023-02-04 21:49:27 -08:00
Shivaram Lingamneni
fc89d72045 publish MSGREFTYPES 005 token
https://github.com/ircv3/ircv3-specifications/pull/510
2023-02-02 14:28:37 -05:00
0653f90b4f update response when killing alwayson targets 2023-01-31 13:27:02 +05:30
Shivaram Lingamneni
abb38ce8a1 bump irctest 2023-01-25 21:52:56 -05:00
Shivaram Lingamneni
5ecf19d01e
Merge pull request #2038 from slingamn/ws_optimization.1
tweaks to websocket handling
2023-01-23 04:25:14 -08:00
Shivaram Lingamneni
abc71684f3 always validate UTF8 from websockets 2023-01-22 14:45:16 -05:00
Shivaram Lingamneni
9439e9b9e1 allow resizing the ws read buffer 2023-01-21 19:10:25 -05:00
Shivaram Lingamneni
5eaf7b37e5 reduce websocket read allocations
See #2037
2023-01-21 19:10:17 -05:00
Shivaram Lingamneni
4317016a09
Merge pull request #2028 from slingamn/channels_taketwo.1
refactor of channel persistence to use UUIDs
2023-01-15 08:01:37 -08:00
Shivaram Lingamneni
7193fa3a3c
Merge pull request #2036 from slingamn/docker
bump docker actions
2023-01-15 06:02:21 -08:00
Shivaram Lingamneni
cd36604efe bump docker actions
Should fix https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ ?
2023-01-15 08:59:53 -05:00
Shivaram Lingamneni
8199edee6c
Merge pull request #2035 from slingamn/actions
bump github actions
2023-01-15 05:54:20 -08:00
Shivaram Lingamneni
81832a26bc bump github actions
https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/
2023-01-15 08:46:18 -05:00
Shivaram Lingamneni
8690a7648b
Merge pull request #2034 from slingamn/deps
dependency upgrades for v2.12 release cycle
2023-01-15 05:44:03 -08:00
Shivaram Lingamneni
7e6c658cad bump irc-go 2023-01-15 08:31:00 -05:00
Shivaram Lingamneni
eb84103865 upgrade other x dependencies 2023-01-15 08:30:18 -05:00
Shivaram Lingamneni
7a82554f9d upgrade mysql 2023-01-15 08:28:13 -05:00
Shivaram Lingamneni
05e5fe3444 upgrade x/text 2023-01-15 08:27:18 -05:00
Shivaram Lingamneni
3f5de80afd upgrade buntdb and dependencies 2023-01-15 08:26:32 -05:00
Shivaram Lingamneni
b2087977d0
Merge pull request #2032 from slingamn/scram.1
recommended default: advertise SCRAM
2023-01-15 04:16:28 -08:00
Shivaram Lingamneni
177133a96f
Merge pull request #2033 from slingamn/rehash
fix #2031
2023-01-15 04:13:53 -08:00
Shivaram Lingamneni
b16350e559
Merge pull request #2030 from slingamn/roundtime
round wait times to the nearest millisecond
2023-01-15 04:13:43 -08:00
Shivaram Lingamneni
16e214e4fb fix #2031
Sanitize the in-band error message from REHASH
2023-01-12 06:58:18 -05:00
Shivaram Lingamneni
46d32520c7 recommended default: advertise SCRAM
Fixes #1782
2023-01-11 09:21:47 -05:00
Shivaram Lingamneni
f72a6fa011 round wait times to the nearest millisecond 2023-01-08 06:36:04 -05:00
Shivaram Lingamneni
1e6dee15b2
Merge pull request #2029 from slingamn/listenconfig.2
make ReloadableListener lock-free
2023-01-06 01:40:18 -08:00
Shivaram Lingamneni
3ceff6a8b1 make ReloadableListener lock-free
Also stop attaching the *tls.Config to the wrapped connection,
since this forces it to be retained beyond its natural lifetime.
2023-01-05 20:18:14 -05:00
Shivaram Lingamneni
7ce0636276 refactor of channel persistence to use UUIDs 2023-01-04 05:06:21 -05:00
Shivaram Lingamneni
bceae9b739 add standard-replies capability 2023-01-01 07:08:44 -08:00
Shivaram Lingamneni
30fbfe4cc0 disable cgo for goreleaser 2023-01-01 07:08:10 -08:00
Shivaram Lingamneni
2a828bb783 clarify the meaning of the password section 2022-12-30 07:20:46 -08:00
Shivaram Lingamneni
4b3a6cb611
Merge pull request #2025 from slingamn/cgo
Fix #2023 (disable dynamic linking by default)
2022-12-25 23:41:55 -08:00
Shivaram Lingamneni
f00fd452be Fix #2023
Disable dynamic linking by default.
2022-12-26 02:27:28 -05:00
Shivaram Lingamneni
f6f7315458 bump version for new development cycle 2022-12-25 03:17:21 -05:00
Shivaram Lingamneni
1e1acdae21
Merge pull request #2022 from slingamn/changelog
final updates for v2.11.0-rc1
2022-12-24 23:22:46 -08:00
Shivaram Lingamneni
df8eef5b0a bump version for stable release 2022-12-25 02:09:51 -05:00
Shivaram Lingamneni
23ba58b327 final changelog for v2.11.0 2022-12-25 02:09:33 -05:00
Shivaram Lingamneni
bf4f3008d4
Merge pull request #2021 from FiskFan1999/ns
Fix SAREGISTER short help in SAVERIFY command
2022-12-24 23:02:50 -08:00
William Rehwinkel
63c08ce537
Fix SAREGISTER short help in SAVERIFY command 2022-12-24 13:13:38 -05:00
Shivaram Lingamneni
f7ab0fb59e tweak changelog 2022-12-19 03:55:15 -05:00
Shivaram Lingamneni
d0c01301fd update version number for v2.11.0-rc1 2022-12-18 00:25:50 -05:00
Shivaram Lingamneni
a052b82c78
don't reference SIGUSR1 on windows (#2018) 2022-12-18 00:15:55 -05:00
Shivaram Lingamneni
238407c70e
draft changelog for v2.11.0-rc1 (#2016)
* draft changelog for v2.11.0-rc1

* add another entry

* copyedits
2022-12-18 00:01:32 -05:00
Shivaram Lingamneni
9ab8b6710c
Merge pull request #2015 from slingamn/ascii
change default casefolding to ascii
2022-12-11 18:57:35 -08:00
Shivaram Lingamneni
05e5e88de4 change default casefolding to ascii
See discussion on #1718
2022-12-11 19:41:15 -05:00
Shivaram Lingamneni
d17faf6bcb fix #1975
Provide a nondestructive stack trace dump option even when the http pprof
listener is disabled
2022-12-10 22:15:37 -08:00
Shivaram Lingamneni
77de026961 persistence broadcast needs a cap check 2022-12-10 22:05:46 -08:00
Shivaram Lingamneni
898f84c613 update persistence spec link 2022-12-10 22:05:46 -08:00
Shivaram Lingamneni
ae1de2554e add persistence broadcasting 2022-12-10 22:05:46 -08:00
Shivaram Lingamneni
893922afe0 don't report PERSISTENCE STATUS in reg burst for anonymous clients 2022-12-10 22:05:46 -08:00
Shivaram Lingamneni
99d27ff737 initial implementation of draft/persistence 2022-12-10 22:05:46 -08:00
Shivaram Lingamneni
fa3de3e149 fix #1983
TAGMSG should not get automatic RPL_AWAY replies
2022-12-10 21:01:28 -08:00
Shivaram Lingamneni
2bfa13b7d0
Merge pull request #2012 from slingamn/doc_update
document that persistent history is not affected by buffer limits
2022-12-10 20:54:28 -08:00
Shivaram Lingamneni
25e698d57f document that persistent history is not affected by buffer limits 2022-12-06 00:18:59 -05:00
Shivaram Lingamneni
30b760483e
Merge pull request #2010 from slingamn/who_services.1
fix #1850
2022-12-03 19:06:44 -08:00
Shivaram Lingamneni
825cdab67d fix #1850
Add WHO responses for services
2022-12-02 07:23:29 -05:00
Shivaram Lingamneni
f665525735
Merge pull request #2009 from slingamn/both_005s
re-add draft/CHATHISTORY 005
2022-12-01 22:43:32 -08:00
Shivaram Lingamneni
35b5613349 re-add draft/CHATHISTORY 005
Kiwi expects it due to https://github.com/kiwiirc/kiwiirc/pull/1244 , but
the corresponding spec change only altered the cap name, not the 005 name.
2022-12-02 01:30:46 -05:00
Shivaram Lingamneni
a5983a1bd1
Merge pull request #2008 from slingamn/chathistory_isupport
fix CHATHISTORY 005 token name
2022-11-30 01:16:28 -08:00
Shivaram Lingamneni
ebda5e6d9a bump irctest 2022-11-30 04:13:04 -05:00
Shivaram Lingamneni
e40f550af8 fix CHATHISTORY 005 token name
Unclear where we got draft/CHATHISTORY from, it looks like the merged drafts
have always used unprefixed CHATHISTORY as the token name.
2022-11-30 04:10:47 -05:00
Shivaram Lingamneni
e20c983b57
fix #2002 (#2003)
* fix #2002

`CS AMODE #channel +f nickname` is invalid, but was being accepted
incorrectly.

* simplify logic
2022-11-06 14:41:29 -05:00
Shivaram Lingamneni
c3b3bf9941
Merge pull request #2006 from progval/ratified-extmonitor
Use ratified extended-monitor cap name
2022-11-06 11:40:31 -08:00
Valentin Lorentz
dd8073208c Use ratified extended-monitor cap name
https://github.com/ircv3/ircv3-specifications/pull/508
2022-11-01 07:41:55 +01:00
Shivaram Lingamneni
062491ebfc
Merge pull request #2005 from PeGaSuS-Coder/patch-1
Update MANUAL.md
2022-10-22 16:05:25 -07:00
Shivaram Lingamneni
7df041d0a6
remove emphasis on "after" 2022-10-22 19:03:33 -04:00
PeGaSuS
06a204d0d3
Update MANUAL.md
- Change instructions to place the script from `post` to `deploy` which will only update the certificate and key IF the renewal is successful.
2022-10-21 22:21:02 +02:00
Shivaram Lingamneni
a9c77af1cb
Merge pull request #1998 from slingamn/away
fix #1996
2022-09-11 01:44:18 -07:00
Shivaram Lingamneni
4e0d2d65e8 fix #1996
According to the de facto standard, `AWAY :\r\n` is equivalent to `AWAY\r\n`.
Our behavior was inconsistent before, now it consistently matches the de facto
standard.
2022-09-11 04:09:26 -04:00
Shivaram Lingamneni
57a213123f
Merge pull request #1995 from slingamn/trylock.1
replace some utils.Semaphore with (*sync.Mutex).TryLock
2022-09-07 11:14:22 -07:00
Shivaram Lingamneni
746309e386 replace some utils.Semaphore with (*sync.Mutex).TryLock
See #1994
2022-09-02 04:25:39 -04:00
Shivaram Lingamneni
531a1d6864 bump irctest 2022-08-28 01:58:42 -04:00
Shivaram Lingamneni
0e8f447326
Merge pull request #1993 from slingamn/fakelag_budget.3
exempt a configurable number of MARKREAD commands from fakelag
2022-08-22 20:26:11 -07:00
Shivaram Lingamneni
7ad31497c2 exempt a configurable number of MARKREAD commands from fakelag 2022-08-22 23:23:17 -04:00
Shivaram Lingamneni
7d6ff58bf8
Merge pull request #1992 from slingamn/issue1991_who.1
fix #1991
2022-08-22 20:07:44 -07:00
Shivaram Lingamneni
68bd2d87e0 fix #1991
WHO <nickname> should not respect +i
2022-08-22 23:03:17 -04:00
Shivaram Lingamneni
8ff5a048f3
Merge pull request #1989 from slingamn/atomic_uint64.1
use new aligned atomic types everywhere
2022-08-21 10:02:06 -07:00
Shivaram Lingamneni
594991d6cc
Merge pull request #1988 from mogad0n/fix/ubanlistduration
/UBAN LIST explicitly states 'indefinite' durations for Klines/Dlines
2022-08-20 21:51:01 -07:00
df234b842e uban list explicitly shows indefinite durations 2022-08-11 03:04:20 +05:30
Shivaram Lingamneni
35128bfc23 use new aligned atomic types everywhere
See 69448b13a10a14517 / #1969; the compiler can now ensure that a uint64
intended for atomic access is always aligned to a 64-bit boundary.
Convert atomic operations on uint32s and pointers as well.
2022-08-10 02:47:39 -04:00
Shivaram Lingamneni
507dc2d838
Merge pull request #1987 from slingamn/go_upgrade
upgrade go to 1.19
2022-08-03 18:16:33 -07:00
Shivaram Lingamneni
a99c8a42f9 remove utils.ConfigStore in favor of atomic.Pointer[T] 2022-08-03 00:59:00 -04:00
Shivaram Lingamneni
de1be675f5 upgrade go to 1.19 2022-08-03 00:56:19 -04:00
Shivaram Lingamneni
5b72cd8622 apply go1.19 gofmt 2022-08-03 00:54:50 -04:00
Shivaram Lingamneni
096c12fb52
Merge pull request #1981 from slingamn/issue1980
fix #1980
2022-07-14 19:07:59 -07:00
Shivaram Lingamneni
1d10eb934a fix #1980
Sanitize ::1 to 0::1 in WHOX output
2022-07-14 21:53:36 -04:00
Shivaram Lingamneni
26e0dae11d
Merge pull request #1977 from slingamn/deps
upgrade dependencies for v2.11 release cycle
2022-06-17 07:50:54 -07:00
Shivaram Lingamneni
321ff109b1 upgrade x/crypto 2022-06-17 10:47:28 -04:00
Shivaram Lingamneni
86f124e938 upgrade buntdb 2022-06-17 10:46:22 -04:00
Shivaram Lingamneni
2138847984
Merge pull request #1976 from slingamn/parallelize_testing
parallelize tests via the native go mechanism
2022-06-16 22:03:38 -07:00
Shivaram Lingamneni
f032fda48d parallelize tests via the native go mechanism 2022-06-16 14:56:28 -04:00
Shivaram Lingamneni
2cace0b5a2
Merge pull request #1973 from progval/crowdin-url
Update Crowdin URL
2022-06-11 18:46:53 -07:00
Valentin Lorentz
4208e11571 Update Crowdin URL 2022-06-11 22:33:47 +02:00
Shivaram Lingamneni
c0e7aac862
Merge pull request #1970 from ergochat/devel+issue1969_alignment
fix #1969
2022-06-10 07:53:04 -07:00
Shivaram Lingamneni
69448b13a1 fix #1969
On a 32-bit architecture, 64-bit atomic loads and stores must be aligned to a
64-bit boundary. Since the (mysql.MySQL) struct is directly included in the
Server struct, it is impossible to guarantee this via the standard technique
of putting the 64-bit value at the beginning of the struct definition
(since the point at which it is included in the parent struct may cross a
64-bit boundary).

This optimization is probably pointless anyway, adding an additional
indirection won't make a difference.
2022-06-10 10:35:56 -04:00
Shivaram Lingamneni
810ec75f95 bump irctest 2022-05-29 15:47:34 -04:00
Shivaram Lingamneni
86f7668c68 set up new development version 2022-05-29 15:46:36 -04:00
Shivaram Lingamneni
e8cd87d8fd
Merge pull request #1967 from slingamn/release
bump version and changelog for v2.10.0
2022-05-29 12:14:45 -07:00
Shivaram Lingamneni
101fd53d6d bump version and changelog for v2.10.0 2022-05-29 02:24:18 -04:00
Shivaram Lingamneni
acd95b9924 bump version and changelog for v2.10.0-rc2 2022-05-23 15:16:51 -04:00
Shivaram Lingamneni
40d70b8aeb
Merge pull request #1962 from slingamn/readmarker_again
only send MARKREAD to sessions with the read-marker cap
2022-05-20 02:03:31 -04:00
Shivaram Lingamneni
ef088373a8 only send MARKREAD to sessions with the read-marker cap 2022-05-20 01:58:14 -04:00
Shivaram Lingamneni
ae55a4c660
Merge pull request #1961 from slingamn/readmarker
fix critical bugs in draft/read-marker
2022-05-20 01:51:41 -04:00
Shivaram Lingamneni
2b86660e5c fix read markers not being reloaded on restart 2022-05-20 01:46:41 -04:00
Shivaram Lingamneni
c3d4be45f1 fix timestamp syntax in MARKREAD 2022-05-20 01:46:41 -04:00
Shivaram Lingamneni
67b2f4ccd2
README.md: fix docker repository link 2022-05-16 11:53:33 -04:00
Shivaram Lingamneni
104d0321e8 bump version and changelog for v2.10.0-rc1 2022-05-16 01:25:37 -04:00
Shivaram Lingamneni
14d1614bba
Merge pull request #1956 from slingamn/changelog.2
changelog updates for v2.10.0-rc1
2022-05-16 01:24:23 -04:00
Shivaram Lingamneni
e48c3fa687
Merge pull request #1957 from slingamn/saregister_responses
fix #1905
2022-05-16 00:38:13 -04:00
16b8d9090b
Documentation: add Python3 syntax highlighting, +i/AMODE documentation and add FAQ on AMODE privileges (#1959)
* MANUAL.md: correct shebangs

* docs/{USERGUIDE,MANUAL}.md: mention amode +v joining through +i

* MANUAL.md: add a FAQ on special privileges of AMODEs

* USERGUIDE.md: add missing channel name

* MANUAL.md: mention that special privileges are cumulative

* Revert "MANUAL.md: correct shebangs"

This reverts commit 75a77c55370089f2b144abc4104db5e13dcc2cb7.

* MANUAL.md: restore syntax highlighting for python3
2022-05-15 11:07:49 -04:00
Shivaram Lingamneni
5e5cc3040b
Update CHANGELOG.md
Co-authored-by: Aminda Suomalainen <suomalainen+git@mikaela.info>
2022-05-14 21:31:01 -04:00
Shivaram Lingamneni
bcaed1aff1 update documentation of moderation techniques
Fixes #1697
2022-05-13 17:30:47 -04:00
Shivaram Lingamneni
7192df4592 update documentation of operator capabilities 2022-05-13 17:18:42 -04:00
Shivaram Lingamneni
da07c0072c bump irctest to latest 2022-05-13 16:42:30 -04:00
Shivaram Lingamneni
6f9e07d2a2 fix #1905
NS SAREGISTER should send machine-readable responses. A simple approach:
check if the account-registration cap is enabled, and if so, send the
the same responses that would be sent by the REGISTER command.
2022-05-13 15:35:11 -04:00
Shivaram Lingamneni
52e0f8e7e9 changelog updates for v2.10.0-rc1 2022-05-13 14:53:39 -04:00
Shivaram Lingamneni
d6d5bbe27b
Merge pull request #1955 from slingamn/history_privs
exempt operators from history cutoffs
2022-05-13 02:04:56 -04:00
Shivaram Lingamneni
737697d1d4 exempt operators from history cutoffs
See #1593; this enables a client-side implementation of bulk deletion
2022-05-12 16:43:11 -04:00
Shivaram Lingamneni
dd75eb1084 fix incorrect HOSTSERV HELP SET
Reported by @Mikaela
2022-05-06 14:48:01 -04:00
Shivaram Lingamneni
a13235880c
Merge pull request #1954 from slingamn/accept.1
fix #1688
2022-05-06 11:40:17 -04:00
Shivaram Lingamneni
87789676c0 add tests covering (*AcceptManager).Unaccept 2022-05-05 22:43:33 -04:00
Shivaram Lingamneni
c454c45d6a
Merge pull request #1953 from slingamn/issue1886_unregistered.1
fix #1886
2022-05-05 22:40:50 -04:00
Shivaram Lingamneni
4948b48b8f
Merge pull request #1952 from slingamn/saverify.1
add SAVERIFY command
2022-05-05 22:40:07 -04:00
Shivaram Lingamneni
c5579a6a34 fix #1688
* Add ACCEPT-tracking functionality (authorizing users to send DMs
  despite +R or other applicable restrictions)
* Sending a DM automatically accepts the recipient
* Add explicit ACCEPT command
2022-05-05 22:34:43 -04:00
Shivaram Lingamneni
b11dc1c84c fix #1886
Add more clarify in NS INFO and SAREGISTER about unregistered nicknames
2022-05-05 01:04:28 -04:00
Shivaram Lingamneni
78548aa9df add SAVERIFY command
Fixes #1924
2022-05-04 16:41:01 -04:00
Shivaram Lingamneni
03092769e7 bump irctest 2022-05-04 14:59:10 -04:00
Shivaram Lingamneni
d5814c10ab
Merge pull request #1949 from slingamn/generic_config.2
genericize atomic config changes
2022-05-04 01:30:52 -04:00
Shivaram Lingamneni
34ad3a2dc1 ConfigStore: clarify intended use 2022-05-03 23:27:24 -04:00
Shivaram Lingamneni
f7853b15ca
Merge pull request #1950 from slingamn/kline_snotice.3
fix #1941
2022-05-03 22:54:54 -04:00
Shivaram Lingamneni
077081076c fix #1941
KLINE'd clients would produce a QUIT snotice without a corresponding
CONNECT snotice; explicitly suppress the QUIT snotice.
2022-05-03 13:13:29 -04:00
Shivaram Lingamneni
dea2e7961a
Merge pull request #1948 from slingamn/reverse_again
remove history.ReverseCorrespondents in favor of generics
2022-05-03 12:42:23 -04:00
Shivaram Lingamneni
c603d41d08 genericize atomic config changes 2022-05-03 11:12:11 -04:00
Shivaram Lingamneni
c87dead39b remove history.ReverseCorrespondents in favor of generics 2022-05-03 02:45:36 -04:00
Shivaram Lingamneni
66bf6244f3
Merge pull request #1947 from Mikaela/docs-op-invite
MANUAL.md: clarify appropiate privs for invite
2022-05-01 16:44:51 -04:00
e6905f4543
MANUAL.md: clarify appropiate privs for invite
Resolves: #1946
2022-05-01 23:29:38 +03:00
Shivaram Lingamneni
71fe4ecf48
Merge pull request #1945 from slingamn/generic_reverse
use genericized slice-reversing function
2022-04-30 21:29:25 -04:00
Shivaram Lingamneni
8eaf6f5166
Merge pull request #1944 from slingamn/kline_snotice.1
make quit logging more consistent
2022-04-30 21:29:14 -04:00
Shivaram Lingamneni
2df5fb1956 use genericized slice-reversing function 2022-04-29 13:39:11 -04:00
Shivaram Lingamneni
42883972a8 make quit logging more consistent
Log it at level INFO for parity with connect / connect-ip
2022-04-28 14:19:11 -04:00
Shivaram Lingamneni
2a3b8e648c
Merge pull request #1940 from slingamn/connect_faq
manual: add faq on connection problems
2022-04-28 03:21:38 -04:00
Shivaram Lingamneni
ae5e1fb49f manual: add faq on connection problems 2022-04-27 15:13:02 -04:00
Shivaram Lingamneni
15303d0247
consistently plug Goguma in documentation (#1939)
* consistently plug Goguma in documentation

* fix userguide header
2022-04-27 13:24:27 -04:00
Shivaram Lingamneni
432f0f62d5
Merge pull request #1938 from slingamn/ratified_bot
bump bot mode spec to ratified version
2022-04-26 15:55:05 -04:00
Shivaram Lingamneni
374bd834fd bump irctest temporarily for new bot tag name 2022-04-26 15:51:55 -04:00
Shivaram Lingamneni
5ecba1d40b use ratified bot mode tag name 2022-04-26 15:43:24 -04:00
Shivaram Lingamneni
5c7df07d91
Merge pull request #1936 from slingamn/nick_empty
fix #1933
2022-04-25 18:02:31 -04:00
Shivaram Lingamneni
911b00787b
Merge pull request #1937 from slingamn/whoischannels
fix #1935
2022-04-25 18:01:48 -04:00
Shivaram Lingamneni
2b8eb93c00 clean up magic numbers 2022-04-24 11:57:21 -04:00
Shivaram Lingamneni
51cdebf167 fix #1935
RPL_WHOISCHANNELS didn't have proper line breaks
2022-04-24 02:47:31 -04:00
Shivaram Lingamneni
61fd7a2534 fix the rest of #1933
`NICK :` pre-registration needs to be special-cased to immediately
send ERR_NONICKNAMEGIVEN (unlike erroneous nonempty nicknames,
which are processed when registration is complete)
2022-04-24 01:39:45 -04:00
Shivaram Lingamneni
7201f14b8b partial fix for #1933
If the nickname must equal the account name (because always-on or
force-nick-equals-account), the correct error response to an empty
or otherwise invalid nickname is the usual "You must use your account
name as your nickname".
2022-04-24 00:31:20 -04:00
Shivaram Lingamneni
504cc44bf7
Merge pull request #1934 from progval/patch-5
Fix implementation of `LIST <n`
2022-04-18 23:58:54 -04:00
Val Lorentz
379632a9e6
Fix implementation of LIST <n 2022-04-16 22:55:58 +02:00
Shivaram Lingamneni
1f08c97238
Merge pull request #1926 from slingamn/readmarker.6
implement draft/read-marker capability
2022-04-08 01:33:06 -04:00
Shivaram Lingamneni
2c488f5ebf
Merge pull request #1929 from slingamn/issue1928_list_err
fix #1928
2022-04-07 18:21:55 -04:00
Shivaram Lingamneni
2fb8b836db fix #1928
LIST should not return ERR_NOSUCHCHANNEL for nonexistent channels
2022-04-07 11:44:23 -04:00
Shivaram Lingamneni
ac2fc0da28
Merge pull request #1927 from FiskFan1999/deletemessagewarn
histserv delete now requires two params
2022-04-02 21:27:13 -04:00
William Rehwinkel
934ad1cec2 histserv delete now requires two params 2022-04-01 20:52:09 -04:00
Shivaram Lingamneni
1adda8d42c bump irctest 2022-03-30 23:16:11 -04:00
Shivaram Lingamneni
32f7868bfd implement draft/read-marker capability 2022-03-30 23:16:09 -04:00
Shivaram Lingamneni
6bd94391ef Dockerfile: un-pin alpine version 2022-03-30 11:45:09 -04:00
Shivaram Lingamneni
b7e2bd9f33
Merge pull request #1925 from slingamn/generics
upgrade to go 1.18, start using generics
2022-03-30 11:42:06 -04:00
Shivaram Lingamneni
fd5317e68e bump .github and Dockerfile for go1.18 2022-03-30 01:17:05 -04:00
Shivaram Lingamneni
a549827f17 upgrade to go 1.18, use generics 2022-03-30 00:44:51 -04:00
Shivaram Lingamneni
446c654dea docs: add a caution about operator passwords with spaces 2022-03-15 16:28:01 -04:00
Shivaram Lingamneni
6d892fe371 bump irctest to latest 2022-03-06 02:06:38 -05:00
Shivaram Lingamneni
9f6e26450b make SAREGISTER override DEFCON
DEFCON 4 and lower were blocking SAREGISTER. This is wrong; admins should be
allowed to make new accounts even under DEFCON (this may be needed
specifically to work around the DEFCON restriction).
2022-03-01 07:57:15 -05:00
William Rehwinkel
4010f3fc02
Fix #1911 +s channels don't appear in /list even though on the channel (#1923)
* Fix #1911 +s channels don't appear in /list even though on the channel

* use channel.HasClient instead of custom iterative checker
2022-02-28 20:31:16 -05:00
Shivaram Lingamneni
99294b8968 docs: expand FAQ on coerce-ident 2022-02-23 07:56:28 -05:00
Shivaram Lingamneni
ba474b9b9a bump irctest to latest 2022-02-21 16:56:24 -05:00
Shivaram Lingamneni
302c9cb908
Merge pull request #1919 from slingamn/default
bump recommended CHATHISTORY limit to 1000
2022-02-20 15:42:31 -05:00
Shivaram Lingamneni
87d9addcfc
Merge pull request #1920 from thesamesam/openrc-init
distrib: refine OpenRC init scripts
2022-02-19 22:53:35 -05:00
Sam James
7c766b2096
distrib: refine OpenRC init scripts
- Add logging to init script
- Add delay so OpenRC realises if we crashed quickly b/c of e.g. bad config
   file
- General cleanups (like supporting multiple instances, style changes)

This should make it a lot easier to see what's going wrong when something
breaks.

Bug: ergochat/ergo#1914
Signed-off-by: Sam James <sam@gentoo.org>
2022-02-19 22:39:39 +00:00
Shivaram Lingamneni
b66ea9f56d bump recommended CHATHISTORY limit to 1000
Discussed with emersion, this is the value used in soju and it works fine.
2022-02-18 13:06:24 -05:00
Shivaram Lingamneni
1e7775f6de
Merge pull request #1918 from slingamn/znc_exact
use exact integer parsing for znc.in/playback
2022-02-15 11:28:32 -05:00
Shivaram Lingamneni
197a9d4b5e use exact integer parsing for znc.in/playback 2022-02-15 10:50:38 -05:00
Shivaram Lingamneni
cba3a2fc10
Merge pull request #1916 from slingamn/issue1895_info
fix #1895
2022-02-11 16:27:59 -05:00
Shivaram Lingamneni
2b0d94dfee manual: add note about secure-nets 2022-02-11 13:28:35 -05:00
Shivaram Lingamneni
0a2a850005
Merge pull request #1917 from progval/patch-4
Update help of REGISTER and VERIFY commands
2022-02-08 16:22:58 -05:00
Val Lorentz
e7abd93e90
Update help of REGISTER and VERIFY commands 2022-02-08 20:25:41 +01:00
Shivaram Lingamneni
0afa7edffe fix #1895
Include server start time in INFO output.
2022-02-08 07:38:11 -05:00
Shivaram Lingamneni
59ef59870a
Merge pull request #1915 from slingamn/privileged_kick
fix #1906
2022-02-08 05:52:04 -05:00
Shivaram Lingamneni
b492b8385b bump irctest 2022-02-07 19:11:31 -05:00
Shivaram Lingamneni
fcb86c54f7 fix #1906
Having the 'samode' capability made all KICK commands privileged. This appears
to have been introduced unintentionally by 42316bc04f4761d and I can't find
any discussion of a rationale. Since this goes against our policy that all
ircop (as opposed to channel founder) privileges must be invoked explicitly
(e.g. SAJOIN, SAMODE), remove this.
2022-02-07 19:06:31 -05:00
Shivaram Lingamneni
6c0e9619cb
Merge pull request #1904 from slingamn/prefix.1
bump irc-go to v0.1.0
2022-01-20 18:03:25 -05:00
Shivaram Lingamneni
e3e8136f85 update ergo to work with irc-go v0.1.0 2022-01-20 18:00:04 -05:00
Shivaram Lingamneni
74f3ea1d2e bump irc-go to v0.1.0 2022-01-20 17:59:57 -05:00
Shivaram Lingamneni
fa7b76d66a
Merge pull request #1903 from slingamn/zero_nick
fix #1896
2022-01-19 22:32:08 -05:00
Shivaram Lingamneni
e3c9eb8e71 fix #1896
Don't allow any new uses of 0 as a nickname, since it conflicts with
the use of 0 as a placeholder for account name in WHOX.
2022-01-19 04:14:00 -05:00
Shivaram Lingamneni
c2bf59ca38
Merge pull request #1897 from slingamn/whox_show_ip
show arbitrary IP in WHOX
2022-01-19 02:16:14 -05:00
Shivaram Lingamneni
86c5839044
Merge pull request #1902 from slingamn/amode_v_join
fix #1901
2022-01-19 01:15:58 -05:00
Shivaram Lingamneni
eb477c3793 fix #1901
AMODE +v should allow you to join a +i channel
2022-01-19 00:54:03 -05:00
Shivaram Lingamneni
bf3c0ad70e
Merge pull request #1900 from csmith/docs-faq-webirc-secure
Add FAQ about WebIRC and secure clients
2022-01-19 00:52:22 -05:00
Chris Smith
0da7e68e6d Add FAQ about WebIRC and secure clients 2022-01-18 16:15:48 +00:00
Shivaram Lingamneni
6dc6abc455 set up new development version (again) 2022-01-10 19:03:54 -05:00
Shivaram Lingamneni
a73ad5fc10 bump irctest to latest 2022-01-10 18:58:11 -05:00
Shivaram Lingamneni
1d8a54289f bump version to 2.9.1 2022-01-10 02:33:56 -05:00
Shivaram Lingamneni
d8c9d0ec03 fix changelog 2022-01-10 02:32:27 -05:00
Shivaram Lingamneni
2e26efa55d changelog update for 2.9.1 2022-01-10 02:31:05 -05:00
Shivaram Lingamneni
7347e94da1
Merge pull request #1899 from slingamn/saregister_fix
fix #1898
2022-01-10 02:27:15 -05:00
Shivaram Lingamneni
3162c8a1c8 fix #1898
NS SAREGISTER would fail due to a nil dereference of `client`;
add two safeguards against this.
2022-01-10 01:58:05 -05:00
Shivaram Lingamneni
8605cd2295 correct 2.9.0 changelog credits for posterity 2022-01-09 23:34:10 -05:00
Shivaram Lingamneni
28ab3612e2
Merge pull request #1894 from thesamesam/openrc-init
distrib: add OpenRC init scripts
2022-01-09 17:27:11 -05:00
Shivaram Lingamneni
dba5d3faae show arbitrary IP in WHOX
This extends #1650 to cover WHO as well as WHOIS
2022-01-09 17:24:24 -05:00
Shivaram Lingamneni
1953e90720 set up new development version 2022-01-09 16:11:34 -05:00
Sam James
e6951aca3f README: add Gentoo package
Signed-off-by: Sam James <sam@gentoo.org>
2022-01-09 07:39:14 +00:00
Sam James
2619a23458 distrib: add OpenRC init scripts
Written originally for Gentoo but should work
on Alpine and other OpenRC-consuming distros too.

Signed-off-by: Sam James <sam@gentoo.org>
2022-01-09 07:39:14 +00:00
Shivaram Lingamneni
a0ad42272d bump version and changelog for v2.9.0 2022-01-09 01:47:27 -05:00
Shivaram Lingamneni
7a6e7f05a1
Merge pull request #1892 from slingamn/unregister_warning
add a warning about NS UNREGISTER unregistering channels
2022-01-06 16:00:17 -05:00
Shivaram Lingamneni
6863d58cab add a warning about NS UNREGISTER unregistering channels
See discussion on #1891
2022-01-05 13:30:36 -05:00
Shivaram Lingamneni
d68813927a
Merge pull request #1890 from slingamn/translations
update translations
2022-01-02 18:52:59 -05:00
Shivaram Lingamneni
c2a5853d08 commit the results of updatetranslations.py 2022-01-02 17:38:02 -05:00
Shivaram Lingamneni
d174b5aad6 add disambiguating numbers in translated strings 2022-01-02 17:36:12 -05:00
Shivaram Lingamneni
fb2be58e60 fix instructions for signing the checksum file 2022-01-02 17:29:10 -05:00
Shivaram Lingamneni
abd4cf7d3b bump version to v2.9.0-rc1 2022-01-02 16:37:22 -05:00
Shivaram Lingamneni
2dde9cb464
version and changelog updates for 2.9.0-rc1 (#1889)
* version and changelog updates for 2.9.0-rc1

* review fixes to changelog and documentation
2022-01-02 16:35:20 -05:00
Shivaram Lingamneni
c01e686221
Merge pull request #1888 from slingamn/ip_check_script.1
add ip-check-script.exempt-sasl
2022-01-02 07:44:09 -05:00
Shivaram Lingamneni
0a59f41cf9 add ip-check-script.exempt-sasl 2022-01-02 01:51:31 -05:00
Shivaram Lingamneni
58d8421f44 Revert "disable languages in default.yaml"
This reverts commit 3b3c5b591f5cd6252e8dc3702e7a656bdd25deef.
2022-01-02 01:46:27 -05:00
Shivaram Lingamneni
378f9cc852
Merge pull request #1836 from slingamn/languages_disable
disable languages in default.yaml
2022-01-02 01:40:18 -05:00
Shivaram Lingamneni
d6b8e59462
Merge pull request #1885 from slingamn/issue1844_again
fix the reopened #1844
2022-01-02 01:25:09 -05:00
Shivaram Lingamneni
40bd298a91 fix the reopened #1844 2022-01-02 01:07:37 -05:00
Shivaram Lingamneni
ed75533cb1
optionally protect against multiple starts with flock (#1873)
* optionally protect against multiple starts with flock

Fixes #1823

* use traditional .lock extension

* move config key to top level
2022-01-01 18:56:40 -05:00
Shivaram Lingamneni
e112a78b9b Merge branch 'issue1883' 2021-12-30 13:07:52 -05:00
Shivaram Lingamneni
a57bf46e6a small refactor 2021-12-30 12:59:14 -05:00
William Rehwinkel
b929691470 Fix #1883 Nickserv gives error when user attempt to change password to * 2021-12-30 12:15:30 -05:00
Shivaram Lingamneni
24ad24562e update help entry for CHATHISTORY 2021-12-29 12:45:29 -05:00
Shivaram Lingamneni
000eb760e6
Merge pull request #1880 from progval/patch-3
README: Add link tothe LDAP plugin
2021-12-26 17:21:54 -05:00
Val Lorentz
d84d6756ed
README: Add link tothe LDAP plugin 2021-12-26 23:08:12 +01:00
Shivaram Lingamneni
f5598cfc1c bump irctest 2021-12-23 15:53:54 -05:00
Shivaram Lingamneni
8b74cd1fd3
Merge pull request #1877 from slingamn/issue1876_invite_ban
fix #1876
2021-12-19 20:58:51 -05:00
Shivaram Lingamneni
e15c355f18 fix #1876
INVITE did not exempt from +b unless the channel was coincidentally also +i.
This was a regression introduced in v2.4.0.
2021-12-19 18:30:18 -05:00
Shivaram Lingamneni
5b3ec9a605
Merge pull request #1872 from slingamn/capbug.1
correctly account for nickname in CAP LS arithmetic
2021-12-16 02:18:48 -05:00
Shivaram Lingamneni
76f7748c8a correctly account for nickname in CAP LS arithmetic
The arithmetic was assuming that the nickname is * (which it is
pre-registration). However, we were sending the actual nickname
post-registration. It would be simpler to always send *, but it
appears that the nickname is actually required by the spec:

>Replies from the server must [sic] contain the client identifier name or
>asterisk if one is not yet available.
2021-12-16 01:59:36 -05:00
Shivaram Lingamneni
c5746c5105
Merge pull request #1871 from slingamn/invite_mask_unregistered_join
+I should allow unregistered users to join a +R channel
2021-12-16 00:41:24 -05:00
Shivaram Lingamneni
ec4fb90d2b +I should allow unregistered users to join a +R channel
See #1858: this was the intent all along, but I missed this issue.
2021-12-15 22:52:11 -05:00
Shivaram Lingamneni
aa4a82e0c8
Merge pull request #1869 from slingamn/atheme.1
fix the atheme side of #1864
2021-12-13 03:11:27 -05:00
Shivaram Lingamneni
8fc20d8eed fix the atheme side of #1864
Import stored certfps from Atheme as well.
2021-12-13 02:14:04 -05:00
Shivaram Lingamneni
98e87f6cc0 explicitly close the DB during importdb
There is no change in behavior since committing the transaction
already write(2)'s all the data to disk. But let's comply with
the official buntdb API.
2021-12-12 22:27:05 -05:00
Shivaram Lingamneni
9293858ba1 bump database version generated by importer
This should have been done in 8b2f6de3e0b9, since we updated both
the database schema and the importer then.
2021-12-12 22:09:42 -05:00
15f5f2e9b0
anope2json certfp support (#1867)
* Advanced certfp support

Signed-off-by: Georg <georg@lysergic.dev>

* Moving certfp logic

Signed-off-by: Georg <georg@lysergic.dev>

* Cleaning up certfp logic

Signed-off-by: Georg <georg@lysergic.dev>
2021-12-12 20:18:41 -05:00
Shivaram Lingamneni
0483e3f6ad
Merge pull request #1866 from slingamn/whox
send `*` for WHOX o (oplevel) instead of `0`
2021-12-12 03:16:03 -05:00
Shivaram Lingamneni
f3f805acb8 send * for WHOX o (oplevel) instead of 0
Jobe points out that 0 is a valid oplevel in some contexts,
* is a better placeholder for "unimplemented".
2021-12-12 03:05:56 -05:00
Shivaram Lingamneni
1cd31f4d61 bump irctest 2021-12-10 01:21:40 -05:00
Shivaram Lingamneni
7bc5bfaa5c
Merge pull request #1863 from slingamn/importer_utf8
anope2json, atheme2json: handle non-UTF8 data
2021-12-10 01:20:35 -05:00
Shivaram Lingamneni
4f7356f19a anope2json, atheme2json: handle non-UTF8 data
Also ignore an unrecognized field type in anope
2021-12-09 22:11:24 -05:00
Shivaram Lingamneni
0d0d9e72b4
Merge pull request #1861 from slingamn/issue1860_amode
fix #1860
2021-12-09 04:18:14 -05:00
Shivaram Lingamneni
ac17bf0e9d
Merge pull request #1857 from slingamn/dollarsign
disallow initial $ in nicknames
2021-12-09 04:17:57 -05:00
Shivaram Lingamneni
e5dff58647
Merge pull request #1859 from slingamn/issue1858_registeredmode
fix #1858
2021-12-09 04:16:31 -05:00
Shivaram Lingamneni
9d9ee11224 fix #1860
CS AMODE changes should take immediate effect even if the nick
does not match the account.
2021-12-08 23:47:33 -05:00
Shivaram Lingamneni
3fe8d01d50 update manual for #1858 2021-12-07 01:41:11 -05:00
Shivaram Lingamneni
8be8f0f08d fix #1858
The channel mode +R used to both prevent joins by unregistered users,
and prevent unregistered users who happened to be joined from speaking.
This changes the behavior so that +R only prevents joins:

1. This allows users who were invited or SAJOIN'ed to speak
2. To restore the old semantics, chanops can set +RM
2021-12-07 01:31:07 -05:00
Shivaram Lingamneni
f40d868cf5 disallow initial $ in nicknames
It collides with the massmessage mask syntax. Reported by @emersion
2021-12-06 14:17:45 -05:00
Shivaram Lingamneni
3e32e3f19e
Merge pull request #1851 from slingamn/default_noctcp
make +C (no CTCP) a default channel mode
2021-12-06 12:01:24 -05:00
Shivaram Lingamneni
4d6e0120b2 plug gamja in the userguide 2021-12-06 03:25:55 -05:00
erin
5c7f8faf0c
Build arm64 container images (#1855)
* Build container image on aarch64 platform

* Revert test code & correct typo

Co-authored-by: erin <erin@localhost>
2021-12-04 22:14:21 -05:00
Shivaram Lingamneni
039d8f3f2d
Merge pull request #1854 from erincerys/update-docker-repo-location
Update docker repo location
2021-12-03 15:14:42 -05:00
erin
b8e38819d8 Update oragono references to ergo
- Environment variable prefix
- DNSBL repo URI
- Project and repo names in link to Docker-specific instructions
- Docker container and volume names in docker run command
2021-12-03 12:07:05 -08:00
erin
fba41a26df Update docker repo location in docs and docker-compose.yml 2021-12-03 11:10:52 -08:00
Shivaram Lingamneni
1951e09eea
Merge pull request #1853 from slingamn/note
change FAIL AUTHENTICATE VERIFICATION_REQUIRED to be a NOTE
2021-12-03 02:31:54 -05:00
Shivaram Lingamneni
cb757c703d change FAIL AUTHENTICATE VERIFICATION_REQUIRED to be a NOTE
See discussion on #1852
2021-12-01 12:16:03 -05:00
Shivaram Lingamneni
72959eb1cf
Merge pull request #1852 from slingamn/fail_authenticate
add FAIL AUTHENTICATE VERIFICATION_REQUIRED
2021-11-30 20:35:33 -05:00
Shivaram Lingamneni
8ec9053448
Merge pull request #1849 from slingamn/bugs.2
fix some papercuts
2021-11-30 20:34:33 -05:00
Shivaram Lingamneni
7d66368274 add FAIL AUTHENTICATE VERIFICATION_REQUIRED
From discussion with @emersion, this will help with UX if people try to
log into their unverified accounts.
2021-11-30 15:27:25 -05:00
Shivaram Lingamneni
028f2fcaa4 make +C (no CTCP) a default channel mode 2021-11-30 13:48:34 -05:00
Shivaram Lingamneni
645721f97e fix missing parenthesis in UBAN INFO 2021-11-30 03:40:03 -05:00
Shivaram Lingamneni
8995dd8842 update DEVELOPING.md 2021-11-30 03:35:07 -05:00
Shivaram Lingamneni
fd45529d94 fix #1842
Warn about banning a single IPv6 address
2021-11-30 03:27:40 -05:00
Shivaram Lingamneni
eef9753912 reorder imports 2021-11-30 03:18:01 -05:00
Shivaram Lingamneni
9ff4047fa6 fix #1840
Account registrations pending verification should produce a logline and
send a sno.
2021-11-30 03:18:01 -05:00
Shivaram Lingamneni
ee720f60e2 fix #1826
CS PURGE should send a snomask
2021-11-30 03:18:01 -05:00
Shivaram Lingamneni
c51569420a fix #1844
Send snomasks for HS SET
2021-11-30 03:18:01 -05:00
Shivaram Lingamneni
20270aeb22 docs: fix incorrect naming of GHCR 2021-11-28 02:46:55 -05:00
Shivaram Lingamneni
57c943c812
Merge pull request #1846 from tacerus/SMF
OpenSolaris / Illumos Service Management Facility (SMF) Service
2021-11-28 01:27:31 -05:00
Shivaram Lingamneni
0344e99bc6 update docker readme 2021-11-28 01:26:36 -05:00
Shivaram Lingamneni
72b51c00de fix irctest submodule path 2021-11-27 18:13:04 -05:00
b106c01303
Solaris SMF Service
Signed-off-by: Georg <georg@lysergic.dev>
2021-11-22 17:11:41 +01:00
Shivaram Lingamneni
5c7fd0ec0b readme: clarify self-containedness 2021-11-21 21:55:25 -05:00
Shivaram Lingamneni
14c16a999d
Merge pull request #1839 from mogad0n/fix/suspend_perms_1828
NS SUSPEND now req 'ban' capabs
2021-11-18 12:21:14 -05:00
e0f4fcee61
NS SUSPEND now req 'ban' capabs 2021-11-18 17:11:32 +05:30
Shivaram Lingamneni
ac91beabfd
Merge pull request #1835 from slingamn/who.1
fix two WHO bugs
2021-11-16 21:45:40 -05:00
Shivaram Lingamneni
21e637b8d7 add uncommitted files from dep update
These should have been committed in 8218d1caabad110400303539a381c66f0e185c55
but weren't.
2021-11-16 20:07:39 -05:00
Shivaram Lingamneni
208a6706c5
Merge pull request #1834 from slingamn/goreleaser
fix #1833
2021-11-16 19:17:50 -05:00
Shivaram Lingamneni
43b0e020e8
Merge pull request #1838 from slingamn/relaymute
fix casefolding issue in muting RELAYMSG
2021-11-16 19:16:23 -05:00
Shivaram Lingamneni
86e5f907ea
Merge pull request #1837 from slingamn/deps
update dependencies for 2.9 window
2021-11-16 19:16:10 -05:00
Shivaram Lingamneni
bc5c2a1250 fix casefolding issue in muting RELAYMSG
Reported by @mogad0n; the mute mask was being case-canonicalized,
but the RELAYMSG identifier wasn't being case-canonicalized before
the check.
2021-11-16 18:39:38 -05:00
Shivaram Lingamneni
8218d1caab upgrade dependencies for 2.9 cycle 2021-11-16 18:38:42 -05:00
Shivaram Lingamneni
51fca3cc0b bump irctest to latest 2021-11-16 18:38:42 -05:00
Shivaram Lingamneni
3b3c5b591f disable languages in default.yaml
See #1822
2021-11-16 18:14:00 -05:00
Shivaram Lingamneni
0713d040be make operators hidden by default in default.yaml 2021-11-15 18:43:06 -05:00
Shivaram Lingamneni
2038763e10 fix #1730
`WHO #channel o` is supposed to return only server operators.
This is RFC1459 cruft; just return an empty list in this case.
2021-11-14 15:13:56 -05:00
Shivaram Lingamneni
62b2d0341e fix #1831
RPL_ENDOFWHO should send the original, un-normalized mask
2021-11-14 13:41:27 -05:00
Shivaram Lingamneni
250a95c8e5 include procedures for signing the checksums file 2021-11-14 13:24:14 -05:00
Shivaram Lingamneni
9ceac66b08 fix #1833
Modernize the list of os-arch pairs that get official release builds;
add darwin/arm64 and openbsd/amd64, remove 386 and armv7, add plan9/amd64
for fun.
2021-11-14 13:13:33 -05:00
Shivaram Lingamneni
92f6bf2d03 set up new development version 2021-11-14 13:12:58 -05:00
Shivaram Lingamneni
c70e518eed bump version to v2.8.0 2021-11-14 00:11:19 -05:00
Shivaram Lingamneni
54b1513931 remove gchr.io landing page link from changelog
The landing page link doesn't seem to work right now.
2021-11-14 00:08:37 -05:00
Shivaram Lingamneni
9a007d07f8 update changelog for ghcr announcement 2021-11-13 23:24:15 -05:00
Shivaram Lingamneni
7ee0712401
Merge pull request #1829 from slingamn/changelog
bump changelog for 2.8.0, fix #1827
2021-11-13 21:16:46 -05:00
Shivaram Lingamneni
050e27b31b fix #1798
Improve documentation for use of certificate fingerprints
2021-11-13 19:58:56 -05:00
Shivaram Lingamneni
e74da6c51e fix #1827
Document operator capabilities.
2021-11-13 19:51:07 -05:00
Shivaram Lingamneni
741cd8e8af changelog updates 2021-11-12 14:33:45 -05:00
Shivaram Lingamneni
3d4170ef98
Merge pull request #1825 from slingamn/samode_f
SAMODE +f shouldn't require channel privileges
2021-11-11 12:32:17 -05:00
Shivaram Lingamneni
4bffdba610 SAMODE +f shouldn't require channel privileges
Reported by @Mikaela; normally this requires +o or higher on the
channel that is the target of the forward, but SAMODE should bypass
this check.
2021-11-09 13:23:25 -05:00
Shivaram Lingamneni
4a3ac617a5
Merge pull request #1819 from slingamn/password_message
fix error message for NS SET EMAIL without the password
2021-11-08 03:20:45 -05:00
Shivaram Lingamneni
dc75b24d23 clarify comment on CAP byte arithmetic 2021-11-07 13:32:18 -05:00
Shivaram Lingamneni
9f0c3cdc0e fix error message for NS SET EMAIL without the password 2021-11-04 20:10:56 -04:00
Shivaram Lingamneni
2274ef3fa0
Merge pull request #1818 from slingamn/logo
temporary logo update
2021-11-04 14:48:37 -04:00
Shivaram Lingamneni
63826da693 temporary logo update 2021-11-04 14:47:32 -04:00
Shivaram Lingamneni
e154126ac2
Merge pull request #1817 from tacerus/languages
Languages README.md: Oragono -> Ergo
2021-11-04 14:34:42 -04:00
9d9ae8ce14
Oragono -> Ergo
Signed-off-by: Georg <georg@lysergic.dev>
2021-11-04 19:32:28 +01:00
Shivaram Lingamneni
486bd699eb fix erroneous command name in changelog
Thanks @brettgilio!
2021-11-03 13:48:30 -04:00
Shivaram Lingamneni
ea4b93dd59 bump version to v2.8.0-rc1 2021-11-03 05:33:46 -04:00
Shivaram Lingamneni
4901e67f51
Merge pull request #1816 from slingamn/changelog.2
bump changelog and version for 2.8.0-rc1
2021-11-03 05:17:21 -04:00
Shivaram Lingamneni
b276d23230 bump changelog for v2.8.0-rc1 2021-11-03 05:14:52 -04:00
Shivaram Lingamneni
0f8f8b0de9
Merge pull request #1815 from slingamn/githash
fix some inconsistencies in how git hashes and tags are included in the binary
2021-11-03 02:33:41 -04:00
Shivaram Lingamneni
f9ca172ad7 include git tag in binary when available 2021-11-03 02:15:46 -04:00
Shivaram Lingamneni
9c4fbeabef docker: include git hash in docker-built binary 2021-11-03 01:58:04 -04:00
Shivaram Lingamneni
475814d613
Merge pull request #1814 from slingamn/alpine_downgrade
docker: downgrade alpine to 3.13 again
2021-11-03 01:15:51 -04:00
Shivaram Lingamneni
e620e3b4bf docker: downgrade alpine to 3.13 again
See https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.14.0#faccessat2 ;
this maintains compatibility with older, buggy versions of docker and runc.
Alpine 3.13 is supported until 2022-11-01.
2021-11-03 01:07:02 -04:00
Shivaram Lingamneni
7e93770540
Merge pull request #1813 from slingamn/message_error
CHATHISTORY with nonexistent msgid should send empty batch
2021-11-02 20:59:02 -04:00
Shivaram Lingamneni
d488cf7f57
Merge pull request #1812 from slingamn/cstransfer
fix case where CS TRANSFER as an operator required acceptance
2021-11-02 20:58:51 -04:00
Shivaram Lingamneni
ad8c97c9bb CHATHISTORY with nonexistent msgid should send empty batch
It was sending MESSAGE_ERROR, which was inappropriate. Send an empty batch
for now (this is at parity with the in-memory implementation).
2021-11-02 18:50:41 -04:00
Shivaram Lingamneni
c4e376c8bb fix spurious error logline in schema change 2021-11-02 18:26:51 -04:00
Shivaram Lingamneni
85fabaad6d fix case where CS TRANSFER as an operator required acceptance
Reported by @mogad0n. If a user had both operator privileges and
channel owner privileges, the CS TRANSFER would proceed as though
unprivileged, requiring acceptance by the receiving user. Fix this
to not require acceptance.
2021-11-02 18:26:47 -04:00
Shivaram Lingamneni
4ba35afa85 try to fix docker entrypoint
docker: Error response from daemon: failed to create shim: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "/ircd-bin/run.sh": permission denied: unknown.
2021-11-02 05:15:56 -04:00
Shivaram Lingamneni
759696836e CI: fix invalid variable warning 2021-11-02 04:41:51 -04:00
Shivaram Lingamneni
1ab2e9c294
Add GHCR workflow
See #1808
2021-11-02 04:38:59 -04:00
Shivaram Lingamneni
c9b54ee2b8
Merge pull request #1809 from slingamn/issue1676_again.3
fix #1676, take 2
2021-11-02 03:51:04 -04:00
Shivaram Lingamneni
ea1678ec8f
Merge pull request #1810 from slingamn/nolusers
fix #1802
2021-11-01 23:25:32 -04:00
Shivaram Lingamneni
62043afbb0
Merge pull request #1811 from slingamn/error_line
fix spurious "corrupt account creds" logline
2021-11-01 23:24:13 -04:00
Shivaram Lingamneni
61bce74018 fix spurious "corrupt account creds" logline
Reported by @tacerus. This was most likely introduced by 8b2f6de3e.
2021-11-01 18:48:37 -04:00
Shivaram Lingamneni
51d573d3c9 fix #1802
Add a config option to suppress LUSERS
2021-11-01 04:48:31 -04:00
Shivaram Lingamneni
8c556fe8c5 schema change to remove ReplayJoinsNever
See #1676
2021-11-01 04:36:06 -04:00
Shivaram Lingamneni
4749d7e776 fix #1676, take 2
Ensure the pagination window is full by making sure that every history item
gets a replay line in CHATHISTORY output, even TAGMSG.
2021-11-01 04:34:59 -04:00
Shivaram Lingamneni
3ec5ffa340 Revert "fix #1676"
This reverts commit 5bbee02fe6bb8ed9dea23675df4349604c5fe247.
2021-11-01 04:34:59 -04:00
Shivaram Lingamneni
84a5b83eb1
Merge pull request #1807 from xnaas/docs-touchup
update 'docker kill' command
2021-10-31 18:34:32 -04:00
xnaas
48897596c4
update 'docker kill' command 2021-10-29 09:56:36 -05:00
Shivaram Lingamneni
41089b0e16
Merge pull request #1806 from slingamn/history.2
fix #1676
2021-10-29 05:04:01 -04:00
Shivaram Lingamneni
84fef29760
Merge pull request #1805 from slingamn/bunt_upgrade
upgrade buntdb
2021-10-29 05:02:50 -04:00
Shivaram Lingamneni
5bbee02fe6 fix #1676
Fix various pagination issues with CHATHISTORY; also undo #491
(msgid munging).
2021-10-29 04:50:24 -04:00
Shivaram Lingamneni
b478e93c11
Merge pull request #1804 from slingamn/pwvalidation
make `ergo genpasswd` warn for bad passwords
2021-10-28 22:25:45 -04:00
Shivaram Lingamneni
c972a92e51 upgrade buntdb
Resolves CVE-2021-42836, which probably didn't affect us, but we might as well
upgrade.
2021-10-28 19:47:33 -04:00
Shivaram Lingamneni
7d5cb723b4 make ergo genpasswd warn for bad passwords 2021-10-28 19:29:55 -04:00
Shivaram Lingamneni
404bf6c2a0 update git workflow documentation 2021-10-15 01:08:27 -04:00
Shivaram Lingamneni
1b55520006
Merge pull request #1800 from ajaspers/samode
Send snomask for channel mode changes via SAMODE.
2021-10-03 23:29:28 -04:00
Alex Jaspersen
53a7e8c334 Send snomask for channel mode changes via SAMODE.
Fixes #1787
2021-10-03 17:15:01 -07:00
Shivaram Lingamneni
20d8d269ca
Merge pull request #1797 from slingamn/signals
move signals code to utils/
2021-09-19 04:18:20 -04:00
Shivaram Lingamneni
b0f412538c move signals code to utils/ 2021-09-19 04:02:44 -04:00
Shivaram Lingamneni
3cf1583aed
Merge pull request #1796 from slingamn/reverseproxy_exportable.1
factor out some shared code
2021-09-19 02:48:55 -04:00
Shivaram Lingamneni
2cae19dde5 remove unnecessary indirection in config 2021-09-19 02:09:43 -04:00
Shivaram Lingamneni
284c3d689b remove obsolete resume config field 2021-09-19 02:00:58 -04:00
Shivaram Lingamneni
e0e4791f72 factor out some shared code 2021-09-18 21:28:16 -04:00
Shivaram Lingamneni
657ce0f1a4
Merge pull request #1795 from slingamn/gamjadoc
document gamja
2021-09-13 01:57:22 -04:00
Shivaram Lingamneni
fc711de360 document gamja 2021-09-13 01:53:55 -04:00
Shivaram Lingamneni
e71643eb73 add dependencies workflow to DEVELOPING.md 2021-09-12 11:28:01 -04:00
Shivaram Lingamneni
2b3fc9d38e
Merge pull request #1793 from slingamn/nope
rename oragono.io/nope to ergo.chat/nope
2021-09-12 11:20:17 -04:00
Shivaram Lingamneni
b8009c4a07 rename oragono.io/nope to ergo.chat/nope 2021-09-10 13:05:09 -04:00
Shivaram Lingamneni
bce3d643bc bump irctest 2021-09-10 09:51:55 -04:00
Shivaram Lingamneni
c7b6b6e917 include /msg form of account registration in manual
Thanks to @cxxboy (#1788)
2021-09-09 21:46:12 -04:00
Shivaram Lingamneni
eff6dd242b
Merge pull request #1791 from slingamn/hashmark
add # to disfavoredNameCharacters
2021-09-09 21:34:54 -04:00
Shivaram Lingamneni
cb39c82222
Merge pull request #1792 from slingamn/info
update logo in /INFO response
2021-09-09 21:34:34 -04:00
Shivaram Lingamneni
69d88fb231 update logo in /INFO response 2021-09-09 21:25:07 -04:00
Shivaram Lingamneni
b83479247e add # to disfavoredNameCharacters
Partially fixes #1679, not sure whether to police the relaymsg config or not
2021-09-09 21:04:47 -04:00
Daniel Oaks
39b0d2c6ac Update #ergo text logo
(based on 'slant' figlet font with some tweaks)
2021-09-10 09:45:31 +10:00
Shivaram Lingamneni
a367c20410 fix inconsistent grammar in documentation 2021-09-05 20:33:25 -04:00
Shivaram Lingamneni
d02eecd8ec
Merge pull request #1790 from slingamn/unknowncommand
fix #1530
2021-09-05 20:32:06 -04:00
Shivaram Lingamneni
ffb5e4f986 bump irctest 2021-09-05 20:15:42 -04:00
Shivaram Lingamneni
aa969b7ff7 fix #1530
Warn if the user sent, e.g., /QUOTE /SAMODE instead of /QUOTE SAMODE
2021-09-05 20:14:13 -04:00
Shivaram Lingamneni
585910a9b1
Merge pull request #1786 from majiru/s6
add s6 services to distrib
2021-09-01 01:04:57 -04:00
Jacob Moody
9f7ead07a5
add s6 services to distrib 2021-08-28 18:14:47 -06:00
Shivaram Lingamneni
9b6ec04ca5
Merge pull request #1761 from delthas/feature-extended-monitor
Add support for extended-monitor
2021-08-26 10:14:31 -04:00
Shivaram Lingamneni
55cf1e6781
Merge pull request #1784 from slingamn/issue1650_whoisactually
fix #1650
2021-08-26 00:11:19 -04:00
Shivaram Lingamneni
26cdb4cf36 fix #1650
RPL_WHOISACTUALLY should display some arbitrarily chosen IP address and
hostname.
2021-08-25 22:44:55 -04:00
Shivaram Lingamneni
8b2f6de3e0
Add email-based password reset (#1779)
* Add email-based password reset

Fixes #734

* rename SETPASS to RESETPASS

* review fixes

* abuse mitigations

* SENDPASS and RESETPASS should both touch the client login throttle
* Produce a logline and a sno on SENDPASS (since it actually sends an email)

* don't re-retrieve the settings value

* add email confirmation for NS SET EMAIL

* smtp: if require-tls is disabled, don't validate server cert

* review fixes

* remove cooldown for NS SET EMAIL

If you accidentally set the wrong address, the cooldown would prevent you
from fixing your mistake. Since we touch the registration throttle anyway,
this shouldn't present more of an abuse concern than registration itself.
2021-08-25 22:32:55 -04:00
Shivaram Lingamneni
0baaf0b711
Merge pull request #1783 from slingamn/scram_mechanisms
remove SCRAM-SHA-256 from advertised SASL mechanisms
2021-08-25 21:03:29 -04:00
Shivaram Lingamneni
a1d4b8ac82 bump irctest to latest 2021-08-25 19:00:33 -04:00
Shivaram Lingamneni
12947644e2 remove SCRAM-SHA-256 from advertised SASL mechanisms
Advertising SCRAM-SHA-256 breaks irccloud, which doesn't fall back to PLAIN
if it sees SCRAM advertised but SCRAM then fails (as is the case for any
account password hashed on Ergo 2.7 or lower).

Leave a config option for irctest to enable it in the controller.
2021-08-25 18:34:27 -04:00
Shivaram Lingamneni
9c77f89bc2
Merge pull request #1781 from slingamn/go_upgrade
upgrade go to 1.17
2021-08-20 16:56:26 -04:00
Shivaram Lingamneni
492109f29d upgrade go to 1.17 2021-08-20 15:59:26 -04:00
Shivaram Lingamneni
00255586cc update manual to point to stable branch systemd file 2021-08-19 10:51:46 -04:00
Shivaram Lingamneni
fea8cc1b9a
Merge pull request #1778 from ProgVal/empty-realname
Add missing argument to ERR_NEEDMOREPARAMS on USER commands.
2021-08-13 16:58:53 -04:00
Valentin Lorentz
a90fbf9f2c Add missing argument to ERR_NEEDMOREPARAMS on USER commands.
Refs:

* other instances in the codebase
* https://defs.ircdocs.horse/defs/numerics.html#err-needmoreparams-461
* https://modern.ircdocs.horse/#errneedmoreparams-461
2021-08-13 21:10:46 +02:00
Shivaram Lingamneni
0d438dd0d6
Merge pull request #1776 from ProgVal/kick-default-comment
Make kick messages default to the kicker name instead of the kicked
2021-08-13 14:20:17 -04:00
Valentin Lorentz
f33f41b0eb Make kick messages default to the kicker name instead of the kicked
For consistency with RFC2812, Bahamut, Hybrid, Insp, Plexus4, Unreal.
https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.8

At the expense of consistency with chary/solanum, irc2, and ircu2.
2021-08-13 20:16:37 +02:00
Shivaram Lingamneni
4785a3953a
Merge pull request #1777 from ProgVal/targmax-kick
Advertise support for multiple KICK targets
2021-08-13 14:14:56 -04:00
Valentin Lorentz
f6f25039b7 Advertise support for multiple KICK targets
This is already implemented, but TARGMAX=KICK:1 says it isn't.

Instead, let's advertise that indefinitely many targets are allowed.
Refs:

* https://defs.ircdocs.horse/defs/isupport.html#targmax
* https://github.com/ircdocs/modern-irc/pull/112
2021-08-13 19:42:03 +02:00
Shivaram Lingamneni
5d2d4a99bc
Merge pull request #1775 from slingamn/channelkey
fix handling of +k with an empty key parameter
2021-08-10 15:15:40 -04:00
Shivaram Lingamneni
abfb8442ab fix handling of +k with an empty key parameter
This should be disallowed; `MODE #keytest +k :` should just be an error.
2021-08-10 15:11:11 -04:00
Shivaram Lingamneni
bbb52bf692
Merge pull request #1773 from ProgVal/keyvalidation
Add missing channel parameter to ERR_INVALIDMODEPARAM.
2021-08-10 15:02:02 -04:00
Valentin Lorentz
e894c44960 Add missing channel parameter to ERR_INVALIDMODEPARAM. 2021-08-10 20:33:00 +02:00
Shivaram Lingamneni
ada135d7cf
Merge pull request #1772 from slingamn/bump_irctest
bump irctest
2021-08-08 17:04:32 -04:00
Shivaram Lingamneni
0355c2df1e bump irctest 2021-08-08 14:49:08 -04:00
Shivaram Lingamneni
37c7b97084 documentation: update CI config path 2021-08-08 12:35:55 -04:00
Shivaram Lingamneni
0ac8b6daea
Merge pull request #1770 from ajaspers/patch-1
Document mute extbans
2021-08-04 01:58:45 -04:00
ajaspers
c3fb7f2ad6
Document mute extbans
Fixes #1747.
2021-08-03 21:31:54 -07:00
Shivaram Lingamneni
1c5a485c17
Merge pull request #1768 from slingamn/scram_clientid
fix SCRAM not supporting client IDs
2021-08-03 23:51:10 -04:00
Shivaram Lingamneni
5b93fdfcf2
Merge pull request #1769 from ajaspers/logout
Correct help text for NS CLIENTS LOGOUT.
2021-08-03 23:50:01 -04:00
Alex Jaspersen
ecd878c169 Correct help text for NS CLIENTS LOGOUT.
"LOGOUT ALL" logs out all clients for a user.

Fixes #1712.
2021-08-03 18:54:37 -07:00
Shivaram Lingamneni
117401f293 fix SCRAM not supporting client IDs
reported by @Mikaela
2021-08-03 11:47:00 -04:00
Shivaram Lingamneni
b13776787b
Merge pull request #1767 from slingamn/bunt
upgrade buntdb
2021-08-03 09:58:10 -04:00
Shivaram Lingamneni
c5a9916302 upgrade buntdb 2021-08-03 01:46:43 -04:00
Shivaram Lingamneni
1389d89a9b
Merge pull request #1766 from slingamn/allow_truncation
fix incorrect handling of overlong lines when allow-truncation is enabled
2021-08-03 00:40:54 -04:00
Shivaram Lingamneni
941c12244f
Merge pull request #1765 from slingamn/autorehash
add autogeneration of SCRAM credentials on successful PLAIN
2021-08-03 00:40:47 -04:00
Shivaram Lingamneni
cf25e894e1 fix incorrect handling of overlong lines when allow-truncation is enabled 2021-08-02 21:49:42 -04:00
Shivaram Lingamneni
4dd9af8f06 add autogeneration of SCRAM credentials on successful PLAIN 2021-08-02 12:26:58 -04:00
Shivaram Lingamneni
d8dc24dee8
Merge pull request #1764 from slingamn/scram.3
implement SCRAM-SHA-256
2021-08-01 22:00:14 -04:00
Shivaram Lingamneni
ebe1f84d64 consolidate login throttle checks
We can check once during initialization of the SASL session, e.g.
on receiving `AUTHENTICATE PLAIN` or `AUTHENTICATE EXTERNAL`
2021-07-30 14:06:13 -04:00
Shivaram Lingamneni
41822813c0 clean up redundant check for sasl mechanism 2021-07-30 14:03:40 -04:00
Shivaram Lingamneni
e1401934df implement SCRAM-SHA-256 2021-07-30 12:20:13 -04:00
Shivaram Lingamneni
3264687803
Merge pull request #1763 from slingamn/jwt
replace jwt library
2021-07-29 20:51:23 -04:00
Shivaram Lingamneni
ef92318282 replace jwt library
Fixes #1762
2021-07-29 16:49:35 -04:00
delthas
7fde04ea94 Add support for extended-monitor
See: https://github.com/ircv3/ircv3-specifications/pull/466
2021-07-24 20:52:03 +02:00
Shivaram Lingamneni
ad61f9f213
Merge pull request #1758 from ergochat/channel_creation
fix channel creation bugs
2021-07-15 08:35:29 -04:00
Shivaram Lingamneni
6851901e20 fix #1756
A default channel mode of +i would block channel creation; fix this by treating
initial joins as SAJOINs.

Note that it's nontrivial to detect initial join in (*Channel).Join, because
having 0 members does not necessarily indicate a new channel.
2021-07-15 06:31:27 -04:00
Shivaram Lingamneni
c99b2be403 fix #1757
Allow SAJOIN or implicit always-on join to override channels.operator-only-creation
2021-07-15 06:27:37 -04:00
Shivaram Lingamneni
699921afff
Merge pull request #1755 from mogad0n/enhance/uban_list_timecreated
add timeCreated to /uban list output
2021-07-14 10:17:49 -04:00
99610eae4b add timeCreated to /uban list output 2021-07-14 12:36:45 +05:30
Shivaram Lingamneni
128142ca41
Merge pull request #1753 from slingamn/rename
fix #1751
2021-07-14 02:09:43 -04:00
Shivaram Lingamneni
dc0bf1a02d
Merge pull request #1748 from ProgVal/patch-2
Add support for KICK #chan user1,user2
2021-07-13 08:55:44 -04:00
Shivaram Lingamneni
5b317d4846 fix #1751
RENAME (channel rename) that was a simple case change (e.g.
renaming #chan to #CHAN) would delete the channel :-|
2021-07-13 08:47:16 -04:00
Valentin Lorentz
f58f8531b2 Restore support for multiple channels + single user 2021-07-12 20:59:05 +02:00
Val Lorentz
54c5d35193 Add support for KICK #chan user1,user2
This is one of the two cases of [RFC 2812 kicks](https://datatracker.ietf.org/doc/html/rfc2812#section-3.2.8):
even when there are multiple user targets, the RFC (and Unreal
and Inspircd and probably others) allows a single channel name.
2021-07-10 11:13:52 +02:00
Shivaram Lingamneni
907f82a27e
Merge pull request #1741 from slingamn/greylisting.3
user visible email errors, email timeouts
2021-07-09 07:22:03 -04:00
Shivaram Lingamneni
497aa429b7
Merge pull request #1746 from slingamn/docker_fix
pin docker base image to alpine 3.13
2021-07-08 20:58:51 -04:00
Shivaram Lingamneni
7190770e12 pin docker base image to alpine 3.13
May fix #1726 (broken build on dockerhub)
2021-07-08 20:55:09 -04:00
Shivaram Lingamneni
3fde046a01
Merge pull request #1743 from mogad0n/fix/disconn_sno_help_missing
fix missing disconnect sno help text
2021-07-08 11:04:25 -04:00
Shivaram Lingamneni
29f1afd565
Merge pull request #1742 from slingamn/register_update
update draft/register -> draft/account-registration
2021-07-07 09:17:20 -04:00
b2ea2583f4 add missing disconnect sno help 2021-07-07 18:43:42 +05:30
Shivaram Lingamneni
4693a88421 bump irctest 2021-07-07 09:04:40 -04:00
Shivaram Lingamneni
5d0e4fa023
Merge pull request #1739 from slingamn/issue1738
fix #1738
2021-07-07 08:17:16 -04:00
Shivaram Lingamneni
59bddd066f update draft/register -> draft/account-registration
Fixes #1740
2021-07-07 07:37:46 -04:00
Shivaram Lingamneni
032ca175e4 add support for email timeouts 2021-07-07 07:21:22 -04:00
Shivaram Lingamneni
46572b871f expose a user-visible error if direct email sending fails
See #1659
2021-07-07 07:21:22 -04:00
Shivaram Lingamneni
1c89f996bc fix #1738
Fix error message for privileged NS PASSWD on a nonexistent account
2021-07-06 13:28:25 -04:00
Shivaram Lingamneni
dcfd8d8fe8
Merge pull request #1737 from slingamn/readerror
add a debug logline for client read errors
2021-07-05 03:34:34 -04:00
Shivaram Lingamneni
fedf4a9176 add a debug logline for client read errors
May help clarify TLS configuration issues, possibly others too
2021-07-05 03:30:18 -04:00
Shivaram Lingamneni
98c4d0e399
Merge pull request #1735 from slingamn/systemd
support systemd notifications
2021-07-05 03:10:27 -04:00
Shivaram Lingamneni
5fc7ac41da always mark the service ready after rehash 2021-07-04 19:51:35 -04:00
Shivaram Lingamneni
5e5c86ad86
Merge pull request #1736 from slingamn/uban_kill
fix UBAN ADD of masks (k-lines) not killing clients
2021-07-04 19:37:53 -04:00
Shivaram Lingamneni
364193df4e refactor some start-stop logging 2021-07-04 17:58:48 -04:00
Shivaram Lingamneni
dbfa704eb2 fix UBAN ADD of masks (k-lines) not killing clients 2021-07-04 08:14:38 -04:00
Shivaram Lingamneni
6f24082705 support systemd notifications
Fixes #1733
2021-07-04 07:41:59 -04:00
Shivaram Lingamneni
c53df2dc88 bump irctest 2021-07-04 04:29:21 -04:00
Shivaram Lingamneni
188d8c499d
Merge pull request #1728 from mogad0n/session_disconnect_sno
DISCONNECT Sno for always-on and/or multiclient
2021-07-04 02:25:10 -04:00
Shivaram Lingamneni
77bfdd8619
Merge pull request #1723 from slingamn/vhost_validation
fix #1722
2021-07-04 01:41:38 -04:00
Shivaram Lingamneni
6e72f12992
Merge pull request #1732 from slingamn/issue1731_invalid_target
fix #1731
2021-07-04 01:41:27 -04:00
Shivaram Lingamneni
d0801e45a8 fix #1731
CHATHISTORY INVALID_TARGETS was missing the subcommand parameter
2021-07-04 01:37:59 -04:00
99cb1fd02c DISCONNECT Sno for always-on and/or multiclient 2021-07-03 04:41:42 +05:30
Shivaram Lingamneni
62d78a64cb
Merge pull request #1727 from kylef/kylef/mode-missing-nick
Include nick in ERR_LISTMODEALREADYSET and ERR_LISTMODENOTSET
2021-07-02 18:00:49 -04:00
Kyle Fuller
9adc77498e Include nick in ERR_LISTMODEALREADYSET and ERR_LISTMODENOTSET 2021-07-02 20:09:48 +01:00
Shivaram Lingamneni
1121c71d75 bump irctest 2021-07-02 07:31:04 -04:00
Shivaram Lingamneni
0751f31b9e fix #1722
Validate operator vhosts against the configured (or default)
vhosts.valid-regexp
2021-06-29 10:06:37 -04:00
Shivaram Lingamneni
5daabdd226
Merge pull request #1717 from ajaspers/voice
Allow +v users to talk in +R channels.
2021-06-29 09:11:52 -04:00
Alex Jaspersen
ff3f959d52 Allow +v users to talk in +R channels. 2021-06-28 17:27:50 -07:00
Shivaram Lingamneni
9af6b86868 bump irctest to reintegrated version 2021-06-28 03:52:25 -04:00
Shivaram Lingamneni
0a811f9d9e
Merge pull request #1719 from slingamn/config_panic
fix #1714
2021-06-28 01:51:40 -04:00
Shivaram Lingamneni
b68696eb9b fix #1714
Fix a panic if the operator class title is empty
2021-06-28 01:45:13 -04:00
Shivaram Lingamneni
20aa8efe56
Merge pull request #1716 from obale/readme-fixes
Fixing the AUR package link in the README.md file
2021-06-27 23:55:57 -04:00
Alex Oberhauser
e8839407d4
Fixing AUR maintainer in README.md 2021-06-27 22:45:08 -04:00
Shivaram Lingamneni
f07524111c
Merge pull request #1702 from ajaspers/whowas
Show real IP in WHOWAS to opers with ban capability.
2021-06-22 02:28:56 -04:00
Shivaram Lingamneni
234459a2fe
Merge pull request #1704 from slingamn/issue1703
fix #1703
2021-06-22 02:28:24 -04:00
Shivaram Lingamneni
e60c2a6806
Merge pull request #1705 from dallemon/docker_readme
update docker readme
2021-06-21 19:13:40 -04:00
Daniel Thamdrup
2abfa66802 update docker readme 2021-06-21 11:56:14 +02:00
Alex Jaspersen
b6264a43b6 Rename "realIP" to "ip" in WhoWas struct.
It could be the real IP or the proxied IP.
2021-06-20 11:13:18 -07:00
Shivaram Lingamneni
51c207dc80 fix #1703
`WHOWAS :` should return an error numeric instead of an empty response
2021-06-20 14:12:13 -04:00
Alex Jaspersen
e5c2588eab Show real IP in WHOWAS to opers with ban capability. 2021-06-20 10:26:30 -07:00
Shivaram Lingamneni
6786b87fbd
Merge pull request #1699 from slingamn/issue1696_mode
fix #1696
2021-06-18 19:06:33 -04:00
Shivaram Lingamneni
d03615d29a bump irctest for #1696 2021-06-18 18:58:39 -04:00
Shivaram Lingamneni
99b9312847 fix #1696 2021-06-18 18:26:45 -04:00
Shivaram Lingamneni
21f51dcc3e
Merge pull request #1694 from slingamn/ircgo.1
use ergochat/irc-go instead of goshuirc/irc-go
2021-06-18 03:14:59 -04:00
Shivaram Lingamneni
4910aefa37 use ergochat/irc-go instead of goshuirc/irc-go 2021-06-18 02:43:25 -04:00
Shivaram Lingamneni
66af8cd63c
Merge pull request #1689 from slingamn/notice
add a FAQ entry for global notices
2021-06-16 03:06:37 -04:00
Shivaram Lingamneni
d097d34737
Merge pull request #1692 from mogad0n/help_modes
Help string updates for +f mode
2021-06-16 03:06:24 -04:00
Shivaram Lingamneni
8f8b71761d
Update irc/help.go
Co-authored-by: Mikaela Suomalainen <mikaela@mikaela.info>
2021-06-16 02:52:21 -04:00
8efb2d23c3 help for +f mode
fix help for +f mode
2021-06-16 11:01:34 +05:30
Shivaram Lingamneni
ece82b44fb add links in README.md to manual/userguide 2021-06-15 19:45:29 -04:00
Shivaram Lingamneni
29982e3ffe mention new init script in manual 2021-06-15 14:21:27 -04:00
Shivaram Lingamneni
57c746bd57
Merge pull request #1691 from tacerus/master
Submitting rc.ergo
2021-06-14 22:38:05 -04:00
ca99a65dc4
Submitting rc.ergo
/etc/rc.d/rc.ergo
2021-06-14 11:28:35 +00:00
Shivaram Lingamneni
c53097000b fix remaining references to Freenode in documentation 2021-06-13 15:57:26 -04:00
Shivaram Lingamneni
2f5484a673 clarify mta configuration in manual 2021-06-13 14:15:03 -04:00
Shivaram Lingamneni
725bb7213d add a FAQ entry for global notices 2021-06-13 13:50:58 -04:00
Shivaram Lingamneni
e59d599eed
Merge pull request #1685 from slingamn/next
set up new development version
2021-06-13 00:50:00 -04:00
Shivaram Lingamneni
94afd012fb
Merge pull request #1680 from tamiko/master
update ChanServ OP command documentation
2021-06-13 00:49:53 -04:00
Shivaram Lingamneni
5d4a12f008
Merge pull request #1677 from ajaspers/email
Show email in NS INFO when user has permission.
2021-06-13 00:49:33 -04:00
Shivaram Lingamneni
97d1786378
Merge pull request #1683 from dallemon/master
update dockerfile and minor changes
2021-06-09 16:26:55 -04:00
Daniel Thamdrup
0898a6aa91 update dockerfile and minor changes 2021-06-09 15:24:52 +02:00
Shivaram Lingamneni
fcaefaca9c set up new development version 2021-06-08 10:47:18 -04:00
Shivaram Lingamneni
9851d2e9bc bump version and changelog for official release 2021-06-08 00:51:37 -04:00
Matthias Maier
6cfd8eadc9 update ChanServ OP command documentation
Commit 7ce396931cff32558112c65e1f9c93aacc5c7a1f introduced the ability
that every user with an account in the AMODE list of a channel can use
the OP command to restore their modes. Update the chanserv help message
accordingly.
2021-06-06 13:57:48 -05:00
Alex Jaspersen
1d832ee1bc Show email in NS INFO when user has permission.
Users logged into an account can see their own email.
Opers with the accreg capability can see all users.
2021-06-04 16:44:00 -07:00
Shivaram Lingamneni
36703580fc
Merge pull request #1674 from slingamn/altnicks
clarify the semantics of additional-nick-limit
2021-06-03 12:41:13 -04:00
Shivaram Lingamneni
99a48496fa clarify the semantics of additional-nick-limit 2021-06-02 12:03:32 -04:00
Shivaram Lingamneni
b81757d273 bump version for 2.7.0-rc1 2021-05-31 01:50:02 -04:00
Shivaram Lingamneni
1b894b73a2 add changelog entry 2021-05-30 21:27:12 -04:00
Shivaram Lingamneni
f93e1f1a7d
Merge pull request #1668 from slingamn/changelog.3
changelog, documentation, and distrib updates for ergo 2.7
2021-05-30 21:25:56 -04:00
Shivaram Lingamneni
b9a1cd618e
Merge pull request #1670 from slingamn/snomasks
fix #1669
2021-05-30 21:25:22 -04:00
Shivaram Lingamneni
6b9cdfeed9 bump irctest 2021-05-30 12:42:04 -04:00
Shivaram Lingamneni
33e3b0ce1b fix #1669
Sort snomasks for display rather than displaying them in hash order
2021-05-30 12:35:16 -04:00
Shivaram Lingamneni
612f527033 change branding in default motd 2021-05-30 03:45:25 -04:00
Shivaram Lingamneni
6ff0486aa0 changelog, documentation, and distrib updates for ergo 2.7 2021-05-30 03:45:25 -04:00
Shivaram Lingamneni
75208d2934
Merge pull request #1663 from kashike/feature/badges
Update status badges in readme for project rename
2021-05-30 02:38:24 -04:00
Shivaram Lingamneni
c24254fe45
Merge pull request #1667 from slingamn/playback.2
fix znc.in/playback for individual DM targets
2021-05-30 02:37:46 -04:00
Riley Park
81b5fa865f
Update status badges in readme for project rename 2021-05-28 20:25:32 -07:00
Shivaram Lingamneni
ac806e5c62 fix znc.in/playback for individual DM targets
This is a regression introduced in 0d05ab4ff4249f; playback for an individual
DM target would play all DMs.
2021-05-28 18:07:54 -04:00
Shivaram Lingamneni
202d982866
Merge pull request #1666 from KiloNiner/patch-1
Update run.sh to use newly renamed ergo binaries
2021-05-28 17:36:52 -04:00
Karsten Højgaard
76fa365a7a
Update run.sh to use newly renamed ergo binaries 2021-05-28 23:15:30 +02:00
Daniel Oaks
cf33122f15
Update Dockerfile to work with Ergo naming change 2021-05-29 06:49:14 +10:00
Daniel Oaks
d85fe8c3cc
Merge pull request #1664 from Rene-Montes/better-spanish
Improved Spanish translations
2021-05-29 06:05:10 +10:00
rene
c3592274dc Improved Spanish translations
Notable changes:
* Use of inclusive pronouns and words (think they/them but on a language
  that relies on gender for almost every word). Keep in mind this might
  cause some flame from conservative folks.
* Corrected many computer-generated translations (one of them translated
  "resume" to resumé)
2021-05-28 19:46:35 +00:00
Shivaram Lingamneni
351eb8ad27
Merge pull request #1662 from slingamn/relay_nuh_followup.2
fix #1661
2021-05-27 11:54:36 -04:00
Shivaram Lingamneni
ec48966b68 fix #1661
If the relay bot and the owner share an IP, legacy bots that identify users
by user@host could misinterpret relayed lines as coming from the bot owner.
Try to avoid this by using the bot's account cloak where applicable.
2021-05-27 11:43:21 -04:00
Shivaram Lingamneni
91cdb96bcb fix HS STATUS help strings 2021-05-27 10:51:54 -04:00
Shivaram Lingamneni
98ea150817
Merge pull request #1660 from ergochat/relay_nuh.1
fix #1647
2021-05-27 02:24:08 -04:00
Shivaram Lingamneni
77313e20ad fix #1647
Send a full NUH with RELAYMSG.

Also fix client-only tags with RELAYMSG.
2021-05-27 02:00:59 -04:00
Shivaram Lingamneni
9527850f7c
Merge pull request #1658 from ajaspers/patch-1
Add documentation for +f channel mode.
2021-05-27 00:18:33 -04:00
ajaspers
b0bdbb775c
Add documentation for +f channel mode. 2021-05-26 21:16:17 -07:00
Shivaram Lingamneni
e76b14d036
Merge pull request #1657 from slingamn/ci_update
bump irctest, remove travis
2021-05-27 00:13:14 -04:00
Shivaram Lingamneni
d740a161db remove travis 2021-05-27 00:08:45 -04:00
Shivaram Lingamneni
b5a154d3d1 bump irctest 2021-05-27 00:08:33 -04:00
Shivaram Lingamneni
60d351f9ff
Merge pull request #1656 from kashike/feature/github-actions
feat: build with github actions
2021-05-26 23:30:05 -04:00
Shivaram Lingamneni
d6b8cb9a8d
Merge pull request #1654 from ergochat/dependencies
migrate additional dependencies to ergochat
2021-05-26 23:29:19 -04:00
Shivaram Lingamneni
7944871eb6 migrate additional dependencies to ergochat 2021-05-26 21:58:29 -04:00
Riley Park
67868f85e8
feat: build with github actions 2021-05-26 18:56:29 -07:00
Shivaram Lingamneni
9dad717c04
Merge pull request #1653 from slingamn/ergo.1
first pass at renaming Oragono to Ergo
2021-05-26 18:57:40 -04:00
Shivaram Lingamneni
42296bdc49 add placeholder logo.png 2021-05-26 18:01:24 -04:00
Shivaram Lingamneni
f1ae8051cb update readmes, add placeholder logo 2021-05-26 18:01:11 -04:00
Shivaram Lingamneni
4d4e134008 bump irctest for ergo rename 2021-05-26 16:03:32 -04:00
Shivaram Lingamneni
23c7218bf1 first pass at renaming Oragono to Ergo 2021-05-26 15:55:24 -04:00
Shivaram Lingamneni
7a1695c628
Merge pull request #1652 from oragono/l10n_master
New Crowdin updates
2021-05-24 13:08:02 -04:00
Daniel Oaks
a300524458 New translations nickserv.lang.json (Spanish) 2021-05-25 02:44:41 +10:00
Shivaram Lingamneni
588efd29b4
Merge pull request #1651 from slingamn/configurable_linelen
make MaxLineLen configurable
2021-05-24 01:33:18 -04:00
Shivaram Lingamneni
cc4b958a41 review fix 2021-05-24 01:02:49 -04:00
Shivaram Lingamneni
7c5a8f2013 make MaxLineLen configurable 2021-05-24 00:38:47 -04:00
Shivaram Lingamneni
297f2af827
Merge pull request #1645 from oragono/noresume.4
remove draft/resume-0.5
2021-05-20 02:04:10 -04:00
Shivaram Lingamneni
56a0407ff5 bump irctest to remove draft/resume-0.5 tests 2021-05-18 23:28:13 -04:00
Shivaram Lingamneni
ba21987d03 remove draft/resume-0.5 2021-05-18 23:27:46 -04:00
Shivaram Lingamneni
df49137aca
Merge pull request #1643 from slingamn/auditorium_zerotime
fix #1642
2021-05-05 10:14:50 -04:00
Shivaram Lingamneni
a131507090 fix #1642
Fix auditorium JOIN lines with zero values for the time and msgid tags.
2021-05-05 10:00:19 -04:00
Shivaram Lingamneni
503e575633
Merge pull request #1640 from oragono/l10n_master
New Crowdin updates
2021-04-29 23:19:52 -04:00
Daniel Oaks
b0b4f0492c New translations irc.lang.json (Italian) 2021-04-30 13:04:46 +10:00
Daniel Oaks
14cc20c2a0 New translations irc.lang.json (Romanian) 2021-04-30 13:04:32 +10:00
Daniel Oaks
d35f38f161 New translations irc.lang.json (Spanish) 2021-04-30 13:04:27 +10:00
Daniel Oaks
648ad4a4c3 New translations irc.lang.json (Bosnian) 2021-04-30 13:03:45 +10:00
Daniel Oaks
9100fb7ca1 New translations irc.lang.json (French) 2021-04-30 13:03:16 +10:00
Shivaram Lingamneni
9983478479
Merge pull request #1639 from slingamn/strings
update strings
2021-04-29 22:58:52 -04:00
Shivaram Lingamneni
23ffc6ef91 update strings 2021-04-28 07:12:54 -04:00
Shivaram Lingamneni
a9294e628f
Merge pull request #1638 from oragono/l10n_master
New Crowdin updates
2021-04-28 07:11:59 -04:00
Daniel Oaks
8b693ec24f New translations irc.lang.json (French) 2021-04-28 16:25:34 +10:00
Shivaram Lingamneni
33616df7c4
Merge pull request #1637 from slingamn/config_warning
add a warning for the #1634 case
2021-04-27 08:49:25 -04:00
Shivaram Lingamneni
aa27ad98a8 add a warning for the #1634 case
Configurations with require-sasl and open account registration are valid,
but likely unintended. Show a warning about them.
2021-04-27 08:44:54 -04:00
Shivaram Lingamneni
5c157adf45
Merge pull request #1636 from slingamn/tor_require_sasl
propagate require-sasl into tor-listeners.require-sasl
2021-04-26 10:38:59 -04:00
Shivaram Lingamneni
2d31a16647 propagate require-sasl into tor-listeners.require-sasl 2021-04-26 10:26:16 -04:00
Shivaram Lingamneni
317720bfc8
Merge pull request #1632 from slingamn/mysql_safety
fix #1622
2021-04-25 23:14:40 -04:00
Shivaram Lingamneni
e14aace1da
Merge pull request #1635 from slingamn/pass
fix #1634 (forward-porting to master)
2021-04-25 21:34:12 -04:00
Shivaram Lingamneni
973d7dc1dc
Merge pull request #1633 from slingamn/register_400
fix FAIL REGISTER INVALID_USERNAME case
2021-04-25 20:43:15 -04:00
Shivaram Lingamneni
97ba1c3d63 fix #1634:
1. Fix auth bypass in the default configuration with the addition of
   server.password (the REGISTER command was allowed before connection
   registration, allowing unauthenticated users to REGISTER and then
   take advantage of skip-server-password)
2. Caution operators against the use of require-sasl without disabling
   user-initiated account registration. (Such a configuration is still valid
   in the case of a public server that requires everyone to register.)
2021-04-25 19:22:08 -04:00
Shivaram Lingamneni
75f89a9f1f fix FAIL REGISTER INVALID_USERNAME case 2021-04-25 17:16:34 -04:00
Shivaram Lingamneni
5eed48c077 fix #1622
Allow users to set max MySQL connections and connection lifetime;
set a sane default for max connections if it's not present.
2021-04-23 13:54:44 -04:00
Shivaram Lingamneni
a2b5548c8b
Merge pull request #1631 from slingamn/autoreplay_dms
fix autoreplay of DMs
2021-04-21 11:10:30 -04:00
Shivaram Lingamneni
c53926acde fix autoreplay of DMs
I broke this in #1615 / 0d05ab4ff4249f
2021-04-21 10:53:43 -04:00
Shivaram Lingamneni
0b414cb158
Merge pull request #1625 from slingamn/cleanup_star.2
fix #1615
2021-04-21 09:27:54 -04:00
Shivaram Lingamneni
6b8a487b0c
Merge pull request #1628 from slingamn/issue1627_channel_listing
fix #1627
2021-04-21 07:58:53 -04:00
Shivaram Lingamneni
8bf6231ec6
Merge pull request #1629 from jesopo/global-notice
implement mass/global messaging
2021-04-21 07:58:40 -04:00
jesopo
4700d4c048 make gofmt happey :)) 2021-04-20 11:05:05 +00:00
jesopo
7345ecba48 don't Sprintf for each match, Details().nick -> .Nick() 2021-04-20 11:04:24 +00:00
jesopo
76697dff0f "massmessage" oper capab in traditional.yaml too 2021-04-20 11:00:09 +00:00
jesopo
c74a64b888 add massmessage oper capab to default.yaml 2021-04-20 10:50:52 +00:00
jesopo
0a1537f928 support $$server and $#hostname global messages 2021-04-20 10:46:43 +00:00
Shivaram Lingamneni
88f8caad0b fix #1627
Fix incorrect description of channel ownership in NS INFO
2021-04-19 15:49:56 -04:00
Shivaram Lingamneni
0d05ab4ff4 fix #1615
Remove the CHATHISTORY * and znc.in/playback *self targets,
clean up associated database code, add new mechanisms to play
all missed DMs.
2021-04-19 08:54:40 -04:00
Shivaram Lingamneni
50d32924ef
Merge pull request #1623 from slingamn/close_1577
fix #1577
2021-04-19 07:54:43 -04:00
Shivaram Lingamneni
da216fc699 fix #1577
Remove debugging loglines for truncation
2021-04-19 07:06:49 -04:00
Shivaram Lingamneni
d2278faf75
Merge pull request #1621 from slingamn/deps
upgrade dependencies for 2.7 window
2021-04-19 06:59:08 -04:00
Shivaram Lingamneni
a7db4a669e upgrade yaml 2021-04-19 06:50:34 -04:00
Shivaram Lingamneni
6f56121662 upgrade x/text 2021-04-19 06:49:56 -04:00
Shivaram Lingamneni
c62edcc909 upgrade x/crypto and dependencies 2021-04-19 06:48:51 -04:00
Shivaram Lingamneni
57c5030e91 upgrade mysql driver 2021-04-19 06:42:39 -04:00
Shivaram Lingamneni
3ceb346c61
Merge pull request #1620 from slingamn/bugs
small fixes to kick off the 2.7 window
2021-04-19 06:32:10 -04:00
Shivaram Lingamneni
3e05502c3f bump irctest to latest 2021-04-18 23:16:37 -04:00
Shivaram Lingamneni
517b776b62 don't call (*Config).prepareListeners twice 2021-04-18 21:40:33 -04:00
Shivaram Lingamneni
1a5d079670 fix #1611
Allow setting the minimum TLS version
2021-04-18 21:40:33 -04:00
Shivaram Lingamneni
eb2dfa78c9 fix #1617
Prevent LUSERS stats from getting out of sync when modes are
modified on offline clients.
2021-04-18 20:18:02 -04:00
Shivaram Lingamneni
fed002d11a fix #1618
Allow snomasks to be added via oper config block, even if the oper
doesn't have `ban` or `snomasks` and therefore can't add snomasks
on their own.
2021-04-18 20:06:00 -04:00
Shivaram Lingamneni
3cca1e2c39 fix #1619
Clean up channels during unregistration if necessary.
2021-04-18 20:03:17 -04:00
Shivaram Lingamneni
fed5134a63 set up new development version 2021-04-18 17:03:25 -04:00
Shivaram Lingamneni
7481bf0385 bump version and changelog 2021-04-18 14:30:35 -04:00
Shivaram Lingamneni
41738471ce final tweak to changelog 2021-04-18 13:44:10 -04:00
Shivaram Lingamneni
319b9a6c6e note irctest_stable in release runbook 2021-04-15 07:24:00 -04:00
Shivaram Lingamneni
022330b9b8 manual: clarify SNI preference order 2021-04-14 05:18:35 -04:00
Shivaram Lingamneni
cda268fe1e fix some documentation 2021-04-11 00:05:46 -04:00
Shivaram Lingamneni
197d487f40 bump version for 2.6.0-rc1 2021-04-10 23:51:10 -04:00
Shivaram Lingamneni
2a99a8e2d5
Merge pull request #1614 from slingamn/changelog
changelog updates for 2.6.0-rc1
2021-04-10 23:40:50 -04:00
Shivaram Lingamneni
48932e7ab1 changelog updates for 2.6.0-rc1 2021-04-10 23:40:27 -04:00
Shivaram Lingamneni
a0a4ab4e17
Merge pull request #1612 from slingamn/forward_mode
publish the FORWARD 005 token
2021-04-08 07:33:17 -04:00
Shivaram Lingamneni
8dd12b0693 publish the FORWARD 005 token 2021-04-08 07:10:05 -04:00
Shivaram Lingamneni
639817f014
Merge pull request #1608 from slingamn/sni.1
close remaining 2.6 issues
2021-04-08 05:31:45 -04:00
Shivaram Lingamneni
5cd76f89d4 fix #1545
Warn users that NS UNREGISTER doesn't give them a "do-over";
the account name will remain reserved.
2021-04-08 05:17:37 -04:00
Shivaram Lingamneni
6817186224 fix #1518
UBAN ADD and DEL need to produce snomasks and loglines
2021-04-08 05:17:37 -04:00
Shivaram Lingamneni
745fd764dd fix #1524
Document permissions structure of CS AMODE
2021-04-08 00:55:30 -04:00
Shivaram Lingamneni
db41b2bc34 fix #765
CS INFO with no arguments should list your registered channels
2021-04-07 23:30:24 -04:00
Shivaram Lingamneni
1fc513cef0 document SNI 2021-04-07 23:30:24 -04:00
Shivaram Lingamneni
aecb28a616 support SNI 2021-04-07 23:30:24 -04:00
Shivaram Lingamneni
f9c1a00b91 populate (tls.Certificate).Leaf 2021-04-07 22:35:54 -04:00
Shivaram Lingamneni
2e3e4f72ba fix inverted error check in deleteCorrespondents 2021-04-07 22:16:18 -04:00
Shivaram Lingamneni
f0796b2eb5
Merge pull request #1607 from slingamn/semaphore_update
simplify semaphore release code
2021-04-07 08:56:53 -04:00
Shivaram Lingamneni
b83b051632
Merge pull request #1606 from slingamn/listcorrespondents.2
fix #1592
2021-04-07 08:55:59 -04:00
Shivaram Lingamneni
5b33cd436f remove unnecessary indirection in semaphore 2021-04-07 08:44:17 -04:00
Shivaram Lingamneni
549d06bc98 simplify semaphore release code 2021-04-07 08:33:19 -04:00
Shivaram Lingamneni
18b6e2f1cd implement CHATHISTORY TARGETS 2021-04-07 05:40:39 -04:00
Shivaram Lingamneni
4052cd12fe fix #1592
Implements the new `CHATHISTORY LISTCORRESPONDENTS` API.
2021-04-06 00:46:07 -04:00
Shivaram Lingamneni
2e9a0d4b2d
Merge pull request #1605 from slingamn/bunt_error
try to record buntdb errors from persisting lastSeen
2021-04-02 04:49:52 -04:00
Shivaram Lingamneni
fd3cbab6ee bump buntdb to v1.2.3
Potentially fixes the database corruption seen on #1603
2021-04-01 20:45:15 -04:00
Shivaram Lingamneni
b022c34a23 try to record buntdb errors from persisting lastSeen
See #1603
2021-03-31 07:06:58 -04:00
Shivaram Lingamneni
0b5544831d
Merge pull request #1599 from slingamn/issue1594_warn_nick
fix #1594
2021-03-18 04:45:29 -04:00
Shivaram Lingamneni
1f2f740344
Merge pull request #1600 from slingamn/issue1576
fix #1576
2021-03-18 04:45:11 -04:00
Shivaram Lingamneni
46c32094d7 review fixes
1. Use FAIL instead of WARN
2. Use NICKNAME_RESERVED instead of ACCOUNT_REQUIRED
3. Don't send 433 at all for registered clients, just send the FAIL
2021-03-18 04:38:28 -04:00
Shivaram Lingamneni
ab870c2ffe fix #1576
List IRCv3 CAPs in `NS CLIENTS LIST` output
2021-03-18 04:24:45 -04:00
Shivaram Lingamneni
67ee36f1ed fix #1594
Send a WARN NICK ACCOUNT_REQUIRED on attempt to take a reserved nick
2021-03-18 04:10:32 -04:00
Shivaram Lingamneni
a6cf667f06
Merge pull request #1598 from slingamn/issue1596_truncation
fix #1596
2021-03-18 04:06:38 -04:00
Shivaram Lingamneni
074a5a077e bump irc-go to include new ircutils function 2021-03-18 03:56:57 -04:00
Shivaram Lingamneni
e447c61c73 fix #1596
All truncation must be made UTF8-safe.
2021-03-18 03:49:12 -04:00
Shivaram Lingamneni
cd43fae478
Merge pull request #1597 from slingamn/away_session.1
fix #1531
2021-03-18 03:28:03 -04:00
Shivaram Lingamneni
0db7f88637
Merge pull request #1595 from slingamn/botmode.1
Fix #1562
2021-03-18 03:27:54 -04:00
Shivaram Lingamneni
07edf2dc1f consolidate auto-away string 2021-03-18 03:04:44 -04:00
Shivaram Lingamneni
70b20750aa fix #1531
AWAY status should be tracked per-session:

1. With auto-away enabled, away status is aggregated across sessions
   (if any session is not away, the client is not away, else use
   the away status that was set most recently)
2. With auto-away disabled, we get the legacy behavior where AWAY
   applies directly to the client
2021-03-18 02:53:18 -04:00
Shivaram Lingamneni
88b877fce4 ensure IsBot gets propagated into history 2021-03-17 19:01:38 -04:00
Shivaram Lingamneni
1efde964e1 Fix #1562
Implement the new bot mode spec:
https://github.com/ircv3/ircv3-specifications/pull/439
2021-03-17 14:36:52 -04:00
Shivaram Lingamneni
507d53c507
Merge pull request #1591 from slingamn/grabbag
some small fixes/enhancements
2021-03-11 02:01:08 -05:00
Shivaram Lingamneni
fe8e6551c3 fix #1502
RELAYMSG should respect mutes
2021-03-11 01:21:03 -05:00
Shivaram Lingamneni
76b0e44474 fix #1534
Improve error message for CS TRANSFER
2021-03-11 00:49:12 -05:00
Shivaram Lingamneni
5c4984f45f fix #1544
PerstentStatusMandatory should display as "enabled", not "mandatory",
in the context where it refers to a user-chosen setting.
2021-03-11 00:45:13 -05:00
Shivaram Lingamneni
44ed0b7a38 fix #1565
Allow chanops to delete channel messages from history
2021-03-11 00:45:13 -05:00
Shivaram Lingamneni
4e49a25ba6 fix #1570
Handle panics in rehash, even if it was triggered by SIGHUP
2021-03-10 23:04:16 -05:00
Shivaram Lingamneni
de31430fdc
Merge pull request #1590 from slingamn/ircgo_bump
bump irc-go again
2021-03-10 20:14:31 -05:00
Shivaram Lingamneni
131eb229bc use the new irc-go identifiers 2021-03-10 20:07:43 -05:00
Shivaram Lingamneni
896ed91d45 bump irc-go 2021-03-10 20:07:42 -05:00
Shivaram Lingamneni
19642680d2
Merge pull request #1588 from ajaspers/docs
Fix link to productionizing guide.
2021-03-07 15:53:35 -05:00
Alex Jaspersen
f495d30f1d Fix link to productionizing guide. 2021-03-07 12:36:00 -08:00
Shivaram Lingamneni
6e20e44879
Merge pull request #1587 from slingamn/irctest_bump
bump irctest
2021-03-06 19:55:35 -05:00
Shivaram Lingamneni
c1e9ac4c3c bump irctest 2021-03-05 15:03:35 -05:00
Shivaram Lingamneni
788b37b12a
Merge pull request #1586 from slingamn/issue1577_no_truncation.2
deprecate message truncation
2021-03-04 23:49:35 -05:00
Shivaram Lingamneni
03185ea4a9 deprecate message truncation
Implements #1577, but the issue should remain open until we clean up
the debugging loglines.
2021-03-04 22:29:34 -05:00
Shivaram Lingamneni
6fae02d335
Merge pull request #1585 from slingamn/issue1584_websocket_preference.1
fix #1584
2021-03-04 20:50:48 -05:00
Shivaram Lingamneni
f05c57344e go get and commit the websocket fork 2021-03-04 18:31:58 -05:00
Shivaram Lingamneni
7b8e15ff1d replace (via modules) gorilla/websocket with our fork
See #1584
2021-03-04 18:31:37 -05:00
Shivaram Lingamneni
26cb622f92
Merge pull request #1580 from ajaspers/plusR
Send 477 ERR_NEEDREGGEDNICK when an unregistered user messages a +R user.
2021-03-04 17:14:45 -05:00
Shivaram Lingamneni
ad3306fa3b
Merge pull request #1581 from ajaspers/docs
Minor documentation updates.
2021-03-03 23:59:53 -05:00
Shivaram Lingamneni
780bdc4cd2
Merge pull request #1579 from Mikaela/nginx-proxy-timeout
docs/MANUAL.md: add recommendation for proxy timeout
2021-03-03 22:58:52 -05:00
Alex Jaspersen
9c7e98ab53 Minor documentation updates.
The +M channel mode is documented twice. Merge the two sections.
Clarify that +R restricts both joining and speaking, but does not kick existing unregistered users.
2021-03-03 19:56:47 -08:00
Alex Jaspersen
23164054f6 Send 477 ERR_NEEDREGGEDNICK when an unregistered user messages a +R user.
Fixes #1064.
2021-03-03 19:36:29 -08:00
da84082e0a
docs/MANUAL.md: add recommendation for proxy timeout
Resolves: #1578
2021-03-03 21:12:58 +02:00
Shivaram Lingamneni
1762a168e8
Merge pull request #1558 from slingamn/websocket_compromise
implement candidate compromise proposal for websockets
2021-03-02 00:18:13 -05:00
Shivaram Lingamneni
00edd78f59
Merge pull request #1573 from slingamn/faq
update the FAQ to cover some controversial issues
2021-03-02 00:16:36 -05:00
Shivaram Lingamneni
088f4d1a37
Merge pull request #1574 from slingamn/nspassword
fix #1547
2021-03-02 00:14:43 -05:00
Shivaram Lingamneni
22af40c995 fix #1547
make PASSWORD an alias for PASSWD in nickserv
2021-03-01 22:40:39 -05:00
Shivaram Lingamneni
72f41f6b3f make systemd and letsencrypt two separate manual sections 2021-03-01 22:21:53 -05:00
Shivaram Lingamneni
96f575c739 update the FAQ to cover some controversial issues 2021-03-01 22:10:11 -05:00
Shivaram Lingamneni
872ca646f3
Merge pull request #1572 from slingamn/ircgo_upgrade
bump irc-go to fix buffering issue
2021-03-01 19:13:51 -05:00
Shivaram Lingamneni
992a09dc90 bump irc-go to fix buffering issue 2021-03-01 19:04:02 -05:00
Shivaram Lingamneni
eeb5f9b24d
Merge pull request #1560 from slingamn/configerrors
fix #1559
2021-03-01 17:10:29 -05:00
Shivaram Lingamneni
143e6ba9e3
Merge pull request #1571 from slingamn/validation
validate that passphrases are valid as non-final IRC parameters
2021-03-01 17:09:51 -05:00
Shivaram Lingamneni
d7ba478519 validate that passphrases are valid as non-final IRC parameters 2021-03-01 11:47:29 -05:00
Shivaram Lingamneni
67db9f3564
Merge pull request #1567 from oragono/l10n_master
New Crowdin updates
2021-02-28 17:51:57 -05:00
Shivaram Lingamneni
3a62fde6bd
Merge pull request #1569 from oragono/bump_irctest
bump irctest to latest upstream master
2021-02-28 17:51:17 -05:00
Shivaram Lingamneni
1642121203 bump irctest to latest upstream master 2021-02-28 17:42:06 -05:00
Shivaram Lingamneni
a3e5b3d991
Merge pull request #1568 from slingamn/register_update
improve compatibility with published register spec
2021-02-28 17:22:27 -05:00
Shivaram Lingamneni
8180c2b572 improve compatibility with published register spec
1. Send COMPLETE_CONNECTION_REQUIRED instead of DISALLOWED
2. Include the account name in all FAIL messages
2021-02-28 17:14:10 -05:00
Daniel Oaks
66f1ea9fd4 New translations nickserv.lang.json (French) 2021-03-01 02:34:26 +10:00
Daniel Oaks
19a71849cf New translations irc.lang.json (French) 2021-03-01 02:34:23 +10:00
Shivaram Lingamneni
1d3d9f4cf5
Merge pull request #1564 from slingamn/issue1563_modeparam
fix #1563
2021-02-27 23:26:02 -05:00
Shivaram Lingamneni
16f0170512 fix #1563
ERR_INVALIDMODEPARAM should take both the mode and the invalid parameter
as arguments.
2021-02-27 21:51:14 -05:00
Shivaram Lingamneni
e9d42e02a2 simplify utf8 validation of incoming WS lines
As of #1483, websockets entail enforce-utf8, so there's no need
to check globalUTF8EnforcementSetting when handling websockets.
2021-02-26 03:10:30 -05:00
Shivaram Lingamneni
29666107ab fix #1559
Improve debuggability of some config deserialization errors
2021-02-26 01:10:21 -05:00
Shivaram Lingamneni
d547d05205 implement candidate compromise proposal for websockets
1. Text and binary frames are accepted
2. Text frames are sent by default
3. Binary frames are sent to clients who negotiate `binary.ircv3.net`
4. Non-UTF8 data is not accepted (enabling websockets still enables UTFONLY)
2021-02-24 14:08:04 -05:00
Shivaram Lingamneni
640572e151 update release branch instructions 2021-02-24 13:21:53 -05:00
Shivaram Lingamneni
cbaa6af9bd
Merge pull request #1557 from slingamn/ircgo_bump
bump irc-go to latest
2021-02-22 23:31:38 -05:00
Shivaram Lingamneni
a6f3e2c748 bump irc-go to latest 2021-02-22 22:11:18 -05:00
Shivaram Lingamneni
2681097516
Merge pull request #1555 from slingamn/irctest_upstream
upgrade irctest to new, reintegrated master
2021-02-22 15:36:14 -05:00
Shivaram Lingamneni
cd5b6211b3 upgrade irctest to new, reintegrated master 2021-02-22 14:46:48 -05:00
Shivaram Lingamneni
092f193326
Merge pull request #1554 from slingamn/ircgo_upgrade
bump irc-go to latest
2021-02-21 21:35:44 -05:00
Shivaram Lingamneni
7af94c79fd bump irc-go to latest 2021-02-21 20:22:32 -05:00
Shivaram Lingamneni
08eabea78f
Merge pull request #1553 from slingamn/zncpanic
fix #1552
2021-02-21 16:00:01 -05:00
Shivaram Lingamneni
1f3f9f18d9 fix #1552
ZNC playback LIST was panicking when history was disabled,
and possibly in other cases
2021-02-21 15:29:19 -05:00
Shivaram Lingamneni
56bef19505
Merge pull request #1550 from slingamn/history_cap_disable
fix #1549
2021-02-21 13:28:43 -05:00
Shivaram Lingamneni
9e25a3027a fix #1549
If history is disabled, disable the history CAPs
2021-02-21 13:25:22 -05:00
Shivaram Lingamneni
3fc277e733 update migration script links to point to stable branch 2021-02-17 17:16:12 -05:00
Shivaram Lingamneni
7814694d17
Merge pull request #1543 from oragono/goupgrade
upgrade go to 1.16
2021-02-17 15:18:50 -05:00
Shivaram Lingamneni
430b40fc2f upgrade go to 1.16
Fixes #1510
2021-02-17 15:14:53 -05:00
Shivaram Lingamneni
dc4214a8ca suggest 0700 permissions for the role user homedir 2021-02-17 02:09:01 -05:00
Shivaram Lingamneni
8de7a0edbe
Merge pull request #1541 from oragono/l10n_master
New Crowdin updates
2021-02-16 00:24:19 -05:00
Daniel Oaks
58e3694e84 New translations chanserv.lang.json (Romanian) 2021-02-16 14:14:15 +10:00
Daniel Oaks
82eb55e0b2 New translations irc.lang.json (Romanian) 2021-02-16 14:14:13 +10:00
Shivaram Lingamneni
a6545728bd
Merge pull request #1540 from slingamn/ircmsg_rename
bump irc-go for the IRCMessage rename
2021-02-15 11:34:45 -05:00
Shivaram Lingamneni
e957a89ee2 bump irc-go for the IRCMessage rename 2021-02-15 11:27:04 -05:00
Shivaram Lingamneni
2fbbfb1337
Merge pull request #1539 from oragono/l10n_master
New Crowdin updates
2021-02-15 11:24:04 -05:00
Daniel Oaks
4d3f9dd509 New translations help.lang.json (Dutch) 2021-02-16 02:18:21 +10:00
Daniel Oaks
43cc2bf9be New translations chanserv.lang.json (Portuguese, Brazilian) 2021-02-16 02:16:13 +10:00
Daniel Oaks
d92bcf0358 New translations nickserv.lang.json (Portuguese, Brazilian) 2021-02-16 02:16:02 +10:00
Daniel Oaks
887fda5221 New translations translation.lang.yaml (Albanian) 2021-02-16 02:15:52 +10:00
Daniel Oaks
1fb736b394 New translations irc.lang.json (Portuguese, Brazilian) 2021-02-16 01:57:50 +10:00
Daniel Oaks
155fb3ea21 New translations chanserv.lang.json (Portuguese, Brazilian) 2021-02-16 01:57:48 +10:00
Daniel Oaks
0aa725e9f2 New translations irc.lang.json (Bosnian) 2021-02-16 01:57:42 +10:00
Daniel Oaks
d90124ca42 New translations help.lang.json (Bosnian) 2021-02-16 01:57:40 +10:00
Daniel Oaks
819228fdb5 New translations chanserv.lang.json (Bosnian) 2021-02-16 01:57:39 +10:00
Daniel Oaks
3f6971e087 New translations irc.lang.json (Greek) 2021-02-16 01:57:36 +10:00
Daniel Oaks
bb4a129c9a New translations chanserv.lang.json (German) 2021-02-16 01:57:34 +10:00
Daniel Oaks
6c727dfc1c New translations irc.lang.json (German) 2021-02-16 01:57:32 +10:00
Daniel Oaks
e0d3b54aaf New translations chanserv.lang.json (Albanian) 2021-02-16 01:57:07 +10:00
Daniel Oaks
3a1327966b New translations irc.lang.json (Romanian) 2021-02-16 01:57:05 +10:00
Daniel Oaks
aef082fd71 New translations help.lang.json (Romanian) 2021-02-16 01:57:03 +10:00
Daniel Oaks
a164b7aef9 New translations chanserv.lang.json (Romanian) 2021-02-16 01:57:02 +10:00
Daniel Oaks
7969229a07 New translations irc.lang.json (French) 2021-02-16 01:57:00 +10:00
Daniel Oaks
c0fc2620e3 New translations help.lang.json (French) 2021-02-16 01:56:59 +10:00
Daniel Oaks
3a8e9ec2ff New translations chanserv.lang.json (French) 2021-02-16 01:56:58 +10:00
Daniel Oaks
41fd2cbabe New translations irc.lang.json (Spanish) 2021-02-16 01:56:55 +10:00
Daniel Oaks
7d08b83eff New translations help.lang.json (Spanish) 2021-02-16 01:56:54 +10:00
Daniel Oaks
014d141b88 New translations irc.lang.json (Italian) 2021-02-16 01:56:45 +10:00
Daniel Oaks
e95ddc72af New translations help.lang.json (Italian) 2021-02-16 01:56:40 +10:00
Daniel Oaks
4da60a41d7 New translations irc.lang.json (Turkish) 2021-02-16 01:56:30 +10:00
Daniel Oaks
f1c927bbf8 New translations irc.lang.json (Chinese Simplified) 2021-02-16 01:56:22 +10:00
Daniel Oaks
f71345d096 New translations chanserv.lang.json (Chinese Simplified) 2021-02-16 01:56:20 +10:00
Daniel Oaks
64b868a725 New translations chanserv.lang.json (Italian) 2021-02-16 01:56:11 +10:00
Daniel Oaks
d873426f5b New translations help.lang.json (Dutch) 2021-02-16 01:55:59 +10:00
Daniel Oaks
06e109e347 New translations irc.lang.json (Norwegian) 2021-02-16 01:55:55 +10:00
Daniel Oaks
528678e2c9 New translations irc.lang.json (Polish) 2021-02-16 01:55:52 +10:00
Shivaram Lingamneni
38275601e1
Merge pull request #1538 from slingamn/strings
update strings
2021-02-14 23:03:58 -05:00
Shivaram Lingamneni
fdfa9de79f update strings 2021-02-14 22:58:02 -05:00
Shivaram Lingamneni
8876b61b9c
Merge pull request #1537 from oragono/l10n_master
New Crowdin updates
2021-02-14 22:57:06 -05:00
Daniel Oaks
230fdc57c8 New translations hostserv.lang.json (Albanian) 2021-02-15 13:54:15 +10:00
Daniel Oaks
0956ce78f4 New translations nickserv.lang.json (Albanian) 2021-02-15 13:54:13 +10:00
Daniel Oaks
cf5d6bdf9a New translations chanserv.lang.json (Albanian) 2021-02-15 13:54:12 +10:00
Daniel Oaks
14ff84a504 New translations help.lang.json (Albanian) 2021-02-15 13:54:11 +10:00
Daniel Oaks
6954d04af9 New translations irc.lang.json (Albanian) 2021-02-15 13:54:10 +10:00
Daniel Oaks
5ad165e31e New translations translation.lang.yaml (Albanian) 2021-02-15 13:54:09 +10:00
Shivaram Lingamneni
9fd4210416
Merge pull request #1536 from slingamn/ircreader.1
use the new goshuirc ircreader
2021-02-13 21:06:42 -05:00
Shivaram Lingamneni
d0e11f49ad use the new goshuirc ircreader 2021-02-13 20:58:19 -05:00
Shivaram Lingamneni
ec8ed2ae2a
Merge pull request #1535 from Mikaela/WeeChat-spelling
docs/USERGUIDE.md & irc/handlers.go: fix spelling of WeeChat
2021-02-13 18:42:14 -05:00
4340da9b6e
docs/USERGUIDE.md & irc/handlers.go: fix spelling of WeeChat 2021-02-13 14:44:13 +02:00
Shivaram Lingamneni
d8083e8de8
Merge pull request #1533 from slingamn/userguide_multiclient
explicitly document multiclient in the user guide
2021-02-12 13:17:34 -05:00
Shivaram Lingamneni
a1b33fc176 explicitly document multiclient 2021-02-12 13:16:53 -05:00
Shivaram Lingamneni
bb39399f97
Merge pull request #1528 from slingamn/issue1176_operprivs
enhancements to operator privilege handling
2021-02-09 22:56:58 -05:00
Shivaram Lingamneni
91cfdb963d fix #1074
Make snomask add/remove behavior match other ircds
2021-02-09 22:07:06 -05:00
Shivaram Lingamneni
42316bc04f fix #1176
Transition most "is an operator" checks to require a specific operator
capability
2021-02-09 22:07:06 -05:00
Shivaram Lingamneni
9aeb80dbf3
Merge pull request #1526 from slingamn/issue1516_amode_op
fix #1516
2021-02-09 13:59:29 -05:00
Shivaram Lingamneni
a9cae85052 bump irctest 2021-02-09 01:06:01 -05:00
Shivaram Lingamneni
dfc26d1182 fix a help typo 2021-02-09 01:05:47 -05:00
Shivaram Lingamneni
7ce396931c fix #1516
CS OP should regrant one's stored amode
2021-02-05 13:07:39 -05:00
Shivaram Lingamneni
4c08bc9c49
Merge pull request #1525 from slingamn/issue1523_halfop_topic
fix #1523
2021-02-05 13:07:07 -05:00
Shivaram Lingamneni
5227c144d7
Merge pull request #1522 from slingamn/issue1507_registered_channels
fix #1507
2021-02-05 13:06:54 -05:00
Shivaram Lingamneni
6f1bc9896b fix #1523
Let halfops change the channel topic
2021-02-05 11:02:31 -05:00
Shivaram Lingamneni
cc6be14c1d fix #1507
Registered channels should be eagerly created on startup, and should
remain (and be visible in LIST) even when they have no members.
2021-02-04 15:26:03 -05:00
Shivaram Lingamneni
1fad76b906 set up new development version (again) 2021-02-03 08:24:01 -05:00
Shivaram Lingamneni
4860c5cad0
Merge pull request #1521 from slingamn/pointfix
security fix necessitating 2.5.1
2021-02-02 17:09:46 -05:00
Shivaram Lingamneni
6f1380f77f bump version and changelog 2021-02-02 16:54:29 -05:00
Shivaram Lingamneni
de392aea5a fix incorrect permissions check in NS CLIENTS LOGOUT 2021-02-02 16:50:47 -05:00
Shivaram Lingamneni
e54d8cfcd9 fix duplicated word in CS AMODE help 2021-02-02 15:27:24 -05:00
Shivaram Lingamneni
ec48d6b97d set up new development version 2021-01-30 23:01:40 -05:00
Shivaram Lingamneni
7953804e72 bump version to 2.5.0 2021-01-30 22:19:08 -05:00
Shivaram Lingamneni
351f3bbc21 final documentation updates for 2.5.0 2021-01-30 22:07:51 -05:00
Shivaram Lingamneni
2ce74ff9c1 bump version for 2.5.0-rc1 2021-01-23 20:42:30 -05:00
Shivaram Lingamneni
7ba594613b
Merge pull request #1498 from slingamn/changelog.1
changelog updates for v2.5.0-rc1
2021-01-23 20:39:35 -05:00
Shivaram Lingamneni
6d3dd66031
Merge pull request #1505 from slingamn/uban_sasl_followup
require-sasl uban should not kill authenticated clients
2021-01-22 11:00:12 -05:00
Shivaram Lingamneni
81ae166dfb require-sasl uban should not kill authenticated clients 2021-01-22 10:49:54 -05:00
Shivaram Lingamneni
a988434bf3
Merge pull request #1504 from slingamn/soft_dline
allow UBAN <ip> REQUIRE-SASL
2021-01-22 10:11:01 -05:00
Shivaram Lingamneni
07fa2ecb3e allow UBAN <ip> REQUIRE-SASL 2021-01-22 09:38:40 -05:00
Shivaram Lingamneni
7728844ac4 documentation updates 2021-01-22 08:50:12 -05:00
Shivaram Lingamneni
7b45e81178
Merge pull request #1503 from slingamn/uban_tweak.1
tweaks to UBAN
2021-01-22 07:49:26 -05:00
Shivaram Lingamneni
8dd39a6e71 tweaks to UBAN 2021-01-22 07:27:10 -05:00
Shivaram Lingamneni
c14154c062
Merge pull request #1501 from slingamn/uban_fixes
fix UBAN INFO considering the wrong IP
2021-01-21 17:01:21 -05:00
Shivaram Lingamneni
a237ce428f fix permissions check in CS HOWTOBAN 2021-01-21 16:48:57 -05:00
Shivaram Lingamneni
6bd396f5a2 fix UBAN INFO considering the wrong IP 2021-01-21 16:40:01 -05:00
Shivaram Lingamneni
b2551a35e0
Merge pull request #1500 from oragono/l10n_master
New Crowdin updates
2021-01-21 12:02:10 -05:00
Daniel Oaks
9416b9f76d New translations irc.lang.json (German) 2021-01-22 02:59:46 +10:00
Daniel Oaks
b4b71f5b73 New translations chanserv.lang.json (German) 2021-01-22 02:59:44 +10:00
Daniel Oaks
7245862bac New translations irc.lang.json (Greek) 2021-01-22 02:59:41 +10:00
Daniel Oaks
d98d1573fb New translations irc.lang.json (Italian) 2021-01-22 02:59:24 +10:00
Daniel Oaks
f24be717aa New translations help.lang.json (Italian) 2021-01-22 02:59:21 +10:00
Daniel Oaks
826aa2504a New translations chanserv.lang.json (Italian) 2021-01-22 02:59:20 +10:00
Daniel Oaks
aaf5abfa0b New translations help.lang.json (Romanian) 2021-01-22 02:59:08 +10:00
Daniel Oaks
e20fb84853 New translations chanserv.lang.json (Romanian) 2021-01-22 02:59:06 +10:00
Daniel Oaks
09007e30c2 New translations irc.lang.json (French) 2021-01-22 02:59:02 +10:00
Daniel Oaks
024645493a New translations help.lang.json (French) 2021-01-22 02:59:00 +10:00
Daniel Oaks
2b088fae58 New translations chanserv.lang.json (French) 2021-01-22 02:58:58 +10:00
Daniel Oaks
2997375c12 New translations irc.lang.json (Spanish) 2021-01-22 02:58:55 +10:00
Daniel Oaks
ac07660a14 New translations help.lang.json (Spanish) 2021-01-22 02:58:53 +10:00
Daniel Oaks
55360ab48e New translations irc.lang.json (Romanian) 2021-01-22 02:58:31 +10:00
Daniel Oaks
712804f246 New translations irc.lang.json (Chinese Simplified) 2021-01-22 02:58:22 +10:00
Daniel Oaks
ddd05421cc New translations irc.lang.json (Portuguese, Brazilian) 2021-01-22 02:58:09 +10:00
Daniel Oaks
585e02e1ce New translations chanserv.lang.json (Portuguese, Brazilian) 2021-01-22 02:58:06 +10:00
Daniel Oaks
5dcf18c702 New translations irc.lang.json (English, Australia) 2021-01-22 02:58:04 +10:00
Daniel Oaks
9892d55e9b New translations irc.lang.json (Bosnian) 2021-01-22 02:57:59 +10:00
Daniel Oaks
48b498347c New translations help.lang.json (Bosnian) 2021-01-22 02:57:57 +10:00
Daniel Oaks
1e8befd6c0 New translations help.lang.json (Turkish) 2021-01-22 02:57:51 +10:00
Daniel Oaks
35263b29e0 New translations irc.lang.json (Norwegian) 2021-01-22 02:57:44 +10:00
Daniel Oaks
d71fa5711b New translations irc.lang.json (Turkish) 2021-01-22 02:57:33 +10:00
Shivaram Lingamneni
ba78489b87
Merge pull request #1499 from slingamn/updatestrings
update strings
2021-01-21 09:48:25 -05:00
Shivaram Lingamneni
7eb21df50a update strings 2021-01-21 09:40:32 -05:00
Shivaram Lingamneni
8198690ffd changelog updates for v2.5.0-rc1 2021-01-21 09:27:46 -05:00
Shivaram Lingamneni
0e311e27ee
Merge pull request #1497 from slingamn/bcrypt_cost
reduce recommended bcrypt cost to the lowest allowed value
2021-01-21 08:25:21 -05:00
Shivaram Lingamneni
e191e67632 reduce recommended bcrypt cost to the lowest allowed value
Two objectives:

1. Reduce thundering-herd effects on server restart (a cost of 4 should be
approximately 1 millisecond of CPU time per reconnecting client)
2. Speed up mobile reattach as much as possible (see also #1420)
2021-01-21 01:42:28 -05:00
Shivaram Lingamneni
2e7cf3cc1e
Merge pull request #1496 from slingamn/jointime.1
fix #1490
2021-01-21 01:20:45 -05:00
Shivaram Lingamneni
62d1f884eb
Merge pull request #1491 from slingamn/uban.3
UBAN and some other operator changes (fixes #1447)
2021-01-20 23:48:30 -05:00
Shivaram Lingamneni
4a48e52518 fix #1490
Track channel join times, use them to optionally enforce history access
restrictions
2021-01-20 21:13:18 -05:00
Shivaram Lingamneni
0d0fb87857
Merge pull request #1493 from oragono/l10n_master
New Crowdin updates
2021-01-19 19:19:28 -05:00
Daniel Oaks
5a0fa6a4b5 New translations hostserv.lang.json (Portuguese, Brazilian) 2021-01-20 07:58:13 +10:00
Daniel Oaks
2c609e4710 New translations nickserv.lang.json (Portuguese, Brazilian) 2021-01-20 07:58:11 +10:00
Shivaram Lingamneni
44bfca80a7 add IP addresses to UBAN ADD <account> output 2021-01-19 12:19:48 -05:00
Shivaram Lingamneni
bb5276553d initial UBAN implementation 2021-01-19 08:49:45 -05:00
Shivaram Lingamneni
64bc363cf1 fix #1443
Improve auditability of sensitive operator actions
2021-01-19 06:45:30 -05:00
Shivaram Lingamneni
e195854851 fix #1442
strip local_ from oper capab names, also consolidate unban into ban
2021-01-19 06:45:30 -05:00
Shivaram Lingamneni
8769e2d92a
Merge pull request #1489 from slingamn/documentation_stopgap
stopgap documentation update for SASL
2021-01-18 06:21:10 -05:00
Shivaram Lingamneni
4e036e7f6c stopgap documentation update for SASL
See #1488
2021-01-17 21:27:45 -05:00
Shivaram Lingamneni
096b2df41c
Merge pull request #1486 from slingamn/alwayson_expiration_followup
fix always-on expiration checks
2021-01-15 06:57:33 -05:00
Shivaram Lingamneni
6b7f0e15ac fix always-on expiration checks
checkAlwaysOnExpirationNoMutex was respecting registered status, but
always-on clients were not considered registered at the time of the
initial check, so they were being created regardless of expiration.
2021-01-15 06:50:35 -05:00
Shivaram Lingamneni
7b300a802f
Merge pull request #1484 from slingamn/utf8only
initial work on #1483
2021-01-15 06:21:53 -05:00
Shivaram Lingamneni
db81b15acb initial work on #1483
Add the new utf8-only cap, disallow non-utf8 when websockets are enabled
2021-01-15 06:19:13 -05:00
Shivaram Lingamneni
3e230e2a17
Merge pull request #1480 from slingamn/issue1479_tor_cloak
fix #1479
2021-01-15 06:18:35 -05:00
Shivaram Lingamneni
2fd537fc52
Merge pull request #1481 from slingamn/issue1476_grouped_sasl
fix #1476
2021-01-15 06:17:45 -05:00
Shivaram Lingamneni
5095a4d814 add commented-out PrivateNetwork to unit file 2021-01-14 01:17:46 -05:00
Shivaram Lingamneni
b7e9d6840b
Merge pull request #1485 from slingamn/manual_update
clarify productionizing guide
2021-01-13 18:09:10 -05:00
Shivaram Lingamneni
a14eecbb44 clarify productionizing guide 2021-01-13 17:56:30 -05:00
Shivaram Lingamneni
641981856b
Merge pull request #1482 from slingamn/tor_manual
update tor section of the manual
2021-01-13 03:25:22 -05:00
Shivaram Lingamneni
042d1320cd update tor section of the manual 2021-01-13 03:16:24 -05:00
Shivaram Lingamneni
bafadf3aac fix #1476
enable the use of grouped nicks as account names for SASL,
if force-nick-equals-account is disabled
2021-01-12 09:33:57 -05:00
Shivaram Lingamneni
d1f8317180 fix #1479
Give Tor clients who authenticate via SASL a unique cloak, so chanops
can ban *!*@tor-network.onion and still allow authenticated Tor users
2021-01-12 08:40:13 -05:00
Shivaram Lingamneni
f2a40b9e5d
Merge pull request #1475 from slingamn/roleplay_action_bug
fix incorrect CTCP ACTION messages sent by roleplay
2021-01-10 21:30:57 -05:00
Shivaram Lingamneni
ec1b8f9c3d
Merge pull request #1478 from slingamn/ircfmt_fix
bump ircfmt to fix snomask bug
2021-01-08 14:44:54 -05:00
Shivaram Lingamneni
3e86f68278 bump ircfmt to fix snomask bug 2021-01-08 14:33:22 -05:00
Shivaram Lingamneni
1db9cdd989 fix incorrect CTCP ACTION messages sent by roleplay 2020-12-31 14:06:19 -05:00
Shivaram Lingamneni
0db4b42ffe
Merge pull request #1474 from slingamn/issue1472_help_nick
fix #1472
2020-12-30 02:31:06 -05:00
Shivaram Lingamneni
ec375f5bdc consolidate ArgsToStrings 2020-12-30 00:41:34 -05:00
Shivaram Lingamneni
0c9ecbade7 ERR_HELPNOTFOUND should take the nick as a parameter 2020-12-29 13:19:46 -05:00
Shivaram Lingamneni
6965031aa9 fix #1472
HELP responses weren't taking the client nickname as a parameter,
as is standard.
2020-12-29 05:20:18 -05:00
Shivaram Lingamneni
a7c5696517
Merge pull request #1471 from slingamn/issue1467_ircfmt
fix #1467
2020-12-29 02:40:54 -05:00
Shivaram Lingamneni
4f3b005d18 bump ircfmt 2020-12-27 20:18:29 -05:00
Shivaram Lingamneni
7c4d016fcb fix incorrect sprintf-before-unescape 2020-12-27 20:17:24 -05:00
Shivaram Lingamneni
5342ab1bb1
Merge pull request #1470 from slingamn/issue1468_relaymsg_tag
fix #1468
2020-12-22 02:16:17 -05:00
Shivaram Lingamneni
a566c85b12
Merge pull request #1438 from slingamn/issue1436_badchars
fix #1436
2020-12-22 02:16:01 -05:00
Shivaram Lingamneni
8abbc1072b additionally disallow ; in nicknames 2020-12-21 23:09:34 -05:00
Shivaram Lingamneni
b3f2058d47 bump irctest 2020-12-21 22:08:00 -05:00
Shivaram Lingamneni
514a2e613f fix #1468 2020-12-21 22:07:48 -05:00
Shivaram Lingamneni
5c64612455
Merge pull request #1469 from slingamn/alwayson_expiration.1
expiration for always-on clients
2020-12-21 22:03:06 -05:00
Shivaram Lingamneni
9727ff5594 bump irctest 2020-12-21 05:12:20 -05:00
Shivaram Lingamneni
48166b5b4b Implement expiration for always-on clients
Fixes #810
2020-12-21 05:11:50 -05:00
Shivaram Lingamneni
be31d33dc4 shorten the cached batch ID for multilines 2020-12-18 03:13:01 -05:00
Shivaram Lingamneni
94df158c90
Merge pull request #1466 from oragono/l10n_master
New Crowdin updates
2020-12-16 13:30:54 -05:00
Daniel Oaks
a50925e348 New translations nickserv.lang.json (Portuguese, Brazilian) 2020-12-16 23:06:30 +10:00
Shivaram Lingamneni
91cc2156f0
Merge pull request #1463 from slingamn/issue1294_purge.1
Enhancements to CS PURGE
2020-12-16 02:24:45 -05:00
Shivaram Lingamneni
2af65a44ed
Merge pull request #1465 from oragono/l10n_master
New Crowdin updates
2020-12-16 02:23:06 -05:00
Daniel Oaks
20de0ad133 New translations nickserv.lang.json (Portuguese, Brazilian) 2020-12-16 11:02:27 +10:00
Shivaram Lingamneni
fd71b79bb8 Enhancements to CS PURGE
1. Consolidate PURGE and UNPURGE into subcommands
2. Add PURGE LIST
3. PURGE ADD now requires a confirmation code

Fixes #1294
2020-12-15 04:00:44 -05:00
Shivaram Lingamneni
4d5815ab2e
Merge pull request #1456 from slingamn/rename_skeleton_bug.2
fix critical bugs in RENAME
2020-12-15 02:02:07 -05:00
Shivaram Lingamneni
5b79e427b5
Merge pull request #1460 from slingamn/issue1449_invite_playback
fix #1449
2020-12-14 23:43:27 -05:00
Shivaram Lingamneni
f717fceead
Merge pull request #1461 from slingamn/issue1452_enable_mysql
fix #1452
2020-12-14 23:42:47 -05:00
Shivaram Lingamneni
cee8f0fcc8 bump irctest 2020-12-14 23:35:46 -05:00
Shivaram Lingamneni
cc2b6d27a0 fix critical bugs in RENAME
Channel rename (both of registered and unregistered channels) would leave
the old name unreclaimable.
2020-12-14 23:35:46 -05:00
Shivaram Lingamneni
0d1521d4c4
Merge pull request #1458 from slingamn/issue1260_channel_forward.1
fix #1260
2020-12-14 22:53:24 -05:00
Shivaram Lingamneni
c217dbb8e4 add an irctest for forwarding 2020-12-14 22:38:23 -05:00
Shivaram Lingamneni
ba72d3acfc implement a channel forwarding mode
Fixes #1260
2020-12-14 22:38:23 -05:00
Shivaram Lingamneni
b7b45e03d8
Merge pull request #1462 from oragono/l10n_master
New Crowdin updates
2020-12-14 22:36:02 -05:00
Daniel Oaks
6953b483ff New translations nickserv.lang.json (Portuguese, Brazilian) 2020-12-15 10:22:43 +10:00
Shivaram Lingamneni
dd4fd1cbb6 fix #1452 2020-12-14 15:31:55 -05:00
Shivaram Lingamneni
15a0cda78b pointless refactor of stripMaskFromNick 2020-12-14 15:23:01 -05:00
Shivaram Lingamneni
a934392987
Merge pull request #1459 from oragono/l10n_master
New Crowdin updates
2020-12-14 08:27:03 -05:00
Shivaram Lingamneni
853bb12c29 fix #1449
INVITE playback (#1409) was buggy, due to the double use of (Item).Params[0]
for the channel name and the recipient nick. Stuff the channel name in
(Item).Message.Message instead.
2020-12-14 08:24:38 -05:00
Daniel Oaks
ff3f62658e New translations chanserv.lang.json (Portuguese, Brazilian) 2020-12-14 22:23:35 +10:00
Daniel Oaks
de0d4d3860 New translations hostserv.lang.json (Portuguese, Brazilian) 2020-12-14 22:23:33 +10:00
Shivaram Lingamneni
9033d97c6f
Merge pull request #1450 from slingamn/tor_defcon
DEFCON 4 and lower should require SASL from Tor users
2020-12-13 20:06:30 -05:00
Shivaram Lingamneni
5e03c560d3
Merge pull request #1454 from slingamn/maxlinelen_caps
make CAP LS/LIST respect the MaxLineLen constant
2020-12-13 20:05:40 -05:00
Shivaram Lingamneni
856a76eaf7
Merge pull request #1457 from oragono/l10n_master
New Crowdin updates
2020-12-13 20:05:17 -05:00
Daniel Oaks
b9d3f57149 New translations hostserv.lang.json (Portuguese, Brazilian) 2020-12-14 10:22:22 +10:00
Shivaram Lingamneni
47f7b9a76e make CAP LS/LIST respect the MaxLineLen constant 2020-12-13 15:44:10 -05:00
Shivaram Lingamneni
aa43822c68
Merge pull request #1451 from slingamn/mysql_quickstart
add a mysql quickstart guide
2020-12-11 02:37:45 -08:00
Shivaram Lingamneni
7465db0149 add a mysql quickstart guide 2020-12-11 05:34:01 -05:00
Shivaram Lingamneni
534939c342 DEFCON 4 and lower should require SASL from Tor users 2020-12-11 05:04:56 -05:00
Shivaram Lingamneni
9cf854405d
Merge pull request #1448 from slingamn/config_update
clean up some config comments
2020-12-08 23:35:16 -08:00
Shivaram Lingamneni
96380adf27
Merge pull request #1446 from slingamn/reservation_docs
manual updates for nick reservation and email verification
2020-12-08 23:10:01 -08:00
Shivaram Lingamneni
b3daf51f0a clean up some config comments 2020-12-09 02:09:35 -05:00
Shivaram Lingamneni
58edabf5c3
Merge pull request #1445 from slingamn/flatiptypes.5
introduce "flat ip" representations
2020-12-08 20:50:31 -08:00
Shivaram Lingamneni
84e3b5d77b stop autocreating d-lines for throttle violations
This didn't work correctly for IPv6 or custom nets.
/UNDLINE IP can temporarily be used to reset the throttle.
2020-12-08 22:01:23 -05:00
Shivaram Lingamneni
9a28cd38c3 update email verification manual entry 2020-12-08 02:11:53 -05:00
Shivaram Lingamneni
45471138d2 update nick reservation docs 2020-12-08 02:11:53 -05:00
Shivaram Lingamneni
44cc4c2092 introduce "flat ip" representations 2020-12-07 21:21:10 -05:00
Shivaram Lingamneni
85c39f3ea0
Merge pull request #1444 from slingamn/issue1439_confusable_import
fix #1439, give all atheme group founders +q
2020-12-07 16:06:46 -08:00
Shivaram Lingamneni
579924c832 give all groupserv founders +q in atheme2json 2020-12-07 17:26:31 -05:00
Shivaram Lingamneni
c8c1ddfb41 fix #1439
Validate imported databases for confusable nicks
2020-12-07 13:35:58 -05:00
Shivaram Lingamneni
e61735aa2c
Merge pull request #1440 from Mikaela/exampleconfig-consistency-ip-cidr
{default,traditional}.yaml: refer to IPs/CIDR instead of addrs
2020-12-07 08:43:35 -08:00
a68d4da5df
{default,traditional}.yaml: refer to IPs/CIDR instead of addrs
Whenever CIDR is mentioned in the config, it's in combination with IP so
talking about addressese in these points gives wrong impression that a
domain name would be valid as those are often thought as addresses.
2020-12-07 12:40:58 +02:00
Shivaram Lingamneni
48d5bd9144 fix #1436 2020-12-07 03:51:52 -05:00
Shivaram Lingamneni
31003bd02d fix bugs in atheme2json.py 2020-12-07 02:53:45 -05:00
Shivaram Lingamneni
d4dd161c45
Merge pull request #1435 from slingamn/morebugs
fix some bugs
2020-12-06 23:31:42 -08:00
Shivaram Lingamneni
e994e0451a fix incorrect vhost serialization 2020-12-07 02:29:18 -05:00
Shivaram Lingamneni
da2d4ca130 make overridden services hostname appear in chanserv 2020-12-07 02:29:18 -05:00
Shivaram Lingamneni
8c2d0762e4
Merge pull request #1434 from slingamn/need_privs
change FAIL codes from NOT_PRIVED to PRIVS_NEEDED
2020-12-06 23:28:04 -08:00
Shivaram Lingamneni
30b514cf95 bump irctest 2020-12-07 02:25:39 -05:00
Shivaram Lingamneni
66d90bef5e change FAIL codes from NOT_PRIVED to PRIVS_NEEDED 2020-12-06 21:01:44 -05:00
Shivaram Lingamneni
937e519595
Merge pull request #1433 from slingamn/import_enhancements
fix #1403
2020-12-06 17:55:02 -08:00
Shivaram Lingamneni
468b5a4a39 fix #1403
Handle the case where atheme had a successor, but didn't correctly
elect them as founder (?)
2020-12-06 20:50:06 -05:00
Shivaram Lingamneni
b95afa1464 preemptively filter out masks from amodes in atheme import 2020-12-06 20:07:45 -05:00
Shivaram Lingamneni
f9768e2259
Merge pull request #1429 from slingamn/issue1428_tor_sts.1
fix #1428
2020-12-06 13:56:33 -08:00
Shivaram Lingamneni
82332b3f37
Merge pull request #1432 from slingamn/issue1431_topic
fix #1431
2020-12-06 09:59:10 -08:00
Shivaram Lingamneni
d6a2c0db4a fix #1431 2020-12-06 12:38:18 -05:00
Shivaram Lingamneni
96fd1c04d6
Merge pull request #1430 from oragono/devel+operblockchanges
fix #1426
2020-12-06 00:11:26 -08:00
Daniel Oaks
cd6d9826b2 Tweaked oper blocks.
I think that 'moderator vs admin' is a pretty common set of priv levels,
whereas 'oper vs admin' is a little confusing, esp. to less irc-savvy
people.

/SAJOIN and /SAMODE are really common for joining channels to check out
what's going on and for e.g. opping someone when nobody in the channel
is opered, so it makes sense for mods to have those. I feel similarly
about vhosts, they're usually something that's delegated to less-prived
opers.

Changed the whois line from 'a server admin' to 'the server admin' to
make it a bit more clear that this is one single user, rather than a set
of privs to be shared. And it's a tiny thing, but removed the 'less
privileged' term from alice's oper block because it felt a bit dodgy.
2020-12-06 17:01:32 +10:00
Shivaram Lingamneni
7bdbb01238 fix #1428
Tor listeners should never see an STS cap.

Add an undocumented 'hide-sts' key for listeners that hides the STS cap.
This can be used if the listener is secured at layer 3 or 4 (VPNs,
E2E mixnets). It will be necessary to add the relevant IPs to `secure-nets`.
2020-12-05 23:06:23 -05:00
Shivaram Lingamneni
c8d999c95f clean up and clarify example operator blocks
See #1426
2020-12-05 20:40:44 -05:00
Shivaram Lingamneni
23a7221137
Merge pull request #1425 from slingamn/issue1421_customlimits.2
fix #1421
2020-12-05 16:03:51 -08:00
Shivaram Lingamneni
f9b842c88b fix #1421
Allow custom limit definitions that encompass multiple subnets
2020-12-04 14:24:19 -05:00
Shivaram Lingamneni
7e56f62aed
Merge pull request #1419 from slingamn/alwayson_channelmodes.1
fix #1345
2020-12-04 02:28:38 -08:00
Shivaram Lingamneni
7624936d8c
Merge pull request #1424 from slingamn/import_enhancements
validate amode recipients during data import
2020-12-04 01:55:01 -08:00
Shivaram Lingamneni
281821ed0e validate amode recipients 2020-12-04 04:50:40 -05:00
Shivaram Lingamneni
79e3f4a15c
Merge pull request #1423 from slingamn/import_certfps
enhancements to database import
2020-12-04 01:40:29 -08:00
Shivaram Lingamneni
82d29aa9b2
Merge pull request #1422 from jlu5/master
atheme2json enhancements
2020-12-04 01:39:56 -08:00
Shivaram Lingamneni
32f3c69a08 support certfp import 2020-12-04 04:37:24 -05:00
James Lu
2dcb084e0f atheme2json: translate channel flag +a to the corresponding amode 2020-12-04 01:08:23 -08:00
James Lu
22b0263c5b atheme2json: flatten channels owned by GroupServ groups to the first group founder 2020-12-04 01:08:23 -08:00
Shivaram Lingamneni
c0bc485840 schema change for #1345
Convert the flat list of channels for always-on clients
to a map from channel names to channel-user modes.
2020-12-02 15:32:17 -05:00
Shivaram Lingamneni
51f279289d fix #1345
Store the channel-user modes of always-on clients along with their
channel memberships, restore them on server startup. This will coexist
alongside /CS AMODE, which autoapplies modes to clients on join regardless
of their always-on status.
2020-12-02 15:31:49 -05:00
Shivaram Lingamneni
3aeac42978
Merge pull request #1418 from slingamn/issue1417_joinzero
fix #1417
2020-12-01 14:05:26 -08:00
Shivaram Lingamneni
01291ceadd fix #1417
Allow `JOIN 0` with a confirmation code
2020-12-01 14:23:47 -05:00
Shivaram Lingamneni
a291e5ca74
Merge pull request #1416 from slingamn/tagmsg_bug.3
fix TAGMSG being relayed to clients without message-tags
2020-11-30 14:23:54 -08:00
Shivaram Lingamneni
b9393e5600 bump irctest 2020-11-30 17:08:53 -05:00
Shivaram Lingamneni
1b91360885 fix incorrect TAGMSG relaying 2020-11-30 17:08:53 -05:00
Shivaram Lingamneni
db100f1f91
Merge pull request #1231 from slingamn/buffer.2
more memory-efficient implementation of line reading
2020-11-30 02:34:25 -08:00
Shivaram Lingamneni
a34918e729 add a fuzz test for IRCStreamConn changes 2020-11-30 02:08:47 -05:00
Shivaram Lingamneni
5edfcced0a
Merge pull request #1415 from slingamn/issue1409_invite_history.1
fix #1409
2020-11-29 19:47:22 -08:00
Shivaram Lingamneni
b4cd62bece
Merge pull request #1414 from slingamn/issue1396_account_snomask
fix #1396
2020-11-29 19:46:12 -08:00
Shivaram Lingamneni
b28004bbd9
Merge pull request #1410 from slingamn/welcome
add network name to RPL_WELCOME
2020-11-29 19:45:36 -08:00
Shivaram Lingamneni
32bbde49a8 fix #1409
Record INVITE in DM history for the benefit of offline always-on clients
2020-11-29 22:12:06 -05:00
Shivaram Lingamneni
c62956f708 fix #1396
* Defer account login snomask until after connection registration is complete
* Don't send account-notify for pre-registration clients
2020-11-29 20:20:26 -05:00
Shivaram Lingamneni
0e470763a2
Merge pull request #1413 from slingamn/issue1411_dmtags
fix #1411
2020-11-29 17:03:38 -08:00
Shivaram Lingamneni
2d857b21c6 bump irctest 2020-11-29 19:57:47 -05:00
Shivaram Lingamneni
e18eb1db99 fix #1411
Clients without event-playback should receive client-only tags in
replayed PRIVMSG / NOTICE.
2020-11-29 19:57:47 -05:00
Shivaram Lingamneni
42d5e767dd add network name to RPL_WELCOME 2020-11-29 16:34:52 -05:00
Shivaram Lingamneni
0fcaf778e0
Merge pull request #1400 from slingamn/issue1387_messagecaching.4
fix #1387
2020-11-29 02:32:59 -08:00
Shivaram Lingamneni
34fc8aa3c8
Merge pull request #1408 from slingamn/services_source.2
fix #1407
2020-11-29 02:31:48 -08:00
Shivaram Lingamneni
3ee6fd1f6c allow overriding services hostname
Fixes #1407
2020-11-29 00:02:26 -05:00
Shivaram Lingamneni
9214d978d0 refactor services prefixes and notice handlers 2020-11-29 00:02:26 -05:00
Shivaram Lingamneni
013c138977
Merge pull request #1406 from oragono/l10n_master
New Crowdin updates
2020-11-28 15:01:30 -08:00
Daniel Oaks
5fd04b71a5 New translations nickserv.lang.json (French) 2020-11-28 16:32:35 +10:00
Daniel Oaks
2a26520240 New translations chanserv.lang.json (French) 2020-11-28 16:32:33 +10:00
Daniel Oaks
7a3da37b0d New translations help.lang.json (French) 2020-11-28 16:32:31 +10:00
Daniel Oaks
74bc7a2ef0 New translations irc.lang.json (French) 2020-11-28 16:32:29 +10:00
Shivaram Lingamneni
bec88be799
Merge pull request #1405 from kylef/kylef/mode-order
Make atheme2json sort channel modes
2020-11-27 11:34:22 -08:00
Kyle Fuller
7e5cdc4f90 refactor atheme2json to sort channel modes
This makes invoking the script multiple times with the same input return
the same result, which may not be the case before because sets are
unordered and thus the channel modes can become reordered across
multiple invocations of the script.
2020-11-27 19:20:58 +00:00
Shivaram Lingamneni
c4b7b8af3a
Merge pull request #1402 from kylef/kylef/mlock-i
Allow atheme2json to handle mlock mode removal (outside of `-t` or `-n`)
2020-11-27 10:26:51 -08:00
Kyle Fuller
97bb044640 fix atheme2json to handle mlock mode removal
For example, if the mlock is set to `-i`, `i` is not present in the
`modes` and thus it would error.

I'm inclined to think that the negative mlock feature doesn't behave
correctly, because the mlock of `-i` (or `-n`) would prevent anyone from
ever setting those modes on the channel. Which this does not appear to
be the case.

Fixes #1401
2020-11-27 16:29:08 +00:00
Shivaram Lingamneni
ec15d367ba fix #1387
Instead of building a new serialized message for each recipient,
try to cache them.
2020-11-27 00:13:47 -05:00
Shivaram Lingamneni
f04648e081
Merge pull request #1398 from slingamn/unkdline
mention UN[DK]LINE in [DK]LINE help
2020-11-26 10:13:53 -08:00
Shivaram Lingamneni
a0d7d8d028 mention UN[DK]LINE in [DK]LINE help 2020-11-26 12:54:50 -05:00
Shivaram Lingamneni
6e2119be60
Merge pull request #1397 from slingamn/resume_joinline
fix duplicated JOIN line sent to some resuming clients
2020-11-25 21:30:35 -08:00
Shivaram Lingamneni
b7377d3f67 bump irctest 2020-11-26 00:27:23 -05:00
Shivaram Lingamneni
8d44fa3c3f fix duplicated JOIN line sent to resuming clients
Resuming clients without the resume capability would receive
two JOIN lines per channel.
2020-11-26 00:27:21 -05:00
Shivaram Lingamneni
453257aace
Merge pull request #1395 from Mikaela/patch-1
docs/MANUAL.md: fix atheme2json link
2020-11-24 09:23:00 -08:00
18dc9081d6
docs/MANUAL.md: fix atheme2json link 2020-11-24 14:32:52 +02:00
Shivaram Lingamneni
5326fd0d06
Merge pull request #1394 from slingamn/rehash
add a manual entry for rehash
2020-11-23 19:45:57 -08:00
Shivaram Lingamneni
e9197f1329 remove version number from manual 2020-11-23 20:39:45 -05:00
Shivaram Lingamneni
15406e0844 add a manual entry for rehash 2020-11-23 20:37:42 -05:00
Shivaram Lingamneni
722a1a80ef
Merge pull request #1392 from slingamn/proxydocs
documentation updates for proxy v2
2020-11-19 14:20:59 -08:00
Shivaram Lingamneni
c57828eb62 documentation updates for proxy v2 2020-11-19 17:01:56 -05:00
Shivaram Lingamneni
f30bd2cff0
Merge pull request #1391 from slingamn/proxy.3
fix #1389
2020-11-19 09:43:55 -08:00
Shivaram Lingamneni
3062f97c2b fix #1389
Support PROXY protocol v2, including ahead of plaintext connections
2020-11-19 12:31:58 -05:00
Shivaram Lingamneni
9ce72a4b02
Merge pull request #1388 from slingamn/builder
upgrade ircmsg again
2020-11-17 19:25:27 -08:00
Shivaram Lingamneni
3f20676f46 upgrade ircmsg again 2020-11-17 21:53:14 -05:00
Shivaram Lingamneni
323ef7c642
Merge pull request #1386 from slingamn/issue1385
upgrade ircmsg; fixes #1385
2020-11-15 21:56:54 -08:00
Shivaram Lingamneni
61e15ca877 upgrade ircmsg; fixes #1385
Validate tag names and values in accordance with the ratified message-tags spec
2020-11-15 23:05:08 -05:00
Shivaram Lingamneni
99fca0553b fix auth-script example in the manual 2020-11-13 12:44:25 -05:00
Shivaram Lingamneni
4fc396c3ef
Merge pull request #1383 from slingamn/chanserv
fix output bugs in AMODE handling
2020-11-12 23:41:32 -08:00
Shivaram Lingamneni
a6a8548466 fix SAJOIN not sending a MODE line where applicable 2020-11-12 11:57:30 -05:00
Shivaram Lingamneni
a8ffb11deb use server name as source for chanserv mode changes, for consistency 2020-11-12 11:50:28 -05:00
Shivaram Lingamneni
7cfb298617
Merge pull request #1382 from slingamn/unsuspend_bug
fix casefolding issue in NS SUSPEND DEL
2020-11-11 09:14:28 -08:00
Shivaram Lingamneni
6a6f104899 fix casefolding issue in NS SUSPEND DEL
NS SUSPEND DEL incorrectly required the use of the casefolded account name.
2020-11-11 11:09:09 -05:00
Shivaram Lingamneni
461e18f4f0
Merge pull request #1381 from slingamn/nsrename.3
implement NS RENAME
2020-11-10 23:11:39 -08:00
Shivaram Lingamneni
4f571c2cf3 implement NS RENAME
Fixes #1380
2020-11-10 19:59:12 -05:00
Shivaram Lingamneni
4861ac90b6
Merge pull request #1379 from slingamn/kline_duplicate
fix duplicated nicks in kline message
2020-11-10 08:22:01 -08:00
Shivaram Lingamneni
8ad6a53433 fix duplicated nicks in kline message 2020-11-10 11:17:17 -05:00
Shivaram Lingamneni
68f4032588
Merge pull request #1378 from slingamn/travis_branch_whitelist
explicitly whitelist branches for building
2020-11-09 21:55:43 -08:00
Shivaram Lingamneni
0bb0d382ee explicitly whitelist branches for building 2020-11-09 14:51:39 -05:00
Shivaram Lingamneni
fc5efcea7a
Merge pull request #1377 from oragono/l10n_master
New Crowdin updates
2020-11-09 08:59:55 -08:00
Daniel Oaks
fdaf26d6c5 New translations help.lang.json (Romanian) 2020-11-09 19:53:08 +10:00
Daniel Oaks
e704469de8 New translations irc.lang.json (Romanian) 2020-11-09 19:53:06 +10:00
Shivaram Lingamneni
60ede87ab0 set up new development version 2020-11-08 20:17:22 -05:00
Shivaram Lingamneni
75ba45f655
Merge pull request #1376 from slingamn/deps
dependency upgrades for 2.5
2020-11-08 17:10:20 -08:00
Shivaram Lingamneni
f8bf3818fe upgrade confusables 2020-11-08 18:13:50 -05:00
Shivaram Lingamneni
7535091d92 upgrade x/text 2020-11-08 17:58:56 -05:00
Shivaram Lingamneni
640dfea85a upgrade x/sys 2020-11-08 17:57:58 -05:00
Shivaram Lingamneni
bd959207e4 upgrade x/crypto 2020-11-08 17:57:24 -05:00
Shivaram Lingamneni
4f9d406b93 upgrade go-dkim 2020-11-08 17:56:46 -05:00
Shivaram Lingamneni
008416e4dd upgrade buntdb and dependencies 2020-11-08 17:55:22 -05:00
Shivaram Lingamneni
025f062a43
Merge pull request #1375 from slingamn/typofix
fix channel mode help text
2020-11-08 14:37:04 -08:00
Shivaram Lingamneni
3583e60f5e
Merge pull request #1374 from oragono/l10n_master
New Crowdin updates
2020-11-08 14:34:07 -08:00
Shivaram Lingamneni
58b5d3c72d fix channel mode help text 2020-11-08 17:33:45 -05:00
Daniel Oaks
ac19fac703 New translations irc.lang.json (Romanian) 2020-11-09 07:52:42 +10:00
Shivaram Lingamneni
eeb7c6cb91 bump version to 2.4.0 2020-11-07 21:34:21 -05:00
Shivaram Lingamneni
9e5d158c30 bump changelog for v2.4.0 2020-11-07 21:33:52 -05:00
Shivaram Lingamneni
0d3d910716 update release runbook 2020-11-06 13:55:09 -05:00
Shivaram Lingamneni
42af936c48
Merge pull request #1373 from oragono/l10n_master
New Crowdin updates
2020-11-05 23:26:50 -08:00
Daniel Oaks
499581b651 New translations hostserv.lang.json (French) 2020-11-06 17:22:30 +10:00
Daniel Oaks
0b34c7a783 New translations nickserv.lang.json (French) 2020-11-06 17:22:28 +10:00
Daniel Oaks
ff3c5b4024 New translations chanserv.lang.json (French) 2020-11-06 17:22:26 +10:00
Daniel Oaks
e0f3f93b04 New translations help.lang.json (French) 2020-11-06 17:22:25 +10:00
Daniel Oaks
f7e7bdbda0 New translations irc.lang.json (French) 2020-11-06 17:22:23 +10:00
Shivaram Lingamneni
507a11a693
Merge pull request #1372 from slingamn/sequence
implement the new chathistory fail code INVALID_TARGET
2020-11-04 07:58:22 -08:00
Shivaram Lingamneni
e67995bea7 bump irctest 2020-11-04 01:54:59 -05:00
Shivaram Lingamneni
9374a75c7a implement FAIL CHATHISTORY INVALID_TARGET 2020-11-04 01:54:59 -05:00
Shivaram Lingamneni
8bb867d3dc
Merge pull request #1371 from slingamn/issue1370_mutes
fix #1370
2020-11-01 15:43:18 -08:00
Shivaram Lingamneni
ee849d2e8f include a regression test in irctest 2020-11-01 18:15:36 -05:00
Shivaram Lingamneni
dac43e6e76 fix #1370 2020-11-01 18:09:04 -05:00
Shivaram Lingamneni
40118d1533 bump version to 2.4.0-rc1 2020-10-31 22:17:58 -04:00
Shivaram Lingamneni
59c17dd409
Merge pull request #1368 from slingamn/changelog.1
changelog and documentation updates for 2.4.0-rc1
2020-10-31 19:14:22 -07:00
Shivaram Lingamneni
a1d63f62cc changelog and documentation updates for 2.4.0-rc1 2020-10-31 21:54:24 -04:00
Shivaram Lingamneni
42ae35d266
Merge pull request #1367 from slingamn/registered_flag_whoreply
fix #1366
2020-10-30 08:42:35 -07:00
Shivaram Lingamneni
6513136ea3 fix #1366
Include an 'r' flag in RPL_WHOREPLY for registered nicks
2020-10-29 14:25:28 -04:00
Shivaram Lingamneni
5f09dbab42
Merge pull request #1365 from oragono/l10n_master
New Crowdin updates
2020-10-29 08:41:32 -07:00
Daniel Oaks
6dc85138d0 New translations chanserv.lang.json (Dutch) 2020-10-30 01:24:58 +10:00
Shivaram Lingamneni
f89fe162bf
Merge pull request #1364 from oragono/l10n_master
New Crowdin updates
2020-10-29 07:09:30 -07:00
Daniel Oaks
70f98d4d75 New translations hostserv.lang.json (Bosnian) 2020-10-29 13:26:15 +10:00
Daniel Oaks
516381a6ae New translations help.lang.json (Hebrew) 2020-10-29 13:26:13 +10:00
Daniel Oaks
866ae35196 New translations irc.lang.json (Hebrew) 2020-10-29 13:26:12 +10:00
Daniel Oaks
0362ea7d6e New translations hostserv.lang.json (Finnish) 2020-10-29 13:26:10 +10:00
Daniel Oaks
29cd33c199 New translations nickserv.lang.json (Finnish) 2020-10-29 13:26:09 +10:00
Daniel Oaks
83ac94a915 New translations chanserv.lang.json (Finnish) 2020-10-29 13:26:07 +10:00
Daniel Oaks
d58e7b7b6a New translations help.lang.json (Finnish) 2020-10-29 13:26:05 +10:00
Daniel Oaks
fe0d3557d7 New translations irc.lang.json (Finnish) 2020-10-29 13:26:04 +10:00
Daniel Oaks
9fbd4978af New translations chanserv.lang.json (Hebrew) 2020-10-29 13:26:02 +10:00
Daniel Oaks
ce2e109199 New translations hostserv.lang.json (Greek) 2020-10-29 13:26:00 +10:00
Daniel Oaks
f6b3b2696e New translations chanserv.lang.json (Greek) 2020-10-29 13:25:58 +10:00
Daniel Oaks
53d61e74af New translations help.lang.json (Greek) 2020-10-29 13:25:57 +10:00
Daniel Oaks
590ec460ba New translations irc.lang.json (Greek) 2020-10-29 13:25:55 +10:00
Daniel Oaks
11a648c592 New translations hostserv.lang.json (German) 2020-10-29 13:25:53 +10:00
Daniel Oaks
4a12d3a3f6 New translations nickserv.lang.json (German) 2020-10-29 13:25:51 +10:00
Daniel Oaks
2ed7bc1116 New translations chanserv.lang.json (German) 2020-10-29 13:25:50 +10:00
Daniel Oaks
2eefd0c214 New translations help.lang.json (German) 2020-10-29 13:25:48 +10:00
Daniel Oaks
8bf6fb7bdf New translations nickserv.lang.json (Greek) 2020-10-29 13:25:46 +10:00
Daniel Oaks
1207f4e134 New translations irc.lang.json (German) 2020-10-29 13:25:45 +10:00
Daniel Oaks
a5d2cffefd New translations nickserv.lang.json (Hebrew) 2020-10-29 13:25:43 +10:00
Daniel Oaks
c6b423a204 New translations irc.lang.json (Hungarian) 2020-10-29 13:25:42 +10:00
Daniel Oaks
cb602d83fd New translations irc.lang.json (Korean) 2020-10-29 13:25:40 +10:00
Daniel Oaks
d59ea9c325 New translations hostserv.lang.json (Japanese) 2020-10-29 13:25:38 +10:00
Daniel Oaks
55a0967e9b New translations nickserv.lang.json (Japanese) 2020-10-29 13:25:37 +10:00
Daniel Oaks
c02290103c New translations chanserv.lang.json (Japanese) 2020-10-29 13:25:35 +10:00
Daniel Oaks
6a9b07d242 New translations help.lang.json (Japanese) 2020-10-29 13:25:34 +10:00
Daniel Oaks
8204ece775 New translations irc.lang.json (Japanese) 2020-10-29 13:25:32 +10:00
Daniel Oaks
0edff1ee63 New translations hostserv.lang.json (Italian) 2020-10-29 13:25:31 +10:00
Daniel Oaks
fd0efa3a78 New translations hostserv.lang.json (Hebrew) 2020-10-29 13:25:29 +10:00
Daniel Oaks
8af07c0a39 New translations nickserv.lang.json (Italian) 2020-10-29 13:25:28 +10:00
Daniel Oaks
886f8d67a3 New translations help.lang.json (Italian) 2020-10-29 13:25:26 +10:00
Daniel Oaks
14bd790b72 New translations irc.lang.json (Italian) 2020-10-29 13:25:24 +10:00
Daniel Oaks
7ff3b720e0 New translations hostserv.lang.json (Hungarian) 2020-10-29 13:25:23 +10:00
Daniel Oaks
f7bd2a1f6a New translations nickserv.lang.json (Hungarian) 2020-10-29 13:25:21 +10:00
Daniel Oaks
a05d790cec New translations chanserv.lang.json (Hungarian) 2020-10-29 13:25:20 +10:00
Daniel Oaks
b55a999dd5 New translations help.lang.json (Hungarian) 2020-10-29 13:25:18 +10:00
Daniel Oaks
fbc6e13dcf New translations chanserv.lang.json (Italian) 2020-10-29 13:25:17 +10:00
Daniel Oaks
ea304f8833 New translations help.lang.json (Korean) 2020-10-29 13:25:15 +10:00
Daniel Oaks
ee69bd1a1e New translations hostserv.lang.json (Danish) 2020-10-29 13:25:13 +10:00
Daniel Oaks
6c5622f313 New translations chanserv.lang.json (Danish) 2020-10-29 13:25:12 +10:00
Daniel Oaks
456fafbda0 New translations chanserv.lang.json (Afrikaans) 2020-10-29 13:25:10 +10:00
Daniel Oaks
7607f72922 New translations help.lang.json (Afrikaans) 2020-10-29 13:25:08 +10:00
Daniel Oaks
c789f555cb New translations irc.lang.json (Afrikaans) 2020-10-29 13:25:07 +10:00
Daniel Oaks
baf771a3e0 New translations hostserv.lang.json (Spanish) 2020-10-29 13:25:05 +10:00
Daniel Oaks
6afe8c2849 New translations nickserv.lang.json (Spanish) 2020-10-29 13:25:04 +10:00
Daniel Oaks
542da6bc70 New translations chanserv.lang.json (Spanish) 2020-10-29 13:25:02 +10:00
Daniel Oaks
cc6868294f New translations help.lang.json (Spanish) 2020-10-29 13:25:00 +10:00
Daniel Oaks
c5163d4d09 New translations nickserv.lang.json (Afrikaans) 2020-10-29 13:24:59 +10:00
Daniel Oaks
d5d2c71443 New translations irc.lang.json (Spanish) 2020-10-29 13:24:57 +10:00
Daniel Oaks
cc4e192867 New translations nickserv.lang.json (French) 2020-10-29 13:24:55 +10:00
Daniel Oaks
fa0632b28d New translations chanserv.lang.json (French) 2020-10-29 13:24:54 +10:00
Daniel Oaks
b511d9dfac New translations help.lang.json (French) 2020-10-29 13:24:52 +10:00
Daniel Oaks
809843272e New translations irc.lang.json (French) 2020-10-29 13:24:50 +10:00
Daniel Oaks
70a89ee738 New translations hostserv.lang.json (Romanian) 2020-10-29 13:24:49 +10:00
Daniel Oaks
a9b834705c New translations nickserv.lang.json (Romanian) 2020-10-29 13:24:47 +10:00
Daniel Oaks
2e63138c69 New translations hostserv.lang.json (French) 2020-10-29 13:24:45 +10:00
Daniel Oaks
4a6b8d880f New translations nickserv.lang.json (Danish) 2020-10-29 13:24:43 +10:00
Daniel Oaks
339f6c1a10 New translations hostserv.lang.json (Afrikaans) 2020-10-29 13:24:42 +10:00
Daniel Oaks
db443f12ef New translations help.lang.json (Arabic) 2020-10-29 13:24:40 +10:00
Daniel Oaks
13e97b4ad0 New translations help.lang.json (Danish) 2020-10-29 13:24:39 +10:00
Daniel Oaks
d3e16491ee New translations irc.lang.json (Danish) 2020-10-29 13:24:37 +10:00
Daniel Oaks
6e6a89ec88 New translations hostserv.lang.json (Czech) 2020-10-29 13:24:36 +10:00
Daniel Oaks
930c669ba7 New translations nickserv.lang.json (Czech) 2020-10-29 13:24:34 +10:00
Daniel Oaks
6af0bebda4 New translations chanserv.lang.json (Czech) 2020-10-29 13:24:33 +10:00
Daniel Oaks
f551ff86ad New translations help.lang.json (Czech) 2020-10-29 13:24:31 +10:00
Daniel Oaks
db5b5da22a New translations irc.lang.json (Czech) 2020-10-29 13:24:30 +10:00
Daniel Oaks
3012627e0c New translations irc.lang.json (Arabic) 2020-10-29 13:24:28 +10:00
Daniel Oaks
df7a0d12d4 New translations hostserv.lang.json (Catalan) 2020-10-29 13:24:27 +10:00
Daniel Oaks
ae9ad90298 New translations chanserv.lang.json (Catalan) 2020-10-29 13:24:25 +10:00
Daniel Oaks
fddcf58ffc New translations help.lang.json (Catalan) 2020-10-29 13:24:24 +10:00
Daniel Oaks
ba441c02b0 New translations irc.lang.json (Catalan) 2020-10-29 13:24:22 +10:00
Daniel Oaks
5339871ab5 New translations hostserv.lang.json (Arabic) 2020-10-29 13:24:21 +10:00
Daniel Oaks
2e65f2bb73 New translations nickserv.lang.json (Arabic) 2020-10-29 13:24:19 +10:00
Daniel Oaks
2b1d16334a New translations chanserv.lang.json (Arabic) 2020-10-29 13:24:17 +10:00
Daniel Oaks
aaf898364c New translations nickserv.lang.json (Catalan) 2020-10-29 13:24:16 +10:00
Daniel Oaks
3e7ef56d28 New translations help.lang.json (Romanian) 2020-10-29 13:24:14 +10:00
Daniel Oaks
65175d7187 New translations chanserv.lang.json (Korean) 2020-10-29 13:24:12 +10:00
Daniel Oaks
1081534d3c New translations hostserv.lang.json (Korean) 2020-10-29 13:24:11 +10:00
Daniel Oaks
0d96868d86 New translations hostserv.lang.json (Chinese Traditional) 2020-10-29 13:24:10 +10:00
Daniel Oaks
13cd742ef7 New translations nickserv.lang.json (Chinese Traditional) 2020-10-29 13:24:08 +10:00
Daniel Oaks
2013df3ce7 New translations chanserv.lang.json (Chinese Traditional) 2020-10-29 13:24:06 +10:00
Daniel Oaks
20b20b3e68 New translations help.lang.json (Chinese Traditional) 2020-10-29 13:24:05 +10:00
Daniel Oaks
1c1be3580e New translations irc.lang.json (Chinese Traditional) 2020-10-29 13:24:03 +10:00
Daniel Oaks
3e9851e425 New translations hostserv.lang.json (Chinese Simplified) 2020-10-29 13:24:01 +10:00
Daniel Oaks
d1ec8c3227 New translations nickserv.lang.json (Chinese Simplified) 2020-10-29 13:23:59 +10:00
Daniel Oaks
b976f4d0ab New translations irc.lang.json (Vietnamese) 2020-10-29 13:23:58 +10:00
Daniel Oaks
e5aff63471 New translations chanserv.lang.json (Chinese Simplified) 2020-10-29 13:23:56 +10:00
Daniel Oaks
0f30d3cd11 New translations irc.lang.json (Chinese Simplified) 2020-10-29 13:23:55 +10:00
Daniel Oaks
0586c68222 New translations hostserv.lang.json (Ukrainian) 2020-10-29 13:23:53 +10:00
Daniel Oaks
2d06d49941 New translations nickserv.lang.json (Ukrainian) 2020-10-29 13:23:52 +10:00
Daniel Oaks
f640886255 New translations chanserv.lang.json (Ukrainian) 2020-10-29 13:23:50 +10:00
Daniel Oaks
753ccf7a17 New translations help.lang.json (Ukrainian) 2020-10-29 13:23:49 +10:00
Daniel Oaks
5c6223d152 New translations irc.lang.json (Ukrainian) 2020-10-29 13:23:47 +10:00
Daniel Oaks
272fb6e106 New translations hostserv.lang.json (Turkish) 2020-10-29 13:23:46 +10:00
Daniel Oaks
7d30a4937c New translations help.lang.json (Chinese Simplified) 2020-10-29 13:23:44 +10:00
Daniel Oaks
c8e8adcce2 New translations nickserv.lang.json (Turkish) 2020-10-29 13:23:43 +10:00
Daniel Oaks
8ba1b9233b New translations help.lang.json (Vietnamese) 2020-10-29 13:23:41 +10:00
Daniel Oaks
9913d47a16 New translations nickserv.lang.json (Vietnamese) 2020-10-29 13:23:40 +10:00
Daniel Oaks
9f78a93ce2 New translations nickserv.lang.json (Bosnian) 2020-10-29 13:23:38 +10:00
Daniel Oaks
3c5f442dbf New translations chanserv.lang.json (Bosnian) 2020-10-29 13:23:37 +10:00
Daniel Oaks
5e2b7d109d New translations help.lang.json (Bosnian) 2020-10-29 13:23:35 +10:00
Daniel Oaks
b2a92ac4c8 New translations irc.lang.json (Bosnian) 2020-10-29 13:23:34 +10:00
Daniel Oaks
a4eb686a34 New translations hostserv.lang.json (English, Australia) 2020-10-29 13:23:32 +10:00
Daniel Oaks
636caf2174 New translations nickserv.lang.json (English, Australia) 2020-10-29 13:23:31 +10:00
Daniel Oaks
3ffae10d32 New translations chanserv.lang.json (English, Australia) 2020-10-29 13:23:29 +10:00
Daniel Oaks
b12ea1aad1 New translations chanserv.lang.json (Vietnamese) 2020-10-29 13:23:27 +10:00
Daniel Oaks
fbcece9c54 New translations help.lang.json (English, Australia) 2020-10-29 13:23:26 +10:00
Daniel Oaks
5769e5cfb1 New translations hostserv.lang.json (Portuguese, Brazilian) 2020-10-29 13:23:24 +10:00
Daniel Oaks
0524a84736 New translations nickserv.lang.json (Portuguese, Brazilian) 2020-10-29 13:23:23 +10:00
Daniel Oaks
af239800c7 New translations chanserv.lang.json (Portuguese, Brazilian) 2020-10-29 13:23:21 +10:00
Daniel Oaks
997e21bdc1 New translations help.lang.json (Portuguese, Brazilian) 2020-10-29 13:23:19 +10:00
Daniel Oaks
4a1d738f12 New translations irc.lang.json (Portuguese, Brazilian) 2020-10-29 13:23:18 +10:00
Daniel Oaks
86048d1a33 New translations hostserv.lang.json (Vietnamese) 2020-10-29 13:23:16 +10:00
Daniel Oaks
83a555e772 New translations irc.lang.json (English, Australia) 2020-10-29 13:23:15 +10:00
Daniel Oaks
bc7c5bbe7f New translations nickserv.lang.json (Korean) 2020-10-29 13:23:13 +10:00
Daniel Oaks
6203b454e5 New translations chanserv.lang.json (Turkish) 2020-10-29 13:23:11 +10:00
Daniel Oaks
5906cc919f New translations irc.lang.json (Turkish) 2020-10-29 13:23:10 +10:00
Daniel Oaks
cb86d76af6 New translations irc.lang.json (Portuguese) 2020-10-29 13:23:08 +10:00
Daniel Oaks
e767fc0f6b New translations hostserv.lang.json (Polish) 2020-10-29 13:23:06 +10:00
Daniel Oaks
748747c0ad New translations nickserv.lang.json (Polish) 2020-10-29 13:23:05 +10:00
Daniel Oaks
2fe52dc6ab New translations chanserv.lang.json (Polish) 2020-10-29 13:23:03 +10:00
Daniel Oaks
f2af9521fe New translations help.lang.json (Polish) 2020-10-29 13:23:02 +10:00
Daniel Oaks
8ba7f9eab5 New translations irc.lang.json (Polish) 2020-10-29 13:23:00 +10:00
Daniel Oaks
3506dc3422 New translations hostserv.lang.json (Norwegian) 2020-10-29 13:22:58 +10:00
Daniel Oaks
4eab704dc0 New translations help.lang.json (Portuguese) 2020-10-29 13:22:57 +10:00
Daniel Oaks
206f2a4459 New translations nickserv.lang.json (Norwegian) 2020-10-29 13:22:55 +10:00
Daniel Oaks
1280f63397 New translations help.lang.json (Norwegian) 2020-10-29 13:22:54 +10:00
Daniel Oaks
0c56b58e66 New translations irc.lang.json (Norwegian) 2020-10-29 13:22:52 +10:00
Daniel Oaks
826789d17b New translations hostserv.lang.json (Dutch) 2020-10-29 13:22:50 +10:00
Daniel Oaks
798db706fd New translations nickserv.lang.json (Dutch) 2020-10-29 13:22:49 +10:00
Daniel Oaks
c375f245aa New translations chanserv.lang.json (Dutch) 2020-10-29 13:22:47 +10:00
Daniel Oaks
4e0d146ea4 New translations help.lang.json (Dutch) 2020-10-29 13:22:46 +10:00
Daniel Oaks
18d85982f3 New translations irc.lang.json (Dutch) 2020-10-29 13:22:44 +10:00
Daniel Oaks
28487a79cc New translations chanserv.lang.json (Norwegian) 2020-10-29 13:22:43 +10:00
Daniel Oaks
1581515094 New translations help.lang.json (Turkish) 2020-10-29 13:22:41 +10:00
Daniel Oaks
2a57db6ef8 New translations chanserv.lang.json (Portuguese) 2020-10-29 13:22:39 +10:00
Daniel Oaks
731322f3ec New translations hostserv.lang.json (Portuguese) 2020-10-29 13:22:38 +10:00
Daniel Oaks
ba9ad9bab0 New translations hostserv.lang.json (Swedish) 2020-10-29 13:22:35 +10:00
Daniel Oaks
69f009254b New translations nickserv.lang.json (Swedish) 2020-10-29 13:22:33 +10:00
Daniel Oaks
32711c6bff New translations chanserv.lang.json (Swedish) 2020-10-29 13:22:32 +10:00
Daniel Oaks
3112858d39 New translations help.lang.json (Swedish) 2020-10-29 13:22:30 +10:00
Daniel Oaks
b75e1dc7bb New translations irc.lang.json (Swedish) 2020-10-29 13:22:29 +10:00
Daniel Oaks
86e1c0a90d New translations hostserv.lang.json (Serbian (Cyrillic)) 2020-10-29 13:22:27 +10:00
Daniel Oaks
9c9dab1183 New translations nickserv.lang.json (Serbian (Cyrillic)) 2020-10-29 13:22:25 +10:00
Daniel Oaks
09ee46789f New translations nickserv.lang.json (Portuguese) 2020-10-29 13:22:24 +10:00
Daniel Oaks
8c9dcde0ac New translations chanserv.lang.json (Serbian (Cyrillic)) 2020-10-29 13:22:22 +10:00
Daniel Oaks
5771d90fba New translations irc.lang.json (Serbian (Cyrillic)) 2020-10-29 13:22:20 +10:00
Daniel Oaks
9c6517c1e5 New translations hostserv.lang.json (Russian) 2020-10-29 13:22:19 +10:00
Daniel Oaks
525efae019 New translations nickserv.lang.json (Russian) 2020-10-29 13:22:17 +10:00
Daniel Oaks
0611a260df New translations chanserv.lang.json (Russian) 2020-10-29 13:22:16 +10:00
Daniel Oaks
610a33da77 New translations help.lang.json (Russian) 2020-10-29 13:22:14 +10:00
Daniel Oaks
2b7e1f71d8 New translations irc.lang.json (Russian) 2020-10-29 13:22:12 +10:00
Daniel Oaks
bf4e7f24ea New translations help.lang.json (Serbian (Cyrillic)) 2020-10-29 13:22:11 +10:00
Daniel Oaks
2f5664b52c New translations irc.lang.json (Romanian) 2020-10-29 13:22:09 +10:00
Shivaram Lingamneni
6829773e34
Merge pull request #1363 from slingamn/strings
update strings
2020-10-28 17:37:07 -07:00
Shivaram Lingamneni
2d5cb0918e update translated strings 2020-10-28 20:33:49 -04:00
Shivaram Lingamneni
5094b9da02 fix strings for NS SUSPEND 2020-10-28 20:32:55 -04:00
Shivaram Lingamneni
2bb2cab6c2
Merge pull request #1361 from slingamn/suspend.5
fix #1274
2020-10-28 08:12:46 -07:00
Shivaram Lingamneni
6cd6da600a
Merge pull request #1362 from slingamn/issue769_rplwhoismodes
fix #769
2020-10-28 08:06:39 -07:00
Shivaram Lingamneni
5418e5e794 fix #769
Add 379 RPL_WHOISMODES
2020-10-27 18:54:45 -04:00
Shivaram Lingamneni
4296ff02a4 schema change for #1274 2020-10-27 18:30:03 -04:00
Shivaram Lingamneni
1f6afa31d6 fix #1274
Enhancements to NS SUSPEND, including stored metadata and the ability
to list suspensions
2020-10-27 18:30:03 -04:00
Shivaram Lingamneni
dc456bd6a4 refactor schema versions to be ints, not strings 2020-10-27 12:26:44 -04:00
Shivaram Lingamneni
70b8bf75c5
Merge pull request #1360 from slingamn/issue861_fakelag
fix #861
2020-10-26 20:12:57 -07:00
Shivaram Lingamneni
a6e7e1f321
Merge pull request #1359 from slingamn/deop.1
fix #361
2020-10-26 20:12:18 -07:00
Shivaram Lingamneni
610fc5068d refactor DEOP
DEOP is now pure syntactic sugar for /mode #channel -o nick,
the change is attributed to the originating user and not to ChanServ.
2020-10-26 23:08:05 -04:00
Shivaram Lingamneni
2f4431a5fb fix #861
Allow messages-per-window to be 0
2020-10-26 21:06:03 -04:00
Shivaram Lingamneni
b57820150e no-op fix to SETNAME workaround 2020-10-26 18:06:06 -04:00
Shivaram Lingamneni
9c4b086113
Merge pull request #1355 from slingamn/invite
security enhancements for INVITE
2020-10-26 13:30:41 -07:00
Shivaram Lingamneni
c61b09e314
Merge pull request #1358 from slingamn/setname
enhancements to SETNAME
2020-10-26 13:30:29 -07:00
Shivaram Lingamneni
5dcb2bb60c enhancements to SETNAME
Address SETNAME UX problems reported by @KoraggKnightWolf in #oragono:

1. No feedback by default on success or failure
2. Multi-word realnames are not correctly interpreted by some clients
2020-10-26 15:31:51 -04:00
Shivaram Lingamneni
6a5ba82f29
Merge pull request #1356 from slingamn/kline
validate normalized masks as IRC params
2020-10-26 11:58:45 -07:00
Shivaram Lingamneni
94e7cfc080
Merge pull request #1352 from slingamn/environment_variables.1
fix #1049
2020-10-26 11:58:10 -07:00
Barry
f010914965
Added ChanServ DEOP command 2020-10-26 18:18:54 +02:00
Shivaram Lingamneni
af2b433195 validate normalized masks as IRC params 2020-10-25 23:32:24 -04:00
Shivaram Lingamneni
4b1e6b04c4 add UNINVITE command (#1171) 2020-10-25 22:16:19 -04:00
Shivaram Lingamneni
42d246b557 improve security properties of INVITE
See #1171.
2020-10-25 22:09:17 -04:00
Shivaram Lingamneni
203fc580f4 fix #1049 2020-10-25 13:58:57 -04:00
Shivaram Lingamneni
9670d96282
Merge pull request #1350 from slingamn/traditional
rename conventional.yaml -> traditional.yaml
2020-10-22 22:20:53 -07:00
Shivaram Lingamneni
dcb05ae0c2 update references to conventional.yaml 2020-10-23 00:27:55 -04:00
Shivaram Lingamneni
4edd3198cd rename conventional.yaml -> traditional.yaml 2020-10-23 00:18:10 -04:00
Shivaram Lingamneni
c2c5fe7cf8
Merge pull request #1347 from slingamn/issue1346.1
fix #1346
2020-10-22 21:16:02 -07:00
Shivaram Lingamneni
eb5a16821f review fix: remove config blocks for vhost requests 2020-10-23 00:12:53 -04:00
Shivaram Lingamneni
cf5a426f90 fix #1346 2020-10-22 12:19:19 -04:00
Shivaram Lingamneni
11ddffa7c5 bump irctest 2020-10-22 11:04:46 -04:00
Shivaram Lingamneni
2d76805b2c
Merge pull request #1344 from slingamn/mute.2
add mute extban
2020-10-21 16:11:57 -07:00
Shivaram Lingamneni
173f57d1b2 bump irctest 2020-10-21 11:09:51 -04:00
Shivaram Lingamneni
bd40b46639 fix #307 2020-10-21 11:08:55 -04:00
Shivaram Lingamneni
50dc265e4d
Merge pull request #1343 from slingamn/chanmodes
add +u and +U to CHANMODES token
2020-10-20 18:43:41 -07:00
Shivaram Lingamneni
a30e369225 add +u and +U to CHANMODES token 2020-10-20 21:24:47 -04:00
Shivaram Lingamneni
f019f62167
Merge pull request #1340 from slingamn/coerce_ident
replace suppress-ident with coerce-ident; make coerce-ident a recommended default
2020-10-20 15:31:13 -07:00
Shivaram Lingamneni
033022599b
Merge pull request #1341 from slingamn/opmoderated
fix #1178
2020-10-20 15:30:12 -07:00
Shivaram Lingamneni
76e3d67b66 replace suppress-ident with coerce-ident 2020-10-20 13:48:19 -04:00
Shivaram Lingamneni
b41642f972 bump irctest 2020-10-20 13:43:30 -04:00
Shivaram Lingamneni
5a9e79f06c fix #1178 2020-10-20 13:37:38 -04:00
Shivaram Lingamneni
4737578748
Merge pull request #1339 from slingamn/issue1337
fix #1337
2020-10-19 08:22:00 -07:00
Shivaram Lingamneni
76da6cf4ec
Merge pull request #1333 from slingamn/issue1332_saregister
fix #1332
2020-10-19 08:01:16 -07:00
Shivaram Lingamneni
a37a91694c fix #1337 2020-10-19 10:52:38 -04:00
Shivaram Lingamneni
347cc30ed4 fix a potential conflict with delayed verification of confusable names
0. Enable email verification
1. Register `dog`
2. Register `d0g`
3. Verify `dog`
4. Verify `d0g`: verification succeeds but the nick cannot be used
2020-10-16 17:21:09 -04:00
Shivaram Lingamneni
cf46377863 fix #1332 2020-10-16 17:21:09 -04:00
Shivaram Lingamneni
c639aac241 log the unfolded account name during registration 2020-10-16 17:21:09 -04:00
Shivaram Lingamneni
e993672a0f
Merge pull request #1331 from slingamn/issue1330_realname
fix #1330
2020-10-16 09:41:57 -07:00
Shivaram Lingamneni
af8ed62de8 schema change for #1330 2020-10-16 12:08:23 -04:00
Shivaram Lingamneni
ea68b9de0a fix #1330 2020-10-16 12:03:38 -04:00
Shivaram Lingamneni
3fe27ae0b5
Merge pull request #1329 from slingamn/issue1327
fix #1327
2020-10-16 06:17:22 -07:00
Shivaram Lingamneni
898f8aad07 add a schema change 2020-10-16 09:11:47 -04:00
Shivaram Lingamneni
1e572e8458 bump irctest 2020-10-16 00:58:24 -04:00
Shivaram Lingamneni
4b2a094921 fix #1327 2020-10-16 00:58:12 -04:00
Shivaram Lingamneni
a412870514
Merge pull request #1328 from slingamn/issue1326_lusersagain
fix #1326
2020-10-15 10:41:18 -07:00
Shivaram Lingamneni
328f6013f3 fix #1326 2020-10-15 13:03:44 -04:00
Shivaram Lingamneni
ff86edc9d7 bump irctest 2020-10-13 09:25:40 -04:00
Shivaram Lingamneni
38fe931656
Merge pull request #1324 from slingamn/chathistory_empty
fix #1322
2020-10-13 06:22:51 -07:00
Shivaram Lingamneni
f1be50dd98
Merge pull request #1321 from oragono/anope_xop
fix xop levels for anope
2020-10-13 04:52:16 -07:00
Shivaram Lingamneni
5a17c41e61
Merge pull request #1323 from mogad0n/update_manualmd
update MANUAL.md with toc links, some umodes, and some language
2020-10-13 04:51:28 -07:00
mogad0n
de3faedec4 update manualmd 2020-10-13 14:49:50 +05:30
Shivaram Lingamneni
1d74185b7d fix #1322 2020-10-13 00:38:23 -04:00
Shivaram Lingamneni
2615b15cc4 fix xop levels for anope 2020-10-12 21:06:00 -04:00
Shivaram Lingamneni
02096134e4
Merge pull request #1314 from slingamn/anope.2
support anope passphrase hashes
2020-10-12 12:18:20 -07:00
Shivaram Lingamneni
82be9a8423 support migrating anope databases 2020-10-12 15:06:17 -04:00
Shivaram Lingamneni
4336f56204
Merge pull request #1317 from slingamn/hidden_userhost
USERHOST needs to respect hidden operators as well
2020-10-09 11:34:35 -07:00
Shivaram Lingamneni
2121f17de7
Merge pull request #1316 from slingamn/alwayson_cloak_followup
separate rawHostname and cloakedHostname for always-on clients
2020-10-09 11:08:24 -07:00
Shivaram Lingamneni
4ee97ddb43 USERHOST needs to respect hidden operators as well 2020-10-09 12:29:09 -04:00
Shivaram Lingamneni
8cd7085d0b separate rawHostname and cloakedHostname for always-on clients
This affects RPL_WHOISACTUALLY and possibly others.
2020-10-09 11:11:06 -04:00
Shivaram Lingamneni
f5374c014b
Merge pull request #1315 from slingamn/issue1194_stealthoper
hidden operators, tweaks to default operator config
2020-10-09 06:40:31 -07:00
Shivaram Lingamneni
174b8ed64d bump irctest to latest 2020-10-09 08:44:24 -04:00
Shivaram Lingamneni
e69c6659b1 disable snomasks by default
See #1309
2020-10-09 08:44:24 -04:00
Shivaram Lingamneni
3346af2b36 change example oper vhost to 'staff' 2020-10-09 08:09:30 -04:00
Shivaram Lingamneni
78b2f61707 fix #1194 2020-10-09 08:03:26 -04:00
Shivaram Lingamneni
3cd0e40146
Merge pull request #1313 from slingamn/issue1312_unique_hostnames.1
fix #1312
2020-10-08 15:42:55 -07:00
Shivaram Lingamneni
c470f63daf fix #1312 2020-10-08 16:33:27 -04:00
Shivaram Lingamneni
32f29aca52
Merge pull request #1310 from slingamn/register_reviewfix
review fix for #1308
2020-10-07 06:28:48 -07:00
Shivaram Lingamneni
7fd5bc8d81 review fix 2020-10-07 09:20:26 -04:00
Shivaram Lingamneni
6bbedadfd5
Merge pull request #1308 from slingamn/register_command.2
fix #1075
2020-10-07 06:17:07 -07:00
Shivaram Lingamneni
754fb79cdd review fixes 2020-10-07 08:54:46 -04:00
Shivaram Lingamneni
9ed789f67c fix #1075 2020-10-06 18:04:29 -04:00
Shivaram Lingamneni
509d3f1fdd
Merge pull request #1301 from oragono/atheme_migration
first draft of atheme migration code
2020-10-06 15:03:08 -07:00
Shivaram Lingamneni
1ec029a53b review fixes
1. Avoid undefined behavior of time.Time{}.UnixNano()
2. Times should be compared with Equal()
2020-10-06 17:56:13 -04:00
Shivaram Lingamneni
a19ac34ace check +F instead of +q for founder 2020-10-05 11:44:22 -04:00
Shivaram Lingamneni
a307c306dc
Merge pull request #1305 from slingamn/queue_loglines
fix #1293
2020-10-05 08:15:11 -07:00
Shivaram Lingamneni
a19324b948 fix #1293 2020-10-05 07:35:18 -04:00
Shivaram Lingamneni
2b66dde72e
Merge pull request #1304 from slingamn/kline_lusers
fix #1303
2020-10-05 03:36:12 -07:00
Shivaram Lingamneni
32fc9f226f fix #1303 2020-10-05 06:28:19 -04:00
Shivaram Lingamneni
8500727b71 bump irctest 2020-10-05 06:27:05 -04:00
Shivaram Lingamneni
7a6413ea2c first draft of atheme migration code 2020-10-02 16:48:37 -04:00
Shivaram Lingamneni
c060113c74
Merge pull request #1300 from slingamn/auditorium.release
auditorium mode
2020-10-02 05:33:02 -07:00
Shivaram Lingamneni
8c99dcb2c7 fix #484 2020-10-02 08:13:52 -04:00
Shivaram Lingamneni
4a7ca14bdc
Merge pull request #1299 from slingamn/fix_noctcp
actually enable the +T no-ctcp umode
2020-10-01 17:52:02 -07:00
Shivaram Lingamneni
c279b2d14c DRY mode apply code
ParseUserModeChanges and ParseChannelModeChanges already validate
that the modes are modifiable, so there's no need to repeat it here.
2020-10-01 19:52:50 -04:00
Shivaram Lingamneni
b426ba628a actually enable the +T no-ctcp umode 2020-10-01 19:21:42 -04:00
Shivaram Lingamneni
a17628eae1
Merge pull request #1297 from slingamn/issue1295_relaymsg
fix #1295
2020-10-01 06:44:58 -07:00
Shivaram Lingamneni
127a03ccf3 fix #1295 2020-10-01 09:42:08 -04:00
Shivaram Lingamneni
a36461415f
Merge pull request #1292 from slingamn/fixbuild
fix non-linux builds
2020-09-29 14:01:03 -07:00
Shivaram Lingamneni
475d7ba418 fix non-linux builds 2020-09-29 15:22:12 -04:00
Shivaram Lingamneni
b876c296eb
Merge pull request #1289 from slingamn/ucred
output unix socket credentials where applicable
2020-09-24 10:09:48 -07:00
Shivaram Lingamneni
f12384c5a6 Output unix socket credentials where applicable
thanks @ajaspers!
2020-09-24 10:59:30 -04:00
Shivaram Lingamneni
e990bc9baa
Merge pull request #1288 from slingamn/tweak
pointless optimization
2020-09-24 07:59:14 -07:00
Shivaram Lingamneni
a6d793a2d5 pointless optimization 2020-09-24 08:35:03 -04:00
Shivaram Lingamneni
1bbf6ab936
Merge pull request #1286 from slingamn/issue1284.1
fix #1284
2020-09-24 05:00:14 -07:00
Shivaram Lingamneni
d9f1c8e1c4 require local_ban capab to see uncloaked IPs 2020-09-24 07:25:04 -04:00
Shivaram Lingamneni
8b01d19336
Merge pull request #1280 from slingamn/issue1277_sanick
fix #1277
2020-09-24 04:10:44 -07:00
Shivaram Lingamneni
bcdf61bd7a fix #1284 2020-09-24 02:44:12 -04:00
Shivaram Lingamneni
0ca27cfeda
Merge pull request #1283 from slingamn/issue1282
attempt to fix #1282
2020-09-23 02:14:05 -07:00
Shivaram Lingamneni
abfbb5b15b attempt to fix #1282 2020-09-23 05:10:03 -04:00
Shivaram Lingamneni
815852191c
Merge pull request #1281 from slingamn/issue414_cacert.2
fix #414
2020-09-22 23:51:26 -07:00
Shivaram Lingamneni
54d165e840
Merge pull request #1279 from oragono/devel+irctest
run irctest in travis
2020-09-22 23:50:04 -07:00
Shivaram Lingamneni
7d5a071a34 fix #1277 2020-09-23 02:30:34 -04:00
Shivaram Lingamneni
1a9f501383 fix #414 2020-09-23 02:25:29 -04:00
Shivaram Lingamneni
eb4dec8d89 run irctest in travis 2020-09-22 10:15:18 -04:00
Shivaram Lingamneni
e7eea14b9d
Merge pull request #1278 from oragono/sanick
fix NICK responses for SANICK
2020-09-21 17:33:51 -07:00
Shivaram Lingamneni
b478a13671 fix NICK responses for SANICK 2020-09-21 20:30:42 -04:00
Shivaram Lingamneni
4a949877a0
Merge pull request #1273 from ajaspers/mute_unregistered
Add +M (only registered/voice can speak) chanmode.
2020-09-21 17:27:52 -07:00
Shivaram Lingamneni
358ae0a2a5
Merge pull request #1276 from oragono/docufix
Update MANUAL.md
2020-09-21 17:26:29 -07:00
dotflac
6c087d19d0 Update MANUAL.md 2020-09-21 20:24:31 -04:00
Alex Jaspersen
504659abb5 Add +M (only registered/voice can speak) chanmode.
Add chanmode preventing speech to error message.

Fixes #1182.
2020-09-21 16:05:00 -07:00
Shivaram Lingamneni
f9ca93118f
Merge pull request #1272 from ajaspers/session_kill
Add NickServ "CLIENTS LIST" and "CLIENTS LOGOUT".
2020-09-21 03:24:53 -07:00
Alex Jaspersen
ca2132ff09 Add NickServ "CLIENTS LIST" and "CLIENTS LOGOUT".
CLIENTS LIST shows information about clients attached to a nick.
CLIENTS LOGOUT allows individual (or all) sessions to be logged out.

SESSIONS is now an alias for CLIENTS LIST.

Fixes #1072.
2020-09-19 10:19:41 -07:00
Shivaram Lingamneni
536c21a874
Merge pull request #1271 from slingamn/issue1240
fix #1240
2020-09-16 18:44:44 -07:00
Shivaram Lingamneni
498d76b131 fix #1240 2020-09-16 12:03:06 -04:00
Shivaram Lingamneni
6a0d11d449 make roleplay.enabled default to false when unset
See #1240.
2020-09-16 11:32:52 -04:00
Shivaram Lingamneni
3ed047ccb8
Merge pull request #1269 from slingamn/websocket_proxy
fix websocket listeners with proxy-before-TLS closing on bad PROXY lines
2020-09-16 03:16:17 -07:00
Shivaram Lingamneni
0073b98505 fix (*http.Server).Serve() exiting on ErrBadProxyLine
anything other than a (net.Error) with Temporary() == true
is treated as a fatal error that causes the http server to exit
2020-09-16 06:12:27 -04:00
Shivaram Lingamneni
36ea93cc9d
Merge pull request #1268 from slingamn/saslmessage
pass the require-sasl message through from the script
2020-09-14 05:45:58 -07:00
Shivaram Lingamneni
f3cdf8442a pass the require-sasl message through from the script 2020-09-14 08:16:03 -04:00
Shivaram Lingamneni
7f13ec14cc
Merge pull request #1267 from slingamn/ipbanscripts.release
scripting API for IP bans
2020-09-14 01:51:08 -07:00
Shivaram Lingamneni
1a98a37a75 scripting API for IP bans
See discussion on #68.
2020-09-14 04:28:12 -04:00
Shivaram Lingamneni
a742ef9639
Merge pull request #1266 from oragono/add_untracked
add untracked x/sys/unix files
2020-09-12 23:16:46 -07:00
Shivaram Lingamneni
676045422b add untracked x/sys/unix files
These should have been added in 66995cdfdfda82b1f32314f7d690db4fea87d321.
Forgetting this seems to be an ongoing problem.
2020-09-13 02:02:17 -04:00
Shivaram Lingamneni
f03602905b
Merge pull request #1262 from slingamn/founderkick
make channel founder unkickable
2020-09-10 02:41:05 -07:00
Shivaram Lingamneni
32bb10f6c6 make channel founder unkickable 2020-09-09 23:15:00 -04:00
Shivaram Lingamneni
f5e9fd3e77
Merge pull request #1261 from slingamn/issue1259_rename
fix #1259
2020-09-09 10:08:53 -07:00
Shivaram Lingamneni
20aaa1ab06 fix #1259 2020-09-09 11:46:05 -04:00
Shivaram Lingamneni
2d277566b5
Merge pull request #1120 from oragono/master+relaymsg
Initial RELAYMSG implementation
2020-09-09 08:40:08 -07:00
Shivaram Lingamneni
af056f26a9 fixes and refactoring 2020-09-09 05:35:04 -04:00
Shivaram Lingamneni
3962ff8643 deprecate roleplay commands
See #1240
2020-09-09 03:57:51 -04:00
Shivaram Lingamneni
8102d1ddb6 Merge remote-tracking branch 'origin/master' into master+relaymsg 2020-09-09 03:55:41 -04:00
Shivaram Lingamneni
adeaa5440b
Merge pull request #1258 from slingamn/issue1213_nouser
fix #1213
2020-09-09 00:02:04 -07:00
Shivaram Lingamneni
307adba8bd fix #1213 2020-09-07 06:00:53 -04:00
Shivaram Lingamneni
60db9536f4
Merge pull request #1241 from slingamn/restricted_stringset
use utils.StringSet in more places
2020-09-05 22:31:17 -07:00
Shivaram Lingamneni
bfb3fd702a
Merge pull request #1249 from jesopo/pong-params
PONG param 0 should be server name
2020-09-05 22:30:50 -07:00
Shivaram Lingamneni
16ca5c2590
Merge pull request #1257 from slingamn/next
set up new development window
2020-09-05 22:30:22 -07:00
Shivaram Lingamneni
66995cdfdf bump x/sys dependency 2020-09-06 01:23:02 -04:00
Shivaram Lingamneni
4d940a5dc0 bump x/crypto dependency 2020-09-06 01:22:12 -04:00
Shivaram Lingamneni
f589a673a7 set up new development version 2020-09-06 01:21:10 -04:00
Shivaram Lingamneni
e43db479ca bump version to 2.3.0 2020-09-06 01:02:05 -04:00
Shivaram Lingamneni
8d1f0067df
Merge pull request #1250 from slingamn/changelog
changelog for v2.3.0
2020-09-05 22:00:17 -07:00
Shivaram Lingamneni
1c110fa501
Merge pull request #1256 from slingamn/deadcode
remove dead code
2020-09-03 21:19:34 -07:00
Shivaram Lingamneni
ed8de2a77a remove dead code 2020-09-03 18:37:18 -04:00
Shivaram Lingamneni
860d61e664 update changelog for 2.3.0 2020-09-01 17:08:49 -04:00
Shivaram Lingamneni
812414189b
Merge pull request #1255 from slingamn/issue1254
fix #1254
2020-09-01 00:24:12 -07:00
Shivaram Lingamneni
71cdc43d9a fix #1254 2020-09-01 02:41:15 -04:00
Shivaram Lingamneni
f58e66152d bump version to 2.3.0-rc2 2020-08-30 02:29:21 -04:00
Shivaram Lingamneni
192f3fb1c9 add changelog for 2.3.0-rc2 2020-08-30 02:29:06 -04:00
Shivaram Lingamneni
e05e624070
Merge pull request #1253 from slingamn/registration_bugs
fix critical bugs in 2.3.0-rc1
2020-08-29 21:21:39 -07:00
Shivaram Lingamneni
01726f8935 fix #1252 2020-08-29 23:40:49 -04:00
Shivaram Lingamneni
3bcf67b956 fix #1251 2020-08-29 21:42:27 -04:00
jesopo
574b07513c PONG param 0 should be server name
fix #1248
2020-08-26 09:18:53 +00:00
Shivaram Lingamneni
fda23264ad
Merge pull request #1247 from slingamn/config_cleanup
remove stray references to timeout enforcement
2020-08-25 17:34:24 -07:00
Shivaram Lingamneni
20fcbe5147 remove stray references to timeout enforcement
These should have been removed in #1027.
2020-08-25 14:11:13 -04:00
Shivaram Lingamneni
75a0e18580
Merge pull request #1245 from slingamn/issue1244
fix #1244
2020-08-23 20:25:37 -07:00
Shivaram Lingamneni
ddb8400124 include go version even without the git hash 2020-08-23 21:59:19 -04:00
Shivaram Lingamneni
6628a3d1c6 fix #1244 2020-08-23 18:11:10 -04:00
Shivaram Lingamneni
1dd0c432fd use utils.StringSet in more places 2020-08-22 22:43:21 -04:00
Shivaram Lingamneni
c78253fd93 more memory-efficient implementation of line reading 2020-08-07 01:10:46 -04:00
Daniel Oaks
48509ea852 Review fixes 2020-06-09 02:05:29 +10:00
Daniel Oaks
4535c82fd3 Review fix 2020-06-08 15:25:59 +10:00
Daniel Oaks
efd3152bfb Add relayed messages to channel history 2020-06-08 15:22:41 +10:00
Daniel Oaks
4ecd7fdf43 Allow configuring relay 2020-06-08 15:17:45 +10:00
Daniel Oaks
4ee49f8450 Initial RELAYMSG implementation 2020-06-08 10:19:28 +10:00
1077 changed files with 159537 additions and 69518 deletions

View File

@ -1,7 +1,7 @@
#!/bin/bash
# exclude vendor/
SOURCES="./oragono.go ./irc"
SOURCES="./ergo.go ./irc"
if [ "$1" = "--fix" ]; then
exec gofmt -s -w $SOURCES

32
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: "build"
on:
pull_request:
branches:
- "master"
- "stable"
push:
branches:
- "master"
- "stable"
jobs:
build:
runs-on: "ubuntu-24.04"
steps:
- name: "checkout repository"
uses: "actions/checkout@v3"
- name: "setup go"
uses: "actions/setup-go@v3"
with:
go-version: "1.24"
- name: "install python3-pytest"
run: "sudo apt install -y python3-pytest"
- name: "make install"
run: "make install"
- name: "make test"
run: "make test"
- name: "make smoke"
run: "make smoke"
- name: "make irctest"
run: "make irctest"

48
.github/workflows/docker-image.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: 'ghcr'
on:
push:
branches:
- "master"
- "stable"
tags:
- 'v*'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout Git repository
uses: actions/checkout@v3
- name: Authenticate to container registry
uses: docker/login-action@v2
if: github.event_name != 'pull_request'
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Setup Docker buildx driver
id: buildx
uses: docker/setup-buildx-action@v2
- name: Build and publish image
uses: docker/build-push-action@v3
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

9
.gitignore vendored
View File

@ -95,7 +95,7 @@ _testmain.go
*.out
### Oragono ###
### custom ###
/_site/
/.vscode/*
/ircd*
@ -103,10 +103,11 @@ _testmain.go
/web.*
/ssl.*
/tls.*
/oragono
/ergo
/build/*
_test
oragono.prof
oragono.mprof
ergo.prof
ergo.mprof
/dist
*.pem
.dccache

3
.gitmodules vendored
View File

@ -0,0 +1,3 @@
[submodule "irctest"]
path = irctest
url = https://github.com/ergochat/irctest

View File

@ -1,53 +1,79 @@
# .goreleaser.yml
# Build customization
project_name: oragono
version: 2
project_name: ergo
builds:
- main: oragono.go
binary: oragono
- main: ergo.go
env:
- CGO_ENABLED=0
binary: ergo
goos:
- freebsd
- linux
- windows
- darwin
- linux
- freebsd
- openbsd
- plan9
goarch:
- "386"
- amd64
- arm
- arm64
- riscv64
goarm:
- 6
- 7
ignore:
- goos: windows
goarch: arm
- goos: windows
goarch: arm64
- goos: windows
goarch: riscv64
- goos: darwin
goarch: arm
- goos: darwin
goarch: 386
goarch: riscv64
- goos: freebsd
goarch: arm
- goos: freebsd
goarch: arm64
- goos: freebsd
goarch: riscv64
- goos: openbsd
goarch: arm
- goos: openbsd
goarch: arm64
- goos: openbsd
goarch: riscv64
- goos: plan9
goarch: arm
- goos: plan9
goarch: arm64
- goos: plan9
goarch: riscv64
flags:
- -trimpath
archives:
-
name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
name_template: >-
{{ .ProjectName }}-{{ .Version }}-
{{- if eq .Os "darwin" }}macos{{- else }}{{ .Os }}{{ end -}}-
{{- if eq .Arch "amd64" }}x86_64{{- else }}{{ .Arch }}{{ end -}}
{{ if .Arm }}v{{ .Arm }}{{ end -}}
format: tar.gz
replacements:
amd64: x86_64
darwin: macos
format_overrides:
- goos: windows
format: zip
files:
- README
- CHANGELOG.md
- oragono.motd
- LICENSE
- ergo.motd
- default.yaml
- conventional.yaml
- docs/*
- traditional.yaml
- docs/API.md
- docs/MANUAL.md
- docs/USERGUIDE.md
- languages/*.yaml
- languages/*.json
- languages/*.md

View File

@ -1,13 +0,0 @@
language: go
go:
- "1.15.x"
before_install:
# https://github.com/travis-ci/travis-ci/issues/8361
- sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'
script:
- make
- make test
- make smoke

View File

@ -1,13 +1,717 @@
# Changelog
All notable changes to Oragono will be documented in this file.
All notable changes to Ergo will be documented in this file.
## [2.3.0-rc1] - 2020-08-23
## [2.16.0] - 2025-05-18
We're pleased to be publishing v2.16.0, a new stable release. This release contains bug fixes and some minor updates.
We're pleased to be publishing the release candidate for 2.3.0 (the official release should follow in a week or so).
This release includes changes to the config file format, all of which are fully backwards-compatible and do not require updating the file before upgrading. It includes no changes to the database file format.
Many thanks to [@csmith](https://github.com/csmith), [@delthas](https://github.com/delthas), donio, [@emersion](https://github.com/emersion), [@KlaasT](https://github.com/KlaasT), [@knolley](https://github.com/knolley), [@Mailaender](https://github.com/Mailaender), and [@prdes](https://github.com/prdes) for reporting issues and helping test.
### Config changes
* Added `api` block for configuring the new HTTP API. If this block is absent, the API is disabled (#2231)
* Added `server.additional-isupport` for publishing arbitrary ISUPPORT tokens (#2220, #2240)
* Added `server.command-aliases` to configure aliases for server commands (#2229, #2236)
* Added options to `roleplay` to customize the NUH's sent for `NPC` and `SCENE`. Roleplay remains deprecated and disabled by default. (#2237)
### Security
* Mitigated HTTP DoS attacks by rejecting IRC sessions that begin with an HTTP verb, such as `POST`. If you were relying on this to create IRC sessions via an HTTP client, please open an issue. (#2239)
### Added
* Added an HTTP API, providing programmatic access to Ergo functionality (#2231, thanks [@KlaasT](https://github.com/KlaasT)!)
* Added SAFERATE to 005 ISUPPORT tokens (#2223, thanks [@delthas](https://github.com/delthas)!)
* Added support for ed25519-sha256 for DKIM. However, enabling this algorithm is not recommended since mainstream email providers still do not support it. (#1041, #2242)
### Fixed
* Fixed `CHATHISTORY TARGETS` from MySQL backend reporting incorrect timestamps when the server timezone is not UTC (#2224)
* Fixed batch name parameter in `draft/isupport` responses (#2253)
* Fixed `NS UNREGISTER` not deleting the stored push subscriptions (#2254)
* Fixed cases where `NS SAREGISTER` could create clients without applying the default user modes (#2252, #2254, thanks donio!)
* Improved validation of `CHATHISTORY` parameters (#2248, #2249, thanks [@prdes](https://github.com/prdes)!)
* Added validation to ensure the MOTD is UTF-8 when `enforce-utf8` is enabled (the recommended default) (#2228, #2233, thanks [@KlaasT](https://github.com/KlaasT)!)
* The client's own `QUIT` line now respects the `server-time` capability (#2218, #2219)
* Fixed sending unnecessary replies to certain invalid `MODE` changes (#2213)
* Improved safety of ISUPPORT length limits (#2241)
### Changed
* The `draft/message-redaction` capability is no longer advertised when `allow-individual-delete` is disabled (#2215, #2216, thanks [@delthas](https://github.com/delthas)!)
* Receiving the UTF-8 BOM (byte-order mark) at the start of an IRC connection now produces an explicit error (#2244, #2247, thanks [@csmith](https://github.com/csmith), [@Mailaender](https://github.com/Mailaender)!)
### Internal
* Release builds use Go 1.24.3 (#2217)
## [2.15.0] - 2025-01-26
We're pleased to be publishing v2.15.0, a new stable release. This release adds support for mobile push notifications, via the [draft/webpush](https://github.com/ircv3/ircv3-specifications/pull/471) specification. More information on this is available in the [manual](https://github.com/ergochat/ergo/blob/ab2d842b270d9df217c779df9c7a5c594d85fdd5/docs/MANUAL.md#push-notifications) and [user guide](https://github.com/ergochat/ergo/blob/ab2d842b270d9df217c779df9c7a5c594d85fdd5/docs/USERGUIDE.md#push-notifications). This feature is still considered to be in an experimental state; `default.yaml` ships with it disabled, and its configuration may have backwards-incompatible changes in the future.
This release includes changes to the config file format, all of which are fully backwards-compatible and do not require updating the file before upgrading.
This release includes a database change. If you have `datastore.autoupgrade` set to `true` in your configuration, it will be automatically applied when you restart Ergo. Otherwise, you can update the database manually by running `ergo upgradedb` (see the manual for complete instructions).
Many thanks to [@delthas](https://github.com/delthas), [@donatj](https://github.com/donatj), donio, [@emersion](https://github.com/emersion), and [@eskimo](https://github.com/eskimo) for contributing patches and helping test.
### Config changes
* Added `webpush` block to the config file to configure push notifications. See `default.yaml` for an example. Note that at this time, `default.yaml` ships with support for push notifications disabled; operators can enable them by setting `webpush.enabled: true`. In the absence of such a block, push notifications are disabled.
* We recommend the addition of `"WEBPUSH": 1` to `fakelag.command-budgets`, to speed up mobile reattach when web push is enabled. See `default.yaml` for an example.
### Added
* Added support for the [draft/webpush](https://github.com/ircv3/ircv3-specifications/pull/471) specification (#2205, thanks [@emersion](https://github.com/emersion), [@eskimo](https://github.com/eskimo)!)
* Added support for the [draft/extended-isupport](https://github.com/ircv3/ircv3-specifications/pull/543) specification (#2184, thanks [@emersion](https://github.com/emersion)!)
* `UBAN ADD` now accepts `REQUIRE-SASL` with NUH masks, i.e. k-lines (#2198, #2199)
* Ergo now publishes the `SAFELIST` ISUPPORT parameter (#2196, thanks [@delthas](https://github.com/delthas)!)
### Fixed
* Fixed incorrect parameters when pushing `005` (ISUPPORT) updates to clients on rehash (#2177, #2184)
### Internal
* Official release builds use Go 1.23.5
* Added a unique identifier to identify connections in debug logs. This has no privacy implications in a standard, non-debug configuration of Ergo. (#2206, thanks donio!)
* Added support for Solaris on amd64 CPUs (#2183)
## [2.14.0] - 2024-06-30
We're pleased to be publishing v2.14.0, a new stable release. This release contains primarily bug fixes, with the addition of some new authentication mechanisms for integrating with web clients.
This release includes changes to the config file format, all of which are fully backwards-compatible and do not require updating the file before upgrading. It includes no changes to the database file format.
Many thanks to [@al3xandros](https://github.com/al3xandros), donio, [@eeeeeta](https://github.com/eeeeeta), [@emersion](https://github.com/emersion), [@Eriner](https://github.com/Eriner), [@eskimo](https://github.com/eskimo), [@Herringway](https://github.com/Herringway), [@jwheare](https://github.com/jwheare), [@knolley](https://github.com/knolley), [@mengzhuo](https://github.com/mengzhuo), pathof, [@poVoq](https://github.com/poVoq), [@progval](https://github.com/progval), [@RNDpacman](https://github.com/RNDpacman), and [@xnaas](https://github.com/xnaas) for contributing patches, reporting issues, and helping test.
### Config changes
* Added `accounts.oauth2` and `accounts.jwt-auth` blocks for configuring OAuth2 and JWT authentication (#2004)
* Added `protocol` and `local-address` options to `accounts.registration.email-verification`, to force emails to be sent over IPv4 (or IPv6) or to force the use of a particular source address (#2142)
* Added `limits.realnamelen`, a configurable limit on the length of realnames. If unset, no limit is enforced beyond the IRC protocol line length limits (the previous behavior). (#2123, thanks [@eskimo](https://github.com/eskimo)!)
* Added the `accept-hostname` option to the webirc config block, allowing Ergo to accept hostnames passed from reverse proxies on the `WEBIRC` line. Note that this will have no effect under the default/recommended configuration, in which cloaks are used instead (#1686, #2146, thanks [@RNDpacman](https://github.com/RNDpacman)!)
* The default/recommended value of `limits.chan-list-modes` (the size limit for ban/except/invite lists) was raised to 100 (#2081, #2165, #2167)
### Added
* Added support for the `OAUTHBEARER` SASL mechanism, allowing Ergo to interoperate with Gamja and an OAuth2 provider (#2004, #2122, thanks [@emersion](https://github.com/emersion)!)
* Added support for the [`IRCV3BEARER` SASL mechanism](https://github.com/ircv3/ircv3-specifications/pull/545), allowing Ergo to accept OAuth2 or JWT bearer tokens (#2158)
* Added support for the legacy `rfc1459` and `rfc1459-strict` casemappings (#2099, #2159, thanks [@xnaas](https://github.com/xnaas)!)
* The new `ergo defaultconfig` subcommand prints a copy of the default config file to standard output (#2157, #2160, thanks [@al3xandros](https://github.com/al3xandros)!)
### Fixed
* Even with `allow-truncation: false` (the recommended default), some oversized messages were being accepted and relayed with truncation. These messages will now be rejected with `417 ERR_INPUTTOOLONG` as expected (#2170)
* NICK and QUIT from invisible members of auditorium channels are no longer recorded in history (#2133, #2137, thanks [@knolley](https://github.com/knolley) and [@poVoq](https://github.com/poVoq)!)
* If channel registration was disabled, registered channels could become inaccessible after rehash; this has been fixed (#2130, thanks [@eeeeeta](https://github.com/eeeeeta)!)
* Attempts to use unrecognized SASL mechanisms no longer count against the login throttle, improving compatibility with Pidgin (#2156, thanks donio and pathof!)
* Fixed database autoupgrade on Windows, which was previously broken due to the use of a colon in the backup filename (#2139, #2140, thanks [@Herringway](https://github.com/Herringway)!)
* Fixed handling of `NS CERT ADD <user> <fp>` when an unprivileged user invokes it on themself (#2128, #2098, thanks [@Eriner](https://github.com/Eriner)!)
* Fixed missing human-readable trailing parameters for two multiline `FAIL` messages (#2043, #2162, thanks [@jwheare](https://github.com/jwheare) and [@progval](https://github.com/progval)!)
* Fixed symbol sent by `353 RPL_NAMREPLY` for secret channels (#2144, #2145, thanks savoyard!)
### Changed
* Trying to claim a registered nickname that is also actually in use by another client now produces `433 ERR_NICKNAMEINUSE` as expected (#2135, #2136, thanks savoyard!)
* `SAMODE` now overrides the enforcement of `limits.chan-list-modes` (the size limit for ban/except/invite lists) (#2081, #2165)
* Certain unsuccessful `MODE` changes no longer send `324 RPL_CHANNELMODEIS` and `329 RPL_CREATIONTIME` (#2163)
* Debug logging for environment variable configuration overrides no longer prints the value, only the key (#2129, #2132, thanks [@eeeeeta](https://github.com/eeeeeta)!)
### Internal
* Official release builds use Go 1.22.4
* Added a linux/riscv64 release (#2172, #2173, thanks [@mengzhuo](https://github.com/mengzhuo)!)
## [2.13.1] - 2024-05-06
Ergo 2.13.1 is a bugfix release, fixing an exploitable deadlock that could lead to a denial of service. We regret the oversight.
This release includes no changes to the config file format or database format.
### Security
* Fixed an exploitable deadlock that could lead to a denial of service (#2149)
### Internal
* Official release builds use Go 1.22.2
## [2.13.0] - 2024-01-14
We're pleased to be publishing v2.13.0, a new stable release. This is a bugfix release that fixes some issues, including a crash.
This release includes no changes to the config file format or database format.
Many thanks to [@dallemon](https://github.com/dallemon), [@jwheare](https://github.com/jwheare), [@Mikaela](https://github.com/Mikaela), [@nealey](https://github.com/nealey), and [@Sheikah45](https://github.com/Sheikah45) for contributing patches, reporting issues, and helping test.
### Fixed
* Fixed a (hopefully rare) crash when persisting always-on client statuses (#2113, #2117, thanks [@Sheikah45](https://github.com/Sheikah45)!)
* Fixed not being able to message channels with `/` (or the configured `RELAYMSG` separator) in their names (#2114, thanks [@Mikaela](https://github.com/Mikaela)!)
* Verification emails now always include a `Message-ID` header, improving compatibility with Gmail (#2108, #2110)
* Improved human-readable description of `REDACT_FORBIDDEN` (#2101, thanks [@jwheare](https://github.com/jwheare)!)
### Removed
* Removed numerics associated with the retired ACC spec (#2109, #2111, thanks [@jwheare](https://github.com/jwheare)!)
### Internal
* Upgraded the Docker base image from Alpine 3.13 to 3.19. The resulting images are incompatible with Docker 19.x and lower (all currently non-EOL Docker versions should be supported). (#2103)
* Official release builds use Go 1.21.6
## [2.12.0] - 2023-10-10
We're pleased to be publishing v2.12.0, a new stable release. This is another bugfix release aimed at improving client compatibility and keeping up with the IRCv3 specification process.
This release includes changes to the config file format, one of which is a compatibility break: if you were using `accounts.email-verification.blacklist-regexes`, you can restore the previous functionality by renaming `blacklist-regexes` to `address-blacklist` and setting the additional key `address-blacklist-syntax: regex`. See [default.yaml](https://github.com/ergochat/ergo/blob/e7597876d987a6fc061b768fcf878d0035d1c85a/default.yaml#L422-L424) for an example; for more details, see the "Changed" section below.
This release includes a database change. If you have `datastore.autoupgrade` set to `true` in your configuration, it will be automatically applied when you restart Ergo. Otherwise, you can update the database manually by running `ergo upgradedb` (see the manual for complete instructions).
Many thanks to [@adsr](https://github.com/adsr), [@avollmerhaus](https://github.com/avollmerhaus), [@csmith](https://github.com/csmith), [@EchedeyLR](https://github.com/EchedeyLR), [@emersion](https://github.com/emersion), [@eskimo](https://github.com/eskimo), [@julio-b](https://github.com/julio-b), knolle, [@KoxSosen](https://github.com/KoxSosen), [@Mikaela](https://github.com/Mikaela), [@mogad0n](https://github.com/mogad0n), and [@progval](https://github.com/progval) for contributing patches, reporting issues, and helping test.
### Config changes
* Removed `accounts.email-verification.blacklist-regexes` in favor of `address-blacklist`, `address-blacklist-syntax`, and `address-blacklist-file`. See the "Changed" section below for the semantics of these new keys. (#1997, #2088)
* Added `implicit-tls` (TLS from the first byte) support for MTAs (#2048, #2049, thanks [@EchedeyLR](https://github.com/EchedeyLR)!)
### Fixed
* Fixed an edge case under `allow-truncation: true` (the recommended default is `false`) where Ergo could truncate a message in the middle of a UTF-8 codepoint (#2074)
* Fixed `CHATHISTORY TARGETS` being sent in a batch even without negotiation of the `batch` capability (#2066, thanks [@julio-b](https://github.com/julio-b)!)
* Errors from `/REHASH` are now properly sanitized before being sent to the user, fixing an edge case where they would be dropped (#2031, thanks [@eskimo](https://github.com/eskimo)!
* Fixed some edge cases in auto-away aggregation (#2044)
* Fixed a FAIL code sent by draft/account-registration (#2092, thanks [@progval](https://github.com/progval)!)
* Fixed a socket leak in the ident client (default/recommended configurations of Ergo disable ident and are not affected by this issue) (#2089)
### Changed
* Bouncer reattach from an "insecure" session is no longer disallowed. We continue to recommend that operators preemptively disable all insecure transports, such as plaintext listeners (#2013)
* Email addresses are now converted to lowercase before checking them against the blacklist (#1997, #2088)
* The default syntax for the email address blacklist is now "glob" (expressions with `*` and `?` as wildcard characters), as opposed to the full [Go regular expression syntax](https://github.com/google/re2/wiki/Syntax). To enable full regular expression syntax, set `address-blacklist-syntax: regex`.
* Due to line length limitations, some capabilities are now hidden from clients that only support version 301 CAP negotiation. To the best of our knowledge, all clients that support these capabilities also support version 302 CAP negotiation, rendering this moot (#2068)
* The default/recommended configuration now advertises the SCRAM-SHA-256 SASL method. We still do not recommend using this method in production. (#2032)
* Improved KILL messages (#2053, #2041, thanks [@mogad0n](https://github.com/mogad0n)!)
### Added
* Added support for automatically joining new clients to a channel or channels (#2077, #2079, thanks [@adsr](https://github.com/adsr)!)
* Added implicit TLS (TLS from the first byte) support for MTAs (#2048, #2049, thanks [@EchedeyLR](https://github.com/EchedeyLR)!)
* Added support for [draft/message-redaction](https://github.com/ircv3/ircv3-specifications/pull/524) (#2065, thanks [@progval](https://github.com/progval)!)
* Added support for [draft/pre-away](https://github.com/ircv3/ircv3-specifications/pull/514) (#2044)
* Added support for [draft/no-implicit-names](https://github.com/ircv3/ircv3-specifications/pull/527) (#2083)
* Added support for the [MSGREFTYPES](https://ircv3.net/specs/extensions/chathistory#isupport-tokens) 005 token (#2042)
* Ergo now advertises the [standard-replies](https://ircv3.net/specs/extensions/standard-replies) capability. Requesting this capability does not change Ergo's behavior.
### Internal
* Release builds are now statically linked by default. This should not affect normal chat operations, but may disrupt attempts to connect to external services (e.g. MTAs) that are configured using a hostname that relies on libc's name resolution behavior. To restore the old behavior, build from source with `CGO_ENABLED=1`. (#2023)
* Upgraded to Go 1.21 (#2045, #2084); official release builds use Go 1.21.3, which includes a fix for CVE-2023-44487
* The default `make` target is now `build` (which builds an `ergo` binary in the working directory) instead of `install` (which builds and installs an `ergo` binary to `${GOPATH}/bin/ergo`). Take note if building from source, or testing Ergo in development! (#2047)
* `make irctest` now depends on `make install`, in an attempt to ensure that irctest runs against the intended development version of Ergo (#2047)
## [2.11.1] - 2022-01-22
Ergo 2.11.1 is a bugfix release, fixing a denial-of-service issue in our websocket implementation. We regret the oversight.
This release includes no changes to the config file format or database file format.
### Security
* Fixed a denial-of-service issue affecting websocket clients (#2039)
## [2.11.0] - 2022-12-25
We're pleased to be publishing v2.11.0, a new stable release. This is another bugfix release aimed at improving client compatibility and keeping up with the IRCv3 specification process.
This release includes changes to the config file format, all of which are fully backwards-compatible and do not require updating the file before upgrading. It includes no changes to the database file format.
Many thanks to dedekro, [@emersion](https://github.com/emersion), [@eskimo](https://github.com/eskimo), [@FiskFan1999](https://github.com/FiskFan1999), hauser, [@jwheare](https://github.com/jwheare), [@kingter-sutjiadi](https://github.com/kingter-sutjiadi), knolle, [@Mikaela](https://github.com/Mikaela), [@mogad0n](https://github.com/mogad0n), [@PeGaSuS-Coder](https://github.com/PeGaSuS-Coder), and [@progval](https://github.com/progval) for contributing patches, reporting issues, and helping test.
### Config changes
* Added `fakelag.command-budgets`, which allows each client session a limited number of specific commands that are exempt from fakelag. This improves compatibility with Goguma in particular. For the current recommended default, see `default.yaml` (#1978, thanks [@emersion](https://github.com/emersion)!)
* The recommended value of `server.casemapping` is now `ascii` instead of `precis`. PRECIS remains fully supported; if you are already running an Ergo instance, we do not recommend changing the value unless you are confident that your existing users are not relying on non-ASCII nicknames and channel names. (#1718)
### Changed
* Network services like `NickServ` now appear in `WHO` responses where applicable (#1850, thanks [@emersion](https://github.com/emersion)!)
* The `extended-monitor` capability now appears under its ratified name (#2006, thanks [@progval](https://github.com/progval)!)
* `TAGMSG` no longer receives automatic `RPL_AWAY` responses (#1983, thanks [@eskimo](https://github.com/eskimo)!)
* `UBAN` now states explicitly that bans without a time limit have "indefinite" duration (#1988, thanks [@mogad0n](https://github.com/mogad0n)!)
### Fixed
* `WHO` with a bare nickname as an argument now shows invisible users, comparable to `WHOIS` (#1991, thanks [@emersion](https://github.com/emersion)!)
* MySQL did not work on 32-bit architectures; this has been fixed (#1969, thanks hauser!)
* Fixed the name of the `CHATHISTORY` 005 token (#2008, #2009, thanks [@emersion](https://github.com/emersion)!)
* Fixed handling of the address `::1` in WHOX output (#1980, thanks knolle!)
* Fixed handling of `AWAY` with an empty parameter (the de facto standard is to treat as a synonym for no parameter, which means "back") (#1996, thanks [@emersion](https://github.com/emersion), [@jwheare](https://github.com/jwheare)!)
* Fixed incorrect handling of some invalid modes in `CS AMODE` (#2002, thanks [@eskimo](https://github.com/eskimo)!)
* Fixed incorrect help text for `NS SAVERIFY` (#2021, thanks [@FiskFan1999](https://github.com/FiskFan1999)!)
### Added
* Added the `draft/persistence` capability and associated `PERSISTENCE` command. This is a first attempt to standardize Ergo's "always-on" functionality so that clients can interact with it programmatically. (#1982)
* Sending `SIGUSR1` to the Ergo process now prints a full goroutine stack dump to stderr, allowing debugging even when the HTTP pprof listener is disabled (#1975)
### Internal
* Upgraded to Go 1.19; this makes further architecture-specific bugs like #1969 much less likely (#1987, #1989)
* The test suite is now parallelized (#1976, thanks [@progval](https://github.com/progval)!)
## [2.10.0] - 2022-05-29
We're pleased to be publishing v2.10.0, a new stable release.
This release contains no changes to the config file format or database file format.
Many thanks to [@csmith](https://github.com/csmith), [@FiskFan1999](https://github.com/FiskFan1999), [@Mikaela](https://github.com/Mikaela), [@progval](https://github.com/progval), and [@thesamesam](https://github.com/thesamesam) for contributing patches, and to [@emersion](https://github.com/emersion), [@eskimo](https://github.com/eskimo), [@FiskFan1999](https://github.com/FiskFan1999), [@jigsy1](https://github.com/jigsy1), [@Mikaela](https://github.com/Mikaela), [@mogad0n](https://github.com/mogad0n), [@progval](https://github.com/progval), and [@xnaas](https://github.com/xnaas) for reporting issues and helping test.
### Config changes
* For better interoperability with [Goguma](https://sr.ht/~emersion/goguma/), the recommended value of `history.chathistory-maxmessages` has been increased to `1000` (previously `100`) (#1919)
### Changed
* Persistent voice (`AMODE +v`) in a channel is now treated as a permanent invite (i.e. overriding `+i` on the channel) (#1901, thanks [@eskimo](https://github.com/eskimo)!)
* If you are `+R`, sending a direct message to an anonymous user allows them to send you replies (#1687, #1688, thanks [@Mikaela](https://github.com/Mikaela) and [@progval](https://github.com/progval)!)
* `0` is no longer valid as a nickname or account name, with a grandfather exception if it was registered on a previous version of Ergo (#1896)
* Implemented the [ratified version of the bot mode spec](https://ircv3.net/specs/extensions/bot-mode); the tag name is now `bot` instead of `draft/bot` (#1938)
* Privileged WHOX on a user with multiclient shows an arbitrarily chosen client IP address, comparable to WHO (#1897)
* `SAREGISTER` is allowed even under `DEFCON` levels 4 and lower (#1922)
* Operators with the `history` capability are now exempted from time cutoff restrictions on history retrieval (#1593, #1955)
### Added
* Added `draft/read-marker` capability, allowing server-side tracking of read messages for synchronization across multiple clients. (#1926, thanks [@emersion](https://github.com/emersion)!)
* `INFO` now includes the server start time (#1895, thanks [@xnaas](https://github.com/xnaas)!)
* Added `ACCEPT` command modeled on Charybdis/Solanum, allowing `+R` users to whitelist users who can DM them (#1688, thanks [@Mikaela](https://github.com/Mikaela)!)
* Added `NS SAVERIFY` for operators to manually complete an account verification (#1924, #1952, thanks [@tacerus](https://github.com/tacerus)!)
### Fixed
* Having the `samode` operator capability made all uses of the `KICK` command privileged (i.e. overriding normal channel privilege checks); this has been fixed (#1906, thanks [@pcho](https://github.com/pcho)!)
* Fixed `LIST <n` always returning no results (#1934, thanks [@progval](https://github.com/progval) and [@mitchr](https://github.com/mitchr)!)
* NickServ commands are now more clear about when a nickname is unavailable because it was previously registered and unregistered (#1886, thanks [@Mikaela](https://github.com/Mikaela)!)
* Fixed KLINE'd clients producing a `QUIT` snotice without a corresponding `CONNECT` snotice (#1941, thanks [@tacerus](https://github.com/tacerus), [@xnaas](https://github.com/xnaas)!)
* Fixed incorrect handling of long/multiline `319 RPL_WHOISCHANNELS` responses (#1935, thanks [@Mikaela](https://github.com/Mikaela)!)
* Fixed `LIST` returning `403 ERR_NOSUCHCHANNEL` for a nonexistent channel; the correct response is an empty list (#1928, thanks [@emersion](https://github.com/emersion)!)
* Fixed `+s` ("secret") channels not appearing in `LIST` even when the client is already a member (#1911, #1923, thanks [@jigsy1](https://github.com/jigsy1) and [@FiskFan1999](https://github.com/FiskFan1999)!)
* Fixed a spurious success message in `HISTSERV DELETE` by always requiring a consistent number of parameters (#1881, #1927, thanks [@FiskFan1999](https://github.com/FiskFan1999)!)
* Sending the empty string as a nickname would not always produce the expected error numeric `431 ERR_NONICKNAMEGIVEN`; this has been fixed (#1933, #1936, thanks [@kylef](https://github.com/kylef)!)
* `znc.in/playback` timestamps are now parsed as pairs of exact integers, not as floats (#1918)
### Internal
* Upgraded to Go 1.18 (#1925)
* Upgraded Alpine version in official Docker image
* Fixed some issues in the example OpenRC init scripts (#1914, #1920, thanks [@thesamesam](https://github.com/thesamesam)!)
## [2.9.1] - 2022-01-10
Ergo 2.9.1 is a bugfix release, fixing a regression introduced in 2.9.0. We regret the oversight.
This release includes no changes to the config file format or database format relative to 2.9.0.
Many thanks to [@FiskFan1999](https://github.com/FiskFan1999) for reporting the issue.
### Fixed
* Every use of NS SAREGISTER would fail; this has been fixed (#1898, thanks [@FiskFan1999](https://github.com/FiskFan1999)!)
## [2.9.0] - 2022-01-09
We're pleased to be publishing 2.9.0, a new stable release. This release contains mostly bug fixes, with some enhancements to moderation tools.
This release includes changes to the config file format, all of which are fully backwards-compatible and do not require updating the file before upgrading. It includes no changes to the database file format.
Many thanks to [@erincerys](https://github.com/erincerys), [@FiskFan1999](https://github.com/FiskFan1999), [@mogad0n](https://github.com/mogad0n), and [@tacerus](https://github.com/tacerus) for contributing patches, and to [@ajaspers](https://github.com/ajaspers), [@emersion](https://github.com/emersion), [@FiskFan1999](https://github.com/FiskFan1999), [@Jobe1986](https://github.com/Jobe1986), [@kylef](https://github.com/kylef), [@Mikaela](https://github.com/Mikaela), [@mogad0n](https://github.com/mogad0n), [@pcho](https://github.com/pcho), and [@progval](https://github.com/progval) for reporting issues and helping test.
### Config changes
* Added `lock-file`, which helps protect against accidentally starting multiple instances of Ergo. This is a no-op if unset. The recommended default value is `ircd.lock`, which (like the default datastore path `ircd.db`) is relative to the working directory of the Ergo process. If your `datastore.path` is absolute, this path (if set) should be absolute as well. (#1823)
* `+C` (no channel-wide CTCP messages other than ACTION) is now a recommended default channel mode (#1851)
* Added `exempt-sasl` boolean to `server.ip-check-script`; if enabled, IP check scripts are run only for connections without SASL, improving performance for registered users (#1888)
* `hidden: true` is now the recommended default for operator definitions (#1730)
### Changed
* The semantics of `+R` have been changed. `+R` now only prevents unauthenticated users from joining, so unregistered users who have already joined can still speak. The old semantics are still available via `+RM` (i.e. `+R` together with the `+M` "moderated-registered" mode). (#1858, thanks [@ajaspers](https://github.com/ajaspers)!)
* Unauthenticated users matching a `+I` invite exception mask can now join `+R` channels (#1871)
* INVITE now exempts the user from `+b` bans (#1876, thanks [@progval](https://github.com/progval)!)
* NS SUSPEND now only requires only the `ban` operator capability, as opposed to `accreg` (#1828, #1839, thanks [@mogad0n](https://github.com/mogad0n)!)
### Added
* SHA-256 certificate fingerprints can now be imported from Anope and Atheme (#1864, #1869, thanks [@tacerus](https://github.com/tacerus)!)
* IP check scripts can now be run only for users that have not authenticated with SASL by the end of the handshake, improving performance for registered users (#1888)
* Logging into an unverified account with SASL sends the new `NOTE AUTHENTICATE VERIFICATION_REQUIRED` [standard reply code](https://ircv3.net/specs/extensions/standard-replies) (#1852, #1853, thanks [@emersion](https://github.com/emersion)!)
* CS PURGE now sends a snotice (#1826, thanks [@tacerus](https://github.com/tacerus)!)
* The `v` snomask is now used to send notifications about vhost changes initiated by operators (#1844, thanks [@pcho](https://github.com/pcho)!)
### Fixed
* CAP LS and LIST responses after connection registration could be truncated in some cases; this has been fixed (#1872)
* Unprivileged users with both a password and a certfp could not remove their password with `NS PASSWD <password> * *` as expected; this has been fixed (#1883, #1884, thanks [@FiskFan1999](https://github.com/FiskFan1999)!)
* RELAYMSG identifiers that were not already in their case-normalized form could not be muted with `+b m:`; this has been fixed (#1838, thanks [@mogad0n](https://github.com/mogad0n)!)
* CS AMODE changes did not take immediate effect if `force-nick-equals-account` was disabled and the nick did not coincide with the account; this has been fixed (#1860, thanks [@eskimo](https://github.com/eskimo)!)
* `315 RPL_ENDOFWHO` now sends the exact, un-normalized mask argument provided by the client (#1831, thanks [@progval](https://github.com/progval)!)
* A leading `$` character is now disallowed in new nicknames and account names, to avoid collision with the massmessage syntax (#1857, thanks [@emersion](https://github.com/emersion)!)
* The [deprecated](https://github.com/ircdocs/modern-irc/pull/138) `o` parameter of `WHO` now returns an empty list of results, instead of being ignored (#1730, thanks [@kylef](https://github.com/kylef), [@emersion](https://github.com/emersion), [@progval](https://github.com/progval)!)
* WHOX queries for channel oplevel now receive `*` instead of `0` (#1866, thanks [@Jobe1986](https://github.com/Jobe1986)!)
### Internal
* Updated list of official release binaries: added Apple M1, OpenBSD x86-64, and Plan 9 x86-64, removed Linux armv7, FreeBSD x86-32, and Windows x86-32. (The removed platforms are still fully supported by Ergo; you can build them from source or ask us for help.) (#1833)
* Added an official Linux arm64 Docker image (#1855, thanks [@erincerys](https://github.com/erincerys)!)
* Added service management files for OpenSolaris/Illumos (#1846, thanks [@tacerus](https://github.com/tacerus)!)
## [2.8.0] - 2021-11-14
We're pleased to be publishing Ergo 2.8.0. This release contains many fixes and enhancements, plus one major user-facing feature: user-initiated password resets via e-mail (#734).
This release includes changes to the config file format, all of which are fully backwards-compatible and do not require updating the file before upgrading.
This release includes a database change. If you have `datastore.autoupgrade` set to `true` in your configuration, it will be automatically applied when you restart Ergo. Otherwise, you can update the database manually by running `ergo upgradedb` (see the manual for complete instructions).
As part of this release, our official Docker images have moved from Docker Hub to the GitHub Container Registry, at `ghcr.io/ergochat/ergo`. The `stable` and `master` tags correspond to the respective branches. Tagged releases (e.g. `v2.8.0`) are available under the corresponding named tags.
Many thanks to [@ajaspers](https://github.com/ajaspers), [@delthas](https://github.com/delthas), [@mogad0n](https://github.com/mogad0n), [@majiru](https://github.com/majiru), [@ProgVal](https://github.com/ProgVal), and [@tacerus](https://github.com/tacerus) for contributing patches, to [@ajaspers](https://github.com/ajaspers) for contributing code review, to [@ajaspers](https://github.com/ajaspers), [@cxxboy](https://github.com/cxxboy), [@dallemon](https://github.com/dallemon), [@emersion](https://github.com/emersion), [@erikh](https://github.com/erikh), [@eskimo](https://github.com/eskimo), [@jwheare](https://github.com/jwheare), [@kylef](https://github.com/kylef), [@Mikaela](https://github.com/Mikaela), [@mogad0n](https://github.com/mogad0n), [@MystaraTheGreat](https://github.com/MystaraTheGreat), [@ProgVal](https://github.com/ProgVal), [@tacerus](https://github.com/tacerus), [@tamiko](https://github.com/tamiko), and [@xnaas](https://github.com/xnaas) for reporting issues and helping test, and to our translators for contributing translations.
### Config changes
* Added `accounts.registration.email-verification.password-reset` block to configure e-mail-based password reset (#734, #1779)
* Added `accounts.registration.email-verification.timeout` to impose a timeout on e-mail sending; the recommended default value is `60s` (60 seconds) (#1741)
* Added `server.suppress-lusers` to allow hiding the LUSERS counts (#1802, thanks [@eskimo](https://github.com/eskimo)!)
### Security
* Added `accounts.registration.email-verification.timeout` to impose a timeout on e-mail sending; the recommended default value is `60s` (60 seconds) (#1741)
### Added
* Added user-initiated password resets via email (#734). This requires e-mail verification of accounts, and must additionally be enabled explicitly: see the `email-verification` block in `default.yaml` for more information.
* Added the `draft/extended-monitor` capability (#1761, thanks [@delthas](https://github.com/delthas)!)
* When doing direct sending of verification emails, make email delivery failures directly visible to the end user (#1659, #1741, thanks [@tacerus](https://github.com/tacerus)!)
* For operators, `NS INFO` now shows the user's email address (you can also view your own address) (#1677, thanks [@ajaspers](https://github.com/ajaspers)!)
* Operators with the appropriate permissions will now see IPs in `/WHOWAS` output (#1702, thanks [@ajaspers](https://github.com/ajaspers)!)
* Added the `+s d` snomask, for operators to receive information about session disconnections that do not result in a full QUIT (#1709, #1728, thanks [@mogad0n](https://github.com/mogad0n)!)
* Added support for the `SCRAM-SHA-256` SASL authentication mechanism (#175). This mechanism is not currently advertised in `CAP LS` output because IRCCloud handles it incorrectly. We also [recommend against using SCRAM because of its lack of genuine security benefits](https://gist.github.com/slingamn/3f2fed196df5ef14d1316a1ffa9d59f8).
* `/UBAN LIST` output now includes the time the ban was created (#1725, #1755, thanks [@Mikaela](https://github.com/Mikaela) and [@mogad0n](https://github.com/mogad0n)!)
* Added support for running as a `Type=notify` systemd service (#1733)
* Added a warning to help users detect incorrect uses of `/QUOTE` (#1530)
### Fixed
* The `+M` (only registered users can speak) channel mode did not work; this has been fixed (#1696, thanks [@Mikaela](https://github.com/Mikaela)!)
* A channel `/RENAME` that only changed the case of the channel would delete the channel registration; this has been fixed (#1751, thanks [@Mikaela](https://github.com/Mikaela)!)
* Fixed `allow-truncation: true` not actually allowing truncation of overlong lines (#1766, thanks [@tacerus](https://github.com/tacerus)!)
* Fixed several pagination bugs in `CHATHISTORY` (#1676, thanks [@emersion](https://github.com/emersion)!)
* Fixed support for kicking multiple users from a channel on the same line, the `TARGMAX` 005 parameter that advertises this, and the default kick message (#1748, #1777, #1776), thanks [@ProgVal](https://github.com/ProgVal)!)
* Fixed `/SAMODE` on a channel not producing a snomask (#1787, thanks [@mogad0n](https://github.com/mogad0n), [@ajaspers](https://github.com/ajaspers)!)
* Adding `+f` to a channel with `SAMODE` used to require channel operator privileges on the receiving channel; this has been fixed (#1825, thanks [@Mikaela](https://github.com/Mikaela)!)
* Fixed parameters sent with `697 ERR_LISTMODEALREADYSET` and `698 ERR_LISTMODENOTSET` (#1727, thanks [@kylef](https://github.com/kylef)!)
* Fixed parameter sent with `696 ERR_INVALIDMODEPARAM` (#1773, thanks [@kylef](https://github.com/kylef)!)
* Fixed handling of channel mode `+k` with an empty parameter (#1774, #1775, thanks [@ProgVal](https://github.com/ProgVal)!)
* `WHOWAS` with an empty string as the parameter now produces an appropriate error response (#1703, thanks [@kylef](https://github.com/kylef)!)
* Fixed error response to an empty realname on the `USER` line (#1778, thanks [@ProgVal](https://github.com/ProgVal)!)
* Fixed `/UBAN ADD` of a NUH mask (i.e. a k-line) not killing affected clients (#1736, thanks [@mogad0n](https://github.com/mogad0n)!)
* Fixed buggy behavior when `+i` is configured as a default mode for channels (#1756, thanks [@Mikaela](https://github.com/Mikaela)!)
* Fixed issues with `channels.operator-only-creation` not respecting `/SAJOIN` or always-on clients (#1757)
* Protocol-breaking operator vhosts are now disallowed during config validation (#1722)
* Fixed error message associated with `/NS PASSWD` on a nonexistent account (#1738, thanks [@Mikaela](https://github.com/Mikaela)!)
* Fixed an incorrect `CHATHISTORY` fail message (#1731, thanks [@ProgVal](https://github.com/ProgVal)!)
* Fixed a panic on an invalid configuration case (#1714, thanks [@erikh](https://github.com/erikh)!)
### Changed
* Upgraded the `draft/register` capability to the latest [`draft/account-registration`](https://github.com/ircv3/ircv3-specifications/pull/435) iteration (#1740)
* Unregistered users with `+v` or higher can now speak in `+R` (registered-only) channels (#1715, thanks [@Mikaela](https://github.com/Mikaela) and [@ajaspers](https://github.com/ajaspers)!)
* For always-on clients with at least one active connection, `338 RPL_WHOISACTUALLY` now displays an arbitrarily chosen client IP address (#1650, thanks [@MystaraTheGreat](https://github.com/MystaraTheGreat)!)
* `#` can no longer be used in new account names and nicknames, or as the RELAYMSG separator (#1679)
* The `oragono.io/nope` capability was renamed to `ergo.chat/nope` (#1793)
### Removed
* `never` is no longer accepted as a value of the `replay-joins` NickServ setting (`/NS SET replay-joins`); user accounts which enabled this setting have been reverted to the default value of `commands-only` (#1676)
### Internal
* We have a cool new logo!
* Official builds now use Go 1.17 (#1781)
* Official Docker containers are now at [ghcr.io/ergochat/ergo](https://ghcr.io/ergochat/ergo) (#1808)
* Added a traditional SysV init script (#1691, thanks [@tacerus](https://github.com/tacerus)!)
* Added an s6 init script (#1786, thanks [@majiru](https://github.com/majiru)!)
## [2.7.0] - 2021-06-07
We're pleased to be publishing Ergo 2.7.0, our first official release under our new name of Ergo. This release contains bug fixes and minor enhancements.
This release includes changes to the config file format, all of which are fully backwards-compatible and do not require updating the file before upgrading. This release includes no changes to the database format.
Because the name of the executable has changed from `oragono` to `ergo` (`ergo.exe` on Windows), you may need to update your system configuration (e.g., scripts or systemd unit files that reference the executable).
Many thanks to [@ajaspers](https://github.com/ajaspers) and [@jesopo](https://github.com/jesopo) for contributing patches, to [@ajaspers](https://github.com/ajaspers), [@ChrisTX](https://github.com/ChrisTX), [@emersion](https://github.com/emersion), [@jwheare](https://github.com/jwheare), [@kylef](https://github.com/kylef), [@Mikaela](https://github.com/Mikaela), [@mogad0n](https://github.com/mogad0n), and [@ProgVal](https://github.com/ProgVal) for reporting issues and helping test, and to our translators for contributing translations.
### Changed
* The project was renamed from "Oragono" to "Ergo" (#897, thanks to everyone who contributed feedback or voted in the poll)
### Config changes
* Entries in `server.listeners` now take a new key, `min-tls-version`, that can be used to set the minimum required TLS version; the recommended default value is `1.2` (#1611, thanks [@ChrisTX](https://github.com/ChrisTX)!)
* Added `max-conns` (maximum connection count) and `max-conn-lifetime` (maximum lifetime of a connection before it is cycled) to `datastore.mysql` (#1622)
* Added `massmessage` operator capability to allow sending NOTICEs to all connected users (#1153, #1629, thanks [@jesopo](https://github.com/jesopo)!)
### Security
* If `require-sasl.enabled` is set to `true`, `tor-listeners.require-sasl` will be automatically set to `true` as well (#1636)
* It is now possible to set the minimum required TLS version, using the `min-tls-version` key in listener configuration
* Configurations that require SASL but allow user registration now produce a warning (#1637)
### Added:
* Operators with the correct permissions can now send "mass messages", e.g. `/NOTICE $$*` will send a `NOTICE` to all users (#1153, #1629, thanks [@jesopo](https://github.com/jesopo)!)
* Operators can now extend the maximum (non-tags) length of the IRC line using the `server.max-line-len` configuration key. This is not recommended for use outside of "closed-circuit" deployments where IRC operators have full control of all client software. (#1651)
### Fixed
* `RELAYMSG` now sends a full NUH ("nick-user-host"), instead of only the relay nickname, as the message source (#1647, thanks [@ProgVal](https://github.com/ProgVal), [@jwheare](https://github.com/jwheare), and [@Mikaela](https://github.com/Mikaela)!)
* Fixed a case where channels would remain visible in `/LIST` after unregistration (#1619, thanks [@ajaspers](https://github.com/ajaspers)!)
* Fixed incorrect tags on `JOIN` lines in `+u` ("auditorium") channels (#1642)
* Fixed an issue where LUSERS counts could get out of sync (#1617)
* It was impossible to add a restricted set of snomasks to an operator's permissions; this has been fixed (#1618)
* Fixed incorrect language in `NS INFO` responses (#1627, thanks [@ajaspers](https://github.com/ajaspers)!)
* Fixed a case where the `REGISTER` command would emit an invalid error message (#1633, thanks [@ajaspers](https://github.com/ajaspers)!)
* Fixed snomasks displaying in a nondeterministic order (#1669, thanks [@Mikaela](https://github.com/Mikaela)!)
### Removed
* Removed the `draft/resume-0.5` capability, and the associated `RESUME` and `BRB` commands (#1624)
### Internal
* Optimized MySQL storage of direct messages (#1615)
## [2.6.1] - 2021-04-26
Oragono 2.6.1 is a bugfix release, fixing a security issue that is critical for some private server configurations. We regret the oversight.
The issue affects two classes of server configuration:
1. Private servers that use `server.password` (i.e., the `PASS` command) for protection. If `accounts.registration.allow-before-connect` is enabled, the `REGISTER` command can be used to bypass authentication. Affected operators should set this field to `false`, or upgrade to 2.6.1, which disallows the insecure configuration. (If the field does not appear in the configuration file, the configuration is secure since the value defaults to false when unset.)
2. Private servers that use `accounts.require-sasl` for protection. If these servers do not additionally set `accounts.registration.enabled` to `false`, the `REGISTER` command can potentially be used to bypass authentication. Affected operators should set `accounts.registration.enabled` to false; this recommendation appeared in the operator manual but was not emphasized sufficiently. (Configurations that require SASL but allow open registration are potentially valid, e.g., in the case of public servers that require everyone to use a registered account; accordingly, Oragono 2.6.1 continues to permit such configurations.)
This release includes no changes to the config file format or the database.
Many thanks to [@ajaspers](https://github.com/ajaspers) for reporting the issue.
### Security
* Fixed and documented potential authentication bypasses via the `REGISTER` command (#1634, thanks [@ajaspers](https://github.com/ajaspers)!)
## [2.6.0] - 2021-04-18
We're pleased to announce Oragono 2.6.0, a new stable release.
This release has some user-facing enhancements, but is primarily focused on fixing bugs and advancing the state of IRCv3 standardization (by publishing a release that implements the latest drafts). Some highlights:
* A new CHATHISTORY API for listing direct message conversations (#1592)
* The latest proposal for IRC-over-websockets, which should be backwards-compatible with existing clients (#1558)
* The latest specification for the bot usermode (`+B` in our implementation) (#1562)
This release includes changes to the config file format, all of which are fully backwards-compatible and do not require updating the file before upgrading.
This release includes no changes to the embedded database format. If you are using MySQL for history storage, it adds a new table; this change is backwards and forwards-compatible and does not require any manual intervention.
If you are using nginx as a reverse proxy for IRC-over-websockets, previous documentation did not recommend increasing `proxy_read_timeout`; the default value of `60s` is too low and can lead to user disconnections. The current recommended value is `proxy_read_timeout 600s;`; see the manual for an example configuration.
Many thanks to [@ajaspers](https://github.com/ajaspers) and [@Mikaela](https://github.com/Mikaela) for contributing patches, to [@aster1sk](https://github.com/aster1sk), [@emersion](https://github.com/emersion), [@eskimo](https://github.com/eskimo), [@hhirtz](https://github.com/hhirtz), [@jlu5](https://github.com/jlu5), [@jwheare](https://github.com/jwheare), [@KoraggKnightWolf](https://github.com/KoraggKnightWolf), [@kylef](https://github.com/kylef), [@Mikaela](https://github.com/Mikaela), [@mogad0n](https://github.com/mogad0n), [@ProgVal](https://github.com/ProgVal), and [@szlend](https://github.com/szlend) for reporting issues and helping test, and to our translators for contributing translations.
### Config changes
* Listeners now support multiple TLS certificates for use with SNI; see the manual for details (#875, thanks [@Mikaela](https://github.com/Mikaela)!)
* Added `server.compatibility.allow-truncation`, controlling whether the server accepts messages that are too long to be relayed intact; this value defaults to `true` when unset (#1577, #1586, thanks [@kylef](https://github.com/kylef)!)
* Added new `snomasks` operator capability; operators must have either the `ban` or `snomasks` capability to subscribe to additional snomasks (#1176)
### Security
* Fixed several edge cases where Oragono might relay invalid UTF8 despite the `UTF8ONLY` guarantee, or to a text-mode websocket client (#1575, #1596, thanks [@ProgVal](https://github.com/ProgVal)!)
* All operator privilege checks now use the capabilities system, making it easier to define operators with restricted powers (#1176)
* Adding and removing bans with `UBAN` now produces snomasks and audit loglines (#1518, thanks [@mogad0n](https://github.com/mogad0n)!)
### Fixed
* Fixed an edge case in line buffering that could result in client disconnections (#1572, thanks [@ProgVal](https://github.com/ProgVal)!)
* Upgraded buntdb, our embedded database library, fixing an edge case that could cause data corruption (#1603, thanks [@Mikaela](https://github.com/Mikaela), [@tidwall](https://github.com/tidwall)!)
* Improved compatibility with the published `draft/register` specification (#1568, thanks [@ProgVal](https://github.com/ProgVal)!)
* `433 ERR_NICKNAMEINUSE` is no longer sent when a fully connected ("registered") client fails to claim a reserved nickname, fixing a bad interaction with some client software (#1594, thanks [@ProgVal](https://github.com/ProgVal)!)
* Fixed `znc.in/playback` commands causing client disconnections when history is disabled (#1552, thanks [@szlend](https://github.com/szlend)!)
* Fixed syntactically invalid `696 ERR_INVALIDMODEPARAM` response for invalid channel keys (#1563, thanks [@ProgVal](https://github.com/ProgVal)!)
* User-set nickserv settings now display as "enabled" instead of "mandatory" (#1544, thanks [@Mikaela](https://github.com/Mikaela)!)
* Improved error messages for some invalid configuration cases (#1559, thanks [@aster1sk](https://github.com/aster1sk)!)
* Improved `CS TRANSFER` error messages (#1534, thanks burning!)
* Handle panics caused when rehashing with SIGHUP (#1570)
### Changed
* Registered channels will always appear in `/LIST` output, even with no members (#1507)
* In the new recommended default configuration, Oragono will preemptively reject messages that are too long to be relayed to clients without truncation. This is controlled by the config variable `server.compatibility.allow-truncation`; this field defaults to `true` when unset, preserving the legacy behavior for older config files (#1577, #1586, thanks [@kylef](https://github.com/kylef)!)
* Auto-away behavior now respects individual clients; the user is not considered away unless all clients are away or disconnected (#1531, thanks [@kylef](https://github.com/kylef)!)
* Direct messages rejected due to the `+R` registered-only usermode now produce an error message (#1064, thanks [@KoraggKnightWolf](https://github.com/KoraggKnightWolf), [@ajaspers](https://github.com/ajaspers)!)
* RELAYMSG identifiers now respect bans and mutes (#1502)
* If end user message deletion is enabled, channel operators can now delete channel messages (#1565, thanks [@Mikaela](https://github.com/Mikaela)!)
* Halfops can change the channel topic (#1523)
* Snomask add/remove syntax now matches other ircds more closely (#1074)
* `CS OP` will regrant your channel `AMODE`, in case you removed it (#1516, #1307, thanks [@jlu5](https://github.com/jlu5)!)
* User passwords may no longer begin with `:` (#1571)
* Improved documentation of `CS AMODE` and `NS UNREGISTER` (#1524, #1545, thanks [@Mikaela](https://github.com/Mikaela)!)
* Disabling history disables history-related CAPs (#1549)
### Added
* Implemented the new [CHATHISTORY TARGETS](https://github.com/ircv3/ircv3-specifications/pull/450) API for listing direct message conversations (#1592, thanks [@emersion](https://github.com/emersion), [@hhirtz](https://github.com/hhirtz), [@jwheare](https://github.com/jwheare), [@kylef](https://github.com/kylef)!)
* Implemented the new [IRC-over-websockets draft](https://github.com/ircv3/ircv3-specifications/pull/342), adding support for binary websockets and subprotocol negotiation (#1558, thanks [@jwheare](https://github.com/jwheare)!)
* Implemented the new [bot mode spec](https://github.com/ircv3/ircv3-specifications/pull/439) (#1562)
* Implemented the new [forward mode spec](https://github.com/ircv3/ircv3-specifications/pull/440) (#1612, thanks [@ProgVal](https://github.com/ProgVal)!)
* `WARN NICK ACCOUNT_REQUIRED` is sent on failed attempts to claim a reserved nickname (#1594)
* `NS CLIENTS LIST` displays enabled client capabilities (#1576)
* `CS INFO` with no arguments lists your registered channels (#765)
* `NS PASSWORD` is now accepted as an alias for `NS PASSWD` (#1547)
### Internal
* Upgraded to Go 1.16 (#1510)
## [2.5.1] - 2021-02-02
Oragono 2.5.1 is a bugfix release that fixes a significant security issue. We apologize for the oversight.
This release includes no changes to the config file format or the database.
Many thanks to [@xnaas](https://github.com/xnaas) for reporting the issue.
### Security
* Fix an incorrect permissions check in NickServ (#1520, thanks [@xnaas](https://github.com/xnaas)!)
## [2.5.0] - 2021-01-31
We're pleased to announce Oragono 2.5.0, a new stable release.
This release includes enhancements based on the needs of real-world operators, as well as bug fixes. Highlights include:
* `UBAN`, a new "unified ban" system for server operators, with a corresponding `CHANSERV HOWTOBAN` command for channel operators (#1447)
* A new forwarding/overflow channel mode `+f` (#1260)
* Support for PROXY protocol v2 (#1389)
This release includes changes to the config file format, including two breaking changes. One is fairly significant: enabling a websocket listener now requires the use of `server.enforce-utf8`, as has been the recommended default since 2.2.0 (so continuing to accept legacy non-UTF-8 content will require disabling websockets). The other is that the "unban" operator capability has been removed (it is now included in the "ban" capability). Other config changes are backwards compatible and do not require updating the file before upgrading.
This release includes a database change. If you have `datastore.autoupgrade` set to `true` in your configuration, it will be automatically applied when you restart Oragono. Otherwise, you can update the database manually by running `oragono upgradedb` (see the manual for complete instructions).
Many thanks to [@jlu5](https://github.com/jlu5), [@kylef](https://github.com/kylef) and [@Mikaela](https://github.com/Mikaela) for contributing patches, to [@bogdomania](https://github.com/bogdomania), [@eskimo](https://github.com/eskimo), [@happyhater](https://github.com/happyhater), [@jlu5](https://github.com/jlu5), [@kylef](https://github.com/kylef), [@LukeHoersten](https://github.com/LukeHoersten), [@Mikaela](https://github.com/Mikaela), [@mogad0n](https://github.com/mogad0n), [@robinlemon](https://github.com/robinlemon), and [@vertisan](https://github.com/vertisan) for reporting issues and helping test, and to our translators for contributing translations.
### Config changes
* Enabling websockets now requires `server.enforce-utf8 = true` (#1483)
* `proxy` is now a top-level field of the listener config block; in particular, the PROXY protocol (v1 or v2) can now be required ahead of a plaintext connection. The field is still accepted in its legacy position (inside the `tls` block). (#1389, thanks [@robinlemon](https://github.com/robinlemon)!)
* Added `accounts.multiclient.always-on-expiration`, allowing always-on clients to be timed out for inactivity (#810, thanks [@bogdomania](https://github.com/bogdomania)!)
* `local_` prefixes have been stripped from operator capability names, so that, e.g., `local_ban` is now just `ban`. The old names are still accepted. (#1442)
* The `local_unban` operator capability has been removed (unbanning is now contained in the `ban` permission). (#1442)
* The recommended value of `accounts.bcrypt-cost` is now `4`, the minimum acceptable value (#1497)
* `server.ip-limits.custom-limits` now accepts networks that contain multiple CIDRs; the old syntax is still accepted (#1421, thanks [@Mikaela](https://github.com/Mikaela)!
* A new field, `history.restrictions.query-cutoff`, generalizes the old `history.restrictions.enforce-registration-date` (the old field is still accepted) (#1490, thanks [@Mikaela](https://github.com/Mikaela)!)
* Added `server.override-services-hostname`, allowing the hostname of NickServ, ChanServ, etc. to be overridden (#1407, thanks [@Mikaela](https://github.com/Mikaela)!)
* Added a boolean `hide-sts` key to the listener block; this can be used to hide the STS CAP when the listener is secured at layer 3 or 4 (e.g., by a VPN or an E2E mixnet). It will still be necessary to add the relevant IPs to `secure-nets`. (#1428, thanks [@Mikaela](https://github.com/Mikaela)!)
### Security
* Improved validation of names and encodings for client-only tags (#1385)
* Improved auditability of sensitive operator actions (#1443, thanks [@mogad0n](https://github.com/mogad0n)!)
* `DEFCON 4` and lower now require Tor users to authenticate with SASL (#1450)
### Fixed
* Fixed `NS UNSUSPEND` requiring the casefolded / lowercase version of the account name (#1382, thanks [@mogad0n](https://github.com/mogad0n)!)
* Fixed client-only tags in direct (user-to-user) `PRIVMSG` not being replayed (#1411)
* Fixed many bugs in import of Anope and Atheme databases (#1403, #1423, #1424, #1431, #1435, #1439, #1444, thanks [@jlu5](https://github.com/jlu5), [@kylef](https://github.com/kylef), and [@Mikaela](https://github.com/Mikaela)!)
* Fixed case-handling bugs in `RENAME` (i.e., channel rename) (#1456, thanks [@mogad0n](https://github.com/mogad0n)!)
* Fixed incorrect processing of color code escapes in MOTD files (#1467, thanks [@mogad0n](https://github.com/mogad0n)!)
* STS is no longer advertised to Tor clients (#1428, thanks [@Mikaela](https://github.com/Mikaela)!)
* Fixed HELP/HELPOP numerics not including the nick as an argument (#1472, thanks [@kylef](https://github.com/kylef)!)
* Made connection registration snomasks less confusing (#1396, thanks [@eskimo](https://github.com/eskimo)!)
* Fixed duplicated nicks in `KLINE` response (#1379, thanks [@mogad0n](https://github.com/mogad0n)!)
* The `RELAYMSG` tag name is now `draft/relaymsg`, conforming to the amended draft specification (#1468, thanks [@jlu5](https://github.com/jlu5)!)
* Fixed `SAJOIN` not sending a `MODE` line to the originating client (#1383, thanks [@mogad0n](https://github.com/mogad0n)!)
* Improved consistency of message sources sent by `CS AMODE` (#1383, thanks [@mogad0n](https://github.com/mogad0n)!)
* Fixed duplicated `JOIN` line sent to some clients using the `draft/resume-0.5` extension (#1397, thanks [@kylef](https://github.com/kylef)!)
* Added a warning that MySQL cannot be enabled by rehash (#1452, thanks [@Mikaela](https://github.com/Mikaela)!)
### Changed
* Channel-user modes (e.g., `+o`, `+v`) of always-on clients are now persisted in the database (#1345)
* `/CHANSERV PURGE` now takes `ADD`, `DEL`, and `LIST` subcommands; the separate `UNPURGE` command has been removed; `PURGE ADD` now requires a confirmation code (#1294, thanks [@mogad0n](https://github.com/mogad0n)!)
* The characters `<`, `>`, `'`, `"`, and `;` are no longer allowed in nicknames (previously registered account names containing these characters are still accepted) (#1436, thanks [@happyhater](https://github.com/happyhater)!)
* Authenticated clients from Tor now receive their (account-unique) always-on cloaked hostname; this allows channel operators to ban unauthenticated Tor users by banning `*!*@tor-network.onion` (#1479, thanks [@mogad0n](https://github.com/mogad0n)!)
* Included the network name in the human-readable final parameter of `001 RPL_WELCOME` (#1410)
* `RELAYMSG` can now take client-only tags (#1470)
* WebSocket listeners will attempt to negotiate the `text.ircv3.net` [subprotocol](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#subprotocols); negotiating this is optional for clients (#1483)
### Added
* Added `UBAN`, a new command giving server operators a unified interface to D-LINEs (IP bans), K-LINEs (NUH mask bans, which are now deprecated), and account suspensions (`NS SUSPEND`) (#1447)
* Added `CHANSERV HOWTOBAN`, a ChanServ subcommand that helps channel operators choose an appropriate ban (#1447)
* Added a new channel mode `+f`; users who cannot join the channel due to `+i` or `+l` will be forwarded to the channel specified by `+f`. (#1260)
* Added support for the PROXY protocol v2 (#1389, thanks [@robinlemon](https://github.com/robinlemon)!)
* Added support for `/JOIN 0` (part all channels), requiring a confirmation code (#1417, thanks [@Mikaela](https://github.com/Mikaela)!)
* Added support for grouped nicknames as SASL usernames (#1476, thanks [@eskimo](https://github.com/eskimo)!)
* Added history support for `INVITE` (#1409, thanks [@Mikaela](https://github.com/Mikaela)!)
* Added a new channel setting accessible via `/CS SET`: `history-cutoff`, allowing the channel owner more fine-grained control over who can see history (#1490, thanks [@Mikaela](https://github.com/Mikaela)!)
* Added the `UTF8ONLY` ISUPPORT token, allowing the server to advertise to clients that only UTF-8 content is accepted (#1483)
* Added `/NICKSERV RENAME`, an operator-only command that can change the case of an account name (#1380, thanks [@LukeHoersten](https://github.com/LukeHoersten)!)
### Internal
* Added caching for serialized messages (#1387)
* Improved memory efficiency of line reading (#1231)
## [2.4.0] - 2020-11-08
We're pleased to announce Oragono 2.4.0, a new stable release.
This release includes a number of exciting enhancements and fixes. Here are some highlights:
* Support for migrating an Anope or Atheme database to Oragono (#1042)
* A pluggable system for validating external IPs, e.g., via DNSBLs (#68, thanks [@moortens](https://github.com/moortens)!)
* [draft/relaymsg](https://github.com/ircv3/ircv3-specifications/pull/417), a new draft extension simplifying bridging with other chat systems (thanks [@jlu5](https://github.com/jlu5)!)
* New moderation tools: `+u` ("auditorium", #1300), `+U` ("op-moderated", #1178), `+M` ("moderated-registered", #1182, thanks [@ajaspers](https://github.com/ajaspers)!), and `+b m:` (an extban for muting users, #307)
This release includes changes to the config file format, including one breaking change: `roleplay.enabled` now defaults to false (the new recommended default) instead of true when unset. Other config changes are backwards compatible and do not require updating the file before upgrading.
This release includes a database change. If you have `datastore.autoupgrade` set to `true` in your configuration, it will be automatically applied when you restart Oragono. Otherwise, you can update the database manually by running `oragono upgradedb` (see the manual for complete instructions).
Many thanks to [@ajaspers](https://github.com/ajaspers), [@jesopo](https://github.com/jesopo), [@moortens](https://github.com/moortens), and [@RunBarryRun](https://github.com/RunBarryRun) for contributing patches, to [@csmith](https://github.com/csmith) for contributing code reviews, to [@ajaspers](https://github.com/ajaspers), [@Amiga60077](https://github.com/Amiga60077), [@bogdomania](https://github.com/bogdomania), [@csmith](https://github.com/csmith), [@edk0](https://github.com/edk0), [@eskimo](https://github.com/eskimo), [@jlu5](https://github.com/jlu5), [@jwheare](https://github.com/jwheare), [@KoraggKnightWolf](https://github.com/KoraggKnightWolf), [@Mitaka8](https://github.com/Mitaka8), [@mogad0n](https://github.com/mogad0n), [@RyanSquared](https://github.com/RyanSquared), and [@vertisan](https://github.com/vertisan) for reporting issues and helping test, and to our translators for contributing translations.
### Config changes
* Added `server.ip-cloaking.enabled-for-always-on`, which generates a unique hostname for each always-on client. The recommended default value of this field is `true` (#1312)
* Added `server.coerce-ident`; if this is set to a string value, all user/ident fields supplied by clients are ignored and replaced with this value. The recommended default value of this field is `~u`. This simplifies bans. (#1340)
* Simplified the config file format for email verification into a new `accounts.nick-reservation.email-verification` section. The old format (`callbacks`) is still accepted (#1075)
* The recommended value of `roleplay.enabled` is now `false`; this field now defaults to false when unset (#1240, #1271)
* Added `server.relaymsg` section for configuring the new `draft/relaymsg` capability; added the new `relaymsg` operator capability for exercising it (#1119)
* Added `allow-environment-overrides` config variable, allowing config options to be overridden by environment variables. See the manual for more details. (#1049, thanks [@csmith](https://github.com/csmith)!)
* Added `server.ip-check-script` for configuring IP check plugins (#68, #1267, thanks [@moortens](https://github.com/moortens)!)
* Added `max-concurrency` restriction to `accounts.auth-script` section. The recommended default value is `64` (`0` or unset disable the restriction) (#1267)
* Added `accounts.registration.allow-before-connect`; this allows the use of the new `REGISTER` command before connecting to the server (#1075)
* Added `hidden` option in operator blocks: if set to `true`, operator status is hidden from commands like `WHOIS` that would otherwise display it (#1194)
* Added `accounts.nick-reservation.forbid-anonymous-nick-changes`, which forbids anonymous users from changing their nicknames after initially connecting (#1337, thanks [@Amiga60077](https://github.com/Amiga60077)!)
* Added `channels.invite-expiration`, allowing invites to `+i` channels to expire after a given amount of time (#1171)
### Security
* Added `/NICKSERV CLIENTS LOGOUT` command for disconnecting clients connected to a user account (#1072, #1272, thanks [@ajaspers](https://github.com/ajaspers)!)
* Disallowed the use of service nicknames during roleplaying (#1240, thanks [@Mitaka8](https://github.com/Mitaka8)!)
* Improved security properties of `INVITE` for invite-only channels, including an `UNINVITE` command (#1171)
### Removed
* Removed the request queue system for HostServ, i.e., the `REQUEST`, `APPROVE`, and `REJECT` subcommands of `HOSTSERV` (#1346)
### Fixed
* `PONG` is now sent with the server name as the first parameter, matching the behavior of other ircds (#1249, thanks [@jesopo](https://github.com/jesopo)!)
* It was not possible to set or unset the `+T` no-CTCP user mode; this has been fixed (#1299, thanks [@mogad0n](https://github.com/mogad0n)!)
* Fixed edge cases with `/NICKSERV SAREGISTER` of confusable nicknames (#1322, thanks [@mogad0n](https://github.com/mogad0n)!)
* Fixed websocket listeners with proxy-before-TLS enabled closing on invalid PROXY lines (#1269, thanks [@RyanSquared](https://github.com/RyanSquared)!)
* Fixed error responses and history for SANICK (#1277, #1278, thanks [@eskimo](https://github.com/eskimo)!)
* Ensured that stored realnames of always-on clients are deleted during account unregistration (#1330)
* Whitespace is now stripped from KLINEs (#1327, thanks [@mogad0n](https://github.com/mogad0n)!)
* Fixed incorrect `LUSERS` counts caused by KLINE (#1303, thanks [@mogad0n](https://github.com/mogad0n)!)
* `CHATHISTORY` queries for invalid channels now get an empty batch instead of a `FAIL` (#1322)
* `fakelag.messages-per-window = 0` no longer causes a panic (#861, thanks [@vertisan](https://github.com/vertisan)!)
### Added
* Added `oragono importdb` command for importing a converted Anope or Atheme database; see the manual for details (#1042)
* Added support for the new [draft/relaymsg](https://github.com/ircv3/ircv3-specifications/pull/417) extension, which simplifies bridging IRC with other protocols relaymsg (#1119, thanks [@jlu5](https://github.com/jlu5)!)
* Added `ip-check-script`, a scripting API for restricting access by client IP. We provide [oragono-dnsbl](https://github.com/oragono/oragono-dnsbl), an external script that can query DNSBLs for this purpose (#68, #1267, thanks [@moortens](https://github.com/moortens)!)
* Added channel mode `+u`. This is an "auditorium" mode that prevents unprivileged users from seeing each other's `JOIN` and `PART` lines. It's useful for large public-announcement channels, possibly in conjunction with `+m` (#1300)
* Added channel mode `+U`. This is an "op-moderated" mode; messages from unprivileged users are sent only to channel operators, who can then choose to grant them `+v`. (#1178)
* Added a mute extban `+b m:`: users matching the ban expression (e.g., `+b m:*!*@j6dwi4vacx47y.irc`) will be able to join the channel, but will be unable to speak. (#307)
* Added support for the new [draft/register](https://gist.github.com/edk0/bf3b50fc219fd1bed1aa15d98bfb6495) extension, which exposes a cleaner account registration API to clients (#1075, thanks [@edk0](https://github.com/edk0)!)
* Added a `379 RPL_WHOISMODES` line to the `WHOIS` response, making it easier for operators to see other users' modes (#769, thanks [@Amiga60077](https://github.com/Amiga60077) and [@KoraggKnightWolf](https://github.com/KoraggKnightWolf)!)
* Added `/CHANSERV DEOP` command for removing channel operator privileges (#361, thanks [@RunBarryRun](https://github.com/RunBarryRun)!)
* Added `r` flag to `/WHO` responses for registered nicknames (#1366, thanks [@Amiga60077](https://github.com/Amiga60077)!)
### Changed
* Always-on clients now receive a user/ident of `~u` by default, instead of `~user`; this can be changed by setting the `coerce-ident` field (#1340)
* `/NICKSERV SUSPEND` has been modified to take subcommands (`ADD`, `DEL`, and `LIST`); the `ADD` subcommand now accepts time duration and reason arguments. See `/msg NickServ HELP SUSPEND` for details. (#1274, thanks [@mogad0n](https://github.com/mogad0n)!)
* Only the channel founder can kick the channel founder, regardless of either party's modes (#1262)
* `/NICKSERV SESSIONS` is now `/NICKSERV CLIENTS LIST`, but the old command is still accepted (#1272, thanks [@ajaspers](https://github.com/ajaspers)!)
* Improved `SETNAME` behavior for legacy clients (#1358, thanks [@KoraggKnightWolf](https://github.com/KoraggKnightWolf)!)
* Halfops can set the channel topic (#1306)
* Full client certificates are now passed to auth scripts. This allows for more flexible checks on certificates, including verification against an internal CA (#414)
### Internal
* Added a logline for debugging client disconnections (#1293)
* Renamed `conventional.yaml` to `traditional.yaml` (#1350)
* Integration tests are now run during CI (#1279)
## [2.3.0] - 2020-09-06
We're pleased to announce Oragono 2.3.0, a new stable release.
This release contains primarily bug fixes, but includes one notable feature enhancement: a change contributed by [@hhirtz](https://github.com/hhirtz) that updates the `draft/rename` specification to correspond to the new (soon-to-be) published draft.
Many thanks to [@hhirtz](https://github.com/hhirtz) for contributing patches, to [@bogdomania](https://github.com/bogdomania), [@jesopo](https://github.com/jesopo), [@kylef](https://github.com/kylef), [@Mitaka8](https://github.com/Mitaka8),and [@mogad0n](https://github.com/mogad0n) for reporting issues and helping test, and to our translators for contributing translations.
Many thanks to [@hhirtz](https://github.com/hhirtz) for contributing patches, to [@bogdomania](https://github.com/bogdomania), [@digitalcircuit](https://github.com/digitalcircuit), [@ivan-avalos](https://github.com/ivan-avalos), [@jesopo](https://github.com/jesopo), [@kylef](https://github.com/kylef), [@Mitaka8](https://github.com/Mitaka8), [@mogad0n](https://github.com/mogad0n), and [@ProgVal](https://github.com/ProgVal) for reporting issues and helping test, and to our translators for contributing translations.
This release includes no changes to the config file format or database changes.
@ -24,6 +728,8 @@ This release includes no changes to the config file format or database changes.
* Fixed an edge case in handling no-op nick changes (#1242)
* Fixed edge cases with users transitioning in and out of always-on status (#1218, #1219, thanks [@bogdomania](https://github.com/bogdomania)!)
* Fixed a race condition related to the registration timeout (#1225, thanks [@hhirtz](https://github.com/hhirtz)!)
* Fixed incorrectly formatted account tags on some messages (#1254, thanks [@digitalcircuit](https://github.com/digitalcircuit)!)
* Improved checks for invalid config files (#1244, thanks [@ivan-avalos](https://github.com/ivan-avalos)!)
* Fixed messages to services and `*playback` not receiving echo-message when applicable (#1204, thanks [@kylef](https://github.com/kylef)!)
* Fixed a help string (#1237, thanks [@Mitaka8](https://github.com/Mitaka8)!)
@ -627,7 +1333,7 @@ Thanks to [slingamn](https://github.com/slingamn) for a lot of heavy lifting thi
## [0.11.0] - 2018-04-15
And v0.11.0 finally comes along! This release has been in the works for almost four months now, with an alpha and beta helping square away the issues.
We're adding a lot of features to improve debugging, better support international users, and make things better for network administrators. Among the new features, you can use the `LANGUAGE` command to set a custom server language (see our [CrowdIn](https://crowdin.com/project/oragono) to contribute), expose a debugging `pprof` endpoint, reserve nicknames with `NickServ`, and force email verification for new user accounts. On the improvements side we have a `CAP REQ` fix, and we now have a manual that contains a nice overview of Oragono's documentation.
We're adding a lot of features to improve debugging, better support international users, and make things better for network administrators. Among the new features, you can use the `LANGUAGE` command to set a custom server language (see our [CrowdIn](https://crowdin.com/project/ergochat) to contribute), expose a debugging `pprof` endpoint, reserve nicknames with `NickServ`, and force email verification for new user accounts. On the improvements side we have a `CAP REQ` fix, and we now have a manual that contains a nice overview of Oragono's documentation.
If you have any trouble with this release, please let us know with an issue on our tracker, or by talking to us in `#oragono` on Freenode.

View File

@ -1,49 +1,76 @@
# Developing Oragono
# Developing Ergo
This is just a bunch of tips and tricks we keep in mind while developing Oragono. If you wanna help develop as well, they might also be worth keeping in mind!
This is a guide to modifying Ergo's code. If you're just trying to run your own Ergo, or use one, you shouldn't need to worry about these issues.
## Golang issues
You should use the [latest distribution of the Go language for your OS and architecture](https://golang.org/dl/). (If `uname -m` on your Raspberry Pi reports `armv7l`, use the `armv6l` distribution of Go; if it reports v8, you may be able to use the `arm64` distribution.)
Oragono vendors all its dependencies. Because of this, Oragono is self-contained and you should not need to fetch any dependencies with `go get`. Doing so is not recommended, since it may fetch incompatible versions of the dependencies.
Ergo vendors all its dependencies. Because of this, Ergo is self-contained and you should not need to fetch any dependencies with `go get`. Doing so is not recommended, since it may fetch incompatible versions of the dependencies.
If you're upgrading the Go version used by Oragono, there are several places where it's hard-coded and must be changed:
If you're upgrading the Go version used by Ergo, there are several places where it's hard-coded and must be changed:
1. `.travis.yml`, which controls the version that our CI test suite uses to build and test the code (e.g., for a PR)
2. `Dockerfile`, which controls the version that the Oragono binaries in our Docker images are built with
1. `.github/workflows/build.yml`, which controls the version that our CI test suite uses to build and test the code (e.g., for a PR)
2. `Dockerfile`, which controls the version that the Ergo binaries in our Docker images are built with
3. `go.mod`: this should be updated automatically by Go when you do module-related operations
## Branches
The `master` branch should be kept relatively runnable. It might be a bit broken or contain some bad commits now and then, but the pre-release checks should weed those out before users see them.
The recommended workflow for development is to create a new branch starting from the current `master`. Even though `master` is not recommended for production use, we strive to keep it in a usable state. Starting from `master` increases the likelihood that your patches will be accepted.
For either particularly broken or particularly WiP changes, we work on them in a `develop` branch. The normal branch naming is `develop+feature[.version]`. For example, when first developing 'cloaking', you may use the branch `develop+cloaks`. If you need to create a new branch to work on it (a second version of the implementation, for example), you could use `develop+cloaks.2`, and so on.
Long-running feature branches that aren't ready for merge into `master` may be maintained under a `devel+` prefix, e.g. `devel+metadata` for a feature branch implementing the IRCv3 METADATA extension.
Develop branches are either used to work out implementation details in preperation for a cleaned-up version, for half-written ideas we want to continue persuing, or for stuff that we just don't want on `master` yet for whatever reason.
## Workflow
We have two test suites:
1. `make test`, which runs some relatively shallow unit tests, checks `go vet`, and does some other internal consistency checks
1. `make irctest`, which runs the [irctest](https://github.com/ProgVal/irctest) integration test suite
Barring special circumstances, both must pass for a PR to be accepted. irctest will test the `ergo` binary visible on `$PATH`; make sure your development version is the one being tested. (If you have `~/go/bin` on your `$PATH`, a successful `make install` will accomplish this.)
The project style is [gofmt](https://go.dev/blog/gofmt); it is enforced by `make test`. You can fix any style issues automatically by running `make gofmt`.
## Updating dependencies
Ergo vendors all dependencies using `go mod vendor`. To update a dependency, or add a new one:
1. `go get -v bazbat.com/path/to/dependency` ; this downloads the new dependency
2. `go mod vendor` ; this writes the dependency's source files to the `vendor/` directory
3. `git add go.mod go.sum vendor/` ; this stages all relevant changes to the vendor directory, including file deletions. Take care that spurious changes (such as editor swapfiles) aren't added.
4. `git commit`
## Releasing a new version
1. Ensure the tests pass, locally on travis (`make test`, `make smoke`, and `make irctest`)
1. Test backwards compatibility guarantees. Get an example config file and an example database from the previous stable release. Make sure the current build still works with them (modulo anything explicitly called out in the changelog as a breaking change).
1. Run `irctest` over it to make sure nothing's severely broken. Talk to the maintainers to find out which version of irctest to run.
1. Run the `ircstress` chanflood benchmark to look for data races (enable race detection) and performance regressions (disable it).
1. Update the changelog with new changes and write release notes.
1. Update the version number `irc/version.go` (either change `-unreleased` to `-rc1`, or remove `-rc1`, as appropriate).
1. Commit the new changelog and constants change.
1. Tag the release with `git tag --sign v0.0.0 -m "Release v0.0.0"` (`0.0.0` replaced with the real ver number).
1. Build binaries using `make release`, upload release to Github including the changelog and binaries.
1. If it's a proper release (i.e. not an alpha/beta), merge the updates into the `stable` branch.
1. Build binaries using `make release`
1. Sign the checksums file with `gpg --sign --detach-sig --local-user <fingerprint>`
1. Smoke-test a built binary locally
1. Point of no return: `git push origin master --tags` (this publishes the tag; any fixes after this will require a new point release)
1. Publish the release on GitHub (Releases -> "Draft a new release"); use the new tag, post the changelog entries, upload the binaries, the checksums file, and the signature of the checksums file
1. Update the `irctest_stable` branch with the new changes (this may be a force push).
1. If it's a production release (as opposed to a release candidate), update the `stable` branch with the new changes. (This may be a force push in the event that stable contained a backport. This is fine because all stable releases and release candidates are tagged.)
1. Similarly, for a production release, update the `irctest_stable` branch (this is the branch used by upstream irctest to integration-test against Ergo).
1. Make the appropriate announcements:
* For a release candidate:
1. the channel topic
1. any operators who may be interested
1. update the testnet
* For a production release:
1. everything applicable to a release candidate
1. Twitter
1. oragono.io/news
1. ergo.chat/news
1. ircv3.net support tables, if applicable
1. other social media?
@ -57,7 +84,7 @@ Once it's built and released, you need to setup the new development version. To
```md
## Unreleased
New release of Oragono!
New release of Ergo!
### Config Changes
@ -74,17 +101,6 @@ New release of Oragono!
## Fuzzing and Testing
Fuzzing can be useful. We don't have testing done inside the IRCd itself, but this fuzzer I've written works alright and has helped shake out various bugs: [irc_fuzz.py](https://gist.github.com/DanielOaks/63ae611039cdf591dfa4).
In addition, I've got the beginnings of a stress-tester here which is useful:
https://github.com/DanielOaks/irc-stress-test
As well, there's a decent set of 'tests' here, which I like to run Oragono through now and then:
https://github.com/DanielOaks/irctest
## Debugging
It's helpful to enable all loglines while developing. Here's how to configure this:
@ -101,12 +117,12 @@ To debug a hang, the best thing to do is to get a stack trace. The easiest way t
$ kill -ABRT <procid>
This will kill Oragono and print out a stack trace for you to take a look at.
This will kill Ergo and print out a stack trace for you to take a look at.
## Concurrency design
Oragono involves a fair amount of shared state. Here are some of the main points:
Ergo involves a fair amount of shared state. Here are some of the main points:
1. Each client has a separate goroutine that listens for incoming messages and synchronously processes them.
1. All sends to clients are asynchronous; `client.Send` appends the message to a queue, which is then processed on a separate goroutine. It is always safe to call `client.Send`.
@ -154,7 +170,7 @@ In addition, throughout most of the codebase, if a string is created using the b
## Updating Translations
We support translating server strings using [CrowdIn](https://crowdin.com/project/oragono)! To send updated source strings to CrowdIn, you should:
We support translating server strings using [CrowdIn](https://crowdin.com/project/ergochat)! To send updated source strings to CrowdIn, you should:
1. `cd` to the base directory (the one this `DEVELOPING` file is in).
2. Install the `pyyaml` and `docopt` deps using `pip3 install pyyamp docopt`.
@ -184,3 +200,14 @@ We also support grabbing translations directly from CrowdIn. To do this:
4. Run `crowdin download`
This will download a bunch of updated files and put them in the right place
## Adding a mode
When adding a mode, keep in mind the following places it may need to be referenced:
1. The mode needs to be defined in the `irc/modes` subpackage
1. It may need to be special-cased in `modes.RplMyInfo()`
1. It may need to be added to the `CHANMODES` ISUPPORT token
1. It may need special handling in `ApplyUserModeChanges` or `ApplyChannelModeChanges`
1. It may need special persistence handling code

View File

@ -1,56 +1,43 @@
## build Oragono
FROM golang:1.15-alpine AS build-env
## build ergo binary
FROM docker.io/golang:1.24-alpine AS build-env
RUN apk add --no-cache git make curl sed
RUN apk upgrade -U --force-refresh --no-cache && apk add --no-cache --purge --clean-protected -l -u make git
# copy oragono
RUN mkdir -p /go/src/github.com/oragono/oragono
WORKDIR /go/src/github.com/oragono/oragono
ADD . /go/src/github.com/oragono/oragono/
# copy ergo source
WORKDIR /go/src/github.com/ergochat/ergo
COPY . .
# modify default config file so that it doesn't die on IPv6
# and so it can be exposed via 6667 by default
run sed -i 's/^\(\s*\)\"127.0.0.1:6667\":.*$/\1":6667":/' /go/src/github.com/oragono/oragono/default.yaml
run sed -i 's/^\s*\"\[::1\]:6667\":.*$//' /go/src/github.com/oragono/oragono/default.yaml
# make sure submodules are up-to-date
RUN git submodule update --init
RUN sed -i 's/^\(\s*\)\"127.0.0.1:6667\":.*$/\1":6667":/' /go/src/github.com/ergochat/ergo/default.yaml && \
sed -i 's/^\s*\"\[::1\]:6667\":.*$//' /go/src/github.com/ergochat/ergo/default.yaml
# compile
RUN make
RUN make install
## run Oragono
FROM alpine:3.9
## build ergo container
FROM docker.io/alpine:3.19
# metadata
LABEL maintainer="daniel@danieloaks.net"
LABEL description="Oragono is a modern, experimental IRC server written in Go"
# install latest updates and configure alpine
RUN apk update
RUN apk upgrade
RUN mkdir /lib/modules
LABEL maintainer="Daniel Oaks <daniel@danieloaks.net>,Daniel Thamdrup <dallemon@protonmail.com>" \
description="Ergo is a modern, experimental IRC server written in Go"
# standard ports listened on
EXPOSE 6667/tcp 6697/tcp
# oragono itself
RUN mkdir -p /ircd-bin
COPY --from=build-env /go/bin/oragono /ircd-bin
COPY --from=build-env /go/src/github.com/oragono/oragono/languages /ircd-bin/languages/
COPY --from=build-env /go/src/github.com/oragono/oragono/default.yaml /ircd-bin/default.yaml
COPY distrib/docker/run.sh /ircd-bin/run.sh
RUN chmod +x /ircd-bin/run.sh
# ergo itself
COPY --from=build-env /go/bin/ergo \
/go/src/github.com/ergochat/ergo/default.yaml \
/go/src/github.com/ergochat/ergo/distrib/docker/run.sh \
/ircd-bin/
COPY --from=build-env /go/src/github.com/ergochat/ergo/languages /ircd-bin/languages/
# running volume holding config file, db, certs
VOLUME /ircd
WORKDIR /ircd
# default motd
COPY --from=build-env /go/src/github.com/oragono/oragono/oragono.motd /ircd/oragono.motd
COPY --from=build-env /go/src/github.com/ergochat/ergo/ergo.motd /ircd/ergo.motd
# launch
ENTRYPOINT ["/ircd-bin/run.sh"]

View File

@ -1,41 +1,48 @@
.PHONY: all install build release capdefs test smoke gofmt
GIT_COMMIT := $(shell git rev-parse HEAD 2> /dev/null)
GIT_TAG := $(shell git tag --points-at HEAD 2> /dev/null | head -n 1)
# disable linking against native libc / libpthread by default;
# this can be overridden by passing CGO_ENABLED=1 to make
export CGO_ENABLED ?= 0
capdef_file = ./irc/caps/defs.go
all: install
.PHONY: all
all: build
.PHONY: install
install:
go install -v -ldflags "-X main.commit=$(GIT_COMMIT)"
go install -v -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)"
.PHONY: build
build:
go build -v -ldflags "-X main.commit=$(GIT_COMMIT)"
go build -v -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)"
.PHONY: release
release:
goreleaser --skip-publish --rm-dist
goreleaser --skip=publish --clean
.PHONY: capdefs
capdefs:
python3 ./gencapdefs.py > ${capdef_file}
.PHONY: test
test:
python3 ./gencapdefs.py | diff - ${capdef_file}
cd irc && go test . && go vet .
cd irc/caps && go test . && go vet .
cd irc/cloaks && go test . && go vet .
cd irc/connection_limits && go test . && go vet .
cd irc/email && go test . && go vet .
cd irc/history && go test . && go vet .
cd irc/isupport && go test . && go vet .
cd irc/modes && go test . && go vet .
cd irc/mysql && go test . && go vet .
cd irc/passwd && go test . && go vet .
cd irc/utils && go test . && go vet .
go test ./...
go vet ./...
./.check-gofmt.sh
smoke:
oragono mkcerts --conf ./default.yaml || true
oragono run --conf ./default.yaml --smoke
.PHONY: smoke
smoke: install
ergo mkcerts --conf ./default.yaml || true
ergo run --conf ./default.yaml --smoke
.PHONY: gofmt
gofmt:
./.check-gofmt.sh --fix
.PHONY: irctest
irctest: install
git submodule update --init
cd irctest && make ergo

55
README
View File

@ -1,20 +1,24 @@
▄▄▄ ▄▄▄· ▄▄ • ▐ ▄
▪ ▀▄ █·▐█ ▀█ ▐█ ▀ ▪▪ •█▌▐█▪
▄█▀▄ ▐▀▀▄ ▄█▀▀█ ▄█ ▀█▄ ▄█▀▄▪▐█▐▐▌ ▄█▀▄
▐█▌.▐▌▐█•█▌▐█ ▪▐▌▐█▄▪▐█▐█▌ ▐▌██▐█▌▐█▌.▐▌
▀█▄▀▪.▀ ▀ ▀ ▀ ·▀▀▀▀ ▀█▄▀ ▀▀ █▪ ▀█▄▀▪
___ _ __ __ _ ___
/ _ \ '__/ _` |/ _ \
| __/ | | (_| | (_) |
\___|_| \__, |\___/
__/ |
|___/
-----------------------------------------------------------------------------------------------
Oragono is a modern IRC server written in Go. It's designed to be simple to setup
and use, and to provide the majority of features that IRC users expect today.
Ergo is a modern IRC server written in Go. Its core design principles are:
It includes features such as UTF-8 nicks and channel names, client accounts and SASL, and other
assorted IRCv3 support.
* Being simple to set up and use
* Combining the features of an ircd, a services framework, and a bouncer:
* Integrated account management
* History storage
* Bouncer functionality
* Bleeding-edge IRCv3 support
* High customizability via a rehashable (i.e., reloadable at runtime) YAML config
https://oragono.io/
https://github.com/oragono/oragono
https://ergo.chat/
https://github.com/ergochat/ergo
#ergo on irc.ergo.chat or irc.libera.chat
-----------------------------------------------------------------------------------------------
@ -25,33 +29,30 @@ Copy the example config file to ircd.yaml with a command like:
$ cp default.yaml ircd.yaml
Modify the config file as you like.
Modify the config file as needed (the recommendations at the top may be helpful).
To generate passwords for opers and connect passwords, you can use this command:
$ oragono genpasswd
$ ./ergo genpasswd
Run these commands in order -- these will setup each section of the server:
If you need to generate self-signed TLS certificates, use this command:
$ oragono mkcerts
$ oragono run
$ ./ergo mkcerts
And you should now be running Oragono!
You are now ready to start Ergo!
$ ./ergo run
For further instructions, consult the manual. A copy of the manual should be
included in your release under `docs/MANUAL.md`. Or you can view it on the
Web: https://ergo.chat/manual.html
=== Updating ===
If you're updating from a previous version of Oragono, checkout the CHANGELOG for a shortlist
If you're updating from a previous version of Ergo, check out the CHANGELOG for a list
of important changes you'll want to take a look at. The change log details config changes,
fixes, new features and anything else you'll want to be aware of!
If there's been a database update, you can run this command to upgrade it manually:
$ oragono upgradedb
Otherwise, just starting the server will run an automagic backup and upgrade.
=== Credits ===
* Jeremy Latt (2012-2014)

View File

@ -1,23 +1,22 @@
![Oragono logo](docs/logo.png)
![Ergo logo](docs/logo.png)
Oragono is a modern IRC server written in Go. Its core design principles are:
Ergo (formerly known as Oragono) is a modern IRC server written in Go. Its core design principles are:
* Being simple to set up and use
* Combining the features of an ircd, a services framework, and a bouncer (integrated account management, history storage, and bouncer functionality)
* Bleeding-edge [IRCv3 support](https://ircv3.net/software/servers.html), suitable for use as an IRCv3 reference implementation
* Highly customizable via a rehashable (i.e., reloadable at runtime) YAML config
* High customizability via a rehashable (i.e., reloadable at runtime) YAML config
Oragono is a fork of the [Ergonomadic](https://github.com/jlatt/ergonomadic) IRC daemon <3
Ergo is a fork of the [Ergonomadic](https://github.com/jlatt/ergonomadic) IRC daemon <3
---
[![Go Report Card](https://goreportcard.com/badge/github.com/oragono/oragono)](https://goreportcard.com/report/github.com/oragono/oragono)
[![Build Status](https://travis-ci.com/oragono/oragono.svg?branch=master)](https://travis-ci.com/oragono/oragono)
[![Download Latest Release](https://img.shields.io/badge/downloads-latest%20release-green.svg)](https://github.com/oragono/oragono/releases/latest)
[![Freenode #oragono](https://img.shields.io/badge/Freenode-%23oragono-1e72ff.svg?style=flat)](https://www.irccloud.com/invite?channel=%23oragono&hostname=irc.freenode.net&port=6697&ssl=1)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/oragono/localized.svg)](https://crowdin.com/project/oragono)
[![Go Report Card](https://goreportcard.com/badge/github.com/ergochat/ergo)](https://goreportcard.com/report/github.com/ergochat/ergo)
[![build](https://github.com/ergochat/ergo/actions/workflows/build.yml/badge.svg)](https://github.com/ergochat/ergo/actions/workflows/build.yml)
[![Download Latest Release](https://img.shields.io/badge/downloads-latest%20release-green.svg)](https://github.com/ergochat/ergo/releases/latest)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/ergochat/localized.svg)](https://crowdin.com/project/ergochat)
If you want to take a look at a running Oragono instance or test some client code, feel free to play with [testnet.oragono.io](https://testnet.oragono.io/) (TLS on port 6697 or plaintext on port 6667).
If you want to take a look at a running Ergo instance or test some client code, feel free to play with [testnet.ergo.chat](https://testnet.ergo.chat/) (TLS on port 6697 or plaintext on port 6667).
---
@ -26,68 +25,72 @@ If you want to take a look at a running Oragono instance or test some client cod
* integrated services: NickServ for user accounts, ChanServ for channel registration, and HostServ for vanity hosts
* bouncer-like features: storing and replaying history, allowing multiple clients to use the same nickname
* UTF-8 nick and channel names with rfc7613 (PRECIS)
* native TLS/SSL support, including support for client certificates
* [IRCv3 support](https://ircv3.net/software/servers.html)
* [yaml](https://yaml.org/) configuration
* updating server config and TLS certificates on-the-fly (rehashing)
* SASL authentication
* LDAP support
* supports [multiple languages](https://crowdin.com/project/oragono) (you can also set a default language for your network)
* [LDAP support](https://github.com/ergochat/ergo-ldap)
* supports [multiple languages](https://crowdin.com/project/ergochat) (you can also set a default language for your network)
* optional support for UTF-8 nick and channel names with RFC 8265 (PRECIS)
* advanced security and privacy features (support for requiring SASL for all logins, cloaking IPs, and running as a Tor hidden service)
* an extensible privilege system for IRC operators
* ident lookups for usernames
* automated client connection limits
* passwords stored with [bcrypt](https://godoc.org/golang.org/x/crypto)
* banning ips/nets and masks with `KLINE` and `DLINE`
* [IRCv3 support](https://ircv3.net/software/servers.html)
* a heavy focus on developing with [specifications](https://oragono.io/specs.html)
* `UBAN`, a unified ban system that can target IPs, networks, masks, and registered accounts (`KLINE` and `DLINE` are also supported)
* a focus on developing with [specifications](https://ergo.chat/specs.html)
For more detailed information on Ergo's functionality, see:
* [MANUAL.md, the operator manual](https://github.com/ergochat/ergo/blob/stable/docs/MANUAL.md)
* [USERGUIDE.md, the guide for end users](https://github.com/ergochat/ergo/blob/stable/docs/USERGUIDE.md)
## Quick start guide
Download the latest release from this page: https://github.com/oragono/oragono/releases/latest
Download the latest release from this page: https://github.com/ergochat/ergo/releases/latest
Extract it into a folder, then run the following commands:
```sh
cp default.yaml ircd.yaml
vim ircd.yaml # modify the config file to your liking
oragono mkcerts
oragono run # server should be ready to go!
vim ircd.yaml # modify the config file to your liking
./ergo mkcerts
./ergo run # server should be ready to go!
```
**Note:** See the [productionizing guide in our manual](https://github.com/oragono/oragono/blob/master/docs/MANUAL.md#productionizing) for recommendations on how to run a production network, including obtaining valid TLS certificates.
**Note:** See the [productionizing guide in our manual](https://github.com/ergochat/ergo/blob/stable/docs/MANUAL.md#productionizing-with-systemd) for recommendations on how to run a production network, including obtaining valid TLS certificates.
### Platform Packages
Some platforms/distros also have Oragono packages maintained for them:
Some platforms/distros also have Ergo packages maintained for them:
* Arch Linux [AUR](https://aur.archlinux.org/packages/oragono/) - Maintained by [Sean Enck (@enckse)](https://github.com/enckse).
* Arch Linux [AUR](https://aur.archlinux.org/packages/ergochat/) - Maintained by [Jason Papakostas (@vith)](https://github.com/vith).
* [Gentoo Linux](https://packages.gentoo.org/packages/net-irc/ergo) - Maintained by [Sam James (@thesamesam)](https://github.com/thesamesam).
### Using Docker
A Dockerfile and example docker-compose recipe are available in the `distrib/docker` directory. Oragono is automatically published
to Docker Hub at [oragono/oragono](https://hub.docker.com/r/oragono/oragono). For more information, see the distrib/docker
[README file](https://github.com/oragono/oragono/blob/master/distrib/docker/README.md).
A Dockerfile and example docker-compose recipe are available in the `distrib/docker` directory. Ergo is automatically published
to the GitHub Container Registry at [ghcr.io/ergochat/ergo](https://ghcr.io/ergochat/ergo). For more information, see the distrib/docker
[README file](https://github.com/ergochat/ergo/blob/master/distrib/docker/README.md).
### From Source
You can also install this repo and use that instead! However, keep some things in mind if you go that way:
You can also clone this repository and build from source. Typical deployments should use the `stable` branch, which points to the latest stable release. In general, `stable` should coincide with the latest published tag that is not designated as a beta or release candidate (for example, `v2.7.0-rc1` was an unstable release candidate and `v2.7.0` was the corresponding stable release), so you can also identify the latest stable release tag on the [releases page](https://github.com/ergochat/ergo/releases) and build that.
`devel` branches are intentionally unstable, containing fixes that may not work, and they may be rebased or reworked extensively.
The `master` branch is not recommended for production use since it may contain bugs, and because the forwards compatibility guarantees for the config file and the database that apply to releases do not apply to master. That is to say, running master may result in changes to your database that end up being incompatible with future versions of Ergo.
The `master` branch _should_ usually be stable, but may contain database changes that either have not been finalised or not had database upgrade code written yet. Don't run `master` on a live production network.
The `stable` branch contains the latest release. You can run this for a production version without any trouble.
For information on contributing to Ergo, see [DEVELOPING.md](https://github.com/ergochat/ergo/blob/master/DEVELOPING.md).
#### Building
You'll need an [up-to-date distribution of the Go language for your OS and architecture](https://golang.org/dl/). Once you have that, just clone the repository and run `make build`. If everything goes well, you should now have an executable named `oragono` in the base directory of the project.
You'll need an [up-to-date distribution of the Go language for your OS and architecture](https://golang.org/dl/). Once that's installed (check the output of `go version`), just check out your desired branch or tag and run `make`. This will produce an executable binary named `ergo` in the base directory of the project. (Ergo vendors all its dependencies, so you will not need to fetch any dependencies remotely.)
## Configuration
The default config file [`default.yaml`](default.yaml) helps walk you through what each option means and changes.
You can use the `--conf` parameter when launching Oragono to control where it looks for the config file. For instance: `oragono run --conf /path/to/ircd.yaml`. The configuration file also stores where the log, database, certificate, and other files are opened. Normally, all these files use relative paths, but you can change them to be absolute (such as `/var/log/ircd.log`) when running Oragono as a service.
You can use the `--conf` parameter when launching Ergo to control where it looks for the config file. For instance: `ergo run --conf /path/to/ircd.yaml`. The configuration file also stores where the log, database, certificate, and other files are opened. Normally, all these files use relative paths, but you can change them to be absolute (such as `/var/log/ircd.log`) when running Ergo as a service.
### Logs
@ -98,21 +101,22 @@ By default, logs go to stderr only. They can be configured to go to a file, or y
Passwords (for both `PASS` and oper logins) are stored using bcrypt. To generate encrypted strings for use in the config, use the `genpasswd` subcommand as such:
```sh
oragono genpasswd
ergo genpasswd
```
With this, you receive a blob of text which you can plug into your configuration file.
### How to register a channel
### Nickname and channel registration
1. Register your account with `/NS REGISTER <password>`
2. Join the channel with `/join #channel`
3. Register the channel with `/CS REGISTER #channel`
Ergo relies heavily on user accounts to enable its distinctive features (such as allowing multiple clients per nickname). As a user, you can register your current nickname as an account using `/msg NickServ register <password>`. Once you have done so, you should [enable SASL in your clients](https://libera.chat/guides/sasl), ensuring that you will be automatically logged into your account on each connection. This will prevent [problems claiming your registered nickname](https://github.com/ergochat/ergo/blob/master/docs/MANUAL.md#nick-equals-account).
Once you have registered your nickname, you can use it to register channels:
1. Join the channel with `/join #channel`
2. Register the channel with `/CS REGISTER #channel`
After this, your channel will remember the fact that you're the owner, the topic, and any modes set on it!
Make sure to setup [SASL](https://freenode.net/kb/answer/sasl) in your client to automatically login to your account when you next join the server.
# Credits
@ -120,4 +124,4 @@ Make sure to setup [SASL](https://freenode.net/kb/answer/sasl) in your client to
* Edmund Huber (2014-2015)
* Daniel Oaks (2016-present)
* Shivaram Lingamneni (2017-present)
* [Many other contributors and friends of the project <3](https://github.com/oragono/oragono/blob/master/CHANGELOG.md)
* [Many other contributors and friends of the project <3](https://github.com/ergochat/ergo/blob/master/CHANGELOG.md)

View File

@ -1,10 +1,10 @@
# This is the default config file for Oragono.
# This is the default config file for Ergo.
# It contains recommended defaults for all settings, including some behaviors
# that differ from conventional ircds. See conventional.yaml for a config
# with more "mainstream" behavior.
# that differ from conventional ircd+services setups. See traditional.yaml
# for a config with more "mainstream" behavior.
#
# If you are setting up a new oragono server, you should copy this file
# to a new one named 'ircd.yaml', then read the whole file to see which
# If you are setting up a new Ergo server, you should copy this file
# to a new one named 'ircd.yaml', then look through the file to see which
# settings you want to customize. If you don't understand a setting, or
# aren't sure what behavior you want, most of the defaults are fine
# to start with (you can change them later, even on a running server).
@ -25,12 +25,12 @@
# network configuration
network:
# name of the network
name: OragonoTest
name: ErgoTest
# server configuration
server:
# server name
name: oragono.test
name: ergo.test
# addresses to listen on
listeners:
@ -49,21 +49,25 @@ server:
# The standard SSL/TLS port for IRC is 6697. This will listen on all interfaces:
":6697":
# this is a standard TLS configuration with a single certificate;
# see the manual for instructions on how to configure SNI
tls:
cert: fullchain.pem
key: privkey.pem
# 'proxy' should typically be false. It's only for Kubernetes-style load
# balancing that does not terminate TLS, but sends an initial PROXY line
# in plaintext.
proxy: false
# 'proxy' should typically be false. It's for cloud load balancers that
# always send a PROXY protocol header ahead of the connection. See the
# manual ("Reverse proxies") for more details.
proxy: false
# set the minimum TLS version:
min-tls-version: 1.2
# Example of a Unix domain socket for proxying:
# "/tmp/oragono_sock":
# "/tmp/ergo_sock":
# Example of a Tor listener: any connection that comes in on this listener will
# be considered a Tor connection. It is strongly recommended that this listener
# *not* be on a public interface --- it should be on 127.0.0.0/8 or unix domain:
# "/hidden_service_sockets/oragono_tor_sock":
# "/hidden_service_sockets/ergo_tor_sock":
# tor: true
# Example of a WebSocket listener:
@ -96,6 +100,7 @@ server:
max-connections-per-duration: 64
# strict transport security, to get clients to automagically use TLS
# (irrelevant in the recommended configuration, with no public plaintext listener)
sts:
# whether to advertise STS
#
@ -116,29 +121,31 @@ server:
websockets:
# Restrict the origin of WebSocket connections by matching the "Origin" HTTP
# header. This settings makes oragono reject every WebSocket connection,
# except when it originates from one of the hosts in this list. Use this to
# prevent malicious websites from making their visitors connect to oragono
# without their knowledge. An empty list means that there are no restrictions.
# header. This setting causes ergo to reject websocket connections unless
# they originate from a page on one of the whitelisted websites in this list.
# This prevents malicious websites from making their visitors connect to your
# ergo instance without their knowledge. An empty list means there are no
# restrictions.
allowed-origins:
# - "https://oragono.io"
# - "https://*.oragono.io"
# - "https://ergo.chat"
# - "https://*.ergo.chat"
# casemapping controls what kinds of strings are permitted as identifiers (nicknames,
# channel names, account names, etc.), and how they are normalized for case.
# with the recommended default of 'precis', UTF8 identifiers that are "sane"
# (according to RFC 8265) are allowed, and the server additionally tries to protect
# against confusable characters ("homoglyph attacks").
# the other options are 'ascii' (traditional ASCII-only identifiers), and 'permissive',
# which allows identifiers to contain unusual characters like emoji, but makes users
# vulnerable to homoglyph attacks. unless you're really confident in your decision,
# we recommend leaving this value at its default (changing it once the network is
# already up and running is problematic).
casemapping: "precis"
# the recommended default is 'ascii' (traditional ASCII-only identifiers).
# the other options are 'precis', which allows UTF8 identifiers that are "sane"
# (according to UFC 8265), with additional mitigations for homoglyph attacks,
# 'permissive', which allows identifiers containing unusual characters like
# emoji, at the cost of increased vulnerability to homoglyph attacks and potential
# client compatibility problems, and the legacy mappings 'rfc1459' and
# 'rfc1459-strict'. we recommend leaving this value at its default;
# however, note that changing it once the network is already up and running is
# problematic.
casemapping: "ascii"
# enforce-utf8 controls whether the server allows non-UTF8 bytes in messages
# (as in traditional IRC) or preemptively discards non-UTF8 messages (since
# they cannot be relayed to websocket clients).
# enforce-utf8 controls whether the server will preemptively discard non-UTF8
# messages (since they cannot be relayed to websocket clients), or will allow
# them and relay them to non-websocket clients (as in traditional IRC).
enforce-utf8: true
# whether to look up user hostnames with reverse DNS. there are 3 possibilities:
@ -154,21 +161,41 @@ server:
# use ident protocol to get usernames
check-ident: false
# password to login to the server
# generated using "oragono genpasswd"
#password: ""
# ignore the supplied user/ident string from the USER command, always setting user/ident
# to the following literal value; this can potentially reduce confusion and simplify bans.
# the value must begin with a '~' character. comment out / omit to disable:
coerce-ident: '~u'
# 'password' allows you to require a global, shared password (the IRC `PASS` command)
# to connect to the server. for operator passwords, see the `opers` section of the
# config. for a more secure way to create a private server, see the `require-sasl`
# section. you must hash the password with `ergo genpasswd`, then enter the hash here:
#password: "$2a$04$0123456789abcdef0123456789abcdef0123456789abcdef01234"
# motd filename
# if you change the motd, you should move it to ircd.motd
motd: oragono.motd
motd: ergo.motd
# motd formatting codes
# if this is true, the motd is escaped using formatting codes like $c, $b, and $i
motd-formatting: true
# addresses/CIDRs the PROXY command can be used from
# this should be restricted to localhost (127.0.0.1/8, ::1/128, and unix sockets),
# unless you have a good reason. you should also add these addresses to the
# relaying using the RELAYMSG command
relaymsg:
# is relaymsg enabled at all?
enabled: true
# which character(s) are reserved for relayed nicks?
separators: "/"
# can channel operators use RELAYMSG in their channels?
# our implementation of RELAYMSG makes it safe for chanops to use without the
# possibility of real users being silently spoofed
available-to-chanops: true
# IPs/CIDRs the PROXY command can be used from
# This should be restricted to localhost (127.0.0.1/8, ::1/128, and unix sockets).
# Unless you have a good reason. you should also add these addresses to the
# connection limits and throttling exemption lists.
proxy-allowed-from:
- localhost
@ -183,19 +210,19 @@ server:
# (comment this out to use passwords only)
certfp: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
# password the gateway uses to connect, made with oragono genpasswd
# password the gateway uses to connect, made with `ergo genpasswd`
password: "$2a$04$abcdef0123456789abcdef0123456789abcdef0123456789abcde"
# addresses/CIDRs that can use this webirc command
# IPs/CIDRs that can use this webirc command
# you should also add these addresses to the connection limits and throttling exemption lists
hosts:
- localhost
# - "192.168.1.1"
# - "192.168.10.1/24"
# allow use of the RESUME extension over plaintext connections:
# do not enable this unless the ircd is only accessible over internal networks
allow-plaintext-resume: false
# whether to accept the hostname parameter on the WEBIRC line as the IRC hostname
# (the default/recommended Ergo configuration will use cloaks instead)
accept-hostname: false
# maximum length of clients' sendQ in bytes
# this should be big enough to hold bursts of channel/direct messages
@ -205,7 +232,7 @@ server:
compatibility:
# many clients require that the final parameter of certain messages be an
# RFC1459 trailing parameter, i.e., prefixed with :, whether or not this is
# actually required. this forces Oragono to send those parameters
# actually required. this forces Ergo to send those parameters
# as trailings. this is recommended unless you're testing clients for conformance;
# defaults to true when unset for that reason.
force-trailing: true
@ -216,6 +243,13 @@ server:
# this works around that bug, allowing them to use SASL.
send-unprefixed-sasl: true
# traditionally, IRC servers will truncate and send messages that are
# too long to be relayed intact. this behavior can be disabled by setting
# allow-truncation to false, in which case Ergo will reject the message
# and return an error to the client. (note that this option defaults to true
# when unset.)
allow-truncation: false
# IP-based DoS protection
ip-limits:
# whether to limit the total number of concurrent connections per IP/CIDR
@ -229,9 +263,6 @@ server:
window: 10m
# maximum number of new connections per IP/CIDR within the given duration
max-connections-per-window: 32
# how long to ban offenders for. after banning them, the number of connections is
# reset, which lets you use /UNDLINE to unban people
throttle-ban-duration: 10m
# how wide the CIDR should be for IPv4 (a /32 is a fully specified IPv4 address)
cidr-len-ipv4: 32
@ -245,18 +276,47 @@ server:
# - "192.168.1.1"
# - "2001:0db8::/32"
# custom connection limits for certain IPs/networks. note that CIDR
# widths defined here override the default CIDR width --- the limit
# will apply to the entire CIDR no matter how large or small it is
# custom connection limits for certain IPs/networks.
custom-limits:
# "8.8.0.0/16":
# max-concurrent-connections: 128
# max-connections-per-window: 1024
#"irccloud":
# nets:
# - "192.184.9.108" # highgate.irccloud.com
# - "192.184.9.110" # ealing.irccloud.com
# - "192.184.9.112" # charlton.irccloud.com
# - "192.184.10.118" # brockwell.irccloud.com
# - "192.184.10.9" # tooting.irccloud.com
# - "192.184.8.73" # hathersage.irccloud.com
# - "192.184.8.103" # stonehaven.irccloud.com
# - "5.254.36.57" # tinside.irccloud.com
# - "5.254.36.56/29" # additional ipv4 net
# - "2001:67c:2f08::/48"
# - "2a03:5180:f::/64"
# max-concurrent-connections: 2048
# max-connections-per-window: 2048
# pluggable IP ban mechanism, via subprocess invocation
# this can be used to check new connections against a DNSBL, for example
# see the manual for details on how to write an IP ban checking script
ip-check-script:
enabled: false
command: "/usr/local/bin/check-ip-ban"
# constant list of args to pass to the command; the actual query
# and result are transmitted over stdin/stdout:
args: []
# timeout for process execution, after which we send a SIGTERM:
timeout: 9s
# how long after the SIGTERM before we follow up with a SIGKILL:
kill-timeout: 1s
# how many scripts are allowed to run at once? 0 for no limit:
max-concurrency: 64
# if true, only check anonymous connections (not logged into an account)
# at the very end of the handshake:
exempt-sasl: false
# IP cloaking hides users' IP addresses from other users and from channel admins
# (but not from server admins), while still allowing channel admins to ban
# offending IP addresses or networks. In place of hostnames derived from reverse
# DNS, users see fake domain names like pwbs2ui4377257x8.oragono. These names are
# DNS, users see fake domain names like pwbs2ui4377257x8.irc. These names are
# generated deterministically from the underlying IP address, but if the underlying
# IP is not already known, it is infeasible to recover it from the cloaked name.
# If you disable this, you should probably enable lookup-hostnames in its place.
@ -264,6 +324,12 @@ server:
# whether to enable IP cloaking
enabled: true
# whether to use these cloak settings (specifically, `netname` and `num-bits`)
# to produce unique hostnames for always-on clients. you can enable this even if
# you disabled IP cloaking for normal clients above. if this is disabled,
# always-on clients will all have an identical hostname (the server name).
enabled-for-always-on: true
# fake TLD at the end of the hostname, e.g., pwbs2ui4377257x8.irc
# you may want to use your network name here
netname: "irc"
@ -292,10 +358,34 @@ server:
secure-nets:
# - "10.0.0.0/8"
# oragono will write files to disk under certain circumstances, e.g.,
# Ergo will write files to disk under certain circumstances, e.g.,
# CPU profiling or data export. by default, these files will be written
# to the working directory. set this to customize:
# output-path: "/home/oragono/out"
#output-path: "/home/ergo/out"
# the hostname used by "services", e.g., NickServ, defaults to "localhost",
# e.g., `NickServ!NickServ@localhost`. uncomment this to override:
#override-services-hostname: "example.network"
# in a "closed-loop" system where you control the server and all the clients,
# you may want to increase the maximum (non-tag) length of an IRC line from
# the default value of 512. DO NOT change this on a public server:
#max-line-len: 512
# send all 0's as the LUSERS (user counts) output to non-operators; potentially useful
# if you don't want to publicize how popular the server is
suppress-lusers: false
# publish additional key-value pairs in ISUPPORT (the 005 numeric).
# keys that collide with a key published by Ergo will be silently ignored.
additional-isupport:
#"draft/FILEHOST": "https://example.com/filehost"
#"draft/bazbat": "" # empty string means no value
# optionally map command alias names to existing ergo commands. most deployments
# should ignore this.
#command-aliases:
#"UMGEBUNG": "AMBIANCE"
# account options
accounts:
@ -308,6 +398,9 @@ accounts:
# the `accreg` capability can still create accounts with `/NICKSERV SAREGISTER`
enabled: true
# can users use the REGISTER command to register before fully connecting?
allow-before-connect: true
# global throttle on new account creation
throttling:
enabled: true
@ -317,33 +410,51 @@ accounts:
max-attempts: 30
# this is the bcrypt cost we'll use for account passwords
bcrypt-cost: 9
# (note that 4 is the lowest value allowed by the bcrypt library)
bcrypt-cost: 4
# length of time a user has to verify their account before it can be re-registered
verify-timeout: "32h"
# callbacks to allow
enabled-callbacks:
- none # no verification needed, will instantly register successfully
# example configuration for sending verification emails
# callbacks:
# mailto:
# sender: "admin@my.network"
# require-tls: true
# helo-domain: "my.network" # defaults to server name if unset
# dkim:
# domain: "my.network"
# selector: "20200229"
# key-file: "dkim.pem"
# # to use an MTA/smarthost instead of sending email directly:
# # mta:
# # server: localhost
# # port: 25
# # username: "admin"
# # password: "hunter2"
# blacklist-regexes:
# # - ".*@mailinator.com"
# options for email verification of account registrations
email-verification:
enabled: false
sender: "admin@my.network"
require-tls: true
helo-domain: "my.network" # defaults to server name if unset
# set to `tcp4` to force sending over IPv4, `tcp6` to force IPv6:
# protocol: "tcp4"
# set to force a specific source/local IPv4 or IPv6 address:
# local-address: "1.2.3.4"
# options to enable DKIM signing of outgoing emails (recommended, but
# requires creating a DNS entry for the public key):
# dkim:
# domain: "my.network"
# selector: "20200229"
# key-file: "dkim.pem"
# to use an MTA/smarthost instead of sending email directly:
# mta:
# server: localhost
# port: 25
# username: "admin"
# password: "hunter2"
# implicit-tls: false # TLS from the first byte, typically on port 465
# addresses that are not accepted for registration:
address-blacklist:
# - "*@mailinator.com"
address-blacklist-syntax: "glob" # change to "regex" for regular expressions
# file of newline-delimited address blacklist entries (no enclosing quotes)
# in the above syntax (i.e. either globs or regexes). supersedes
# address-blacklist if set:
# address-blacklist-file: "/path/to/address-blacklist-file"
timeout: 60s
# email-based password reset:
password-reset:
enabled: false
# time before we allow resending the email
cooldown: 1h
# time for which a password reset code is valid
timeout: 1d
# throttle account login attempts (to prevent either password guessing, or DoS
# attacks on the server aimed at forcing repeated expensive bcrypt computations)
@ -367,10 +478,17 @@ accounts:
# this is useful for compatibility with old clients that don't support SASL
login-via-pass-command: true
# advertise the SCRAM-SHA-256 authentication method. set to false in case of
# compatibility issues with certain clients:
advertise-scram: true
# require-sasl controls whether clients are required to have accounts
# (and sign into them using SASL) to connect to the server
require-sasl:
# if this is enabled, all clients must authenticate with SASL while connecting
# if this is enabled, all clients must authenticate with SASL while connecting.
# WARNING: for a private server, you MUST set accounts.registration.enabled
# to false as well, in order to prevent non-administrators from registering
# accounts.
enabled: false
# IPs/CIDRs which are exempted from the account requirement
@ -384,32 +502,27 @@ accounts:
enabled: true
# how many nicknames, in addition to the account name, can be reserved?
additional-nick-limit: 2
# (note that additional nicks are unusable under force-nick-equals-account
# or if the client is always-on)
additional-nick-limit: 0
# method describes how nickname reservation is handled
# timeout: let the user change to the registered nickname, give them X seconds
# to login and then rename them if they haven't done so
# strict: don't let the user change to the registered nickname unless they're
# already logged-in using SASL or NickServ
# strict: users must already be logged in to their account (via
# SASL, PASS account:password, or /NickServ IDENTIFY)
# in order to use their reserved nickname(s)
# optional: no enforcement by default, but allow users to opt in to
# the enforcement level of their choice
#
# 'optional' matches the behavior of other NickServs, but 'strict' is
# preferable if all your users can enable SASL.
method: strict
# allow users to set their own nickname enforcement status, e.g.,
# to opt out of strict enforcement
allow-custom-enforcement: false
# rename-timeout - this is how long users have 'til they're renamed
rename-timeout: 30s
# format for guest nicknames:
# 1. these nicknames cannot be registered or reserved
# 2. if a client is automatically renamed by the server,
# this is the template that will be used (e.g., Guest-nccj6rgmt97cg)
# 3. if enforce-guest-format (see below) is enabled, clients without
# 3. if force-guest-format (see below) is enabled, clients without
# a registered account will have this template applied to their
# nicknames (e.g., 'katie' will become 'Guest-katie')
guest-nickname-format: "Guest-*"
@ -426,7 +539,12 @@ accounts:
# as equivalent for the purpose of ban/invite/exception lists.
force-nick-equals-account: true
# multiclient controls whether oragono allows multiple connections to
# parallel setting to force-nick-equals-account: if true, this forbids
# anonymous users (i.e., users not logged into an account) to change their
# nickname after the initial connection is complete
forbid-anonymous-nick-changes: false
# multiclient controls whether Ergo allows multiple connections to
# attach to the same client/nickname identity; this is part of the
# functionality traditionally provided by a bouncer like ZNC
multiclient:
@ -449,6 +567,10 @@ accounts:
# whether to mark always-on clients away when they have no active connections:
auto-away: "opt-in"
# QUIT always-on clients from the server if they go this long without connecting
# (use 0 or omit for no expiration):
#always-on-expiration: 90d
# vhosts controls the assignment of vhosts (strings displayed in place of the user's
# hostname/IP) by the HostServ service
vhosts:
@ -462,23 +584,6 @@ accounts:
# (make sure any changes you make here are RFC-compliant)
valid-regexp: '^[0-9A-Za-z.\-_/]+$'
# options controlling users requesting vhosts:
user-requests:
# can users request vhosts at all? if this is false, operators with the
# 'vhosts' capability can still assign vhosts manually
enabled: false
# if uncommented, all new vhost requests will be dumped into the given
# channel, so opers can review them as they are sent in. ensure that you
# have registered and restricted the channel appropriately before you
# uncomment this.
#channel: "#vhosts"
# after a user's vhost has been approved or rejected, they need to wait
# this long (starting from the time of their original request)
# before they can request a new one.
cooldown: 168h
# modes that are set by default when a user connects
# if unset, no user modes will be set by default
# +i is invisible (a user's channels are hidden from whois replies)
@ -499,13 +604,50 @@ accounts:
timeout: 9s
# how long after the SIGTERM before we follow up with a SIGKILL:
kill-timeout: 1s
# how many scripts are allowed to run at once? 0 for no limit:
max-concurrency: 64
# support for login via OAuth2 bearer tokens
oauth2:
enabled: false
# should we automatically create users on presentation of a valid token?
autocreate: true
# enable this to use auth-script for validation:
auth-script: false
introspection-url: "https://example.com/api/oidc/introspection"
introspection-timeout: 10s
# omit for auth method `none`; required for auth method `client_secret_basic`:
client-id: "ergo"
client-secret: "4TA0I7mJ3fUUcW05KJiODg"
# support for login via JWT bearer tokens
jwt-auth:
enabled: false
# should we automatically create users on presentation of a valid token?
autocreate: true
# any of these token definitions can be accepted, allowing for key rotation
tokens:
-
algorithm: "hmac" # either 'hmac', 'rsa', or 'eddsa' (ed25519)
# hmac takes a symmetric key, rsa and eddsa take PEM-encoded public keys;
# either way, the key can be specified either as a YAML string:
key: "nANiZ1De4v6WnltCHN2H7Q"
# or as a path to the file containing the key:
#key-file: "jwt_pubkey.pem"
# list of JWT claim names to search for the user's account name (make sure the format
# is what you expect, especially if using "sub"):
account-claims: ["preferred_username"]
# if a claim is formatted as an email address, require it to have the following domain,
# and then strip off the domain and use the local-part as the account name:
#strip-domain: "example.com"
# channel options
channels:
# modes that are set when new channels are created
# +n is no-external-messages and +t is op-only-topic
# +n is no-external-messages, +t is op-only-topic,
# +C is no CTCPs (besides ACTION)
# see /QUOTE HELP cmodes for more channel modes
default-modes: +nt
default-modes: +ntC
# how many channels can a client be in at once?
max-channels-per-client: 100
@ -530,75 +672,86 @@ channels:
# than this value will get an empty response to /LIST (a time period of 0 disables)
list-delay: 0s
# operator classes
# INVITE to an invite-only channel expires after this amount of time
# (0 or omit for no expiration):
invite-expiration: 24h
# channels that new clients will automatically join. this should be used with
# caution, since traditional IRC users will likely view it as an antifeature.
# it may be useful in small community networks that have a single "primary" channel:
#auto-join:
# - "#lounge"
# operator classes:
# an operator has a single "class" (defining a privilege level), which can include
# multiple "capabilities" (defining privileged actions they can take). all
# currently available operator capabilities are associated with either the
# 'chat-moderator' class (less privileged) or the 'server-admin' class (full
# privileges) below: you can mix and match to create new classes.
oper-classes:
# local operator
"local-oper":
# chat moderator: can ban/unban users from the server, join channels,
# fix mode issues and sort out vhosts.
"chat-moderator":
# title shown in WHOIS
title: Local Operator
title: Chat Moderator
# capability names
capabilities:
- "local_kill"
- "local_ban"
- "local_unban"
- "nofakelag"
- "roleplay"
- "kill" # disconnect user sessions
- "ban" # ban IPs, CIDRs, NUH masks, and suspend accounts (UBAN / DLINE / KLINE)
- "nofakelag" # exempted from "fakelag" restrictions on rate of message sending
- "relaymsg" # use RELAYMSG in any channel (see the `relaymsg` config block)
- "vhosts" # add and remove vhosts from users
- "sajoin" # join arbitrary channels, including private channels
- "samode" # modify arbitrary channel and user modes
- "snomasks" # subscribe to arbitrary server notice masks
- "roleplay" # use the (deprecated) roleplay commands in any channel
# network operator
"network-oper":
# title shown in WHOIS
title: Network Operator
# oper class this extends from
extends: "local-oper"
# capability names
capabilities:
- "remote_kill"
- "remote_ban"
- "remote_unban"
# server admin
# server admin: has full control of the ircd, including nickname and
# channel registrations
"server-admin":
# title shown in WHOIS
title: Server Admin
# oper class this extends from
extends: "local-oper"
extends: "chat-moderator"
# capability names
capabilities:
- "rehash"
- "die"
- "accreg"
- "sajoin"
- "samode"
- "vhosts"
- "chanreg"
- "history"
- "defcon"
- "rehash" # rehash the server, i.e. reload the config at runtime
- "accreg" # modify arbitrary account registrations
- "chanreg" # modify arbitrary channel registrations
- "history" # modify or delete history messages
- "defcon" # use the DEFCON command (restrict server capabilities)
- "massmessage" # message all users on the server
- "metadata" # modify arbitrary metadata on channels and users
# ircd operators
opers:
# operator named 'admin'; log in with /OPER admin [password]
# default operator named 'admin'; log in with /OPER admin <password>
admin:
# which capabilities this oper has access to
class: "server-admin"
# custom whois line
whois-line: is a cool dude
# traditionally, operator status is visible to unprivileged users in
# WHO and WHOIS responses. this can be disabled with 'hidden'.
hidden: true
# custom hostname
vhost: "n"
# custom whois line (if `hidden` is enabled, visible only to other operators)
whois-line: is the server administrator
# modes are the modes to auto-set upon opering-up
modes: +is acjknoqtuxv
# custom hostname (ignored if `hidden` is enabled)
#vhost: "staff"
# modes are modes to auto-set upon opering-up. uncomment this to automatically
# enable snomasks ("server notification masks" that alert you to server events;
# see `/quote help snomasks` while opered-up for more information):
#modes: +is acdjknoqtuxv
# operators can be authenticated either by password (with the /OPER command),
# or by certificate fingerprint, or both. if a password hash is set, then a
# password is required to oper up (e.g., /OPER dan mypassword). to generate
# the hash, use `oragono genpasswd`.
# the hash, use `ergo genpasswd`.
password: "$2a$04$0123456789abcdef0123456789abcdef0123456789abcdef01234"
# if a SHA-256 certificate fingerprint is configured here, then it will be
@ -609,6 +762,13 @@ opers:
# granted automatically as soon as you connect with the right fingerprint.
#auto: true
# example of a moderator named 'alice'
# (log in with /OPER alice <password>):
#alice:
# class: "chat-moderator"
# whois-line: "can help with moderation issues!"
# password: "$2a$04$0123456789abcdef0123456789abcdef0123456789abcdef01234"
# logging, takes inspiration from Insp
logging:
-
@ -629,11 +789,10 @@ logging:
# be logged, even if you explicitly include it
#
# useful types include:
# * everything (usually used with exclusing some types below)
# * everything (usually used with excluding some types below)
# server server startup, rehash, and shutdown events
# accounts account registration and authentication
# channels channel creation and operations
# commands command calling and operations
# opers oper actions, authentication, etc
# services actions related to NickServ, ChanServ, etc.
# internal unexpected runtime behavior, including potential bugs
@ -652,7 +811,7 @@ logging:
# debug options
debug:
# when enabled, oragono will attempt to recover from certain kinds of
# when enabled, Ergo will attempt to recover from certain kinds of
# client-triggered runtime errors that would normally crash the server.
# this makes the server more resilient to DoS, but could result in incorrect
# behavior. deployments that would prefer to "start from scratch", e.g., by
@ -666,9 +825,15 @@ debug:
# set to `null`, "", leave blank, or omit to disable
# pprof-listener: "localhost:6060"
# lock file preventing multiple instances of Ergo from accidentally being
# started at once. comment out or set to the empty string ("") to disable.
# this path is relative to the working directory; if your datastore.path
# is absolute, you should use an absolute path here as well.
lock-file: "ircd.lock"
# datastore configuration
datastore:
# path to the datastore
# path to the database file (used to store account and channel registrations):
path: ircd.db
# if the database schema requires an upgrade, `autoupgrade` will attempt to
@ -683,10 +848,13 @@ datastore:
port: 3306
# if socket-path is set, it will be used instead of host:port
#socket-path: "/var/run/mysqld/mysqld.sock"
user: "oragono"
user: "ergo"
password: "hunter2"
history-database: "oragono_history"
history-database: "ergo_history"
timeout: 3s
max-conns: 4
# this may be necessary to prevent middleware from closing your connections:
#conn-max-lifetime: 180s
# languages config
languages:
@ -708,6 +876,9 @@ limits:
# identlen is the max ident length allowed
identlen: 20
# realnamelen is the maximum realname length allowed
realnamelen: 150
# channellen is the max channel length allowed
channellen: 64
@ -727,7 +898,7 @@ limits:
whowas-entries: 100
# maximum length of channel lists (beI modes)
chan-list-modes: 60
chan-list-modes: 100
# maximum number of messages to accept during registration (prevents
# DoS / resource exhaustion attacks):
@ -757,13 +928,22 @@ fakelag:
# sending any commands:
cooldown: 2s
# exempt a certain number of command invocations per session from fakelag;
# this is to speed up "resynchronization" of client state during reattach
command-budgets:
"CHATHISTORY": 16
"MARKREAD": 16
"MONITOR": 1
"WHO": 4
"WEBPUSH": 1
# the roleplay commands are semi-standardized extensions to IRC that allow
# sending and receiving messages from pseudo-nicknames. this can be used either
# for actual roleplaying, or for bridging IRC with other protocols.
roleplay:
# are roleplay commands enabled at all? (channels and clients still have to
# opt in individually with the +E mode)
enabled: true
enabled: false
# require the "roleplay" oper capability to send roleplay messages?
require-oper: false
@ -774,6 +954,12 @@ roleplay:
# add the real nickname, in parentheses, to the end of every roleplay message?
add-suffix: true
# allow customizing the NUH's sent for NPC and SCENE commands
# NPC: the first %s is the NPC name, the second is the user's real nick
#npc-nick-mask: "*%s*!%s@npc.fakeuser.invalid"
# SCENE: the %s is the client's real nick
#scene-nick-mask: "=Scene=!%s@npc.fakeuser.invalid"
# external services can integrate with the ircd using JSON Web Tokens (https://jwt.io).
# in effect, the server can sign a token attesting that the client is present on
# the server, is a member of a particular channel, etc.
@ -821,7 +1007,7 @@ history:
# maximum number of CHATHISTORY messages that can be
# requested at once (0 disables support for CHATHISTORY)
chathistory-maxmessages: 100
chathistory-maxmessages: 1000
# maximum number of messages that can be replayed at once during znc emulation
# (znc.in/playback, or automatic replay on initial reattach to a persistent client):
@ -833,17 +1019,26 @@ history:
# (and will eventually be deleted from persistent storage, if that's enabled)
expire-time: 1w
# if this is set, logged-in users cannot retrieve messages older than their
# account registration date, and logged-out users cannot retrieve messages
# older than their sign-on time (modulo grace-period, see below):
enforce-registration-date: false
# this restricts access to channel history (it can be overridden by channel
# owners). options are: 'none' (no restrictions), 'registration-time'
# (logged-in users cannot retrieve messages older than their account
# registration date, and anonymous users cannot retrieve messages older than
# their sign-on time, modulo the grace-period described below), and
# 'join-time' (users cannot retrieve messages older than the time they
# joined the channel, so only always-on clients can view history).
query-cutoff: 'none'
# but if this is set, you can retrieve messages that are up to `grace-period`
# older than the above cutoff time. this is recommended to allow logged-out
# users to do session resumption / query history after disconnections.
# if query-cutoff is set to 'registration-time', this allows retrieval
# of messages that are up to 'grace-period' older than the above cutoff.
# if you use 'registration-time', this is recommended to allow logged-out
# users to query history after disconnections.
grace-period: 1h
# options to store history messages in a persistent database (currently only MySQL):
# options to store history messages in a persistent database (currently only MySQL).
# in order to enable any of this functionality, you must configure a MySQL server
# in the `datastore.mysql` section. enabling persistence overrides the history
# size limits above (`channel-length`, `client-length`, etc.); persistent
# history has no limits other than those imposed by expire-time.
persistent:
enabled: false
@ -865,7 +1060,8 @@ history:
# options to control how messages are stored and deleted:
retention:
# allow users to delete their own messages from history?
# allow users to delete their own messages from history,
# and channel operators to delete messages in their channel?
allow-individual-delete: false
# if persistent history is enabled, create additional index tables,
@ -881,9 +1077,61 @@ history:
# if `default` is false, store TAGMSG containing any of these tags:
whitelist:
- "+draft/react"
- "react"
- "+react"
# if `default` is true, don't store TAGMSG containing any of these tags:
#blacklist:
# - "+draft/typing"
# - "typing"
# whether to allow customization of the config at runtime using environment variables,
# e.g., ERGO__SERVER__MAX_SENDQ=128k. see the manual for more details.
allow-environment-overrides: true
# metadata support for setting key/value data on channels and nicknames.
metadata:
# can clients store metadata?
enabled: true
# how many keys can a client subscribe to?
max-subs: 100
# how many keys can be stored per entity?
max-keys: 100
# experimental support for mobile push notifications
# see the manual for potential security, privacy, and performance implications.
# DO NOT enable if you are running a Tor or I2P hidden service (i.e. one
# with no public IP listeners, only Tor/I2P listeners).
webpush:
# are push notifications enabled at all?
enabled: false
# request timeout for POST'ing the http notification
timeout: 10s
# delay sending the notification for this amount of time, then suppress it
# if the client sent MARKREAD to indicate that it was read on another device
delay: 0s
# subscriber field for the VAPID JWT authorization:
#subscriber: "https://your-website.com/"
# maximum number of push subscriptions per user
max-subscriptions: 4
# expiration time for a push subscription; it must be renewed within this time
# by the client reconnecting to IRC. we also detect whether the client is no longer
# successfully receiving push messages.
expiration: 14d
# HTTP API. we strongly recommend leaving this disabled unless you have a specific
# need for it.
api:
# is the API enabled at all?
enabled: false
# listen address:
listener: "127.0.0.1:8089"
# serve over TLS (strongly recommended if the listener is public):
#tls:
#cert: fullchain.pem
#key: privkey.pem
# one or more static bearer tokens accepted for HTTP bearer authentication.
# these must be strong, unique, high-entropy printable ASCII strings.
# to generate a new token, use `ergo gentoken` or:
# python3 -c "import secrets; print(secrets.token_urlsafe(32))"
bearer-tokens:
- "example"

26
distrib/SMF/README Normal file
View File

@ -0,0 +1,26 @@
Created 22/11/2021 by georg@lysergic.dev.
This directory contains Service Management Facility service files for ergo.
These files should be compatible with current OpenSolaris / Illumos based operating systems. Tested on OpenIndiana.
Prerequesites:
- ergo binary located at /opt/ergo/ergo
- ergo configuration located at /opt/ergo/ircd.yaml (hardcoded)
- ergo languages located at /opt/ergo/languages (to be compatible with default.yaml - you may adjust this path or disable languages in your custom ircd.yaml)
- ergo certificate and key located at /opt/ergo/fullchain.pem /opt/ergo/privkey.pem (to be compatible with default.yaml - you may adjust these paths in your custom ircd.yaml)
- `ergo` role user and `ergo` role group owning all of the above
Installation:
- cp ergo.xml /lib/svc/manifest/network/
- cp ergo /lib/svc/method/
- svcadm restart manifest-import
Usage:
- svcadm enable ergo (Start)
- tail /var/svc/log/network-ergo:default.log (Check ergo log and SMF output)
- svcs ergo (Check status)
- svcadm refresh ergo (Reload manifest and ergo configuration)
- svcadm disable ergo (Stop)
Notes:
- Does not support multiple instances - spawns instance :default

26
distrib/SMF/ergo Executable file
View File

@ -0,0 +1,26 @@
#!/sbin/sh
#
# SMF method script for ergo - used by manifest file ergo.xml
# Created 22/11/2021 by georg@lysergic.dev
. /lib/svc/share/smf_include.sh
case $1 in
'start')
exec /opt/ergo/ergo run --conf /opt/ergo/ircd.yaml
;;
'refresh' )
exec pkill -1 -U ergo -x ergo
;;
'stop' )
exec pkill -U ergo -x ergo
;;
*)
echo "Usage: $0 { start | refresh | stop }"
exit 1
;;
esac
exit $?

48
distrib/SMF/ergo.xml Normal file
View File

@ -0,0 +1,48 @@
<?xml version='1.0'?>
<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
<service_bundle type='manifest' name='ergo'>
<service name='network/ergo' type='service' version='0'>
<create_default_instance enabled="true"/>
<single_instance/>
<dependency name='fs-local' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/system/filesystem/local'/>
</dependency>
<dependency name='fs-autofs' grouping='optional_all' restart_on='none' type='service'>
<service_fmri value='svc:/system/filesystem/autofs'/>
</dependency>
<dependency name='net-loopback' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/network/loopback'/>
</dependency>
<dependency name='net-physical' grouping='require_all' restart_on='none' type='service'>
<service_fmri value='svc:/network/physical'/>
</dependency>
<dependency name='config_data' grouping='require_all' restart_on='restart' type='path'>
<service_fmri value='file://localhost/opt/ergo/ircd.yaml'/>
</dependency>
<method_context working_directory="/opt/ergo">
<method_credential user='ergo' group='ergo' />
</method_context>
<exec_method name='start' type='method' exec='/lib/svc/method/ergo start' timeout_seconds='20'>
<method_context security_flags='aslr'/>
</exec_method>
<exec_method name='stop' type='method' exec='/lib/svc/method/ergo stop' timeout_seconds='20'/>
<exec_method name='refresh' type='method' exec='/lib/svc/method/ergo refresh' timeout_seconds='20'/>
<property_group name='general' type='framework'>
<propval name='action_authorization' type='astring' value='solaris.smf.manage.ergo'/>
</property_group>
<property_group name='startd' type='framework'>
<propval name='ignore_error' type='astring' value='core,signal'/>
<propval name='duration' type='astring' value='child'/>
</property_group>
<stability value='Unstable'/>
<template>
<common_name>
<loctext xml:lang='C'>IRC server</loctext>
</common_name>
<documentation>
<doc_link name='ergo-manual' uri='https://github.com/ergochat/ergo/blob/master/docs/MANUAL.md'/>
<doc_link name='ergo-userguide' uri='https://github.com/ergochat/ergo/blob/master/docs/USERGUIDE.md'/>
</documentation>
</template>
</service>
</service_bundle>

206
distrib/anope/anope2json.py Executable file
View File

@ -0,0 +1,206 @@
#!/usr/bin/python3
import binascii
import json
import logging
import re
import sys
from collections import defaultdict, namedtuple
AnopeObject = namedtuple('AnopeObject', ('type', 'kv'))
MASK_MAGIC_REGEX = re.compile(r'[*?!@]')
def access_level_to_amode(level):
# https://wiki.anope.org/index.php/2.0/Modules/cs_xop
if level == 'QOP':
return 'q'
elif level == 'SOP':
return 'a'
elif level == 'AOP':
return 'o'
elif level == 'HOP':
return 'h'
elif level == 'VOP':
return 'v'
try:
level = int(level)
except:
return None
if level >= 10000:
return 'q'
elif level >= 9999:
return 'a'
elif level >= 5:
return 'o'
elif level >= 4:
return 'h'
elif level >= 3:
return 'v'
else:
return None
def to_unixnano(timestamp):
return int(timestamp) * (10**9)
def file_to_objects(infile):
result = []
obj = None
while True:
line = infile.readline()
if not line:
break
line = line.rstrip(b'\r\n')
try:
line = line.decode('utf-8')
except UnicodeDecodeError:
line = line.decode('utf-8', 'replace')
logging.warning("line contained invalid utf8 data " + line)
pieces = line.split(' ', maxsplit=2)
if len(pieces) == 0:
logging.warning("skipping blank line in db")
continue
if pieces[0] == 'END':
result.append(obj)
obj = None
elif pieces[0] == 'OBJECT':
obj = AnopeObject(pieces[1], {})
elif pieces[0] == 'DATA':
obj.kv[pieces[1]] = pieces[2]
elif pieces[0] == 'ID':
# not sure what these do?
continue
else:
raise ValueError("unknown command found in anope db", pieces[0])
return result
ANOPE_MODENAME_TO_MODE = {
'NOEXTERNAL': 'n',
'TOPIC': 't',
'INVITE': 'i',
'NOCTCP': 'C',
'AUDITORIUM': 'u',
'SECRET': 's',
}
# verify that a certfp appears to be a hex-encoded SHA-256 fingerprint;
# if it's anything else, silently ignore it
def validate_certfps(certobj):
certfps = []
for fingerprint in certobj.split():
try:
dec = binascii.unhexlify(fingerprint)
except:
continue
if len(dec) == 32:
certfps.append(fingerprint)
return certfps
def convert(infile):
out = {
'version': 1,
'source': 'anope',
'users': defaultdict(dict),
'channels': defaultdict(dict),
}
objects = file_to_objects(infile)
lastmode_channels = set()
for obj in objects:
if obj.type == 'NickCore':
username = obj.kv['display']
userdata = {'name': username, 'hash': obj.kv['pass'], 'email': obj.kv['email']}
certobj = obj.kv.get('cert')
if certobj:
userdata['certfps'] = validate_certfps(certobj)
out['users'][username] = userdata
elif obj.type == 'NickAlias':
username = obj.kv['nc']
nick = obj.kv['nick']
userdata = out['users'][username]
if username.lower() == nick.lower():
userdata['registeredAt'] = to_unixnano(obj.kv['time_registered'])
else:
if 'additionalNicks' not in userdata:
userdata['additionalNicks'] = []
userdata['additionalNicks'].append(nick)
elif obj.type == 'ChannelInfo':
chname = obj.kv['name']
founder = obj.kv['founder']
chdata = {
'name': chname,
'founder': founder,
'registeredAt': to_unixnano(obj.kv['time_registered']),
'topic': obj.kv['last_topic'],
'topicSetBy': obj.kv['last_topic_setter'],
'topicSetAt': to_unixnano(obj.kv['last_topic_time']),
'amode': {founder: 'q',}
}
# DATA last_modes INVITE KEY,hunter2 NOEXTERNAL REGISTERED TOPIC
last_modes = obj.kv.get('last_modes')
if last_modes:
modes = []
for mode_desc in last_modes.split():
if ',' in mode_desc:
mode_name, mode_value = mode_desc.split(',', maxsplit=1)
else:
mode_name, mode_value = mode_desc, None
if mode_name == 'KEY':
chdata['key'] = mode_value
else:
modes.append(ANOPE_MODENAME_TO_MODE.get(mode_name, ''))
chdata['modes'] = ''.join(modes)
# prevent subsequent ModeLock objects from modifying the mode list further:
lastmode_channels.add(chname)
out['channels'][chname] = chdata
elif obj.type == 'ModeLock':
if obj.kv.get('set') != '1':
continue
chname = obj.kv['ci']
if chname in lastmode_channels:
continue
chdata = out['channels'][chname]
modename = obj.kv['name']
if modename == 'KEY':
chdata['key'] = obj.kv['param']
else:
oragono_mode = ANOPE_MODENAME_TO_MODE.get(modename)
if oragono_mode is not None:
stored_modes = chdata.get('modes', '')
stored_modes += oragono_mode
chdata['modes'] = stored_modes
elif obj.type == 'ChanAccess':
chname = obj.kv['ci']
target = obj.kv['mask']
mode = access_level_to_amode(obj.kv['data'])
if mode is None:
continue
if MASK_MAGIC_REGEX.search(target):
continue
chdata = out['channels'][chname]
amode = chdata.setdefault('amode', {})
amode[target] = mode
chdata['amode'] = amode
# do some basic integrity checks
for chname, chdata in out['channels'].items():
founder = chdata.get('founder')
if founder not in out['users']:
raise ValueError("no user corresponding to channel founder", chname, chdata.get('founder'))
return out
def main():
if len(sys.argv) != 3:
raise Exception("Usage: anope2json.py anope.db output.json")
with open(sys.argv[1], 'rb') as infile:
output = convert(infile)
with open(sys.argv[2], 'w') as outfile:
json.dump(output, outfile)
if __name__ == '__main__':
logging.basicConfig()
sys.exit(main())

34
distrib/apparmor/ergo Normal file
View File

@ -0,0 +1,34 @@
include <tunables/global>
# Georg Pfuetzenreuter <georg+ergo@lysergic.dev>
# AppArmor confinement for ergo and ergo-ldap
profile ergo /usr/bin/ergo {
include <abstractions/base>
include <abstractions/consoles>
include <abstractions/nameservice>
/etc/ergo/ircd.{motd,yaml} r,
/etc/ssl/irc/{crt,key} r,
/etc/ssl/ergo/{crt,key} r,
/usr/bin/ergo mr,
/proc/sys/net/core/somaxconn r,
/sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,
/usr/share/ergo/languages/{,*.lang.json,*.yaml} r,
owner /run/ergo/ircd.lock rwk,
owner /var/lib/ergo/ircd.db rw,
include if exists <local/ergo>
}
profile ergo-ldap /usr/bin/ergo-ldap {
include <abstractions/openssl>
include <abstractions/ssl_certs>
/usr/bin/ergo-ldap rm,
/etc/ergo/ldap.yaml r,
include if exists <local/ergo-ldap>
}

209
distrib/atheme/atheme2json.py Executable file
View File

@ -0,0 +1,209 @@
#!/usr/bin/python3
import binascii
import json
import logging
import re
import sys
from collections import defaultdict
MASK_MAGIC_REGEX = re.compile(r'[*?!@$]')
def to_unixnano(timestamp):
return int(timestamp) * (10**9)
# include/atheme/channels.h
CMODE_FLAG_TO_MODE = {
0x001: 'i', # CMODE_INVITE
0x010: 'n', # CMODE_NOEXT
0x080: 's', # CMODE_SEC
0x100: 't', # CMODE_TOPIC
}
# attempt to interpret certfp as a hex-encoded SHA-256 fingerprint
def validate_certfp(certfp):
try:
dec = binascii.unhexlify(certfp)
except:
return False
return len(dec) == 32
def convert(infile):
out = {
'version': 1,
'source': 'atheme',
'users': defaultdict(dict),
'channels': defaultdict(dict),
}
group_to_founders = defaultdict(list)
channel_to_founder = defaultdict(lambda: (None, None))
while True:
line = infile.readline()
if not line:
break
line = line.rstrip(b'\r\n')
try:
line = line.decode('utf-8')
except UnicodeDecodeError:
line = line.decode('utf-8', 'replace')
logging.warning("line contained invalid utf8 data " + line)
parts = line.split(' ')
category = parts[0]
if category == 'GACL':
# Note: all group definitions precede channel access entries (token CA) by design, so it
# should be safe to read this in using one pass.
groupname = parts[1]
user = parts[2]
flags = parts[3]
if 'F' in flags:
group_to_founders[groupname].append(user)
elif category == 'MU':
# user account
# MU AAAAAAAAB shivaram $1$hcspif$nCm4r3S14Me9ifsOPGuJT. user@example.com 1600134392 1600467343 +sC default
name = parts[2]
user = {'name': name, 'hash': parts[3], 'email': parts[4], 'registeredAt': to_unixnano(parts[5])}
out['users'][name].update(user)
pass
elif category == 'MN':
# grouped nick
# MN shivaram slingamn 1600218831 1600467343
username, groupednick = parts[1], parts[2]
if username != groupednick:
user = out['users'][username]
user.setdefault('additionalnicks', []).append(groupednick)
elif category == 'MDU':
if parts[2] == 'private:usercloak':
username = parts[1]
out['users'][username]['vhost'] = parts[3]
elif category == 'MCFP':
username, certfp = parts[1], parts[2]
if validate_certfp(certfp):
user = out['users'][username]
user.setdefault('certfps', []).append(certfp.lower())
elif category == 'MC':
# channel registration
# MC #mychannel 1600134478 1600467343 +v 272 0 0
# MC #NEWCHANNELTEST 1602270889 1602270974 +vg 1 0 0 jaeger4
chname = parts[1]
chdata = out['channels'][chname]
# XXX just give everyone +nt, regardless of lock status; they can fix it later
chdata.update({'name': chname, 'registeredAt': to_unixnano(parts[2])})
if parts[8] != '':
chdata['key'] = parts[8]
modes = {'n', 't'}
mlock_on, mlock_off = int(parts[5]), int(parts[6])
for flag, mode in CMODE_FLAG_TO_MODE.items():
if flag & mlock_on != 0:
modes.add(mode)
elif flag & mlock_off != 0 and mode in modes:
modes.remove(mode)
chdata['modes'] = ''.join(sorted(modes))
chdata['limit'] = int(parts[7])
elif category == 'MDC':
# auxiliary data for a channel registration
# MDC #mychannel private:topic:setter s
# MDC #mychannel private:topic:text hi again
# MDC #mychannel private:topic:ts 1600135864
chname = parts[1]
category = parts[2]
if category == 'private:topic:text':
out['channels'][chname]['topic'] = line.split(maxsplit=3)[3]
elif category == 'private:topic:setter':
out['channels'][chname]['topicSetBy'] = parts[3]
elif category == 'private:topic:ts':
out['channels'][chname]['topicSetAt'] = to_unixnano(parts[3])
elif category == 'private:mlockext':
# the channel forward mode is +L on insp/unreal, +f on charybdis
# charybdis has a +L ("large banlist") taking no argument
# and unreal has a +f ("flood limit") taking two colon-delimited numbers,
# so check for an argument that starts with a #
if parts[3].startswith('L#') or parts[3].startswith('f#'):
out['channels'][chname]['forward'] = parts[3][1:]
elif category == 'CA':
# channel access lists
# CA #mychannel shivaram +AFORafhioqrstv 1600134478 shivaram
chname, username, flags, set_at = parts[1], parts[2], parts[3], int(parts[4])
chname = parts[1]
chdata = out['channels'][chname]
flags = parts[3]
set_at = int(parts[4])
if 'amode' not in chdata:
chdata['amode'] = {}
# see libathemecore/flags.c: +o is op, +O is autoop, etc.
if 'F' in flags:
# If the username starts with "!", it's actually a GroupServ group.
if username.startswith('!'):
group_founders = group_to_founders.get(username)
if not group_founders:
# skip this and warn about it later
continue
# attempt to promote the first group founder to channel founder
username = group_founders[0]
# but everyone gets the +q flag
for founder in group_founders:
chdata['amode'][founder] = 'q'
# there can only be one founder
preexisting_founder, preexisting_set_at = channel_to_founder[chname]
if preexisting_founder is None or set_at < preexisting_set_at:
chdata['founder'] = username
channel_to_founder[chname] = (username, set_at)
# but multiple people can receive the 'q' amode
chdata['amode'][username] = 'q'
continue
if MASK_MAGIC_REGEX.search(username):
# ignore groups, masks, etc. for any field other than founder
continue
# record the first appearing successor, if necessary
if 'S' in flags:
if not chdata.get('successor'):
chdata['successor'] = username
# finally, handle amodes
if 'q' in flags:
chdata['amode'][username] = 'q'
elif 'a' in flags:
chdata['amode'][username] = 'a'
elif 'o' in flags or 'O' in flags:
chdata['amode'][username] = 'o'
elif 'h' in flags or 'H' in flags:
chdata['amode'][username] = 'h'
elif 'v' in flags or 'V' in flags:
chdata['amode'][username] = 'v'
else:
pass
# do some basic integrity checks
def validate_user(name):
if not name:
return False
return bool(out['users'].get(name))
invalid_channels = []
for chname, chdata in out['channels'].items():
if not validate_user(chdata.get('founder')):
if validate_user(chdata.get('successor')):
chdata['founder'] = chdata['successor']
else:
invalid_channels.append(chname)
for chname in invalid_channels:
logging.warning("Unable to find a valid founder for channel %s, discarding it", chname)
del out['channels'][chname]
return out
def main():
if len(sys.argv) != 3:
raise Exception("Usage: atheme2json.py atheme_db output.json")
with open(sys.argv[1], 'rb') as infile:
output = convert(infile)
with open(sys.argv[2], 'w') as outfile:
json.dump(output, outfile)
if __name__ == '__main__':
logging.basicConfig()
sys.exit(main())

29
distrib/bsd-rc/README.md Normal file
View File

@ -0,0 +1,29 @@
Ergo init script for bsd-rc
===
Written for and tested using FreeBSD.
## Installation
Copy the `ergo` file from this folder to `/etc/rc.d/ergo`,
permissions should be `555`.
You should create a system user for Ergo.
This script defaults to running Ergo as a user named `ergo`,
but that can be changed using `/etc/rc.conf`.
Here are all `rc.conf` variables and their defaults:
- `ergo_enable`, defaults to `NO`. Whether to run `ergo` at system start.
- `ergo_user`, defaults to `ergo`. Run using this user.
- `ergo_group`, defaults to `ergo`. Run using this group.
- `ergo_chdir`, defaults to `/var/db/ergo`. Path to the working directory for the server. Should be writable for `ergo_user`.
- `ergo_conf`, defaults to `/usr/local/etc/ergo/ircd.yaml`. Config file path. Make sure `ergo_user` can read it.
This script assumes ergo to be installed at `/usr/local/bin/ergo`.
## Usage
```shell
/etc/rc.d/ergo <command>
```
In addition to the obvious `start` and `stop` commands, this
script also has a `reload` command that sends `SIGHUP` to the Ergo process.

45
distrib/bsd-rc/ergo Normal file
View File

@ -0,0 +1,45 @@
#!/bin/sh
# PROVIDE: ergo
# REQUIRE: DAEMON
# KEYWORD: shutdown
#
# Add the following lines to /etc/rc.conf to enable Ergo
#
# ergo_enable (bool): Set to YES to enable ergo.
# Default is "NO".
# ergo_user (user): Set user to run ergo.
# Default is "ergo".
# ergo_group (group): Set group to run ergo.
# Default is "ergo".
# ergo_config (file): Set ergo config file path.
# Default is "/usr/local/etc/ergo/config.yaml".
# ergo_chdir (dir): Set ergo working directory
# Default is "/var/db/ergo".
. /etc/rc.subr
name=ergo
rcvar=ergo_enable
desc="Ergo IRCv3 server"
load_rc_config "$name"
: ${ergo_enable:=NO}
: ${ergo_user:=ergo}
: ${ergo_group:=ergo}
: ${ergo_chdir:=/var/db/ergo}
: ${ergo_conf:=/usr/local/etc/ergo/ircd.yaml}
# If you don't define a custom reload function,
# rc automagically sends SIGHUP to the process on reload.
# But you have to list reload as an extra_command for that.
extra_commands="reload"
procname="/usr/local/bin/${name}"
command=/usr/sbin/daemon
command_args="-S -T ${name} ${procname} run --conf ${ergo_conf}"
run_rc_command "$1"

View File

@ -1,33 +1,35 @@
# Oragono Docker
# Ergo Docker
This folder holds Oragono's Dockerfile and related materials. Oragono
is published automatically to Docker Hub at
[oragono/oragono](https://hub.docker.com/r/oragono/oragono).
This folder holds Ergo's Docker compose file. The Dockerfile is in the root
directory. Ergo is published automatically to the GitHub Container Registry at
[ghcr.io/ergochat/ergo](https://ghcr.io/ergochat/ergo).
The `latest` tag tracks the `stable` branch of Oragono, which contains
the latest stable release. The `dev` tag tracks the master branch, which
may by unstable and is not recommended for production.
Most users should use either the `stable` tag (corresponding to the
`stable` branch in git, which tracks the latest stable release), or
a tag corresponding to a tagged version (e.g. `v2.8.0`). The `master`
tag corresponds to the `master` branch, which is not recommended for
production use. The `latest` tag is not recommended.
## Quick start
The Oragono docker image is designed to work out of the box - it comes with a
The Ergo docker image is designed to work out of the box - it comes with a
usable default config and will automatically generate self-signed TLS
certificates. To get a working ircd, all you need to do is run the image and
expose the ports:
```shell
docker run --name oragono -d -p 6667:6667 -p 6697:6697 oragono/oragono:tag
docker run --init --name ergo -d -p 6667:6667 -p 6697:6697 ghcr.io/ergochat/ergo:stable
```
This will start Oragono and listen on ports 6667 (plain text) and 6697 (TLS).
The first time Oragono runs it will create a config file with a randomised
This will start Ergo and listen on ports 6667 (plain text) and 6697 (TLS).
The first time Ergo runs it will create a config file with a randomised
oper password. This is output to stdout, and you can view it with the docker
logs command:
```shell
# Assuming your container is named `oragono`; use `docker container ls` to
# Assuming your container is named `ergo`; use `docker container ls` to
# find the name if you're not sure.
docker logs oragono
docker logs ergo
```
You should see a line similar to:
@ -36,69 +38,74 @@ You should see a line similar to:
Oper username:password is admin:cnn2tm9TP3GeI4vLaEMS
```
We recommend the use of `--init` (`init: true` in docker-compose) to solve an
edge case involving unreaped zombie processes when Ergo's script API is used
for authentication or IP validation. For more details, see
[krallin/tini#8](https://github.com/krallin/tini/issues/8).
## Persisting data
Oragono has a persistent data store, used to keep account details, channel
Ergo has a persistent data store, used to keep account details, channel
registrations, and so on. To persist this data across restarts, you can mount
a volume at /ircd.
For example, to create a new docker volume and then mount it:
```shell
docker volume create oragono-data
docker run -d -v oragono-data:/ircd -p 6667:6667 -p 6697:6697 oragono/oragono:tag
docker volume create ergo-data
docker run --init --name ergo -d -v ergo-data:/ircd -p 6667:6667 -p 6697:6697 ghcr.io/ergochat/ergo:stable
```
Or to mount a folder from your host machine:
```shell
mkdir oragono-data
docker run -d -v $(PWD)/oragono-data:/ircd -p 6667:6667 -p 6697:6697 oragono/oragono:tag
mkdir ergo-data
docker run --init --name ergo -d -v $(pwd)/ergo-data:/ircd -p 6667:6667 -p 6697:6697 ghcr.io/ergochat/ergo:stable
```
## Customising the config
Oragono's config file is stored at /ircd/ircd.yaml. If the file does not
Ergo's config file is stored at /ircd/ircd.yaml. If the file does not
exist, the default config will be written out. You can copy the config from
the container, edit it, and then copy it back:
```shell
# Assuming that your container is named `oragono`, as above.
docker cp oragono:/ircd/ircd.yaml .
# Assuming that your container is named `ergo`, as above.
docker cp ergo:/ircd/ircd.yaml .
vim ircd.yaml # edit the config to your liking
docker cp ircd.yaml oragono:/ircd/ircd.yaml
docker cp ircd.yaml ergo:/ircd/ircd.yaml
```
You can use the `/rehash` command to make Oragono reload its config, or
You can use the `/rehash` command to make Ergo reload its config, or
send it the HUP signal:
```shell
docker kill -HUP oragono
docker kill -s SIGHUP ergo
```
## Using custom TLS certificates
TLS certs will by default be read from /ircd/tls.crt, with a private key
in /ircd/tls.key. You can customise this path in the ircd.yaml file if
TLS certs will by default be read from /ircd/fullchain.pem, with a private key
in /ircd/privkey.pem. You can customise this path in the ircd.yaml file if
you wish to mount the certificates from another volume. For information
on using Let's Encrypt certificates, see
[this manual entry](https://github.com/oragono/oragono/blob/master/docs/MANUAL.md#how-do-i-use-lets-encrypt-certificates).
[this manual entry](https://github.com/ergochat/ergo/blob/master/docs/MANUAL.md#using-valid-tls-certificates).
## Using docker-compose
This folder contains a sample docker-compose file which can be used
to start an Oragono instance with ports exposed and data persisted in
to start an Ergo instance with ports exposed and data persisted in
a docker volume. Simply download the file and then bring it up:
```shell
curl -O https://raw.githubusercontent.com/oragono/oragono/master/distrib/docker/docker-compose.yml
curl -O https://raw.githubusercontent.com/ergochat/ergo/master/distrib/docker/docker-compose.yml
docker-compose up -d
```
## Building
If you wish to manually build the docker image, you need to do so from
the root of the Oragono repository (not the `distrib/docker` directory):
the root of the Ergo repository (not the `distrib/docker` directory):
```shell
docker build .

View File

@ -1,8 +1,9 @@
version: "3.2"
version: "3.8"
services:
oragono:
image: oragono/oragono:latest
ergo:
init: true
image: ghcr.io/ergochat/ergo:stable
ports:
- "6667:6667/tcp"
- "6697:6697/tcp"

9
distrib/docker/run.sh Normal file → Executable file
View File

@ -1,8 +1,5 @@
#!/bin/sh
# start in right dir
cd /ircd
# make config file
if [ ! -f "/ircd/ircd.yaml" ]; then
awk '{gsub(/path: languages/,"path: /ircd-bin/languages")}1' /ircd-bin/default.yaml > /tmp/ircd.yaml
@ -10,7 +7,7 @@ if [ ! -f "/ircd/ircd.yaml" ]; then
# change default oper passwd
OPERPASS=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c20)
echo "Oper username:password is admin:$OPERPASS"
ENCRYPTEDPASS=$(echo "$OPERPASS" | /ircd-bin/oragono genpasswd)
ENCRYPTEDPASS=$(echo "$OPERPASS" | /ircd-bin/ergo genpasswd)
ORIGINALPASS='\$2a\$04\$0123456789abcdef0123456789abcdef0123456789abcdef01234'
awk "{gsub(/password: \\\"$ORIGINALPASS\\\"/,\"password: \\\"$ENCRYPTEDPASS\\\"\")}1" /tmp/ircd.yaml > /tmp/ircd2.yaml
@ -23,7 +20,7 @@ if [ ! -f "/ircd/ircd.yaml" ]; then
fi
# make self-signed certs if they don't already exist
/ircd-bin/oragono mkcerts
/ircd-bin/ergo mkcerts
# run!
exec /ircd-bin/oragono run
exec /ircd-bin/ergo run

57
distrib/init/rc.ergo Normal file
View File

@ -0,0 +1,57 @@
#!/bin/sh
# Init script for the ergo IRCd
# Created 14/06/2021 by georg@lysergic.dev
# Desgigned for and tested on Slackware -current
# Depends on `daemon` (installable using slackpkg)
# In its stock configuration ergo will be jailed to /opt/ergo - all paths are relative from there. Consider this in your ergo configuration file (i.e. certificate, database and log locations)
NAME=ergo
DIR=/opt/ergo
ERGO=/ergo
DAEMONIZER=/usr/bin/daemon
CONFIG=ircd.yaml
USER=ergo
GROUP=ergo
daemon_start() {
$DAEMONIZER -n $NAME -v -- chroot --userspec=$USER --groups=$USER -- $DIR $ERGO run --conf $CONFIG
}
daemon_stop() {
$DAEMONIZER --stop -n $NAME -v
}
daemon_restart() {
$DAEMONIZER --restart -n $NAME -v
}
daemon_reload() {
$DAEMONIZER --signal=SIGHUP -n $NAME -v
}
daemon_status() {
$DAEMONIZER --running -n $NAME -v
}
case "$1" in
start)
daemon_start
;;
stop)
daemon_stop
;;
restart)
daemon_restart
;;
reload)
daemon_reload
;;
status)
daemon_status
;;
*)
echo "Source: https://github.com/ergochat/ergo"
echo "Usage: $0 {start|stop|restart|reload|status}"
exit 1
esac

View File

@ -0,0 +1,3 @@
# /etc/conf.d/ergo: config file for /etc/init.d/ergo
ERGO_CONFIGFILE="/etc/ergo/ircd.yaml"
ERGO_USERNAME="ergo"

32
distrib/openrc/ergo.initd Normal file
View File

@ -0,0 +1,32 @@
#!/sbin/openrc-run
name=${RC_SVCNAME}
description="ergo IRC daemon"
command=/usr/bin/ergo
command_args="run --conf ${ERGO_CONFIGFILE:-'/etc/ergo/ircd.yaml'}"
command_user=${ERGO_USERNAME:-ergo}
command_background=true
pidfile=/var/run/${RC_SVCNAME}.pid
output_log="/var/log/${RC_SVCNAME}.out"
error_log="/var/log/${RC_SVCNAME}.err"
# --wait: to wait 1 second after launching to see if it survived startup
start_stop_daemon_args="--wait 1000"
extra_started_commands="reload"
depend() {
use dns
provide ircd
}
start_pre() {
checkpath --owner ${command_user}:${command_user} --mode 0640 --file /var/log/${RC_SVCNAME}.out /var/log/${RC_SVCNAME}.err
}
reload() {
ebegin "Reloading ${RC_SVCNAME}"
start-stop-daemon --signal HUP --pidfile "${pidfile}"
eend $?
}

8
distrib/s6/README Normal file
View File

@ -0,0 +1,8 @@
This directory contains s6 srv and log services for ergo.
These services expect that ergo is installed to /opt/ergo,
and an ergo system user that owns /opt/ergo.
To install:
cp -r ergo-srv ergo-log /etc/s6/sv/
cp ergo.conf /etc/s6/config/

View File

@ -0,0 +1 @@
ergo-srv

View File

@ -0,0 +1 @@
3

View File

@ -0,0 +1 @@
ergo

9
distrib/s6/ergo-log/run Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/execlineb -P
envfile /etc/s6/config/ergo.conf
importas -sCiu DIRECTIVES DIRECTIVES
ifelse { test -w /var/log } {
foreground { install -d -o s6log -g s6log /var/log/ergo }
s6-setuidgid s6log exec -c s6-log -d3 -b -- ${DIRECTIVES} /var/log/ergo
}
foreground { install -d -o s6log -g s6log /run/log/ergo }
s6-setuidgid s6log exec -c s6-log -d3 -b -- ${DIRECTIVES} /run/log/ergo

1
distrib/s6/ergo-log/type Normal file
View File

@ -0,0 +1 @@
longrun

View File

@ -0,0 +1 @@
ergo-log

4
distrib/s6/ergo-srv/run Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/execlineb -P
fdmove -c 2 1
execline-cd /opt/ergo
s6-setuidgid ergo ./ergo run

1
distrib/s6/ergo-srv/type Normal file
View File

@ -0,0 +1 @@
longrun

2
distrib/s6/ergo.conf Normal file
View File

@ -0,0 +1,2 @@
# This configures the directives used for s6-log in the log service.
DIRECTIVES="n3 s2000000"

View File

@ -1,5 +1,5 @@
[Unit]
Description=oragono
Description=ergo
After=network.target
# If you are using MySQL for history storage, comment out the above line
# and uncomment these two instead (you must independently install and configure
@ -8,13 +8,16 @@ After=network.target
# After=network.target mysql.service
[Service]
Type=simple
User=oragono
WorkingDirectory=/home/oragono
ExecStart=/home/oragono/oragono run --conf /home/oragono/ircd.yaml
Type=notify
User=ergo
WorkingDirectory=/home/ergo
ExecStart=/home/ergo/ergo run --conf /home/ergo/ircd.yaml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
LimitNOFILE=1048576
NotifyAccess=main
# Uncomment this for a hidden service:
# PrivateNetwork=true
[Install]
WantedBy=multi-user.target

124
docs/API.md Normal file
View File

@ -0,0 +1,124 @@
__ __ ______ ___ ______ ___
__/ // /_/ ____/ __ \/ ____/ __ \
/_ // __/ __/ / /_/ / / __/ / / /
/_ // __/ /___/ _, _/ /_/ / /_/ /
/_//_/ /_____/_/ |_|\____/\____/
Ergo IRCd API Documentation
https://ergo.chat/
_Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn@cs.stanford.edu>_
--------------------------------------------------------------------------------------------
Ergo has an experimental HTTP API. Some general information about the API:
1. All requests to the API are via POST.
1. All requests to the API are authenticated via bearer authentication. This is a header named `Authorization` with the value `Bearer <token>`. A list of valid tokens is hardcoded in the Ergo config. Future versions of Ergo may allow additional validation schemes for tokens.
1. The request parameters are sent as JSON in the POST body.
1. Any status code other than 200 is an error response; the response body is undefined in this case (likely human-readable text for debugging).
1. A 200 status code indicates successful execution of the request. The response body will be JSON and may indicate application-level success or failure (typically via the `success` field, which takes a boolean value).
API endpoints are versioned (currently all endpoints have a `/v1/` path prefix). Backwards-incompatible updates will most likely take the form of endpoints with new names, or an increased version prefix. Any exceptions to this will be specifically documented in the changelog.
All API endpoints should be considered highly privileged. Bearer tokens should be kept secret. Access to the API should be either over a trusted link (like loopback) or secured via verified TLS. See the `api` section of `default.yaml` for examples of how to configure this.
Here's an example of how to test an API configured to run over loopback TCP in plaintext:
```bash
curl -d '{"accountName": "invalidaccountname", "passphrase": "invalidpassphrase"}' -H 'Authorization: Bearer EYBbXVilnumTtfn4A9HE8_TiKLGWEGylre7FG6gEww0' -v http://127.0.0.1:8089/v1/check_auth
```
This returns:
```json
{"success":false}
```
Endpoints
=========
`/v1/account_details`
----------------
This endpoint fetches account details and returns them as JSON. The request is a JSON object with fields:
* `accountName`: string, name of the account
The response is a JSON object with fields:
* `success`: whether the account exists or not
* `accountName`: canonical, case-unfolded version of the account name
* `email`: email address of the account provided
* `registeredAt`: string, registration date/time of the account (in ISO8601 format)
* `channels`: array of strings, list of channels the account is registered on or associated with
`/v1/check_auth`
----------------
This endpoint verifies the credentials of a NickServ account; this allows Ergo to be used as the source of truth for authentication by another system. The request is a JSON object with fields:
* `accountName`: string, name of the account
* `passphrase`: string, alleged passphrase of the account
The response is a JSON object with fields:
* `success`: whether the credentials provided were valid
* `accountName`: canonical, case-unfolded version of the account name
`/v1/rehash`
------------
This endpoint rehashes the server (i.e. reloads the configuration file, TLS certificates, and other associated data). The body is ignored. The response is a JSON object with fields:
* `success`: boolean, indicates whether the rehash was successful
* `error`: string, optional, human-readable description of the failure
`/v1/saregister`
----------------
This endpoint registers an account in NickServ, with the same semantics as `NS SAREGISTER`. The request is a JSON object with fields:
* `accountName`: string, name of the account
* `passphrase`: string, passphrase of the account
The response is a JSON object with fields:
* `success`: whether the account creation succeeded
* `errorCode`: string, optional, machine-readable description of the error. Possible values include: `ACCOUNT_EXISTS`, `INVALID_PASSPHRASE`, `UNKNOWN_ERROR`.
* `error`: string, optional, human-readable description of the failure.
`/v1/account_list`
-------------------
This endpoint fetches a list of all accounts. The request body is ignored and can be empty.
The response is a JSON object with fields:
* `success`: whether the request succeeded
* `accounts`: array of objects, each with fields:
* `success`: boolean, whether this individual account query succeeded
* `accountName`: string, canonical, case-unfolded version of the account name
* `totalCount`: integer, total number of accounts returned
`/v1/status`
-------------
This endpoint returns status information about the running Ergo server. The request body is ignored and can be empty.
The response is a JSON object with fields:
* `success`: whether the request succeeded
* `version`: string, Ergo server version string
* `go_version`: string, version of Go runtime used
* `start_time`: string, server start time in ISO8601 format
* `users`: object with fields:
* `total`: total number of users connected
* `invisible`: number of invisible users
* `operators`: number of operators connected
* `unknown`: number of users with unknown status
* `max`: maximum number of users seen connected at once
* `channels`: integer, number of channels currently active
* `servers`: integer, number of servers connected in the network

View File

@ -1,92 +0,0 @@
# Oragono Information
Here's a bunch of misc info about the Oragono server! This can include questions, plans on
how I'm going forward, how to properly use features, or why Oragono does/doesn't do
something.
Essentially, this document acts as a braindump about Oragono while we figure out a better
place to put all this information.
## Accounts and Channels
Most IRC servers out there offer IRC account and channel registration through external
services such as NickServ and ChanServ. In Oragono, we bundle accounts and channel ownership
in as a native server feature instead!
Because there's a lot of aspects of accounts/channels that haven't been specified as native
commands and all yet, Oragono includes the pseudo-clients NickServ and ChanServ to roughly
mimic the functionality that other IRCds get from services packages, in a user-facing set
of commands that's familiar to everyone.
The plan is to move more features and functionality (such as channel registration, channel
permissions and all) over to native commands first and to use the NickServ/ChanServ as
legacy interfaces to access these functions. However, it's gonna be a while before all of
this is specified by someone like the IRCv3 WG.
## PROXY
The PROXY command, specified by [HAProxy's PROXY v1 specifications](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt),
allows someone to setup HAProxy in front of Oragono. This allows them to use HAProxy for
TLS negotiation (allowing older versions of SSL/TLS than Go's inbuilt TLS support does).
However, it also allows them to update TLS certificates by updating them with HAProxy,
rather than relying on our `REHASH` command (which is less-well-tested than I'd like
right now).
This is a toss-up of course  allowing older versions of TLS might be seen as undesired,
and I wouldn't use the feature myself, but it's useful for real-world installations which
is why it exists. The command is only allowed from specific hosts which should restrict it
appropriately.
## Server-to-Server Linking (or Federation)
Right now Oragono doesn't support linking multiple servers together. It's certainly planned,
but it's a fair while away.
When I do add S2S linking to Oragono, I want to use it as a testbed for a new sort of
linking protocol. Mostly, I want a meshy protocol that minimises the effects of netsplits
while still ensuring that messages get delivered, and preserves the AP nature of IRC
reliability (in terms of the CAP theorem), which is something that traditional solutions
based on the Raft protocol don't do.
Basically, I'm going to continue working on my [DCMI](https://github.com/DanielOaks/dcmi)
protocol, get that to a point where I'm happy with it and _then_ start looking at S2S
linking properly. If anyone is interested in server protocols and wants to look at this with
me, please feel free to reach out!
## Rehashing
Rehashing is reloading the config files and TLS certificates. Of course, you can rehash the
server by connect, opering-up and using the `/REHASH` command. However, similar to other
IRCds, you can also make the server rehash by sending an appropriate signal to it!
To make the server rehash from the command line, send it a `SIGHUP` signal. In *nix and OSX,
you can do this by performing the following command:
killall -HUP oragono
This will make the server rehash its configuration files and TLS certificates, and so can be
useful if you're automatically updating your TLS certs!
## Rejected Features
'Rejected' sounds harsh, but basically these are features I've decided I'm not gonna
implement in Oragono (at least, not until someone convinces me they're worth doing).
### Force/Auto-Join Channels on Connect
When a user connects, some IRC servers let you force-join them to a given channel. For
instance, this could be a channel like `#coolnet` for a network named CoolNet, a lobby
channel, or something similar.
My main objection to having this feature is just that I don't like it that much. It doesn't
seem nice to forcibly join clients to a channel, and I know I'm always annoyed when networks
do it to me.
To network operators that want to do this, I'd suggest instead mentioning the channel(s) in
your MOTD so that your users know the channels exist! If they want to join in, they can do
it from there :)

File diff suppressed because it is too large Load Diff

128
docs/USERGUIDE.md Normal file
View File

@ -0,0 +1,128 @@
__ __ ______ ___ ______ ___
__/ // /_/ ____/ __ \/ ____/ __ \
/_ // __/ __/ / /_/ / / __/ / / /
/_ // __/ /___/ _, _/ /_/ / /_/ /
/_//_/ /_____/_/ |_|\____/\____/
Ergo IRCd User Guide
https://ergo.chat/
_Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn@cs.stanford.edu>_
--------------------------------------------------------------------------------------------
Table of Contents
- [Introduction](#introduction)
- [About IRC](#about-irc)
- [How Ergo is different](#how-ergo-is-different)
- [Account registration](#account-registration)
- [Channel registration](#channel-registration)
- [Always-on](#always-on)
- [Multiclient](#multiclient)
- [History](#history)
- [Push notifications](#push-notifications)
--------------------------------------------------------------------------------------------
# Introduction
Welcome to Ergo, a modern IRC server!
This guide is for end users of Ergo (people using Ergo to chat). If you're installing your own Ergo instance, you should consult the official manual instead (a copy should be bundled with your release, in the `docs/` directory).
This guide assumes that Ergo is in its default or recommended configuration; Ergo server administrators can change settings to make the server behave differently. If something isn't working as expected, ask your server administrator for help.
# About IRC
Before continuing, you should be familiar with basic features of the IRC platform. If you're comfortable with IRC, you can skip this section.
[IRC](https://en.wikipedia.org/wiki/Internet_Relay_Chat) is a chat platform invented in 1988, which makes it older than the World Wide Web! At its most basic level, IRC is a chat system composed of chat rooms; these are called "channels" and their names begin with a `#` character (this is actually the origin of the [hashtag](https://www.cmu.edu/homepage/computing/2014/summer/originstory.shtml)!). As a user, you "join" the channels you're interested in, enabling you to participate in those discussions.
Here are some guides covering the basics of IRC:
* [Fedora Magazine: Beginner's Guide to IRC](https://fedoramagazine.org/beginners-guide-irc/)
* [IRCHelp's IRC Tutorial](https://www.irchelp.org/faq/irctutorial.html) (in particular, section 3, "Beyond the Basics")
# How Ergo is different
Ergo differs in many ways from conventional IRC servers. If you're *not* familiar with other IRC servers, you may want to skip this section. Here are some of the most salient differences:
* Ergo integrates a "bouncer" into the server. In particular:
* Ergo stores message history for later retrieval.
* You can be "present" on the server (joined to channels, able to receive DMs) without having an active client connection to the server.
* Conversely, you can use multiple clients to view / control the same presence (nickname) on the server, as long as you authenticate with SASL when connecting.
* Ergo integrates "services" into the server. In particular:
* Nicknames are strictly reserved: once you've registered your nickname, you must log in in order to use it. Consequently, SASL is more important when using Ergo than in other systems.
* All properties of registered channels are protected without the need for `ChanServ` to be joined to the channel.
* Ergo "cloaks", i.e., cryptographically scrambles, end user IPs so that they are not displayed publicly.
* By default, the user/ident field is inoperative in Ergo: it is always set to `~u`, regardless of the `USER` command or the client's support for identd. This is because it is not in general a reliable or trustworthy way to distinguish users coming from the same IP. Ergo's integrated bouncer features should reduce the need for shared shell hosts and hosted bouncers (one of the main remaining use cases for identd).
* By default, Ergo is only accessible via TLS.
# Account registration
Although (as in other IRC systems) basic chat functionality is available without creating an account, most of Ergo's features require an account. You can create an account by sending a direct message to `NickServ`. (In IRC jargon, `NickServ` is a "network service", but if you're not familiar with the concept you can just think of it as a bot or a text user interface.) In a typical client, this will be:
```
/msg NickServ register mySecretPassword validEmailAddress@example.com
```
This registers your current nickname as your account name, with the password `mySecretPassword` (replace this with your own secret password!)
Once you have registered your account, you must configure SASL in your client, so that you will be logged in automatically on each connection. [libera.chat's SASL guide](https://libera.chat/guides/sasl) covers most popular clients.
If your client doesn't support SASL, you can typically use the "server password" (`PASS`) field in your client to log into your account automatically when connecting. Set the server password to `accountname:accountpassword`, where `accountname` is your account name and `accountpassword` is your account password.
For information on how to use a client certificate for authentication, see the [operator manual](https://github.com/ergochat/ergo/blob/stable/docs/MANUAL.md#client-certificates).
# Channel registration
Once you've registered your nickname, you can use it to register channels. By default, channels are ephemeral; they go away when there are no longer any users in the channel, or when the server is restarted. Registering a channel gives you permanent control over it, and ensures that its settings will persist. To register a channel, send a message to `ChanServ`:
```
/msg ChanServ register #myChannel
```
The channel must exist (if it doesn't, you can create it with `/join #myChannel`) and you must already be an operator (have the `+o` channel mode --- your client may display this as an `@` next to your nickname). If you're not a channel operator in the channel you want to register, ask your server administrator for help.
# Always-on
By default, if you lose your connection to the IRC server, you are no longer present on the server; other users will see that you have "quit", you will no longer appear in channel lists, and you will not be able to receive direct messages. Ergo supports "always-on clients", where you remain on the server even when you are disconnected. To enable this, you can send a message to `NickServ`:
```
/msg NickServ set always-on true
```
# Multiclient
Ergo natively supports attaching multiple clients to the same nickname (this normally requires the use of an external bouncer, like ZNC or WeeChat's "relay" functionality). To use this feature, simply authenticate with SASL (or the PASS workaround, if necessary) when connecting. In the recommended configuration of Ergo, you will receive the nickname associated with your account, even if you have other clients already using it.
# History
Ergo stores message history on the server side (typically not an unlimited amount --- consult your server's FAQ, or your server administrator, to find out how much is being stored and how long it's being retained).
1. The [IRCv3 chathistory specification](https://ircv3.net/specs/extensions/chathistory) offers the most fine-grained control over history replay. It is supported by [Gamja](https://git.sr.ht/~emersion/gamja), [Goguma](https://sr.ht/~emersion/goguma/), and [Kiwi IRC](https://github.com/kiwiirc/kiwiirc), and hopefully other clients soon.
1. We emulate the [ZNC playback module](https://wiki.znc.in/Playback) for clients that support it. You may need to enable support for it explicitly in your client. For example, in [Textual](https://www.codeux.com/textual/), go to "Server properties", select "Vendor specific", uncheck "Do not automatically join channels on connect", and check "Only play back messages you missed". ZNC's wiki page covers other common clients (although if the feature is only supported via a script or third-party extension, the following option may be easier).
1. If you set your client to always-on (see the previous section for details), you can set a "device ID" for each device you use. Ergo will then remember the last time your device was present on the server, and each time you sign on, it will attempt to replay exactly those messages you missed. There are a few ways to set your device ID when connecting:
- You can add it to your SASL username with an `@`, e.g., if your SASL username is `alice` you can send `alice@phone`
- You can add it in a similar way to your IRC protocol username ("ident"), e.g., `alice@phone`
- If login to user accounts via the `PASS` command is enabled on the server, you can provide it there, e.g., by sending `alice@phone:hunter2` as the server password
1. If you only have one device, you can set your client to be always-on and furthermore `/msg NickServ set autoreplay-missed true`. This will replay missed messages, with the caveat that you must be connecting with at most one client at a time.
1. You can manually request history using `/history #channel 1h` (the parameter is either a message count or a time duration). (Depending on your client, you may need to use `/QUOTE history` instead.)
1. You can autoreplay a fixed number of lines (e.g., 25) each time you join a channel using `/msg NickServ set autoreplay-lines 25`.
# Private channels
If you have registered a channel, you can make it private. The best way to do this is with the `+i` ("invite-only") mode:
1. Set your channel to be invite-only (`/mode #example +i`)
1. Identify the users you want to be able to access the channel. Ensure that they have registered their accounts (you should be able to see their registration status if you `/WHOIS` their nicknames).
1. Add the desired nick/account names to the invite exception list (`/mode #example +I alice`) or give them persistent voice (`/msg ChanServ AMODE #example +v alice`)
1. If you want to grant a persistent channel privilege to a user, you can do it with `CS AMODE` (`/msg ChanServ AMODE #example +o bob`)
# Push notifications
Ergo has experimental support for mobile push notifications. The server operator must enable this functionality; to check whether this is the case, you can send `/msg NickServ push list`. You must additionally be using a client (e.g. Goguma) that supports the functionality, and your account must be set to always-on (`/msg NickServ set always-on true`, as described above).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -1 +1,2 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 209"><defs><style>.cls-1{fill:#0f0f0f;}.cls-2{fill:#6a83c1;}.cls-3{fill:#9eb6de;}</style></defs><title>logo</title><path class="cls-1" d="M96.63,94v21.95H73.8V137H62.39v39.89H73.8v21.95H96.63V220H51V198.87H28.13V178.52H16.72V135.44H28.13V115.91H51V94H96.63Z" transform="translate(-12 -11)"/><path class="cls-1" d="M621.85,94v21.95H599V137H587.6v39.89H599v21.95h22.84V220H576.18V198.87H553.34V178.52H541.93V135.44h11.41V115.91h22.84V94h45.67Z" transform="translate(-12 -11)"/><path class="cls-1" d="M713.19,11V52.48h11.43V94H736v41.48h11.43V54.07H736V32.95h22.84V52.48h22.84V95.56H770.29v83H758.86V220H736V178.52H724.62v-83H713.19V198.87H667.52V135.44h11.43V94h11.41V52.48h11.43V11h11.41Z" transform="translate(-12 -11)"/><path class="cls-1" d="M873,94v21.95H850.2V137H838.79v39.89H850.2v21.95H873V220H827.37V198.87H804.53V178.52H793.12V135.44h11.41V115.91h22.84V94H873Z" transform="translate(-12 -11)"/><path class="cls-2" d="M154.07,204.11a2.45,2.45,0,0,1,1.78.73,2.51,2.51,0,0,1,0,3.57,2.47,2.47,0,0,1-1.77.72H153.4a2.45,2.45,0,0,1-1.78-.73,2.51,2.51,0,0,1,0-3.57,2.48,2.48,0,0,1,1.78-.72h0.67Z" transform="translate(-12 -11)"/><path class="cls-3" d="M775,192.06v9.41h-9.42v-9.41H775Z" transform="translate(-12 -11)"/><path class="cls-3" d="M135.6,192.06v9.41h-9.42v-9.41h9.42Z" transform="translate(-12 -11)"/><path class="cls-3" d="M912,192.06v9.41h-9.42v-9.41H912Z" transform="translate(-12 -11)"/><path class="cls-1" d="M895.87,115.91v19.54H907.3v43.07H895.87v20.35H873V176.93h11.43V137H873V115.91h22.84Z" transform="translate(-12 -11)"/><path class="cls-1" d="M644.68,115.91v19.54h11.43v43.07H644.68v20.35H621.85V176.93h11.43V137H621.85V115.91h22.84Z" transform="translate(-12 -11)"/><path class="cls-1" d="M462,32.95V52.48h22.84V74.43H462V54.07H439.16V157.39H462v19.54h34.26V137H484.84V115.91H462V94h45.67v21.95h22.84v62.61H507.67v20.35H416.33V178.52H404.92V137H393.49V115.91h22.84V95.56H404.92V52.48h11.41V32.95H462Z" transform="translate(-12 -11)"/><path class="cls-1" d="M210.81,32.95V52.48h22.84V95.56H210.81v20.35H165.14v19.54H188v63.43H165.14V178.52H153.73V94h11.41V74.43H188V94h22.84V54.07H165.14V74.43H142.31V32.95h68.51Z" transform="translate(-12 -11)"/><path class="cls-1" d="M119.47,115.91v19.54H130.9v43.07H119.47v20.35H96.63V176.93h11.43V137H96.63V115.91h22.84Z" transform="translate(-12 -11)"/><path class="cls-1" d="M233.65,115.91v19.54h11.43v41.48h11.41v21.95H233.65V178.52H210.81V115.91h22.84Z" transform="translate(-12 -11)"/><path class="cls-1" d="M347.82,32.95V52.48h22.84v83h11.43v43.07H370.66v20.35H347.82V176.93h11.43V137H347.82V115.91H302.15v83H279.32V178.52H267.91V137H256.48V115.91h22.84V95.56H267.91V52.48h11.41V32.95h68.51ZM325,54.07H302.15V94h45.67V74.43H325V54.07Z" transform="translate(-12 -11)"/><path class="cls-2" d="M408.56,194a1.9,1.9,0,0,1,0,3.81,1.85,1.85,0,0,1-1.36-.56,1.9,1.9,0,0,1,0-2.69A1.84,1.84,0,0,1,408.56,194Z" transform="translate(-12 -11)"/><path class="cls-2" d="M85.56,162.63a2.45,2.45,0,0,1,1.78.73,2.51,2.51,0,0,1,0,3.57,2.47,2.47,0,0,1-1.77.72H84.89a2.45,2.45,0,0,1-1.78-.73,2.51,2.51,0,0,1,0-3.57,2.47,2.47,0,0,1,1.77-.73h0.67Z" transform="translate(-12 -11)"/><path class="cls-2" d="M862,162.63a2.45,2.45,0,0,1,1.78.73,2.51,2.51,0,0,1,0,3.57,2.47,2.47,0,0,1-1.77.72h-0.67a2.45,2.45,0,0,1-1.78-.73,2.51,2.51,0,0,1,0-3.57,2.47,2.47,0,0,1,1.78-.73H862Z" transform="translate(-12 -11)"/><path class="cls-3" d="M341.12,150.58V160H331.7v-9.41h9.42Z" transform="translate(-12 -11)"/><path class="cls-3" d="M478.13,150.58V160h-9.42v-9.41h9.42Z" transform="translate(-12 -11)"/><path class="cls-3" d="M199.38,150.69a4.6,4.6,0,1,1-3.24,1.35A4.45,4.45,0,0,1,199.38,150.69Z" transform="translate(-12 -11)"/><path class="cls-3" d="M660.81,109.09v9.41h-9.42v-9.41h9.42Z" transform="translate(-12 -11)"/><path class="cls-3" d="M523.8,67.61V77h-9.42V67.61h9.42Z" transform="translate(-12 -11)"/><path class="cls-3" d="M21.42,67.61V77H12V67.61h9.42Z" transform="translate(-12 -11)"/><path class="cls-3" d="M546.63,67.61V77h-9.42V67.61h9.42Z" transform="translate(-12 -11)"/><path class="cls-3" d="M797.82,67.61V77H788.4V67.61h9.42Z" transform="translate(-12 -11)"/><path class="cls-3" d="M678.93,67.72a4.6,4.6,0,1,1-3.24,1.35,4.45,4.45,0,0,1,3.24-1.35h0Z" transform="translate(-12 -11)"/><path class="cls-2" d="M248.72,69.54l0.72,0.14,0.61,0.42a1.84,1.84,0,0,1,.56,1.36,1.9,1.9,0,0,1-1.9,1.89,1.84,1.84,0,0,1-1.36-.56,1.82,1.82,0,0,1-.56-1.34A1.9,1.9,0,0,1,248.72,69.54Z" transform="translate(-12 -11)"/><path class="cls-3" d="M496.24,26.24a4.6,4.6,0,1,1,0,9.19A4.59,4.59,0,0,1,493,27.59,4.42,4.42,0,0,1,496.24,26.24Z" transform="translate(-12 -11)"/><path class="cls-2" d="M362.89,28.06a1.82,1.82,0,0,1,1.34.56,1.85,1.85,0,0,1,.56,1.36,1.9,1.9,0,0,1-1.9,1.89,1.85,1.85,0,0,1-1.36-.56A1.82,1.82,0,0,1,361,30,1.9,1.9,0,0,1,362.89,28.06Z" transform="translate(-12 -11)"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="552.48" height="226.39" version="1.1" viewBox="0 0 146.18 59.901" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(3.0169 0 0 3.0169 -99.412 -462.64)"><g stroke-width=".40656" aria-label="#ERGO"><path d="m34.33 165.07h1.9027l2.0003-11.351h-1.9027l-0.55292 3.1549h-2.0328v1.7888h1.7075l-0.24394 1.4636h-2.0816v1.7888h1.7563zm3.1549 0h1.9027l0.55292-3.1549h2.0328v-1.7888h-1.7075l0.24393-1.4636h2.0816v-1.7888h-1.7563l0.55292-3.1549h-1.9027z" fill="#5f901d"/><g fill="#161616"><path d="m51.898 165.07v-2.0003h-4.8136v-2.7483h4.651v-2.0003h-4.651v-2.602h4.8136v-2.0003h-7.253v11.351z"/><path d="m56.001 160.86h1.1221l1.9027 4.2119h2.667l-2.2279-4.5372c1.2685-0.35777 2.0003-1.61 2.0003-3.2037 0-2.1954-1.2522-3.6102-3.4801-3.6102h-4.3908v11.351h2.4068zm0-1.8864v-3.285h1.3986c1.1384 0 1.5287 0.40656 1.5287 1.3986v0.48787c0 0.992-0.3903 1.3986-1.5287 1.3986z"/><path d="m68.823 165.07h2.1791v-6.0658h-4.1144v1.7238h1.9352v0.82938c0 1.0083-0.55292 1.7401-1.6425 1.7401-1.4148 0-1.8864-1.2034-1.8864-3.041v-1.8539c0-1.8214 0.45534-2.911 1.6913-2.911 1.1221 0 1.4961 0.89443 1.7401 1.8539l2.2767-0.55291c-0.45534-1.9515-1.6262-3.2687-3.968-3.2687-2.9435 0-4.3258 2.1141-4.3258 5.9683 0 3.6102 1.2034 5.7731 3.4313 5.7731 1.3986 0 2.1629-0.8619 2.5369-1.8051h0.14636z"/><path d="m76.791 165.27c3.0411 0 4.4396-2.1629 4.4396-5.8707 0-3.7078-1.3986-5.8707-4.4396-5.8707-3.041 0-4.4396 2.1629-4.4396 5.8707 0 3.7078 1.3986 5.8707 4.4396 5.8707zm0-1.9677c-1.3823 0-1.8376-1.0896-1.8376-2.911v-1.984c0-1.8214 0.45534-2.911 1.8376-2.911 1.3823 0 1.8376 1.0896 1.8376 2.911v1.9677c0 1.8376-0.45534 2.9272-1.8376 2.9272z"/></g></g><g fill="#4a7411" stroke-width=".17823" aria-label="irc server"><path d="m42.203 168.4c0.24239 0 0.34932-0.12833 0.34932-0.32081v-0.0927c0-0.19249-0.10694-0.32081-0.34932-0.32081s-0.34933 0.12832-0.34933 0.32081v0.0927c0 0.19248 0.10694 0.32081 0.34933 0.32081zm-0.28516 4.5412h0.57033v-3.6786h-0.57033z"/><path d="m44.271 172.94v-2.4952c0-0.34933 0.37071-0.61311 0.98382-0.61311h0.33507v-0.57032h-0.221c-0.59884 0-0.93391 0.32793-1.0622 0.67726h-0.03565v-0.67726h-0.57033v3.6786z"/><path d="m47.65 173.03c0.69865 0 1.1763-0.3422 1.4116-0.86975l-0.41349-0.27804c-0.19961 0.42062-0.53468 0.64162-0.99807 0.64162-0.67726 0-1.0266-0.46339-1.0266-1.105v-0.62736c0-0.64162 0.34932-1.105 1.0266-1.105 0.44913 0 0.76281 0.221 0.89826 0.59885l0.47765-0.24239c-0.21387-0.50617-0.64875-0.86262-1.3759-0.86262-1.0337 0-1.6397 0.74855-1.6397 1.9248s0.60597 1.9249 1.6397 1.9249z"/><path d="m52.655 173.03c0.84123 0 1.3617-0.43488 1.3617-1.1478 0-0.55607-0.31368-0.91252-1.1264-1.0337l-0.28516-0.0428c-0.45626-0.0713-0.70578-0.21387-0.70578-0.57033 0-0.34932 0.24952-0.57032 0.72004-0.57032 0.47052 0 0.7842 0.221 0.94817 0.44913l0.37784-0.3422c-0.29942-0.37071-0.69152-0.59171-1.2832-0.59171-0.74855 0-1.3118 0.35645-1.3118 1.0836 0 0.68439 0.50616 0.96243 1.1834 1.0622l0.29229 0.0428c0.48478 0.0713 0.64162 0.29229 0.64162 0.57745 0 0.37785-0.28516 0.59885-0.76994 0.59885-0.46339 0-0.80559-0.20675-1.0908-0.5632l-0.40636 0.32794c0.32794 0.43487 0.77707 0.72004 1.4543 0.72004z"/><path d="m56.405 173.03c0.69152 0 1.2191-0.3422 1.4543-0.84124l-0.40636-0.29229c-0.19248 0.40636-0.54894 0.63449-1.0123 0.63449-0.68439 0-1.0908-0.47765-1.0908-1.1121v-0.1711h2.6449v-0.2709c0-1.0408-0.60597-1.7965-1.5898-1.7965-0.99807 0-1.6539 0.75568-1.6539 1.9248s0.65588 1.9249 1.6539 1.9249zm0-3.3721c0.58459 0 0.97669 0.43487 0.97669 1.0836v0.0784h-2.0318v-0.0499c0-0.64161 0.43487-1.1121 1.0551-1.1121z"/><path d="m59.506 172.94v-2.4952c0-0.34933 0.37071-0.61311 0.98381-0.61311h0.33507v-0.57032h-0.221c-0.59884 0-0.93391 0.32793-1.0622 0.67726h-0.03565v-0.67726h-0.57033v3.6786z"/><path d="m63.099 172.94 1.2975-3.6786h-0.54894l-0.65588 1.825-0.39923 1.2547h-0.03564l-0.39923-1.2547-0.64162-1.825h-0.57033l1.2904 3.6786z"/><path d="m66.457 173.03c0.69152 0 1.2191-0.3422 1.4543-0.84124l-0.40636-0.29229c-0.19249 0.40636-0.54894 0.63449-1.0123 0.63449-0.68439 0-1.0908-0.47765-1.0908-1.1121v-0.1711h2.6449v-0.2709c0-1.0408-0.60597-1.7965-1.5898-1.7965-0.99807 0-1.6539 0.75568-1.6539 1.9248s0.65588 1.9249 1.6539 1.9249zm0-3.3721c0.58458 0 0.97668 0.43487 0.97668 1.0836v0.0784h-2.0318v-0.0499c0-0.64161 0.43488-1.1121 1.0551-1.1121z"/><path d="m69.558 172.94v-2.4952c0-0.34933 0.37071-0.61311 0.98382-0.61311h0.33507v-0.57032h-0.221c-0.59884 0-0.93391 0.32793-1.0622 0.67726h-0.03565v-0.67726h-0.57033v3.6786z"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -7,37 +7,37 @@ package main
import (
"bufio"
_ "embed"
"fmt"
"log"
"os"
"strings"
"syscall"
"github.com/docopt/docopt-go"
"github.com/oragono/oragono/irc"
"github.com/oragono/oragono/irc/logger"
"github.com/oragono/oragono/irc/mkcerts"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/term"
"github.com/docopt/docopt-go"
"github.com/ergochat/ergo/irc"
"github.com/ergochat/ergo/irc/logger"
"github.com/ergochat/ergo/irc/mkcerts"
"github.com/ergochat/ergo/irc/utils"
)
// set via linker flags, either by make or by goreleaser:
var commit = "" // git hash
var version = "" // tagged version
//go:embed default.yaml
var defaultConfig string
// get a password from stdin from the user
func getPassword() string {
fd := int(os.Stdin.Fd())
if terminal.IsTerminal(fd) {
bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
log.Fatal("Error reading password:", err.Error())
}
return string(bytePassword)
func getPasswordFromTerminal() string {
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
log.Fatal("Error reading password:", err.Error())
}
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
return strings.TrimSpace(text)
return string(bytePassword)
}
func fileDoesNotExist(file string) bool {
@ -47,7 +47,7 @@ func fileDoesNotExist(file string) bool {
return false
}
// implements the `oragono mkcerts` command
// implements the `ergo mkcerts` command
func doMkcerts(configFile string, quiet bool) {
config, err := irc.LoadRawConfig(configFile)
if err != nil {
@ -78,7 +78,7 @@ func doMkcerts(configFile string, quiet bool) {
if !(fileDoesNotExist(cert) && fileDoesNotExist(key)) {
log.Fatalf("Preexisting TLS cert and/or key files: %s %s", cert, key)
}
err := mkcerts.CreateCert("Oragono", host, cert, key)
err := mkcerts.CreateCert("Ergo", host, cert, key)
if err == nil {
if !quiet {
log.Printf(" Certificate created at %s : %s\n", cert, key)
@ -92,15 +92,18 @@ func doMkcerts(configFile string, quiet bool) {
func main() {
irc.SetVersionString(version, commit)
usage := `oragono.
usage := `ergo.
Usage:
oragono initdb [--conf <filename>] [--quiet]
oragono upgradedb [--conf <filename>] [--quiet]
oragono genpasswd [--conf <filename>] [--quiet]
oragono mkcerts [--conf <filename>] [--quiet]
oragono run [--conf <filename>] [--quiet] [--smoke]
oragono -h | --help
oragono --version
ergo initdb [--conf <filename>] [--quiet]
ergo upgradedb [--conf <filename>] [--quiet]
ergo importdb <database.json> [--conf <filename>] [--quiet]
ergo genpasswd [--conf <filename>] [--quiet]
ergo mkcerts [--conf <filename>] [--quiet]
ergo defaultconfig
ergo gentoken
ergo run [--conf <filename>] [--quiet] [--smoke]
ergo -h | --help
ergo --version
Options:
--conf <filename> Configuration file to use [default: ircd.yaml].
--quiet Don't show startup/shutdown lines.
@ -112,28 +115,36 @@ Options:
// don't require a config file for genpasswd
if arguments["genpasswd"].(bool) {
var password string
fd := int(os.Stdin.Fd())
if terminal.IsTerminal(fd) {
if term.IsTerminal(int(syscall.Stdin)) {
fmt.Print("Enter Password: ")
password = getPassword()
password = getPasswordFromTerminal()
fmt.Print("\n")
fmt.Print("Reenter Password: ")
confirm := getPassword()
confirm := getPasswordFromTerminal()
fmt.Print("\n")
if confirm != password {
log.Fatal("passwords do not match")
}
} else {
password = getPassword()
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
password = strings.TrimSpace(text)
}
if err := irc.ValidatePassphrase(password); err != nil {
log.Printf("WARNING: this password contains characters that may cause problems with your IRC client software.\n")
log.Printf("We strongly recommend choosing a different password.\n")
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
if err != nil {
log.Fatal("encoding error:", err.Error())
}
fmt.Print(string(hash))
if terminal.IsTerminal(fd) {
fmt.Println()
}
fmt.Println(string(hash))
return
} else if arguments["defaultconfig"].(bool) {
fmt.Print(defaultConfig)
return
} else if arguments["gentoken"].(bool) {
fmt.Println(utils.GenerateSecretKey())
return
} else if arguments["mkcerts"].(bool) {
doMkcerts(arguments["--conf"].(string), arguments["--quiet"].(bool))
@ -155,7 +166,10 @@ Options:
}
if arguments["initdb"].(bool) {
irc.InitDB(config.Datastore.Path)
err = irc.InitDB(config.Datastore.Path)
if err != nil {
log.Fatal("Error while initializing db:", err.Error())
}
if !arguments["--quiet"].(bool) {
log.Println("database initialized: ", config.Datastore.Path)
}
@ -167,6 +181,11 @@ Options:
if !arguments["--quiet"].(bool) {
log.Println("database upgraded: ", config.Datastore.Path)
}
} else if arguments["importdb"].(bool) {
err = irc.ImportDB(config, arguments["<database.json>"].(string))
if err != nil {
log.Fatal("Error while importing db:", err.Error())
}
} else if arguments["run"].(bool) {
if !arguments["--quiet"].(bool) {
logman.Info("server", fmt.Sprintf("%s starting", irc.Ver))
@ -174,7 +193,7 @@ Options:
// warning if running a non-final version
if strings.Contains(irc.Ver, "unreleased") {
logman.Warning("server", "You are currently running an unreleased beta version of Oragono that may be unstable and could corrupt your database.\nIf you are running a production network, please download the latest build from https://oragono.io/downloads.html and run that instead.")
logman.Warning("server", "You are currently running an unreleased beta version of Ergo that may be unstable and could corrupt your database.\nIf you are running a production network, please download the latest build from https://ergo.chat/about and run that instead.")
}
server, err := irc.NewServer(config, logman)
@ -182,10 +201,6 @@ Options:
logman.Error("server", fmt.Sprintf("Could not load server: %s", err.Error()))
os.Exit(1)
}
if !arguments["--quiet"].(bool) {
logman.Info("server", "Server running")
defer logman.Info("server", fmt.Sprintf("Oragono v%s exiting", irc.SemVer))
}
if !arguments["--smoke"].(bool) {
server.Run()
}

View File

@ -1,11 +1,11 @@
__ __ ______ ___ ______ ___
__/ // /_/ ____/ __ \/ ____/ __ \
/_ // __/ __/ / /_/ / / __/ / / /
/_ // __/ /___/ _, _/ /_/ / /_/ /
/_//_/ /_____/_/ |_|\____/\____/
▄▄▄ ▄▄▄· ▄▄ • ▐ ▄
▪ ▀▄ █·▐█ ▀█ ▐█ ▀ ▪▪ •█▌▐█▪
▄█▀▄ ▐▀▀▄ ▄█▀▀█ ▄█ ▀█▄ ▄█▀▄▪▐█▐▐▌ ▄█▀▄
▐█▌.▐▌▐█•█▌▐█ ▪▐▌▐█▄▪▐█▐█▌ ▐▌██▐█▌▐█▌.▐▌
▀█▄▀▪.▀ ▀ ▀ ▀ ·▀▀▀▀ ▀█▄▀ ▀▀ █▪ ▀█▄▀▪
This is the default Oragono MOTD.
This is the default Ergo MOTD.
If motd-formatting is enabled in the config file, you can use the dollarsign character to

View File

@ -63,6 +63,12 @@ CAPDEFS = [
url="https://ircv3.net/specs/extensions/extended-join-3.1.html",
standard="IRCv3",
),
CapDef(
identifier="ExtendedMonitor",
name="extended-monitor",
url="https://ircv3.net/specs/extensions/extended-monitor.html",
standard="IRCv3",
),
CapDef(
identifier="InviteNotify",
name="invite-notify",
@ -81,6 +87,12 @@ CAPDEFS = [
url="https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6",
standard="proposed IRCv3",
),
CapDef(
identifier="MessageRedaction",
name="draft/message-redaction",
url="https://github.com/progval/ircv3-specifications/blob/redaction/extensions/message-redaction.md",
standard="proposed IRCv3",
),
CapDef(
identifier="MessageTags",
name="message-tags",
@ -94,16 +106,16 @@ CAPDEFS = [
standard="IRCv3",
),
CapDef(
identifier="Rename",
name="draft/rename",
url="https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md",
identifier="Relaymsg",
name="draft/relaymsg",
url="https://github.com/ircv3/ircv3-specifications/pull/417",
standard="proposed IRCv3",
),
CapDef(
identifier="Resume",
name="draft/resume-0.5",
url="https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md",
standard="proposed IRCv3",
identifier="ChannelRename",
name="draft/channel-rename",
url="https://ircv3.net/specs/extensions/channel-rename",
standard="draft IRCv3",
),
CapDef(
identifier="SASL",
@ -155,9 +167,9 @@ CAPDEFS = [
),
CapDef(
identifier="Nope",
name="oragono.io/nope",
url="https://oragono.io/nope",
standard="Oragono vendor",
name="ergo.chat/nope",
url="https://ergo.chat/nope",
standard="Ergo vendor",
),
CapDef(
identifier="Multiline",
@ -171,6 +183,67 @@ CAPDEFS = [
url="https://github.com/ircv3/ircv3-specifications/pull/393",
standard="proposed IRCv3",
),
CapDef(
identifier="AccountRegistration",
name="draft/account-registration",
url="https://github.com/ircv3/ircv3-specifications/pull/435",
standard="draft IRCv3",
),
CapDef(
identifier="ReadMarker",
name="draft/read-marker",
url="https://github.com/ircv3/ircv3-specifications/pull/489",
standard="draft IRCv3",
),
CapDef(
identifier="Persistence",
name="draft/persistence",
url="https://github.com/ircv3/ircv3-specifications/pull/503",
standard="proposed IRCv3",
),
CapDef(
identifier="Preaway",
name="draft/pre-away",
url="https://github.com/ircv3/ircv3-specifications/pull/514",
standard="proposed IRCv3",
),
CapDef(
identifier="StandardReplies",
name="standard-replies",
url="https://github.com/ircv3/ircv3-specifications/pull/506",
standard="IRCv3",
),
CapDef(
identifier="NoImplicitNames",
name="draft/no-implicit-names",
url="https://github.com/ircv3/ircv3-specifications/pull/527",
standard="proposed IRCv3",
),
CapDef(
identifier="ExtendedISupport",
name="draft/extended-isupport",
url="https://github.com/ircv3/ircv3-specifications/pull/543",
standard="proposed IRCv3",
),
CapDef(
identifier="WebPush",
name="draft/webpush",
url="https://github.com/ircv3/ircv3-specifications/pull/471",
standard="proposed IRCv3",
),
CapDef(
identifier="SojuWebPush",
name="soju.im/webpush",
url="https://github.com/ircv3/ircv3-specifications/pull/471",
standard="Soju/Goguma vendor",
),
CapDef(
identifier="Metadata",
name="draft/metadata-2",
url="https://ircv3.net/specs/extensions/metadata",
standard="draft IRCv3",
),
]
def validate_defs():
@ -206,7 +279,7 @@ package caps
const (
// number of recognized capabilities:
numCapabs = %d
// length of the uint64 array that represents the bitset:
// length of the uint32 array that represents the bitset:
bitsetLen = %d
)
""" % (numCapabs, bitsetLen), file=output)

51
go.mod
View File

@ -1,24 +1,47 @@
module github.com/oragono/oragono
module github.com/ergochat/ergo
go 1.15
go 1.24
require (
code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/go-sql-driver/mysql v1.5.0
github.com/go-test/deep v1.0.6 // indirect
github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1
github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881
github.com/ergochat/irc-go v0.5.0-rc2
github.com/go-sql-driver/mysql v1.7.0
github.com/gofrs/flock v0.8.1
github.com/gorilla/websocket v1.4.2
github.com/goshuirc/irc-go v0.0.0-20200311142257-57fd157327ac
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd
github.com/onsi/ginkgo v1.12.0 // indirect
github.com/onsi/gomega v1.9.0 // indirect
github.com/oragono/confusables v0.0.0-20190624102032-fe1cf31a24b0
github.com/oragono/go-ident v0.0.0-20200511222032-830550b1d775
github.com/stretchr/testify v1.4.0 // indirect
github.com/tidwall/buntdb v1.1.2
github.com/toorop/go-dkim v0.0.0-20200526084421-76378ae5207e
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c // indirect
golang.org/x/text v0.3.3
gopkg.in/yaml.v2 v2.3.0
github.com/tidwall/buntdb v1.3.2
github.com/xdg-go/scram v1.0.2
golang.org/x/crypto v0.38.0
golang.org/x/term v0.32.0
golang.org/x/text v0.25.0
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/emersion/go-msgauth v0.7.0
github.com/ergochat/webpush-go/v2 v2.0.0
github.com/golang-jwt/jwt/v5 v5.2.2
)
require (
github.com/tidwall/btree v1.4.2 // indirect
github.com/tidwall/gjson v1.14.3 // indirect
github.com/tidwall/grect v0.1.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
golang.org/x/sys v0.33.0 // indirect
)
replace github.com/gorilla/websocket => github.com/ergochat/websocket v1.4.2-oragono1
replace github.com/xdg-go/scram => github.com/ergochat/scram v1.0.2-ergo1

130
go.sum
View File

@ -1,85 +1,99 @@
code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 h1:/EMHruHCFXR9xClkGV/t0rmHrdhX4+trQUcBqjwc9xE=
code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc=
github.com/DanielOaks/go-idn v0.0.0-20160120021903-76db0e10dc65/go.mod h1:GYIaL2hleNQvfMUBTes1Zd/lDTyI/p2hv3kYB4jssyU=
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/emersion/go-msgauth v0.6.8 h1:kW/0E9E8Zx5CdKsERC/WnAvnXvX7q9wTHia1OA4944A=
github.com/emersion/go-msgauth v0.6.8/go.mod h1:YDwuyTCUHu9xxmAeVj0eW4INnwB6NNZoPdLerpSxRrc=
github.com/emersion/go-msgauth v0.7.0 h1:vj2hMn6KhFtW41kshIBTXvp6KgYSqpA/ZN9Pv4g1INc=
github.com/emersion/go-msgauth v0.7.0/go.mod h1:mmS9I6HkSovrNgq0HNXTeu8l3sRAAuQ9RMvbM4KU7Ck=
github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1 h1:WLHTOodthVyv5NvYLIvWl112kSFv5IInKKrRN2qpons=
github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1/go.mod h1:mov+uh1DPWsltdQnOdzn08UO9GsJ3MEvhtu0Ci37fdk=
github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881 h1:+J5m88nvybxB5AnBVGzTXM/yHVytt48rXBGcJGzSbms=
github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881/go.mod h1:ASYJtQujNitna6cVHsNQTGrfWvMPJ5Sa2lZlmsH65uM=
github.com/ergochat/irc-go v0.5.0-rc2 h1:VuSQJF5K4hWvYSzGa4b8vgL6kzw8HF6LSOejE+RWpAo=
github.com/ergochat/irc-go v0.5.0-rc2/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
github.com/ergochat/scram v1.0.2-ergo1 h1:2bYXiRFQH636pT0msOG39fmEYl4Eq+OuutcyDsCix/g=
github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/ergochat/webpush-go/v2 v2.0.0 h1:n6eoJk8RpzJFeBJ6gxvqo/dngnVEmJbzJwzKtCZbByo=
github.com/ergochat/webpush-go/v2 v2.0.0/go.mod h1:OQlhnq8JeHDzRzAy6bdDObr19uqbHliOV+z7mHbYr4c=
github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM=
github.com/ergochat/websocket v1.4.2-oragono1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-test/deep v1.0.6 h1:UHSEyLZUwX9Qoi99vVwvewiMC8mM2bf7XEM2nqvzEn8=
github.com/go-test/deep v1.0.6/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940 h1:KmRLPRstEJiE/9OjumKqI8Rccip8Qmyw2FwyTFxtVqs=
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940/go.mod h1:VOmrX6cmj7zwUeexC9HzznUdTIObHqIXUrWNYS+Ik7w=
github.com/goshuirc/irc-go v0.0.0-20200311142257-57fd157327ac h1:0JSojWrghcpK9/wx1RpV9Bv2d+3TbBWtHWubKjU2tao=
github.com/goshuirc/irc-go v0.0.0-20200311142257-57fd157327ac/go.mod h1:BRnLblzpqH2T5ANCODHBZLytz0NZN2KaMJ+di8oh3EM=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd h1:+iAPaTbi1gZpcpDwe/BW1fx7Xoesv69hLNGPheoyhBs=
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd/go.mod h1:4soZNh0zW0LtYGdQ416i0jO0EIqMGcbtaspRS4BDvRQ=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/oragono/confusables v0.0.0-20190624102032-fe1cf31a24b0 h1:4qw57EiWD2MhnmXoQus2ClSyPpGRd8/UxcAmmNe2FCg=
github.com/oragono/confusables v0.0.0-20190624102032-fe1cf31a24b0/go.mod h1:+uesPRay9e5tW6zhw4CJkRV3QOEbbZIJcsuo9ZnC+hE=
github.com/oragono/go-ident v0.0.0-20200511222032-830550b1d775 h1:AMAsAn/i4AgsmWQYdMoze9omwtHpbxrKuT+AT1LmhtI=
github.com/oragono/go-ident v0.0.0-20200511222032-830550b1d775/go.mod h1:r5Fk840a4eu3ii1kxGDNSJupQu9Z1UC1nfJOZZXC24c=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI=
github.com/tidwall/gjson v1.3.4 h1:On5waDnyKKk3SWE4EthbjjirAWXp43xx5cKCUZY1eZw=
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE=
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2KoJQD9cTQ6dyP2co9q4yzmT9FZo=
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao=
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE=
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
github.com/toorop/go-dkim v0.0.0-20200526084421-76378ae5207e h1:uZTp+hhFm+PCH0t0Px5oE+QYlVTwVJ+XKNQr7ct4Q7w=
github.com/toorop/go-dkim v0.0.0-20200526084421-76378ae5207e/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v1.4.2 h1:PpkaieETJMUxYNADsjgtNRcERX7mGc/GP2zp/r5FM3g=
github.com/tidwall/btree v1.4.2/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
github.com/tidwall/buntdb v1.3.2 h1:qd+IpdEGs0pZci37G4jF51+fSKlkuUTMXuHhXL1AkKg=
github.com/tidwall/buntdb v1.3.2/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -91,5 +105,5 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

76
irc/accept.go Normal file
View File

@ -0,0 +1,76 @@
package irc
import (
"sync"
"github.com/ergochat/ergo/irc/utils"
)
// tracks ACCEPT relationships, i.e., `accepter` is willing to receive DMs from
// `accepted` despite some restriction (currently the only relevant restriction
// is that `accepter` is +R and `accepted` is not logged in)
type AcceptManager struct {
sync.RWMutex
// maps recipient -> whitelist of permitted senders:
// this is what we actually check
clientToAccepted map[*Client]utils.HashSet[*Client]
// this is the reverse mapping, it's needed so we can
// clean up the forward mapping during (*Client).destroy():
clientToAccepters map[*Client]utils.HashSet[*Client]
}
func (am *AcceptManager) Initialize() {
am.clientToAccepted = make(map[*Client]utils.HashSet[*Client])
am.clientToAccepters = make(map[*Client]utils.HashSet[*Client])
}
func (am *AcceptManager) MaySendTo(sender, recipient *Client) (result bool) {
am.RLock()
defer am.RUnlock()
return am.clientToAccepted[recipient].Has(sender)
}
func (am *AcceptManager) Accept(accepter, accepted *Client) {
am.Lock()
defer am.Unlock()
var m utils.HashSet[*Client]
m = am.clientToAccepted[accepter]
if m == nil {
m = make(utils.HashSet[*Client])
am.clientToAccepted[accepter] = m
}
m.Add(accepted)
m = am.clientToAccepters[accepted]
if m == nil {
m = make(utils.HashSet[*Client])
am.clientToAccepters[accepted] = m
}
m.Add(accepter)
}
func (am *AcceptManager) Unaccept(accepter, accepted *Client) {
am.Lock()
defer am.Unlock()
delete(am.clientToAccepted[accepter], accepted)
delete(am.clientToAccepters[accepted], accepter)
}
func (am *AcceptManager) Remove(client *Client) {
am.Lock()
defer am.Unlock()
for accepter := range am.clientToAccepters[client] {
delete(am.clientToAccepted[accepter], client)
}
for accepted := range am.clientToAccepted[client] {
delete(am.clientToAccepters[accepted], client)
}
delete(am.clientToAccepters, client)
delete(am.clientToAccepted, client)
}

108
irc/accept_test.go Normal file
View File

@ -0,0 +1,108 @@
package irc
import (
"testing"
)
func TestAccept(t *testing.T) {
var am AcceptManager
am.Initialize()
alice := new(Client)
bob := new(Client)
eve := new(Client)
// must not panic:
am.Unaccept(eve, bob)
assertEqual(am.MaySendTo(alice, bob), false)
assertEqual(am.MaySendTo(bob, alice), false)
assertEqual(am.MaySendTo(alice, eve), false)
assertEqual(am.MaySendTo(eve, alice), false)
assertEqual(am.MaySendTo(bob, eve), false)
assertEqual(am.MaySendTo(eve, bob), false)
am.Accept(alice, bob)
assertEqual(am.MaySendTo(alice, bob), false)
assertEqual(am.MaySendTo(bob, alice), true)
assertEqual(am.MaySendTo(alice, eve), false)
assertEqual(am.MaySendTo(eve, alice), false)
assertEqual(am.MaySendTo(bob, eve), false)
assertEqual(am.MaySendTo(eve, bob), false)
am.Accept(bob, alice)
assertEqual(am.MaySendTo(alice, bob), true)
assertEqual(am.MaySendTo(bob, alice), true)
assertEqual(am.MaySendTo(alice, eve), false)
assertEqual(am.MaySendTo(eve, alice), false)
assertEqual(am.MaySendTo(bob, eve), false)
assertEqual(am.MaySendTo(eve, bob), false)
am.Accept(bob, eve)
assertEqual(am.MaySendTo(alice, bob), true)
assertEqual(am.MaySendTo(bob, alice), true)
assertEqual(am.MaySendTo(alice, eve), false)
assertEqual(am.MaySendTo(eve, alice), false)
assertEqual(am.MaySendTo(bob, eve), false)
assertEqual(am.MaySendTo(eve, bob), true)
am.Accept(eve, bob)
assertEqual(am.MaySendTo(alice, bob), true)
assertEqual(am.MaySendTo(bob, alice), true)
assertEqual(am.MaySendTo(alice, eve), false)
assertEqual(am.MaySendTo(eve, alice), false)
assertEqual(am.MaySendTo(bob, eve), true)
assertEqual(am.MaySendTo(eve, bob), true)
am.Unaccept(eve, bob)
assertEqual(am.MaySendTo(alice, bob), true)
assertEqual(am.MaySendTo(bob, alice), true)
assertEqual(am.MaySendTo(alice, eve), false)
assertEqual(am.MaySendTo(eve, alice), false)
assertEqual(am.MaySendTo(bob, eve), false)
assertEqual(am.MaySendTo(eve, bob), true)
am.Remove(alice)
assertEqual(am.MaySendTo(alice, bob), false)
assertEqual(am.MaySendTo(bob, alice), false)
assertEqual(am.MaySendTo(alice, eve), false)
assertEqual(am.MaySendTo(eve, alice), false)
assertEqual(am.MaySendTo(bob, eve), false)
assertEqual(am.MaySendTo(eve, bob), true)
am.Remove(bob)
assertEqual(am.MaySendTo(alice, bob), false)
assertEqual(am.MaySendTo(bob, alice), false)
assertEqual(am.MaySendTo(alice, eve), false)
assertEqual(am.MaySendTo(eve, alice), false)
assertEqual(am.MaySendTo(bob, eve), false)
assertEqual(am.MaySendTo(eve, bob), false)
}
func TestAcceptInternal(t *testing.T) {
var am AcceptManager
am.Initialize()
alice := new(Client)
bob := new(Client)
eve := new(Client)
am.Accept(alice, bob)
am.Accept(bob, alice)
am.Accept(bob, eve)
am.Remove(alice)
am.Remove(bob)
// assert that there is no memory leak
for _, client := range []*Client{alice, bob, eve} {
assertEqual(len(am.clientToAccepted[client]), 0)
assertEqual(len(am.clientToAccepters[client]), 0)
}
}

File diff suppressed because it is too large Load Diff

311
irc/api.go Normal file
View File

@ -0,0 +1,311 @@
package irc
import (
"crypto/subtle"
"encoding/json"
"fmt"
"net/http"
"runtime"
"strings"
"github.com/ergochat/ergo/irc/utils"
)
func newAPIHandler(server *Server) http.Handler {
api := &ergoAPI{
server: server,
mux: http.NewServeMux(),
}
api.mux.HandleFunc("POST /v1/rehash", api.handleRehash)
api.mux.HandleFunc("POST /v1/check_auth", api.handleCheckAuth)
api.mux.HandleFunc("POST /v1/saregister", api.handleSaregister)
api.mux.HandleFunc("POST /v1/account_details", api.handleAccountDetails)
api.mux.HandleFunc("POST /v1/account_list", api.handleAccountList)
api.mux.HandleFunc("POST /v1/status", api.handleStatus)
return api
}
type ergoAPI struct {
server *Server
mux *http.ServeMux
}
func (a *ergoAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer a.server.HandlePanic(nil)
defer a.server.logger.Debug("api", r.URL.Path)
if a.checkBearerAuth(r.Header.Get("Authorization")) {
a.mux.ServeHTTP(w, r)
} else {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
}
}
func (a *ergoAPI) checkBearerAuth(authHeader string) (authorized bool) {
if authHeader == "" {
return false
}
c := a.server.Config()
if !c.API.Enabled {
return false
}
spaceIdx := strings.IndexByte(authHeader, ' ')
if spaceIdx < 0 {
return false
}
if !strings.EqualFold("Bearer", authHeader[:spaceIdx]) {
return false
}
providedTokenBytes := []byte(authHeader[spaceIdx+1:])
for _, tokenBytes := range c.API.bearerTokenBytes {
if subtle.ConstantTimeCompare(tokenBytes, providedTokenBytes) == 1 {
return true
}
}
return false
}
func (a *ergoAPI) decodeJSONRequest(request any, w http.ResponseWriter, r *http.Request) (err error) {
err = json.NewDecoder(r.Body).Decode(request)
if err != nil {
http.Error(w, fmt.Sprintf("failed to deserialize json request: %v", err), http.StatusBadRequest)
}
return err
}
func (a *ergoAPI) writeJSONResponse(response any, w http.ResponseWriter, r *http.Request) {
j, err := json.Marshal(response)
if err == nil {
j = append(j, '\n') // less annoying in curl output
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(j)
} else {
a.server.logger.Error("internal", "failed to serialize API response", r.URL.Path, err.Error())
http.Error(w, fmt.Sprintf("failed to serialize json response: %v", err), http.StatusInternalServerError)
}
}
type apiGenericResponse struct {
Success bool `json:"success"`
Error string `json:"error,omitempty"`
ErrorCode string `json:"errorCode,omitempty"`
}
func (a *ergoAPI) handleRehash(w http.ResponseWriter, r *http.Request) {
var response apiGenericResponse
err := a.server.rehash()
if err == nil {
response.Success = true
} else {
response.Success = false
response.Error = err.Error()
}
a.writeJSONResponse(response, w, r)
}
type apiCheckAuthResponse struct {
apiGenericResponse
AccountName string `json:"accountName,omitempty"`
}
func (a *ergoAPI) handleCheckAuth(w http.ResponseWriter, r *http.Request) {
var request AuthScriptInput
if err := a.decodeJSONRequest(&request, w, r); err != nil {
return
}
var response apiCheckAuthResponse
// try passphrase if present
if request.AccountName != "" && request.Passphrase != "" {
account, err := a.server.accounts.checkPassphrase(request.AccountName, request.Passphrase)
switch err {
case nil:
// success, no error
response.Success = true
response.AccountName = account.Name
case errAccountDoesNotExist, errAccountInvalidCredentials, errAccountUnverified, errAccountSuspended:
// fail, no error
response.Success = false
default:
response.Success = false
response.Error = err.Error()
}
}
// try certfp if present
if !response.Success && request.Certfp != "" {
// TODO support cerftp
}
a.writeJSONResponse(response, w, r)
}
type apiSaregisterRequest struct {
AccountName string `json:"accountName"`
Passphrase string `json:"passphrase"`
}
func (a *ergoAPI) handleSaregister(w http.ResponseWriter, r *http.Request) {
var request apiSaregisterRequest
if err := a.decodeJSONRequest(&request, w, r); err != nil {
return
}
var response apiGenericResponse
err := a.server.accounts.SARegister(request.AccountName, request.Passphrase)
if err == nil {
response.Success = true
} else {
response.Success = false
response.Error = err.Error()
switch err {
case errAccountAlreadyRegistered, errAccountAlreadyVerified, errNameReserved:
response.ErrorCode = "ACCOUNT_EXISTS"
case errAccountBadPassphrase:
response.ErrorCode = "INVALID_PASSPHRASE"
default:
response.ErrorCode = "UNKNOWN_ERROR"
}
}
a.writeJSONResponse(response, w, r)
}
type apiAccountDetailsResponse struct {
apiGenericResponse
AccountName string `json:"accountName,omitempty"`
Email string `json:"email,omitempty"`
RegisteredAt string `json:"registeredAt,omitempty"`
Channels []string `json:"channels,omitempty"`
}
type apiAccountDetailsRequest struct {
AccountName string `json:"accountName"`
}
func (a *ergoAPI) handleAccountDetails(w http.ResponseWriter, r *http.Request) {
var request apiAccountDetailsRequest
if err := a.decodeJSONRequest(&request, w, r); err != nil {
return
}
var response apiAccountDetailsResponse
if request.AccountName != "" {
accountData, err := a.server.accounts.LoadAccount(request.AccountName)
if err == nil {
if !accountData.Verified {
err = errAccountUnverified
} else if accountData.Suspended != nil {
err = errAccountSuspended
}
}
switch err {
case nil:
response.AccountName = accountData.Name
response.Email = accountData.Settings.Email
if !accountData.RegisteredAt.IsZero() {
response.RegisteredAt = accountData.RegisteredAt.Format(utils.IRCv3TimestampFormat)
}
// Get channels the account is in
response.Channels = a.server.channels.ChannelsForAccount(accountData.NameCasefolded)
response.Success = true
case errAccountDoesNotExist, errAccountUnverified, errAccountSuspended:
response.Success = false
default:
response.Success = false
response.ErrorCode = "UNKNOWN_ERROR"
response.Error = err.Error()
}
} else {
response.Success = false
response.ErrorCode = "INVALID_REQUEST"
}
a.writeJSONResponse(response, w, r)
}
type apiAccountListResponse struct {
apiGenericResponse
Accounts []apiAccountDetailsResponse `json:"accounts"`
TotalCount int `json:"totalCount"`
}
func (a *ergoAPI) handleAccountList(w http.ResponseWriter, r *http.Request) {
var response apiAccountListResponse
// Get all account names
accounts := a.server.accounts.AllNicks()
response.TotalCount = len(accounts)
// Load account details
response.Accounts = make([]apiAccountDetailsResponse, len(accounts))
for i, account := range accounts {
accountData, err := a.server.accounts.LoadAccount(account)
if err != nil {
response.Accounts[i] = apiAccountDetailsResponse{
apiGenericResponse: apiGenericResponse{
Success: false,
Error: err.Error(),
},
}
continue
}
response.Accounts[i] = apiAccountDetailsResponse{
apiGenericResponse: apiGenericResponse{
Success: true,
},
AccountName: accountData.Name,
Email: accountData.Settings.Email,
}
}
response.Success = true
a.writeJSONResponse(response, w, r)
}
type apiStatusResponse struct {
apiGenericResponse
Version string `json:"version"`
GoVersion string `json:"go_version"`
Commit string `json:"commit,omitempty"`
StartTime string `json:"start_time"`
Users struct {
Total int `json:"total"`
Invisible int `json:"invisible"`
Operators int `json:"operators"`
Unknown int `json:"unknown"`
Max int `json:"max"`
} `json:"users"`
Channels int `json:"channels"`
Servers int `json:"servers"`
}
func (a *ergoAPI) handleStatus(w http.ResponseWriter, r *http.Request) {
server := a.server
stats := server.stats.GetValues()
response := apiStatusResponse{
apiGenericResponse: apiGenericResponse{Success: true},
Version: SemVer,
GoVersion: runtime.Version(),
Commit: Commit,
StartTime: server.ctime.Format(utils.IRCv3TimestampFormat),
}
response.Users.Total = stats.Total
response.Users.Invisible = stats.Invisible
response.Users.Operators = stats.Operators
response.Users.Unknown = stats.Unknown
response.Users.Max = stats.Max
response.Channels = server.channels.Len()
response.Servers = 1
a.writeJSONResponse(response, w, r)
}

View File

@ -4,21 +4,25 @@
package irc
import (
"bufio"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"os/exec"
"syscall"
"time"
"net"
"github.com/ergochat/ergo/irc/oauth2"
"github.com/ergochat/ergo/irc/utils"
)
// JSON-serializable input and output types for the script
type AuthScriptInput struct {
AccountName string `json:"accountName,omitempty"`
Passphrase string `json:"passphrase,omitempty"`
Certfp string `json:"certfp,omitempty"`
IP string `json:"ip,omitempty"`
AccountName string `json:"accountName,omitempty"`
Passphrase string `json:"passphrase,omitempty"`
Certfp string `json:"certfp,omitempty"`
PeerCerts []string `json:"peerCerts,omitempty"`
peerCerts []*x509.Certificate
IP string `json:"ip,omitempty"`
OAuthBearer *oauth2.OAuthBearerOptions `json:"oauth2,omitempty"`
}
type AuthScriptOutput struct {
@ -27,84 +31,85 @@ type AuthScriptOutput struct {
Error string `json:"error"`
}
// internal tupling of output and error for passing over a channel
type authScriptResponse struct {
output AuthScriptOutput
err error
}
func CheckAuthScript(sem utils.Semaphore, config ScriptConfig, input AuthScriptInput) (output AuthScriptOutput, err error) {
if sem != nil {
sem.Acquire()
defer sem.Release()
}
// PEM-encode the peer certificates before applying JSON
if len(input.peerCerts) != 0 {
input.PeerCerts = make([]string, len(input.peerCerts))
for i, cert := range input.peerCerts {
input.PeerCerts[i] = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))
}
}
func CheckAuthScript(config AuthScriptConfig, input AuthScriptInput) (output AuthScriptOutput, err error) {
inputBytes, err := json.Marshal(input)
if err != nil {
return
}
cmd := exec.Command(config.Command, config.Args...)
stdin, err := cmd.StdinPipe()
outBytes, err := RunScript(config.Command, config.Args, inputBytes, config.Timeout, config.KillTimeout)
if err != nil {
return
}
stdout, err := cmd.StdoutPipe()
err = json.Unmarshal(outBytes, &output)
if err != nil {
return
}
channel := make(chan authScriptResponse, 1)
err = cmd.Start()
if err != nil {
return
if output.Error != "" {
err = fmt.Errorf("Authentication process reported error: %s", output.Error)
}
stdin.Write(inputBytes)
stdin.Write([]byte{'\n'})
// lots of potential race conditions here. we want to ensure that Wait()
// will be called, and will return, on the other goroutine, no matter
// where it is blocked. If it's blocked on ReadBytes(), we will kill it
// (first with SIGTERM, then with SIGKILL) and ReadBytes will return
// with EOF. If it's blocked on Wait(), then one of the kill signals
// will succeed and unblock it.
go processAuthScriptOutput(cmd, stdout, channel)
outputTimer := time.NewTimer(config.Timeout)
select {
case response := <-channel:
return response.output, response.err
case <-outputTimer.C:
}
err = errTimedOut
cmd.Process.Signal(syscall.SIGTERM)
termTimer := time.NewTimer(config.Timeout)
select {
case <-channel:
return
case <-termTimer.C:
}
cmd.Process.Kill()
return
}
func processAuthScriptOutput(cmd *exec.Cmd, stdout io.Reader, channel chan authScriptResponse) {
var response authScriptResponse
var out AuthScriptOutput
type IPScriptResult uint
reader := bufio.NewReader(stdout)
outBytes, err := reader.ReadBytes('\n')
if err == nil {
err = json.Unmarshal(outBytes, &out)
if err == nil {
response.output = out
if out.Error != "" {
err = fmt.Errorf("Authentication process reported error: %s", out.Error)
}
}
}
response.err = err
const (
IPNotChecked IPScriptResult = 0
IPAccepted IPScriptResult = 1
IPBanned IPScriptResult = 2
IPRequireSASL IPScriptResult = 3
)
// always call Wait() to ensure resource cleanup
err = cmd.Wait()
if err != nil {
response.err = err
}
channel <- response
type IPScriptInput struct {
IP string `json:"ip"`
}
type IPScriptOutput struct {
Result IPScriptResult `json:"result"`
BanMessage string `json:"banMessage"`
// for caching: the network to which this result is applicable, and a TTL in seconds:
CacheNet string `json:"cacheNet"`
CacheSeconds int `json:"cacheSeconds"`
Error string `json:"error"`
}
func CheckIPBan(sem utils.Semaphore, config IPCheckScriptConfig, addr net.IP) (output IPScriptOutput, err error) {
if sem != nil {
sem.Acquire()
defer sem.Release()
}
inputBytes, err := json.Marshal(IPScriptInput{IP: addr.String()})
if err != nil {
return
}
outBytes, err := RunScript(config.Command, config.Args, inputBytes, config.Timeout, config.KillTimeout)
if err != nil {
return
}
err = json.Unmarshal(outBytes, &output)
if err != nil {
return
}
if output.Error != "" {
err = fmt.Errorf("IP ban process reported error: %s", output.Error)
} else if !(IPAccepted <= output.Result && output.Result <= IPRequireSASL) {
err = fmt.Errorf("Invalid result from IP checking script: %d", output.Result)
}
return
}

107
irc/bunt/bunt_datastore.go Normal file
View File

@ -0,0 +1,107 @@
// Copyright (c) 2022 Shivaram Lingamneni
// released under the MIT license
package bunt
import (
"fmt"
"strings"
"time"
"github.com/tidwall/buntdb"
"github.com/ergochat/ergo/irc/datastore"
"github.com/ergochat/ergo/irc/logger"
"github.com/ergochat/ergo/irc/utils"
)
// BuntKey yields a string key corresponding to a (table, UUID) pair.
// Ideally this would not be public, but some of the migration code
// needs it.
func BuntKey(table datastore.Table, uuid utils.UUID) string {
return fmt.Sprintf("%x %s", table, uuid.String())
}
// buntdbDatastore implements datastore.Datastore using a buntdb.
type buntdbDatastore struct {
db *buntdb.DB
logger *logger.Manager
}
// NewBuntdbDatastore returns a datastore.Datastore backed by buntdb.
func NewBuntdbDatastore(db *buntdb.DB, logger *logger.Manager) datastore.Datastore {
return &buntdbDatastore{
db: db,
logger: logger,
}
}
func (b *buntdbDatastore) Backoff() time.Duration {
return 0
}
func (b *buntdbDatastore) GetAll(table datastore.Table) (result []datastore.KV, err error) {
tablePrefix := fmt.Sprintf("%x ", table)
err = b.db.View(func(tx *buntdb.Tx) error {
err := tx.AscendGreaterOrEqual("", tablePrefix, func(key, value string) bool {
encUUID, ok := strings.CutPrefix(key, tablePrefix)
if !ok {
return false
}
uuid, err := utils.DecodeUUID(encUUID)
if err == nil {
result = append(result, datastore.KV{UUID: uuid, Value: []byte(value)})
} else {
b.logger.Error("datastore", "invalid uuid", key)
}
return true
})
return err
})
return
}
func (b *buntdbDatastore) Get(table datastore.Table, uuid utils.UUID) (value []byte, err error) {
buntKey := BuntKey(table, uuid)
var result string
err = b.db.View(func(tx *buntdb.Tx) error {
result, err = tx.Get(buntKey)
return err
})
return []byte(result), err
}
func (b *buntdbDatastore) Set(table datastore.Table, uuid utils.UUID, value []byte, expiration time.Time) (err error) {
buntKey := BuntKey(table, uuid)
var setOptions *buntdb.SetOptions
if !expiration.IsZero() {
ttl := time.Until(expiration)
if ttl > 0 {
setOptions = &buntdb.SetOptions{Expires: true, TTL: ttl}
} else {
return nil // it already expired, i guess?
}
}
strVal := string(value)
err = b.db.Update(func(tx *buntdb.Tx) error {
_, _, err := tx.Set(buntKey, strVal, setOptions)
return err
})
return
}
func (b *buntdbDatastore) Delete(table datastore.Table, key utils.UUID) (err error) {
buntKey := BuntKey(table, key)
err = b.db.Update(func(tx *buntdb.Tx) error {
_, err := tx.Delete(buntKey)
return err
})
// deleting a nonexistent key is not considered an error
switch err {
case buntdb.ErrNotFound:
return nil
default:
return err
}
}

View File

@ -58,10 +58,17 @@ const (
// More draft names associated with draft/multiline:
MultilineBatchType = "draft/multiline"
MultilineConcatTag = "draft/multiline-concat"
// draft/relaymsg:
RelaymsgTagName = "draft/relaymsg"
// BOT mode: https://ircv3.net/specs/extensions/bot-mode
BotTagName = "bot"
// https://ircv3.net/specs/extensions/chathistory
ChathistoryTargetsBatchType = "draft/chathistory-targets"
ExtendedISupportBatchType = "draft/isupport"
)
func init() {
nameToCapability = make(map[string]Capability)
nameToCapability = make(map[string]Capability, numCapabs)
for capab, name := range capabilityNames {
nameToCapability[name] = Capability(capab)
}

View File

@ -7,9 +7,9 @@ package caps
const (
// number of recognized capabilities:
numCapabs = 26
// length of the uint64 array that represents the bitset:
bitsetLen = 1
numCapabs = 38
// length of the uint32 array that represents the bitset:
bitsetLen = 2
)
const (
@ -37,6 +37,14 @@ const (
// https://ircv3.net/specs/extensions/chghost-3.2.html
ChgHost Capability = iota
// AccountRegistration is the draft IRCv3 capability named "draft/account-registration":
// https://github.com/ircv3/ircv3-specifications/pull/435
AccountRegistration Capability = iota
// ChannelRename is the draft IRCv3 capability named "draft/channel-rename":
// https://ircv3.net/specs/extensions/channel-rename
ChannelRename Capability = iota
// Chathistory is the proposed IRCv3 capability named "draft/chathistory":
// https://github.com/ircv3/ircv3-specifications/pull/393
Chathistory Capability = iota
@ -45,30 +53,66 @@ const (
// https://github.com/ircv3/ircv3-specifications/pull/362
EventPlayback Capability = iota
// ExtendedISupport is the proposed IRCv3 capability named "draft/extended-isupport":
// https://github.com/ircv3/ircv3-specifications/pull/543
ExtendedISupport Capability = iota
// Languages is the proposed IRCv3 capability named "draft/languages":
// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
Languages Capability = iota
// MessageRedaction is the proposed IRCv3 capability named "draft/message-redaction":
// https://github.com/progval/ircv3-specifications/blob/redaction/extensions/message-redaction.md
MessageRedaction Capability = iota
// Metadata is the draft IRCv3 capability named "draft/metadata-2":
// https://ircv3.net/specs/extensions/metadata
Metadata Capability = iota
// Multiline is the proposed IRCv3 capability named "draft/multiline":
// https://github.com/ircv3/ircv3-specifications/pull/398
Multiline Capability = iota
// Rename is the proposed IRCv3 capability named "draft/rename":
// https://github.com/SaberUK/ircv3-specifications/blob/rename/extensions/rename.md
Rename Capability = iota
// NoImplicitNames is the proposed IRCv3 capability named "draft/no-implicit-names":
// https://github.com/ircv3/ircv3-specifications/pull/527
NoImplicitNames Capability = iota
// Resume is the proposed IRCv3 capability named "draft/resume-0.5":
// https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md
Resume Capability = iota
// Persistence is the proposed IRCv3 capability named "draft/persistence":
// https://github.com/ircv3/ircv3-specifications/pull/503
Persistence Capability = iota
// Preaway is the proposed IRCv3 capability named "draft/pre-away":
// https://github.com/ircv3/ircv3-specifications/pull/514
Preaway Capability = iota
// ReadMarker is the draft IRCv3 capability named "draft/read-marker":
// https://github.com/ircv3/ircv3-specifications/pull/489
ReadMarker Capability = iota
// Relaymsg is the proposed IRCv3 capability named "draft/relaymsg":
// https://github.com/ircv3/ircv3-specifications/pull/417
Relaymsg Capability = iota
// WebPush is the proposed IRCv3 capability named "draft/webpush":
// https://github.com/ircv3/ircv3-specifications/pull/471
WebPush Capability = iota
// EchoMessage is the IRCv3 capability named "echo-message":
// https://ircv3.net/specs/extensions/echo-message-3.2.html
EchoMessage Capability = iota
// Nope is the Ergo vendor capability named "ergo.chat/nope":
// https://ergo.chat/nope
Nope Capability = iota
// ExtendedJoin is the IRCv3 capability named "extended-join":
// https://ircv3.net/specs/extensions/extended-join-3.1.html
ExtendedJoin Capability = iota
// ExtendedMonitor is the IRCv3 capability named "extended-monitor":
// https://ircv3.net/specs/extensions/extended-monitor.html
ExtendedMonitor Capability = iota
// InviteNotify is the IRCv3 capability named "invite-notify":
// https://ircv3.net/specs/extensions/invite-notify-3.2.html
InviteNotify Capability = iota
@ -85,10 +129,6 @@ const (
// https://ircv3.net/specs/extensions/multi-prefix-3.1.html
MultiPrefix Capability = iota
// Nope is the Oragono vendor capability named "oragono.io/nope":
// https://oragono.io/nope
Nope Capability = iota
// SASL is the IRCv3 capability named "sasl":
// https://ircv3.net/specs/extensions/sasl-3.2.html
SASL Capability = iota
@ -101,6 +141,14 @@ const (
// https://ircv3.net/specs/extensions/setname.html
SetName Capability = iota
// SojuWebPush is the Soju/Goguma vendor capability named "soju.im/webpush":
// https://github.com/ircv3/ircv3-specifications/pull/471
SojuWebPush Capability = iota
// StandardReplies is the IRCv3 capability named "standard-replies":
// https://github.com/ircv3/ircv3-specifications/pull/506
StandardReplies Capability = iota
// STS is the IRCv3 capability named "sts":
// https://ircv3.net/specs/extensions/sts.html
STS Capability = iota
@ -127,22 +175,34 @@ var (
"batch",
"cap-notify",
"chghost",
"draft/account-registration",
"draft/channel-rename",
"draft/chathistory",
"draft/event-playback",
"draft/extended-isupport",
"draft/languages",
"draft/message-redaction",
"draft/metadata-2",
"draft/multiline",
"draft/rename",
"draft/resume-0.5",
"draft/no-implicit-names",
"draft/persistence",
"draft/pre-away",
"draft/read-marker",
"draft/relaymsg",
"draft/webpush",
"echo-message",
"ergo.chat/nope",
"extended-join",
"extended-monitor",
"invite-notify",
"labeled-response",
"message-tags",
"multi-prefix",
"oragono.io/nope",
"sasl",
"server-time",
"setname",
"soju.im/webpush",
"standard-replies",
"sts",
"userhost-in-names",
"znc.in/playback",

View File

@ -5,7 +5,7 @@ package caps
import (
"fmt"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/utils"
)
// Set holds a set of enabled capabilities.
@ -102,6 +102,13 @@ func (s *Set) Strings(version Version, values Values, maxLen int) (result []stri
var capab Capability
asSlice := s[:]
for capab = 0; capab < numCapabs; capab++ {
// XXX clients that only support CAP LS 301 cannot handle multiline
// responses. omit some CAPs in this case, forcing the response to fit on
// a single line. this is technically buggy for CAP LIST (as opposed to LS)
// but it shouldn't matter
if version < Cap302 && !isAllowed301(capab) {
continue
}
// skip any capabilities that are not enabled
if !utils.BitsetGet(asSlice, uint(capab)) {
continue
@ -122,3 +129,15 @@ func (s *Set) Strings(version Version, values Values, maxLen int) (result []stri
}
return
}
// this is a fixed whitelist of caps that are eligible for display in CAP LS 301
func isAllowed301(capab Capability) bool {
switch capab {
case AccountNotify, AccountTag, AwayNotify, Batch, ChgHost, Chathistory, EventPlayback,
Relaymsg, EchoMessage, Nope, ExtendedJoin, InviteNotify, LabeledResponse, MessageTags,
MultiPrefix, SASL, ServerTime, SetName, STS, UserhostInNames, ZNCSelfMessage, ZNCPlayback:
return true
default:
return false
}
}

View File

@ -3,8 +3,11 @@
package caps
import "testing"
import "reflect"
import (
"fmt"
"reflect"
"testing"
)
func TestSets(t *testing.T) {
s1 := NewSet()
@ -60,6 +63,19 @@ func TestSets(t *testing.T) {
}
}
func assertEqual(found, expected interface{}) {
if !reflect.DeepEqual(found, expected) {
panic(fmt.Sprintf("found %#v, expected %#v", found, expected))
}
}
func Test301WhitelistNotRespectedFor302(t *testing.T) {
s1 := NewSet()
s1.Enable(AccountTag, EchoMessage, StandardReplies)
assertEqual(s1.Strings(Cap301, nil, 0), []string{"account-tag echo-message"})
assertEqual(s1.Strings(Cap302, nil, 0), []string{"account-tag echo-message standard-replies"})
}
func TestSubtract(t *testing.T) {
s1 := NewSet(AccountTag, EchoMessage, UserhostInNames, ServerTime)
@ -78,14 +94,14 @@ func BenchmarkSetReads(b *testing.B) {
set.Has(UserhostInNames)
set.Has(LabeledResponse)
set.Has(EchoMessage)
set.Has(Rename)
set.Has(Nope)
}
}
func BenchmarkSetWrites(b *testing.B) {
for i := 0; i < b.N; i++ {
set := NewSet(UserhostInNames, EchoMessage)
set.Add(Rename)
set.Add(Nope)
set.Add(ExtendedJoin)
set.Remove(UserhostInNames)
set.Remove(LabeledResponse)

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,12 @@
package irc
import (
"sort"
"sync"
"time"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/datastore"
"github.com/ergochat/ergo/irc/utils"
)
type channelManagerEntry struct {
@ -24,119 +27,129 @@ type channelManagerEntry struct {
type ChannelManager struct {
sync.RWMutex // tier 2
// chans is the main data structure, mapping casefolded name -> *Channel
chans map[string]*channelManagerEntry
chansSkeletons utils.StringSet // skeletons of *unregistered* chans
registeredChannels utils.StringSet // casefolds of registered chans
registeredSkeletons utils.StringSet // skeletons of registered chans
purgedChannels utils.StringSet // casefolds of purged chans
server *Server
chans map[string]*channelManagerEntry
chansSkeletons utils.HashSet[string]
purgedChannels map[string]ChannelPurgeRecord // casefolded name to purge record
server *Server
}
// NewChannelManager returns a new ChannelManager.
func (cm *ChannelManager) Initialize(server *Server) {
func (cm *ChannelManager) Initialize(server *Server, config *Config) (err error) {
cm.chans = make(map[string]*channelManagerEntry)
cm.chansSkeletons = make(utils.StringSet)
cm.chansSkeletons = make(utils.HashSet[string])
cm.server = server
cm.loadRegisteredChannels(server.Config())
// purging should work even if registration is disabled
cm.purgedChannels = cm.server.channelRegistry.PurgedChannels()
return cm.loadRegisteredChannels(config)
}
func (cm *ChannelManager) loadRegisteredChannels(config *Config) {
if !config.Channels.Registration.Enabled {
func (cm *ChannelManager) loadRegisteredChannels(config *Config) (err error) {
allChannels, err := FetchAndDeserializeAll[RegisteredChannel](datastore.TableChannels, cm.server.dstore, cm.server.logger)
if err != nil {
return
}
allPurgeRecords, err := FetchAndDeserializeAll[ChannelPurgeRecord](datastore.TableChannelPurges, cm.server.dstore, cm.server.logger)
if err != nil {
return
}
rawNames := cm.server.channelRegistry.AllChannels()
registeredChannels := make(utils.StringSet, len(rawNames))
registeredSkeletons := make(utils.StringSet, len(rawNames))
for _, name := range rawNames {
cfname, err := CasefoldChannel(name)
if err == nil {
registeredChannels.Add(cfname)
}
skeleton, err := Skeleton(name)
if err == nil {
registeredSkeletons.Add(skeleton)
}
}
cm.Lock()
defer cm.Unlock()
cm.registeredChannels = registeredChannels
cm.registeredSkeletons = registeredSkeletons
cm.purgedChannels = make(map[string]ChannelPurgeRecord, len(allPurgeRecords))
for _, purge := range allPurgeRecords {
cm.purgedChannels[purge.NameCasefolded] = purge
}
for _, regInfo := range allChannels {
cfname, err := CasefoldChannel(regInfo.Name)
if err != nil {
cm.server.logger.Error("channels", "couldn't casefold registered channel, skipping", regInfo.Name, err.Error())
continue
} else {
cm.server.logger.Debug("channels", "initializing registered channel", regInfo.Name)
}
skeleton, err := Skeleton(regInfo.Name)
if err == nil {
cm.chansSkeletons.Add(skeleton)
}
if _, ok := cm.purgedChannels[cfname]; !ok {
ch := NewChannel(cm.server, regInfo.Name, cfname, true, regInfo)
cm.chans[cfname] = &channelManagerEntry{
channel: ch,
pendingJoins: 0,
skeleton: skeleton,
}
}
}
return nil
}
// Get returns an existing channel with name equivalent to `name`, or nil
func (cm *ChannelManager) Get(name string) (channel *Channel) {
name, err := CasefoldChannel(name)
if err == nil {
cm.RLock()
defer cm.RUnlock()
entry := cm.chans[name]
// if the channel is still loading, pretend we don't have it
if entry != nil && entry.channel.IsLoaded() {
return entry.channel
}
if err != nil {
return nil
}
cm.RLock()
defer cm.RUnlock()
entry := cm.chans[name]
if entry != nil {
return entry.channel
}
return nil
}
// Join causes `client` to join the channel named `name`, creating it if necessary.
func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) error {
func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) (err error, forward string) {
server := client.server
casefoldedName, err := CasefoldChannel(name)
skeleton, skerr := Skeleton(name)
if err != nil || skerr != nil || len(casefoldedName) > server.Config().Limits.ChannelLen {
return errNoSuchChannel
return errNoSuchChannel, ""
}
channel, err := func() (*Channel, error) {
channel, err, newChannel := func() (*Channel, error, bool) {
var newChannel bool
cm.Lock()
defer cm.Unlock()
if cm.purgedChannels.Has(casefoldedName) {
return nil, errChannelPurged
// check purges first; a registered purged channel will still be present in `chans`
if _, ok := cm.purgedChannels[casefoldedName]; ok {
return nil, errChannelPurged, false
}
entry := cm.chans[casefoldedName]
if entry == nil {
registered := cm.registeredChannels.Has(casefoldedName)
// enforce OpOnlyCreation
if !registered && server.Config().Channels.OpOnlyCreation && !client.HasRoleCapabs("chanreg") {
return nil, errInsufficientPrivs
if server.Config().Channels.OpOnlyCreation &&
!(isSajoin || client.HasRoleCapabs("chanreg")) {
return nil, errInsufficientPrivs, false
}
// enforce confusables
if cm.chansSkeletons.Has(skeleton) || (!registered && cm.registeredSkeletons.Has(skeleton)) {
return nil, errConfusableIdentifier
if cm.chansSkeletons.Has(skeleton) {
return nil, errConfusableIdentifier, false
}
entry = &channelManagerEntry{
channel: NewChannel(server, name, casefoldedName, registered),
channel: NewChannel(server, name, casefoldedName, false, RegisteredChannel{}),
pendingJoins: 0,
}
if !registered {
// for an unregistered channel, we already have the correct unfolded name
// and therefore the final skeleton. for a registered channel, we don't have
// the unfolded name yet (it needs to be loaded from the db), but we already
// have the final skeleton in `registeredSkeletons` so we don't need to track it
cm.chansSkeletons.Add(skeleton)
entry.skeleton = skeleton
}
cm.chansSkeletons.Add(skeleton)
entry.skeleton = skeleton
cm.chans[casefoldedName] = entry
newChannel = true
}
entry.pendingJoins += 1
return entry.channel, nil
return entry.channel, nil, newChannel
}()
if err != nil {
return err
return err, ""
}
channel.EnsureLoaded()
err = channel.Join(client, key, isSajoin, rb)
err, forward = channel.Join(client, key, isSajoin || newChannel, rb)
cm.maybeCleanup(channel, true)
return err
return
}
func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
@ -150,6 +163,10 @@ func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
return
}
cm.maybeCleanupInternal(cfname, entry, afterJoin)
}
func (cm *ChannelManager) maybeCleanupInternal(cfname string, entry *channelManagerEntry, afterJoin bool) {
if afterJoin {
entry.pendingJoins -= 1
}
@ -189,6 +206,10 @@ func (cm *ChannelManager) Cleanup(channel *Channel) {
}
func (cm *ChannelManager) SetRegistered(channelName string, account string) (err error) {
if account == "" {
return errAuthRequired // this is already enforced by ChanServ, but do a final check
}
if cm.server.Defcon() <= 4 {
return errFeatureDisabled
}
@ -219,10 +240,6 @@ func (cm *ChannelManager) SetRegistered(channelName string, account string) (err
if err != nil {
return err
}
cm.registeredChannels.Add(cfname)
if skel, err := Skeleton(channel.Name()); err == nil {
cm.registeredSkeletons.Add(skel)
}
return nil
}
@ -232,17 +249,13 @@ func (cm *ChannelManager) SetUnregistered(channelName string, account string) (e
return err
}
info, err := cm.server.channelRegistry.LoadChannel(cfname)
if err != nil {
return err
}
if info.Founder != account {
return errChannelNotOwnedByAccount
}
var uuid utils.UUID
defer func() {
if err == nil {
err = cm.server.channelRegistry.Delete(info)
if delErr := cm.server.dstore.Delete(datastore.TableChannels, uuid); delErr != nil {
cm.server.logger.Error("datastore", "couldn't delete channel registration", cfname, delErr.Error())
}
}
}()
@ -250,18 +263,21 @@ func (cm *ChannelManager) SetUnregistered(channelName string, account string) (e
defer cm.Unlock()
entry := cm.chans[cfname]
if entry != nil {
entry.channel.SetUnregistered(account)
delete(cm.registeredChannels, cfname)
if skel, err := Skeleton(entry.channel.Name()); err == nil {
delete(cm.registeredSkeletons, skel)
if entry.channel.Founder() != account {
return errChannelNotOwnedByAccount
}
uuid = entry.channel.UUID()
entry.channel.SetUnregistered(account) // changes the UUID
// #1619: if the channel has 0 members and was only being retained
// because it was registered, clean it up:
cm.maybeCleanupInternal(cfname, entry, false)
}
return nil
}
// Rename renames a channel (but does not notify the members)
func (cm *ChannelManager) Rename(name string, newName string) (err error) {
cfname, err := CasefoldChannel(name)
oldCfname, err := CasefoldChannel(name)
if err != nil {
return errNoSuchChannel
}
@ -279,52 +295,49 @@ func (cm *ChannelManager) Rename(name string, newName string) (err error) {
var info RegisteredChannel
defer func() {
if channel != nil && info.Founder != "" {
channel.Store(IncludeAllAttrs)
// we just flushed the channel under its new name, therefore this delete
// cannot be overwritten by a write to the old name:
cm.server.channelRegistry.Delete(info)
channel.MarkDirty(IncludeAllAttrs)
}
// always-on clients need to update their saved channel memberships
for _, member := range channel.Members() {
member.markDirty(IncludeChannels)
}
}()
cm.Lock()
defer cm.Unlock()
if newCfname == cfname {
entry := cm.chans[cfname]
if entry == nil || !entry.channel.IsLoaded() {
return errNoSuchChannel
}
entry.channel.Rename(newName, cfname)
return nil
}
if cm.chans[newCfname] != nil || cm.registeredChannels.Has(newCfname) {
return errChannelNameInUse
}
if cm.chansSkeletons.Has(newSkeleton) || cm.registeredSkeletons.Has(newSkeleton) {
return errChannelNameInUse
}
entry := cm.chans[cfname]
if entry == nil || !entry.channel.IsLoaded() {
entry := cm.chans[oldCfname]
if entry == nil {
return errNoSuchChannel
}
channel = entry.channel
info = channel.ExportRegistration(IncludeInitial)
info = channel.ExportRegistration()
registered := info.Founder != ""
delete(cm.chans, cfname)
cm.chans[newCfname] = entry
if registered {
delete(cm.registeredChannels, cfname)
if oldSkeleton, err := Skeleton(info.Name); err == nil {
delete(cm.registeredSkeletons, oldSkeleton)
}
cm.registeredChannels.Add(newCfname)
cm.registeredSkeletons.Add(newSkeleton)
} else {
delete(cm.chansSkeletons, entry.skeleton)
cm.chansSkeletons.Add(newSkeleton)
entry.skeleton = newSkeleton
cm.chans[cfname] = entry
oldSkeleton, err := Skeleton(info.Name)
if err != nil {
return errNoSuchChannel // ugh
}
if newCfname != oldCfname {
if cm.chans[newCfname] != nil {
return errChannelNameInUse
}
}
if oldSkeleton != newSkeleton {
if cm.chansSkeletons.Has(newSkeleton) {
return errConfusableIdentifier
}
}
delete(cm.chans, oldCfname)
if !registered {
entry.skeleton = newSkeleton
}
cm.chans[newCfname] = entry
delete(cm.chansSkeletons, oldSkeleton)
cm.chansSkeletons.Add(newSkeleton)
entry.channel.Rename(newName, newCfname)
return nil
}
@ -342,7 +355,18 @@ func (cm *ChannelManager) Channels() (result []*Channel) {
defer cm.RUnlock()
result = make([]*Channel, 0, len(cm.chans))
for _, entry := range cm.chans {
if entry.channel.IsLoaded() {
result = append(result, entry.channel)
}
return
}
// ListableChannels returns a slice of all non-purged channels.
func (cm *ChannelManager) ListableChannels() (result []*Channel) {
cm.RLock()
defer cm.RUnlock()
result = make([]*Channel, 0, len(cm.chans))
for cfname, entry := range cm.chans {
if _, ok := cm.purgedChannels[cfname]; !ok {
result = append(result, entry.channel)
}
}
@ -356,12 +380,45 @@ func (cm *ChannelManager) Purge(chname string, record ChannelPurgeRecord) (err e
return errInvalidChannelName
}
cm.Lock()
cm.purgedChannels.Add(chname)
cm.Unlock()
record.NameCasefolded = chname
record.UUID = utils.GenerateUUIDv4()
cm.server.channelRegistry.PurgeChannel(chname, record)
return nil
channel, err := func() (channel *Channel, err error) {
cm.Lock()
defer cm.Unlock()
if _, ok := cm.purgedChannels[chname]; ok {
return nil, errChannelPurgedAlready
}
entry := cm.chans[chname]
// atomically prevent anyone from rejoining
cm.purgedChannels[chname] = record
if entry != nil {
channel = entry.channel
}
return
}()
if err != nil {
return err
}
if channel != nil {
// actually kick everyone off the channel
channel.Purge("")
}
var purgeBytes []byte
if purgeBytes, err = record.Serialize(); err != nil {
cm.server.logger.Error("internal", "couldn't serialize purge record", channel.Name(), err.Error())
}
// TODO we need a better story about error handling for later
if err = cm.server.dstore.Set(datastore.TableChannelPurges, record.UUID, purgeBytes, time.Time{}); err != nil {
cm.server.logger.Error("datastore", "couldn't store purge record", chname, err.Error())
}
return
}
// IsPurged queries whether a channel is purged.
@ -372,7 +429,7 @@ func (cm *ChannelManager) IsPurged(chname string) (result bool) {
}
cm.RLock()
result = cm.purgedChannels.Has(chname)
_, result = cm.purgedChannels[chname]
cm.RUnlock()
return
}
@ -385,13 +442,74 @@ func (cm *ChannelManager) Unpurge(chname string) (err error) {
}
cm.Lock()
found := cm.purgedChannels.Has(chname)
record, found := cm.purgedChannels[chname]
delete(cm.purgedChannels, chname)
cm.Unlock()
cm.server.channelRegistry.UnpurgeChannel(chname)
if !found {
return errNoSuchChannel
}
if err := cm.server.dstore.Delete(datastore.TableChannelPurges, record.UUID); err != nil {
cm.server.logger.Error("datastore", "couldn't delete purge record", chname, err.Error())
}
return nil
}
func (cm *ChannelManager) ListPurged() (result []string) {
cm.RLock()
result = make([]string, 0, len(cm.purgedChannels))
for c := range cm.purgedChannels {
result = append(result, c)
}
cm.RUnlock()
sort.Strings(result)
return
}
func (cm *ChannelManager) UnfoldName(cfname string) (result string) {
cm.RLock()
entry := cm.chans[cfname]
cm.RUnlock()
if entry != nil {
return entry.channel.Name()
}
return cfname
}
func (cm *ChannelManager) LoadPurgeRecord(cfchname string) (record ChannelPurgeRecord, err error) {
cm.RLock()
defer cm.RUnlock()
if record, ok := cm.purgedChannels[cfchname]; ok {
return record, nil
} else {
return record, errNoSuchChannel
}
}
func (cm *ChannelManager) ChannelsForAccount(account string) (channels []string) {
cm.RLock()
defer cm.RUnlock()
for cfname, entry := range cm.chans {
if entry.channel.Founder() == account {
channels = append(channels, cfname)
}
}
return
}
// AllChannels returns the uncasefolded names of all registered channels.
func (cm *ChannelManager) AllRegisteredChannels() (result []string) {
cm.RLock()
defer cm.RUnlock()
for cfname, entry := range cm.chans {
if entry.channel.Founder() != "" {
result = append(result, cfname)
}
}
return
}

View File

@ -5,60 +5,15 @@ package irc
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/tidwall/buntdb"
"github.com/oragono/oragono/irc/modes"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/utils"
)
// this is exclusively the *persistence* layer for channel registration;
// channel creation/tracking/destruction is in channelmanager.go
const (
keyChannelExists = "channel.exists %s"
keyChannelName = "channel.name %s" // stores the 'preferred name' of the channel, not casemapped
keyChannelRegTime = "channel.registered.time %s"
keyChannelFounder = "channel.founder %s"
keyChannelTopic = "channel.topic %s"
keyChannelTopicSetBy = "channel.topic.setby %s"
keyChannelTopicSetTime = "channel.topic.settime %s"
keyChannelBanlist = "channel.banlist %s"
keyChannelExceptlist = "channel.exceptlist %s"
keyChannelInvitelist = "channel.invitelist %s"
keyChannelPassword = "channel.key %s"
keyChannelModes = "channel.modes %s"
keyChannelAccountToUMode = "channel.accounttoumode %s"
keyChannelUserLimit = "channel.userlimit %s"
keyChannelSettings = "channel.settings %s"
keyChannelPurged = "channel.purged %s"
)
var (
channelKeyStrings = []string{
keyChannelExists,
keyChannelName,
keyChannelRegTime,
keyChannelFounder,
keyChannelTopic,
keyChannelTopicSetBy,
keyChannelTopicSetTime,
keyChannelBanlist,
keyChannelExceptlist,
keyChannelInvitelist,
keyChannelPassword,
keyChannelModes,
keyChannelAccountToUMode,
keyChannelUserLimit,
keyChannelSettings,
}
)
// these are bit flags indicating what part of the channel status is "dirty"
// and needs to be read from memory and written to the db
const (
@ -78,8 +33,8 @@ const (
type RegisteredChannel struct {
// Name of the channel.
Name string
// Casefolded name of the channel.
NameCasefolded string
// UUID for the datastore.
UUID utils.UUID
// RegisteredAt represents the time that the channel was registered.
RegisteredAt time.Time
// Founder indicates the founder of the channel.
@ -94,6 +49,8 @@ type RegisteredChannel struct {
Modes []modes.Mode
// Key represents the channel key / password
Key string
// Forward is the forwarding/overflow (+f) channel
Forward string
// UserLimit is the user limit (0 for no limit)
UserLimit int
// AccountToUMode maps user accounts to their persistent channel modes (e.g., +q, +h)
@ -106,317 +63,30 @@ type RegisteredChannel struct {
Invites map[string]MaskInfo
// Settings are the chanserv-modifiable settings
Settings ChannelSettings
// Metadata set using the METADATA command
Metadata map[string]string
}
func (r *RegisteredChannel) Serialize() ([]byte, error) {
return json.Marshal(r)
}
func (r *RegisteredChannel) Deserialize(b []byte) (err error) {
return json.Unmarshal(b, r)
}
type ChannelPurgeRecord struct {
Oper string
PurgedAt time.Time
Reason string
NameCasefolded string `json:"Name"`
UUID utils.UUID
Oper string
PurgedAt time.Time
Reason string
}
// ChannelRegistry manages registered channels.
type ChannelRegistry struct {
server *Server
func (c *ChannelPurgeRecord) Serialize() ([]byte, error) {
return json.Marshal(c)
}
// NewChannelRegistry returns a new ChannelRegistry.
func (reg *ChannelRegistry) Initialize(server *Server) {
reg.server = server
}
// AllChannels returns the uncasefolded names of all registered channels.
func (reg *ChannelRegistry) AllChannels() (result []string) {
prefix := fmt.Sprintf(keyChannelName, "")
reg.server.store.View(func(tx *buntdb.Tx) error {
return tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
if !strings.HasPrefix(key, prefix) {
return false
}
result = append(result, value)
return true
})
})
return
}
// PurgedChannels returns the set of all casefolded channel names that have been purged
func (reg *ChannelRegistry) PurgedChannels() (result utils.StringSet) {
result = make(utils.StringSet)
prefix := fmt.Sprintf(keyChannelPurged, "")
reg.server.store.View(func(tx *buntdb.Tx) error {
return tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
if !strings.HasPrefix(key, prefix) {
return false
}
channel := strings.TrimPrefix(key, prefix)
result.Add(channel)
return true
})
})
return
}
// StoreChannel obtains a consistent view of a channel, then persists it to the store.
func (reg *ChannelRegistry) StoreChannel(info RegisteredChannel, includeFlags uint) (err error) {
if !reg.server.ChannelRegistrationEnabled() {
return
}
if info.Founder == "" {
// sanity check, don't try to store an unregistered channel
return
}
reg.server.store.Update(func(tx *buntdb.Tx) error {
reg.saveChannel(tx, info, includeFlags)
return nil
})
return nil
}
// LoadChannel loads a channel from the store.
func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredChannel, err error) {
if !reg.server.ChannelRegistrationEnabled() {
err = errFeatureDisabled
return
}
channelKey := nameCasefolded
// nice to have: do all JSON (de)serialization outside of the buntdb transaction
err = reg.server.store.View(func(tx *buntdb.Tx) error {
_, dberr := tx.Get(fmt.Sprintf(keyChannelExists, channelKey))
if dberr == buntdb.ErrNotFound {
// chan does not already exist, return
return errNoSuchChannel
}
// channel exists, load it
name, _ := tx.Get(fmt.Sprintf(keyChannelName, channelKey))
regTime, _ := tx.Get(fmt.Sprintf(keyChannelRegTime, channelKey))
regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
founder, _ := tx.Get(fmt.Sprintf(keyChannelFounder, channelKey))
topic, _ := tx.Get(fmt.Sprintf(keyChannelTopic, channelKey))
topicSetBy, _ := tx.Get(fmt.Sprintf(keyChannelTopicSetBy, channelKey))
topicSetTime, _ := tx.Get(fmt.Sprintf(keyChannelTopicSetTime, channelKey))
topicSetTimeInt, _ := strconv.ParseInt(topicSetTime, 10, 64)
password, _ := tx.Get(fmt.Sprintf(keyChannelPassword, channelKey))
modeString, _ := tx.Get(fmt.Sprintf(keyChannelModes, channelKey))
userLimitString, _ := tx.Get(fmt.Sprintf(keyChannelUserLimit, channelKey))
banlistString, _ := tx.Get(fmt.Sprintf(keyChannelBanlist, channelKey))
exceptlistString, _ := tx.Get(fmt.Sprintf(keyChannelExceptlist, channelKey))
invitelistString, _ := tx.Get(fmt.Sprintf(keyChannelInvitelist, channelKey))
accountToUModeString, _ := tx.Get(fmt.Sprintf(keyChannelAccountToUMode, channelKey))
settingsString, _ := tx.Get(fmt.Sprintf(keyChannelSettings, channelKey))
modeSlice := make([]modes.Mode, len(modeString))
for i, mode := range modeString {
modeSlice[i] = modes.Mode(mode)
}
userLimit, _ := strconv.Atoi(userLimitString)
var banlist map[string]MaskInfo
_ = json.Unmarshal([]byte(banlistString), &banlist)
var exceptlist map[string]MaskInfo
_ = json.Unmarshal([]byte(exceptlistString), &exceptlist)
var invitelist map[string]MaskInfo
_ = json.Unmarshal([]byte(invitelistString), &invitelist)
accountToUMode := make(map[string]modes.Mode)
_ = json.Unmarshal([]byte(accountToUModeString), &accountToUMode)
var settings ChannelSettings
_ = json.Unmarshal([]byte(settingsString), &settings)
info = RegisteredChannel{
Name: name,
NameCasefolded: nameCasefolded,
RegisteredAt: time.Unix(regTimeInt, 0).UTC(),
Founder: founder,
Topic: topic,
TopicSetBy: topicSetBy,
TopicSetTime: time.Unix(topicSetTimeInt, 0).UTC(),
Key: password,
Modes: modeSlice,
Bans: banlist,
Excepts: exceptlist,
Invites: invitelist,
AccountToUMode: accountToUMode,
UserLimit: int(userLimit),
Settings: settings,
}
return nil
})
return
}
// Delete deletes a channel corresponding to `info`. If no such channel
// is present in the database, no error is returned.
func (reg *ChannelRegistry) Delete(info RegisteredChannel) (err error) {
if !reg.server.ChannelRegistrationEnabled() {
return
}
reg.server.store.Update(func(tx *buntdb.Tx) error {
reg.deleteChannel(tx, info.NameCasefolded, info)
return nil
})
return nil
}
// delete a channel, unless it was overwritten by another registration of the same channel
func (reg *ChannelRegistry) deleteChannel(tx *buntdb.Tx, key string, info RegisteredChannel) {
_, err := tx.Get(fmt.Sprintf(keyChannelExists, key))
if err == nil {
regTime, _ := tx.Get(fmt.Sprintf(keyChannelRegTime, key))
regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
registeredAt := time.Unix(regTimeInt, 0).UTC()
founder, _ := tx.Get(fmt.Sprintf(keyChannelFounder, key))
// to see if we're deleting the right channel, confirm the founder and the registration time
if founder == info.Founder && registeredAt.Unix() == info.RegisteredAt.Unix() {
for _, keyFmt := range channelKeyStrings {
tx.Delete(fmt.Sprintf(keyFmt, key))
}
// remove this channel from the client's list of registered channels
channelsKey := fmt.Sprintf(keyAccountChannels, info.Founder)
channelsStr, err := tx.Get(channelsKey)
if err == buntdb.ErrNotFound {
return
}
registeredChannels := unmarshalRegisteredChannels(channelsStr)
var nowRegisteredChannels []string
for _, channel := range registeredChannels {
if channel != key {
nowRegisteredChannels = append(nowRegisteredChannels, channel)
}
}
tx.Set(channelsKey, strings.Join(nowRegisteredChannels, ","), nil)
}
}
}
func (reg *ChannelRegistry) updateAccountToChannelMapping(tx *buntdb.Tx, channelInfo RegisteredChannel) {
channelKey := channelInfo.NameCasefolded
chanFounderKey := fmt.Sprintf(keyChannelFounder, channelKey)
founder, existsErr := tx.Get(chanFounderKey)
if existsErr == buntdb.ErrNotFound || founder != channelInfo.Founder {
// add to new founder's list
accountChannelsKey := fmt.Sprintf(keyAccountChannels, channelInfo.Founder)
alreadyChannels, _ := tx.Get(accountChannelsKey)
newChannels := channelKey // this is the casefolded channel name
if alreadyChannels != "" {
newChannels = fmt.Sprintf("%s,%s", alreadyChannels, newChannels)
}
tx.Set(accountChannelsKey, newChannels, nil)
}
if existsErr == nil && founder != channelInfo.Founder {
// remove from old founder's list
accountChannelsKey := fmt.Sprintf(keyAccountChannels, founder)
alreadyChannelsRaw, _ := tx.Get(accountChannelsKey)
var newChannels []string
if alreadyChannelsRaw != "" {
for _, chname := range strings.Split(alreadyChannelsRaw, ",") {
if chname != channelInfo.NameCasefolded {
newChannels = append(newChannels, chname)
}
}
}
tx.Set(accountChannelsKey, strings.Join(newChannels, ","), nil)
}
}
// saveChannel saves a channel to the store.
func (reg *ChannelRegistry) saveChannel(tx *buntdb.Tx, channelInfo RegisteredChannel, includeFlags uint) {
channelKey := channelInfo.NameCasefolded
// maintain the mapping of account -> registered channels
reg.updateAccountToChannelMapping(tx, channelInfo)
if includeFlags&IncludeInitial != 0 {
tx.Set(fmt.Sprintf(keyChannelExists, channelKey), "1", nil)
tx.Set(fmt.Sprintf(keyChannelName, channelKey), channelInfo.Name, nil)
tx.Set(fmt.Sprintf(keyChannelRegTime, channelKey), strconv.FormatInt(channelInfo.RegisteredAt.Unix(), 10), nil)
tx.Set(fmt.Sprintf(keyChannelFounder, channelKey), channelInfo.Founder, nil)
}
if includeFlags&IncludeTopic != 0 {
tx.Set(fmt.Sprintf(keyChannelTopic, channelKey), channelInfo.Topic, nil)
tx.Set(fmt.Sprintf(keyChannelTopicSetTime, channelKey), strconv.FormatInt(channelInfo.TopicSetTime.Unix(), 10), nil)
tx.Set(fmt.Sprintf(keyChannelTopicSetBy, channelKey), channelInfo.TopicSetBy, nil)
}
if includeFlags&IncludeModes != 0 {
tx.Set(fmt.Sprintf(keyChannelPassword, channelKey), channelInfo.Key, nil)
modeStrings := make([]string, len(channelInfo.Modes))
for i, mode := range channelInfo.Modes {
modeStrings[i] = string(mode)
}
tx.Set(fmt.Sprintf(keyChannelModes, channelKey), strings.Join(modeStrings, ""), nil)
tx.Set(fmt.Sprintf(keyChannelUserLimit, channelKey), strconv.Itoa(channelInfo.UserLimit), nil)
}
if includeFlags&IncludeLists != 0 {
banlistString, _ := json.Marshal(channelInfo.Bans)
tx.Set(fmt.Sprintf(keyChannelBanlist, channelKey), string(banlistString), nil)
exceptlistString, _ := json.Marshal(channelInfo.Excepts)
tx.Set(fmt.Sprintf(keyChannelExceptlist, channelKey), string(exceptlistString), nil)
invitelistString, _ := json.Marshal(channelInfo.Invites)
tx.Set(fmt.Sprintf(keyChannelInvitelist, channelKey), string(invitelistString), nil)
accountToUModeString, _ := json.Marshal(channelInfo.AccountToUMode)
tx.Set(fmt.Sprintf(keyChannelAccountToUMode, channelKey), string(accountToUModeString), nil)
}
if includeFlags&IncludeSettings != 0 {
settingsString, _ := json.Marshal(channelInfo.Settings)
tx.Set(fmt.Sprintf(keyChannelSettings, channelKey), string(settingsString), nil)
}
}
// PurgeChannel records a channel purge.
func (reg *ChannelRegistry) PurgeChannel(chname string, record ChannelPurgeRecord) (err error) {
serialized, err := json.Marshal(record)
if err != nil {
return err
}
serializedStr := string(serialized)
key := fmt.Sprintf(keyChannelPurged, chname)
return reg.server.store.Update(func(tx *buntdb.Tx) error {
tx.Set(key, serializedStr, nil)
return nil
})
}
// LoadPurgeRecord retrieves information about whether and how a channel was purged.
func (reg *ChannelRegistry) LoadPurgeRecord(chname string) (record ChannelPurgeRecord, err error) {
var rawRecord string
key := fmt.Sprintf(keyChannelPurged, chname)
reg.server.store.View(func(tx *buntdb.Tx) error {
rawRecord, _ = tx.Get(key)
return nil
})
if rawRecord == "" {
err = errNoSuchChannel
return
}
err = json.Unmarshal([]byte(rawRecord), &record)
if err != nil {
reg.server.logger.Error("internal", "corrupt purge record", chname, err.Error())
err = errNoSuchChannel
return
}
return
}
// UnpurgeChannel deletes the record of a channel purge.
func (reg *ChannelRegistry) UnpurgeChannel(chname string) (err error) {
key := fmt.Sprintf(keyChannelPurged, chname)
return reg.server.store.Update(func(tx *buntdb.Tx) error {
tx.Delete(key)
return nil
})
func (c *ChannelPurgeRecord) Deserialize(b []byte) error {
return json.Unmarshal(b, c)
}

View File

@ -6,18 +6,18 @@ package irc
import (
"fmt"
"regexp"
"slices"
"sort"
"strings"
"time"
"github.com/goshuirc/irc-go/ircfmt"
"github.com/oragono/oragono/irc/modes"
"github.com/oragono/oragono/irc/sno"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/sno"
"github.com/ergochat/ergo/irc/utils"
"github.com/ergochat/irc-go/ircfmt"
)
const chanservHelp = `ChanServ lets you register and manage channels.`
const chanservMask = "ChanServ!ChanServ@localhost"
func chanregEnabled(config *Config) bool {
return config.Channels.Registration.Enabled
@ -30,12 +30,22 @@ var (
help: `Syntax: $bOP #channel [nickname]$b
OP makes the given nickname, or yourself, a channel admin. You can only use
this command if you're the founder of the channel.`,
this command if you're a founder or in the AMODEs of the channel.`,
helpShort: `$bOP$b makes the given user (or yourself) a channel admin.`,
authRequired: true,
enabled: chanregEnabled,
minParams: 1,
},
"deop": {
handler: csDeopHandler,
help: `Syntax: $bDEOP #channel [nickname]$b
DEOP removes the given nickname, or yourself, the channel admin. You can only use
this command if you're the founder of the channel.`,
helpShort: `$bDEOP$b removes the given user (or yourself) from a channel admin.`,
enabled: chanregEnabled,
minParams: 1,
},
"register": {
handler: csRegisterHandler,
help: `Syntax: $bREGISTER #channel$b
@ -67,10 +77,12 @@ invoking the command without a code will display the necessary code.`,
help: `Syntax: $bAMODE #channel [mode change] [account]$b
AMODE lists or modifies persistent mode settings that affect channel members.
For example, $bAMODE #channel +o dan$b grants the the holder of the "dan"
For example, $bAMODE #channel +o dan$b grants the holder of the "dan"
account the +o operator mode every time they join #channel. To list current
accounts and modes, use $bAMODE #channel$b. Note that users are always
referenced by their registered account names, not their nicknames.`,
referenced by their registered account names, not their nicknames.
The permissions hierarchy for adding and removing modes is the same as in
the ordinary /MODE command.`,
helpShort: `$bAMODE$b modifies persistent mode settings for channel members.`,
enabled: chanregEnabled,
minParams: 1,
@ -105,28 +117,19 @@ To cancel a pending transfer, transfer the channel to yourself.`,
},
"purge": {
handler: csPurgeHandler,
help: `Syntax: $bPURGE #channel [reason]$b
help: `Syntax: $bPURGE <ADD | DEL | LIST> #channel [code] [reason]$b
PURGE blacklists a channel from the server, making it impossible to join
PURGE ADD blacklists a channel from the server, making it impossible to join
or otherwise interact with the channel. If the channel currently has members,
they will be kicked from it. PURGE may also be applied preemptively to
channels that do not currently have members.`,
channels that do not currently have members. A purge can be undone with
PURGE DEL. To list purged channels, use PURGE LIST.`,
helpShort: `$bPURGE$b blacklists a channel from the server.`,
capabs: []string{"chanreg"},
minParams: 1,
maxParams: 2,
maxParams: 3,
unsplitFinalParam: true,
},
"unpurge": {
handler: csUnpurgeHandler,
help: `Syntax: $bUNPURGE #channel$b
UNPURGE removes any blacklisting of a channel that was previously
set using PURGE.`,
helpShort: `$bUNPURGE$b undoes a previous PURGE command.`,
capabs: []string{"chanreg"},
minParams: 1,
},
"list": {
handler: csListHandler,
help: `Syntax: $bLIST [regex]$b
@ -144,7 +147,6 @@ If no regex is provided, all registered channels are returned.`,
INFO displays info about a registered channel.`,
helpShort: `$bINFO$b displays info about a registered channel.`,
enabled: chanregEnabled,
minParams: 1,
},
"get": {
handler: csGetHandler,
@ -171,34 +173,59 @@ SET modifies a channel's settings. The following settings are available:`,
2. 'ephemeral' [a limited amount of temporary history, not stored on disk]
3. 'on' [history stored in a permanent database, if available]
4. 'default' [use the server default]`,
`$bQUERY-CUTOFF$b
'query-cutoff' lets you restrict how much channel history can be retrieved
by unprivileged users. Your options are:
1. 'none' [no restrictions]
2. 'registration-time' [users can view history from after their account was
registered, plus a grace period]
3. 'join-time' [users can view history from after they joined the
channel; note that history will be effectively
unavailable to clients that are not always-on]
4. 'default' [use the server default]`,
},
enabled: chanregEnabled,
minParams: 3,
},
"howtoban": {
handler: csHowToBanHandler,
helpShort: `$bHOWTOBAN$b suggests the best available way of banning a user`,
help: `Syntax: $bHOWTOBAN #channel <nick>
The best way to ban a user from a channel will depend on how they are
connected to the server. $bHOWTOBAN$b suggests a ban command that will
(ideally) prevent the user from returning to the channel.`,
enabled: chanregEnabled,
minParams: 2,
},
}
)
// csNotice sends the client a notice from ChanServ
func csNotice(rb *ResponseBuffer, text string) {
rb.Add(nil, chanservMask, "NOTICE", rb.target.Nick(), text)
}
func csAmodeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func csAmodeHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
channelName := params[0]
channel := server.channels.Get(channelName)
if channel == nil {
csNotice(rb, client.t("Channel does not exist"))
service.Notice(rb, client.t("Channel does not exist"))
return
} else if channel.Founder() == "" {
csNotice(rb, client.t("Channel is not registered"))
service.Notice(rb, client.t("Channel is not registered"))
return
}
modeChanges, unknown := modes.ParseChannelModeChanges(params[1:]...)
invalid := len(unknown) != 0
// #2002: +f takes an argument but is not a channel-user mode,
// check for anything valid as a channel mode change that is not valid
// as an AMODE change
for _, modeChange := range modeChanges {
if !slices.Contains(modes.ChannelUserModes, modeChange.Mode) {
invalid = true
}
}
var change modes.ModeChange
if len(modeChanges) > 1 || len(unknown) > 0 {
csNotice(rb, client.t("Invalid mode change"))
if len(modeChanges) > 1 || invalid {
service.Notice(rb, client.t("Invalid mode change"))
return
} else if len(modeChanges) == 1 {
change = modeChanges[0]
@ -223,17 +250,17 @@ func csAmodeHandler(server *Server, client *Client, command string, params []str
accountIsValid = (change.Arg != "")
}
if !accountIsValid {
csNotice(rb, client.t("Account does not exist"))
service.Notice(rb, client.t("Account does not exist"))
return
}
affectedModes, err := channel.ProcessAccountToUmodeChange(client, change)
if err == errInsufficientPrivs {
csNotice(rb, client.t("Insufficient privileges"))
service.Notice(rb, client.t("Insufficient privileges"))
return
} else if err != nil {
csNotice(rb, client.t("Internal error"))
service.Notice(rb, client.t("Internal error"))
return
}
@ -243,39 +270,44 @@ func csAmodeHandler(server *Server, client *Client, command string, params []str
sort.Slice(affectedModes, func(i, j int) bool {
return umodeGreaterThan(affectedModes[i].Mode, affectedModes[j].Mode)
})
csNotice(rb, fmt.Sprintf(client.t("Channel %[1]s has %[2]d persistent modes set"), channelName, len(affectedModes)))
service.Notice(rb, fmt.Sprintf(client.t("Channel %[1]s has %[2]d persistent modes set"), channelName, len(affectedModes)))
for _, modeChange := range affectedModes {
csNotice(rb, fmt.Sprintf(client.t("Account %[1]s receives mode +%[2]s"), modeChange.Arg, string(modeChange.Mode)))
service.Notice(rb, fmt.Sprintf(client.t("Account %[1]s receives mode +%[2]s"), modeChange.Arg, string(modeChange.Mode)))
}
case modes.Add, modes.Remove:
if len(affectedModes) > 0 {
csNotice(rb, fmt.Sprintf(client.t("Successfully set persistent mode %[1]s on %[2]s"), strings.Join([]string{string(change.Op), string(change.Mode)}, ""), change.Arg))
service.Notice(rb, fmt.Sprintf(client.t("Successfully set persistent mode %[1]s on %[2]s"), strings.Join([]string{string(change.Op), string(change.Mode)}, ""), change.Arg))
// #729: apply change to current membership
for _, member := range channel.Members() {
if member.Account() == change.Arg {
applied, change := channel.applyModeToMember(client, change, rb)
// applyModeToMember takes the nickname, not the account name,
// so translate:
modeChange := change
modeChange.Arg = member.Nick()
applied, modeChange := channel.applyModeToMember(client, modeChange, rb)
if applied {
announceCmodeChanges(channel, modes.ModeChanges{change}, chanservMask, "*", "", rb)
announceCmodeChanges(channel, modes.ModeChanges{modeChange}, server.name, "*", "", false, rb)
}
}
}
} else {
csNotice(rb, client.t("No changes were made"))
service.Notice(rb, client.t("No changes were made"))
}
}
}
func csOpHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func csOpHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
channelInfo := server.channels.Get(params[0])
if channelInfo == nil {
csNotice(rb, client.t("Channel does not exist"))
service.Notice(rb, client.t("Channel does not exist"))
return
}
channelName := channelInfo.Name()
founder := channelInfo.Founder()
clientAccount := client.Account()
if clientAccount == "" || clientAccount != channelInfo.Founder() {
csNotice(rb, client.t("Only the channel founder can do this"))
if clientAccount == "" {
service.Notice(rb, client.t("You're not logged into an account"))
return
}
@ -283,18 +315,33 @@ func csOpHandler(server *Server, client *Client, command string, params []string
if len(params) > 1 {
target = server.clients.Get(params[1])
if target == nil {
csNotice(rb, client.t("Could not find given client"))
service.Notice(rb, client.t("Could not find given client"))
return
}
} else {
target = client
}
// give them privs
givenMode := modes.ChannelOperator
if clientAccount == target.Account() {
givenMode = modes.ChannelFounder
var givenMode modes.Mode
if target == client {
if clientAccount == founder {
givenMode = modes.ChannelFounder
} else {
givenMode = channelInfo.getAmode(clientAccount)
if givenMode == modes.Mode(0) {
service.Notice(rb, client.t("You don't have any stored privileges on that channel"))
return
}
}
} else {
if clientAccount == founder {
givenMode = modes.ChannelOperator
} else {
service.Notice(rb, client.t("Only the channel founder can do this"))
return
}
}
applied, change := channelInfo.applyModeToMember(client,
modes.ModeChange{Mode: givenMode,
Op: modes.Add,
@ -302,45 +349,97 @@ func csOpHandler(server *Server, client *Client, command string, params []string
},
rb)
if applied {
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, "*", "", rb)
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, server.name, "*", "", false, rb)
}
csNotice(rb, client.t("Successfully granted operator privileges"))
service.Notice(rb, client.t("Successfully granted operator privileges"))
tnick := target.Nick()
server.logger.Info("services", fmt.Sprintf("Client %s op'd [%s] in channel %s", client.Nick(), tnick, channelName))
server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] CS OP'd $c[grey][$r%s$c[grey]] in channel $c[grey][$r%s$c[grey]]"), client.NickMaskString(), tnick, channelName))
}
func csRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func csDeopHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
channel := server.channels.Get(params[0])
if channel == nil {
service.Notice(rb, client.t("Channel does not exist"))
return
}
if !channel.hasClient(client) {
service.Notice(rb, client.t("You're not on that channel"))
return
}
var target *Client
if len(params) > 1 {
target = server.clients.Get(params[1])
if target == nil {
service.Notice(rb, client.t("Could not find given client"))
return
}
} else {
target = client
}
present, _, cumodes := channel.ClientStatus(target)
if !present || len(cumodes) == 0 {
service.Notice(rb, client.t("Target has no privileges to remove"))
return
}
tnick := target.Nick()
modeChanges := make(modes.ModeChanges, len(cumodes))
for i, mode := range cumodes {
modeChanges[i] = modes.ModeChange{
Mode: mode,
Op: modes.Remove,
Arg: tnick,
}
}
// use the user's own permissions for the check, then announce
// the changes as coming from chanserv
applied := channel.ApplyChannelModeChanges(client, false, modeChanges, rb)
details := client.Details()
isBot := client.HasMode(modes.Bot)
announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, isBot, rb)
if len(applied) == 0 {
return
}
service.Notice(rb, client.t("Successfully removed operator privileges"))
}
func csRegisterHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
if server.Config().Channels.Registration.OperatorOnly && !client.HasRoleCapabs("chanreg") {
csNotice(rb, client.t("Channel registration is restricted to server operators"))
service.Notice(rb, client.t("Channel registration is restricted to server operators"))
return
}
channelName := params[0]
channelInfo := server.channels.Get(channelName)
if channelInfo == nil {
csNotice(rb, client.t("No such channel"))
service.Notice(rb, client.t("No such channel"))
return
}
if !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) {
csNotice(rb, client.t("You must be an oper on the channel to register it"))
service.Notice(rb, client.t("You must be an oper on the channel to register it"))
return
}
account := client.Account()
if !checkChanLimit(client, rb) {
if !checkChanLimit(service, client, rb) {
return
}
// this provides the synchronization that allows exactly one registration of the channel:
err := server.channels.SetRegistered(channelName, account)
if err != nil {
csNotice(rb, err.Error())
service.Notice(rb, err.Error())
return
}
csNotice(rb, fmt.Sprintf(client.t("Channel %s successfully registered"), channelName))
service.Notice(rb, fmt.Sprintf(client.t("Channel %s successfully registered"), channelName))
server.logger.Info("services", fmt.Sprintf("Client %s registered channel %s", client.Nick(), channelName))
server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString))
@ -354,38 +453,38 @@ func csRegisterHandler(server *Server, client *Client, command string, params []
},
rb)
if applied {
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, "*", "", rb)
announceCmodeChanges(channelInfo, modes.ModeChanges{change}, service.prefix, "*", "", false, rb)
}
}
// check whether a client has already registered too many channels
func checkChanLimit(client *Client, rb *ResponseBuffer) (ok bool) {
func checkChanLimit(service *ircService, client *Client, rb *ResponseBuffer) (ok bool) {
account := client.Account()
channelsAlreadyRegistered := client.server.accounts.ChannelsForAccount(account)
channelsAlreadyRegistered := client.server.channels.ChannelsForAccount(account)
ok = len(channelsAlreadyRegistered) < client.server.Config().Channels.Registration.MaxChannelsPerAccount || client.HasRoleCapabs("chanreg")
if !ok {
csNotice(rb, client.t("You have already registered the maximum number of channels; try dropping some with /CS UNREGISTER"))
service.Notice(rb, client.t("You have already registered the maximum number of channels; try dropping some with /CS UNREGISTER"))
}
return
}
func csPrivsCheck(channel RegisteredChannel, client *Client, rb *ResponseBuffer) (success bool) {
func csPrivsCheck(service *ircService, channel RegisteredChannel, client *Client, rb *ResponseBuffer) (success bool) {
founder := channel.Founder
if founder == "" {
csNotice(rb, client.t("That channel is not registered"))
service.Notice(rb, client.t("That channel is not registered"))
return false
}
if client.HasRoleCapabs("chanreg") {
return true
}
if founder != client.Account() {
csNotice(rb, client.t("Insufficient privileges"))
service.Notice(rb, client.t("Insufficient privileges"))
return false
}
return true
}
func csUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func csUnregisterHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
channelName := params[0]
var verificationCode string
if len(params) > 1 {
@ -394,41 +493,41 @@ func csUnregisterHandler(server *Server, client *Client, command string, params
channel := server.channels.Get(channelName)
if channel == nil {
csNotice(rb, client.t("No such channel"))
service.Notice(rb, client.t("No such channel"))
return
}
info := channel.ExportRegistration(0)
channelKey := info.NameCasefolded
if !csPrivsCheck(info, client, rb) {
info := channel.exportSummary()
channelKey := channel.NameCasefolded()
if !csPrivsCheck(service, info, client, rb) {
return
}
expectedCode := utils.ConfirmationCode(info.Name, info.RegisteredAt)
if expectedCode != verificationCode {
csNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this channel will remove all stored channel attributes.$b")))
csNotice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/CS UNREGISTER %s %s", channelKey, expectedCode)))
service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this channel will remove all stored channel attributes.$b")))
service.Notice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/CS UNREGISTER %s %s", channelKey, expectedCode)))
return
}
server.channels.SetUnregistered(channelKey, info.Founder)
csNotice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey))
service.Notice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey))
}
func csClearHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func csClearHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
channel := server.channels.Get(params[0])
if channel == nil {
csNotice(rb, client.t("Channel does not exist"))
service.Notice(rb, client.t("Channel does not exist"))
return
}
if !csPrivsCheck(channel.ExportRegistration(0), client, rb) {
if !csPrivsCheck(service, channel.exportSummary(), client, rb) {
return
}
switch strings.ToLower(params[1]) {
case "access":
channel.resetAccess()
csNotice(rb, client.t("Successfully reset channel access"))
service.Notice(rb, client.t("Successfully reset channel access"))
case "users":
for _, target := range channel.Members() {
if target != client {
@ -436,63 +535,74 @@ func csClearHandler(server *Server, client *Client, command string, params []str
}
}
default:
csNotice(rb, client.t("Invalid parameters"))
service.Notice(rb, client.t("Invalid parameters"))
}
}
func csTransferHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func csTransferHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
if strings.ToLower(params[0]) == "accept" {
processTransferAccept(client, params[1], rb)
processTransferAccept(service, client, params[1], rb)
return
}
chname := params[0]
channel := server.channels.Get(chname)
if channel == nil {
csNotice(rb, client.t("Channel does not exist"))
service.Notice(rb, client.t("Channel does not exist"))
return
}
regInfo := channel.ExportRegistration(0)
regInfo := channel.exportSummary()
chname = regInfo.Name
account := client.Account()
isFounder := account != "" && account == regInfo.Founder
hasPrivs := client.HasRoleCapabs("chanreg")
if !(isFounder || hasPrivs) {
csNotice(rb, client.t("Insufficient privileges"))
oper := client.Oper()
hasPrivs := oper.HasRoleCapab("chanreg")
if !isFounder && !hasPrivs {
service.Notice(rb, client.t("Insufficient privileges"))
return
}
target := params[1]
targetAccount, err := server.accounts.LoadAccount(params[1])
if err != nil {
csNotice(rb, client.t("Account does not exist"))
service.Notice(rb, client.t("Account does not exist"))
return
}
if targetAccount.NameCasefolded != account {
expectedCode := utils.ConfirmationCode(regInfo.Name, regInfo.RegisteredAt)
codeValidated := 2 < len(params) && params[2] == expectedCode
if !codeValidated {
csNotice(rb, ircfmt.Unescape(client.t("$bWarning: you are about to transfer control of your channel to another user.$b")))
csNotice(rb, fmt.Sprintf(client.t("To confirm your channel transfer, type: /CS TRANSFER %[1]s %[2]s %[3]s"), chname, target, expectedCode))
service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: you are about to transfer control of your channel to another user.$b")))
service.Notice(rb, fmt.Sprintf(client.t("To confirm your channel transfer, type: /CS TRANSFER %[1]s %[2]s %[3]s"), chname, target, expectedCode))
return
}
}
if !isFounder {
message := fmt.Sprintf("Operator %s ran CS TRANSFER on %s to account %s", oper.Name, chname, target)
server.snomasks.Send(sno.LocalOpers, message)
server.logger.Info("opers", message)
}
status, err := channel.Transfer(client, target, hasPrivs)
if err == nil {
switch status {
case channelTransferComplete:
csNotice(rb, fmt.Sprintf(client.t("Successfully transferred channel %[1]s to account %[2]s"), chname, target))
service.Notice(rb, fmt.Sprintf(client.t("Successfully transferred channel %[1]s to account %[2]s"), chname, target))
case channelTransferPending:
sendTransferPendingNotice(server, target, chname)
csNotice(rb, fmt.Sprintf(client.t("Transfer of channel %[1]s to account %[2]s succeeded, pending acceptance"), chname, target))
sendTransferPendingNotice(service, server, target, chname)
service.Notice(rb, fmt.Sprintf(client.t("Transfer of channel %[1]s to account %[2]s succeeded, pending acceptance"), chname, target))
case channelTransferCancelled:
csNotice(rb, fmt.Sprintf(client.t("Cancelled pending transfer of channel %s"), chname))
service.Notice(rb, fmt.Sprintf(client.t("Cancelled pending transfer of channel %s"), chname))
}
} else {
csNotice(rb, client.t("Could not transfer channel"))
switch err {
case errChannelNotOwnedByAccount:
service.Notice(rb, client.t("You don't own that channel"))
default:
service.Notice(rb, client.t("Could not transfer channel"))
}
}
}
func sendTransferPendingNotice(server *Server, account, chname string) {
func sendTransferPendingNotice(service *ircService, server *Server, account, chname string) {
clients := server.accounts.AccountToClients(account)
if len(clients) == 0 {
return
@ -504,187 +614,238 @@ func sendTransferPendingNotice(server *Server, account, chname string) {
break // prefer the login where the nick is the account
}
}
client.Send(nil, chanservMask, "NOTICE", client.Nick(), fmt.Sprintf(client.t("You have been offered ownership of channel %[1]s. To accept, /CS TRANSFER ACCEPT %[1]s"), chname))
client.Send(nil, service.prefix, "NOTICE", client.Nick(), fmt.Sprintf(client.t("You have been offered ownership of channel %[1]s. To accept, /CS TRANSFER ACCEPT %[1]s"), chname))
}
func processTransferAccept(client *Client, chname string, rb *ResponseBuffer) {
func processTransferAccept(service *ircService, client *Client, chname string, rb *ResponseBuffer) {
channel := client.server.channels.Get(chname)
if channel == nil {
csNotice(rb, client.t("Channel does not exist"))
service.Notice(rb, client.t("Channel does not exist"))
return
}
if !checkChanLimit(client, rb) {
if !checkChanLimit(service, client, rb) {
return
}
switch channel.AcceptTransfer(client) {
case nil:
csNotice(rb, fmt.Sprintf(client.t("Successfully accepted ownership of channel %s"), channel.Name()))
service.Notice(rb, fmt.Sprintf(client.t("Successfully accepted ownership of channel %s"), channel.Name()))
case errChannelTransferNotOffered:
csNotice(rb, fmt.Sprintf(client.t("You weren't offered ownership of channel %s"), channel.Name()))
service.Notice(rb, fmt.Sprintf(client.t("You weren't offered ownership of channel %s"), channel.Name()))
default:
csNotice(rb, fmt.Sprintf(client.t("Could not accept ownership of channel %s"), channel.Name()))
service.Notice(rb, fmt.Sprintf(client.t("Could not accept ownership of channel %s"), channel.Name()))
}
}
func csPurgeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func csPurgeHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
oper := client.Oper()
if oper == nil {
return // should be impossible because you need oper capabs for this
}
switch strings.ToLower(params[0]) {
case "add":
csPurgeAddHandler(service, client, params[1:], oper.Name, rb)
case "del", "remove":
csPurgeDelHandler(service, client, params[1:], oper.Name, rb)
case "list":
csPurgeListHandler(service, client, rb)
default:
service.Notice(rb, client.t("Invalid parameters"))
}
}
func csPurgeAddHandler(service *ircService, client *Client, params []string, operName string, rb *ResponseBuffer) {
if len(params) == 0 {
service.Notice(rb, client.t("Invalid parameters"))
return
}
chname := params[0]
params = params[1:]
channel := client.server.channels.Get(chname) // possibly nil
var ctime time.Time
if channel != nil {
chname = channel.Name()
ctime = channel.Ctime()
}
code := utils.ConfirmationCode(chname, ctime)
if len(params) == 0 || params[0] != code {
service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: you are about to empty this channel and remove it from the server.$b")))
service.Notice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/CS PURGE ADD %s %s", chname, code)))
return
}
params = params[1:]
var reason string
if 1 < len(params) {
reason = params[1]
}
purgeRecord := ChannelPurgeRecord{
Oper: oper.Name,
Oper: operName,
PurgedAt: time.Now().UTC(),
Reason: reason,
}
switch server.channels.Purge(chname, purgeRecord) {
switch client.server.channels.Purge(chname, purgeRecord) {
case nil:
channel := server.channels.Get(chname)
if channel != nil { // channel need not exist to be purged
for _, target := range channel.Members() {
channel.Kick(client, target, "Cleared by ChanServ", rb, true)
}
}
csNotice(rb, fmt.Sprintf(client.t("Successfully purged channel %s from the server"), chname))
service.Notice(rb, fmt.Sprintf(client.t("Successfully purged channel %s from the server"), chname))
client.server.snomasks.Send(sno.LocalChannels, fmt.Sprintf("Operator %s purged channel %s [reason: %s]", operName, chname, reason))
case errInvalidChannelName:
csNotice(rb, fmt.Sprintf(client.t("Can't purge invalid channel %s"), chname))
service.Notice(rb, fmt.Sprintf(client.t("Can't purge invalid channel %s"), chname))
default:
csNotice(rb, client.t("An error occurred"))
service.Notice(rb, client.t("An error occurred"))
}
}
func csUnpurgeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
chname := params[0]
switch server.channels.Unpurge(chname) {
case nil:
csNotice(rb, fmt.Sprintf(client.t("Successfully unpurged channel %s from the server"), chname))
case errNoSuchChannel:
csNotice(rb, fmt.Sprintf(client.t("Channel %s wasn't previously purged from the server"), chname))
default:
csNotice(rb, client.t("An error occurred"))
}
}
func csListHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
if !client.HasRoleCapabs("chanreg") {
csNotice(rb, client.t("Insufficient privileges"))
func csPurgeDelHandler(service *ircService, client *Client, params []string, operName string, rb *ResponseBuffer) {
if len(params) == 0 {
service.Notice(rb, client.t("Invalid parameters"))
return
}
chname := params[0]
switch client.server.channels.Unpurge(chname) {
case nil:
service.Notice(rb, fmt.Sprintf(client.t("Successfully unpurged channel %s from the server"), chname))
client.server.snomasks.Send(sno.LocalChannels, fmt.Sprintf("Operator %s removed purge of channel %s", operName, chname))
case errNoSuchChannel:
service.Notice(rb, fmt.Sprintf(client.t("Channel %s wasn't previously purged from the server"), chname))
default:
service.Notice(rb, client.t("An error occurred"))
}
}
func csPurgeListHandler(service *ircService, client *Client, rb *ResponseBuffer) {
l := client.server.channels.ListPurged()
service.Notice(rb, fmt.Sprintf(client.t("There are %d purged channel(s)."), len(l)))
for i, c := range l {
service.Notice(rb, fmt.Sprintf("%d: %s", i+1, c))
}
}
func csListHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
var searchRegex *regexp.Regexp
if len(params) > 0 {
var err error
searchRegex, err = regexp.Compile(params[0])
if err != nil {
csNotice(rb, client.t("Invalid regex"))
service.Notice(rb, client.t("Invalid regex"))
return
}
}
csNotice(rb, ircfmt.Unescape(client.t("*** $bChanServ LIST$b ***")))
service.Notice(rb, ircfmt.Unescape(client.t("*** $bChanServ LIST$b ***")))
channels := server.channelRegistry.AllChannels()
channels := server.channels.AllRegisteredChannels()
for _, channel := range channels {
if searchRegex == nil || searchRegex.MatchString(channel) {
csNotice(rb, fmt.Sprintf(" %s", channel))
service.Notice(rb, fmt.Sprintf(" %s", channel))
}
}
csNotice(rb, ircfmt.Unescape(client.t("*** $bEnd of ChanServ LIST$b ***")))
service.Notice(rb, ircfmt.Unescape(client.t("*** $bEnd of ChanServ LIST$b ***")))
}
func csInfoHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func csInfoHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
if len(params) == 0 {
// #765
listRegisteredChannels(service, client.Account(), rb)
return
}
chname, err := CasefoldChannel(params[0])
if err != nil {
csNotice(rb, client.t("Invalid channel name"))
service.Notice(rb, client.t("Invalid channel name"))
return
}
// purge status
if client.HasRoleCapabs("chanreg") {
purgeRecord, err := server.channelRegistry.LoadPurgeRecord(chname)
purgeRecord, err := server.channels.LoadPurgeRecord(chname)
if err == nil {
csNotice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname))
csNotice(rb, fmt.Sprintf(client.t("Purged by operator: %s"), purgeRecord.Oper))
csNotice(rb, fmt.Sprintf(client.t("Purged at: %s"), purgeRecord.PurgedAt.Format(time.RFC1123)))
service.Notice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname))
service.Notice(rb, fmt.Sprintf(client.t("Purged by operator: %s"), purgeRecord.Oper))
service.Notice(rb, fmt.Sprintf(client.t("Purged at: %s"), purgeRecord.PurgedAt.Format(time.RFC1123)))
if purgeRecord.Reason != "" {
csNotice(rb, fmt.Sprintf(client.t("Purge reason: %s"), purgeRecord.Reason))
service.Notice(rb, fmt.Sprintf(client.t("Purge reason: %s"), purgeRecord.Reason))
}
}
} else {
if server.channels.IsPurged(chname) {
csNotice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname))
service.Notice(rb, fmt.Sprintf(client.t("Channel %s was purged by the server operators and cannot be used"), chname))
}
}
var chinfo RegisteredChannel
channel := server.channels.Get(params[0])
if channel != nil {
chinfo = channel.ExportRegistration(0)
} else {
chinfo, err = server.channelRegistry.LoadChannel(chname)
if err != nil && !(err == errNoSuchChannel || err == errFeatureDisabled) {
csNotice(rb, client.t("An error occurred"))
return
}
chinfo = channel.exportSummary()
}
// channel exists but is unregistered, or doesn't exist:
if chinfo.Founder == "" {
csNotice(rb, fmt.Sprintf(client.t("Channel %s is not registered"), chname))
service.Notice(rb, fmt.Sprintf(client.t("Channel %s is not registered"), chname))
return
}
csNotice(rb, fmt.Sprintf(client.t("Channel %s is registered"), chinfo.Name))
csNotice(rb, fmt.Sprintf(client.t("Founder: %s"), chinfo.Founder))
csNotice(rb, fmt.Sprintf(client.t("Registered at: %s"), chinfo.RegisteredAt.Format(time.RFC1123)))
service.Notice(rb, fmt.Sprintf(client.t("Channel %s is registered"), chinfo.Name))
service.Notice(rb, fmt.Sprintf(client.t("Founder: %s"), chinfo.Founder))
service.Notice(rb, fmt.Sprintf(client.t("Registered at: %s"), chinfo.RegisteredAt.Format(time.RFC1123)))
}
func displayChannelSetting(settingName string, settings ChannelSettings, client *Client, rb *ResponseBuffer) {
func displayChannelSetting(service *ircService, settingName string, settings ChannelSettings, client *Client, rb *ResponseBuffer) {
config := client.server.Config()
switch strings.ToLower(settingName) {
case "history":
effectiveValue := historyEnabled(config.History.Persistent.RegisteredChannels, settings.History)
csNotice(rb, fmt.Sprintf(client.t("The stored channel history setting is: %s"), historyStatusToString(settings.History)))
csNotice(rb, fmt.Sprintf(client.t("Given current server settings, the channel history setting is: %s"), historyStatusToString(effectiveValue)))
service.Notice(rb, fmt.Sprintf(client.t("The stored channel history setting is: %s"), historyStatusToString(settings.History)))
service.Notice(rb, fmt.Sprintf(client.t("Given current server settings, the channel history setting is: %s"), historyStatusToString(effectiveValue)))
case "query-cutoff":
effectiveValue := settings.QueryCutoff
if effectiveValue == HistoryCutoffDefault {
effectiveValue = config.History.Restrictions.queryCutoff
}
service.Notice(rb, fmt.Sprintf(client.t("The stored channel history query cutoff setting is: %s"), historyCutoffToString(settings.QueryCutoff)))
service.Notice(rb, fmt.Sprintf(client.t("Given current server settings, the channel history query cutoff setting is: %s"), historyCutoffToString(effectiveValue)))
default:
csNotice(rb, client.t("Invalid params"))
service.Notice(rb, client.t("Invalid params"))
}
}
func csGetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func csGetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
chname, setting := params[0], params[1]
channel := server.channels.Get(chname)
if channel == nil {
csNotice(rb, client.t("No such channel"))
service.Notice(rb, client.t("No such channel"))
return
}
info := channel.ExportRegistration(IncludeSettings)
if !csPrivsCheck(info, client, rb) {
info := channel.exportSummary()
if !csPrivsCheck(service, info, client, rb) {
return
}
displayChannelSetting(setting, info.Settings, client, rb)
displayChannelSetting(service, setting, channel.Settings(), client, rb)
}
func csSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func csSetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
chname, setting, value := params[0], params[1], params[2]
channel := server.channels.Get(chname)
if channel == nil {
csNotice(rb, client.t("No such channel"))
service.Notice(rb, client.t("No such channel"))
return
}
info := channel.ExportRegistration(IncludeSettings)
settings := info.Settings
if !csPrivsCheck(info, client, rb) {
info := channel.exportSummary()
if !csPrivsCheck(service, info, client, rb) {
return
}
settings := channel.Settings()
var err error
switch strings.ToLower(setting) {
case "history":
@ -695,16 +856,103 @@ func csSetHandler(server *Server, client *Client, command string, params []strin
}
channel.SetSettings(settings)
channel.resizeHistory(server.Config())
case "query-cutoff":
settings.QueryCutoff, err = historyCutoffFromString(value)
if err != nil {
err = errInvalidParams
break
}
channel.SetSettings(settings)
}
switch err {
case nil:
csNotice(rb, client.t("Successfully changed the channel settings"))
displayChannelSetting(setting, settings, client, rb)
service.Notice(rb, client.t("Successfully changed the channel settings"))
displayChannelSetting(service, setting, settings, client, rb)
case errInvalidParams:
csNotice(rb, client.t("Invalid parameters"))
service.Notice(rb, client.t("Invalid parameters"))
default:
server.logger.Error("internal", "CS SET error:", err.Error())
csNotice(rb, client.t("An error occurred"))
service.Notice(rb, client.t("An error occurred"))
}
}
func csHowToBanHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
success := false
defer func() {
if success {
service.Notice(rb, client.t("Note that if the user is currently in the channel, you must /KICK them after you ban them"))
}
}()
chname, nick := params[0], params[1]
channel := server.channels.Get(chname)
if channel == nil {
service.Notice(rb, client.t("No such channel"))
return
}
if !(channel.ClientIsAtLeast(client, modes.ChannelOperator) || client.HasRoleCapabs("samode")) {
service.Notice(rb, client.t("Insufficient privileges"))
return
}
var details WhoWas
target := server.clients.Get(nick)
if target == nil {
whowasList := server.whoWas.Find(nick, 1)
if len(whowasList) == 0 {
service.Notice(rb, client.t("No such nick"))
return
}
service.Notice(rb, fmt.Sprintf(client.t("Warning: %s is not currently connected to the server. Using WHOWAS data, which may be inaccurate:"), nick))
details = whowasList[0]
} else {
details = target.Details().WhoWas
}
if details.account != "" {
if channel.getAmode(details.account) != modes.Mode(0) {
service.Notice(rb, fmt.Sprintf(client.t("Warning: account %s currently has a persistent channel privilege granted with CS AMODE. If this mode is not removed, bans will not be respected"), details.accountName))
return
} else if details.account == channel.Founder() {
service.Notice(rb, fmt.Sprintf(client.t("Warning: account %s is the channel founder and cannot be banned"), details.accountName))
return
}
}
config := server.Config()
if !config.Server.Cloaks.EnabledForAlwaysOn {
service.Notice(rb, client.t("Warning: server.ip-cloaking.enabled-for-always-on is disabled. This reduces the precision of channel bans."))
}
if details.account != "" {
if config.Accounts.NickReservation.ForceNickEqualsAccount || target.AlwaysOn() {
service.Notice(rb, fmt.Sprintf(client.t("User %[1]s is authenticated and can be banned by nickname: /MODE %[2]s +b %[3]s!*@*"), details.nick, channel.Name(), details.nick))
success = true
return
}
}
ban := fmt.Sprintf("*!*@%s", strings.ToLower(details.hostname))
banRe, err := utils.CompileGlob(ban, false)
if err != nil {
server.logger.Error("internal", "couldn't compile ban regex", ban, err.Error())
service.Notice(rb, "An error occurred")
return
}
var collateralDamage []string
for _, mcl := range channel.Members() {
if mcl != target && banRe.MatchString(mcl.NickMaskCasefolded()) {
collateralDamage = append(collateralDamage, mcl.Nick())
}
}
service.Notice(rb, fmt.Sprintf(client.t("User %[1]s can be banned by hostname: /MODE %[2]s +b %[3]s"), details.nick, channel.Name(), ban))
success = true
if len(collateralDamage) != 0 {
service.Notice(rb, fmt.Sprintf(client.t("Warning: this ban will affect %d other users:"), len(collateralDamage)))
for _, line := range utils.BuildTokenLines(maxLastArgLength, collateralDamage, " ") {
service.Notice(rb, line)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,14 +5,12 @@
package irc
import (
"regexp"
"strings"
"sync"
"time"
"github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/modes"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/caps"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/utils"
)
// ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks
@ -28,14 +26,6 @@ func (clients *ClientManager) Initialize() {
clients.bySkeleton = make(map[string]*Client)
}
// Count returns how many clients are in the manager.
func (clients *ClientManager) Count() int {
clients.RLock()
defer clients.RUnlock()
count := len(clients.byNick)
return count
}
// Get retrieves a client from the manager, if they exist.
func (clients *ClientManager) Get(nick string) *Client {
casefoldedName, err := CasefoldName(nick)
@ -48,9 +38,8 @@ func (clients *ClientManager) Get(nick string) *Client {
return nil
}
func (clients *ClientManager) removeInternal(client *Client) (err error) {
func (clients *ClientManager) removeInternal(client *Client, oldcfnick, oldskeleton string) (err error) {
// requires holding the writable Lock()
oldcfnick, oldskeleton := client.uniqueIdentifiers()
if oldcfnick == "*" || oldcfnick == "" {
return errNickMissing
}
@ -88,31 +77,14 @@ func (clients *ClientManager) Remove(client *Client) error {
clients.Lock()
defer clients.Unlock()
return clients.removeInternal(client)
}
// Handles a RESUME by attaching a session to a designated client. It is the
// caller's responsibility to verify that the resume is allowed (checking tokens,
// TLS status, etc.) before calling this.
func (clients *ClientManager) Resume(oldClient *Client, session *Session) (err error) {
clients.Lock()
defer clients.Unlock()
cfnick := oldClient.NickCasefolded()
if _, ok := clients.byNick[cfnick]; !ok {
return errNickMissing
}
success, _, _, _ := oldClient.AddSession(session)
if !success {
return errNickMissing
}
return nil
oldcfnick, oldskeleton := client.uniqueIdentifiers()
return clients.removeInternal(client, oldcfnick, oldskeleton)
}
// SetNick sets a client's nickname, validating it against nicknames in use
func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string) (setNick string, err error, returnedFromAway bool) {
// XXX: dryRun validates a client's ability to claim a nick, without
// actually claiming it
func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string, dryRun bool) (setNick string, err error, awayChanged bool) {
config := client.server.Config()
var newCfNick, newSkeleton string
@ -122,9 +94,20 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
accountName := client.accountName
settings := client.accountSettings
registered := client.registered
realname := client.realname
client.stateMutex.RUnlock()
// these restrictions have grandfather exceptions for nicknames registered
// on previous versions of Ergo:
if newNick != accountName {
// can't contain "disfavored" characters like <, or start with a $ because
// it collides with the massmessage mask syntax. '0' conflicts with the use of 0
// as a placeholder in WHOX (#1896):
if strings.ContainsAny(newNick, disfavoredNameCharacters) || strings.HasPrefix(newNick, "$") ||
newNick == "0" {
return "", errNicknameInvalid, false
}
}
// recompute always-on status, because client.alwaysOn is not set for unregistered clients
var alwaysOn, useAccountName bool
if account != "" {
@ -132,8 +115,10 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
useAccountName = alwaysOn || config.Accounts.NickReservation.ForceNickEqualsAccount
}
nickIsReserved := false
if useAccountName {
if registered && newNick != accountName && newNick != "" {
if registered && newNick != accountName {
return "", errNickAccountMismatch, false
}
newNick = accountName
@ -148,7 +133,7 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
return "", errNickMissing, false
}
if account == "" && config.Accounts.NickReservation.ForceGuestFormat {
if account == "" && config.Accounts.NickReservation.ForceGuestFormat && !dryRun {
newCfNick, err = CasefoldName(newNick)
if err != nil {
return "", errNicknameInvalid, false
@ -173,13 +158,19 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
return "", errNicknameInvalid, false
}
if restrictedCasefoldedNicks[newCfNick] || restrictedSkeletons[newSkeleton] {
if config.isRelaymsgIdentifier(newNick) {
return "", errNicknameInvalid, false
}
if restrictedCasefoldedNicks.Has(newCfNick) || restrictedSkeletons.Has(newSkeleton) {
return "", errNicknameInvalid, false
}
reservedAccount, method := client.server.accounts.EnforcementStatus(newCfNick, newSkeleton)
if method == NickEnforcementStrict && reservedAccount != "" && reservedAccount != account {
return "", errNicknameReserved, false
// see #2135: we want to enter the critical section, see if the nick is actually in use,
// and return errNicknameInUse in that case
nickIsReserved = true
}
}
@ -201,36 +192,24 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
currentClient := clients.byNick[newCfNick]
// the client may just be changing case
if currentClient != nil && currentClient != client && session != nil {
if currentClient != nil && currentClient != client {
// these conditions forbid reattaching to an existing session:
if registered || !bouncerAllowed || account == "" || account != currentClient.Account() {
if registered || !bouncerAllowed || account == "" || account != currentClient.Account() ||
dryRun || session == nil {
return "", errNicknameInUse, false
}
// check TLS modes
if client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
if useAccountName {
// #955: this is fatal because they can't fix it by trying a different nick
return "", errInsecureReattach, false
} else {
return "", errNicknameInUse, false
}
}
reattachSuccessful, numSessions, lastSeen, back := currentClient.AddSession(session)
reattachSuccessful, numSessions, lastSeen, wasAway, nowAway := currentClient.AddSession(session)
if !reattachSuccessful {
return "", errNicknameInUse, false
}
if numSessions == 1 {
invisible := currentClient.HasMode(modes.Invisible)
operator := currentClient.HasMode(modes.Operator) || currentClient.HasMode(modes.LocalOperator)
operator := currentClient.HasMode(modes.Operator)
client.server.stats.AddRegistered(invisible, operator)
}
session.autoreplayMissedSince = lastSeen
// TODO: transition mechanism for #1065, clean this up eventually:
if currentClient.Realname() == "" {
currentClient.SetRealname(realname)
}
// successful reattach!
return newNick, nil, back
return newNick, nil, wasAway != nowAway
} else if currentClient == client && currentClient.Nick() == newNick {
return "", errNoop, false
}
@ -239,11 +218,19 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
if skeletonHolder != nil && skeletonHolder != client {
return "", errNicknameInUse, false
}
if nickIsReserved {
return "", errNicknameReserved, false
}
if dryRun {
return "", nil, false
}
formercfnick, formerskeleton := client.uniqueIdentifiers()
if changeSuccess := client.SetNick(newNick, newCfNick, newSkeleton); !changeSuccess {
return "", errClientDestroyed, false
}
clients.removeInternal(client)
clients.removeInternal(client, formercfnick, formerskeleton)
clients.byNick[newCfNick] = client
clients.bySkeleton[newSkeleton] = client
return newNick, nil, false
@ -261,13 +248,14 @@ func (clients *ClientManager) AllClients() (result []*Client) {
return
}
// AllWithCaps returns all clients with the given capabilities.
func (clients *ClientManager) AllWithCaps(capabs ...caps.Capability) (sessions []*Session) {
// AllWithCapsNotify returns all sessions that support cap-notify.
func (clients *ClientManager) AllWithCapsNotify() (sessions []*Session) {
clients.RLock()
defer clients.RUnlock()
for _, client := range clients.byNick {
for _, session := range client.Sessions() {
if session.capabilities.HasAll(capabs...) {
// cap-notify is implicit in cap version 302 and above
if session.capabilities.Has(caps.CapNotify) || 302 <= session.capVersion {
sessions = append(sessions, session)
}
}
@ -276,21 +264,16 @@ func (clients *ClientManager) AllWithCaps(capabs ...caps.Capability) (sessions [
return
}
// AllWithCapsNotify returns all clients with the given capabilities, and that support cap-notify.
func (clients *ClientManager) AllWithCapsNotify(capabs ...caps.Capability) (sessions []*Session) {
capabs = append(capabs, caps.CapNotify)
// AllWithPushSubscriptions returns all clients that are always-on with an active push subscription.
func (clients *ClientManager) AllWithPushSubscriptions() (result []*Client) {
clients.RLock()
defer clients.RUnlock()
for _, client := range clients.byNick {
for _, session := range client.Sessions() {
// cap-notify is implicit in cap version 302 and above
if session.capabilities.HasAll(capabs...) || 302 <= session.capVersion {
sessions = append(sessions, session)
}
if client.hasPushSubscriptions() && client.AlwaysOn() {
result = append(result, client)
}
}
return
return result
}
// FindAll returns all clients that match the given userhost mask.
@ -318,133 +301,15 @@ func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
return set
}
//
// usermask to regexp
//
//TODO(dan): move this over to generally using glob syntax instead?
// kinda more expected in normal ban/etc masks, though regex is useful (probably as an extban?)
type MaskInfo struct {
TimeCreated time.Time
CreatorNickmask string
CreatorAccount string
}
// UserMaskSet holds a set of client masks and lets you match hostnames to them.
type UserMaskSet struct {
sync.RWMutex
serialCacheUpdateMutex sync.Mutex
masks map[string]MaskInfo
regexp *regexp.Regexp
}
func NewUserMaskSet() *UserMaskSet {
return new(UserMaskSet)
}
// Add adds the given mask to this set.
func (set *UserMaskSet) Add(mask, creatorNickmask, creatorAccount string) (maskAdded string, err error) {
casefoldedMask, err := CanonicalizeMaskWildcard(mask)
if err != nil {
return
// Determine the canonical / unfolded form of a nick, if a client matching it
// is present (or always-on).
func (clients *ClientManager) UnfoldNick(cfnick string) (nick string) {
clients.RLock()
c := clients.byNick[cfnick]
clients.RUnlock()
if c != nil {
return c.Nick()
} else {
return cfnick
}
set.serialCacheUpdateMutex.Lock()
defer set.serialCacheUpdateMutex.Unlock()
set.Lock()
if set.masks == nil {
set.masks = make(map[string]MaskInfo)
}
_, present := set.masks[casefoldedMask]
if !present {
maskAdded = casefoldedMask
set.masks[casefoldedMask] = MaskInfo{
TimeCreated: time.Now().UTC(),
CreatorNickmask: creatorNickmask,
CreatorAccount: creatorAccount,
}
}
set.Unlock()
if !present {
set.setRegexp()
}
return
}
// Remove removes the given mask from this set.
func (set *UserMaskSet) Remove(mask string) (maskRemoved string, err error) {
mask, err = CanonicalizeMaskWildcard(mask)
if err != nil {
return
}
set.serialCacheUpdateMutex.Lock()
defer set.serialCacheUpdateMutex.Unlock()
set.Lock()
_, removed := set.masks[mask]
if removed {
maskRemoved = mask
delete(set.masks, mask)
}
set.Unlock()
if removed {
set.setRegexp()
}
return
}
func (set *UserMaskSet) SetMasks(masks map[string]MaskInfo) {
set.Lock()
set.masks = masks
set.Unlock()
set.setRegexp()
}
func (set *UserMaskSet) Masks() (result map[string]MaskInfo) {
set.RLock()
defer set.RUnlock()
result = make(map[string]MaskInfo, len(set.masks))
for mask, info := range set.masks {
result[mask] = info
}
return
}
// Match matches the given n!u@h.
func (set *UserMaskSet) Match(userhost string) bool {
set.RLock()
regexp := set.regexp
set.RUnlock()
if regexp == nil {
return false
}
return regexp.MatchString(userhost)
}
func (set *UserMaskSet) Length() int {
set.RLock()
defer set.RUnlock()
return len(set.masks)
}
func (set *UserMaskSet) setRegexp() {
set.RLock()
maskExprs := make([]string, len(set.masks))
for mask := range set.masks {
maskExprs = append(maskExprs, mask)
}
set.RUnlock()
re, _ := utils.CompileMasks(maskExprs)
set.Lock()
set.regexp = re
set.Unlock()
}

View File

@ -4,14 +4,16 @@
package irc
import (
"fmt"
"testing"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/languages"
"github.com/ergochat/ergo/irc/utils"
)
func TestGenerateBatchID(t *testing.T) {
var session Session
s := make(utils.StringSet)
s := make(utils.HashSet[string])
count := 100000
for i := 0; i < count; i++ {
@ -30,6 +32,47 @@ func BenchmarkGenerateBatchID(b *testing.B) {
}
}
func BenchmarkNames(b *testing.B) {
channelSize := 1024
server := &Server{
name: "ergo.test",
}
lm, err := languages.NewManager(false, "", "")
if err != nil {
b.Fatal(err)
}
server.config.Store(&Config{
languageManager: lm,
})
for i := 0; i < b.N; i++ {
channel := &Channel{
name: "#test",
nameCasefolded: "#test",
server: server,
members: make(MemberSet),
}
for j := 0; j < channelSize; j++ {
nick := fmt.Sprintf("client_%d", j)
client := &Client{
server: server,
nick: nick,
nickCasefolded: nick,
}
channel.members.Add(client)
channel.regenerateMembersCache()
session := &Session{
client: client,
}
rb := NewResponseBuffer(session)
channel.Names(client, rb)
if len(rb.messages) < 2 {
b.Fatalf("not enough messages: %d", len(rb.messages))
}
// to inspect the messages: line, _ := rb.messages[0].Line()
}
}
}
func TestUserMasks(t *testing.T) {
var um UserMaskSet

View File

@ -104,3 +104,33 @@ func BenchmarkCloaks(b *testing.B) {
config.ComputeCloak(v6ip)
}
}
func TestAccountCloak(t *testing.T) {
config := cloakConfForTesting()
// just assert that we get all distinct values
assertEqual(config.ComputeAccountCloak("shivaram"), "8yu8kunudb45ztxm.oragono", t)
assertEqual(config.ComputeAccountCloak("dolph🐬n"), "hhgeqsvzeagv3wjw.oragono", t)
assertEqual(config.ComputeAccountCloak("SHIVARAM"), "bgx32x4r7qzih4uh.oragono", t)
assertEqual(config.ComputeAccountCloak("ed"), "j5autmgxtdjdyzf4.oragono", t)
}
func TestAccountCloakCollisions(t *testing.T) {
config := cloakConfForTesting()
v4ip := easyParseIP("97.97.97.97")
v4cloak := config.ComputeCloak(v4ip)
// "aaaa" is the same bytestring as 97.97.97.97
aaaacloak := config.ComputeAccountCloak("aaaa")
if v4cloak == aaaacloak {
t.Errorf("cloak collision between 97.97.97.97 and aaaa: %s", v4cloak)
}
}
func BenchmarkAccountCloaks(b *testing.B) {
config := cloakConfForTesting()
b.ResetTimer()
for i := 0; i < b.N; i++ {
config.ComputeAccountCloak("shivaram")
}
}

View File

@ -6,18 +6,19 @@ import (
"fmt"
"net"
"golang.org/x/crypto/sha3"
"crypto/sha3"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/utils"
)
type CloakConfig struct {
Enabled bool
Netname string
CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
NumBits int `yaml:"num-bits"`
LegacySecretValue string `yaml:"secret"`
Enabled bool
EnabledForAlwaysOn bool `yaml:"enabled-for-always-on"`
Netname string
CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
NumBits int `yaml:"num-bits"`
LegacySecretValue string `yaml:"secret"`
secret string
numBytes int
@ -26,14 +27,10 @@ type CloakConfig struct {
}
func (cloakConfig *CloakConfig) Initialize() {
if !cloakConfig.Enabled {
return
}
// sanity checks:
numBits := cloakConfig.NumBits
if 0 == numBits {
numBits = 80
numBits = 64
} else if 256 < numBits {
numBits = 256
}
@ -69,12 +66,30 @@ func (config *CloakConfig) ComputeCloak(ip net.IP) string {
} else {
masked = ip.Mask(config.ipv6Mask)
}
return config.macAndCompose(masked)
}
func (config *CloakConfig) macAndCompose(b []byte) string {
// SHA3(K || M):
// https://crypto.stackexchange.com/questions/17735/is-hmac-needed-for-a-sha-3-based-mac
input := make([]byte, len(config.secret)+len(masked))
input := make([]byte, len(config.secret)+len(b))
copy(input, config.secret[:])
copy(input[len(config.secret):], masked)
copy(input[len(config.secret):], b)
digest := sha3.Sum512(input)
b32digest := utils.B32Encoder.EncodeToString(digest[:config.numBytes])
return fmt.Sprintf("%s.%s", b32digest, config.Netname)
}
func (config *CloakConfig) ComputeAccountCloak(accountName string) string {
// XXX don't bother checking EnabledForAlwaysOn, since if it's disabled,
// we need to use the server name which we don't have
if config.NumBits == 0 || config.secret == "" {
return config.Netname
}
// pad with 16 initial bytes of zeroes, avoiding any possibility of collision
// with a masked IP that could be an input to ComputeCloak:
paddedAccountName := make([]byte, 16+len(accountName))
copy(paddedAccountName[16:], accountName[:])
return config.macAndCompose(paddedAccountName)
}

View File

@ -6,22 +6,38 @@
package irc
import (
"github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/modes"
"github.com/ergochat/irc-go/ircmsg"
)
// Command represents a command accepted from a client.
type Command struct {
handler func(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool
oper bool
handler func(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool
usablePreReg bool
allowedInBatch bool // allowed in client-to-server batches
minParams int
capabs []string
}
// resolveCommand returns the command to execute in response to a user input line.
// some invalid commands (unknown command verb, invalid UTF8) get a fake handler
// to ensure that labeled-response still works as expected.
func (server *Server) resolveCommand(command string, invalidUTF8 bool) (canonicalName string, result Command) {
if invalidUTF8 {
return command, invalidUtf8Command
}
if cmd, ok := Commands[command]; ok {
return command, cmd
}
if target, ok := server.Config().Server.CommandAliases[command]; ok {
if cmd, ok := Commands[target]; ok {
return target, cmd
}
}
return command, unknownCommand
}
// Run runs this command with the given client/message.
func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ircmsg.IrcMessage) (exiting bool) {
func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ircmsg.Message) (exiting bool) {
rb := NewResponseBuffer(session)
rb.Label = GetLabel(msg)
@ -32,10 +48,6 @@ func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ir
rb.Add(nil, server.name, ERR_NOTREGISTERED, "*", client.t("You need to register before you can use that command"))
return false
}
if cmd.oper && !client.HasMode(modes.Operator) {
rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied - You're not an IRC operator"))
return false
}
if len(cmd.capabs) > 0 && !client.HasRoleCapabs(cmd.capabs...) {
rb.Add(nil, server.name, ERR_NOPRIVILEGES, client.Nick(), client.t("Permission Denied"))
return false
@ -59,7 +71,7 @@ func (cmd *Command) Run(server *Server, client *Client, session *Session, msg ir
}
if client.registered {
client.Touch(session)
client.Touch(session) // even if `exiting`, we bump the lastSeen timestamp
}
return exiting
@ -81,6 +93,10 @@ var Commands map[string]Command
func init() {
Commands = map[string]Command{
"ACCEPT": {
handler: acceptHandler,
minParams: 1,
},
"AMBIANCE": {
handler: sceneHandler,
minParams: 2,
@ -91,18 +107,15 @@ func init() {
minParams: 1,
},
"AWAY": {
handler: awayHandler,
minParams: 0,
handler: awayHandler,
usablePreReg: true,
minParams: 0,
},
"BATCH": {
handler: batchHandler,
minParams: 1,
allowedInBatch: true,
},
"BRB": {
handler: brbHandler,
minParams: 0,
},
"CAP": {
handler: capHandler,
usablePreReg: true,
@ -115,7 +128,7 @@ func init() {
"DEBUG": {
handler: debugHandler,
minParams: 1,
oper: true,
capabs: []string{"rehash"},
},
"DEFCON": {
handler: defconHandler,
@ -124,12 +137,11 @@ func init() {
"DEOPER": {
handler: deoperHandler,
minParams: 0,
oper: true,
},
"DLINE": {
handler: dlineHandler,
minParams: 1,
oper: true,
capabs: []string{"ban"},
},
"EXTJWT": {
handler: extjwtHandler,
@ -158,6 +170,10 @@ func init() {
handler: isonHandler,
minParams: 1,
},
"ISUPPORT": {
handler: isupportHandler,
usablePreReg: true,
},
"JOIN": {
handler: joinHandler,
minParams: 1,
@ -169,13 +185,12 @@ func init() {
"KILL": {
handler: killHandler,
minParams: 1,
oper: true,
capabs: []string{"local_kill"}, //TODO(dan): when we have S2S, this will be checked in the command handler itself
capabs: []string{"kill"},
},
"KLINE": {
handler: klineHandler,
minParams: 1,
oper: true,
capabs: []string{"ban"},
},
"LANGUAGE": {
handler: languageHandler,
@ -190,6 +205,15 @@ func init() {
handler: lusersHandler,
minParams: 0,
},
"MARKREAD": {
handler: markReadHandler,
minParams: 0, // send FAIL instead of ERR_NEEDMOREPARAMS
},
"METADATA": {
handler: metadataHandler,
minParams: 2,
usablePreReg: true,
},
"MODE": {
handler: modeHandler,
minParams: 1,
@ -237,6 +261,10 @@ func init() {
usablePreReg: true,
minParams: 1,
},
"PERSISTENCE": {
handler: persistenceHandler,
minParams: 1,
},
"PING": {
handler: pingHandler,
usablePreReg: true,
@ -252,15 +280,19 @@ func init() {
minParams: 2,
allowedInBatch: true,
},
"RELAYMSG": {
handler: relaymsgHandler,
minParams: 3,
},
"REGISTER": {
handler: registerHandler,
minParams: 3,
usablePreReg: true,
},
"RENAME": {
handler: renameHandler,
minParams: 2,
},
"RESUME": {
handler: resumeHandler,
usablePreReg: true,
minParams: 1,
},
"SAJOIN": {
handler: sajoinHandler,
minParams: 1,
@ -269,7 +301,7 @@ func init() {
"SANICK": {
handler: sanickHandler,
minParams: 2,
oper: true,
capabs: []string{"samode"},
},
"SAMODE": {
handler: modeHandler,
@ -296,10 +328,13 @@ func init() {
usablePreReg: true,
minParams: 0,
},
"REDACT": {
handler: redactHandler,
minParams: 2,
},
"REHASH": {
handler: rehashHandler,
minParams: 0,
oper: true,
capabs: []string{"rehash"},
},
"TIME": {
@ -310,15 +345,24 @@ func init() {
handler: topicHandler,
minParams: 1,
},
"UBAN": {
handler: ubanHandler,
minParams: 1,
capabs: []string{"ban"},
},
"UNDLINE": {
handler: unDLineHandler,
minParams: 1,
oper: true,
capabs: []string{"ban"},
},
"UNINVITE": {
handler: inviteHandler,
minParams: 2,
},
"UNKLINE": {
handler: unKLineHandler,
minParams: 1,
oper: true,
capabs: []string{"ban"},
},
"USER": {
handler: userHandler,
@ -332,6 +376,11 @@ func init() {
"USERS": {
handler: usersHandler,
},
"VERIFY": {
handler: verifyHandler,
usablePreReg: true,
minParams: 2,
},
"VERSION": {
handler: versionHandler,
minParams: 0,
@ -341,6 +390,10 @@ func init() {
usablePreReg: true,
minParams: 4,
},
"WEBPUSH": {
handler: webpushHandler,
minParams: 2,
},
"WHO": {
handler: whoHandler,
minParams: 1,

File diff suppressed because it is too large Load Diff

101
irc/config_test.go Normal file
View File

@ -0,0 +1,101 @@
// Copyright (c) 2020 Shivaram Lingamneni
// released under the MIT license
package irc
import (
"reflect"
"testing"
)
func TestEnvironmentOverrides(t *testing.T) {
var config Config
config.Server.Compatibility.SendUnprefixedSasl = true
config.History.Enabled = true
defaultUserModes := "+i"
config.Accounts.DefaultUserModes = &defaultUserModes
config.Server.WebSockets.AllowedOrigins = []string{"https://www.ircv3.net"}
config.Server.MOTD = "long.motd.txt" // overwrite this
env := []string{
`USER=shivaram`, // unrelated var
`ORAGONO_USER=oragono`, // this should be ignored as well
`ERGO__NETWORK__NAME=example.com`,
`ORAGONO__SERVER__COMPATIBILITY__FORCE_TRAILING=false`,
`ORAGONO__SERVER__COERCE_IDENT="~user"`,
`ERGO__SERVER__MOTD=short.motd.txt`,
`ORAGONO__ACCOUNTS__NICK_RESERVATION__ENABLED=true`,
`ERGO__ACCOUNTS__DEFAULT_USER_MODES="+iR"`,
`ORAGONO__SERVER__IP_CLOAKING={"enabled": true, "enabled-for-always-on": true, "netname": "irc", "cidr-len-ipv4": 32, "cidr-len-ipv6": 64, "num-bits": 64}`,
}
for _, envPair := range env {
_, _, err := mungeFromEnvironment(&config, envPair)
if err != nil {
t.Errorf("couldn't apply override `%s`: %v", envPair, err)
}
}
if config.Network.Name != "example.com" {
t.Errorf("unexpected value of network.name: %s", config.Network.Name)
}
if config.Server.CoerceIdent != "~user" {
t.Errorf("unexpected value of coerce-ident: %s", config.Server.CoerceIdent)
}
if config.Server.MOTD != "short.motd.txt" {
t.Errorf("unexpected value of motd: %s", config.Server.MOTD)
}
if !config.Accounts.NickReservation.Enabled {
t.Errorf("did not set bool as expected")
}
if !config.Server.Compatibility.SendUnprefixedSasl {
t.Errorf("overwrote unrelated field")
}
if !config.History.Enabled {
t.Errorf("overwrote unrelated field")
}
if !reflect.DeepEqual(config.Server.WebSockets.AllowedOrigins, []string{"https://www.ircv3.net"}) {
t.Errorf("overwrote unrelated field: %#v", config.Server.WebSockets.AllowedOrigins)
}
cloakConf := config.Server.Cloaks
if !(cloakConf.Enabled == true && cloakConf.EnabledForAlwaysOn == true && cloakConf.Netname == "irc" && cloakConf.CidrLenIPv6 == 64) {
t.Errorf("bad value of Cloaks: %#v", config.Server.Cloaks)
}
if *config.Server.Compatibility.ForceTrailing != false {
t.Errorf("couldn't set unset ptr field to false")
}
if *config.Accounts.DefaultUserModes != "+iR" {
t.Errorf("couldn't override pre-set ptr field")
}
}
func TestEnvironmentOverrideErrors(t *testing.T) {
var config Config
config.Server.Compatibility.SendUnprefixedSasl = true
config.History.Enabled = true
invalidEnvs := []string{
`ORAGONO__=asdf`,
`ORAGONO__SERVER__=asdf`,
`ORAGONO__SERVER____=asdf`,
`ORAGONO__NONEXISTENT_KEY=1`,
`ORAGONO__SERVER__NONEXISTENT_KEY=1`,
// invalid yaml:
`ORAGONO__SERVER__IP_CLOAKING__NETNAME="`,
// invalid type:
`ORAGONO__SERVER__IP_CLOAKING__NUM_BITS=asdf`,
`ORAGONO__SERVER__STS=[]`,
// index into non-struct:
`ORAGONO__NETWORK__NAME__QUX=1`,
// private field:
`ORAGONO__SERVER__PASSWORDBYTES="asdf"`,
}
for _, env := range invalidEnvs {
success, _, err := mungeFromEnvironment(&config, env)
if err == nil || success {
t.Errorf("accepted invalid env override `%s`", env)
}
}
}

View File

@ -4,13 +4,14 @@
package connection_limits
import (
"crypto/md5"
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/flatip"
"github.com/ergochat/ergo/irc/utils"
)
var (
@ -19,14 +20,23 @@ var (
)
type CustomLimitConfig struct {
Nets []string
MaxConcurrent int `yaml:"max-concurrent-connections"`
MaxPerWindow int `yaml:"max-connections-per-window"`
}
// tuples the key-value pair of a CIDR and its custom limit/throttle values
type customLimit struct {
CustomLimitConfig
ipNet net.IPNet
name [16]byte
customID string // operator-configured identifier for a custom net
maxConcurrent int
maxPerWindow int
nets []flatip.IPNet
}
type limiterKey struct {
maskedIP flatip.IP
prefixLen uint8 // 0 for the fake nets we generate for custom limits
}
// LimiterConfig controls the automated connection limits.
@ -38,8 +48,7 @@ type rawLimiterConfig struct {
Throttle bool
Window time.Duration
MaxPerWindow int `yaml:"max-connections-per-window"`
BanDuration time.Duration `yaml:"throttle-ban-duration"`
MaxPerWindow int `yaml:"max-connections-per-window"`
CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
@ -52,9 +61,7 @@ type rawLimiterConfig struct {
type LimiterConfig struct {
rawLimiterConfig
ipv4Mask net.IPMask
ipv6Mask net.IPMask
exemptedNets []net.IPNet
exemptedNets []flatip.IPNet
customLimits []customLimit
}
@ -66,24 +73,41 @@ func (config *LimiterConfig) UnmarshalYAML(unmarshal func(interface{}) error) (e
}
func (config *LimiterConfig) postprocess() (err error) {
config.exemptedNets, err = utils.ParseNetList(config.Exempted)
exemptedNets, err := utils.ParseNetList(config.Exempted)
if err != nil {
return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
}
for netStr, customLimitConf := range config.CustomLimits {
normalizedNet, err := utils.NormalizedNetFromString(netStr)
if err != nil {
return fmt.Errorf("Could not parse custom limit specification: %v", err.Error())
}
config.customLimits = append(config.customLimits, customLimit{
CustomLimitConfig: customLimitConf,
ipNet: normalizedNet,
})
config.exemptedNets = make([]flatip.IPNet, len(exemptedNets))
for i, exempted := range exemptedNets {
config.exemptedNets[i] = flatip.FromNetIPNet(exempted)
}
config.ipv4Mask = net.CIDRMask(config.CidrLenIPv4, 32)
config.ipv6Mask = net.CIDRMask(config.CidrLenIPv6, 128)
for identifier, customLimitConf := range config.CustomLimits {
nets := make([]flatip.IPNet, len(customLimitConf.Nets))
for i, netStr := range customLimitConf.Nets {
normalizedNet, err := flatip.ParseToNormalizedNet(netStr)
if err != nil {
return fmt.Errorf("Bad net %s in custom-limits block %s: %w", netStr, identifier, err)
}
nets[i] = normalizedNet
}
if len(customLimitConf.Nets) == 0 {
// see #1421: this is the legacy config format where the
// dictionary key of the block is a CIDR string
normalizedNet, err := flatip.ParseToNormalizedNet(identifier)
if err != nil {
return fmt.Errorf("Custom limit block %s has no defined nets", identifier)
}
nets = []flatip.IPNet{normalizedNet}
}
config.customLimits = append(config.customLimits, customLimit{
maxConcurrent: customLimitConf.MaxConcurrent,
maxPerWindow: customLimitConf.MaxPerWindow,
name: md5.Sum([]byte(identifier)),
customID: identifier,
nets: nets,
})
}
return nil
}
@ -95,51 +119,56 @@ type Limiter struct {
config *LimiterConfig
// IP/CIDR -> count of clients connected from there:
limiter map[string]int
limiter map[limiterKey]int
// IP/CIDR -> throttle state:
throttler map[string]ThrottleDetails
throttler map[limiterKey]ThrottleDetails
}
// addrToKey canonicalizes `addr` to a string key, and returns
// the relevant connection limit and throttle max-per-window values
func (cl *Limiter) addrToKey(addr net.IP) (key string, limit int, throttle int) {
// `key` will be a CIDR string like "8.8.8.8/32" or "2001:0db8::/32"
func (cl *Limiter) addrToKey(addr flatip.IP) (key limiterKey, customID string, limit int, throttle int) {
for _, custom := range cl.config.customLimits {
if custom.ipNet.Contains(addr) {
return custom.ipNet.String(), custom.MaxConcurrent, custom.MaxPerWindow
for _, net := range custom.nets {
if net.Contains(addr) {
return limiterKey{maskedIP: custom.name, prefixLen: 0}, custom.customID, custom.maxConcurrent, custom.maxPerWindow
}
}
}
var ipNet net.IPNet
addrv4 := addr.To4()
if addrv4 != nil {
ipNet = net.IPNet{
IP: addrv4.Mask(cl.config.ipv4Mask),
Mask: cl.config.ipv4Mask,
}
var prefixLen int
if addr.IsIPv4() {
prefixLen = cl.config.CidrLenIPv4
addr = addr.Mask(prefixLen, 32)
prefixLen += 96
} else {
ipNet = net.IPNet{
IP: addr.Mask(cl.config.ipv6Mask),
Mask: cl.config.ipv6Mask,
}
prefixLen = cl.config.CidrLenIPv6
addr = addr.Mask(prefixLen, 128)
}
return ipNet.String(), cl.config.MaxConcurrent, cl.config.MaxPerWindow
return limiterKey{maskedIP: addr, prefixLen: uint8(prefixLen)}, "", cl.config.MaxConcurrent, cl.config.MaxPerWindow
}
// AddClient adds a client to our population if possible. If we can't, throws an error instead.
func (cl *Limiter) AddClient(addr net.IP) error {
func (cl *Limiter) AddClient(addr flatip.IP) error {
cl.Lock()
defer cl.Unlock()
// we don't track populations for exempted addresses or nets - this is by design
if utils.IPInNets(addr, cl.config.exemptedNets) {
if flatip.IPInNets(addr, cl.config.exemptedNets) {
return nil
}
addrString, maxConcurrent, maxPerWindow := cl.addrToKey(addr)
addrString, _, maxConcurrent, maxPerWindow := cl.addrToKey(addr)
// check limiter
var count int
if cl.config.Count {
count = cl.limiter[addrString] + 1
if count > maxConcurrent {
return ErrLimitExceeded
}
}
// XXX check throttle first; if we checked limit first and then checked throttle,
// we'd have to decrement the limit on an unsuccessful throttle check
if cl.config.Throttle {
details := cl.throttler[addrString] // retrieve mutable throttle state from the map
// add in constant state to process the limiting operation
@ -151,16 +180,13 @@ func (cl *Limiter) AddClient(addr net.IP) error {
throttled, _ := g.Touch() // actually check the limit
cl.throttler[addrString] = g.ThrottleDetails // store modified mutable state
if throttled {
// back out the limiter add
return ErrThrottleExceeded
}
}
// now check limiter
// success, record in limiter
if cl.config.Count {
count := cl.limiter[addrString] + 1
if count > maxConcurrent {
return ErrLimitExceeded
}
cl.limiter[addrString] = count
}
@ -168,15 +194,15 @@ func (cl *Limiter) AddClient(addr net.IP) error {
}
// RemoveClient removes the given address from our population
func (cl *Limiter) RemoveClient(addr net.IP) {
func (cl *Limiter) RemoveClient(addr flatip.IP) {
cl.Lock()
defer cl.Unlock()
if !cl.config.Count || utils.IPInNets(addr, cl.config.exemptedNets) {
if !cl.config.Count || flatip.IPInNets(addr, cl.config.exemptedNets) {
return
}
addrString, _, _ := cl.addrToKey(addr)
addrString, _, _, _ := cl.addrToKey(addr)
count := cl.limiter[addrString]
count -= 1
if count < 0 {
@ -185,16 +211,56 @@ func (cl *Limiter) RemoveClient(addr net.IP) {
cl.limiter[addrString] = count
}
// ResetThrottle resets the throttle count for an IP
func (cl *Limiter) ResetThrottle(addr net.IP) {
type LimiterStatus struct {
Exempt bool
Count int
MaxCount int
Throttle int
MaxPerWindow int
ThrottleDuration time.Duration
}
func (cl *Limiter) Status(addr flatip.IP) (netName string, status LimiterStatus) {
cl.Lock()
defer cl.Unlock()
if !cl.config.Throttle || utils.IPInNets(addr, cl.config.exemptedNets) {
if flatip.IPInNets(addr, cl.config.exemptedNets) {
status.Exempt = true
return
}
addrString, _, _ := cl.addrToKey(addr)
status.ThrottleDuration = cl.config.Window
limiterKey, customID, maxConcurrent, maxPerWindow := cl.addrToKey(addr)
status.MaxCount = maxConcurrent
status.MaxPerWindow = maxPerWindow
status.Count = cl.limiter[limiterKey]
status.Throttle = cl.throttler[limiterKey].Count
netName = customID
if netName == "" {
netName = flatip.IPNet{
IP: limiterKey.maskedIP,
PrefixLen: limiterKey.prefixLen,
}.String()
}
return
}
// ResetThrottle resets the throttle count for an IP
func (cl *Limiter) ResetThrottle(addr flatip.IP) {
cl.Lock()
defer cl.Unlock()
if !cl.config.Throttle || flatip.IPInNets(addr, cl.config.exemptedNets) {
return
}
addrString, _, _, _ := cl.addrToKey(addr)
delete(cl.throttler, addrString)
}
@ -204,10 +270,10 @@ func (cl *Limiter) ApplyConfig(config *LimiterConfig) {
defer cl.Unlock()
if cl.limiter == nil {
cl.limiter = make(map[string]int)
cl.limiter = make(map[limiterKey]int)
}
if cl.throttler == nil {
cl.throttler = make(map[string]ThrottleDetails)
cl.throttler = make(map[limiterKey]ThrottleDetails)
}
cl.config = config

View File

@ -4,15 +4,17 @@
package connection_limits
import (
"net"
"crypto/md5"
"testing"
"time"
"github.com/ergochat/ergo/irc/flatip"
)
func easyParseIP(ipstr string) (result net.IP) {
result = net.ParseIP(ipstr)
if result == nil {
panic(ipstr)
func easyParseIP(ipstr string) (result flatip.IP) {
result, err := flatip.ParseIP(ipstr)
if err != nil {
panic(err)
}
return
}
@ -32,7 +34,8 @@ var baseConfig = LimiterConfig{
Exempted: []string{"localhost"},
CustomLimits: map[string]CustomLimitConfig{
"8.8.0.0/16": {
"google": {
Nets: []string{"8.8.0.0/16"},
MaxConcurrent: 128,
MaxPerWindow: 256,
},
@ -46,18 +49,23 @@ func TestKeying(t *testing.T) {
var limiter Limiter
limiter.ApplyConfig(&config)
key, maxConc, maxWin := limiter.addrToKey(easyParseIP("1.1.1.1"))
assertEqual(key, "1.1.1.1/32", t)
// an ipv4 /32 looks like a /128 to us after applying the 4-in-6 mapping
key, _, maxConc, maxWin := limiter.addrToKey(easyParseIP("1.1.1.1"))
assertEqual(key.prefixLen, uint8(128), t)
assertEqual(key.maskedIP[12:], []byte{1, 1, 1, 1}, t)
assertEqual(maxConc, 4, t)
assertEqual(maxWin, 8, t)
key, maxConc, maxWin = limiter.addrToKey(easyParseIP("2607:5301:201:3100::7426"))
assertEqual(key, "2607:5301:201:3100::/64", t)
testIPv6 := easyParseIP("2607:5301:201:3100::7426")
key, _, maxConc, maxWin = limiter.addrToKey(testIPv6)
assertEqual(key.prefixLen, uint8(64), t)
assertEqual(flatip.IP(key.maskedIP), easyParseIP("2607:5301:201:3100::"), t)
assertEqual(maxConc, 4, t)
assertEqual(maxWin, 8, t)
key, maxConc, maxWin = limiter.addrToKey(easyParseIP("8.8.4.4"))
assertEqual(key, "8.8.0.0/16", t)
key, _, maxConc, maxWin = limiter.addrToKey(easyParseIP("8.8.4.4"))
assertEqual(key.prefixLen, uint8(0), t)
assertEqual([16]byte(key.maskedIP), md5.Sum([]byte("google")), t)
assertEqual(maxConc, 128, t)
assertEqual(maxWin, 256, t)
}

View File

@ -4,7 +4,6 @@
package connection_limits
import (
"net"
"reflect"
"testing"
"time"
@ -83,7 +82,7 @@ func makeTestThrottler(v4len, v6len int) *Limiter {
func TestConnectionThrottle(t *testing.T) {
throttler := makeTestThrottler(32, 64)
addr := net.ParseIP("8.8.8.8")
addr := easyParseIP("8.8.8.8")
for i := 0; i < 3; i += 1 {
err := throttler.AddClient(addr)
@ -97,14 +96,14 @@ func TestConnectionThrottleIPv6(t *testing.T) {
throttler := makeTestThrottler(32, 64)
var err error
err = throttler.AddClient(net.ParseIP("2001:0db8::1"))
err = throttler.AddClient(easyParseIP("2001:0db8::1"))
assertEqual(err, nil, t)
err = throttler.AddClient(net.ParseIP("2001:0db8::2"))
err = throttler.AddClient(easyParseIP("2001:0db8::2"))
assertEqual(err, nil, t)
err = throttler.AddClient(net.ParseIP("2001:0db8::3"))
err = throttler.AddClient(easyParseIP("2001:0db8::3"))
assertEqual(err, nil, t)
err = throttler.AddClient(net.ParseIP("2001:0db8::4"))
err = throttler.AddClient(easyParseIP("2001:0db8::4"))
assertEqual(err, ErrThrottleExceeded, t)
}
@ -112,13 +111,13 @@ func TestConnectionThrottleIPv4(t *testing.T) {
throttler := makeTestThrottler(24, 64)
var err error
err = throttler.AddClient(net.ParseIP("192.168.1.101"))
err = throttler.AddClient(easyParseIP("192.168.1.101"))
assertEqual(err, nil, t)
err = throttler.AddClient(net.ParseIP("192.168.1.102"))
err = throttler.AddClient(easyParseIP("192.168.1.102"))
assertEqual(err, nil, t)
err = throttler.AddClient(net.ParseIP("192.168.1.103"))
err = throttler.AddClient(easyParseIP("192.168.1.103"))
assertEqual(err, nil, t)
err = throttler.AddClient(net.ParseIP("192.168.1.104"))
err = throttler.AddClient(easyParseIP("192.168.1.104"))
assertEqual(err, ErrThrottleExceeded, t)
}

View File

@ -5,6 +5,7 @@
package irc
import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
@ -13,45 +14,61 @@ import (
"strings"
"time"
"github.com/oragono/oragono/irc/modes"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/bunt"
"github.com/ergochat/ergo/irc/datastore"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/utils"
"github.com/ergochat/ergo/irc/webpush"
"github.com/tidwall/buntdb"
)
const (
// 'version' of the database schema
keySchemaVersion = "db.version"
// latest schema of the db
latestDbSchema = "12"
// TODO migrate metadata keys as well
keyCloakSecret = "crypto.cloak_secret"
// 'version' of the database schema
// latest schema of the db
latestDbSchema = 24
)
var (
schemaVersionUUID = utils.UUID{0, 255, 85, 13, 212, 10, 191, 121, 245, 152, 142, 89, 97, 141, 219, 87} // AP9VDdQKv3n1mI5ZYY3bVw
cloakSecretUUID = utils.UUID{170, 214, 184, 208, 116, 181, 67, 75, 161, 23, 233, 16, 113, 251, 94, 229} // qta40HS1Q0uhF-kQcfte5Q
vapidKeysUUID = utils.UUID{87, 215, 189, 5, 65, 105, 249, 44, 65, 96, 170, 56, 187, 110, 12, 235} // V9e9BUFp-SxBYKo4u24M6w
keySchemaVersion = bunt.BuntKey(datastore.TableMetadata, schemaVersionUUID)
keyCloakSecret = bunt.BuntKey(datastore.TableMetadata, cloakSecretUUID)
keyVAPIDKeys = bunt.BuntKey(datastore.TableMetadata, vapidKeysUUID)
)
type SchemaChanger func(*Config, *buntdb.Tx) error
type SchemaChange struct {
InitialVersion string // the change will take this version
TargetVersion string // and transform it into this version
InitialVersion int // the change will take this version
TargetVersion int // and transform it into this version
Changer SchemaChanger
}
// maps an initial version to a schema change capable of upgrading it
var schemaChanges map[string]SchemaChange
// InitDB creates the database, implementing the `oragono initdb` command.
func InitDB(path string) {
func checkDBReadyForInit(path string) error {
_, err := os.Stat(path)
if err == nil {
log.Fatal("Datastore already exists (delete it manually to continue): ", path)
return fmt.Errorf("Datastore already exists (delete it manually to continue): %s", path)
} else if !os.IsNotExist(err) {
log.Fatal("Datastore path is inaccessible: ", err.Error())
return fmt.Errorf("Datastore path %s is inaccessible: %w", path, err)
}
return nil
}
// InitDB creates the database, implementing the `oragono initdb` command.
func InitDB(path string) error {
if err := checkDBReadyForInit(path); err != nil {
return err
}
err = initializeDB(path)
if err != nil {
log.Fatal("Could not save datastore: ", err.Error())
if err := initializeDB(path); err != nil {
return fmt.Errorf("Could not save datastore: %w", err)
}
return nil
}
// internal database initialization code
@ -64,8 +81,17 @@ func initializeDB(path string) error {
err = store.Update(func(tx *buntdb.Tx) error {
// set schema version
tx.Set(keySchemaVersion, latestDbSchema, nil)
tx.Set(keySchemaVersion, strconv.Itoa(latestDbSchema), nil)
tx.Set(keyCloakSecret, utils.GenerateSecretKey(), nil)
vapidKeys, err := webpush.GenerateVAPIDKeys()
if err != nil {
return err
}
j, err := json.Marshal(vapidKeys)
if err != nil {
return err
}
tx.Set(keyVAPIDKeys, string(j), nil)
return nil
})
@ -92,9 +118,9 @@ func openDatabaseInternal(config *Config, allowAutoupgrade bool) (db *buntdb.DB,
}()
// read the current version string
var version string
err = db.View(func(tx *buntdb.Tx) error {
version, err = tx.Get(keySchemaVersion)
var version int
err = db.View(func(tx *buntdb.Tx) (err error) {
version, err = retrieveSchemaVersion(tx)
return err
})
if err != nil {
@ -122,11 +148,22 @@ func openDatabaseInternal(config *Config, allowAutoupgrade bool) (db *buntdb.DB,
}
}
func performAutoUpgrade(currentVersion string, config *Config) (err error) {
func retrieveSchemaVersion(tx *buntdb.Tx) (version int, err error) {
if val, err := tx.Get(keySchemaVersion); err == nil {
return strconv.Atoi(val)
}
// legacy key:
if val, err := tx.Get("db.version"); err == nil {
return strconv.Atoi(val)
}
return 0, buntdb.ErrNotFound
}
func performAutoUpgrade(currentVersion int, config *Config) (err error) {
path := config.Datastore.Path
log.Printf("attempting to auto-upgrade schema from version %s to %s\n", currentVersion, latestDbSchema)
timestamp := time.Now().UTC().Format("2006-01-02-15:04:05.000Z")
backupPath := fmt.Sprintf("%s.v%s.%s.bak", path, currentVersion, timestamp)
log.Printf("attempting to auto-upgrade schema from version %d to %d\n", currentVersion, latestDbSchema)
timestamp := time.Now().UTC().Format("2006-01-02-15.04.05.000Z")
backupPath := fmt.Sprintf("%s.v%d.%s.bak", path, currentVersion, timestamp)
log.Printf("making a backup of current database at %s\n", backupPath)
err = utils.CopyFile(path, backupPath)
if err != nil {
@ -156,29 +193,35 @@ func UpgradeDB(config *Config) (err error) {
}
defer store.Close()
var version string
var version int
err = store.Update(func(tx *buntdb.Tx) error {
for {
version, _ = tx.Get(keySchemaVersion)
change, schemaNeedsChange := schemaChanges[version]
if !schemaNeedsChange {
if version == latestDbSchema {
// success!
break
if version == 0 {
version, err = retrieveSchemaVersion(tx)
if err != nil {
return err
}
}
if version == latestDbSchema {
// success!
break
}
change, ok := getSchemaChange(version)
if !ok {
// unable to upgrade to the desired version, roll back
return &utils.IncompatibleSchemaError{CurrentVersion: version, RequiredVersion: latestDbSchema}
}
log.Println("attempting to update schema from version " + version)
log.Printf("attempting to update schema from version %d\n", version)
err := change.Changer(config, tx)
if err != nil {
return err
}
_, _, err = tx.Set(keySchemaVersion, change.TargetVersion, nil)
version = change.TargetVersion
_, _, err = tx.Set(keySchemaVersion, strconv.Itoa(version), nil)
if err != nil {
return err
}
log.Println("successfully updated schema to version " + change.TargetVersion)
log.Printf("successfully updated schema to version %d\n", version)
}
return nil
})
@ -189,19 +232,27 @@ func UpgradeDB(config *Config) (err error) {
return err
}
func LoadCloakSecret(db *buntdb.DB) (result string) {
db.View(func(tx *buntdb.Tx) error {
result, _ = tx.Get(keyCloakSecret)
return nil
})
return
func LoadCloakSecret(dstore datastore.Datastore) (result string, err error) {
val, err := dstore.Get(datastore.TableMetadata, cloakSecretUUID)
if err != nil {
return
}
return string(val), nil
}
func StoreCloakSecret(db *buntdb.DB, secret string) {
db.Update(func(tx *buntdb.Tx) error {
tx.Set(keyCloakSecret, secret, nil)
return nil
})
func StoreCloakSecret(dstore datastore.Datastore, secret string) {
// TODO error checking
dstore.Set(datastore.TableMetadata, cloakSecretUUID, []byte(secret), time.Time{})
}
func LoadVAPIDKeys(dstore datastore.Datastore) (*webpush.VAPIDKeys, error) {
val, err := dstore.Get(datastore.TableMetadata, vapidKeysUUID)
if err != nil {
return nil, err
}
result := new(webpush.VAPIDKeys)
err = json.Unmarshal([]byte(val), result)
return result, nil
}
func schemaChangeV1toV2(config *Config, tx *buntdb.Tx) error {
@ -686,68 +737,646 @@ func schemaChangeV11ToV12(config *Config, tx *buntdb.Tx) error {
return nil
}
func init() {
allChanges := []SchemaChange{
{
InitialVersion: "1",
TargetVersion: "2",
Changer: schemaChangeV1toV2,
},
{
InitialVersion: "2",
TargetVersion: "3",
Changer: schemaChangeV2ToV3,
},
{
InitialVersion: "3",
TargetVersion: "4",
Changer: schemaChangeV3ToV4,
},
{
InitialVersion: "4",
TargetVersion: "5",
Changer: schemaChangeV4ToV5,
},
{
InitialVersion: "5",
TargetVersion: "6",
Changer: schemaChangeV5ToV6,
},
{
InitialVersion: "6",
TargetVersion: "7",
Changer: schemaChangeV6ToV7,
},
{
InitialVersion: "7",
TargetVersion: "8",
Changer: schemaChangeV7ToV8,
},
{
InitialVersion: "8",
TargetVersion: "9",
Changer: schemaChangeV8ToV9,
},
{
InitialVersion: "9",
TargetVersion: "10",
Changer: schemaChangeV9ToV10,
},
{
InitialVersion: "10",
TargetVersion: "11",
Changer: schemaChangeV10ToV11,
},
{
InitialVersion: "11",
TargetVersion: "12",
Changer: schemaChangeV11ToV12,
},
type accountCredsLegacyV13 struct {
Version CredentialsVersion
PassphraseHash []byte
Certfps []string
}
// see #212 / #284. this packs the legacy salts into a single passphrase hash,
// allowing legacy passphrases to be verified using the new API `checkLegacyPassphrase`.
func schemaChangeV12ToV13(config *Config, tx *buntdb.Tx) error {
salt, err := tx.Get("crypto.salt")
if err != nil {
return nil // no change required
}
tx.Delete("crypto.salt")
rawSalt, err := base64.StdEncoding.DecodeString(salt)
if err != nil {
return nil // just throw away the creds at this point
}
prefix := "account.credentials "
var accounts []string
var credentials []accountCredsLegacyV13
tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
if !strings.HasPrefix(key, prefix) {
return false
}
account := strings.TrimPrefix(key, prefix)
var credsOld accountCredsLegacyV9
err = json.Unmarshal([]byte(value), &credsOld)
if err != nil {
return true
}
// skip if these aren't legacy creds!
if credsOld.Version != 0 {
return true
}
var credsNew accountCredsLegacyV13
credsNew.Version = 0 // mark hash for migration
credsNew.Certfps = credsOld.Certfps
credsNew.PassphraseHash = append(credsNew.PassphraseHash, rawSalt...)
credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseSalt...)
credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseHash...)
accounts = append(accounts, account)
credentials = append(credentials, credsNew)
return true
})
for i, account := range accounts {
bytesOut, err := json.Marshal(credentials[i])
if err != nil {
return err
}
_, _, err = tx.Set(prefix+account, string(bytesOut), nil)
if err != nil {
return err
}
}
// build the index
schemaChanges = make(map[string]SchemaChange)
for _, change := range allChanges {
schemaChanges[change.InitialVersion] = change
}
return nil
}
// channel registration time and topic set time at nanosecond resolution
func schemaChangeV13ToV14(config *Config, tx *buntdb.Tx) error {
prefix := "channel.registered.time "
var channels, times []string
tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
if !strings.HasPrefix(key, prefix) {
return false
}
channel := strings.TrimPrefix(key, prefix)
channels = append(channels, channel)
times = append(times, value)
return true
})
billion := int64(time.Second)
for i, channel := range channels {
regTime, err := strconv.ParseInt(times[i], 10, 64)
if err != nil {
log.Printf("corrupt registration time entry for %s: %v\n", channel, err)
continue
}
regTime = regTime * billion
tx.Set(prefix+channel, strconv.FormatInt(regTime, 10), nil)
topicTimeKey := "channel.topic.settime " + channel
topicSetAt, err := tx.Get(topicTimeKey)
if err == nil {
if setTime, err := strconv.ParseInt(topicSetAt, 10, 64); err == nil {
tx.Set(topicTimeKey, strconv.FormatInt(setTime*billion, 10), nil)
}
}
}
return nil
}
// #1327: delete any invalid klines
func schemaChangeV14ToV15(config *Config, tx *buntdb.Tx) error {
prefix := "bans.klinev2 "
var keys []string
tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
if !strings.HasPrefix(key, prefix) {
return false
}
if key != strings.TrimSpace(key) {
keys = append(keys, key)
}
return true
})
// don't bother trying to fix these up
for _, key := range keys {
tx.Delete(key)
}
return nil
}
// #1330: delete any stale realname records
func schemaChangeV15ToV16(config *Config, tx *buntdb.Tx) error {
prefix := "account.realname "
verifiedPrefix := "account.verified "
var keys []string
tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
if !strings.HasPrefix(key, prefix) {
return false
}
acct := strings.TrimPrefix(key, prefix)
verifiedKey := verifiedPrefix + acct
_, verifiedErr := tx.Get(verifiedKey)
if verifiedErr != nil {
keys = append(keys, key)
}
return true
})
for _, key := range keys {
tx.Delete(key)
}
return nil
}
// #1346: remove vhost request queue
func schemaChangeV16ToV17(config *Config, tx *buntdb.Tx) error {
prefix := "vhostQueue "
var keys []string
tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
if !strings.HasPrefix(key, prefix) {
return false
}
keys = append(keys, key)
return true
})
for _, key := range keys {
tx.Delete(key)
}
return nil
}
// #1274: we used to suspend accounts by deleting their "verified" key,
// now we save some metadata under a new key
func schemaChangeV17ToV18(config *Config, tx *buntdb.Tx) error {
now := time.Now().UTC()
exists := "account.exists "
suspended := "account.suspended "
verif := "account.verified "
verifCode := "account.verificationcode "
var accounts []string
tx.AscendGreaterOrEqual("", exists, func(key, value string) bool {
if !strings.HasPrefix(key, exists) {
return false
}
account := strings.TrimPrefix(key, exists)
_, verifiedErr := tx.Get(verif + account)
_, verifCodeErr := tx.Get(verifCode + account)
if verifiedErr != nil && verifCodeErr != nil {
// verified key not present, but there's no code either,
// this is a suspension
accounts = append(accounts, account)
}
return true
})
type accountSuspensionV18 struct {
TimeCreated time.Time
Duration time.Duration
OperName string
Reason string
}
for _, account := range accounts {
var sus accountSuspensionV18
sus.TimeCreated = now
sus.OperName = "*"
sus.Reason = "[unknown]"
susBytes, err := json.Marshal(sus)
if err != nil {
return err
}
tx.Set(suspended+account, string(susBytes), nil)
}
return nil
}
// #1345: persist the channel-user modes of always-on clients
func schemaChangeV18To19(config *Config, tx *buntdb.Tx) error {
channelToAmodesCache := make(map[string]map[string]modes.Mode)
joinedto := "account.joinedto "
var accounts []string
var channels [][]string
tx.AscendGreaterOrEqual("", joinedto, func(key, value string) bool {
if !strings.HasPrefix(key, joinedto) {
return false
}
accounts = append(accounts, strings.TrimPrefix(key, joinedto))
var ch []string
if value != "" {
ch = strings.Split(value, ",")
}
channels = append(channels, ch)
return true
})
for i := 0; i < len(accounts); i++ {
account := accounts[i]
channels := channels[i]
tx.Delete(joinedto + account)
newValue := make(map[string]string, len(channels))
for _, channel := range channels {
chcfname, err := CasefoldChannel(channel)
if err != nil {
continue
}
// get amodes from the channelToAmodesCache, fill if necessary
amodes, ok := channelToAmodesCache[chcfname]
if !ok {
amodeStr, _ := tx.Get("channel.accounttoumode " + chcfname)
if amodeStr != "" {
jErr := json.Unmarshal([]byte(amodeStr), &amodes)
if jErr != nil {
log.Printf("error retrieving amodes for %s: %v\n", channel, jErr)
amodes = nil
}
}
// setting/using the nil value here is ok
channelToAmodesCache[chcfname] = amodes
}
if mode, ok := amodes[account]; ok {
newValue[channel] = string(mode)
} else {
newValue[channel] = ""
}
}
newValueBytes, jErr := json.Marshal(newValue)
if jErr != nil {
log.Printf("couldn't serialize new mode values for v19: %v\n", jErr)
continue
}
tx.Set("account.channeltomodes "+account, string(newValueBytes), nil)
}
return nil
}
// #1490: start tracking join times for always-on clients
func schemaChangeV19To20(config *Config, tx *buntdb.Tx) error {
type joinData struct {
Modes string
JoinTime int64
}
var accounts []string
var data []string
now := time.Now().UnixNano()
prefix := "account.channeltomodes "
tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
if !strings.HasPrefix(key, prefix) {
return false
}
accounts = append(accounts, strings.TrimPrefix(key, prefix))
data = append(data, value)
return true
})
for i, account := range accounts {
var existingMap map[string]string
err := json.Unmarshal([]byte(data[i]), &existingMap)
if err != nil {
return err
}
newMap := make(map[string]joinData)
for channel, modeStr := range existingMap {
newMap[channel] = joinData{
Modes: modeStr,
JoinTime: now,
}
}
serialized, err := json.Marshal(newMap)
if err != nil {
return err
}
tx.Set(prefix+account, string(serialized), nil)
}
return nil
}
// #734: move the email address into the settings object,
// giving people a way to change it
func schemaChangeV20To21(config *Config, tx *buntdb.Tx) error {
type accountSettingsv21 struct {
AutoreplayLines *int
NickEnforcement NickEnforcementMethod
AllowBouncer MulticlientAllowedSetting
ReplayJoins ReplayJoinsSetting
AlwaysOn PersistentStatus
AutoreplayMissed bool
DMHistory HistoryStatus
AutoAway PersistentStatus
Email string
}
var accounts []string
var emails []string
callbackPrefix := "account.callback "
tx.AscendGreaterOrEqual("", callbackPrefix, func(key, value string) bool {
if !strings.HasPrefix(key, callbackPrefix) {
return false
}
account := strings.TrimPrefix(key, callbackPrefix)
if _, err := tx.Get("account.verified " + account); err != nil {
return true
}
if strings.HasPrefix(value, "mailto:") {
accounts = append(accounts, account)
emails = append(emails, strings.TrimPrefix(value, "mailto:"))
}
return true
})
for i, account := range accounts {
var settings accountSettingsv21
email := emails[i]
settingsKey := "account.settings " + account
settingsStr, err := tx.Get(settingsKey)
if err == nil && settingsStr != "" {
json.Unmarshal([]byte(settingsStr), &settings)
}
settings.Email = email
settingsBytes, err := json.Marshal(settings)
if err != nil {
log.Printf("couldn't marshal settings for %s: %v\n", account, err)
} else {
tx.Set(settingsKey, string(settingsBytes), nil)
}
tx.Delete(callbackPrefix + account)
}
return nil
}
// #1676: we used to have ReplayJoinsNever, now it's desupported
func schemaChangeV21To22(config *Config, tx *buntdb.Tx) error {
type accountSettingsv22 struct {
AutoreplayLines *int
NickEnforcement NickEnforcementMethod
AllowBouncer MulticlientAllowedSetting
ReplayJoins ReplayJoinsSetting
AlwaysOn PersistentStatus
AutoreplayMissed bool
DMHistory HistoryStatus
AutoAway PersistentStatus
Email string
}
var accounts []string
var serializedSettings []string
settingsPrefix := "account.settings "
tx.AscendGreaterOrEqual("", settingsPrefix, func(key, value string) bool {
if !strings.HasPrefix(key, settingsPrefix) {
return false
}
if value == "" {
return true
}
account := strings.TrimPrefix(key, settingsPrefix)
if _, err := tx.Get("account.verified " + account); err != nil {
return true
}
var settings accountSettingsv22
err := json.Unmarshal([]byte(value), &settings)
if err != nil {
log.Printf("error (v21-22) processing settings for %s: %v\n", account, err)
return true
}
// if necessary, change ReplayJoinsNever (2) to ReplayJoinsCommandsOnly (0)
if settings.ReplayJoins == ReplayJoinsSetting(2) {
settings.ReplayJoins = ReplayJoinsSetting(0)
if b, err := json.Marshal(settings); err == nil {
accounts = append(accounts, account)
serializedSettings = append(serializedSettings, string(b))
} else {
log.Printf("error (v21-22) processing settings for %s: %v\n", account, err)
}
}
return true
})
for i, account := range accounts {
tx.Set(settingsPrefix+account, serializedSettings[i], nil)
}
return nil
}
// first phase of document-oriented database refactor: channels
func schemaChangeV22ToV23(config *Config, tx *buntdb.Tx) error {
keyChannelExists := "channel.exists "
var channelNames []string
tx.AscendGreaterOrEqual("", keyChannelExists, func(key, value string) bool {
if !strings.HasPrefix(key, keyChannelExists) {
return false
}
channelNames = append(channelNames, strings.TrimPrefix(key, keyChannelExists))
return true
})
for _, channelName := range channelNames {
channel, err := loadLegacyChannel(tx, channelName)
if err != nil {
log.Printf("error loading legacy channel %s: %v", channelName, err)
continue
}
channel.UUID = utils.GenerateUUIDv4()
newKey := bunt.BuntKey(datastore.TableChannels, channel.UUID)
j, err := json.Marshal(channel)
if err != nil {
log.Printf("error marshaling channel %s: %v", channelName, err)
continue
}
tx.Set(newKey, string(j), nil)
deleteLegacyChannel(tx, channelName)
}
// purges
keyChannelPurged := "channel.purged "
var purgeKeys []string
var channelPurges []ChannelPurgeRecord
tx.AscendGreaterOrEqual("", keyChannelPurged, func(key, value string) bool {
if !strings.HasPrefix(key, keyChannelPurged) {
return false
}
purgeKeys = append(purgeKeys, key)
cfname := strings.TrimPrefix(key, keyChannelPurged)
var record ChannelPurgeRecord
err := json.Unmarshal([]byte(value), &record)
if err != nil {
log.Printf("error unmarshaling channel purge for %s: %v", cfname, err)
return true
}
record.NameCasefolded = cfname
record.UUID = utils.GenerateUUIDv4()
channelPurges = append(channelPurges, record)
return true
})
for _, record := range channelPurges {
newKey := bunt.BuntKey(datastore.TableChannelPurges, record.UUID)
j, err := json.Marshal(record)
if err != nil {
log.Printf("error marshaling channel purge %s: %v", record.NameCasefolded, err)
continue
}
tx.Set(newKey, string(j), nil)
}
for _, purgeKey := range purgeKeys {
tx.Delete(purgeKey)
}
// clean up denormalized account-to-channels mapping
keyAccountChannels := "account.channels "
var accountToChannels []string
tx.AscendGreaterOrEqual("", keyAccountChannels, func(key, value string) bool {
if !strings.HasPrefix(key, keyAccountChannels) {
return false
}
accountToChannels = append(accountToChannels, key)
return true
})
for _, key := range accountToChannels {
tx.Delete(key)
}
// migrate cloak secret
val, _ := tx.Get("crypto.cloak_secret")
tx.Set(keyCloakSecret, val, nil)
// bump the legacy version key to mark the database as downgrade-incompatible
tx.Set("db.version", "23", nil)
return nil
}
// webpush signing key
func schemaChangeV23ToV24(config *Config, tx *buntdb.Tx) error {
keys, err := webpush.GenerateVAPIDKeys()
if err != nil {
return err
}
j, err := json.Marshal(keys)
if err != nil {
return err
}
tx.Set(keyVAPIDKeys, string(j), nil)
return nil
}
func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
for _, change := range allChanges {
if initialVersion == change.InitialVersion {
return change, true
}
}
return
}
var allChanges = []SchemaChange{
{
InitialVersion: 1,
TargetVersion: 2,
Changer: schemaChangeV1toV2,
},
{
InitialVersion: 2,
TargetVersion: 3,
Changer: schemaChangeV2ToV3,
},
{
InitialVersion: 3,
TargetVersion: 4,
Changer: schemaChangeV3ToV4,
},
{
InitialVersion: 4,
TargetVersion: 5,
Changer: schemaChangeV4ToV5,
},
{
InitialVersion: 5,
TargetVersion: 6,
Changer: schemaChangeV5ToV6,
},
{
InitialVersion: 6,
TargetVersion: 7,
Changer: schemaChangeV6ToV7,
},
{
InitialVersion: 7,
TargetVersion: 8,
Changer: schemaChangeV7ToV8,
},
{
InitialVersion: 8,
TargetVersion: 9,
Changer: schemaChangeV8ToV9,
},
{
InitialVersion: 9,
TargetVersion: 10,
Changer: schemaChangeV9ToV10,
},
{
InitialVersion: 10,
TargetVersion: 11,
Changer: schemaChangeV10ToV11,
},
{
InitialVersion: 11,
TargetVersion: 12,
Changer: schemaChangeV11ToV12,
},
{
InitialVersion: 12,
TargetVersion: 13,
Changer: schemaChangeV12ToV13,
},
{
InitialVersion: 13,
TargetVersion: 14,
Changer: schemaChangeV13ToV14,
},
{
InitialVersion: 14,
TargetVersion: 15,
Changer: schemaChangeV14ToV15,
},
{
InitialVersion: 15,
TargetVersion: 16,
Changer: schemaChangeV15ToV16,
},
{
InitialVersion: 16,
TargetVersion: 17,
Changer: schemaChangeV16ToV17,
},
{
InitialVersion: 17,
TargetVersion: 18,
Changer: schemaChangeV17ToV18,
},
{
InitialVersion: 18,
TargetVersion: 19,
Changer: schemaChangeV18To19,
},
{
InitialVersion: 19,
TargetVersion: 20,
Changer: schemaChangeV19To20,
},
{
InitialVersion: 20,
TargetVersion: 21,
Changer: schemaChangeV20To21,
},
{
InitialVersion: 21,
TargetVersion: 22,
Changer: schemaChangeV21To22,
},
{
InitialVersion: 22,
TargetVersion: 23,
Changer: schemaChangeV22ToV23,
},
{
InitialVersion: 23,
TargetVersion: 24,
Changer: schemaChangeV23ToV24,
},
}

View File

@ -0,0 +1,45 @@
// Copyright (c) 2022 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package datastore
import (
"time"
"github.com/ergochat/ergo/irc/utils"
)
type Table uint16
// XXX these are persisted and must remain stable;
// do not reorder, when deleting use _ to ensure that the deleted value is skipped
const (
TableMetadata Table = iota
TableChannels
TableChannelPurges
)
type KV struct {
UUID utils.UUID
Value []byte
}
// A Datastore provides the following abstraction:
// 1. Tables, each keyed on a UUID (the implementation is free to merge
// the table name and the UUID into a single key as long as the rest of
// the contract can be satisfied). Table names are [a-z0-9_]+
// 2. The ability to efficiently enumerate all uuid-value pairs in a table
// 3. Gets, sets, and deletes for individual (table, uuid) keys
type Datastore interface {
Backoff() time.Duration
GetAll(table Table) ([]KV, error)
// This is rarely used because it would typically lead to TOCTOU races
Get(table Table, key utils.UUID) (value []byte, err error)
Set(table Table, key utils.UUID, value []byte, expiration time.Time) error
// Note that deleting a nonexistent key is not considered an error
Delete(table Table, key utils.UUID) error
}

View File

@ -6,12 +6,11 @@ package irc
import (
"encoding/json"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/flatip"
"github.com/tidwall/buntdb"
)
@ -21,6 +20,8 @@ const (
// IPBanInfo holds info about an IP/net ban.
type IPBanInfo struct {
// RequireSASL indicates a "soft" ban; connections are allowed but they must SASL
RequireSASL bool
// Reason is the ban reason.
Reason string `json:"reason"`
// OperReason is an oper ban reason.
@ -47,41 +48,33 @@ func (info IPBanInfo) TimeLeft() string {
// BanMessage returns the ban message.
func (info IPBanInfo) BanMessage(message string) string {
message = fmt.Sprintf(message, info.Reason)
reason := info.Reason
if reason == "" {
reason = "No reason given"
}
message = fmt.Sprintf(message, reason)
if info.Duration != 0 {
message += fmt.Sprintf(" [%s]", info.TimeLeft())
}
return message
}
// dLineNet contains the net itself and expiration time for a given network.
type dLineNet struct {
// Network is the network that is blocked.
// This is always an IPv6 CIDR; IPv4 CIDRs are translated with the 4-in-6 prefix,
// individual IPv4 and IPV6 addresses are translated to the relevant /128.
Network net.IPNet
// Info contains information on the ban.
Info IPBanInfo
}
// DLineManager manages and dlines.
type DLineManager struct {
sync.RWMutex // tier 1
persistenceMutex sync.Mutex // tier 2
// networks that are dlined:
// XXX: the keys of this map (which are also the database persistence keys)
// are the human-readable representations returned by NetToNormalizedString
networks map[string]dLineNet
networks map[flatip.IPNet]IPBanInfo
// this keeps track of expiration timers for temporary bans
expirationTimers map[string]*time.Timer
expirationTimers map[flatip.IPNet]*time.Timer
server *Server
}
// NewDLineManager returns a new DLineManager.
func NewDLineManager(server *Server) *DLineManager {
var dm DLineManager
dm.networks = make(map[string]dLineNet)
dm.expirationTimers = make(map[string]*time.Timer)
dm.networks = make(map[flatip.IPNet]IPBanInfo)
dm.expirationTimers = make(map[flatip.IPNet]*time.Timer)
dm.server = server
dm.loadFromDatastore()
@ -96,21 +89,21 @@ func (dm *DLineManager) AllBans() map[string]IPBanInfo {
dm.RLock()
defer dm.RUnlock()
// map keys are already the human-readable forms, just return a copy of the map
for key, info := range dm.networks {
allb[key] = info.Info
allb[key.HumanReadableString()] = info
}
return allb
}
// AddNetwork adds a network to the blocked list.
func (dm *DLineManager) AddNetwork(network net.IPNet, duration time.Duration, reason, operReason, operName string) error {
func (dm *DLineManager) AddNetwork(network flatip.IPNet, duration time.Duration, requireSASL bool, reason, operReason, operName string) error {
dm.persistenceMutex.Lock()
defer dm.persistenceMutex.Unlock()
// assemble ban info
info := IPBanInfo{
RequireSASL: requireSASL,
Reason: reason,
OperReason: operReason,
OperName: operName,
@ -122,9 +115,8 @@ func (dm *DLineManager) AddNetwork(network net.IPNet, duration time.Duration, re
return dm.persistDline(id, info)
}
func (dm *DLineManager) addNetworkInternal(network net.IPNet, info IPBanInfo) (id string) {
network = utils.NormalizeNet(network)
id = utils.NetToNormalizedString(network)
func (dm *DLineManager) addNetworkInternal(flatnet flatip.IPNet, info IPBanInfo) (id flatip.IPNet) {
id = flatnet
var timeLeft time.Duration
if info.Duration != 0 {
@ -137,12 +129,9 @@ func (dm *DLineManager) addNetworkInternal(network net.IPNet, info IPBanInfo) (i
dm.Lock()
defer dm.Unlock()
dm.networks[id] = dLineNet{
Network: network,
Info: info,
}
dm.networks[flatnet] = info
dm.cancelTimer(id)
dm.cancelTimer(flatnet)
if info.Duration == 0 {
return
@ -154,29 +143,29 @@ func (dm *DLineManager) addNetworkInternal(network net.IPNet, info IPBanInfo) (i
dm.Lock()
defer dm.Unlock()
netBan, ok := dm.networks[id]
if ok && netBan.Info.TimeCreated.Equal(timeCreated) {
delete(dm.networks, id)
banInfo, ok := dm.networks[flatnet]
if ok && banInfo.TimeCreated.Equal(timeCreated) {
delete(dm.networks, flatnet)
// TODO(slingamn) here's where we'd remove it from the radix tree
delete(dm.expirationTimers, id)
delete(dm.expirationTimers, flatnet)
}
}
dm.expirationTimers[id] = time.AfterFunc(timeLeft, processExpiration)
dm.expirationTimers[flatnet] = time.AfterFunc(timeLeft, processExpiration)
return
}
func (dm *DLineManager) cancelTimer(id string) {
oldTimer := dm.expirationTimers[id]
func (dm *DLineManager) cancelTimer(flatnet flatip.IPNet) {
oldTimer := dm.expirationTimers[flatnet]
if oldTimer != nil {
oldTimer.Stop()
delete(dm.expirationTimers, id)
delete(dm.expirationTimers, flatnet)
}
}
func (dm *DLineManager) persistDline(id string, info IPBanInfo) error {
func (dm *DLineManager) persistDline(id flatip.IPNet, info IPBanInfo) error {
// save in datastore
dlineKey := fmt.Sprintf(keyDlineEntry, id)
dlineKey := fmt.Sprintf(keyDlineEntry, id.String())
// assemble json from ban info
b, err := json.Marshal(info)
if err != nil {
@ -199,8 +188,8 @@ func (dm *DLineManager) persistDline(id string, info IPBanInfo) error {
return err
}
func (dm *DLineManager) unpersistDline(id string) error {
dlineKey := fmt.Sprintf(keyDlineEntry, id)
func (dm *DLineManager) unpersistDline(id flatip.IPNet) error {
dlineKey := fmt.Sprintf(keyDlineEntry, id.String())
return dm.server.store.Update(func(tx *buntdb.Tx) error {
_, err := tx.Delete(dlineKey)
return err
@ -208,11 +197,11 @@ func (dm *DLineManager) unpersistDline(id string) error {
}
// RemoveNetwork removes a network from the blocked list.
func (dm *DLineManager) RemoveNetwork(network net.IPNet) error {
func (dm *DLineManager) RemoveNetwork(network flatip.IPNet) error {
dm.persistenceMutex.Lock()
defer dm.persistenceMutex.Unlock()
id := utils.NetToNormalizedString(utils.NormalizeNet(network))
id := network
present := func() bool {
dm.Lock()
@ -230,35 +219,19 @@ func (dm *DLineManager) RemoveNetwork(network net.IPNet) error {
return dm.unpersistDline(id)
}
// AddIP adds an IP address to the blocked list.
func (dm *DLineManager) AddIP(addr net.IP, duration time.Duration, reason, operReason, operName string) error {
return dm.AddNetwork(utils.NormalizeIPToNet(addr), duration, reason, operReason, operName)
}
// RemoveIP removes an IP address from the blocked list.
func (dm *DLineManager) RemoveIP(addr net.IP) error {
return dm.RemoveNetwork(utils.NormalizeIPToNet(addr))
}
// CheckIP returns whether or not an IP address was banned, and how long it is banned for.
func (dm *DLineManager) CheckIP(addr net.IP) (isBanned bool, info IPBanInfo) {
addr = addr.To16() // almost certainly unnecessary
if addr.IsLoopback() {
return // #671
}
func (dm *DLineManager) CheckIP(addr flatip.IP) (isBanned bool, info IPBanInfo) {
dm.RLock()
defer dm.RUnlock()
// check networks
// TODO(slingamn) use a radix tree as the data plane for this
for _, netBan := range dm.networks {
if netBan.Network.Contains(addr) {
return true, netBan.Info
for flatnet, info := range dm.networks {
if flatnet.Contains(addr) {
return true, info
}
}
// no matches!
isBanned = false
return
}
@ -274,7 +247,7 @@ func (dm *DLineManager) loadFromDatastore() {
key = strings.TrimPrefix(key, dlinePrefix)
// load addr/net
hostNet, err := utils.NormalizedNetFromString(key)
hostNet, err := flatip.ParseToNormalizedNet(key)
if err != nil {
dm.server.logger.Error("internal", "bad dline cidr", err.Error())
return true

View File

@ -4,9 +4,18 @@
package email
import (
"bytes"
"crypto"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
dkim "github.com/toorop/go-dkim"
"io/ioutil"
"fmt"
"os"
dkim "github.com/emersion/go-msgauth/dkim"
)
var (
@ -17,38 +26,77 @@ type DKIMConfig struct {
Domain string
Selector string
KeyFile string `yaml:"key-file"`
keyBytes []byte
privKey crypto.Signer
}
func (dkim *DKIMConfig) Enabled() bool {
return dkim.Domain != ""
}
func (dkim *DKIMConfig) Postprocess() (err error) {
if dkim.Domain != "" {
if dkim.Selector == "" || dkim.KeyFile == "" {
return ErrMissingFields
}
dkim.keyBytes, err = ioutil.ReadFile(dkim.KeyFile)
if err != nil {
return err
}
if !dkim.Enabled() {
return nil
}
if dkim.Selector == "" || dkim.KeyFile == "" {
return ErrMissingFields
}
keyBytes, err := os.ReadFile(dkim.KeyFile)
if err != nil {
return fmt.Errorf("Could not read DKIM key file: %w", err)
}
dkim.privKey, err = parseDKIMPrivKey(keyBytes)
if err != nil {
return fmt.Errorf("Could not parse DKIM key file: %w", err)
}
return nil
}
var defaultOptions = dkim.SigOptions{
Version: 1,
Canonicalization: "relaxed/relaxed",
Algo: "rsa-sha256",
Headers: []string{"from", "to", "subject", "message-id", "date"},
BodyLength: 0,
QueryMethods: []string{"dns/txt"},
AddSignatureTimestamp: true,
SignatureExpireIn: 0,
func parseDKIMPrivKey(input []byte) (crypto.Signer, error) {
if len(input) == 0 {
return nil, errors.New("DKIM private key is empty")
}
// raw ed25519 private key format
if len(input) == ed25519.PrivateKeySize {
return ed25519.PrivateKey(input), nil
}
d, _ := pem.Decode(input)
if d == nil {
return nil, errors.New("Invalid PEM data for DKIM private key")
}
if rsaKey, err := x509.ParsePKCS1PrivateKey(d.Bytes); err == nil {
return rsaKey, nil
}
if k, err := x509.ParsePKCS8PrivateKey(d.Bytes); err == nil {
switch key := k.(type) {
case *rsa.PrivateKey:
return key, nil
case ed25519.PrivateKey:
return key, nil
default:
return nil, fmt.Errorf("Unacceptable type for DKIM private key: %T", k)
}
}
return nil, errors.New("No acceptable format for DKIM private key")
}
func DKIMSign(message []byte, dkimConfig DKIMConfig) (result []byte, err error) {
options := defaultOptions
options.PrivateKey = dkimConfig.keyBytes
options.Domain = dkimConfig.Domain
options.Selector = dkimConfig.Selector
err = dkim.Sign(&message, options)
return message, err
options := dkim.SignOptions{
Domain: dkimConfig.Domain,
Selector: dkimConfig.Selector,
Signer: dkimConfig.privKey,
HeaderCanonicalization: dkim.CanonicalizationRelaxed,
BodyCanonicalization: dkim.CanonicalizationRelaxed,
}
input := bytes.NewBuffer(message)
output := bytes.NewBuffer(make([]byte, 0, len(message)+1024))
err = dkim.Sign(output, input, &options)
return output.Bytes(), err
}

View File

@ -4,41 +4,131 @@
package email
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"net"
"os"
"regexp"
"strings"
"time"
"github.com/oragono/oragono/irc/smtp"
"github.com/ergochat/ergo/irc/custime"
"github.com/ergochat/ergo/irc/smtp"
"github.com/ergochat/ergo/irc/utils"
)
var (
ErrBlacklistedAddress = errors.New("Email address is blacklisted")
ErrInvalidAddress = errors.New("Email address is blacklisted")
ErrInvalidAddress = errors.New("Email address is invalid")
ErrNoMXRecord = errors.New("Couldn't resolve MX record")
)
type BlacklistSyntax uint
const (
BlacklistSyntaxGlob BlacklistSyntax = iota
BlacklistSyntaxRegexp
)
func blacklistSyntaxFromString(status string) (BlacklistSyntax, error) {
switch strings.ToLower(status) {
case "glob", "":
return BlacklistSyntaxGlob, nil
case "re", "regex", "regexp":
return BlacklistSyntaxRegexp, nil
default:
return BlacklistSyntaxRegexp, fmt.Errorf("Unknown blacklist syntax type `%s`", status)
}
}
func (bs *BlacklistSyntax) UnmarshalYAML(unmarshal func(interface{}) error) error {
var orig string
var err error
if err = unmarshal(&orig); err != nil {
return err
}
if result, err := blacklistSyntaxFromString(orig); err == nil {
*bs = result
return nil
} else {
return err
}
}
type MTAConfig struct {
Server string
Port int
Username string
Password string
Server string
Port int
Username string
Password string
ImplicitTLS bool `yaml:"implicit-tls"`
}
type MailtoConfig struct {
// legacy config format assumed the use of an MTA/smarthost,
// so server, port, etc. appear directly at top level
// XXX: see https://github.com/go-yaml/yaml/issues/63
MTAConfig `yaml:",inline"`
Sender string
HeloDomain string `yaml:"helo-domain"`
RequireTLS bool `yaml:"require-tls"`
VerifyMessageSubject string `yaml:"verify-message-subject"`
DKIM DKIMConfig
MTAReal MTAConfig `yaml:"mta"`
BlacklistRegexes []string `yaml:"blacklist-regexes"`
blacklistRegexes []*regexp.Regexp
MTAConfig `yaml:",inline"`
Enabled bool
Sender string
HeloDomain string `yaml:"helo-domain"`
RequireTLS bool `yaml:"require-tls"`
Protocol string `yaml:"protocol"`
LocalAddress string `yaml:"local-address"`
localAddress net.Addr
VerifyMessageSubject string `yaml:"verify-message-subject"`
DKIM DKIMConfig
MTAReal MTAConfig `yaml:"mta"`
AddressBlacklist []string `yaml:"address-blacklist"`
AddressBlacklistSyntax BlacklistSyntax `yaml:"address-blacklist-syntax"`
AddressBlacklistFile string `yaml:"address-blacklist-file"`
blacklistRegexes []*regexp.Regexp
Timeout time.Duration
PasswordReset struct {
Enabled bool
Cooldown custime.Duration
Timeout custime.Duration
} `yaml:"password-reset"`
}
func (config *MailtoConfig) compileBlacklistEntry(source string) (re *regexp.Regexp, err error) {
if config.AddressBlacklistSyntax == BlacklistSyntaxGlob {
return utils.CompileGlob(source, false)
} else {
return regexp.Compile(fmt.Sprintf("^%s$", source))
}
}
func (config *MailtoConfig) processBlacklistFile(filename string) (result []*regexp.Regexp, err error) {
f, err := os.Open(filename)
if err != nil {
return
}
defer f.Close()
reader := bufio.NewReader(f)
lineNo := 0
for {
line, err := reader.ReadString('\n')
lineNo++
line = strings.TrimSpace(line)
if line != "" && line[0] != '#' {
if compiled, compileErr := config.compileBlacklistEntry(line); compileErr == nil {
result = append(result, compiled)
} else {
return result, fmt.Errorf("Failed to compile line %d of blacklist-regex-file `%s`: %w", lineNo, line, compileErr)
}
}
switch err {
case io.EOF:
return result, nil
case nil:
continue
default:
return result, err
}
}
}
func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
@ -56,12 +146,39 @@ func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
config.HeloDomain = heloDomain
}
for _, reg := range config.BlacklistRegexes {
compiled, err := regexp.Compile(fmt.Sprintf("^%s$", reg))
if config.AddressBlacklistFile != "" {
config.blacklistRegexes, err = config.processBlacklistFile(config.AddressBlacklistFile)
if err != nil {
return err
}
config.blacklistRegexes = append(config.blacklistRegexes, compiled)
} else if len(config.AddressBlacklist) != 0 {
config.blacklistRegexes = make([]*regexp.Regexp, 0, len(config.AddressBlacklist))
for _, reg := range config.AddressBlacklist {
compiled, err := config.compileBlacklistEntry(reg)
if err != nil {
return err
}
config.blacklistRegexes = append(config.blacklistRegexes, compiled)
}
}
config.Protocol = strings.ToLower(config.Protocol)
if config.Protocol == "" {
config.Protocol = "tcp"
}
if !(config.Protocol == "tcp" || config.Protocol == "tcp4" || config.Protocol == "tcp6") {
return fmt.Errorf("Invalid protocol for email sending: `%s`", config.Protocol)
}
if config.LocalAddress != "" {
ipAddr := net.ParseIP(config.LocalAddress)
if ipAddr == nil {
return fmt.Errorf("Could not parse local-address for email sending: `%s`", config.LocalAddress)
}
config.localAddress = &net.TCPAddr{
IP: ipAddr,
Port: 0,
}
}
if config.MTAConfig.Server != "" {
@ -72,6 +189,11 @@ func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
return config.DKIM.Postprocess()
}
// are we sending email directly, as opposed to deferring to an MTA?
func (config *MailtoConfig) DirectSendingEnabled() bool {
return config.MTAReal.Server == ""
}
// get the preferred MX record hostname, "" on error
func lookupMX(domain string) (server string) {
var minPref uint16
@ -87,14 +209,31 @@ func lookupMX(domain string) (server string) {
return
}
func ComposeMail(config MailtoConfig, recipient, subject string) (message bytes.Buffer) {
fmt.Fprintf(&message, "From: %s\r\n", config.Sender)
fmt.Fprintf(&message, "To: %s\r\n", recipient)
dkimDomain := config.DKIM.Domain
if dkimDomain != "" {
fmt.Fprintf(&message, "Message-ID: <%s@%s>\r\n", utils.GenerateSecretKey(), dkimDomain)
} else {
// #2108: send Message-ID even if dkim is not enabled
fmt.Fprintf(&message, "Message-ID: <%s-%s>\r\n", utils.GenerateSecretKey(), config.Sender)
}
fmt.Fprintf(&message, "Date: %s\r\n", time.Now().UTC().Format(time.RFC1123Z))
fmt.Fprintf(&message, "Subject: %s\r\n", subject)
message.WriteString("\r\n") // blank line: end headers, begin message body
return message
}
func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
recipientLower := strings.ToLower(recipient)
for _, reg := range config.blacklistRegexes {
if reg.MatchString(recipient) {
if reg.MatchString(recipientLower) {
return ErrBlacklistedAddress
}
}
if config.DKIM.Domain != "" {
if config.DKIM.Enabled() {
msg, err = DKIMSign(msg, config.DKIM)
if err != nil {
return
@ -103,11 +242,13 @@ func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
var addr string
var auth smtp.Auth
if config.MTAReal.Server != "" {
var implicitTLS bool
if !config.DirectSendingEnabled() {
addr = fmt.Sprintf("%s:%d", config.MTAReal.Server, config.MTAReal.Port)
if config.MTAReal.Username != "" && config.MTAReal.Password != "" {
auth = smtp.PlainAuth("", config.MTAReal.Username, config.MTAReal.Password, config.MTAReal.Server)
}
implicitTLS = config.MTAReal.ImplicitTLS
} else {
idx := strings.IndexByte(recipient, '@')
if idx == -1 {
@ -120,5 +261,8 @@ func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
addr = fmt.Sprintf("%s:smtp", mx)
}
return smtp.SendMail(addr, auth, config.HeloDomain, config.Sender, []string{recipient}, msg, config.RequireTLS)
return smtp.SendMail(
addr, auth, config.HeloDomain, config.Sender, []string{recipient}, msg,
config.RequireTLS, implicitTLS, config.Protocol, config.localAddress, config.Timeout,
)
}

View File

@ -10,7 +10,7 @@ import (
"fmt"
"time"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/utils"
)
// Runtime Errors
@ -28,12 +28,13 @@ var (
errAccountAlreadyLoggedIn = errors.New("You're already logged into an account")
errAccountTooManyNicks = errors.New("Account has too many reserved nicks")
errAccountUnverified = errors.New(`Account is not yet verified`)
errAccountSuspended = errors.New(`Account has been suspended`)
errAccountVerificationFailed = errors.New("Account verification failed")
errAccountVerificationInvalidCode = errors.New("Invalid account verification code")
errAccountUpdateFailed = errors.New(`Error while updating your account information`)
errAccountMustHoldNick = errors.New(`You must hold that nickname in order to register it`)
errAuthRequired = errors.New("You must be logged into an account to do this")
errAuthzidAuthcidMismatch = errors.New(`authcid and authzid must be the same`)
errCallbackFailed = errors.New("Account verification could not be sent")
errCertfpAlreadyExists = errors.New(`An account already exists for your certificate fingerprint`)
errChannelNotOwnedByAccount = errors.New("Channel not owned by the specified account")
errChannelTransferNotOffered = errors.New(`You weren't offered ownership of that channel`)
@ -51,6 +52,7 @@ var (
errNoExistingBan = errors.New("Ban does not exist")
errNoSuchChannel = errors.New(`No such channel`)
errChannelPurged = errors.New(`This channel was purged by the server operators and cannot be used`)
errChannelPurgedAlready = errors.New(`This channel was already purged and cannot be purged again`)
errConfusableIdentifier = errors.New("This identifier is confusable with one already in use")
errInsufficientPrivs = errors.New("Insufficient privileges")
errInvalidUsername = errors.New("Invalid username")
@ -63,6 +65,7 @@ var (
errCASFailed = errors.New("Compare-and-swap update of database value failed")
errEmptyCredentials = errors.New("No more credentials are approved")
errCredsExternallyManaged = errors.New("Credentials are externally managed and cannot be changed here")
errNoSCRAMCredentials = errors.New("SCRAM credentials are not initialized for this account; consult the user guide")
errInvalidMultilineBatch = errors.New("Invalid multiline batch")
errTimedOut = errors.New("Operation timed out")
errInvalidUtf8 = errors.New("Message rejected for invalid utf8")
@ -71,6 +74,10 @@ var (
errWrongChannelKey = errors.New("Cannot join password-protected channel without the password")
errInviteOnly = errors.New("Cannot join invite-only channel without an invite")
errRegisteredOnly = errors.New("Cannot join registered-only channel without an account")
errValidEmailRequired = errors.New("A valid email address is required for account registration")
errInvalidAccountRename = errors.New("Account renames can only change the casefolding of the account name")
errNameReserved = errors.New(`Name reserved due to a prior registration`)
errInvalidBearerTokenType = errors.New("invalid bearer token type")
)
// String Errors
@ -93,5 +100,5 @@ type ThrottleError struct {
}
func (te *ThrottleError) Error() string {
return fmt.Sprintf(`Please wait at least %v and try again`, te.Duration)
return fmt.Sprintf(`Please wait at least %v and try again`, te.Duration.Round(time.Millisecond))
}

View File

@ -4,6 +4,7 @@
package irc
import (
"maps"
"time"
)
@ -36,6 +37,10 @@ type Fakelag struct {
func (fl *Fakelag) Initialize(config FakelagConfig) {
fl.config = config
// XXX don't share mutable member CommandBudgets:
if config.CommandBudgets != nil {
fl.config.CommandBudgets = maps.Clone(config.CommandBudgets)
}
fl.nowFunc = time.Now
fl.sleepFunc = time.Sleep
fl.state = FakelagBursting
@ -58,11 +63,16 @@ func (fl *Fakelag) Unsuspend() {
}
// register a new command, sleep if necessary to delay it
func (fl *Fakelag) Touch() {
func (fl *Fakelag) Touch(command string) {
if !fl.config.Enabled {
return
}
if budget, ok := fl.config.CommandBudgets[command]; ok && budget > 0 {
fl.config.CommandBudgets[command] = budget - 1
return
}
now := fl.nowFunc()
// XXX if lastTouch.IsZero(), treat it as "very far in the past", which is fine
elapsed := now.Sub(fl.lastTouch)
@ -90,10 +100,20 @@ func (fl *Fakelag) Touch() {
if elapsed > fl.config.Cooldown {
// let them burst again
fl.state = FakelagBursting
fl.burstCount = 1
return
}
// space them out by at least window/messagesperwindow
sleepDuration := time.Duration((int64(fl.config.Window) / int64(fl.config.MessagesPerWindow)) - int64(elapsed))
var sleepDuration time.Duration
if fl.config.MessagesPerWindow > 0 {
// space them out by at least window/messagesperwindow
sleepDuration = time.Duration((int64(fl.config.Window) / int64(fl.config.MessagesPerWindow)) - int64(elapsed))
} else {
// only burst messages are allowed: sleep until cooldown expires,
// then count this as a burst message
sleepDuration = time.Duration(int64(fl.config.Cooldown) - int64(elapsed))
fl.state = FakelagBursting
fl.burstCount = 1
}
if sleepDuration > 0 {
fl.sleepFunc(sleepDuration)
// the touch time should take into account the time we slept

View File

@ -60,7 +60,7 @@ func TestFakelag(t *testing.T) {
window, _ := time.ParseDuration("1s")
fl, mt := newFakelagForTesting(window, 3, 2, window)
fl.Touch()
fl.Touch("")
slept, _ := mt.lastSleep()
if slept {
t.Fatalf("should not have slept")
@ -69,7 +69,7 @@ func TestFakelag(t *testing.T) {
interval, _ := time.ParseDuration("100ms")
for i := 0; i < 2; i++ {
mt.pause(interval)
fl.Touch()
fl.Touch("")
slept, _ := mt.lastSleep()
if slept {
t.Fatalf("should not have slept")
@ -77,7 +77,7 @@ func TestFakelag(t *testing.T) {
}
mt.pause(interval)
fl.Touch()
fl.Touch("")
if fl.state != FakelagThrottled {
t.Fatalf("should be throttled")
}
@ -91,7 +91,7 @@ func TestFakelag(t *testing.T) {
}
// send another message without a pause; we should have to sleep for 500 msec
fl.Touch()
fl.Touch("")
if fl.state != FakelagThrottled {
t.Fatalf("should be throttled")
}
@ -102,7 +102,7 @@ func TestFakelag(t *testing.T) {
}
mt.pause(interval * 6)
fl.Touch()
fl.Touch("")
if fl.state != FakelagThrottled {
t.Fatalf("should still be throttled")
}
@ -112,7 +112,7 @@ func TestFakelag(t *testing.T) {
}
mt.pause(window * 2)
fl.Touch()
fl.Touch("")
if fl.state != FakelagBursting {
t.Fatalf("should be bursting again")
}
@ -125,31 +125,31 @@ func TestFakelag(t *testing.T) {
func TestSuspend(t *testing.T) {
window, _ := time.ParseDuration("1s")
fl, _ := newFakelagForTesting(window, 3, 2, window)
assertEqual(fl.config.Enabled, true, t)
assertEqual(fl.config.Enabled, true)
// suspend idempotently disables
fl.Suspend()
assertEqual(fl.config.Enabled, false, t)
assertEqual(fl.config.Enabled, false)
fl.Suspend()
assertEqual(fl.config.Enabled, false, t)
assertEqual(fl.config.Enabled, false)
// unsuspend idempotently enables
fl.Unsuspend()
assertEqual(fl.config.Enabled, true, t)
assertEqual(fl.config.Enabled, true)
fl.Unsuspend()
assertEqual(fl.config.Enabled, true, t)
assertEqual(fl.config.Enabled, true)
fl.Suspend()
assertEqual(fl.config.Enabled, false, t)
assertEqual(fl.config.Enabled, false)
fl2, _ := newFakelagForTesting(window, 3, 2, window)
fl2.config.Enabled = false
// if we were never enabled, suspend and unsuspend are both no-ops
fl2.Suspend()
assertEqual(fl2.config.Enabled, false, t)
assertEqual(fl2.config.Enabled, false)
fl2.Suspend()
assertEqual(fl2.config.Enabled, false, t)
assertEqual(fl2.config.Enabled, false)
fl2.Unsuspend()
assertEqual(fl2.config.Enabled, false, t)
assertEqual(fl2.config.Enabled, false)
fl2.Unsuspend()
assertEqual(fl2.config.Enabled, false, t)
assertEqual(fl2.config.Enabled, false)
}

33
irc/flatip/adhoc.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// Released under the MIT license
package flatip
// begin ad-hoc utilities
// ParseToNormalizedNet attempts to interpret a string either as an IP
// network in CIDR notation, returning an IPNet, or as an IP address,
// returning an IPNet that contains only that address.
func ParseToNormalizedNet(netstr string) (ipnet IPNet, err error) {
_, ipnet, err = ParseCIDR(netstr)
if err == nil {
return
}
ip, err := ParseIP(netstr)
if err == nil {
ipnet.IP = ip
ipnet.PrefixLen = 128
}
return
}
// IPInNets is a convenience function for testing whether an IP is contained
// in any member of a slice of IPNet's.
func IPInNets(addr IP, nets []IPNet) bool {
for _, net := range nets {
if net.Contains(addr) {
return true
}
}
return false
}

220
irc/flatip/flatip.go Normal file
View File

@ -0,0 +1,220 @@
// Copyright 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// Copyright 2009 The Go Authors
// Released under the MIT license
package flatip
import (
"bytes"
"errors"
"net"
)
var (
v4InV6Prefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}
IPv6loopback = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
IPv6zero = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
IPv4zero = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0}
ErrInvalidIPString = errors.New("String could not be interpreted as an IP address")
)
// packed versions of net.IP and net.IPNet; these are pure value types,
// so they can be compared with == and used as map keys.
// IP is a 128-bit representation of an IP address, using the 4-in-6 mapping
// to represent IPv4 addresses.
type IP [16]byte
// IPNet is a IP network. In a valid value, all bits after PrefixLen are zeroes.
type IPNet struct {
IP
PrefixLen uint8
}
// NetIP converts an IP into a net.IP.
func (ip IP) NetIP() (result net.IP) {
result = make(net.IP, 16)
copy(result[:], ip[:])
return
}
// FromNetIP converts a net.IP into an IP.
func FromNetIP(ip net.IP) (result IP) {
if len(ip) == 16 {
copy(result[:], ip[:])
} else {
result[10] = 0xff
result[11] = 0xff
copy(result[12:], ip[:])
}
return
}
// IPv4 returns the IP address representation of a.b.c.d
func IPv4(a, b, c, d byte) (result IP) {
copy(result[:12], v4InV6Prefix)
result[12] = a
result[13] = b
result[14] = c
result[15] = d
return
}
// ParseIP parses a string representation of an IP address into an IP.
// Unlike net.ParseIP, it returns an error instead of a zero value on failure,
// since the zero value of `IP` is a representation of a valid IP (::0, the
// IPv6 "unspecified address").
func ParseIP(ipstr string) (ip IP, err error) {
// TODO reimplement this without net.ParseIP
netip := net.ParseIP(ipstr)
if netip == nil {
err = ErrInvalidIPString
return
}
netip = netip.To16()
copy(ip[:], netip)
return
}
// String returns the string representation of an IP
func (ip IP) String() string {
// TODO reimplement this without using (net.IP).String()
return (net.IP)(ip[:]).String()
}
// IsIPv4 returns whether the IP is an IPv4 address.
func (ip IP) IsIPv4() bool {
return bytes.Equal(ip[:12], v4InV6Prefix)
}
// IsLoopback returns whether the IP is a loopback address.
func (ip IP) IsLoopback() bool {
if ip.IsIPv4() {
return ip[12] == 127
} else {
return ip == IPv6loopback
}
}
func (ip IP) IsUnspecified() bool {
return ip == IPv4zero || ip == IPv6zero
}
func rawCidrMask(length int) (m IP) {
n := uint(length)
for i := 0; i < 16; i++ {
if n >= 8 {
m[i] = 0xff
n -= 8
continue
}
m[i] = ^byte(0xff >> n)
return
}
return
}
func (ip IP) applyMask(mask IP) (result IP) {
for i := 0; i < 16; i += 1 {
result[i] = ip[i] & mask[i]
}
return
}
func cidrMask(ones, bits int) (result IP) {
switch bits {
case 32:
return rawCidrMask(96 + ones)
case 128:
return rawCidrMask(ones)
default:
return
}
}
// Mask returns the result of masking ip with the CIDR mask of
// length 'ones', out of a total of 'bits' (which must be either
// 32 for an IPv4 subnet or 128 for an IPv6 subnet).
func (ip IP) Mask(ones, bits int) (result IP) {
return ip.applyMask(cidrMask(ones, bits))
}
// ToNetIPNet converts an IPNet into a net.IPNet.
func (cidr IPNet) ToNetIPNet() (result net.IPNet) {
return net.IPNet{
IP: cidr.IP.NetIP(),
Mask: net.CIDRMask(int(cidr.PrefixLen), 128),
}
}
// Contains retuns whether the network contains `ip`.
func (cidr IPNet) Contains(ip IP) bool {
maskedIP := ip.Mask(int(cidr.PrefixLen), 128)
return cidr.IP == maskedIP
}
func (cidr IPNet) Size() (ones, bits int) {
if cidr.IP.IsIPv4() {
return int(cidr.PrefixLen) - 96, 32
} else {
return int(cidr.PrefixLen), 128
}
}
// FromNetIPnet converts a net.IPNet into an IPNet.
func FromNetIPNet(network net.IPNet) (result IPNet) {
ones, _ := network.Mask.Size()
if len(network.IP) == 16 {
copy(result.IP[:], network.IP[:])
} else {
result.IP[10] = 0xff
result.IP[11] = 0xff
copy(result.IP[12:], network.IP[:])
ones += 96
}
// perform masking so that equal CIDRs are ==
result.IP = result.IP.Mask(ones, 128)
result.PrefixLen = uint8(ones)
return
}
// String returns a string representation of an IPNet.
func (cidr IPNet) String() string {
ip := make(net.IP, 16)
copy(ip[:], cidr.IP[:])
ipnet := net.IPNet{
IP: ip,
Mask: net.CIDRMask(int(cidr.PrefixLen), 128),
}
return ipnet.String()
}
// HumanReadableString returns a string representation of an IPNet;
// if the network contains only a single IP address, it returns
// a representation of that address.
func (cidr IPNet) HumanReadableString() string {
if cidr.PrefixLen == 128 {
return cidr.IP.String()
}
return cidr.String()
}
// IsZero tests whether ipnet is the zero value of an IPNet, 0::0/0.
// Although this is a valid subnet, it can still be used as a sentinel
// value in some contexts.
func (ipnet IPNet) IsZero() bool {
return ipnet == IPNet{}
}
// ParseCIDR parses a string representation of an IP network in CIDR notation,
// then returns it as an IPNet (along with the original, unmasked address).
func ParseCIDR(netstr string) (ip IP, ipnet IPNet, err error) {
// TODO reimplement this without net.ParseCIDR
nip, nipnet, err := net.ParseCIDR(netstr)
if err != nil {
return
}
return FromNetIP(nip), FromNetIPNet(*nipnet), nil
}

208
irc/flatip/flatip_test.go Normal file
View File

@ -0,0 +1,208 @@
package flatip
import (
"bytes"
"fmt"
"math/rand"
"net"
"reflect"
"testing"
"time"
)
func easyParseIP(ipstr string) (result net.IP) {
result = net.ParseIP(ipstr)
if result == nil {
panic(ipstr)
}
return
}
func easyParseFlat(ipstr string) (result IP) {
x := easyParseIP(ipstr)
return FromNetIP(x)
}
func easyParseIPNet(nipstr string) (result net.IPNet) {
_, nip, err := net.ParseCIDR(nipstr)
if err != nil {
panic(err)
}
return *nip
}
func TestBasic(t *testing.T) {
nip := easyParseIP("8.8.8.8")
flatip := FromNetIP(nip)
if flatip.String() != "8.8.8.8" {
t.Errorf("conversions don't work")
}
}
func TestLoopback(t *testing.T) {
localhost_v4 := easyParseFlat("127.0.0.1")
localhost_v4_again := easyParseFlat("127.2.3.4")
google := easyParseFlat("8.8.8.8")
loopback_v6 := easyParseFlat("::1")
google_v6 := easyParseFlat("2607:f8b0:4006:801::2004")
if !(localhost_v4.IsLoopback() && localhost_v4_again.IsLoopback() && loopback_v6.IsLoopback()) {
t.Errorf("can't detect loopbacks")
}
if google_v6.IsLoopback() || google.IsLoopback() {
t.Errorf("incorrectly detected loopbacks")
}
}
func TestContains(t *testing.T) {
nipnet := easyParseIPNet("8.8.0.0/16")
flatipnet := FromNetIPNet(nipnet)
nip := easyParseIP("8.8.8.8")
flatip_ := FromNetIP(nip)
if !flatipnet.Contains(flatip_) {
t.Errorf("contains doesn't work")
}
}
var testIPStrs = []string{
"8.8.8.8",
"127.0.0.1",
"1.1.1.1",
"128.127.65.64",
"2001:0db8::1",
"::1",
"255.255.255.255",
}
func doMaskingTest(ip net.IP, t *testing.T) {
flat := FromNetIP(ip)
netLen := len(ip) * 8
for i := 0; i < netLen; i++ {
masked := flat.Mask(i, netLen)
netMask := net.CIDRMask(i, netLen)
netMasked := ip.Mask(netMask)
if !bytes.Equal(masked[:], netMasked.To16()) {
t.Errorf("Masking %s with %d/%d; expected %s, got %s", ip.String(), i, netLen, netMasked.String(), masked.String())
}
}
}
func assertEqual(found, expected interface{}) {
if !reflect.DeepEqual(found, expected) {
panic(fmt.Sprintf("expected %#v, found %#v", expected, found))
}
}
func TestSize(t *testing.T) {
_, net, err := ParseCIDR("8.8.8.8/24")
if err != nil {
panic(err)
}
ones, bits := net.Size()
assertEqual(ones, 24)
assertEqual(bits, 32)
_, net, err = ParseCIDR("2001::0db8/64")
if err != nil {
panic(err)
}
ones, bits = net.Size()
assertEqual(ones, 64)
assertEqual(bits, 128)
_, net, err = ParseCIDR("2001::0db8/96")
if err != nil {
panic(err)
}
ones, bits = net.Size()
assertEqual(ones, 96)
assertEqual(bits, 128)
}
func TestMasking(t *testing.T) {
for _, ipstr := range testIPStrs {
doMaskingTest(easyParseIP(ipstr), t)
}
}
func TestMaskingFuzz(t *testing.T) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
buf := make([]byte, 4)
for i := 0; i < 10000; i++ {
r.Read(buf)
doMaskingTest(net.IP(buf), t)
}
buf = make([]byte, 16)
for i := 0; i < 10000; i++ {
r.Read(buf)
doMaskingTest(net.IP(buf), t)
}
}
func BenchmarkMasking(b *testing.B) {
ip := easyParseIP("2001:0db8::42")
flat := FromNetIP(ip)
b.ResetTimer()
for i := 0; i < b.N; i++ {
flat.Mask(64, 128)
}
}
func BenchmarkMaskingLegacy(b *testing.B) {
ip := easyParseIP("2001:0db8::42")
mask := net.CIDRMask(64, 128)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ip.Mask(mask)
}
}
func BenchmarkMaskingCached(b *testing.B) {
i := easyParseIP("2001:0db8::42")
flat := FromNetIP(i)
mask := cidrMask(64, 128)
b.ResetTimer()
for i := 0; i < b.N; i++ {
flat.applyMask(mask)
}
}
func BenchmarkMaskingConstruct(b *testing.B) {
for i := 0; i < b.N; i++ {
cidrMask(69, 128)
}
}
func BenchmarkContains(b *testing.B) {
ip := easyParseIP("2001:0db8::42")
flat := FromNetIP(ip)
_, ipnet, err := net.ParseCIDR("2001:0db8::/64")
if err != nil {
panic(err)
}
flatnet := FromNetIPNet(*ipnet)
b.ResetTimer()
for i := 0; i < b.N; i++ {
flatnet.Contains(flat)
}
}
func BenchmarkContainsLegacy(b *testing.B) {
ip := easyParseIP("2001:0db8::42")
_, ipnetptr, err := net.ParseCIDR("2001:0db8::/64")
if err != nil {
panic(err)
}
ipnet := *ipnetptr
b.ResetTimer()
for i := 0; i < b.N; i++ {
ipnet.Contains(ip)
}
}

24
irc/flock/flock.go Normal file
View File

@ -0,0 +1,24 @@
//go:build !(plan9 || solaris)
package flock
import (
"errors"
"github.com/gofrs/flock"
)
var (
CouldntAcquire = errors.New("Couldn't acquire flock (is another Ergo running?)")
)
func TryAcquireFlock(path string) (fl Flocker, err error) {
f := flock.New(path)
success, err := f.TryLock()
if err != nil {
return nil, err
} else if !success {
return nil, CouldntAcquire
}
return f, nil
}

14
irc/flock/flock_iface.go Normal file
View File

@ -0,0 +1,14 @@
package flock
// documentation for github.com/gofrs/flock incorrectly claims that
// Flock implements sync.Locker; it does not because the Unlock method
// has a return type (err).
type Flocker interface {
Unlock() error
}
type noopFlocker struct{}
func (n *noopFlocker) Unlock() error {
return nil
}

View File

@ -0,0 +1,7 @@
//go:build plan9 || solaris
package flock
func TryAcquireFlock(path string) (fl Flocker, err error) {
return &noopFlocker{}, nil
}

View File

@ -9,8 +9,9 @@ import (
"errors"
"net"
"github.com/oragono/oragono/irc/modes"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/flatip"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/utils"
)
var (
@ -31,6 +32,7 @@ type webircConfig struct {
Fingerprint *string // legacy name for certfp, #1050
Certfp string
Hosts []string
AcceptHostname bool `yaml:"accept-hostname"`
allowedNets []net.IPNet
}
@ -77,16 +79,20 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP net.IP, tls boo
}
proxiedIP = proxiedIP.To16()
isBanned, banMsg := client.server.checkBans(proxiedIP)
isBanned, requireSASL, banMsg := client.server.checkBans(client.server.Config(), proxiedIP, true)
if isBanned {
return errBanned, banMsg
}
client.requireSASL = requireSASL
if requireSASL {
client.requireSASLMessage = banMsg
}
// successfully added a limiter entry for the proxied IP;
// remove the entry for the real IP if applicable (#197)
client.server.connectionLimiter.RemoveClient(session.realIP)
client.server.connectionLimiter.RemoveClient(flatip.FromNetIP(session.realIP))
// given IP is sane! override the client's current IP
client.server.logger.Info("connect-ip", "Accepted proxy IP for client", proxiedIP.String())
client.server.logger.Info("connect-ip", session.connID, "Accepted proxy IP for client", proxiedIP.String())
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
@ -95,6 +101,7 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP net.IP, tls boo
// nickmask will be updated when the client completes registration
// set tls info
session.certfp = ""
session.peerCerts = nil
client.SetMode(modes.TLS, tls)
return nil, ""
@ -116,9 +123,11 @@ func handleProxyCommand(server *Server, client *Client, session *Session, line s
}
}()
ip, err := utils.ParseProxyLine(line)
ip, err := utils.ParseProxyLineV1(line)
if err != nil {
return err
} else if ip == nil {
return nil
}
if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) {

View File

@ -4,25 +4,21 @@
package irc
import (
"fmt"
"maps"
"net"
"sync/atomic"
"slices"
"time"
"unsafe"
"github.com/oragono/oragono/irc/languages"
"github.com/oragono/oragono/irc/modes"
"github.com/ergochat/ergo/irc/caps"
"github.com/ergochat/ergo/irc/languages"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/utils"
"github.com/ergochat/ergo/irc/webpush"
)
func (server *Server) Config() (config *Config) {
return (*Config)(atomic.LoadPointer(&server.config))
}
func (server *Server) SetConfig(config *Config) {
atomic.StorePointer(&server.config, unsafe.Pointer(config))
}
func (server *Server) ChannelRegistrationEnabled() bool {
return server.Config().Channels.Registration.Enabled
return server.config.Load()
}
func (server *Server) GetOperator(name string) (oper *Oper) {
@ -38,11 +34,11 @@ func (server *Server) Languages() (lm *languages.Manager) {
}
func (server *Server) Defcon() uint32 {
return atomic.LoadUint32(&server.defcon)
return server.defcon.Load()
}
func (server *Server) SetDefcon(defcon uint32) {
atomic.StoreUint32(&server.defcon, defcon)
server.defcon.Store(defcon)
}
func (client *Client) Sessions() (sessions []*Session) {
@ -52,28 +48,20 @@ func (client *Client) Sessions() (sessions []*Session) {
return
}
func (client *Client) GetSessionByResumeID(resumeID string) (result *Session) {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
for _, session := range client.sessions {
if session.resumeID == resumeID {
return session
}
}
return
}
type SessionData struct {
ctime time.Time
atime time.Time
ip net.IP
hostname string
certfp string
deviceID string
ctime time.Time
atime time.Time
ip net.IP
hostname string
certfp string
deviceID string
connInfo string
connID string
sessionID int64
caps []string
}
func (client *Client) AllSessionData(currentSession *Session) (data []SessionData, currentIndex int) {
func (client *Client) AllSessionData(currentSession *Session, hasPrivs bool) (data []SessionData, currentIndex int) {
currentIndex = -1
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
@ -84,22 +72,29 @@ func (client *Client) AllSessionData(currentSession *Session) (data []SessionDat
currentIndex = i
}
data[i] = SessionData{
atime: session.lastActive,
ctime: session.ctime,
hostname: session.rawHostname,
certfp: session.certfp,
deviceID: session.deviceID,
atime: session.lastActive,
ctime: session.ctime,
hostname: session.rawHostname,
certfp: session.certfp,
deviceID: session.deviceID,
connID: session.connID,
sessionID: session.sessionID,
}
if session.proxiedIP != nil {
data[i].ip = session.proxiedIP
} else {
data[i].ip = session.realIP
}
if hasPrivs {
data[i].connInfo = utils.DescribeConn(session.socket.conn.UnderlyingConn().Conn)
}
data[i].caps = session.capabilities.Strings(caps.Cap302, nil, 300)
}
return
}
func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time, back bool) {
func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time, wasAway, nowAway string) {
config := client.server.Config()
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
@ -109,21 +104,32 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in
}
// success, attach the new session to the client
session.client = client
session.sessionID = client.nextSessionID
client.nextSessionID++
newSessions := make([]*Session, len(client.sessions)+1)
copy(newSessions, client.sessions)
newSessions[len(newSessions)-1] = session
if client.accountSettings.AutoreplayMissed || session.deviceID != "" {
lastSeen = client.lastSeen[session.deviceID]
client.setLastSeen(time.Now().UTC(), session.deviceID)
}
client.setLastSeen(time.Now().UTC(), session.deviceID)
client.sessions = newSessions
if client.autoAway {
back = true
client.autoAway = false
client.away = false
client.awayMessage = ""
wasAway = client.awayMessage
if client.autoAwayEnabledNoMutex(config) {
client.setAutoAwayNoMutex(config)
} else {
if session.awayMessage != "" && session.awayMessage != "*" {
// set the away message
client.awayMessage = session.awayMessage
} else if session.awayMessage == "" && !session.awayAt.IsZero() {
// weird edge case: explicit `AWAY` or `AWAY :` during pre-registration makes the client back
client.awayMessage = ""
}
// else: the client sent no AWAY command at all, no-op
// or: the client sent `AWAY *`, which should not modify the publicly visible away state
}
return true, len(client.sessions), lastSeen, back
nowAway = client.awayMessage
return true, len(client.sessions), lastSeen, wasAway, nowAway
}
func (client *Client) removeSession(session *Session) (success bool, length int) {
@ -143,10 +149,15 @@ func (client *Client) removeSession(session *Session) (success bool, length int)
return
}
func (session *Session) SetResumeID(resumeID string) {
session.client.stateMutex.Lock()
session.resumeID = resumeID
session.client.stateMutex.Unlock()
// #1650: show an arbitrarily chosen session IP and hostname in RPL_WHOISACTUALLY
func (client *Client) getWhoisActually() (ip net.IP, hostname string) {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
for _, session := range client.sessions {
return session.IP(), session.rawHostname
}
return utils.IPv4LoopbackAddress, client.server.name
}
func (client *Client) Nick() string {
@ -187,24 +198,71 @@ func (client *Client) Hostname() string {
func (client *Client) Away() (result bool, message string) {
client.stateMutex.Lock()
result, message = client.away, client.awayMessage
message = client.awayMessage
client.stateMutex.Unlock()
result = client.awayMessage != ""
return
}
func (client *Client) SetAway(away bool, awayMessage string) (changed bool) {
func (session *Session) SetAway(awayMessage string) (wasAway, nowAway string) {
client := session.client
config := client.server.Config()
client.stateMutex.Lock()
changed = away != client.away
client.away = away
client.awayMessage = awayMessage
client.stateMutex.Unlock()
defer client.stateMutex.Unlock()
session.awayMessage = awayMessage
session.awayAt = time.Now().UTC()
wasAway = client.awayMessage
if client.autoAwayEnabledNoMutex(config) {
client.setAutoAwayNoMutex(config)
} else if awayMessage != "*" {
client.awayMessage = awayMessage
} // else: `AWAY *`, should not modify publicly visible away state
nowAway = client.awayMessage
return
}
func (session *Session) ConnID() string {
if session == nil {
return "*"
}
return session.connID
}
func (client *Client) autoAwayEnabledNoMutex(config *Config) bool {
return client.registered && client.alwaysOn &&
persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway)
}
func (client *Client) setAutoAwayNoMutex(config *Config) {
// aggregate the away statuses of the individual sessions:
var globalAwayState string
var awaySetAt time.Time
for _, cSession := range client.sessions {
if cSession.awayMessage == "" {
// a session is active, we are not auto-away
client.awayMessage = ""
return
} else if cSession.awayAt.After(awaySetAt) && cSession.awayMessage != "*" {
// choose the latest valid away message from any session
globalAwayState = cSession.awayMessage
awaySetAt = cSession.awayAt
}
}
if awaySetAt.IsZero() {
// no sessions, enable auto-away
client.awayMessage = config.languageManager.Translate(client.languages, `User is currently disconnected`)
} else {
client.awayMessage = globalAwayState
}
}
func (client *Client) AlwaysOn() (alwaysOn bool) {
client.stateMutex.Lock()
client.stateMutex.RLock()
alwaysOn = client.registered && client.alwaysOn
client.stateMutex.Unlock()
client.stateMutex.RUnlock()
return
}
@ -217,18 +275,6 @@ func (client *Client) uniqueIdentifiers() (nickCasefolded string, skeleton strin
return client.nickCasefolded, client.skeleton
}
func (client *Client) ResumeID() string {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
return client.resumeID
}
func (client *Client) SetResumeID(id string) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
client.resumeID = id
}
func (client *Client) Oper() *Oper {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
@ -260,12 +306,6 @@ func (client *Client) AwayMessage() (result string) {
return
}
func (client *Client) SetAwayMessage(message string) {
client.stateMutex.Lock()
client.awayMessage = message
client.stateMutex.Unlock()
}
func (client *Client) Account() string {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
@ -291,6 +331,26 @@ func (client *Client) Login(account ClientAccount) {
return
}
func (client *Client) setAccountName(name string) {
// XXX this assumes validation elsewhere
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
client.accountName = name
}
func (client *Client) setCloakedHostname(cloak string) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
client.cloakedHostname = cloak
client.updateNickMaskNoMutex()
}
func (client *Client) CloakedHostname() string {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
return client.cloakedHostname
}
func (client *Client) historyCutoff() (cutoff time.Time) {
client.stateMutex.Lock()
if client.account != "" {
@ -321,26 +381,19 @@ func (client *Client) AccountSettings() (result AccountSettings) {
func (client *Client) SetAccountSettings(settings AccountSettings) {
// we mark dirty if the client is transitioning to always-on
var becameAlwaysOn, autoreplayMissedDisabled bool
var becameAlwaysOn bool
alwaysOn := persistenceEnabled(client.server.Config().Accounts.Multiclient.AlwaysOn, settings.AlwaysOn)
client.stateMutex.Lock()
if client.registered {
// only allow the client to become always-on if their nick equals their account name
alwaysOn = alwaysOn && client.nick == client.accountName
autoreplayMissedDisabled = (client.accountSettings.AutoreplayMissed && !settings.AutoreplayMissed)
becameAlwaysOn = (!client.alwaysOn && alwaysOn)
client.alwaysOn = alwaysOn
if autoreplayMissedDisabled {
// clear the lastSeen entry for the default session, but not for device IDs
delete(client.lastSeen, "")
}
}
client.accountSettings = settings
client.stateMutex.Unlock()
if becameAlwaysOn {
client.markDirty(IncludeAllAttrs)
} else if autoreplayMissedDisabled {
client.markDirty(IncludeLastSeen)
}
}
@ -411,6 +464,7 @@ func (client *Client) detailsNoMutex() (result ClientDetails) {
result.username = client.username
result.hostname = client.hostname
result.realname = client.realname
result.ip = client.getIPNoMutex()
result.nickMask = client.nickMaskString
result.nickMaskCasefolded = client.nickMaskCasefolded
result.account = client.account
@ -433,6 +487,252 @@ func (client *Client) Realname() string {
return result
}
func (client *Client) IsExpiredAlwaysOn(config *Config) (result bool) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
return client.checkAlwaysOnExpirationNoMutex(config, false)
}
func (client *Client) checkAlwaysOnExpirationNoMutex(config *Config, ignoreRegistration bool) (result bool) {
if !((client.registered || ignoreRegistration) && client.alwaysOn) {
return false
}
if len(client.lastSeen) == 0 {
return true // #2252: do not precreate the client if it was never logged into at all
}
deadline := time.Duration(config.Accounts.Multiclient.AlwaysOnExpiration)
if deadline == 0 {
return false
}
now := time.Now()
for _, ts := range client.lastSeen {
if now.Sub(ts) < deadline {
return false
}
}
return true
}
func (client *Client) GetReadMarker(cfname string) (result string) {
client.stateMutex.RLock()
t, ok := client.readMarkers[cfname]
client.stateMutex.RUnlock()
if ok {
return fmt.Sprintf("timestamp=%s", t.Format(utils.IRCv3TimestampFormat))
}
return "*"
}
func (client *Client) getMarkreadTime(cfname string) (timestamp time.Time, ok bool) {
client.stateMutex.RLock()
timestamp, ok = client.readMarkers[cfname]
client.stateMutex.RUnlock()
return
}
func (client *Client) copyReadMarkers() (result map[string]time.Time) {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
return maps.Clone(client.readMarkers)
}
func (client *Client) SetReadMarker(cfname string, now time.Time) (result time.Time) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
if client.readMarkers == nil {
client.readMarkers = make(map[string]time.Time)
}
result = updateLRUMap(client.readMarkers, cfname, now, maxReadMarkers)
client.dirtyTimestamps = true
return
}
func updateLRUMap(lru map[string]time.Time, key string, val time.Time, maxItems int) (result time.Time) {
if currentVal := lru[key]; currentVal.After(val) {
return currentVal
}
lru[key] = val
// evict the least-recently-used entry if necessary
if maxItems < len(lru) {
var minKey string
var minVal time.Time
for key, val := range lru {
if minVal.IsZero() || val.Before(minVal) {
minKey, minVal = key, val
}
}
delete(lru, minKey)
}
return val
}
func (client *Client) addClearablePushMessage(cftarget string, messageTime time.Time) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
if client.clearablePushMessages == nil {
client.clearablePushMessages = make(map[string]time.Time)
}
updateLRUMap(client.clearablePushMessages, cftarget, messageTime, maxReadMarkers)
}
func (client *Client) clearClearablePushMessage(cftarget string, readTimestamp time.Time) (ok bool) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
pushMessageTime, ok := client.clearablePushMessages[cftarget]
if ok && utils.ReadMarkerLessThanOrEqual(pushMessageTime, readTimestamp) {
delete(client.clearablePushMessages, cftarget)
return true
}
return false
}
func (client *Client) shouldFlushTimestamps() (result bool) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
result = client.dirtyTimestamps && client.registered && client.alwaysOn
client.dirtyTimestamps = false
return
}
func (client *Client) setKlined() {
client.stateMutex.Lock()
client.isKlined = true
client.stateMutex.Unlock()
}
func (client *Client) refreshPushSubscription(endpoint string, keys webpush.Keys) bool {
// do not mark dirty --- defer the write to periodic maintenance
now := time.Now().UTC()
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
sub, ok := client.pushSubscriptions[endpoint]
if ok && sub.Keys.Equal(keys) {
sub.LastRefresh = now
return true
}
return false // subscription doesn't exist, we need to send a test message
}
func (client *Client) addPushSubscription(endpoint string, keys webpush.Keys) error {
changed := false
defer func() {
if changed {
client.markDirty(IncludeAllAttrs)
}
}()
config := client.server.Config()
now := time.Now().UTC()
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
if client.pushSubscriptions == nil {
client.pushSubscriptions = make(map[string]*pushSubscription)
}
sub, ok := client.pushSubscriptions[endpoint]
if ok {
changed = !sub.Keys.Equal(keys)
sub.Keys = keys
sub.LastRefresh = now
} else {
if len(client.pushSubscriptions) >= config.WebPush.MaxSubscriptions {
return errLimitExceeded
}
changed = true
sub = newPushSubscription(storedPushSubscription{
Endpoint: endpoint,
Keys: keys,
LastRefresh: now,
LastSuccess: now, // assume we just sent a successful message to confirm the sub
})
client.pushSubscriptions[endpoint] = sub
}
if changed {
client.rebuildPushSubscriptionCache()
}
return nil
}
func (client *Client) hasPushSubscriptions() bool {
return client.pushSubscriptionsExist.Load() != 0
}
func (client *Client) getPushSubscriptions(refresh bool) []storedPushSubscription {
if refresh {
func() {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
client.rebuildPushSubscriptionCache()
}()
}
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
return client.cachedPushSubscriptions
}
func (client *Client) rebuildPushSubscriptionCache() {
// must hold write lock
if len(client.pushSubscriptions) == 0 {
client.cachedPushSubscriptions = nil
client.pushSubscriptionsExist.Store(0)
return
}
client.cachedPushSubscriptions = make([]storedPushSubscription, 0, len(client.pushSubscriptions))
for _, subscription := range client.pushSubscriptions {
client.cachedPushSubscriptions = append(client.cachedPushSubscriptions, subscription.storedPushSubscription)
}
client.pushSubscriptionsExist.Store(1)
}
func (client *Client) deletePushSubscription(endpoint string, writeback bool) (changed bool) {
defer func() {
if writeback && changed {
client.markDirty(IncludeAllAttrs)
}
}()
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
_, ok := client.pushSubscriptions[endpoint]
if ok {
changed = true
delete(client.pushSubscriptions, endpoint)
client.rebuildPushSubscriptionCache()
}
return
}
func (client *Client) recordPush(endpoint string, success bool) {
now := time.Now().UTC()
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
subscription, ok := client.pushSubscriptions[endpoint]
if !ok {
return
}
if success {
subscription.LastSuccess = now
}
// TODO we may want to track failures in some way in the future
}
func (channel *Channel) Name() string {
channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock()
@ -483,9 +783,11 @@ func (channel *Channel) Founder() string {
func (channel *Channel) HighestUserMode(client *Client) (result modes.Mode) {
channel.stateMutex.RLock()
clientModes := channel.members[client]
channel.stateMutex.RUnlock()
return clientModes.HighestChannelUserMode()
defer channel.stateMutex.RUnlock()
if clientData, ok := channel.members[client]; ok {
return clientData.modes.HighestChannelUserMode()
}
return
}
func (channel *Channel) Settings() (result ChannelSettings) {
@ -496,8 +798,244 @@ func (channel *Channel) Settings() (result ChannelSettings) {
}
func (channel *Channel) SetSettings(settings ChannelSettings) {
defer channel.MarkDirty(IncludeSettings)
channel.stateMutex.Lock()
defer channel.stateMutex.Unlock()
channel.settings = settings
channel.stateMutex.Unlock()
channel.MarkDirty(IncludeSettings)
}
func (channel *Channel) setForward(forward string) {
channel.stateMutex.Lock()
channel.forward = forward
channel.stateMutex.Unlock()
}
func (channel *Channel) Ctime() (ctime time.Time) {
channel.stateMutex.RLock()
ctime = channel.createdTime
channel.stateMutex.RUnlock()
return
}
func (channel *Channel) getAmode(cfaccount string) (result modes.Mode) {
channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock()
return channel.accountToUMode[cfaccount]
}
func (channel *Channel) UUID() utils.UUID {
channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock()
return channel.uuid
}
func (session *Session) isSubscribedTo(key string) bool {
session.client.stateMutex.RLock()
defer session.client.stateMutex.RUnlock()
return session.metadataSubscriptions.Has(key)
}
func (session *Session) SubscribeTo(keys ...string) ([]string, error) {
maxSubs := session.client.server.Config().Metadata.MaxSubs
session.client.stateMutex.Lock()
defer session.client.stateMutex.Unlock()
if session.metadataSubscriptions == nil {
session.metadataSubscriptions = make(utils.HashSet[string])
}
var added []string
for _, k := range keys {
if !session.metadataSubscriptions.Has(k) {
if len(session.metadataSubscriptions) > maxSubs {
return added, errMetadataTooManySubs
}
added = append(added, k)
session.metadataSubscriptions.Add(k)
}
}
return added, nil
}
func (session *Session) UnsubscribeFrom(keys ...string) []string {
session.client.stateMutex.Lock()
defer session.client.stateMutex.Unlock()
var removed []string
for k := range session.metadataSubscriptions {
if slices.Contains(keys, k) {
removed = append(removed, k)
session.metadataSubscriptions.Remove(k)
}
}
return removed
}
func (session *Session) MetadataSubscriptions() utils.HashSet[string] {
session.client.stateMutex.Lock()
defer session.client.stateMutex.Unlock()
return maps.Clone(session.metadataSubscriptions)
}
func (channel *Channel) GetMetadata(key string) (string, bool) {
channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock()
val, ok := channel.metadata[key]
return val, ok
}
func (channel *Channel) SetMetadata(key string, value string, limit int) (updated bool, err error) {
defer channel.MarkDirty(IncludeAllAttrs)
channel.stateMutex.Lock()
defer channel.stateMutex.Unlock()
if channel.metadata == nil {
channel.metadata = make(map[string]string)
}
existing, ok := channel.metadata[key]
if !ok && len(channel.metadata) >= limit {
return false, errLimitExceeded
}
updated = !ok || value != existing
if updated {
channel.metadata[key] = value
}
return updated, nil
}
func (channel *Channel) ListMetadata() map[string]string {
channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock()
return maps.Clone(channel.metadata)
}
func (channel *Channel) DeleteMetadata(key string) (updated bool) {
defer channel.MarkDirty(IncludeAllAttrs)
channel.stateMutex.Lock()
defer channel.stateMutex.Unlock()
_, updated = channel.metadata[key]
if updated {
delete(channel.metadata, key)
}
return updated
}
func (channel *Channel) ClearMetadata() map[string]string {
defer channel.MarkDirty(IncludeAllAttrs)
channel.stateMutex.Lock()
defer channel.stateMutex.Unlock()
oldMap := channel.metadata
channel.metadata = nil
return oldMap
}
func (channel *Channel) CountMetadata() int {
channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock()
return len(channel.metadata)
}
func (client *Client) GetMetadata(key string) (string, bool) {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
val, ok := client.metadata[key]
return val, ok
}
func (client *Client) SetMetadata(key string, value string, limit int) (updated bool, err error) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
if client.metadata == nil {
client.metadata = make(map[string]string)
}
existing, ok := client.metadata[key]
if !ok && len(client.metadata) >= limit {
return false, errLimitExceeded
}
updated = !ok || value != existing
if updated {
client.metadata[key] = value
}
return updated, nil
}
func (client *Client) UpdateMetadataFromPrereg(preregData map[string]string, limit int) (updates map[string]string) {
updates = make(map[string]string, len(preregData))
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
if client.metadata == nil {
client.metadata = make(map[string]string)
}
for k, v := range preregData {
// do not overwrite any existing keys
_, ok := client.metadata[k]
if ok {
continue
}
if len(client.metadata) >= limit {
return // we know this is a new key
}
client.metadata[k] = v
updates[k] = v
}
return
}
func (client *Client) ListMetadata() map[string]string {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
return maps.Clone(client.metadata)
}
func (client *Client) DeleteMetadata(key string) (updated bool) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
_, updated = client.metadata[key]
if updated {
delete(client.metadata, key)
}
return updated
}
func (client *Client) ClearMetadata() map[string]string {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
oldMap := client.metadata
client.metadata = nil
return oldMap
}
func (client *Client) CountMetadata() int {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
return len(client.metadata)
}

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ import (
"strings"
"sync"
"github.com/oragono/oragono/irc/languages"
"github.com/ergochat/ergo/irc/languages"
)
// HelpEntryType represents the different sorts of help entries that can exist.
@ -37,7 +37,7 @@ type HelpEntry struct {
var (
cmodeHelpText = `== Channel Modes ==
Oragono supports the following channel modes:
Ergo supports the following channel modes:
+b | Client masks that are banned from the channel (e.g. *!*@127.0.0.1)
+e | Client masks that are exempted from bans.
@ -45,14 +45,21 @@ Oragono supports the following channel modes:
+i | Invite-only mode, only invited clients can join the channel.
+k | Key required when joining the channel.
+l | Client join limit for the channel.
+f | Users who are unable to join this channel (due to another mode) are forwarded
to the provided channel instead.
+m | Moderated mode, only privileged clients can talk on the channel.
+n | No-outside-messages mode, only users that are on the channel can send
| messages to it.
+R | Only registered users can join the channel.
+M | Only registered or voiced users can speak in the channel.
+s | Secret mode, channel won't show up in /LIST or whois replies.
+t | Only channel opers can modify the topic.
+E | Roleplaying commands are enabled in the channel.
+C | Clients are blocked from sending CTCP messages in the channel.
+u | Auditorium mode: JOIN, PART, QUIT, NAMES, and WHO are hidden
from unvoiced clients.
+U | Op-moderated mode: messages from unprivileged clients are sent
only to channel operators.
= Prefixes =
@ -63,7 +70,7 @@ Oragono supports the following channel modes:
+v (+) | Voice channel mode.`
umodeHelpText = `== User Modes ==
Oragono supports the following user modes:
Ergo supports the following user modes:
+a | User is marked as being away. This mode is set with the /AWAY command.
+i | User is marked as invisible (their channels are hidden from whois replies).
@ -73,13 +80,14 @@ Oragono supports the following user modes:
+Z | User is connected via TLS.
+B | User is a bot.
+E | User can receive roleplaying commands.
+T | User is blocked from sending CTCP messages.`
+T | CTCP messages to the user are blocked.`
snomaskHelpText = `== Server Notice Masks ==
Oragono supports the following server notice masks for operators:
Ergo supports the following server notice masks for operators:
a | Local announcements.
c | Local client connections.
d | Local client disconnects.
j | Local channel actions.
k | Local kills.
n | Local nick changes.
@ -102,13 +110,12 @@ For instance, this would set the kill, oper, account and xline snomasks on dan:
// Help contains the help strings distributed with the IRCd.
var Help = map[string]HelpEntry{
// Commands
"acc": {
text: `ACC LS
ACC REGISTER <accountname> [callback_namespace:]<callback> [cred_type] :<credential>
ACC VERIFY <accountname> <auth_code>
"accept": {
text: `ACCEPT <target>
Used in account registration. See the relevant specs for more info:
https://oragono.io/specs.html`,
ACCEPT allows the target user to send you direct messages, overriding any
restrictions that might otherwise prevent this. Currently, the only
applicable restriction is the +R registered-only mode.`,
},
"ambiance": {
text: `AMBIANCE <target> <text to be sent>
@ -132,14 +139,6 @@ longer away.`,
BATCH initiates an IRCv3 client-to-server batch. You should never need to
issue this command manually.`,
},
"brb": {
text: `BRB [message]
Disconnects you from the server, while instructing the server to keep you
present for a short time window. During this window, you can either resume
or reattach to your nickname. If [message] is sent, it is used as your away
message (and as your quit message if you don't return in time).`,
},
"cap": {
text: `CAP <subcommand> [:<capabilities>]
@ -152,8 +151,8 @@ http://ircv3.net/specs/core/capability-negotiation-3.2.html`,
text: `CHATHISTORY [params]
CHATHISTORY is a history replay command associated with the IRCv3
specification draft/chathistory. See this document:
https://github.com/ircv3/ircv3-specifications/pull/393`,
chathistory extension. See this document:
https://ircv3.net/specs/extensions/chathistory`,
},
"debug": {
oper: true,
@ -177,7 +176,8 @@ spam or other hostile activity. It has five levels, which are cumulative
(i.e., level 3 includes all restrictions from level 4 and so on):
5: Normal operation
4: No new account or channel registrations
4: No new account or channel registrations; if Tor is enabled, no new
unauthenticated connections from Tor
3: All users are +R; no changes to vhosts
2: No new unauthenticated connections; all channels are +R
1: No new connections except from localhost or other trusted IPs`,
@ -216,7 +216,9 @@ ON <server> specifies that the ban is to be set on that specific server.
[reason] and [oper reason], if they exist, are separated by a vertical bar (|).
If "DLINE LIST" is sent, the server sends back a list of our current DLINEs.`,
If "DLINE LIST" is sent, the server sends back a list of our current DLINEs.
To remove a DLINE, use the "UNDLINE" command.`,
},
"extjwt": {
text: `EXTJWT <target> [service_name]
@ -236,11 +238,10 @@ Get an explanation of <argument>, or "index" for a list of help topics.`,
"history": {
text: `HISTORY <target> [limit]
Replay message history. <target> can be a channel name, "me" to replay direct
message history, or a nickname to replay another client's direct message
history (they must be logged into the same account as you). [limit] can be
either an integer (the maximum number of messages to replay), or a time
duration like 10m or 1h (the time window within which to replay messages).`,
Replay message history. <target> can be a channel name or a nickname you have
direct message history with. [limit] can be either an integer (the maximum
number of messages to replay), or a time duration like 10m or 1h (the time
window within which to replay messages).`,
},
"info": {
text: `INFO
@ -257,6 +258,11 @@ appropriate channel privs.`,
text: `ISON <nickname>{ <nickname>}
Returns whether the given nicks exist on the network.`,
},
"isupport": {
text: `ISUPPORT
Returns RPL_ISUPPORT lines describing the server's capabilities.`,
},
"join": {
text: `JOIN <channel>{,<channel>} [<key>{,<key>}]
@ -303,7 +309,9 @@ ON <server> specifies that the ban is to be set on that specific server.
[reason] and [oper reason], if they exist, are separated by a vertical bar (|).
If "KLINE LIST" is sent, the server sends back a list of our current KLINEs.`,
If "KLINE LIST" is sent, the server sends back a list of our current KLINEs.
To remove a KLINE, use the "UNKLINE" command.`,
},
"language": {
text: `LANGUAGE <code>{ <code>}
@ -323,6 +331,19 @@ channels). <elistcond>s modify how the channels are selected.`,
Shows statistics about the size of the network. If <mask> is given, only
returns stats for servers matching the given mask. If <server> is given, the
command is processed by that server.`,
},
"markread": {
text: `MARKREAD <target> [timestamp]
MARKREAD updates an IRCv3 read message marker. It is not intended for use by
end users. For more details, see the latest draft of the read-marker
specification.`,
},
"metadata": {
text: `METADATA <target> <subcommand> [<everything else>...]
Retrieve and meddle with metadata for the given target.
Have a look at https://ircv3.net/specs/extensions/metadata for interesting technical information.`,
},
"mode": {
text: `MODE <target> [<modestring> [<mode arguments>...]]
@ -402,6 +423,13 @@ Leaves the given channels and shows people the given reason.`,
When the server requires a connection password to join, used to send us the
password.`,
},
"persistence": {
text: `PERSISTENCE [params]
PERSISTENCE is a command associated with an IRC protocol extension for
persistent connections. End users should probably use /NS GET ALWAYS-ON
and /NS SET ALWAYS-ON instead.`,
},
"ping": {
text: `PING <args>...
@ -417,6 +445,22 @@ Replies to a PING. Used to check link connectivity.`,
text: `PRIVMSG <target>{,<target>} <text to be sent>
Sends the text to the given targets as a PRIVMSG.`,
},
"redact": {
text: `REDACT <target> <targetmsgid> [<reason>]
Removes the message of the target msgid from the chat history of a channel
or target user.`,
},
"relaymsg": {
text: `RELAYMSG <channel> <spoofed nick> :<message>
This command lets channel operators relay messages to their
channel from other messaging systems using relay bots. The
spoofed nickname MUST contain a forwardslash.
For example:
RELAYMSG #ircv3 Mallory/D :Welp, we linked Discord...`,
},
"rename": {
text: `RENAME <channel> <newname> [<reason>]
@ -472,18 +516,17 @@ specs for more info: http://ircv3.net/specs/core/message-tags-3.3.html`,
text: `QUIT [reason]
Indicates that you're leaving the server, and shows everyone the given reason.`,
},
"register": {
text: `REGISTER <account> <email | *> <password>
Registers an account in accordance with the draft/account-registration capability.`,
},
"rehash": {
oper: true,
text: `REHASH
Reloads the config file and updates TLS certificates on listeners`,
},
"resume": {
text: `RESUME <oldnick> [timestamp]
Sent before registration has completed, this indicates that the client wants to
resume their old connection <oldnick>.`,
},
"time": {
text: `TIME [server]
@ -495,6 +538,19 @@ Shows the time of the current, or the given, server.`,
If [topic] is given, sets the topic in the channel to that. If [topic] is not
given, views the current topic on the channel.`,
},
"uban": {
text: `UBAN <subcommand> [arguments]
Ergo's "unified ban" system. Accepts the following subcommands:
1. UBAN ADD <target> [REQUIRE-SASL] [DURATION <duration>] [REASON...]
2. UBAN DEL <target>
3. UBAN LIST
4. UBAN INFO <target>
<target> may be an IP, a CIDR, a nickmask with wildcards, or the name of an
account to suspend. Note that REQUIRE-SASL is only valid for IP and CIDR bans.`,
},
"undline": {
oper: true,
@ -521,6 +577,11 @@ For example:
Used in connection registration, sets your username and realname to the given
values (though your username may also be looked up with Ident).`,
},
"uninvite": {
text: `UNINVITE <nickname> <channel>
UNINVITE rescinds a channel invitation sent for an invite-only channel.`,
},
"users": {
text: `USERS [parameters]
@ -531,6 +592,11 @@ The USERS command is not implemented.`,
text: `USERHOST <nickname>{ <nickname>}
Shows information about the given users. Takes up to 10 nicknames.`,
},
"verify": {
text: `VERIFY <account> <code>
Verifies an account in accordance with the draft/account-registration capability.`,
},
"version": {
text: `VERSION [server]
@ -549,6 +615,11 @@ ircv3.net/specs/extensions/webirc.html
the connection from the client to the gateway, such as:
- tls: this flag indicates that the client->gateway connection is secure`,
},
"webpush": {
text: `WEBPUSH <subcommand> [arguments]
Configures web push settings. Not for direct use by end users.`,
},
"who": {
text: `WHO <name> [o]
@ -612,15 +683,15 @@ for direct use by end users.`,
"casemapping": {
text: `RPL_ISUPPORT CASEMAPPING
Oragono supports an experimental unicode casemapping designed for extended
Ergo supports an experimental unicode casemapping designed for extended
Unicode support. This casemapping is based off RFC 7613 and the draft rfc7613
casemapping spec here: https://oragono.io/specs.html`,
casemapping spec here: https://ergo.chat/specs.html`,
helpType: ISupportHelpEntry,
},
"prefix": {
text: `RPL_ISUPPORT PREFIX
Oragono supports the following channel membership prefixes:
Ergo supports the following channel membership prefixes:
+q (~) | Founder channel mode.
+a (&) | Admin channel mode.
@ -717,22 +788,19 @@ func (hm *HelpIndexManager) GenerateIndices(lm *languages.Manager) {
}
// sendHelp sends the client help of the given string.
func (client *Client) sendHelp(name string, text string, rb *ResponseBuffer) {
splitName := strings.Split(name, " ")
func (client *Client) sendHelp(helpEntry string, text string, rb *ResponseBuffer) {
helpEntry = strings.ToUpper(helpEntry)
nick := client.Nick()
textLines := strings.Split(text, "\n")
for i, line := range textLines {
args := splitName
args = append(args, line)
if i == 0 {
rb.Add(nil, client.server.name, RPL_HELPSTART, args...)
rb.Add(nil, client.server.name, RPL_HELPSTART, nick, helpEntry, line)
} else {
rb.Add(nil, client.server.name, RPL_HELPTXT, args...)
rb.Add(nil, client.server.name, RPL_HELPTXT, nick, helpEntry, line)
}
}
args := splitName
args = append(args, client.t("End of /HELPOP"))
rb.Add(nil, client.server.name, RPL_ENDOFHELP, args...)
rb.Add(nil, client.server.name, RPL_ENDOFHELP, nick, helpEntry, client.t("End of /HELPOP"))
}
// GetHelpIndex returns the help index for the given language.

View File

@ -4,9 +4,11 @@
package history
import (
"github.com/oragono/oragono/irc/utils"
"slices"
"sync"
"time"
"github.com/ergochat/ergo/irc/utils"
)
type ItemType uint
@ -23,6 +25,7 @@ const (
Tagmsg
Nick
Topic
Invite
)
const (
@ -43,7 +46,8 @@ type Item struct {
// for a DM, this is the casefolded nickname of the other party (whether this is
// an incoming or outgoing message). this lets us emulate the "query buffer" functionality
// required by CHATHISTORY:
CfCorrespondent string
CfCorrespondent string `json:"CfCorrespondent,omitempty"`
IsBot bool `json:"IsBot,omitempty"`
}
// HasMsgid tests whether a message has the message id `msgid`.
@ -53,12 +57,6 @@ func (item *Item) HasMsgid(msgid string) bool {
type Predicate func(item *Item) (matches bool)
func Reverse(results []Item) {
for i, j := 0, len(results)-1; i < j; i, j = i+1, j-1 {
results[i], results[j] = results[j], results[i]
}
}
// Buffer is a ring buffer holding message/event history for a channel or user
type Buffer struct {
sync.RWMutex
@ -158,7 +156,7 @@ func (list *Buffer) betweenHelper(start, end Selector, cutoff time.Time, pred Pr
defer func() {
if !ascending {
Reverse(results)
slices.Reverse(results)
}
}()
@ -199,6 +197,78 @@ func (list *Buffer) betweenHelper(start, end Selector, cutoff time.Time, pred Pr
return list.matchInternal(satisfies, ascending, limit), complete, nil
}
// returns all correspondents, in reverse time order
func (list *Buffer) allCorrespondents() (results []TargetListing) {
seen := make(utils.HashSet[string])
list.RLock()
defer list.RUnlock()
if list.start == -1 || len(list.buffer) == 0 {
return
}
// XXX traverse in reverse order, so we get the latest timestamp
// of any message sent to/from the correspondent
pos := list.prev(list.end)
stop := list.start
for {
if !seen.Has(list.buffer[pos].CfCorrespondent) {
seen.Add(list.buffer[pos].CfCorrespondent)
results = append(results, TargetListing{
CfName: list.buffer[pos].CfCorrespondent,
Time: list.buffer[pos].Message.Time,
})
}
if pos == stop {
break
}
pos = list.prev(pos)
}
return
}
// list DM correspondents, as one input to CHATHISTORY TARGETS
func (list *Buffer) listCorrespondents(start, end Selector, cutoff time.Time, limit int) (results []TargetListing, err error) {
after := start.Time
before := end.Time
after, before, ascending := MinMaxAsc(after, before, cutoff)
correspondents := list.allCorrespondents()
if len(correspondents) == 0 {
return
}
// XXX allCorrespondents returns results in reverse order,
// so if we're ascending, we actually go backwards
var i int
if ascending {
i = len(correspondents) - 1
} else {
i = 0
}
for 0 <= i && i < len(correspondents) && (limit == 0 || len(results) < limit) {
if (after.IsZero() || correspondents[i].Time.After(after)) &&
(before.IsZero() || correspondents[i].Time.Before(before)) {
results = append(results, correspondents[i])
}
if ascending {
i--
} else {
i++
}
}
if !ascending {
slices.Reverse(results)
}
return
}
// implements history.Sequence, emulating a single history buffer (for a channel,
// a single user's DMs, or a DM conversation)
type bufferSequence struct {
@ -221,14 +291,27 @@ func (list *Buffer) MakeSequence(correspondent string, cutoff time.Time) Sequenc
}
}
func (seq *bufferSequence) Between(start, end Selector, limit int) (results []Item, complete bool, err error) {
return seq.list.betweenHelper(start, end, seq.cutoff, seq.pred, limit)
func (seq *bufferSequence) Between(start, end Selector, limit int) (results []Item, err error) {
results, _, err = seq.list.betweenHelper(start, end, seq.cutoff, seq.pred, limit)
return
}
func (seq *bufferSequence) Around(start Selector, limit int) (results []Item, err error) {
return GenericAround(seq, start, limit)
}
func (seq *bufferSequence) ListCorrespondents(start, end Selector, limit int) (results []TargetListing, err error) {
return seq.list.listCorrespondents(start, end, seq.cutoff, limit)
}
func (seq *bufferSequence) Cutoff() time.Time {
return seq.cutoff
}
func (seq *bufferSequence) Ephemeral() bool {
return true
}
// you must be holding the read lock to call this
func (list *Buffer) matchInternal(predicate Predicate, ascending bool, limit int) (results []Item) {
if list.start == -1 || len(list.buffer) == 0 {
@ -321,18 +404,6 @@ func (list *Buffer) next(index int) int {
}
}
// return n such that v <= n and n == 2**i for some i
func roundUpToPowerOfTwo(v int) int {
// http://graphics.stanford.edu/~seander/bithacks.html
v -= 1
v |= v >> 1
v |= v >> 2
v |= v >> 4
v |= v >> 8
v |= v >> 16
return v + 1
}
func (list *Buffer) maybeExpand() {
if list.window == 0 {
return // autoresize is disabled
@ -352,7 +423,7 @@ func (list *Buffer) maybeExpand() {
return // oldest element is old enough to overwrite
}
newSize := roundUpToPowerOfTwo(length + 1)
newSize := utils.RoundUpToPowerOfTwo(length + 1)
if list.maximumSize < newSize {
newSize = list.maximumSize
}

View File

@ -241,17 +241,6 @@ func TestDisabledByResize(t *testing.T) {
assertEqual(len(items), 0, t)
}
func TestRoundUp(t *testing.T) {
assertEqual(roundUpToPowerOfTwo(2), 2, t)
assertEqual(roundUpToPowerOfTwo(3), 4, t)
assertEqual(roundUpToPowerOfTwo(64), 64, t)
assertEqual(roundUpToPowerOfTwo(65), 128, t)
assertEqual(roundUpToPowerOfTwo(100), 128, t)
assertEqual(roundUpToPowerOfTwo(1000), 1024, t)
assertEqual(roundUpToPowerOfTwo(1025), 2048, t)
assertEqual(roundUpToPowerOfTwo(269435457), 536870912, t)
}
func BenchmarkInsert(b *testing.B) {
buf := NewHistoryBuffer(1024, 0)
b.ResetTimer()

View File

@ -4,6 +4,7 @@
package history
import (
"strings"
"time"
)
@ -17,15 +18,24 @@ type Selector struct {
// it encapsulates restrictions such as registration time cutoffs, or
// only looking at a single "query buffer" (DMs with a particular correspondent)
type Sequence interface {
Between(start, end Selector, limit int) (results []Item, complete bool, err error)
Between(start, end Selector, limit int) (results []Item, err error)
Around(start Selector, limit int) (results []Item, err error)
ListCorrespondents(start, end Selector, limit int) (results []TargetListing, err error)
// this are weird hacks that violate the encapsulation of Sequence to some extent;
// Cutoff() returns the cutoff time for other code to use (it returns the zero time
// if none is set), and Ephemeral() returns whether the backing store is in-memory
// or a persistent database.
Cutoff() time.Time
Ephemeral() bool
}
// This is a bad, slow implementation of CHATHISTORY AROUND using the BETWEEN semantics
func GenericAround(seq Sequence, start Selector, limit int) (results []Item, err error) {
var halfLimit int
halfLimit = (limit + 1) / 2
initialResults, _, err := seq.Between(Selector{}, start, halfLimit)
initialResults, err := seq.Between(Selector{}, start, halfLimit)
if err != nil {
return
} else if len(initialResults) == 0 {
@ -34,7 +44,7 @@ func GenericAround(seq Sequence, start Selector, limit int) (results []Item, err
return
}
newStart := Selector{Time: initialResults[0].Message.Time}
results, _, err = seq.Between(newStart, Selector{}, limit)
results, err = seq.Between(newStart, Selector{}, limit)
return
}
@ -68,3 +78,16 @@ func MinMaxAsc(after, before, cutoff time.Time) (min, max time.Time, ascending b
}
return after, before, ascending
}
// maps regular msgids from JOIN, etc. to a msgid suitable for attaching
// to a HistServ message describing the JOIN. See #491 for some history.
func HistservMungeMsgid(msgid string) string {
return "_" + msgid
}
// strips munging from a msgid. future schemes may not support a well-defined
// mapping of munged msgids to true msgids, but munged msgids should always contain
// a _, with metadata in front and data (possibly the true msgid) after.
func NormalizeMsgid(msgid string) string {
return strings.TrimPrefix(msgid, "_")
}

77
irc/history/targets.go Normal file
View File

@ -0,0 +1,77 @@
// Copyright (c) 2021 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package history
import (
"slices"
"sort"
"time"
)
type TargetListing struct {
CfName string
Time time.Time
}
// Merge `base`, a paging window of targets, with `extras` (the target entries
// for all joined channels).
func MergeTargets(base []TargetListing, extra []TargetListing, start, end time.Time, limit int) (results []TargetListing) {
if len(extra) == 0 {
return base
}
SortCorrespondents(extra)
start, end, ascending := MinMaxAsc(start, end, time.Time{})
predicate := func(t time.Time) bool {
return (start.IsZero() || start.Before(t)) && (end.IsZero() || end.After(t))
}
prealloc := len(base) + len(extra)
if limit < prealloc {
prealloc = limit
}
results = make([]TargetListing, 0, prealloc)
if !ascending {
slices.Reverse(base)
slices.Reverse(extra)
}
for len(results) < limit {
if len(extra) != 0 {
if !predicate(extra[0].Time) {
extra = extra[1:]
continue
}
if len(base) != 0 {
if base[0].Time.Before(extra[0].Time) == ascending {
results = append(results, base[0])
base = base[1:]
} else {
results = append(results, extra[0])
extra = extra[1:]
}
} else {
results = append(results, extra[0])
extra = extra[1:]
}
} else if len(base) != 0 {
results = append(results, base[0])
base = base[1:]
} else {
break
}
}
if !ascending {
slices.Reverse(results)
}
return
}
func SortCorrespondents(list []TargetListing) {
sort.Slice(list, func(i, j int) bool {
return list[i].Time.Before(list[j].Time)
})
}

View File

@ -7,18 +7,24 @@ import (
"bufio"
"fmt"
"os"
"runtime/debug"
"strconv"
"strings"
"time"
"github.com/oragono/oragono/irc/history"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/history"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/utils"
)
type CanDelete uint
const (
canDeleteAny CanDelete = iota // User is allowed to delete any message (for a given channel/PM)
canDeleteSelf // User is allowed to delete their own messages (ditto)
canDeleteNone // User is not allowed to delete any message (ditto)
)
const (
histservHelp = `HistServ provides commands related to history.`
histServMask = "HistServ!HistServ@localhost"
)
func histservEnabled(config *Config) bool {
@ -44,14 +50,13 @@ FORGET deletes all history messages sent by an account.`,
},
"delete": {
handler: histservDeleteHandler,
help: `Syntax: $bDELETE [target] <msgid>$b
help: `Syntax: $bDELETE <target> <msgid>$b
DELETE deletes an individual message by its msgid. The target is a channel
name or nickname; depending on the history implementation, this may or may not
be necessary to locate the message.`,
helpShort: `$bDELETE$b deletes an individual message by its msgid.`,
DELETE deletes an individual message by its msgid. The target is the channel
name. The msgid is the ID as can be found in the tags of that message.`,
helpShort: `$bDELETE$b deletes an individual message by its target and msgid.`,
enabled: histservEnabled,
minParams: 1,
minParams: 2,
maxParams: 2,
},
"export": {
@ -71,7 +76,7 @@ the request of the account holder.`,
help: `Syntax: $bPLAY <target> [limit]$b
PLAY plays back history messages, rendering them into direct messages from
HistServ. 'target' is a channel name (or 'me' for direct messages), and 'limit'
HistServ. 'target' is a channel name or nickname to query, and 'limit'
is a message count or a time duration. Note that message playback may be
incomplete or degraded, relative to direct playback from /HISTORY or
CHATHISTORY.`,
@ -83,81 +88,96 @@ CHATHISTORY.`,
}
)
// histNotice sends the client a notice from HistServ
func histNotice(rb *ResponseBuffer, text string) {
rb.Add(nil, histServMask, "NOTICE", rb.target.Nick(), text)
}
func histservForgetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func histservForgetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
accountName := server.accounts.AccountToAccountName(params[0])
if accountName == "" {
histNotice(rb, client.t("Could not look up account name, proceeding anyway"))
service.Notice(rb, client.t("Could not look up account name, proceeding anyway"))
accountName = params[0]
}
server.ForgetHistory(accountName)
histNotice(rb, fmt.Sprintf(client.t("Enqueued account %s for message deletion"), accountName))
service.Notice(rb, fmt.Sprintf(client.t("Enqueued account %s for message deletion"), accountName))
}
func histservDeleteHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
var target, msgid string
if len(params) == 1 {
msgid = params[0]
// Returns:
//
// 1. `canDeleteAny` if the client allowed to delete other users' messages from the target, ie.:
// - the client is a channel operator, or
// - the client is an operator with "history" capability
//
// 2. `canDeleteSelf` if the client is allowed to delete their own messages from the target
// 3. `canDeleteNone` otherwise
func deletionPolicy(server *Server, client *Client, target string) CanDelete {
isOper := client.HasRoleCapabs("history")
if isOper {
return canDeleteAny
} else {
target, msgid = params[0], params[1]
if server.Config().History.Retention.AllowIndividualDelete {
channel := server.channels.Get(target)
if channel != nil && channel.ClientIsAtLeast(client, modes.Operator) {
return canDeleteAny
} else {
return canDeleteSelf
}
} else {
return canDeleteNone
}
}
}
func histservDeleteHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
target, msgid := params[0], params[1] // Fix #1881 2 params are required
canDelete := deletionPolicy(server, client, target)
accountName := "*"
hasPrivs := client.HasRoleCapabs("history")
if !hasPrivs {
if canDelete == canDeleteNone {
service.Notice(rb, client.t("Insufficient privileges"))
return
} else if canDelete == canDeleteSelf {
accountName = client.AccountName()
if !(server.Config().History.Retention.AllowIndividualDelete && accountName != "*") {
histNotice(rb, client.t("Insufficient privileges"))
if accountName == "*" {
service.Notice(rb, client.t("Insufficient privileges"))
return
}
}
err := server.DeleteMessage(target, msgid, accountName)
if err == nil {
histNotice(rb, client.t("Successfully deleted message"))
service.Notice(rb, client.t("Successfully deleted message"))
} else {
if hasPrivs {
histNotice(rb, fmt.Sprintf(client.t("Error deleting message: %v"), err))
isOper := client.HasRoleCapabs("history")
if isOper {
service.Notice(rb, fmt.Sprintf(client.t("Error deleting message: %v"), err))
} else {
histNotice(rb, client.t("Could not delete message"))
service.Notice(rb, client.t("Could not delete message"))
}
}
}
func histservExportHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func histservExportHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
cfAccount, err := CasefoldName(params[0])
if err != nil {
histNotice(rb, client.t("Invalid account name"))
service.Notice(rb, client.t("Invalid account name"))
return
}
config := server.Config()
// don't include the account name in the filename because of escaping concerns
filename := fmt.Sprintf("%s-%s.json", utils.GenerateSecretToken(), time.Now().UTC().Format(IRCv3TimestampFormat))
filename := fmt.Sprintf("%s-%s.json", utils.GenerateSecretToken(), time.Now().UTC().Format(utils.IRCv3TimestampFormat))
pathname := config.getOutputPath(filename)
outfile, err := os.Create(pathname)
if err != nil {
histNotice(rb, fmt.Sprintf(client.t("Error opening export file: %v"), err))
service.Notice(rb, fmt.Sprintf(client.t("Error opening export file: %v"), err))
} else {
histNotice(rb, fmt.Sprintf(client.t("Started exporting data for account %[1]s to file %[2]s"), cfAccount, filename))
service.Notice(rb, fmt.Sprintf(client.t("Started exporting data for account %[1]s to file %[2]s"), cfAccount, filename))
}
go histservExportAndNotify(server, cfAccount, outfile, filename, client.Nick())
go histservExportAndNotify(service, server, cfAccount, outfile, filename, client.Nick())
}
func histservExportAndNotify(server *Server, cfAccount string, outfile *os.File, filename, alertNick string) {
defer func() {
if r := recover(); r != nil {
server.logger.Error("history",
fmt.Sprintf("Panic in history export routine: %v\n%s", r, debug.Stack()))
}
}()
func histservExportAndNotify(service *ircService, server *Server, cfAccount string, outfile *os.File, filename, alertNick string) {
defer server.HandlePanic(nil)
defer outfile.Close()
writer := bufio.NewWriter(outfile)
@ -167,19 +187,19 @@ func histservExportAndNotify(server *Server, cfAccount string, outfile *os.File,
client := server.clients.Get(alertNick)
if client != nil && client.HasRoleCapabs("history") {
client.Send(nil, histServMask, "NOTICE", client.Nick(), fmt.Sprintf(client.t("Data export for %[1]s completed and written to %[2]s"), cfAccount, filename))
client.Send(nil, service.prefix, "NOTICE", client.Nick(), fmt.Sprintf(client.t("Data export for %[1]s completed and written to %[2]s"), cfAccount, filename))
}
}
func histservPlayHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func histservPlayHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
items, _, err := easySelectHistory(server, client, params)
if err != nil {
histNotice(rb, client.t("Could not retrieve history"))
service.Notice(rb, client.t("Could not retrieve history"))
return
}
playMessage := func(timestamp time.Time, nick, message string) {
histNotice(rb, fmt.Sprintf("%s <%s> %s", timestamp.Format("15:04:05"), stripMaskFromNick(nick), message))
service.Notice(rb, fmt.Sprintf("%s <%s> %s", timestamp.Format("15:04:05"), NUHToNick(nick), message))
}
for _, item := range items {
@ -196,16 +216,12 @@ func histservPlayHandler(server *Server, client *Client, command string, params
}
}
histNotice(rb, client.t("End of history playback"))
service.Notice(rb, client.t("End of history playback"))
}
// handles parameter parsing and history queries for /HISTORY and /HISTSERV PLAY
func easySelectHistory(server *Server, client *Client, params []string) (items []history.Item, channel *Channel, err error) {
target := params[0]
if strings.ToLower(target) == "me" {
target = "*"
}
channel, sequence, err := server.GetHistorySequence(nil, client, target)
channel, sequence, err := server.GetHistorySequence(nil, client, params[0])
if sequence == nil || err != nil {
return nil, nil, errNoSuchChannel
@ -233,12 +249,12 @@ func easySelectHistory(server *Server, client *Client, params []string) (items [
}
if duration == 0 {
items, _, err = sequence.Between(history.Selector{}, history.Selector{}, limit)
items, err = sequence.Between(history.Selector{}, history.Selector{}, limit)
} else {
now := time.Now().UTC()
start := history.Selector{Time: now}
end := history.Selector{Time: now.Add(-duration)}
items, _, err = sequence.Between(start, end, limit)
items, err = sequence.Between(start, end, limit)
}
return
}

View File

@ -7,18 +7,16 @@ import (
"errors"
"fmt"
"regexp"
"time"
"github.com/goshuirc/irc-go/ircfmt"
"github.com/ergochat/irc-go/ircfmt"
"github.com/oragono/oragono/irc/sno"
"github.com/oragono/oragono/irc/utils"
"github.com/ergochat/ergo/irc/sno"
"github.com/ergochat/ergo/irc/utils"
)
const (
hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed
in place of your client's hostname/IP).`
hsNickMask = "HostServ!HostServ@localhost"
)
var (
@ -32,10 +30,6 @@ func hostservEnabled(config *Config) bool {
return config.Accounts.VHosts.Enabled
}
func hostservRequestsEnabled(config *Config) bool {
return config.Accounts.VHosts.Enabled && config.Accounts.VHosts.UserRequests.Enabled
}
var (
hostservCommands = map[string]*serviceCommand{
"on": {
@ -56,31 +50,20 @@ OFF disables your vhost, if you have one approved.`,
authRequired: true,
enabled: hostservEnabled,
},
"request": {
handler: hsRequestHandler,
help: `Syntax: $bREQUEST <vhost>$b
REQUEST requests that a new vhost by assigned to your account. The request must
then be approved by a server operator.`,
helpShort: `$bREQUEST$b requests a new vhost, pending operator approval.`,
authRequired: true,
enabled: hostservRequestsEnabled,
minParams: 1,
},
"status": {
handler: hsStatusHandler,
help: `Syntax: $bSTATUS [user]$b
STATUS displays your current vhost, if any, and the status of your most recent
request for a new one. A server operator can view someone else's status.`,
helpShort: `$bSTATUS$b shows your vhost and request status.`,
STATUS displays your current vhost, if any, and whether it is enabled or
disabled. A server operator can view someone else's status.`,
helpShort: `$bSTATUS$b shows your vhost status.`,
enabled: hostservEnabled,
},
"set": {
handler: hsSetHandler,
help: `Syntax: $bSET <user> <vhost>$b
SET sets a user's vhost, bypassing the request system.`,
SET sets a user's vhost.`,
helpShort: `$bSET$b sets a user's vhost.`,
capabs: []string{"vhosts"},
enabled: hostservEnabled,
@ -96,39 +79,6 @@ DEL deletes a user's vhost.`,
enabled: hostservEnabled,
minParams: 1,
},
"waiting": {
handler: hsWaitingHandler,
help: `Syntax: $bWAITING$b
WAITING shows a list of pending vhost requests, which can then be approved
or rejected.`,
helpShort: `$bWAITING$b shows a list of pending vhost requests.`,
capabs: []string{"vhosts"},
enabled: hostservEnabled,
},
"approve": {
handler: hsApproveHandler,
help: `Syntax: $bAPPROVE <user>$b
APPROVE approves a user's vhost request.`,
helpShort: `$bAPPROVE$b approves a user's vhost request.`,
capabs: []string{"vhosts"},
enabled: hostservEnabled,
minParams: 1,
},
"reject": {
handler: hsRejectHandler,
help: `Syntax: $bREJECT <user> [<reason>]$b
REJECT rejects a user's vhost request, optionally giving them a reason
for the rejection.`,
helpShort: `$bREJECT$b rejects a user's vhost request.`,
capabs: []string{"vhosts"},
enabled: hostservEnabled,
minParams: 1,
maxParams: 2,
unsplitFinalParam: true,
},
"setcloaksecret": {
handler: hsSetCloakSecretHandler,
help: `Syntax: $bSETCLOAKSECRET$b <secret> [code]
@ -145,25 +95,7 @@ display the necessary code.`,
}
)
// hsNotice sends the client a notice from HostServ
func hsNotice(rb *ResponseBuffer, text string) {
rb.Add(nil, hsNickMask, "NOTICE", rb.target.Nick(), text)
}
// hsNotifyChannel notifies the designated channel of new vhost activity
func hsNotifyChannel(server *Server, message string) {
chname := server.Config().Accounts.VHosts.UserRequests.Channel
channel := server.channels.Get(chname)
if channel == nil {
return
}
chname = channel.Name()
for _, client := range channel.Members() {
client.Send(nil, hsNickMask, "PRIVMSG", chname, message)
}
}
func hsOnOffHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func hsOnOffHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
enable := false
if command == "on" {
enable = true
@ -171,51 +103,28 @@ func hsOnOffHandler(server *Server, client *Client, command string, params []str
_, err := server.accounts.VHostSetEnabled(client, enable)
if err == errNoVhost {
hsNotice(rb, client.t(err.Error()))
service.Notice(rb, client.t(err.Error()))
} else if err != nil {
hsNotice(rb, client.t("An error occurred"))
service.Notice(rb, client.t("An error occurred"))
} else if enable {
hsNotice(rb, client.t("Successfully enabled your vhost"))
service.Notice(rb, client.t("Successfully enabled your vhost"))
} else {
hsNotice(rb, client.t("Successfully disabled your vhost"))
service.Notice(rb, client.t("Successfully disabled your vhost"))
}
}
func hsRequestHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
vhost := params[0]
if validateVhost(server, vhost, false) != nil {
hsNotice(rb, client.t("Invalid vhost"))
return
}
accountName := client.Account()
_, err := server.accounts.VHostRequest(accountName, vhost, time.Duration(server.Config().Accounts.VHosts.UserRequests.Cooldown))
if err != nil {
if throttled, ok := err.(*vhostThrottleExceeded); ok {
hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before making another request"), throttled.timeRemaining))
} else {
hsNotice(rb, client.t("An error occurred"))
}
} else {
hsNotice(rb, client.t("Your vhost request will be reviewed by an administrator"))
chanMsg := fmt.Sprintf("Account %s requests vhost %s", accountName, vhost)
hsNotifyChannel(server, chanMsg)
server.snomasks.Send(sno.LocalVhosts, chanMsg)
}
}
func hsStatusHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func hsStatusHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
var accountName string
if len(params) > 0 {
if !client.HasRoleCapabs("vhosts") {
hsNotice(rb, client.t("Command restricted"))
service.Notice(rb, client.t("Command restricted"))
return
}
accountName = params[0]
} else {
accountName = client.Account()
if accountName == "" {
hsNotice(rb, client.t("You're not logged into an account"))
service.Notice(rb, client.t("You're not logged into an account"))
return
}
}
@ -225,24 +134,17 @@ func hsStatusHandler(server *Server, client *Client, command string, params []st
if err != errAccountDoesNotExist {
server.logger.Warning("internal", "error loading account info", accountName, err.Error())
}
hsNotice(rb, client.t("No such account"))
service.Notice(rb, client.t("No such account"))
return
}
if account.VHost.ApprovedVHost != "" {
hsNotice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost))
service.Notice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost))
if !account.VHost.Enabled {
hsNotice(rb, client.t("This vhost is currently disabled, but can be enabled with /HS ON"))
service.Notice(rb, client.t("This vhost is currently disabled, but can be enabled with /HS ON"))
}
} else {
hsNotice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName))
}
if account.VHost.RequestedVHost != "" {
hsNotice(rb, fmt.Sprintf(client.t("A request is pending for vhost: %s"), account.VHost.RequestedVHost))
}
if account.VHost.RejectedVHost != "" {
hsNotice(rb, fmt.Sprintf(client.t("A request was previously made for vhost: %s"), account.VHost.RejectedVHost))
hsNotice(rb, fmt.Sprintf(client.t("It was rejected for reason: %s"), account.VHost.RejectionReason))
service.Notice(rb, fmt.Sprintf(client.t("Account %s has no vhost"), accountName))
}
}
@ -251,20 +153,21 @@ func validateVhost(server *Server, vhost string, oper bool) error {
if len(vhost) > config.Accounts.VHosts.MaxLength {
return errVHostTooLong
}
if !config.Accounts.VHosts.ValidRegexp.MatchString(vhost) {
if !config.Accounts.VHosts.validRegexp.MatchString(vhost) {
return errVHostBadCharacters
}
return nil
}
func hsSetHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func hsSetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
oper := client.Oper()
user := params[0]
var vhost string
if command == "set" {
vhost = params[1]
if validateVhost(server, vhost, true) != nil {
hsNotice(rb, client.t("Invalid vhost"))
service.Notice(rb, client.t("Invalid vhost"))
return
}
}
@ -272,72 +175,24 @@ func hsSetHandler(server *Server, client *Client, command string, params []strin
_, err := server.accounts.VHostSet(user, vhost)
if err != nil {
hsNotice(rb, client.t("An error occurred"))
service.Notice(rb, client.t("An error occurred"))
} else if vhost != "" {
hsNotice(rb, client.t("Successfully set vhost"))
service.Notice(rb, client.t("Successfully set vhost"))
server.snomasks.Send(sno.LocalVhosts, fmt.Sprintf("Operator %[1]s set vhost %[2]s on account %[3]s", oper.Name, vhost, user))
} else {
hsNotice(rb, client.t("Successfully cleared vhost"))
service.Notice(rb, client.t("Successfully cleared vhost"))
server.snomasks.Send(sno.LocalVhosts, fmt.Sprintf("Operator %[1]s cleared vhost on account %[2]s", oper.Name, user))
}
}
func hsWaitingHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
requests, total := server.accounts.VHostListRequests(10)
hsNotice(rb, fmt.Sprintf(client.t("There are %[1]d pending requests for vhosts (%[2]d displayed)"), total, len(requests)))
for i, request := range requests {
hsNotice(rb, fmt.Sprintf(client.t("%[1]d. User %[2]s requests vhost: %[3]s"), i+1, request.Account, request.RequestedVHost))
}
}
func hsApproveHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
user := params[0]
vhostInfo, err := server.accounts.VHostApprove(user)
if err != nil {
hsNotice(rb, client.t("An error occurred"))
} else {
hsNotice(rb, fmt.Sprintf(client.t("Successfully approved vhost request for %s"), user))
chanMsg := fmt.Sprintf("Oper %[1]s approved vhost %[2]s for account %[3]s", client.Nick(), vhostInfo.ApprovedVHost, user)
hsNotifyChannel(server, chanMsg)
server.snomasks.Send(sno.LocalVhosts, chanMsg)
for _, client := range server.accounts.AccountToClients(user) {
client.Send(nil, hsNickMask, "NOTICE", client.Nick(), client.t("Your vhost request was approved by an administrator"))
}
}
}
func hsRejectHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
var reason string
user := params[0]
if len(params) > 1 {
reason = params[1]
}
vhostInfo, err := server.accounts.VHostReject(user, reason)
if err != nil {
hsNotice(rb, client.t("An error occurred"))
} else {
hsNotice(rb, fmt.Sprintf(client.t("Successfully rejected vhost request for %s"), user))
chanMsg := fmt.Sprintf("Oper %s rejected vhost %s for account %s, with the reason: %v", client.Nick(), vhostInfo.RejectedVHost, user, reason)
hsNotifyChannel(server, chanMsg)
server.snomasks.Send(sno.LocalVhosts, chanMsg)
for _, client := range server.accounts.AccountToClients(user) {
if reason == "" {
client.Send(nil, hsNickMask, "NOTICE", client.Nick(), client.t("Your vhost request was rejected by an administrator"))
} else {
client.Send(nil, hsNickMask, "NOTICE", client.Nick(), fmt.Sprintf(client.t("Your vhost request was rejected by an administrator. The reason given was: %s"), reason))
}
}
}
}
func hsSetCloakSecretHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
func hsSetCloakSecretHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
secret := params[0]
expectedCode := utils.ConfirmationCode(secret, server.ctime)
if len(params) == 1 || params[1] != expectedCode {
hsNotice(rb, ircfmt.Unescape(client.t("$bWarning: changing the cloak secret will invalidate stored ban/invite/exception lists.$b")))
hsNotice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/HS SETCLOAKSECRET %s %s", secret, expectedCode)))
service.Notice(rb, ircfmt.Unescape(client.t("$bWarning: changing the cloak secret will invalidate stored ban/invite/exception lists.$b")))
service.Notice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/HS SETCLOAKSECRET %s %s", secret, expectedCode)))
return
}
StoreCloakSecret(server.store, secret)
hsNotice(rb, client.t("Rotated the cloak secret; you must rehash or restart the server for it to take effect"))
StoreCloakSecret(server.dstore, secret)
service.Notice(rb, client.t("Rotated the cloak secret; you must rehash or restart the server for it to take effect"))
}

View File

@ -1,133 +0,0 @@
// Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
import (
"time"
)
// BrbTimer is a timer on the client as a whole (not an individual session) for implementing
// the BRB command and related functionality (where a client can remain online without
// having any connected sessions).
type BrbState uint
const (
// BrbDisabled is the default state; the client will be disconnected if it has no sessions
BrbDisabled BrbState = iota
// BrbEnabled allows the client to remain online without sessions; if a timeout is
// reached, it will be removed
BrbEnabled
// BrbDead is the state of a client after its timeout has expired; it will be removed
// and therefore new sessions cannot be attached to it
BrbDead
)
type BrbTimer struct {
// XXX we use client.stateMutex for synchronization, so we can atomically test
// conditions that use both brbTimer.state and client.sessions. This code
// is tightly coupled with the rest of Client.
client *Client
state BrbState
brbAt time.Time
duration time.Duration
timer *time.Timer
}
func (bt *BrbTimer) Initialize(client *Client) {
bt.client = client
}
// attempts to enable BRB for a client, returns whether it succeeded
func (bt *BrbTimer) Enable() (success bool, duration time.Duration) {
// TODO make this configurable
duration = ResumeableTotalTimeout
bt.client.stateMutex.Lock()
defer bt.client.stateMutex.Unlock()
if !bt.client.registered || bt.client.alwaysOn || bt.client.resumeID == "" {
return
}
switch bt.state {
case BrbDisabled, BrbEnabled:
bt.state = BrbEnabled
bt.duration = duration
bt.resetTimeout()
// only track the earliest BRB, if multiple sessions are BRB'ing at once
// TODO(#524) this is inaccurate in case of an auto-BRB
if bt.brbAt.IsZero() {
bt.brbAt = time.Now().UTC()
}
success = true
default:
// BrbDead
success = false
}
return
}
// turns off BRB for a client and stops the timer; used on resume and during
// client teardown
func (bt *BrbTimer) Disable() (brbAt time.Time) {
bt.client.stateMutex.Lock()
defer bt.client.stateMutex.Unlock()
if bt.state == BrbEnabled {
bt.state = BrbDisabled
brbAt = bt.brbAt
bt.brbAt = time.Time{}
}
bt.resetTimeout()
return
}
func (bt *BrbTimer) resetTimeout() {
if bt.timer != nil {
bt.timer.Stop()
}
if bt.state != BrbEnabled {
return
}
if bt.timer == nil {
bt.timer = time.AfterFunc(bt.duration, bt.processTimeout)
} else {
bt.timer.Reset(bt.duration)
}
}
func (bt *BrbTimer) processTimeout() {
dead := false
defer func() {
if dead {
bt.client.Quit(bt.client.AwayMessage(), nil)
bt.client.destroy(nil)
}
}()
bt.client.stateMutex.Lock()
defer bt.client.stateMutex.Unlock()
if bt.client.alwaysOn {
return
}
switch bt.state {
case BrbDisabled, BrbEnabled:
if len(bt.client.sessions) == 0 {
// client never returned, quit them
bt.state = BrbDead
dead = true
} else {
// client resumed, reattached, or has another active session
bt.state = BrbDisabled
bt.brbAt = time.Time{}
}
case BrbDead:
dead = true // shouldn't be possible but whatever
}
bt.resetTimeout()
}

Some files were not shown because too many files have changed in this diff Show More