Browse Source

Merge remote-tracking branch 'origin/master' into websockets_draft.2

tags/v2.1.0-rc1
Shivaram Lingamneni 4 years ago
parent
commit
25813f6d3a

+ 3
- 3
README.md View File

@@ -4,7 +4,7 @@ Oragono is a modern IRC server written in Go. Its core design principles are:
4 4
 
5 5
 * Being simple to set up and use
6 6
 * Combining the features of an ircd, a services framework, and a bouncer (integrated account management, history storage, and bouncer functionality)
7
-* Bleeding-edge [IRCv3 support](http://ircv3.net/software/servers.html), suitable for use as an IRCv3 reference implementation
7
+* Bleeding-edge [IRCv3 support](https://ircv3.net/software/servers.html), suitable for use as an IRCv3 reference implementation
8 8
 * Highly customizable via a rehashable (i.e., reloadable at runtime) YAML config
9 9
 
10 10
 Oragono is a fork of the [Ergonomadic](https://github.com/jlatt/ergonomadic) IRC daemon <3
@@ -39,7 +39,7 @@ If you want to take a look at a running Oragono instance or test some client cod
39 39
 * automated client connection limits
40 40
 * passwords stored with [bcrypt](https://godoc.org/golang.org/x/crypto)
41 41
 * banning ips/nets and masks with `KLINE` and `DLINE`
42
-* [IRCv3 support](http://ircv3.net/software/servers.html)
42
+* [IRCv3 support](https://ircv3.net/software/servers.html)
43 43
 * a heavy focus on developing with [specifications](https://oragono.io/specs.html)
44 44
 
45 45
 ## Installation
@@ -112,7 +112,7 @@ oragono run
112 112
 
113 113
 ### How to register a channel
114 114
 
115
-1. Register your account with `/NS REGISTER <username> <email> <password>`
115
+1. Register your account with `/NS REGISTER <password>`
116 116
 2. Join the channel with `/join #channel`
117 117
 3. Register the channel with `/CS REGISTER #channel`
118 118
 

+ 10
- 0
conventional.yaml View File

@@ -434,6 +434,12 @@ accounts:
434 434
         offer-list:
435 435
             #- "oragono.test"
436 436
 
437
+    # modes that are set by default when a user connects
438
+    # if unset, no user modes will be set by default
439
+    # +i is invisible (a user's channels are hidden from whois replies)
440
+    # see  /QUOTE HELP umodes  for more user modes
441
+    # default-user-modes: +i
442
+
437 443
     # support for deferring password checking to an external LDAP server
438 444
     # you should probably ignore this section! consult the grafana docs for details:
439 445
     # https://grafana.com/docs/grafana/latest/auth/ldap/
@@ -495,6 +501,10 @@ channels:
495 501
         # how many channels can each account register?
496 502
         max-channels-per-account: 15
497 503
 
504
+    # as a crude countermeasure against spambots, anonymous connections younger
505
+    # than this value will get an empty response to /LIST (a time period of 0 disables)
506
+    list-delay: 0s
507
+
498 508
 # operator classes
499 509
 oper-classes:
500 510
     # local operator

+ 19
- 0
distrib/systemd/oragono.service View File

@@ -0,0 +1,19 @@
1
+[Unit]
2
+Description=oragono
3
+After=network.target
4
+# If you are using MySQL for history storage, comment out the above line
5
+# and uncomment these two instead:
6
+# Requires=mysql.service
7
+# After=network.target mysql.service
8
+
9
+[Service]
10
+Type=simple
11
+User=oragono
12
+WorkingDirectory=/home/oragono
13
+ExecStart=/home/oragono/oragono run --conf /home/oragono/ircd.yaml
14
+ExecReload=/bin/kill -HUP $MAINPID
15
+Restart=on-failure
16
+LimitNOFILE=1048576
17
+
18
+[Install]
19
+WantedBy=multi-user.target

+ 38
- 27
docs/MANUAL.md View File

@@ -22,6 +22,7 @@ _Copyright © Daniel Oaks <daniel@danieloaks.net>, Shivaram Lingamneni <slingamn
22 22
 - Installing
23 23
     - Windows
24 24
     - macOS / Linux / Raspberry Pi
25
+    - Productionizing
25 26
     - Upgrading
26 27
 - Features
27 28
     - User Accounts
@@ -120,16 +121,47 @@ If you're using Arch Linux, you can also install the [`oragono` package](https:/
120 121
 1. Create a volume for persistent data: `docker volume create oragono-data`
121 122
 1. Run the container, exposing the default ports: `docker run -d --name oragono -v oragono-data:/ircd-data -p 6667:6667 -p 6697:6697 oragono/oragono:latest`
122 123
 
123
-For further information and a sample docker-compose file see the separate [Docker documentation](https://github.com/oragono/oragono/blog/master/distrib/docker/README.md).
124
+For further information and a sample docker-compose file see the separate [Docker documentation](https://github.com/oragono/oragono/blob/master/distrib/docker/README.md).
124 125
 
125 126
 
126
-## Running oragono as a service on Linux
127
+## Productionizing
127 128
 
128 129
 The recommended way to operate oragono as a service on Linux is via systemd. This provides a standard interface for starting, stopping, and rehashing (via `systemctl reload`) the service. It also captures oragono's loglines (sent to stderr in the default configuration) and writes them to the system journal.
129 130
 
130
-If you're using Arch, the abovementioned AUR package bundles a systemd file for starting and stopping the server. If you're rolling your own deployment, here's an [example](https://github.com/darwin-network/slash/blob/master/etc/systemd/system/ircd.service) of a systemd unit file that can be used to run Oragono as an unprivileged role user.
131
+The only major distribution that currently packages Oragono is Arch Linux; the aforementioned AUR package includes a systemd unit file. However, it should be fairly straightforward to set up a productionized Oragono on any Linux distribution. Here's a quickstart guide for Debian/Ubuntu:
131 132
 
132
-On a non-systemd system, oragono can be configured to log to a file and used [logrotate(8)](https://linux.die.net/man/8/logrotate), since it will reopen its log files (as well as rehashing the config file) upon receiving a SIGHUP.
133
+1. Create a dedicated, unprivileged role user who will own the oragono process and all its associated files: `adduser --system --group oragono`. This user now has a home directory at `/home/oragono`.
134
+1. Copy the executable binary `oragono`, the config file `ircd.yaml`, the database `ircd.db`, and the self-signed TLS certificate (`tls.crt` and `tls.key`) to `/home/oragono`. Ensure that they are all owned by the new oragono role user: `sudo chown oragono:oragono /home/oragono/*`. Ensure that the configuration file logs to stderr.
135
+1. Install our example [oragono.service](https://github.com/oragono/oragono/blob/master/distrib/systemd/oragono.service) file to `/etc/systemd/system/oragono.service`.
136
+1. Enable and start the new service with the following commands:
137
+    1. `systemctl daemon-reload`
138
+    1. `systemctl enable oragono.service`
139
+    1. `systemctl start oragono.service`
140
+    1. Confirm that the service started correctly with `systemctl status oragono.service`
141
+
142
+The other major hurdle for productionizing (but one well worth the effort) is obtaining valid TLS certificates for your domain, if you haven't already done so:
143
+
144
+1. The simplest way to get valid TLS certificates is from [Let's Encrypt](https://letsencrypt.org/) with [Certbot](https://certbot.eff.org/). The correct procedure will depend on whether you are already running a web server on port 80. If you are, follow the guides on the Certbot website; if you aren't, you can use `certbot certonly --standalone --preferred-challenges http -d example.com` (replace `example.com` with your domain).
145
+1. At this point, you should have certificates available at `/etc/letsencrypt/live/example.com` (replacing `example.com` with your domain). You should serve `fullchain.pem` as the certificate and `privkey.pem` as its private key. However, these files are owned by root and the private key is not readable by the oragono role user, so you won't be able to use them directly in their current locations. You can write a post-renewal hook for certbot to make copies of these certificates accessible to the oragono role user. For example, install the following script as `/etc/letsencrypt/renewal-hooks/post/install-oragono-certificates`, again replacing `example.com` with your domain name, and chmod it 0755:
146
+
147
+````bash
148
+#!/bin/bash
149
+
150
+set -eu
151
+
152
+umask 077
153
+cp /etc/letsencrypt/live/example.com/fullchain.pem /home/oragono/tls.crt
154
+cp /etc/letsencrypt/live/example.com/privkey.pem /home/oragono/tls.key
155
+chown oragono:oragono /home/oragono/tls.*
156
+# rehash oragono, which will reload the certificates:
157
+systemctl reload oragono.service
158
+````
159
+
160
+Executing this script manually will install the certificates for the first time and perform a rehash, enabling them.
161
+
162
+If you are using Certbot 0.29.0 or higher, you can also change the ownership of the files under `/etc/letsencrypt` so that the oragono user can read them, as described in the [UnrealIRCd documentation](https://www.unrealircd.org/docs/Setting_up_certbot_for_use_with_UnrealIRCd#Tweaking_permissions_on_the_key_file).
163
+
164
+On a non-systemd system, oragono can be configured to log to a file and used [logrotate(8)](https://linux.die.net/man/8/logrotate), since it will reopen its log files (as well as rehashing the config file) upon receiving a SIGHUP. To rehash manually outside the context of log rotation, you can use `killall -HUP oragono` or `pkill -HUP oragono`.
133 165
 
134 166
 
135 167
 ## Upgrading to a new version of Oragono
@@ -366,30 +398,9 @@ Similarly, for a public channel (one without `+i`), users can ban nick/account n
366 398
 
367 399
 # IRC over TLS
368 400
 
369
-IRC has traditionally been available over both plaintext (on port 6667) and SSL/TLS (on port 6697). We recommend that you make your server available exclusively via TLS, since exposing plaintext access allows for unauthorized interception or modification of user data or passwords. While the default config file exposes a plaintext public port, it also contains instructions on how to disable it or replace it with a 'dummy' plaintext listener that simply directs users to reconnect using TLS.
370
-
371
-
372
-## How do I use Let's Encrypt certificates?
373
-
374
-[Let's Encrypt](https://letsencrypt.org) is a widely recognized certificate authority that provides free certificates. Here's a quick-start guide for using those certificates with Oragono:
375
-
376
-1. Follow this [guidance](https://letsencrypt.org/getting-started/) from Let's Encrypt to create your certificates.
377
-2. You should now have a set of `pem` files, Mainly, we're interested in your `live/` Let's Encrypt directory (e.g. `/etc/letsencrypt/live/<site>/`).
378
-3. Here are how the config file keys map to LE files:
379
-    - `cert: tls.crt` is `live/<site>/fullchain.pem`
380
-    - ` key: tls.key` is `live/<site>/privkey.pem`
381
-4. You may need to copy the `pem` files to another directory so Oragono can read them, or similarly use a script like [this one](https://github.com/darwin-network/slash/blob/master/etc/bin/install-lecerts) to automagically do something similar.
382
-5. By default, `certbot` will automatically renew your certificates. Oragono will only reread certificates when it is restarted, or during a rehash (e.g., on receiving the `/rehash` command or the `SIGHUP` signal). You can add an executable script to `/etc/letsencrypt/renewal-hooks/post` that can perform the rehash. Here's one example of such a script:
383
-
384
-```bash
385
-#!/bin/bash
386
-pkill -HUP oragono
387
-```
388
-
389
-The main issues you'll run into are going to be permissions issues. This is because by default, certbot will generate certificates that non-root users can't (and probably shouldn't) read. If you run into trouble, look over the script in step **4** and/or make sure you're copying the files to somewhere else, as well as giving them correct permissions with `chown`, `chgrp` and `chmod`.
390
-
391
-On other platforms or with alternative ACME tools, you may need to use other steps or the specific files may be named differently.
401
+IRC has traditionally been available over both plaintext (on port 6667) and SSL/TLS (on port 6697). We recommend that you make your server available exclusively via TLS, since exposing plaintext access allows for unauthorized interception or modification of user data or passwords. The default config file no longer exposes a plaintext port, so if you haven't modified your `listeners` section, you're good to go.
392 402
 
403
+For a quickstart guide to obtaining valid TLS certificates from Let's Encrypt, see the "productionizing" section of the manual above.
393 404
 
394 405
 ## How can I "redirect" users from plaintext to TLS?
395 406
 

+ 5
- 0
irc/accounts.go View File

@@ -1174,7 +1174,12 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
1174 1174
 	var channelsStr string
1175 1175
 	keepProtections := false
1176 1176
 	am.server.store.Update(func(tx *buntdb.Tx) error {
1177
+		// get the unfolded account name; for an active account, this is
1178
+		// stored under accountNameKey, for an unregistered account under unregisteredKey
1177 1179
 		accountName, _ = tx.Get(accountNameKey)
1180
+		if accountName == "" {
1181
+			accountName, _ = tx.Get(unregisteredKey)
1182
+		}
1178 1183
 		if erase {
1179 1184
 			tx.Delete(unregisteredKey)
1180 1185
 		} else {

+ 48
- 12
irc/channel.go View File

@@ -541,10 +541,16 @@ func (channel *Channel) ClientPrefixes(client *Client, isMultiPrefix bool) strin
541 541
 
542 542
 func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool {
543 543
 	channel.stateMutex.RLock()
544
+	founder := channel.registeredFounder
544 545
 	clientModes := channel.members[client]
545 546
 	targetModes := channel.members[target]
546 547
 	channel.stateMutex.RUnlock()
547 548
 
549
+	if founder != "" && founder == client.Account() {
550
+		// #950: founder can kick or whatever without actually having the +q mode
551
+		return true
552
+	}
553
+
548 554
 	return channelUserModeHasPrivsOver(clientModes.HighestChannelUserMode(), targetModes.HighestChannelUserMode())
549 555
 }
550 556
 
@@ -1064,6 +1070,25 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
1064 1070
 				message := fmt.Sprintf(client.t("%[1]s changed nick to %[2]s"), nick, item.Params[0])
1065 1071
 				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
1066 1072
 			}
1073
+		case history.Topic:
1074
+			if eventPlayback {
1075
+				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "TOPIC", chname, item.Message.Message)
1076
+			} else {
1077
+				message := fmt.Sprintf(client.t("%[1]s set the channel topic to: %[2]s"), nick, item.Message.Message)
1078
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
1079
+			}
1080
+		case history.Mode:
1081
+			params := make([]string, len(item.Message.Split)+1)
1082
+			params[0] = chname
1083
+			for i, pair := range item.Message.Split {
1084
+				params[i+1] = pair.Message
1085
+			}
1086
+			if eventPlayback {
1087
+				rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, nil, "MODE", params...)
1088
+			} else {
1089
+				message := fmt.Sprintf(client.t("%[1]s set channel modes: %[2]s"), nick, strings.Join(params[1:], " "))
1090
+				rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histServMask, "*", nil, "PRIVMSG", chname, message)
1091
+			}
1067 1092
 		}
1068 1093
 	}
1069 1094
 }
@@ -1113,22 +1138,30 @@ func (channel *Channel) SetTopic(client *Client, topic string, rb *ResponseBuffe
1113 1138
 	}
1114 1139
 
1115 1140
 	channel.stateMutex.Lock()
1141
+	chname := channel.name
1116 1142
 	channel.topic = topic
1117 1143
 	channel.topicSetBy = client.nickMaskString
1118 1144
 	channel.topicSetTime = time.Now().UTC()
1119 1145
 	channel.stateMutex.Unlock()
1120 1146
 
1121
-	prefix := client.NickMaskString()
1147
+	details := client.Details()
1148
+	message := utils.MakeMessage(topic)
1149
+	rb.AddFromClient(message.Time, message.Msgid, details.nickMask, details.accountName, nil, "TOPIC", chname, topic)
1122 1150
 	for _, member := range channel.Members() {
1123 1151
 		for _, session := range member.Sessions() {
1124
-			if session == rb.session {
1125
-				rb.Add(nil, prefix, "TOPIC", channel.name, topic)
1126
-			} else {
1127
-				session.Send(nil, prefix, "TOPIC", channel.name, topic)
1152
+			if session != rb.session {
1153
+				session.sendFromClientInternal(false, message.Time, message.Msgid, details.nickMask, details.accountName, nil, "TOPIC", chname, topic)
1128 1154
 			}
1129 1155
 		}
1130 1156
 	}
1131 1157
 
1158
+	channel.AddHistoryItem(history.Item{
1159
+		Type:        history.Topic,
1160
+		Nick:        details.nickMask,
1161
+		AccountName: details.accountName,
1162
+		Message:     message,
1163
+	})
1164
+
1132 1165
 	channel.MarkDirty(IncludeTopic)
1133 1166
 }
1134 1167
 
@@ -1251,13 +1284,16 @@ func (channel *Channel) SendSplitMessage(command string, minPrefixMode modes.Mod
1251 1284
 		}
1252 1285
 	}
1253 1286
 
1254
-	channel.AddHistoryItem(history.Item{
1255
-		Type:        histType,
1256
-		Message:     message,
1257
-		Nick:        nickmask,
1258
-		AccountName: account,
1259
-		Tags:        clientOnlyTags,
1260
-	})
1287
+	// #959: don't save STATUSMSG
1288
+	if minPrefixMode == modes.Mode(0) {
1289
+		channel.AddHistoryItem(history.Item{
1290
+			Type:        histType,
1291
+			Message:     message,
1292
+			Nick:        nickmask,
1293
+			AccountName: account,
1294
+			Tags:        clientOnlyTags,
1295
+		})
1296
+	}
1261 1297
 }
1262 1298
 
1263 1299
 func (channel *Channel) applyModeToMember(client *Client, change modes.ModeChange, rb *ResponseBuffer) (applied bool, result modes.ModeChange) {

+ 3
- 3
irc/chanserv.go View File

@@ -244,7 +244,7 @@ func csAmodeHandler(server *Server, client *Client, command string, params []str
244 244
 				if member.Account() == change.Arg {
245 245
 					applied, change := channel.applyModeToMember(client, change, rb)
246 246
 					if applied {
247
-						announceCmodeChanges(channel, modes.ModeChanges{change}, chanservMask, rb)
247
+						announceCmodeChanges(channel, modes.ModeChanges{change}, chanservMask, "*", rb)
248 248
 					}
249 249
 				}
250 250
 			}
@@ -291,7 +291,7 @@ func csOpHandler(server *Server, client *Client, command string, params []string
291 291
 		},
292 292
 		rb)
293 293
 	if applied {
294
-		announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, rb)
294
+		announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, "*", rb)
295 295
 	}
296 296
 
297 297
 	csNotice(rb, fmt.Sprintf(client.t("Successfully op'd in channel %s"), channelName))
@@ -343,7 +343,7 @@ func csRegisterHandler(server *Server, client *Client, command string, params []
343 343
 		},
344 344
 		rb)
345 345
 	if applied {
346
-		announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, rb)
346
+		announceCmodeChanges(channelInfo, modes.ModeChanges{change}, chanservMask, "*", rb)
347 347
 	}
348 348
 }
349 349
 

+ 8
- 0
irc/client.go View File

@@ -318,6 +318,10 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
318 318
 	session.idletimer.Initialize(session)
319 319
 	session.resetFakelag()
320 320
 
321
+	for _, defaultMode := range config.Accounts.defaultUserModes {
322
+		client.SetMode(defaultMode, true)
323
+	}
324
+
321 325
 	if conn.Config.TLSConfig != nil {
322 326
 		client.SetMode(modes.TLS, true)
323 327
 		// error is not useful to us here anyways so we can ignore it
@@ -371,6 +375,10 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
371 375
 		alwaysOn: true,
372 376
 	}
373 377
 
378
+	for _, defaultMode := range config.Accounts.defaultUserModes {
379
+		client.SetMode(defaultMode, true)
380
+	}
381
+
374 382
 	client.SetMode(modes.TLS, true)
375 383
 	client.writerSemaphore.Initialize(1)
376 384
 	client.history.Initialize(0, 0)

+ 10
- 1
irc/client_lookup_set.go View File

@@ -205,9 +205,18 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
205 205
 	// the client may just be changing case
206 206
 	if currentClient != nil && currentClient != client && session != nil {
207 207
 		// these conditions forbid reattaching to an existing session:
208
-		if registered || !bouncerAllowed || account == "" || account != currentClient.Account() || client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
208
+		if registered || !bouncerAllowed || account == "" || account != currentClient.Account() {
209 209
 			return "", errNicknameInUse
210 210
 		}
211
+		// check TLS modes
212
+		if client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
213
+			if useAccountName {
214
+				// #955: this is fatal because they can't fix it by trying a different nick
215
+				return "", errInsecureReattach
216
+			} else {
217
+				return "", errNicknameInUse
218
+			}
219
+		}
211 220
 		reattachSuccessful, numSessions, lastSeen := currentClient.AddSession(session)
212 221
 		if !reattachSuccessful {
213 222
 			return "", errNicknameInUse

+ 5
- 0
irc/config.go View File

@@ -263,6 +263,8 @@ type AccountConfig struct {
263 263
 		Exempted     []string
264 264
 		exemptedNets []net.IPNet
265 265
 	} `yaml:"require-sasl"`
266
+	DefaultUserModes   *string `yaml:"default-user-modes"`
267
+	defaultUserModes   modes.Modes
266 268
 	LDAP               ldap.ServerConfig
267 269
 	LoginThrottling    ThrottleConfig `yaml:"login-throttling"`
268 270
 	SkipServerPassword bool           `yaml:"skip-server-password"`
@@ -552,6 +554,7 @@ type Config struct {
552 554
 			OperatorOnly          bool `yaml:"operator-only"`
553 555
 			MaxChannelsPerAccount int  `yaml:"max-channels-per-account"`
554 556
 		}
557
+		ListDelay time.Duration `yaml:"list-delay"`
555 558
 	}
556 559
 
557 560
 	OperClasses map[string]*OperClassConfig `yaml:"oper-classes"`
@@ -984,6 +987,8 @@ func LoadConfig(filename string) (config *Config, err error) {
984 987
 		}
985 988
 	}
986 989
 
990
+	config.Accounts.defaultUserModes = ParseDefaultUserModes(config.Accounts.DefaultUserModes)
991
+
987 992
 	config.Accounts.RequireSasl.exemptedNets, err = utils.ParseNetList(config.Accounts.RequireSasl.Exempted)
988 993
 	if err != nil {
989 994
 		return nil, fmt.Errorf("Could not parse require-sasl exempted nets: %v", err.Error())

+ 1
- 0
irc/errors.go View File

@@ -42,6 +42,7 @@ var (
42 42
 	errNickMissing                    = errors.New("nick missing")
43 43
 	errNicknameInvalid                = errors.New("invalid nickname")
44 44
 	errNicknameInUse                  = errors.New("nickname in use")
45
+	errInsecureReattach               = errors.New("insecure reattach")
45 46
 	errNicknameReserved               = errors.New("nickname is reserved")
46 47
 	errNickAccountMismatch            = errors.New(`Your nickname must match your account name; try logging out and logging back in with SASL`)
47 48
 	errNoExistingBan                  = errors.New("Ban does not exist")

+ 40
- 11
irc/handlers.go View File

@@ -1408,6 +1408,14 @@ func languageHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *
1408 1408
 
1409 1409
 // LIST [<channel>{,<channel>}] [<elistcond>{,<elistcond>}]
1410 1410
 func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
1411
+	config := server.Config()
1412
+	if time.Since(client.ctime) < config.Channels.ListDelay && client.Account() == "" && !client.HasMode(modes.Operator) {
1413
+		remaining := time.Until(client.ctime.Add(config.Channels.ListDelay))
1414
+		csNotice(rb, fmt.Sprintf(client.t("This server requires that you wait %v after connecting before you can use /LIST. You have %v left."), config.Channels.ListDelay, remaining))
1415
+		rb.Add(nil, server.name, RPL_LISTEND, client.Nick(), client.t("End of LIST"))
1416
+		return false
1417
+	}
1418
+
1411 1419
 	// get channels
1412 1420
 	var channels []string
1413 1421
 	for _, param := range msg.Params {
@@ -1520,24 +1528,35 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
1520 1528
 	}
1521 1529
 	// process mode changes, include list operations (an empty set of changes does a list)
1522 1530
 	applied := channel.ApplyChannelModeChanges(client, msg.Command == "SAMODE", changes, rb)
1523
-	announceCmodeChanges(channel, applied, client.NickMaskString(), rb)
1531
+	details := client.Details()
1532
+	announceCmodeChanges(channel, applied, details.nickMask, details.accountName, rb)
1524 1533
 
1525 1534
 	return false
1526 1535
 }
1527 1536
 
1528
-func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source string, rb *ResponseBuffer) {
1537
+func announceCmodeChanges(channel *Channel, applied modes.ModeChanges, source, accountName string, rb *ResponseBuffer) {
1529 1538
 	// send out changes
1530 1539
 	if len(applied) > 0 {
1531
-		//TODO(dan): we should change the name of String and make it return a slice here
1532
-		args := append([]string{channel.name}, applied.Strings()...)
1533
-		rb.Add(nil, source, "MODE", args...)
1540
+		message := utils.MakeMessage("")
1541
+		changeStrings := applied.Strings()
1542
+		for _, changeString := range changeStrings {
1543
+			message.Split = append(message.Split, utils.MessagePair{Message: changeString})
1544
+		}
1545
+		args := append([]string{channel.name}, changeStrings...)
1546
+		rb.AddFromClient(message.Time, message.Msgid, source, accountName, nil, "MODE", args...)
1534 1547
 		for _, member := range channel.Members() {
1535 1548
 			for _, session := range member.Sessions() {
1536 1549
 				if session != rb.session {
1537
-					session.Send(nil, source, "MODE", args...)
1550
+					session.sendFromClientInternal(false, message.Time, message.Msgid, source, accountName, nil, "MODE", args...)
1538 1551
 				}
1539 1552
 			}
1540 1553
 		}
1554
+		channel.AddHistoryItem(history.Item{
1555
+			Type:        history.Mode,
1556
+			Nick:        source,
1557
+			AccountName: accountName,
1558
+			Message:     message,
1559
+		})
1541 1560
 	}
1542 1561
 }
1543 1562
 
@@ -2054,7 +2073,7 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
2054 2073
 	}
2055 2074
 
2056 2075
 	// must pass at least one check, and all enabled checks
2057
-	var checkPassed, checkFailed bool
2076
+	var checkPassed, checkFailed, passwordFailed bool
2058 2077
 	oper := server.GetOperator(msg.Params[0])
2059 2078
 	if oper != nil {
2060 2079
 		if oper.Fingerprint != "" {
@@ -2065,8 +2084,11 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
2065 2084
 			}
2066 2085
 		}
2067 2086
 		if !checkFailed && oper.Pass != nil {
2068
-			if len(msg.Params) == 1 || bcrypt.CompareHashAndPassword(oper.Pass, []byte(msg.Params[1])) != nil {
2087
+			if len(msg.Params) == 1 {
2069 2088
 				checkFailed = true
2089
+			} else if bcrypt.CompareHashAndPassword(oper.Pass, []byte(msg.Params[1])) != nil {
2090
+				checkFailed = true
2091
+				passwordFailed = true
2070 2092
 			} else {
2071 2093
 				checkPassed = true
2072 2094
 			}
@@ -2075,11 +2097,18 @@ func operHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
2075 2097
 
2076 2098
 	if !checkPassed || checkFailed {
2077 2099
 		rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.Nick(), client.t("Password incorrect"))
2078
-		client.Quit(client.t("Password incorrect"), rb.session)
2079
-		return true
2100
+		// #951: only disconnect them if we actually tried to check a password for them
2101
+		if passwordFailed {
2102
+			client.Quit(client.t("Password incorrect"), rb.session)
2103
+			return true
2104
+		} else {
2105
+			return false
2106
+		}
2080 2107
 	}
2081 2108
 
2082
-	applyOper(client, oper, rb)
2109
+	if oper != nil {
2110
+		applyOper(client, oper, rb)
2111
+	}
2083 2112
 	return false
2084 2113
 }
2085 2114
 

+ 1
- 0
irc/history/history.go View File

@@ -22,6 +22,7 @@ const (
22 22
 	Mode
23 23
 	Tagmsg
24 24
 	Nick
25
+	Topic
25 26
 )
26 27
 
27 28
 const (

+ 26
- 8
irc/modes.go View File

@@ -20,6 +20,10 @@ var (
20 20
 	DefaultChannelModes = modes.Modes{
21 21
 		modes.NoOutside, modes.OpOnlyTopic,
22 22
 	}
23
+
24
+	// DefaultUserModes are set on all users when they login.
25
+	// this can be overridden in the `accounts` config, with the `default-user-modes` key
26
+	DefaultUserModes = modes.Modes{}
23 27
 )
24 28
 
25 29
 // ApplyUserModeChanges applies the given changes, and returns the applied changes.
@@ -102,21 +106,35 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
102 106
 	return applied
103 107
 }
104 108
 
109
+// parseDefaultModes uses the provided mode change parser to parse the rawModes.
110
+func parseDefaultModes(rawModes string, parser func(params ...string) (modes.ModeChanges, map[rune]bool)) modes.Modes {
111
+	modeChangeStrings := strings.Fields(rawModes)
112
+	modeChanges, _ := parser(modeChangeStrings...)
113
+	defaultModes := make(modes.Modes, 0)
114
+	for _, modeChange := range modeChanges {
115
+		if modeChange.Op == modes.Add {
116
+			defaultModes = append(defaultModes, modeChange.Mode)
117
+		}
118
+	}
119
+	return defaultModes
120
+}
121
+
105 122
 // ParseDefaultChannelModes parses the `default-modes` line of the config
106 123
 func ParseDefaultChannelModes(rawModes *string) modes.Modes {
107 124
 	if rawModes == nil {
108 125
 		// not present in config, fall back to compile-time default
109 126
 		return DefaultChannelModes
110 127
 	}
111
-	modeChangeStrings := strings.Fields(*rawModes)
112
-	modeChanges, _ := modes.ParseChannelModeChanges(modeChangeStrings...)
113
-	defaultChannelModes := make(modes.Modes, 0)
114
-	for _, modeChange := range modeChanges {
115
-		if modeChange.Op == modes.Add {
116
-			defaultChannelModes = append(defaultChannelModes, modeChange.Mode)
117
-		}
128
+	return parseDefaultModes(*rawModes, modes.ParseChannelModeChanges)
129
+}
130
+
131
+// ParseDefaultUserModes parses the `default-user-modes` line of the config
132
+func ParseDefaultUserModes(rawModes *string) modes.Modes {
133
+	if rawModes == nil {
134
+		// not present in config, fall back to compile-time default
135
+		return DefaultUserModes
118 136
 	}
119
-	return defaultChannelModes
137
+	return parseDefaultModes(*rawModes, modes.ParseUserModeChanges)
120 138
 }
121 139
 
122 140
 // ApplyChannelModeChanges applies a given set of mode changes.

+ 25
- 0
irc/modes_test.go View File

@@ -35,6 +35,31 @@ func TestParseDefaultChannelModes(t *testing.T) {
35 35
 	}
36 36
 }
37 37
 
38
+func TestParseDefaultUserModes(t *testing.T) {
39
+	iR := "+iR"
40
+	i := "+i"
41
+	empty := ""
42
+	rminusi := "+R -i"
43
+
44
+	var parseTests = []struct {
45
+		raw      *string
46
+		expected modes.Modes
47
+	}{
48
+		{&iR, modes.Modes{modes.Invisible, modes.RegisteredOnly}},
49
+		{&i, modes.Modes{modes.Invisible}},
50
+		{&empty, modes.Modes{}},
51
+		{&rminusi, modes.Modes{modes.RegisteredOnly}},
52
+		{nil, modes.Modes{}},
53
+	}
54
+
55
+	for _, testcase := range parseTests {
56
+		result := ParseDefaultUserModes(testcase.raw)
57
+		if !reflect.DeepEqual(result, testcase.expected) {
58
+			t.Errorf("expected modes %s, got %s", testcase.expected, result)
59
+		}
60
+	}
61
+}
62
+
38 63
 func TestUmodeGreaterThan(t *testing.T) {
39 64
 	if !umodeGreaterThan(modes.Halfop, modes.Voice) {
40 65
 		t.Errorf("expected Halfop > Voice")

+ 5
- 6
irc/nickname.go View File

@@ -25,12 +25,11 @@ var (
25 25
 	restrictedSkeletons       = make(map[string]bool)
26 26
 )
27 27
 
28
-// returns whether the change succeeded or failed
29
-func performNickChange(server *Server, client *Client, target *Client, session *Session, nickname string, rb *ResponseBuffer) bool {
28
+func performNickChange(server *Server, client *Client, target *Client, session *Session, nickname string, rb *ResponseBuffer) error {
30 29
 	currentNick := client.Nick()
31 30
 	details := target.Details()
32 31
 	if details.nick == nickname {
33
-		return true
32
+		return nil
34 33
 	}
35 34
 	hadNick := details.nick != "*"
36 35
 	origNickMask := details.nickMask
@@ -52,7 +51,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
52 51
 		rb.Add(nil, server.name, ERR_UNKNOWNERROR, currentNick, "NICK", fmt.Sprintf(client.t("Could not set or change nickname: %s"), err.Error()))
53 52
 	}
54 53
 	if err != nil {
55
-		return false
54
+		return err
56 55
 	}
57 56
 
58 57
 	message := utils.MakeMessage("")
@@ -88,7 +87,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
88 87
 		client.server.monitorManager.AlertAbout(target, true)
89 88
 		target.nickTimer.Touch(rb)
90 89
 	} // else: these will be deferred to the end of registration (see #572)
91
-	return true
90
+	return nil
92 91
 }
93 92
 
94 93
 func (server *Server) RandomlyRename(client *Client) {
@@ -124,7 +123,7 @@ func fixupNickEqualsAccount(client *Client, rb *ResponseBuffer, config *Config)
124 123
 	if !client.registered {
125 124
 		return true
126 125
 	}
127
-	if !performNickChange(client.server, client, client, rb.session, client.AccountName(), rb) {
126
+	if performNickChange(client.server, client, client, rb.session, client.AccountName(), rb) != nil {
128 127
 		client.server.accounts.Logout(client)
129 128
 		nsNotice(rb, client.t("A client is already using that account; try logging out and logging back in with SASL"))
130 129
 		return false

+ 2
- 0
irc/nickserv.go View File

@@ -841,6 +841,8 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params
841 841
 	if erase {
842 842
 		// account may not be in a loadable state, e.g., if it was unregistered
843 843
 		accountName = username
844
+		// make the confirmation code nondeterministic for ERASE
845
+		registeredAt = server.ctime
844 846
 	} else {
845 847
 		account, err := server.accounts.LoadAccount(username)
846 848
 		if err == errAccountDoesNotExist {

+ 7
- 4
irc/server.go View File

@@ -438,11 +438,14 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) {
438 438
 	}
439 439
 
440 440
 	rb := NewResponseBuffer(session)
441
-	nickAssigned := performNickChange(server, c, c, session, c.preregNick, rb)
441
+	nickError := performNickChange(server, c, c, session, c.preregNick, rb)
442 442
 	rb.Send(true)
443
-	if !nickAssigned {
443
+	if nickError == errInsecureReattach {
444
+		c.Quit(c.t("You can't mix secure and insecure connections to this account"), nil)
445
+		return true
446
+	} else if nickError != nil {
444 447
 		c.preregNick = ""
445
-		return
448
+		return false
446 449
 	}
447 450
 
448 451
 	if session.client != c {
@@ -450,7 +453,7 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) {
450 453
 		// we'll play the reg burst later, on the new goroutine associated with
451 454
 		// (thisSession, otherClient). This is to avoid having to transfer state
452 455
 		// like nickname, hostname, etc. to show the correct values in the reg burst.
453
-		return
456
+		return false
454 457
 	}
455 458
 
456 459
 	// check KLINEs

+ 2
- 2
languages/ro-RO-irc.lang.json View File

@@ -113,7 +113,7 @@
113 113
   "Former Core Developers:": "Foști dezvoltatori:",
114 114
   "Founder: %s": "Fondator: %s",
115 115
   "GHOSTed by %s": "%s a folosit GHOST",
116
-  "Given current server settings, the channel history setting is: %s": "",
116
+  "Given current server settings, the channel history setting is: %s": "Conform setărilor actuale ale serverului, setarea pentru istoricul de canal este: %s",
117 117
   "Given current server settings, your client is always-on": "Conform setărilor actuale ale serverului, clientul tău are activă opțiunea de conectare permanentă",
118 118
   "Given current server settings, your client is not always-on": "Conform setărilor actuale ale serverului, clientul tău nu are activă opțiunea de conectare permanentă",
119 119
   "Given current server settings, your direct message history setting is: %s": "Conform setărilor actuale ale serverului, setarea aferentă istoricului de mesaje este: %s",
@@ -228,7 +228,7 @@
228 228
   "Successfully ungrouped nick %s with your account": "Pseudonimul %s a fost degrupat de la contul tău, cu succes",
229 229
   "Successfully unpurged channel %s from the server": "Canalul %s nu mai este purjat din server",
230 230
   "Successfully unregistered account %s": "Contul %s a fost șters cu succes",
231
-  "That account is set to always-on; try logging out and logging back in with SASL": "",
231
+  "That account is set to always-on; try logging out and logging back in with SASL": "Acel cont are activă setarea conectare-permanentă; încearcă să te deconectezi și să te reconectezi cu SASL",
232 232
   "That certificate fingerprint is already associated with another account": "Amprenta de certificat este deja asociată unui alt cont",
233 233
   "That certificate fingerprint was already authorized": "Amprenta certificatului a fost autorizată deja",
234 234
   "That channel is not registered": "Acel canal nu este înregistrat",

+ 10
- 0
oragono.yaml View File

@@ -455,6 +455,12 @@ accounts:
455 455
         offer-list:
456 456
             #- "oragono.test"
457 457
 
458
+    # modes that are set by default when a user connects
459
+    # if unset, no user modes will be set by default
460
+    # +i is invisible (a user's channels are hidden from whois replies)
461
+    # see  /QUOTE HELP umodes  for more user modes
462
+    default-user-modes: +i
463
+
458 464
     # support for deferring password checking to an external LDAP server
459 465
     # you should probably ignore this section! consult the grafana docs for details:
460 466
     # https://grafana.com/docs/grafana/latest/auth/ldap/
@@ -516,6 +522,10 @@ channels:
516 522
         # how many channels can each account register?
517 523
         max-channels-per-account: 15
518 524
 
525
+    # as a crude countermeasure against spambots, anonymous connections younger
526
+    # than this value will get an empty response to /LIST (a time period of 0 disables)
527
+    list-delay: 0s
528
+
519 529
 # operator classes
520 530
 oper-classes:
521 531
     # local operator

Loading…
Cancel
Save