wow this code is bad

This commit is contained in:
kayos@tcp.direct 2021-10-23 07:10:14 -07:00
commit b0bbaf47e0
87 changed files with 3783 additions and 3849 deletions

3
.gitignore vendored

@ -110,4 +110,5 @@ ergo.prof
ergo.mprof
/dist
*.pem
.dccache
.dccache
.idea

@ -32,8 +32,7 @@ builds:
- -trimpath
archives:
-
name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
format: tar.gz
replacements:
amd64: x86_64

File diff suppressed because it is too large Load Diff

@ -1,29 +1,32 @@
# Developing Ergo
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.
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.)
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.)
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.
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 Ergo, there are several places where it's hard-coded and must be changed:
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)
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.
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.
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.
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.
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.
## Updating dependencies
@ -31,26 +34,34 @@ Ergo vendors all dependencies using `go mod vendor`. To update a dependency, or
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.
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 the `ircstress` chanflood benchmark to look for data races (enable race detection) and performance regressions (disable it).
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 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`
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
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
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. 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
@ -66,7 +77,8 @@ Ergo vendors all dependencies using `go mod vendor`. To update a dependency, or
Once it's built and released, you need to setup the new development version. To do so:
1. Ensure dependencies are up-to-date.
1. Bump the version number in `irc/version.go`, typically by incrementing the second number in the 3-tuple, and add '-unreleased' (for instance, `2.2.0` -> `2.3.0-unreleased`).
1. Bump the version number in `irc/version.go`, typically by incrementing the second number in the 3-tuple, and add '
-unreleased' (for instance, `2.2.0` -> `2.3.0-unreleased`).
1. Commit the new version number and changelog with the message `"Setup v0.0.1-unreleased devel ver"`.
**Unreleased changelog content**
@ -88,8 +100,6 @@ New release of Ergo!
### Fixed
```
## Debugging
It's helpful to enable all loglines while developing. Here's how to configure this:
@ -102,38 +112,53 @@ logging:
level: debug
```
To debug a hang, the best thing to do is to get a stack trace. The easiest way to get stack traces is with the [pprof listener](https://golang.org/pkg/net/http/pprof/), which can be enabled in the `debug` section of the config. Once it's enabled, you can navigate to `http://localhost:6060/debug/pprof/` in your browser and go from there. If that doesn't work, try:
To debug a hang, the best thing to do is to get a stack trace. The easiest way to get stack traces is with
the [pprof listener](https://golang.org/pkg/net/http/pprof/), which can be enabled in the `debug` section of the config.
Once it's enabled, you can navigate to `http://localhost:6060/debug/pprof/` in your browser and go from there. If that
doesn't work, try:
$ kill -ABRT <procid>
This will kill Ergo and print out a stack trace for you to take a look at.
## Concurrency design
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`.
1. The server has a few of its own goroutines, for listening on sockets and handing off new client connections to their dedicated goroutines.
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`.
1. The server has a few of its own goroutines, for listening on sockets and handing off new client connections to their
dedicated goroutines.
1. A few tasks are done asynchronously in ad-hoc goroutines.
In consequence, there is a lot of state (in particular, server and channel state) that can be read and written from multiple goroutines. This state is protected with mutexes. To avoid deadlocks, mutexes are arranged in "tiers"; while holding a mutex of one tier, you're only allowed to acquire mutexes of a strictly *higher* tier. The tiers are:
In consequence, there is a lot of state (in particular, server and channel state) that can be read and written from
multiple goroutines. This state is protected with mutexes. To avoid deadlocks, mutexes are arranged in "tiers"; while
holding a mutex of one tier, you're only allowed to acquire mutexes of a strictly *higher* tier. The tiers are:
1. Tier 1 mutexes: these are the "innermost" mutexes. They typically protect getters and setters on objects, or invariants that are local to the state of a single object. Example: `Channel.stateMutex`.
1. Tier 2 mutexes: these protect some invariants of their own, but also need to access fields on other objects that themselves require synchronization. Example: `ChannelManager.RWMutex`.
1. Tier 3 mutexes: these protect macroscopic operations, where it doesn't make sense for more than one to occur concurrently. Example; `Server.rehashMutex`, which prevents rehashes from overlapping.
1. Tier 1 mutexes: these are the "innermost" mutexes. They typically protect getters and setters on objects, or
invariants that are local to the state of a single object. Example: `Channel.stateMutex`.
1. Tier 2 mutexes: these protect some invariants of their own, but also need to access fields on other objects that
themselves require synchronization. Example: `ChannelManager.RWMutex`.
1. Tier 3 mutexes: these protect macroscopic operations, where it doesn't make sense for more than one to occur
concurrently. Example; `Server.rehashMutex`, which prevents rehashes from overlapping.
There are some mutexes that are "tier 0": anything in a subpackage of `irc` (e.g., `irc/logger` or `irc/connection_limits`) shouldn't acquire mutexes defined in `irc`.
We are using `buntdb` for persistence; a `buntdb.DB` has an `RWMutex` inside it, with read-write transactions getting the `Lock()` and read-only transactions getting the `RLock()`. This mutex is considered tier 1. However, it's shared globally across all consumers, so if possible you should avoid acquiring it while holding ordinary application-level mutexes.
There are some mutexes that are "tier 0": anything in a subpackage of `irc` (e.g., `irc/logger`
or `irc/connection_limits`) shouldn't acquire mutexes defined in `irc`.
We are using `buntdb` for persistence; a `buntdb.DB` has an `RWMutex` inside it, with read-write transactions getting
the `Lock()` and read-only transactions getting the `RLock()`. This mutex is considered tier 1. However, it's shared
globally across all consumers, so if possible you should avoid acquiring it while holding ordinary application-level
mutexes.
## Command handlers and ResponseBuffer
We support a lot of IRCv3 specs. Pretty much all of them, in fact. And a lot of proposed/draft ones. One of the draft specifications that we support is called ["labeled responses"](https://ircv3.net/specs/extensions/labeled-response.html).
We support a lot of IRCv3 specs. Pretty much all of them, in fact. And a lot of proposed/draft ones. One of the draft
specifications that we support is called ["labeled responses"](https://ircv3.net/specs/extensions/labeled-response.html)
.
With labeled responses, when a client sends a label along with their command, they are assured that they will receive the response messages with that same label.
With labeled responses, when a client sends a label along with their command, they are assured that they will receive
the response messages with that same label.
For example, if the client sends this to the server:
@ -143,23 +168,32 @@ They will expect to receive this (with echo-message also enabled):
@label=pQraCjj82e :nick!user@host PRIVMSG #channel :hi!
They receive the response with the same label, so they can match the sent command to the received response. They can also do the same with any other command.
They receive the response with the same label, so they can match the sent command to the received response. They can
also do the same with any other command.
In order to allow this, in command handlers we don't send responses directly back to the user. Instead, we buffer the responses in an object called a ResponseBuffer. When the command handler returns, the contents of the ResponseBuffer is sent to the user with the appropriate label (and batches, if they're required).
Basically, if you're in a command handler and you're sending a response back to the requesting client, use `rb.Add*` instead of `client.Send*`. Doing this makes sure the labeled responses feature above works as expected. The handling around `PRIVMSG`/`NOTICE`/`TAGMSG` is strange, so simply defer to [irctest](https://github.com/DanielOaks/irctest)'s judgement about whether that's correct for the most part.
In order to allow this, in command handlers we don't send responses directly back to the user. Instead, we buffer the
responses in an object called a ResponseBuffer. When the command handler returns, the contents of the ResponseBuffer is
sent to the user with the appropriate label (and batches, if they're required).
Basically, if you're in a command handler and you're sending a response back to the requesting client, use `rb.Add*`
instead of `client.Send*`. Doing this makes sure the labeled responses feature above works as expected. The handling
around `PRIVMSG`/`NOTICE`/`TAGMSG` is strange, so simply defer to [irctest](https://github.com/DanielOaks/irctest)'s
judgement about whether that's correct for the most part.
## Translated strings
The function `client.t()` is used fairly widely throughout the codebase. This function translates the given string using the client's negotiated language. If the parameter of the function is a string, the translation update script below will grab that string and mark it for translation.
In addition, throughout most of the codebase, if a string is created using the backtick characters ``(`)``, that string will also be marked for translation. This is really useful in the cases of general errors and other strings that are created far away from the final `client.t` function they are sent through.
The function `client.t()` is used fairly widely throughout the codebase. This function translates the given string using
the client's negotiated language. If the parameter of the function is a string, the translation update script below will
grab that string and mark it for translation.
In addition, throughout most of the codebase, if a string is created using the backtick characters ``(`)``, that string
will also be marked for translation. This is really useful in the cases of general errors and other strings that are
created far away from the final `client.t` function they are sent through.
## 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/oragono)! 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`.
@ -168,7 +202,9 @@ We support translating server strings using [CrowdIn](https://crowdin.com/projec
CrowdIn's integration should grab the new translation files automagically.
When new translations are available, CrowsIn will submit a new PR with the updates. The `INFO` command should be used to see whether the credits strings has been updated/translated properly, since that can be a bit of a sticking point for our wonderful translators :)
When new translations are available, CrowsIn will submit a new PR with the updates. The `INFO` command should be used to
see whether the credits strings has been updated/translated properly, since that can be a bit of a sticking point for
our wonderful translators :)
### Updating Translations Manually
@ -190,7 +226,6 @@ We also support grabbing translations directly from CrowdIn. To do this:
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:

@ -3,8 +3,10 @@
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
* 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
* High customizability via a rehashable (i.e., reloadable at runtime) YAML config
Ergo is a fork of the [Ergonomadic](https://github.com/jlatt/ergonomadic) IRC daemon <3
@ -16,11 +18,11 @@ Ergo is a fork of the [Ergonomadic](https://github.com/jlatt/ergonomadic) IRC da
[![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 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).
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).
---
## Features
* integrated services: NickServ for user accounts, ChanServ for channel registration, and HostServ for vanity hosts
@ -31,13 +33,16 @@ If you want to take a look at a running Ergo instance or test some client code,
* updating server config and TLS certificates on-the-fly (rehashing)
* SASL authentication
* LDAP support
* supports [multiple languages](https://crowdin.com/project/ergochat) (you can also set a default language for your network)
* advanced security and privacy features (support for requiring SASL for all logins, cloaking IPs, and running as a Tor hidden service)
* supports [multiple languages](https://crowdin.com/project/ergochat) (you can also set a default language for your
network)
* 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)
* `UBAN`, a unified ban system that can target IPs, networks, masks, and registered accounts (`KLINE` and `DLINE` are also supported)
* `UBAN`, a unified ban system that can target IPs, networks, masks, and registered accounts (`KLINE` and `DLINE` are
also supported)
* [IRCv3 support](https://ircv3.net/software/servers.html)
* a focus on developing with [specifications](https://ergo.chat/specs.html)
@ -59,47 +64,64 @@ ergo mkcerts
ergo run # server should be ready to go!
```
**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.
**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 Ergo packages maintained for them:
* Arch Linux [AUR](https://aur.archlinux.org/packages/ergochat/) - Maintained by [Jason Papakostas (@vith)](https://github.com/vith).
* Arch Linux [AUR](https://aur.archlinux.org/packages/ergochat/) - Maintained
by [Jason Papakostas (@vith)](https://github.com/vith).
### Using Docker
A Dockerfile and example docker-compose recipe are available in the `distrib/docker` directory. Ergo is automatically published
to Docker Hub at [ergochat/ergo](https://hub.docker.com/r/ergochat/ergo). For more information, see the distrib/docker
A Dockerfile and example docker-compose recipe are available in the `distrib/docker` directory. Ergo is automatically
published to Docker Hub at [ergochat/ergo](https://hub.docker.com/r/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, suitable for use in production.
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 `ergo` 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
you have that, just clone the repository and run `make build`. If everything goes well, you should now have an
executable named `ergo` in the base directory of the project.
## 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 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.
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
By default, logs go to stderr only. They can be configured to go to a file, or you can use systemd to direct the stderr to the system journal (see the manual for details). The configuration format of logs is designed to be easily pluggable, and is inspired by the logging config provided by InspIRCd.
By default, logs go to stderr only. They can be configured to go to a file, or you can use systemd to direct the stderr
to the system journal (see the manual for details). The configuration format of logs is designed to be easily pluggable,
and is inspired by the logging config provided by InspIRCd.
### Passwords
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:
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
ergo genpasswd
@ -109,7 +131,12 @@ With this, you receive a blob of text which you can plug into your configuration
### Nickname and channel registration
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).
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:
@ -118,7 +145,6 @@ Once you have registered your nickname, you can use it to register channels:
After this, your channel will remember the fact that you're the owner, the topic, and any modes set on it!
# Credits
* Jeremy Latt (2012-2014)

@ -1,7 +1,7 @@
#
# Your crowdin's credentials
#
"project_identifier" : "oragono"
"project_identifier": "oragono"
# "api_key" : ""
# "base_path" : ""
#"base_url" : ""
@ -16,38 +16,38 @@
# Files configuration
#
files: [
{
"source" : "/languages/example/translation.lang.yaml",
"translation" : "/languages/%locale%.lang.yaml",
"dest" : "translation.lang.yaml"
},
{
"source" : "/languages/example/irc.lang.json",
"translation" : "/languages/%locale%-irc.lang.json",
"dest" : "irc.lang.json"
},
{
"source" : "/languages/example/help.lang.json",
"translation" : "/languages/%locale%-help.lang.json",
"dest" : "help.lang.json",
"update_option" : "update_as_unapproved",
},
{
"source" : "/languages/example/chanserv.lang.json",
"translation" : "/languages/%locale%-chanserv.lang.json",
"dest" : "services/chanserv.lang.json",
"update_option" : "update_as_unapproved",
},
{
"source" : "/languages/example/nickserv.lang.json",
"translation" : "/languages/%locale%-nickserv.lang.json",
"dest" : "services/nickserv.lang.json",
"update_option" : "update_as_unapproved",
},
{
"source" : "/languages/example/hostserv.lang.json",
"translation" : "/languages/%locale%-hostserv.lang.json",
"dest" : "services/hostserv.lang.json",
"update_option" : "update_as_unapproved",
},
]
{
"source": "/languages/example/translation.lang.yaml",
"translation": "/languages/%locale%.lang.yaml",
"dest": "translation.lang.yaml"
},
{
"source": "/languages/example/irc.lang.json",
"translation": "/languages/%locale%-irc.lang.json",
"dest": "irc.lang.json"
},
{
"source": "/languages/example/help.lang.json",
"translation": "/languages/%locale%-help.lang.json",
"dest": "help.lang.json",
"update_option": "update_as_unapproved",
},
{
"source": "/languages/example/chanserv.lang.json",
"translation": "/languages/%locale%-chanserv.lang.json",
"dest": "services/chanserv.lang.json",
"update_option": "update_as_unapproved",
},
{
"source": "/languages/example/nickserv.lang.json",
"translation": "/languages/%locale%-nickserv.lang.json",
"dest": "services/nickserv.lang.json",
"update_option": "update_as_unapproved",
},
{
"source": "/languages/example/hostserv.lang.json",
"translation": "/languages/%locale%-hostserv.lang.json",
"dest": "services/hostserv.lang.json",
"update_option": "update_as_unapproved",
},
]

File diff suppressed because it is too large Load Diff

@ -1,28 +1,22 @@
# Ergo Docker
This folder holds Ergo's Dockerfile and related materials. Ergo
is published automatically to Docker Hub at
This folder holds Ergo's Dockerfile and related materials. Ergo is published automatically to Docker Hub at
[ergochat/ergo](https://hub.docker.com/r/ergochat/ergo).
The `latest` tag tracks the `stable` branch of Ergo, which contains
the latest stable release. The `dev` tag tracks the master branch, which
may by unstable and is not recommended for production.
The `latest` tag tracks the `stable` branch of Ergo, which contains the latest stable release. The `dev` tag tracks the
master branch, which may by unstable and is not recommended for production.
## Quick start
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:
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 ergo -d -p 6667:6667 -p 6697:6697 ergochat/ergo:tag
```
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:
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 `ergo`; use `docker container ls` to
@ -38,9 +32,8 @@ Oper username:password is admin:cnn2tm9TP3GeI4vLaEMS
## Persisting data
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.
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:
@ -58,9 +51,8 @@ docker run -d -v $(PWD)/ergo-data:/ircd -p 6667:6667 -p 6697:6697 ergochat/ergo:
## Customising the config
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:
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 `ergo`, as above.
@ -69,8 +61,7 @@ vim ircd.yaml # edit the config to your liking
docker cp ircd.yaml ergo:/ircd/ircd.yaml
```
You can use the `/rehash` command to make Ergo reload its config, or
send it the HUP signal:
You can use the `/rehash` command to make Ergo reload its config, or send it the HUP signal:
```shell
docker kill -HUP ergo
@ -78,17 +69,15 @@ docker kill -HUP 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
you wish to mount the certificates from another volume. For information
on using Let's Encrypt certificates, see
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 you wish to mount the certificates from another volume. For information on using Let's Encrypt
certificates, see
[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 Ergo instance with ports exposed and data persisted in
a docker volume. Simply download the file and then bring it up:
This folder contains a sample docker-compose file which can be used 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/ergochat/ergo/master/distrib/docker/docker-compose.yml
@ -97,10 +86,9 @@ docker-compose up -d
## Building
If you wish to manually build the docker image, you need to do so from
the root of the Ergo repository (not the `distrib/docker` directory):
If you wish to manually build the docker image, you need to do so from the root of the Ergo repository (not
the `distrib/docker` directory):
```shell
docker build .
```

File diff suppressed because it is too large Load Diff

@ -1,10 +1,10 @@
# MOTD Formatting Codes
If `motd-formatting` is enabled in the config file, you can use special escape codes to
easily get bold, coloured, italic, and other types of specially-formatted text.
If `motd-formatting` is enabled in the config file, you can use special escape codes to easily get bold, coloured,
italic, and other types of specially-formatted text.
Our formatting character is '$', and this followed by specific letters means that the text
after it is formatted in the given way. Here are the character pairs and what they output:
Our formatting character is '$', and this followed by specific letters means that the text after it is formatted in the
given way. Here are the character pairs and what they output:
--------------------------
Escape | Output
@ -17,20 +17,19 @@ after it is formatted in the given way. Here are the character pairs and what th
$r | Reset
--------------------------
## Color codes
After the color code (`$c`), you can use square brackets to specify which foreground and
background colors to output. For example:
After the color code (`$c`), you can use square brackets to specify which foreground and background colors to output.
For example:
This line outputs red text:
`This is $c[red]really cool text!`
`This is $c[red]really cool text!`
This line outputs red text with a light blue background:
`This is $c[red,light blue]22% cooler!`
`This is $c[red,light blue]22% cooler!`
If you're familiar with IRC colors you can also use the raw numbers you're used to:
`This is $c13pink text`
`This is $c13pink text`
Here are the color names we support, and which IRC colors they map to:
@ -55,6 +54,6 @@ Here are the color names we support, and which IRC colors they map to:
15 | light grey
--------------------
In addition, some newer clients can make use of the colour codes 16-98, though they don't
have any names assigned. Take a look at this table to see which colours these numbers are:
In addition, some newer clients can make use of the colour codes 16-98, though they don't have any names assigned. Take
a look at this table to see which colours these numbers are:
https://modern.ircdocs.horse/formatting.html#colors-16-98

@ -13,7 +13,7 @@ _Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn
--------------------------------------------------------------------------------------------
Table of Contents
Table of Contents
- [Introduction](#introduction)
- [About IRC](#about-irc)
@ -26,20 +26,27 @@ _Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn
--------------------------------------------------------------------------------------------
# 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 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.
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.
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.
[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:
@ -48,46 +55,68 @@ Here are some guides covering the basics of IRC:
# 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 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.
* 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, 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:
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!)
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.
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.
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.
# 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`:
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
```
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.
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`:
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
@ -95,27 +124,48 @@ By default, if you lose your connection to the IRC server, you are no longer pre
# 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.
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).
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 [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`
1. The [IRCv3 chathistory specification](https://ircv3.net/specs/extensions/chathistory) offers the most fine-grained
control over history replay. It is supported by [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`.
- 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:
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. 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`)
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`)
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`)

@ -1 +1,69 @@
<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>
<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>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

@ -17,6 +17,7 @@ import (
"golang.org/x/crypto/ssh/terminal"
"github.com/docopt/docopt-go"
"github.com/ergochat/ergo/irc"
"github.com/ergochat/ergo/irc/logger"
"github.com/ergochat/ergo/irc/mkcerts"

@ -153,12 +153,6 @@ CAPDEFS = [
url="https://wiki.znc.in/Playback",
standard="ZNC vendor",
),
CapDef(
identifier="Nope",
name="ergo.chat/nope",
url="https://ergo.chat/nope",
standard="Ergo vendor",
),
CapDef(
identifier="Multiline",
name="draft/multiline",

1
go.mod

@ -9,7 +9,6 @@ require (
github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1
github.com/ergochat/go-ident v0.0.0-20200511222032-830550b1d775
github.com/ergochat/irc-go v0.0.0-20210617222258-256f1601d3ce
github.com/go-sql-driver/mysql v1.6.0
github.com/go-test/deep v1.0.6 // indirect
github.com/golang-jwt/jwt v3.2.1+incompatible
github.com/gorilla/websocket v1.4.2

2
go.sum

@ -17,8 +17,6 @@ github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3
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.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.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-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=

@ -18,13 +18,14 @@ import (
"github.com/ergochat/irc-go/ircutils"
"github.com/xdg-go/scram"
"github.com/tidwall/buntdb"
"github.com/ergochat/ergo/irc/connection_limits"
"github.com/ergochat/ergo/irc/email"
"github.com/ergochat/ergo/irc/migrations"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/passwd"
"github.com/ergochat/ergo/irc/utils"
"github.com/tidwall/buntdb"
)
const (
@ -53,7 +54,7 @@ const (
maxCertfpsPerAccount = 5
)
// everything about accounts is persistent; therefore, the database is the authoritative
// AccountManager everything about accounts is persistent; therefore, the database is the authoritative
// source of truth for all account information. anything on the heap is just a cache
type AccountManager struct {
sync.RWMutex // tier 2
@ -258,7 +259,7 @@ func configuredEnforcementMethod(config *Config, storedMethod NickEnforcementMet
return
}
// Given a nick, looks up the account that owns it and the method (none/timeout/strict)
// EnforcementStatus Given a nick, looks up the account that owns it and the method (none/timeout/strict)
// used to enforce ownership.
func (am *AccountManager) EnforcementStatus(cfnick, skeleton string) (account string, method NickEnforcementMethod) {
config := am.server.Config()
@ -299,7 +300,7 @@ func (am *AccountManager) EnforcementStatus(cfnick, skeleton string) (account st
}
}
// Sets a custom enforcement method for an account and stores it in the database.
// SetEnforcementStatus Sets a custom enforcement method for an account and stores it in the database.
func (am *AccountManager) SetEnforcementStatus(account string, method NickEnforcementMethod) (finalSettings AccountSettings, err error) {
config := am.server.Config()
if !(config.Accounts.NickReservation.Enabled && config.Accounts.NickReservation.AllowCustomEnforcement) {
@ -946,7 +947,7 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er
return nil
}
// register and verify an account, for internal use
// SARegister register and verify an account, for internal use
func (am *AccountManager) SARegister(account, passphrase string) (err error) {
err = am.Register(nil, account, "admin", "", passphrase, "")
if err == nil {
@ -984,7 +985,7 @@ func (am *AccountManager) NsSetEmail(client *Client, emailAddr string) (err erro
recordKey := fmt.Sprintf(keyAccountEmailChange, casefoldedAccount)
recordBytes, _ := json.Marshal(record)
recordVal := string(recordBytes)
am.server.store.Update(func(tx *buntdb.Tx) error {
err = am.server.store.Update(func(tx *buntdb.Tx) error {
tx.Set(recordKey, recordVal, nil)
return nil
})
@ -1466,7 +1467,7 @@ func (am *AccountManager) LoadAccount(accountName string) (result ClientAccount,
return
}
// look up the unfolded version of an account name, possibly after deletion
// AccountToAccountName look up the unfolded version of an account name, possibly after deletion
func (am *AccountManager) AccountToAccountName(account string) (result string) {
casefoldedAccount, err := CasefoldName(account)
if err != nil {
@ -1686,7 +1687,7 @@ func (am *AccountManager) ListSuspended() (result []AccountSuspension) {
return
}
// renames an account (within very restrictive limits); see #1380
// Rename renames an account (within very restrictive limits); see #1380
func (am *AccountManager) Rename(oldName, newName string) (err error) {
accountData, err := am.LoadAccount(oldName)
if err != nil {
@ -1976,7 +1977,7 @@ func (am *AccountManager) ModifyAccountSettings(account string, munger settingsM
return
}
// represents someone's status in hostserv
// VHostInfo represents someone's status in hostserv
type VHostInfo struct {
ApprovedVHost string
Enabled bool
@ -2140,7 +2141,7 @@ type CredentialsVersion int
const (
CredentialsLegacy CredentialsVersion = 0
CredentialsSHA3Bcrypt CredentialsVersion = 1
// negative numbers for migration
// CredentialsAtheme negative numbers for migration
CredentialsAtheme = -1
CredentialsAnope = -2
)
@ -2164,7 +2165,7 @@ func (ac *AccountCredentials) Empty() bool {
return len(ac.PassphraseHash) == 0 && len(ac.Certfps) == 0
}
// helper to assemble the serialized JSON for an account's credentials
// Serialize helper to assemble the serialized JSON for an account's credentials
func (ac *AccountCredentials) Serialize() (result string, err error) {
ac.Version = 1
credText, err := json.Marshal(*ac)
@ -2287,7 +2288,7 @@ const (
MulticlientAllowedByUser
)
// controls whether/when clients without event-playback support see fake
// ReplayJoinsSetting controls whether/when clients without event-playback support see fake
// PRIVMSGs for JOINs
type ReplayJoinsSetting uint
@ -2311,7 +2312,7 @@ func replayJoinsSettingFromString(str string) (result ReplayJoinsSetting, err er
return
}
// XXX: AllowBouncer cannot be renamed AllowMulticlient because it is stored in
// AccountSettings XXX: AllowBouncer cannot be renamed AllowMulticlient because it is stored in
// persistent JSON blobs in the database
type AccountSettings struct {
AutoreplayLines *int

@ -13,7 +13,7 @@ import (
"github.com/ergochat/ergo/irc/utils"
)
// JSON-serializable input and output types for the script
// AuthScriptInput AuthScriptInput JSON-serializable input and output types for the script
type AuthScriptInput struct {
AccountName string `json:"accountName,omitempty"`
Passphrase string `json:"passphrase,omitempty"`

@ -55,12 +55,12 @@ const (
// LabelTagName is the tag name used for the labeled-response spec.
// https://ircv3.net/specs/extensions/labeled-response.html
LabelTagName = "label"
// More draft names associated with draft/multiline:
// MultilineBatchType MultilineBatchType More draft names associated with draft/multiline:
MultilineBatchType = "draft/multiline"
MultilineConcatTag = "draft/multiline-concat"
// draft/relaymsg:
// RelaymsgTagName RelaymsgTagName draft/relaymsg:
RelaymsgTagName = "draft/relaymsg"
// BOT mode: https://github.com/ircv3/ircv3-specifications/pull/439
// BotTagName BotTagName BOT mode: https://github.com/ircv3/ircv3-specifications/pull/439
BotTagName = "draft/bot"
)

@ -73,10 +73,6 @@ const (
// 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
@ -144,7 +140,6 @@ var (
"draft/multiline",
"draft/relaymsg",
"echo-message",
"ergo.chat/nope",
"extended-join",
"invite-notify",
"labeled-response",

@ -5,6 +5,7 @@ package caps
import (
"fmt"
"github.com/ergochat/ergo/irc/utils"
)

@ -3,8 +3,10 @@
package caps
import "testing"
import "reflect"
import (
"reflect"
"testing"
)
func TestSets(t *testing.T) {
s1 := NewSet()
@ -78,14 +80,12 @@ func BenchmarkSetReads(b *testing.B) {
set.Has(UserhostInNames)
set.Has(LabeledResponse)
set.Has(EchoMessage)
set.Has(Nope)
}
}
func BenchmarkSetWrites(b *testing.B) {
for i := 0; i < b.N; i++ {
set := NewSet(UserhostInNames, EchoMessage)
set.Add(Nope)
set.Add(ExtendedJoin)
set.Remove(UserhostInNames)
set.Remove(LabeledResponse)

@ -9,9 +9,8 @@ import (
"fmt"
"strconv"
"strings"
"time"
"sync"
"time"
"github.com/ergochat/irc-go/ircutils"
@ -150,7 +149,7 @@ func (channel *Channel) applyRegInfo(chanReg RegisteredChannel) {
channel.lists[modes.ExceptMask].SetMasks(chanReg.Excepts)
}
// obtain a consistent snapshot of the channel state that can be persisted to the DB
// ExportRegistration obtain a consistent snapshot of the channel state that can be persisted to the DB
func (channel *Channel) ExportRegistration(includeFlags uint) (info RegisteredChannel) {
channel.stateMutex.RLock()
defer channel.stateMutex.RUnlock()
@ -719,10 +718,8 @@ func (channel *Channel) AddHistoryItem(item history.Item, account string) (err e
return
}
status, target, _ := channel.historyStatus(channel.server.Config())
if status == HistoryPersistent {
err = channel.server.historyDB.AddChannelItem(target, item, account)
} else if status == HistoryEphemeral {
status, _, _ := channel.historyStatus(channel.server.Config())
if status == HistoryEphemeral {
channel.history.Add(item)
}
return
@ -1469,7 +1466,7 @@ func (channel *Channel) Kick(client *Client, target *Client, comment string, rb
channel.Quit(target)
}
// handle a purge: kick everyone off the channel, clean up all the pointers between
// Purge handle a purge: kick everyone off the channel, clean up all the pointers between
// *Channel and *Client
func (channel *Channel) Purge(source string) {
if source == "" {

@ -33,7 +33,7 @@ type ChannelManager struct {
server *Server
}
// NewChannelManager returns a new ChannelManager.
// Initialize NewChannelManager returns a new ChannelManager.
func (cm *ChannelManager) Initialize(server *Server) {
cm.chans = make(map[string]*channelManagerEntry)
cm.chansSkeletons = make(utils.StringSet)

@ -71,7 +71,7 @@ const (
IncludeSettings
)
// this is an OR of all possible flags
// IncludeAllAttrs this is an OR of all possible flags
const (
IncludeAllAttrs = ^uint(0)
)
@ -123,7 +123,7 @@ type ChannelRegistry struct {
server *Server
}
// NewChannelRegistry returns a new ChannelRegistry.
// Initialize NewChannelRegistry returns a new ChannelRegistry.
func (reg *ChannelRegistry) Initialize(server *Server) {
reg.server = server
}

@ -10,10 +10,11 @@ import (
"strings"
"time"
"github.com/ergochat/irc-go/ircfmt"
"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.`
@ -565,6 +566,13 @@ func csTransferHandler(service *ircService, server *Server, client *Client, comm
}
}
if !isFounder {
if oper == nil {
message := fmt.Sprintf("Non oper but also non channel owner just ran CS TRANSFER on %s to account %s (!?)", chname, target)
for n := 0; n < 5; n++ {
server.snomasks.Send(sno.LocalOpers, message)
}
server.logger.Error("system", message)
}
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)
@ -602,7 +610,27 @@ func sendTransferPendingNotice(service *ircService, server *Server, account, chn
break // prefer the login where the nick is the account
}
}
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))
if client == nil {
message := fmt.Sprintf("sendTransferPendingNotice client is nil: " + chname)
server.logger.Error("system", message)
server.snomasks.Send(sno.LocalOpers, message)
}
server.snoErr(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 (s *Server) snoErr(err error) {
if err == nil {
return
}
s.logger.Error("system", err.Error())
s.snomasks.Send(sno.LocalOpers, err.Error())
}
func processTransferAccept(service *ircService, client *Client, chname string, rb *ResponseBuffer) {
@ -624,7 +652,13 @@ func processTransferAccept(service *ircService, client *Client, chname string, r
}
}
func csPurgeHandler(service *ircService, 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
@ -699,9 +733,9 @@ func csPurgeDelHandler(service *ircService, client *Client, params []string, ope
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))
service.Notice(rb, fmt.Sprintf(client.t("[%s] successfully unpurged channel %s from the server"), operName, chname))
case errNoSuchChannel:
service.Notice(rb, fmt.Sprintf(client.t("Channel %s wasn't previously purged from the server"), chname))
service.Notice(rb, fmt.Sprintf(client.t("[%s] channel %s wasn't previously purged from the server"), operName, chname))
default:
service.Notice(rb, client.t("An error occurred"))
}

@ -16,7 +16,7 @@ import (
"sync/atomic"
"time"
ident "github.com/ergochat/go-ident"
"github.com/ergochat/go-ident"
"github.com/ergochat/irc-go/ircfmt"
"github.com/ergochat/irc-go/ircmsg"
"github.com/ergochat/irc-go/ircreader"
@ -32,7 +32,7 @@ import (
)
const (
// maximum IRC line length, not including tags
// DefaultMaxLineLen maximum IRC line length, not including tags
DefaultMaxLineLen = 512
// IdentTimeout is how long before our ident (username) check times out.
@ -50,15 +50,15 @@ const (
RegisterTimeout = time.Minute
// DefaultIdleTimeout is how long without traffic before we send the client a PING
DefaultIdleTimeout = time.Minute + 30*time.Second
// For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug
// TorIdleTimeout For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug
// (single-onion circuits will close unless the client sends data once every 60 seconds):
// https://bugs.torproject.org/29665
TorIdleTimeout = time.Second * 30
// This is how long a client gets without sending any message, including the PONG to our
// DefaultTotalTimeout This is how long a client gets without sending any message, including the PONG to our
// PING, before we disconnect them:
DefaultTotalTimeout = 2*time.Minute + 30*time.Second
DefaultTotalTimeout = 5 * time.Minute
// round off the ping interval by this much, see below:
// PingCoalesceThreshold round off the ping interval by this much, see below:
PingCoalesceThreshold = time.Second
)
@ -193,7 +193,7 @@ type MultilineBatch struct {
tags map[string]string
}
// Starts a multiline batch, failing if there's one already open
// StartMultilineBatch Starts a multiline batch, failing if there's one already open
func (s *Session) StartMultilineBatch(label, target, responseLabel string, tags map[string]string) (err error) {
if s.batch.label != "" {
return errInvalidMultilineBatch
@ -204,7 +204,7 @@ func (s *Session) StartMultilineBatch(label, target, responseLabel string, tags
return
}
// Closes a multiline batch unconditionally; returns the batch and whether
// EndMultilineBatch Closes a multiline batch unconditionally; returns the batch and whether
// it was validly terminated (pass "" as the label if you don't care about the batch)
func (s *Session) EndMultilineBatch(label string) (batch MultilineBatch, err error) {
batch = s.batch
@ -249,7 +249,7 @@ func (s *Session) IP() net.IP {
return s.realIP
}
// returns whether the client supports a smart history replay cap,
// HasHistoryCaps returns whether the client supports a smart history replay cap,
// and therefore autoreplay-on-join and similar should be suppressed
func (session *Session) HasHistoryCaps() bool {
return session.capabilities.Has(caps.Chathistory) || session.capabilities.Has(caps.ZNCPlayback)
@ -488,40 +488,19 @@ func (client *Client) lookupHostname(session *Session, overwrite bool) {
if session.proxiedIP != nil {
ip = session.proxiedIP
}
ipString := ip.String()
var hostname, candidate string
var hostname string
lookupSuccessful := false
if config.Server.lookupHostnames {
session.Notice("*** Looking up your hostname...")
names, err := net.LookupAddr(ipString)
if err == nil && 0 < len(names) {
candidate = strings.TrimSuffix(names[0], ".")
}
if utils.IsHostname(candidate) {
if config.Server.ForwardConfirmHostnames {
addrs, err := net.LookupHost(candidate)
if err == nil {
for _, addr := range addrs {
if addr == ipString {
hostname = candidate // successful forward confirmation
break
}
}
}
} else {
hostname = candidate
}
}
}
if hostname != "" {
session.Notice("*** Found your hostname")
} else {
if config.Server.lookupHostnames {
hostname, lookupSuccessful = utils.LookupHostname(ip, config.Server.ForwardConfirmHostnames)
if lookupSuccessful {
session.Notice("*** Found your hostname")
} else {
session.Notice("*** Couldn't look up your hostname")
}
hostname = utils.IPStringToHostname(ipString)
} else {
hostname = utils.IPStringToHostname(ip.String())
}
session.rawHostname = hostname
@ -808,7 +787,7 @@ func (client *Client) updateIdleTimer(session *Session, now time.Time) {
session.pingSent = false
if session.idleTimer == nil {
pingTimeout := DefaultIdleTimeout
pingTimeout := 5 * time.Minute
if session.isTor {
pingTimeout = TorIdleTimeout
}
@ -818,7 +797,7 @@ func (client *Client) updateIdleTimer(session *Session, now time.Time) {
func (session *Session) handleIdleTimeout() {
totalTimeout := DefaultTotalTimeout
pingTimeout := DefaultIdleTimeout
pingTimeout := 5 * time.Minute
if session.isTor {
pingTimeout = TorIdleTimeout
}
@ -1025,7 +1004,7 @@ func (client *Client) Friends(capabs ...caps.Capability) (result map[*Session]em
return
}
// Friends refers to clients that share a channel or extended-monitor this client.
// FriendsMonitors Friends refers to clients that share a channel or extended-monitor this client.
func (client *Client) FriendsMonitors(capabs ...caps.Capability) (result map[*Session]empty) {
result = client.Friends(capabs...)
client.server.monitorManager.AddMonitors(result, client.nickCasefolded, capabs...)
@ -1617,7 +1596,7 @@ type channelInvite struct {
invitedAt time.Time
}
// Records that the client has been invited to join an invite-only channel
// Invite Records that the client has been invited to join an invite-only channel
func (client *Client) Invite(casefoldedChannel string, channelCreatedAt time.Time) {
now := time.Now().UTC()
client.stateMutex.Lock()
@ -1641,7 +1620,7 @@ func (client *Client) Uninvite(casefoldedChannel string) {
delete(client.invitedTo, casefoldedChannel)
}
// Checks that the client was invited to join a given channel
// CheckInvited Checks that the client was invited to join a given channel
func (client *Client) CheckInvited(casefoldedChannel string, createdTime time.Time) (invited bool) {
config := client.server.Config()
expTime := time.Duration(config.Channels.InviteExpiration)
@ -1723,10 +1702,7 @@ func (client *Client) addHistoryItem(target *Client, item history.Item, details,
item.CfCorrespondent = details.nickCasefolded
target.history.Add(item)
}
if cStatus == HistoryPersistent || tStatus == HistoryPersistent {
targetedItem.CfCorrespondent = ""
client.server.historyDB.AddDirectMessage(details.nickCasefolded, details.account, tDetails.nickCasefolded, tDetails.account, targetedItem)
}
return nil
}
@ -1750,10 +1726,6 @@ func (client *Client) listTargets(start, end history.Selector, limit int) (resul
chcfnames = append(chcfnames, channel.NameCasefolded())
}
}
persistentExtras, err := client.server.historyDB.ListChannels(chcfnames)
if err == nil && len(persistentExtras) != 0 {
extras = append(extras, persistentExtras...)
}
_, cSeq, err := client.server.GetHistorySequence(nil, client, "")
if err == nil && cSeq != nil {
@ -1891,7 +1863,7 @@ func (client *Client) performWrite(additionalDirtyBits uint) {
}
}
// Blocking store; see Channel.Store and Socket.BlockingWrite
// Store Blocking store; see Channel.Store and Socket.BlockingWrite
func (client *Client) Store(dirtyBits uint) (err error) {
defer func() {
client.stateMutex.Lock()

@ -289,7 +289,7 @@ func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
return set
}
// Determine the canonical / unfolded form of a nick, if a client matching it
// UnfoldNick 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()

@ -49,7 +49,7 @@ func (cloakConfig *CloakConfig) SetSecret(secret string) {
cloakConfig.secret = secret
}
// simple cloaking algorithm: normalize the IP to its CIDR,
// ComputeCloak simple cloaking algorithm: normalize the IP to its CIDR,
// then hash the resulting bytes with a secret key,
// then truncate to the desired length, b32encode, and append the fake TLD.
func (config *CloakConfig) ComputeCloak(ip net.IP) string {

@ -98,10 +98,6 @@ func init() {
usablePreReg: true,
minParams: 1,
},
"CHATHISTORY": {
handler: chathistoryHandler,
minParams: 4,
},
"DEBUG": {
handler: debugHandler,
minParams: 1,
@ -132,10 +128,6 @@ func init() {
handler: helpHandler,
minParams: 0,
},
"HISTORY": {
handler: historyHandler,
minParams: 1,
},
"INFO": {
handler: infoHandler,
},
@ -276,9 +268,6 @@ func init() {
handler: setnameHandler,
minParams: 1,
},
"SUMMON": {
handler: summonHandler,
},
"TAGMSG": {
handler: messageHandler,
minParams: 1,
@ -358,10 +347,6 @@ func init() {
handler: whowasHandler,
minParams: 1,
},
"ZNC": {
handler: zncHandler,
minParams: 1,
},
}
initializeServices()

@ -18,7 +18,6 @@ import (
"path/filepath"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"time"
@ -37,7 +36,6 @@ import (
"github.com/ergochat/ergo/irc/languages"
"github.com/ergochat/ergo/irc/logger"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/mysql"
"github.com/ergochat/ergo/irc/passwd"
"github.com/ergochat/ergo/irc/utils"
)
@ -473,7 +471,7 @@ type OperConfig struct {
Modes string
}
// Various server-enforced limits on data size.
// Limits Various server-enforced limits on data size.
type Limits struct {
AwayLen int `yaml:"awaylen"`
ChanListModes int `yaml:"chan-list-modes"`
@ -618,7 +616,6 @@ type Config struct {
Datastore struct {
Path string
AutoUpgrade bool
MySQL mysql.Config
}
Accounts AccountConfig
@ -650,7 +647,7 @@ type Config struct {
Debug struct {
RecoverFromErrors *bool `yaml:"recover-from-errors"`
recoverFromErrors bool
PprofListener *string `yaml:"pprof-listener"`
PprofListener string `yaml:"pprof-listener"`
}
Limits Limits
@ -831,9 +828,9 @@ func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error
}
oper.Vhost = opConf.Vhost
if oper.Vhost != "" && !conf.Accounts.VHosts.validRegexp.MatchString(oper.Vhost) {
/*if oper.Vhost != "" && !conf.Accounts.VHosts.validRegexp.MatchString(oper.Vhost) {
return nil, fmt.Errorf("Oper %s has an invalid vhost: `%s`", name, oper.Vhost)
}
}*/
class, exists := oc[opConf.Class]
if !exists {
return nil, fmt.Errorf("Could not load operator [%s] - they use operclass [%s] which does not exist", name, opConf.Class)
@ -944,7 +941,10 @@ func (conf *Config) prepareListeners() (err error) {
}
lconf.TLSConfig, err = loadTlsConfig(block)
if err != nil {
return &CertKeyError{Err: err}
return &CertKeyError{
Err: err,
Details: addr,
}
}
lconf.RequireProxy = block.TLS.Proxy || block.Proxy
lconf.WebSocket = block.WebSocket
@ -1133,7 +1133,7 @@ func LoadConfig(filename string) (config *Config, err error) {
if config.Datastore.Path == "" {
return nil, errors.New("Datastore path missing")
}
//dan: automagically fix identlen until a few releases in the future (from now, 0.12.0), being a newly-introduced limit
// dan: automagically fix identlen until a few releases in the future (from now, 0.12.0), being a newly-introduced limit
if config.Limits.IdentLen < 1 {
config.Limits.IdentLen = 20
}
@ -1146,11 +1146,6 @@ func LoadConfig(filename string) (config *Config, err error) {
if config.Server.MaxLineLen < DefaultMaxLineLen {
config.Server.MaxLineLen = DefaultMaxLineLen
}
if config.Datastore.MySQL.Enabled {
if config.Limits.NickLen > mysql.MaxTargetLength || config.Limits.ChannelLen > mysql.MaxTargetLength {
return nil, fmt.Errorf("to use MySQL, nick and channel length limits must be %d or lower", mysql.MaxTargetLength)
}
}
if config.Server.CoerceIdent != "" {
if config.Server.CheckIdent {
@ -1369,9 +1364,9 @@ func LoadConfig(filename string) (config *Config, err error) {
rawRegexp := config.Accounts.VHosts.ValidRegexpRaw
if rawRegexp != "" {
regexp, err := regexp.Compile(rawRegexp)
regx, err := regexp.Compile(rawRegexp)
if err == nil {
config.Accounts.VHosts.validRegexp = regexp
config.Accounts.VHosts.validRegexp = regx
} else {
log.Printf("invalid vhost regexp: %s\n", err.Error())
}
@ -1481,10 +1476,6 @@ func LoadConfig(filename string) (config *Config, err error) {
config.History.Persistent.DirectMessages = PersistentDisabled
}
if config.History.Persistent.Enabled && !config.Datastore.MySQL.Enabled {
return nil, fmt.Errorf("You must configure a MySQL server in order to enable persistent history")
}
if config.History.ZNCMax == 0 {
config.History.ZNCMax = config.History.ChathistoryMax
}
@ -1504,15 +1495,6 @@ func LoadConfig(filename string) (config *Config, err error) {
config.Roleplay.addSuffix = utils.BoolDefaultTrue(config.Roleplay.AddSuffix)
config.Datastore.MySQL.ExpireTime = time.Duration(config.History.Restrictions.ExpireTime)
config.Datastore.MySQL.TrackAccountMessages = config.History.Retention.EnableAccountIndexing
if config.Datastore.MySQL.MaxConns == 0 {
// #1622: not putting an upper limit on the number of MySQL connections is
// potentially dangerous. as a naive heuristic, assume they're running on the
// same machine:
config.Datastore.MySQL.MaxConns = runtime.NumCPU()
}
config.Server.Cloaks.Initialize()
if config.Server.Cloaks.Enabled {
if !utils.IsHostname(config.Server.Cloaks.Netname) {
@ -1561,50 +1543,50 @@ func (config *Config) generateISupport() (err error) {
maxTargetsString := strconv.Itoa(maxTargets)
// add RPL_ISUPPORT tokens
isupport := &config.Server.isupport
isupport.Initialize()
isupport.Add("AWAYLEN", strconv.Itoa(config.Limits.AwayLen))
isupport.Add("BOT", "B")
isupport.Add("CASEMAPPING", "ascii")
isupport.Add("CHANLIMIT", fmt.Sprintf("%s:%d", chanTypes, config.Channels.MaxChannelsPerClient))
isupport.Add("CHANMODES", chanmodesToken)
isupp := &config.Server.isupport
isupp.Initialize()
isupp.Add("AWAYLEN", strconv.Itoa(config.Limits.AwayLen))
isupp.Add("BOT", "B")
isupp.Add("CASEMAPPING", "ascii")
isupp.Add("CHANLIMIT", fmt.Sprintf("%s:%d", chanTypes, config.Channels.MaxChannelsPerClient))
isupp.Add("CHANMODES", chanmodesToken)
if config.History.Enabled && config.History.ChathistoryMax > 0 {
isupport.Add("draft/CHATHISTORY", strconv.Itoa(config.History.ChathistoryMax))
isupp.Add("draft/CHATHISTORY", strconv.Itoa(config.History.ChathistoryMax))
}
isupport.Add("CHANNELLEN", strconv.Itoa(config.Limits.ChannelLen))
isupport.Add("CHANTYPES", chanTypes)
isupport.Add("ELIST", "U")
isupport.Add("EXCEPTS", "")
isupp.Add("CHANNELLEN", strconv.Itoa(config.Limits.ChannelLen))
isupp.Add("CHANTYPES", chanTypes)
isupp.Add("ELIST", "U")
isupp.Add("EXCEPTS", "")
if config.Extjwt.Default.Enabled() || len(config.Extjwt.Services) != 0 {
isupport.Add("EXTJWT", "1")
isupp.Add("EXTJWT", "1")
}
isupport.Add("EXTBAN", ",m")
isupport.Add("FORWARD", "f")
isupport.Add("INVEX", "")
isupport.Add("KICKLEN", strconv.Itoa(config.Limits.KickLen))
isupport.Add("MAXLIST", fmt.Sprintf("beI:%s", strconv.Itoa(config.Limits.ChanListModes)))
isupport.Add("MAXTARGETS", maxTargetsString)
isupport.Add("MODES", "")
isupport.Add("MONITOR", strconv.Itoa(config.Limits.MonitorEntries))
isupport.Add("NETWORK", config.Network.Name)
isupport.Add("NICKLEN", strconv.Itoa(config.Limits.NickLen))
isupport.Add("PREFIX", "(qaohv)~&@%+")
isupp.Add("EXTBAN", ",m")
isupp.Add("FORWARD", "f")
isupp.Add("INVEX", "")
isupp.Add("KICKLEN", strconv.Itoa(config.Limits.KickLen))
isupp.Add("MAXLIST", fmt.Sprintf("beI:%s", strconv.Itoa(config.Limits.ChanListModes)))
isupp.Add("MAXTARGETS", maxTargetsString)
isupp.Add("MODES", "")
isupp.Add("MONITOR", strconv.Itoa(config.Limits.MonitorEntries))
isupp.Add("NETWORK", config.Network.Name)
isupp.Add("NICKLEN", strconv.Itoa(config.Limits.NickLen))
isupp.Add("PREFIX", "(qaohv)~&@%+")
if config.Roleplay.Enabled {
isupport.Add("RPCHAN", "E")
isupport.Add("RPUSER", "E")
isupp.Add("RPCHAN", "E")
isupp.Add("RPUSER", "E")
}
isupport.Add("STATUSMSG", "~&@%+")
isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:%d", maxTargetsString, maxTargetsString, maxTargetsString, config.Limits.MonitorEntries))
isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen))
isupp.Add("STATUSMSG", "~&@%+")
isupp.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:%d", maxTargetsString, maxTargetsString, maxTargetsString, config.Limits.MonitorEntries))
isupp.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen))
if config.Server.Casemapping == CasemappingPRECIS {
isupport.Add("UTF8MAPPING", precisUTF8MappingToken)
isupp.Add("UTF8MAPPING", precisUTF8MappingToken)
}
if config.Server.EnforceUtf8 {
isupport.Add("UTF8ONLY", "")
isupp.Add("UTF8ONLY", "")
}
isupport.Add("WHOX", "")
isupp.Add("WHOX", "")
err = isupport.RegenerateCachedReply()
err = isupp.RegenerateCachedReply()
return
}

@ -637,13 +637,14 @@ func schemaChangeV9ToV10(config *Config, tx *buntdb.Tx) error {
return true
})
for i, account := range accounts {
time, err := strconv.ParseInt(times[i], 10, 64)
actTime, err := strconv.ParseInt(times[i], 10, 64)
if err != nil {
log.Printf("corrupt registration time entry for %s: %v\n", account, err)
continue
}
time = time * 1000000000
tx.Set(prefix+account, strconv.FormatInt(time, 10), nil)
// ????????????????? wtf are these people doing
actTime = actTime * 1000000000
tx.Set(prefix+account, strconv.FormatInt(actTime, 10), nil)
}
return nil
}

@ -10,8 +10,9 @@ import (
"sync"
"time"
"github.com/ergochat/ergo/irc/flatip"
"github.com/tidwall/buntdb"
"github.com/ergochat/ergo/irc/flatip"
)
const (

@ -5,8 +5,9 @@ package email
import (
"errors"
dkim "github.com/toorop/go-dkim"
"os"
"github.com/toorop/go-dkim"
)
var (

@ -83,7 +83,7 @@ func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
return config.DKIM.Postprocess()
}
// are we sending email directly, as opposed to deferring to an MTA?
// DirectSendingEnabled are we sending email directly, as opposed to deferring to an MTA?
func (config *MailtoConfig) DirectSendingEnabled() bool {
return config.MTAReal.Server == ""
}
@ -131,7 +131,7 @@ func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
}
var addr string
var auth smtp.Auth
var auth smtp.AuthMethod
if !config.DirectSendingEnabled() {
addr = fmt.Sprintf("%s:%d", config.MTAReal.Server, config.MTAReal.Port)
if config.MTAReal.Username != "" && config.MTAReal.Password != "" {

@ -84,11 +84,12 @@ var (
)
type CertKeyError struct {
Err error
Err error
Details string
}
func (ck *CertKeyError) Error() string {
return fmt.Sprintf("Invalid TLS cert/key pair: %v", ck.Err)
return fmt.Sprintf("Invalid TLS cert/key pair for %s: %v", ck.Details, ck.Err)
}
type ThrottleError struct {

@ -13,15 +13,15 @@ import (
type FakelagState uint
const (
// initially, the client is "bursting" and can send n commands without
// FakelagBursting FakelagBursting initially, the client is "bursting" and can send n commands without
// encountering fakelag
FakelagBursting FakelagState = iota
// after that, they're "throttled" and we sleep in between commands until
// FakelagThrottled FakelagThrottled after that, they're "throttled" and we sleep in between commands until
// they're spaced sufficiently far apart
FakelagThrottled
)
// this is intentionally not threadsafe, because it should only be touched
// Fakelag Fakelag this is intentionally not threadsafe, because it should only be touched
// from the loop that accepts the client's input and runs commands
type Fakelag struct {
config FakelagConfig
@ -41,7 +41,7 @@ func (fl *Fakelag) Initialize(config FakelagConfig) {
fl.state = FakelagBursting
}
// Idempotently turn off fakelag if it's enabled
// Suspend Idempotently turn off fakelag if it's enabled
func (fl *Fakelag) Suspend() {
if fl.config.Enabled {
fl.suspended = true
@ -49,7 +49,7 @@ func (fl *Fakelag) Suspend() {
}
}
// Idempotently turn fakelag back on if it was previously Suspend'ed
// Unsuspend Idempotently turn fakelag back on if it was previously Suspend'ed
func (fl *Fakelag) Unsuspend() {
if fl.suspended {
fl.config.Enabled = true
@ -57,7 +57,7 @@ func (fl *Fakelag) Unsuspend() {
}
}
// register a new command, sleep if necessary to delay it
// Touch register a new command, sleep if necessary to delay it
func (fl *Fakelag) Touch() {
if !fl.config.Enabled {
return

@ -155,7 +155,7 @@ func (cidr IPNet) Contains(ip IP) bool {
return cidr.IP == maskedIP
}
// FromNetIPnet converts a net.IPNet into an IPNet.
// FromNetIPNet FromNetIPnet converts a net.IPNet into an IPNet.
func FromNetIPNet(network net.IPNet) (result IPNet) {
ones, _ := network.Mask.Size()
if len(network.IP) == 16 {

@ -69,7 +69,7 @@ func registrationErrorToMessage(config *Config, client *Client, err error) (mess
}
switch err {
case errAccountAlreadyRegistered, errAccountAlreadyVerified, errAccountAlreadyUnregistered, errAccountAlreadyLoggedIn, errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled, errAccountBadPassphrase:
case errAccountAlreadyRegistered, errAccountAlreadyVerified, errAccountAlreadyUnregistered, errAccountAlreadyLoggedIn, errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled:
message = err.Error()
case errLimitExceeded:
message = `There have been too many registration attempts recently; try again later`
@ -100,7 +100,7 @@ func sendSuccessfulAccountAuth(service *ircService, client *Client, rb *Response
if service != nil {
service.Notice(rb, fmt.Sprintf(client.t("You're now logged in as %s"), details.accountName))
} else {
//TODO(dan): some servers send this numeric even for NickServ logins iirc? to confirm and maybe do too
// TODO(dan): some servers send this numeric even for NickServ logins iirc? to confirm and maybe do too
rb.Add(nil, client.server.name, RPL_LOGGEDIN, details.nick, details.nickMask, details.accountName, fmt.Sprintf(client.t("You are now logged in as %s"), details.accountName))
if forSASL {
rb.Add(nil, client.server.name, RPL_SASLSUCCESS, details.nick, client.t("Authentication successful"))
@ -553,16 +553,9 @@ func capHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response
rb.session.capState = caps.NegotiatingState
}
// make sure all capabilities actually exist
// #511, #521: oragono.io/nope is a fake cap to trap bad clients who blindly request
// every offered capability. during registration, requesting it produces a quit,
// otherwise just a CAP NAK
if badCaps || (toAdd.Has(caps.Nope) && client.registered) {
if badCaps && client.registered {
rb.Add(nil, server.name, "CAP", details.nick, "NAK", capString)
return false
} else if toAdd.Has(caps.Nope) && !client.registered {
client.Quit(fmt.Sprintf(client.t("Requesting the %s client capability is forbidden"), caps.Nope.Name()), rb.session)
return true
}
rb.session.capabilities.Union(toAdd)
@ -1099,37 +1092,6 @@ Get an explanation of <argument>, or "index" for a list of help topics.`), rb)
return false
}
// HISTORY <target> [<limit>]
// e.g., HISTORY #ubuntu 10
// HISTORY alice 15
// HISTORY #darwin 1h
func historyHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
config := server.Config()
if !config.History.Enabled {
rb.Notice(client.t("This command has been disabled by the server administrators"))
return false
}
items, channel, err := easySelectHistory(server, client, msg.Params)
if err == errNoSuchChannel {
rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.Nick(), utils.SafeErrorParam(msg.Params[0]), client.t("No such channel"))
return false
} else if err != nil {
rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.Nick(), msg.Command, client.t("Could not retrieve history"))
return false
}
if len(items) != 0 {
if channel != nil {
channel.replayHistoryItems(rb, items, false)
} else {
client.replayPrivmsgHistory(rb, items, "")
}
}
return false
}
// INFO
func infoHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
nick := client.Nick()
@ -1714,8 +1676,16 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respon
return false
}
}
isSamode := msg.Command == "SAMODE"
if isSamode {
message := fmt.Sprintf("Operator %s ran SAMODE %s", client.Oper().Name, strings.Join(msg.Params, " "))
server.snomasks.Send(sno.LocalOpers, message)
server.logger.Info("opers", message)
}
// process mode changes, include list operations (an empty set of changes does a list)
applied := channel.ApplyChannelModeChanges(client, msg.Command == "SAMODE", changes, rb)
applied := channel.ApplyChannelModeChanges(client, isSamode, changes, rb)
details := client.Details()
isBot := client.HasMode(modes.Bot)
announceCmodeChanges(channel, applied, details.nickMask, details.accountName, details.account, isBot, rb)
@ -2271,7 +2241,7 @@ func dispatchMessageToTarget(client *Client, tags map[string]string, histType hi
// the originating session may get an echo message:
rb.addEchoMessage(tags, nickMaskString, accountName, command, tnick, message)
if histType != history.Notice {
//TODO(dan): possibly implement cooldown of away notifications to users
// TODO(dan): possibly implement cooldown of away notifications to users
if away, awayMessage := user.Away(); away {
rb.Add(nil, server.name, RPL_AWAY, client.Nick(), tnick, awayMessage)
}
@ -2910,12 +2880,6 @@ func setnameHandler(server *Server, client *Client, msg ircmsg.Message, rb *Resp
return false
}
// SUMMON [parameters]
func summonHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
rb.Add(nil, server.name, ERR_SUMMONDISABLED, client.Nick(), client.t("SUMMON has been disabled"))
return false
}
// TIME
func timeHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
rb.Add(nil, server.name, RPL_TIME, client.nick, server.name, time.Now().UTC().Format(time.RFC1123))
@ -3274,7 +3238,7 @@ func (client *Client) rplWhoReply(channel *Channel, target *Client, rb *Response
params = append(params, fAccount)
}
if fields.Has('o') { // target's channel power level
//TODO: implement this
// TODO: implement this
params = append(params, "0")
}
if fields.Has('r') {
@ -3336,12 +3300,12 @@ func whoHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response
fields = fields.Add(field)
}
//TODO(dan): is this used and would I put this param in the Modern doc?
// TODO(dan): is this used and would I put this param in the Modern doc?
// if not, can we remove it?
//var operatorOnly bool
//if len(msg.Params) > 1 && msg.Params[1] == "o" {
// var operatorOnly bool
// if len(msg.Params) > 1 && msg.Params[1] == "o" {
// operatorOnly = true
//}
// }
oper := client.Oper()
hasPrivs := oper.HasRoleCapab("sajoin")
@ -3402,10 +3366,10 @@ func whoHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response
// WHOIS [<target>] <mask>{,<mask>}
func whoisHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
var masksString string
//var target string
// var target string
if len(msg.Params) > 1 {
//target = msg.Params[0]
// target = msg.Params[0]
masksString = msg.Params[1]
} else {
masksString = msg.Params[0]

@ -297,7 +297,7 @@ Sets your preferred languages to the given ones.`,
Shows information on the given channels (or if none are given, then on all
channels). <elistcond>s modify how the channels are selected.`,
//TODO(dan): Explain <elistcond>s in more specific detail
// TODO(dan): Explain <elistcond>s in more specific detail
},
"lusers": {
text: `LUSERS [<mask> [<server>]]
@ -728,7 +728,7 @@ func (client *Client) sendHelp(helpEntry string, text string, rb *ResponseBuffer
rb.Add(nil, client.server.name, RPL_ENDOFHELP, nick, helpEntry, client.t("End of /HELPOP"))
}
// GetHelpIndex returns the help index for the given language.
// GetIndex GetHelpIndex returns the help index for the given language.
func (hm *HelpIndexManager) GetIndex(languages []string, oper bool) string {
hm.RLock()
langToIndex := hm.langToIndex
@ -752,7 +752,7 @@ func init() {
for name := range Commands {
_, exists := Help[strings.ToLower(name)]
if !exists {
panic(fmt.Sprintf("Help entry does not exist for command %s", name))
fmt.Println(fmt.Sprintf("Help entry does not exist for command %s", name))
}
}
}

@ -4,9 +4,10 @@
package history
import (
"github.com/ergochat/ergo/irc/utils"
"sync"
"time"
"github.com/ergochat/ergo/irc/utils"
)
type ItemType uint

@ -22,7 +22,7 @@ type Sequence interface {
ListCorrespondents(start, end Selector, limit int) (results []TargetListing, err error)
// this are weird hacks that violate the encapsulation of Sequence to some extent;
// Cutoff 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.
@ -30,7 +30,7 @@ type Sequence interface {
Ephemeral() bool
}
// This is a bad, slow implementation of CHATHISTORY AROUND using the BETWEEN semantics
// GenericAround 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

@ -13,7 +13,7 @@ type TargetListing struct {
Time time.Time
}
// Merge `base`, a paging window of targets, with `extras` (the target entries
// MergeTargets 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 {

@ -7,7 +7,6 @@ import (
"bufio"
"fmt"
"os"
"runtime/debug"
"strconv"
"time"
@ -156,19 +155,12 @@ func histservExportHandler(service *ircService, server *Server, client *Client,
}
func histservExportAndNotify(service *ircService, 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()))
}
}()
defer server.HandlePanic()
defer outfile.Close()
writer := bufio.NewWriter(outfile)
defer writer.Flush()
server.historyDB.Export(cfAccount, writer)
client := server.clients.Get(alertNick)
if client != nil && client.HasRoleCapabs("history") {
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))

@ -36,10 +36,10 @@ func maxReadQBytes() int {
type IRCConn interface {
UnderlyingConn() *utils.WrappedConn
// these take an IRC line or lines, correctly terminated with CRLF:
// WriteLine these take an IRC line or lines, correctly terminated with CRLF:
WriteLine([]byte) error
WriteLines([][]byte) error
// this returns an IRC line, possibly terminated with CRLF, LF, or nothing:
// ReadLine this returns an IRC line, possibly terminated with CRLF, LF, or nothing:
ReadLine() (line []byte, err error)
Close() error

@ -8,10 +8,9 @@ import (
"bytes"
"fmt"
"os"
"time"
"sync"
"sync/atomic"
"time"
)
// Level represents the level to log messages at.

@ -4,7 +4,7 @@ import (
"golang.org/x/crypto/bcrypt"
)
// See the v12-to-v13 schema change. The format of this hash is:
// CheckOragonoPassphraseV0 See the v12-to-v13 schema change. The format of this hash is:
// 30 bytes of global salt, 30 bytes of per-passphrase salt, then the bcrypt hash
func CheckOragonoPassphraseV0(hash, passphrase []byte) error {
globalSalt := hash[:30]

@ -38,7 +38,7 @@ import (
"encoding/binary"
)
// The size of a SHA256 checksum in bytes.
// Size The size of a SHA256 checksum in bytes.
const Size = 32
const (

@ -49,6 +49,13 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
client.server.stats.ChangeInvisible(1)
} else if change.Mode == modes.Operator && present {
client.server.stats.ChangeOperators(1)
} else if change.Mode == modes.Cloaked {
config := client.server.Config()
if client.proxiedIP != nil {
client.setCloakedHostname(config.Server.Cloaks.ComputeCloak(client.proxiedIP))
} else {
client.setCloakedHostname(config.Server.Cloaks.ComputeCloak(client.realIP))
}
}
applied = append(applied, change)
}
@ -67,6 +74,8 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
if removedSnomasks != "" {
client.server.snomasks.RemoveClient(client)
}
} else if change.Mode == modes.Cloaked {
client.setCloakedHostname(client.rawHostname)
}
applied = append(applied, change)
if removedSnomasks != "" {

@ -109,7 +109,7 @@ const (
UserNoCTCP Mode = 'T'
UserRoleplaying Mode = 'E'
WallOps Mode = 'w'
Cloaked Mode = 'x'
Cloaked Mode = 'x'
)
// Channel Modes
@ -124,7 +124,7 @@ const (
Moderated Mode = 'm' // flag
NoOutside Mode = 'n' // flag
OpOnlyTopic Mode = 't' // flag
// RegisteredOnly mode is reused here from umode definition
// RegisteredOnlySpeak RegisteredOnlySpeak RegisteredOnly mode is reused here from umode definition
RegisteredOnlySpeak Mode = 'M' // flag
Secret Mode = 's' // flag
UserLimit Mode = 'l' // flag arg
@ -340,13 +340,13 @@ const (
maxMode = 122 // 'z'
)
// returns a pointer to a new ModeSet
// NewModeSet NewModeSet returns a pointer to a new ModeSet
func NewModeSet() *ModeSet {
var set ModeSet
return &set
}
// test whether `mode` is set
// HasMode HasMode test whether `mode` is set
func (set *ModeSet) HasMode(mode Mode) bool {
if set == nil {
return false
@ -355,17 +355,17 @@ func (set *ModeSet) HasMode(mode Mode) bool {
return utils.BitsetGet(set[:], uint(mode)-minMode)
}
// set `mode` to be on or off, return whether the value actually changed
// SetMode SetMode set `mode` to be on or off, return whether the value actually changed
func (set *ModeSet) SetMode(mode Mode, on bool) (applied bool) {
return utils.BitsetSet(set[:], uint(mode)-minMode, on)
}
// copy the contents of another modeset on top of this one
// Copy Copy copy the contents of another modeset on top of this one
func (set *ModeSet) Copy(other *ModeSet) {
utils.BitsetCopy(set[:], other[:])
}
// return the modes in the set as a slice
// AllModes AllModes return the modes in the set as a slice
func (set *ModeSet) AllModes() (result []Mode) {
if set == nil {
return

@ -1,26 +0,0 @@
// Copyright (c) 2020 Shivaram Lingamneni
// released under the MIT license
package mysql
import (
"time"
)
type Config struct {
// these are intended to be written directly into the config file:
Enabled bool
Host string
Port int
SocketPath string `yaml:"socket-path"`
User string
Password string
HistoryDatabase string `yaml:"history-database"`
Timeout time.Duration
MaxConns int `yaml:"max-conns"`
ConnMaxLifetime time.Duration `yaml:"conn-max-lifetime"`
// XXX these are copied from elsewhere in the config:
ExpireTime time.Duration
TrackAccountMessages bool
}

File diff suppressed because it is too large Load Diff

@ -1,23 +0,0 @@
package mysql
import (
"encoding/json"
"github.com/ergochat/ergo/irc/history"
"github.com/ergochat/ergo/irc/utils"
)
// 123 / '{' is the magic number that means JSON;
// if we want to do a binary encoding later, we just have to add different magic version numbers
func marshalItem(item *history.Item) (result []byte, err error) {
return json.Marshal(item)
}
func unmarshalItem(data []byte, result *history.Item) (err error) {
return json.Unmarshal(data, result)
}
func decodeMsgid(msgid string) ([]byte, error) {
return utils.B32Encoder.DecodeString(msgid)
}

@ -9,11 +9,12 @@ import (
"fmt"
"strings"
"github.com/ergochat/irc-go/ircfmt"
"github.com/ergochat/ergo/irc/history"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/sno"
"github.com/ergochat/ergo/irc/utils"
"github.com/ergochat/irc-go/ircfmt"
)
var (

@ -1500,7 +1500,7 @@ func nsSuspendRemoveHandler(service *ircService, server *Server, client *Client,
}
}
// sort in reverse order of creation time
// ByCreationTime sort in reverse order of creation time
type ByCreationTime []AccountSuspension
func (a ByCreationTime) Len() int { return len(a) }

19
irc/panic.go Normal file

@ -0,0 +1,19 @@
// Copyright (c) 2021 Shivaram Lingamneni
// released under the MIT license
package irc
import (
"fmt"
"runtime/debug"
)
// HandlePanic is a general-purpose panic handler for ad-hoc goroutines.
// Because of the semantics of `recover`, it must be called directly
// from the routine on whose call stack the panic would occur, with `defer`,
// e.g. `defer server.HandlePanic()`
func (server *Server) HandlePanic() {
if r := recover(); r != nil {
server.logger.Error("internal", fmt.Sprintf("Panic encountered: %v\n%s", r, debug.Stack()))
}
}

@ -3,8 +3,10 @@
package passwd
import "golang.org/x/crypto/bcrypt"
import "golang.org/x/crypto/sha3"
import (
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/sha3"
)
const (
MinCost = bcrypt.MinCost

@ -7,9 +7,10 @@ import (
"runtime/debug"
"time"
"github.com/ergochat/irc-go/ircmsg"
"github.com/ergochat/ergo/irc/caps"
"github.com/ergochat/ergo/irc/utils"
"github.com/ergochat/irc-go/ircmsg"
)
const (
@ -190,7 +191,7 @@ func (rb *ResponseBuffer) sendBatchEnd(blocking bool) {
rb.session.SendRawMessage(message, blocking)
}
// Starts a nested batch (see the ResponseBuffer struct definition for a description of
// StartNestedBatch Starts a nested batch (see the ResponseBuffer struct definition for a description of
// how this works)
func (rb *ResponseBuffer) StartNestedBatch(batchType string, params ...string) (batchID string) {
batchID = rb.session.generateBatchID()

@ -131,7 +131,7 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
session.sendSplitMsgFromClientInternal(false, sourceMask, "*", isBot, nil, "PRIVMSG", tnick, splitMessage)
}
if away, awayMessage := user.Away(); away {
//TODO(dan): possibly implement cooldown of away notifications to users
// TODO(dan): possibly implement cooldown of away notifications to users
rb.Add(nil, server.name, RPL_AWAY, cnick, tnick, awayMessage)
}
}

@ -14,7 +14,7 @@ import (
// (regrettably, unary-encoded) counting semaphore to enforce these restrictions.
const (
// this is a tradeoff between exploiting CPU-level parallelism (higher values better)
// MaxServerSemaphoreCapacity this is a tradeoff between exploiting CPU-level parallelism (higher values better)
// and not thrashing the allocator (lower values better). really this is all just
// guesswork. oragono *can* make use of cores beyond this limit --- just not for
// the protected operations.

@ -12,7 +12,6 @@ import (
_ "net/http/pprof"
"os"
"os/signal"
"runtime/debug"
"strconv"
"strings"
"sync"
@ -23,16 +22,16 @@ import (
"github.com/ergochat/irc-go/ircfmt"
"github.com/okzk/sdnotify"
"github.com/tidwall/buntdb"
"github.com/ergochat/ergo/irc/caps"
"github.com/ergochat/ergo/irc/connection_limits"
"github.com/ergochat/ergo/irc/flatip"
"github.com/ergochat/ergo/irc/history"
"github.com/ergochat/ergo/irc/logger"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/mysql"
"github.com/ergochat/ergo/irc/sno"
"github.com/ergochat/ergo/irc/utils"
"github.com/tidwall/buntdb"
)
const (
@ -84,7 +83,6 @@ type Server struct {
exitSignals chan os.Signal
snomasks SnoManager
store *buntdb.DB
historyDB mysql.MySQL
torLimiter connection_limits.TorLimiter
whoWas WhoWasList
stats Stats
@ -100,7 +98,7 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
listeners: make(map[string]IRCListener),
logger: logger,
rehashSignal: make(chan os.Signal, 1),
exitSignals: make(chan os.Signal, len(ServerExitSignals)),
exitSignals: make(chan os.Signal, len(utils.ServerExitSignals)),
defcon: 5,
}
@ -115,7 +113,7 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
}
// Attempt to clean up when receiving these signals.
signal.Notify(server.exitSignals, ServerExitSignals...)
signal.Notify(server.exitSignals, utils.ServerExitSignals...)
signal.Notify(server.rehashSignal, syscall.SIGHUP)
time.AfterFunc(alwaysOnExpirationPollPeriod, server.handleAlwaysOnExpirations)
@ -128,7 +126,7 @@ func (server *Server) Shutdown() {
sdnotify.Stopping()
server.logger.Info("server", "Stopping server")
//TODO(dan): Make sure we disallow new nicks
// TODO(dan): Make sure we disallow new nicks
for _, client := range server.clients.AllClients() {
client.Notice("Server is shutting down")
if client.AlwaysOn() {
@ -140,7 +138,6 @@ func (server *Server) Shutdown() {
server.logger.Error("shutdown", fmt.Sprintln("Could not close datastore:", err))
}
server.historyDB.Close()
server.logger.Info("server", fmt.Sprintf("%s exiting", Ver))
}
@ -245,14 +242,12 @@ func (server *Server) checkTorLimits() (banned bool, message string) {
func (server *Server) handleAlwaysOnExpirations() {
defer func() {
if r := recover(); r != nil {
server.logger.Error("internal",
fmt.Sprintf("Panic in always-on cleanup: %v\n%s", r, debug.Stack()))
}
// either way, reschedule
// reschedule whether or not there was a panic
time.AfterFunc(alwaysOnExpirationPollPeriod, server.handleAlwaysOnExpirations)
}()
defer server.HandlePanic()
config := server.Config()
deadline := time.Duration(config.Accounts.Multiclient.AlwaysOnExpiration)
if deadline == 0 {
@ -379,7 +374,7 @@ func (server *Server) playRegistrationBurst(session *Session) {
}
// send welcome text
//NOTE(dan): we specifically use the NICK here instead of the nickmask
// NOTE(dan): we specifically use the NICK here instead of the nickmask
// see http://modern.ircdocs.horse/#rplwelcome-001 for details on why we avoid using the nickmask
config := server.Config()
session.Send(nil, server.name, RPL_WELCOME, d.nick, fmt.Sprintf(c.t("Welcome to the %s Network %s"), config.Network.Name, d.nick))
@ -478,7 +473,8 @@ func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuff
}
}
if client == target || oper.HasRoleCapab("ban") || !target.HasMode(modes.Cloaked) {
rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, cnick, tnick, fmt.Sprintf("%s@%s", targetInfo.username, target.RawHostname()), target.IPString(), client.t("Actual user@host, Actual IP"))
ip, hostname := target.getWhoisActually()
rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, cnick, tnick, fmt.Sprintf("%s@%s", targetInfo.username, hostname), utils.IPStringToHostname(ip.String()), client.t("Actual user@host, Actual IP"))
}
if client == target || oper.HasRoleCapab("samode") {
rb.Add(nil, client.server.name, RPL_WHOISMODES, cnick, tnick, fmt.Sprintf(client.t("is using modes +%s"), target.modes.String()))
@ -509,16 +505,7 @@ func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuff
// rehash reloads the config and applies the changes from the config file.
func (server *Server) rehash() error {
// #1570; this needs its own panic handling because it can be invoked via SIGHUP
defer func() {
if r := recover(); r != nil {
if server.Config().Debug.recoverFromErrors {
server.logger.Error("internal",
fmt.Sprintf("Panic during rehash: %v\n%s", r, debug.Stack()))
} else {
panic(r)
}
}
}()
defer server.HandlePanic()
server.logger.Info("server", "Attempting rehash")
@ -577,8 +564,6 @@ func (server *Server) applyConfig(config *Config) (err error) {
return fmt.Errorf("Cannot change max-concurrency for scripts after launching the server, rehash aborted")
} else if oldConfig.Server.OverrideServicesHostname != config.Server.OverrideServicesHostname {
return fmt.Errorf("Cannot change override-services-hostname after launching the server, rehash aborted")
} else if !oldConfig.Datastore.MySQL.Enabled && config.Datastore.MySQL.Enabled {
return fmt.Errorf("Cannot enable MySQL after launching the server, rehash aborted")
} else if oldConfig.Server.MaxLineLen != config.Server.MaxLineLen {
return fmt.Errorf("Cannot change max-line-len after launching the server, rehash aborted")
}
@ -648,10 +633,6 @@ func (server *Server) applyConfig(config *Config) (err error) {
if err := server.loadDatastore(config); err != nil {
return err
}
} else {
if config.Datastore.MySQL.Enabled && config.Datastore.MySQL != oldConfig.Datastore.MySQL {
server.historyDB.SetConfig(config.Datastore.MySQL)
}
}
// now that the datastore is initialized, we can load the cloak secret from it
@ -740,10 +721,7 @@ func (server *Server) applyConfig(config *Config) (err error) {
}
func (server *Server) setupPprofListener(config *Config) {
pprofListener := ""
if config.Debug.PprofListener != nil {
pprofListener = *config.Debug.PprofListener
}
pprofListener := config.Debug.PprofListener
if server.pprofServer != nil {
if pprofListener == "" || (pprofListener != server.pprofServer.Addr) {
server.logger.Info("server", "Stopping pprof listener", server.pprofServer.Addr)
@ -797,15 +775,6 @@ func (server *Server) loadFromDatastore(config *Config) (err error) {
server.channels.Initialize(server)
server.accounts.Initialize(server)
if config.Datastore.MySQL.Enabled {
server.historyDB.Initialize(server.logger, config.Datastore.MySQL)
err = server.historyDB.Open()
if err != nil {
server.logger.Error("internal", "could not connect to mysql", err.Error())
return err
}
}
return nil
}
@ -864,7 +833,7 @@ func (server *Server) setupListeners(config *Config) (err error) {
return
}
// Gets the abstract sequence from which we're going to query history;
// GetHistorySequence Gets the abstract sequence from which we're going to query history;
// we may already know the channel we're querying, or we may have
// to look it up via a string query. This function is responsible for
// privilege checking.
@ -880,7 +849,7 @@ func (server *Server) GetHistorySequence(providedChannel *Channel, client *Clien
// if we're retrieving a DM conversation ("query buffer"). with persistent history,
// target is always nonempty, and correspondent is either empty or nonempty as before.
var status HistoryStatus
var target, correspondent string
var correspondent string
var hist *history.Buffer
restriction := HistoryCutoffNone
channel = providedChannel
@ -900,7 +869,7 @@ func (server *Server) GetHistorySequence(providedChannel *Channel, client *Clien
err = errInsufficientPrivs
return
}
status, target, restriction = channel.historyStatus(config)
status, _, restriction = channel.historyStatus(config)
switch status {
case HistoryEphemeral:
hist = &channel.history
@ -910,7 +879,7 @@ func (server *Server) GetHistorySequence(providedChannel *Channel, client *Clien
return
}
} else {
status, target = client.historyStatus(config)
status, _ = client.historyStatus(config)
if query != "" {
correspondent, err = CasefoldName(query)
if err != nil {
@ -951,9 +920,8 @@ func (server *Server) GetHistorySequence(providedChannel *Channel, client *Clien
if hist != nil {
sequence = hist.MakeSequence(correspondent, cutoff)
} else if target != "" {
sequence = server.historyDB.MakeSequence(target, correspondent, cutoff)
}
return
}
@ -968,10 +936,6 @@ func (server *Server) ForgetHistory(accountName string) {
return
}
if cfAccount, err := CasefoldName(accountName); err == nil {
server.historyDB.Forget(cfAccount)
}
persistent := config.History.Persistent
if persistent.Enabled && persistent.UnregisteredChannels && persistent.RegisteredChannels == PersistentMandatory && persistent.DirectMessages == PersistentMandatory {
return
@ -1013,9 +977,7 @@ func (server *Server) DeleteMessage(target, msgid, accountName string) (err erro
}
}
if hist == nil {
err = server.historyDB.DeleteMsgid(msgid, accountName)
} else {
if hist != nil {
count := hist.Delete(func(item *history.Item) bool {
return item.Message.Msgid == msgid && (accountName == "*" || item.AccountName == accountName)
})

@ -11,9 +11,10 @@ import (
"strings"
"time"
"github.com/ergochat/ergo/irc/utils"
"github.com/ergochat/irc-go/ircfmt"
"github.com/ergochat/irc-go/ircmsg"
"github.com/ergochat/ergo/irc/utils"
)
// defines an IRC service, e.g., NICKSERV
@ -91,7 +92,7 @@ var (
}
)
// all services, by lowercase name
// OragonoServices all services, by lowercase name
var OragonoServices = map[string]*ircService{
"nickserv": nickservService,
"chanserv": chanservService,

@ -11,8 +11,8 @@ import (
"fmt"
)
// Auth is implemented by an SMTP authentication mechanism.
type Auth interface {
// AuthMethod is implemented by an SMTP authentication mechanism.
type AuthMethod interface {
// Start begins an authentication with a server.
// It returns the name of the authentication protocol
// and optionally data to include in the initial AUTH message
@ -43,15 +43,15 @@ type plainAuth struct {
host string
}
// PlainAuth returns an Auth that implements the PLAIN authentication
// mechanism as defined in RFC 4616. The returned Auth uses the given
// PlainAuth returns an AuthMethod that implements the PLAIN authentication
// mechanism as defined in RFC 4616. The returned AuthMethod uses the given
// username and password to authenticate to host and act as identity.
// Usually identity should be the empty string, to act as username.
//
// PlainAuth will only send the credentials if the connection is using TLS
// or is connected to localhost. Otherwise authentication will fail with an
// error, without sending the credentials.
func PlainAuth(identity, username, password, host string) Auth {
func PlainAuth(identity, username, password, host string) AuthMethod {
return &plainAuth{identity, username, password, host}
}
@ -87,11 +87,11 @@ type cramMD5Auth struct {
username, secret string
}
// CRAMMD5Auth returns an Auth that implements the CRAM-MD5 authentication
// CRAMMD5Auth returns an AuthMethod that implements the CRAM-MD5 authentication
// mechanism as defined in RFC 2195.
// The returned Auth uses the given username and secret to authenticate
// The returned AuthMethod uses the given username and secret to authenticate
// to the server using the challenge-response mechanism.
func CRAMMD5Auth(username, secret string) Auth {
func CRAMMD5Auth(username, secret string) AuthMethod {
return &cramMD5Auth{username, secret}
}

@ -215,7 +215,7 @@ func (c *Client) Verify(addr string) error {
// Auth authenticates a client using the provided authentication mechanism.
// A failed authentication closes the connection.
// Only servers that advertise the AUTH extension support this function.
func (c *Client) Auth(a Auth) error {
func (c *Client) Auth(a AuthMethod) error {
if err := c.hello(); err != nil {
return err
}
@ -336,7 +336,7 @@ var testHookStartTLS func(*tls.Config) // nil, except for tests
// functionality. Higher-level packages exist outside of the standard
// library.
// XXX: modified in Ergo to add `requireTLS`, `heloDomain`, and `timeout` arguments
func SendMail(addr string, a Auth, heloDomain string, from string, to []string, msg []byte, requireTLS bool, timeout time.Duration) error {
func SendMail(addr string, a AuthMethod, heloDomain string, from string, to []string, msg []byte, requireTLS bool, timeout time.Duration) error {
if err := validateLine(from); err != nil {
return err
}

@ -39,7 +39,7 @@ func (masks Masks) Sort() {
sort.Slice(masks, func(i, j int) bool { return masks[i] < masks[j] })
}
// Evaluate changes to snomasks made with MODE. There are several cases:
// EvaluateSnomaskChanges Evaluate changes to snomasks made with MODE. There are several cases:
// adding snomasks with `/mode +s a` or `/mode +s +a`, removing them with `/mode +s -a`,
// adding all with `/mode +s *` or `/mode +s +*`, removing all with `/mode +s -*` or `/mode -s`
func EvaluateSnomaskChanges(add bool, arg string, currentMasks Masks) (addMasks, removeMasks Masks, newArg string) {

@ -4,8 +4,9 @@ import (
"fmt"
"sync"
"github.com/ergochat/ergo/irc/sno"
"github.com/ergochat/irc-go/ircfmt"
"github.com/ergochat/ergo/irc/sno"
)
// SnoManager keeps track of which clients to send snomasks to.

@ -19,14 +19,14 @@ type Stats struct {
mutex sync.Mutex
}
// Adds an unregistered client
// Add Adds an unregistered client
func (s *Stats) Add() {
s.mutex.Lock()
s.Unknown += 1
s.mutex.Unlock()
}
// Activates a registered client, e.g., for the initial attach to a persistent client
// AddRegistered Activates a registered client, e.g., for the initial attach to a persistent client
func (s *Stats) AddRegistered(invisible, operator bool) {
s.mutex.Lock()
if invisible {
@ -40,7 +40,7 @@ func (s *Stats) AddRegistered(invisible, operator bool) {
s.mutex.Unlock()
}
// Transition a client from unregistered to registered
// Register Transition a client from unregistered to registered
func (s *Stats) Register(invisible bool) {
s.mutex.Lock()
s.Unknown -= 1
@ -58,14 +58,14 @@ func (s *Stats) setMax() {
}
}
// Modify the Invisible count
// ChangeInvisible Modify the Invisible count
func (s *Stats) ChangeInvisible(increment int) {
s.mutex.Lock()
s.Invisible += increment
s.mutex.Unlock()
}
// Modify the Operator count
// ChangeOperators Modify the Operator count
func (s *Stats) ChangeOperators(increment int) {
s.mutex.Lock()
s.Operators += increment
@ -89,7 +89,7 @@ func (s *Stats) Remove(registered, invisible, operator bool) {
s.mutex.Unlock()
}
// GetStats retrives total, invisible and oper count
// GetValues GetStats retrives total, invisible and oper count
func (s *Stats) GetValues() (result StatsValues) {
s.mutex.Lock()
result = s.StatsValues

@ -47,15 +47,15 @@ var (
type Casemapping uint
const (
// "precis" is the default / zero value:
// CasemappingPRECIS "precis" is the default / zero value:
// casefolding/validation: PRECIS + ircd restrictions (like no *)
// confusables detection: standard skeleton algorithm
CasemappingPRECIS Casemapping = iota
// "ascii" is the traditional ircd behavior:
// CasemappingASCII "ascii" is the traditional ircd behavior:
// casefolding/validation: must be pure ASCII and follow ircd restrictions, ASCII lowercasing
// confusables detection: none
CasemappingASCII
// "permissive" is an insecure mode:
// CasemappingPermissive "permissive" is an insecure mode:
// casefolding/validation: arbitrary unicodes that follow ircd restrictions, unicode casefolding
// confusables detection: standard skeleton algorithm (which may be ineffective
// over the larger set of permitted identifiers)
@ -225,7 +225,7 @@ func realSkeleton(name string) (string, error) {
return cases.Fold().String(name), nil
}
// maps a nickmask fragment to an expanded, casefolded wildcard:
// CanonicalizeMaskWildcard maps a nickmask fragment to an expanded, casefolded wildcard:
// Shivaram@good-fortune -> *!shivaram@good-fortune
// EDMUND -> edmund!*@*
func CanonicalizeMaskWildcard(userhost string) (expanded string, err error) {

@ -110,22 +110,22 @@ func (set *UserMaskSet) Masks() (result map[string]MaskInfo) {
// Match matches the given n!u@h against the standard (non-ext) bans.
func (set *UserMaskSet) Match(userhost string) bool {
regexp := (*regexp.Regexp)(atomic.LoadPointer(&set.regexp))
regx := (*regexp.Regexp)(atomic.LoadPointer(&set.regexp))
if regexp == nil {
if regx == nil {
return false
}
return regexp.MatchString(userhost)
return regx.MatchString(userhost)
}
// MatchMute matches the given NUH against the mute extbans.
func (set *UserMaskSet) MatchMute(userhost string) bool {
regexp := set.MuteRegexp()
regx := set.MuteRegexp()
if regexp == nil {
if regx == nil {
return false
}
return regexp.MatchString(userhost)
return regx.MatchString(userhost)
}
func (set *UserMaskSet) MuteRegexp() *regexp.Regexp {

@ -30,7 +30,7 @@ func StringToBool(str string) (result bool, err error) {
return
}
// Checks that a parameter can be passed as a non-trailing, and returns "*"
// SafeErrorParam Checks that a parameter can be passed as a non-trailing, and returns "*"
// if it can't. See #697.
func SafeErrorParam(param string) string {
if param == "" || param[0] == ':' || strings.IndexByte(param, ' ') != -1 {

@ -9,7 +9,7 @@ import (
"time"
)
// Deterministically generates a confirmation code for some destructive activity;
// ConfirmationCode Deterministically generates a confirmation code for some destructive activity;
// `name` is typically the name of the identity being destroyed (a channel being
// unregistered, or the server being crashed) and `createdAt` means a different
// value is required each time.

@ -19,7 +19,7 @@ import (
)
var (
// slingamn's own private b32 alphabet, removing 1, l, o, and 0
// B32Encoder B32Encoder slingamn's own private b32 alphabet, removing 1, l, o, and 0
B32Encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding)
ErrInvalidCertfp = errors.New("Invalid certfp")
@ -33,7 +33,7 @@ const (
SecretTokenLength = 26
)
// generate a secret token that cannot be brute-forced via online attacks
// GenerateSecretToken GenerateSecretToken generate a secret token that cannot be brute-forced via online attacks
func GenerateSecretToken() string {
// 128 bits of entropy are enough to resist any online attack:
var buf [16]byte
@ -42,7 +42,7 @@ func GenerateSecretToken() string {
return B32Encoder.EncodeToString(buf[:])
}
// "munge" a secret token to a new value. requirements:
// MungeSecretToken MungeSecretToken "munge" a secret token to a new value. requirements:
// 1. MUST be roughly as unlikely to collide with `GenerateSecretToken` outputs
// as those outputs are with each other
// 2. SHOULD be deterministic (motivation: if a JOIN line has msgid x,
@ -65,7 +65,7 @@ func MungeSecretToken(token string) (result string) {
return B32Encoder.EncodeToString(bytes)
}
// securely check if a supplied token matches a stored token
// SecretTokensMatch securely check if a supplied token matches a stored token
func SecretTokensMatch(storedToken string, suppliedToken string) bool {
// XXX fix a potential gotcha: if the stored token is uninitialized,
// then nothing should match it, not even supplying an empty token.
@ -76,14 +76,14 @@ func SecretTokensMatch(storedToken string, suppliedToken string) bool {
return subtle.ConstantTimeCompare([]byte(storedToken), []byte(suppliedToken)) == 1
}
// generate a 256-bit secret key that can be written into a config file
// GenerateSecretKey generate a 256-bit secret key that can be written into a config file
func GenerateSecretKey() string {
var buf [32]byte
rand.Read(buf[:])
return base64.RawURLEncoding.EncodeToString(buf[:])
}
// Normalize openssl-formatted certfp's to oragono's format
// NormalizeCertfp Normalize openssl-formatted certfp's to oragono's format
func NormalizeCertfp(certfp string) (result string, err error) {
result = strings.ToLower(strings.Replace(certfp, ":", "", -1))
decoded, err := hex.DecodeString(result)

@ -46,7 +46,7 @@ func CompileGlob(glob string, submatch bool) (result *regexp.Regexp, err error)
return regexp.Compile(buf.String())
}
// Compile a list of globs into a single or-expression that matches any one of them.
// CompileMasks Compile a list of globs into a single or-expression that matches any one of them.
// This is used for channel ban/invite/exception lists. It's applicable to k-lines
// but we're not using it there yet.
func CompileMasks(masks []string) (result *regexp.Regexp, err error) {

@ -3,7 +3,7 @@
package utils
// return n such that v <= n and n == 2**i for some i
// RoundUpToPowerOfTwo 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

@ -64,7 +64,7 @@ func IsServerName(name string) bool {
return IsHostname(name) && strings.IndexByte(name, '.') != -1
}
// Convenience to test whether `ip` is contained in any of `nets`.
// IPInNets Convenience to test whether `ip` is contained in any of `nets`.
func IPInNets(ip net.IP, nets []net.IPNet) bool {
for _, network := range nets {
if network.Contains(ip) {
@ -101,7 +101,7 @@ func NormalizeNet(network net.IPNet) (result net.IPNet) {
}
}
// Given a network, produce a human-readable string
// NetToNormalizedString Given a network, produce a human-readable string
// (i.e., CIDR if it's actually a network, IPv6 address if it's a v6 /128,
// dotted quad if it's a v4 /32).
func NetToNormalizedString(network net.IPNet) string {
@ -193,3 +193,37 @@ func HandleXForwardedFor(remoteAddr string, xForwardedFor string, whitelist []ne
// or nil:
return
}
// LookupHostname does an (optionally reverse-confirmed) hostname lookup
// suitable for use as an IRC hostname. It falls back to a string
// representation of the IP address (again suitable for use as an IRC
// hostname).
func LookupHostname(ip net.IP, forwardConfirm bool) (hostname string, lookupSuccessful bool) {
ipString := ip.String()
var candidate string
names, err := net.LookupAddr(ipString)
if err == nil && 0 < len(names) {
candidate = strings.TrimSuffix(names[0], ".")
}
if IsHostname(candidate) {
if forwardConfirm {
addrs, err := net.LookupHost(candidate)
if err == nil {
for _, addr := range addrs {
if forwardIP := net.ParseIP(addr); ip.Equal(forwardIP) {
hostname = candidate // successful forward confirmation
break
}
}
}
} else {
hostname = candidate
}
}
if hostname != "" {
return hostname, true
} else {
return IPStringToHostname(ipString), false
}
}

@ -9,7 +9,7 @@ import (
"syscall"
)
// Output a description of a connection that can identify it to other systems
// DescribeConn Output a description of a connection that can identify it to other systems
// administration tools.
func DescribeConn(c net.Conn) (description string) {
description = "<error>"

@ -4,9 +4,11 @@
package utils
import "net"
import "reflect"
import "testing"
import (
"net"
"reflect"
"testing"
)
func assertEqual(supplied, expected interface{}, t *testing.T) {
if !reflect.DeepEqual(supplied, expected) {

@ -7,7 +7,7 @@ import (
"os"
)
// implementation of `cp` (go should really provide this...)
// CopyFile implementation of `cp` (go should really provide this...)
func CopyFile(src string, dst string) (err error) {
in, err := os.Open(src)
if err != nil {

@ -203,7 +203,7 @@ func parseProxyLineV2(line []byte) (ip net.IP, err error) {
return ip, nil
}
/// WrappedConn is a net.Conn with some additional data stapled to it;
// WrappedConn / WrappedConn is a net.Conn with some additional data stapled to it;
// the proxied IP, if one was read via the PROXY protocol, and the listener
// configuration.
type WrappedConn struct {

@ -4,7 +4,7 @@
// Copyright (c) 2020 Shivaram Lingamneni
// released under the MIT license
package irc
package utils
import (
"os"

@ -4,7 +4,7 @@
// Copyright (c) 2020 Shivaram Lingamneni
// released under the MIT license
package irc
package utils
import (
"os"

@ -17,7 +17,7 @@ var (
Commit string
)
// initialize version strings (these are set in package main via linker flags)
// SetVersionString initialize version strings (these are set in package main via linker flags)
func SetVersionString(version, commit string) {
Commit = commit
if version != "" {

@ -22,7 +22,7 @@ type WhoWasList struct {
accessMutex sync.RWMutex // tier 1
}
// NewWhoWasList returns a new WhoWasList
// Initialize NewWhoWasList returns a new WhoWasList
func (list *WhoWasList) Initialize(size int) {
list.buffer = make([]WhoWas, size)
list.start = -1

@ -1 +0,0 @@
Subproject commit 33f0702c260ea716a4ad0f24821a50d44c91fca1

12
languages/README.md vendored

@ -1,7 +1,13 @@
# Oragono's Translations
These translations have been contributed by amazing people using [Crowdin](https://crowdin.com/project/oragono). If you'd like to fix up a mistake or help add a language, feel free to pop over there! Always interested in new contributors and language support.
These translations have been contributed by amazing people using [Crowdin](https://crowdin.com/project/oragono). If
you'd like to fix up a mistake or help add a language, feel free to pop over there! Always interested in new
contributors and language support.
Contributors to translations are noted in the translation's info file (the `yaml` file). You shouldn't be touching these files manually  they should be getting updated through CrowdIn. However, the `example` files exist if you want a reference for the format. To regenerate the `example` files (that get fed into CrowdIn), look at the `updatetranslations.py` file in the source directory.
Contributors to translations are noted in the translation's info file (the `yaml` file). You shouldn't be touching these
files manually they should be getting updated through CrowdIn. However, the `example` files exist if you want a
reference for the format. To regenerate the `example` files (that get fed into CrowdIn), look at
the `updatetranslations.py` file in the source directory.
My eventual intent is to use these translations to help create a standard set that other IRC software authors can see, download, and use in their own software (with proper attribution to the contributors, of course).
My eventual intent is to use these translations to help create a standard set that other IRC software authors can see,
download, and use in their own software (with proper attribution to the contributors, of course).

File diff suppressed because it is too large Load Diff

1
vendor/modules.txt vendored

@ -22,7 +22,6 @@ github.com/ergochat/irc-go/ircfmt
github.com/ergochat/irc-go/ircmsg
github.com/ergochat/irc-go/ircreader
github.com/ergochat/irc-go/ircutils
# github.com/go-sql-driver/mysql v1.6.0
## explicit; go 1.10
github.com/go-sql-driver/mysql
# github.com/go-test/deep v1.0.6