diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e53671d9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +vendor/* linguist-vendored +languages/* linguist-vendored diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 00000000..8412eb12 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,56 @@ +# .goreleaser.yml +# Build customization +project_name: ergo +builds: + - main: ergo.go + binary: ergo + goos: + - freebsd + - windows + - darwin + - linux + goarch: + - "386" + - amd64 + - arm + - arm64 + goarm: + - 6 + - 7 + ignore: + - goos: windows + goarch: arm + - goos: darwin + goarch: arm + - goos: darwin + goarch: 386 + - goos: freebsd + goarch: arm + - goos: freebsd + goarch: arm64 + flags: + - -trimpath + +archives: + - name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" + format: tar.gz + replacements: + amd64: x86_64 + darwin: macos + format_overrides: + - goos: windows + format: zip + files: + - README + - CHANGELOG.md + - ergo.motd + - default.yaml + - traditional.yaml + - docs/MANUAL.md + - docs/USERGUIDE.md + - languages/*.yaml + - languages/*.json + - languages/*.md + wrap_in_directory: true +checksum: + name_template: "{{ .ProjectName }}-{{ .Version }}-checksums.txt" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ab45eb3c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +## build ergo binary +FROM golang:1.17-alpine AS build-env + +RUN apk add -U --force-refresh --no-cache --purge --clean-protected -l -u make + +# copy ergo source +WORKDIR /go/src/github.com/ergochat/ergo +COPY . . + +# modify default config file so that it doesn't die on IPv6 +# and so it can be exposed via 6667 by default +RUN sed -i 's/^\(\s*\)\"127.0.0.1:6667\":.*$/\1":6667":/' /go/src/github.com/ergochat/ergo/default.yaml && \ + sed -i 's/^\s*\"\[::1\]:6667\":.*$//' /go/src/github.com/ergochat/ergo/default.yaml + +# compile +RUN make + +## build ergo container +FROM alpine:3.13 + +# metadata +LABEL maintainer="Daniel Oaks ,Daniel Thamdrup " \ + description="Ergo is a modern, experimental IRC server written in Go" + +# standard ports listened on +EXPOSE 6667/tcp 6697/tcp + +# ergo itself +COPY --from=build-env /go/bin/ergo \ + /go/src/github.com/ergochat/ergo/default.yaml \ + /go/src/github.com/ergochat/ergo/distrib/docker/run.sh \ + /ircd-bin/ +COPY --from=build-env /go/src/github.com/ergochat/ergo/languages /ircd-bin/languages/ + +# running volume holding config file, db, certs +VOLUME /ircd +WORKDIR /ircd + +# default motd +COPY --from=build-env /go/src/github.com/ergochat/ergo/ergo.motd /ircd/ergo.motd + +# launch +ENTRYPOINT ["/ircd-bin/run.sh"] + +# # uncomment to debug +# RUN apk add --no-cache bash +# RUN apk add --no-cache vim +# CMD /bin/bash diff --git a/LICENSE b/LICENSE index 779814db..acbac131 100644 --- a/LICENSE +++ b/LICENSE @@ -4,7 +4,6 @@ Copyright (c) 2012-2014 Jeremy Latt Copyright (c) 2014-2015 Edmund Huber Copyright (c) 2016-2020 Daniel Oaks Copyright (c) 2017-2020 Shivaram Lingamneni -Copyright (c) 2020-2022 kayos@tcp.direct Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/irc/accounts.go b/irc/accounts.go index 808ab0b0..399d424b 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -194,8 +194,8 @@ func (am *AccountManager) buildNickToAccountIndex(config *Config) { if config.Accounts.NickReservation.Method == NickEnforcementStrict { unregisteredPrefix := fmt.Sprintf(keyAccountUnregistered, "") - err := am.server.store.View(func(tx *buntdb.Tx) error { - err = tx.AscendGreaterOrEqual("", unregisteredPrefix, func(key, value string) bool { + am.server.store.View(func(tx *buntdb.Tx) error { + tx.AscendGreaterOrEqual("", unregisteredPrefix, func(key, value string) bool { if !strings.HasPrefix(key, unregisteredPrefix) { return false } @@ -206,11 +206,8 @@ func (am *AccountManager) buildNickToAccountIndex(config *Config) { skeletonToAccount[skeleton] = account return true }) - return err + return nil }) - if err != nil { - am.server.logger.Error("internal", "buildNickToAccountIndex buntdb transaction error during AscendGreaterOrEqual", err.Error()) - } } if err != nil { @@ -406,10 +403,7 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames if err != nil { return err } - err = creds.AddCertfp(certfp) - if err != nil { - return err - } + creds.AddCertfp(certfp) credStr, err := creds.Serialize() if err != nil { return err @@ -477,7 +471,7 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames code, err := am.dispatchCallback(client, account, callbackNamespace, callbackValue) if err != nil { - _ = am.Unregister(casefoldedAccount, true) + am.Unregister(casefoldedAccount, true) return ®istrationCallbackError{underlying: err} } else { am.server.logger.Info("accounts", @@ -543,11 +537,11 @@ func (am *AccountManager) setPassword(accountName string, password string, hasPr credKey := fmt.Sprintf(keyAccountCredentials, cfAccount) var credStr string - err = am.server.store.View(func(tx *buntdb.Tx) error { + am.server.store.View(func(tx *buntdb.Tx) error { // no need to check verification status here or below; // you either need to be auth'ed to the account or be an oper to do this credStr, err = tx.Get(credKey) - return err + return nil }) if err != nil { @@ -657,14 +651,12 @@ func (am *AccountManager) saveLastSeen(account string, lastSeen map[string]time. val = string(text) } err := am.server.store.Update(func(tx *buntdb.Tx) error { - var err error - switch val { - case "": - _, err = tx.Delete(key) - default: - _, _, err = tx.Set(key, val, nil) + if val != "" { + tx.Set(key, val, nil) + } else { + tx.Delete(key) } - return err + return nil }) if err != nil { am.server.logger.Error("internal", "error persisting lastSeen", account, err.Error()) @@ -722,9 +714,9 @@ func (am *AccountManager) addRemoveCertfp(account, certfp string, add bool, hasP credKey := fmt.Sprintf(keyAccountCredentials, cfAccount) var credStr string - err = am.server.store.View(func(tx *buntdb.Tx) error { + am.server.store.View(func(tx *buntdb.Tx) error { credStr, err = tx.Get(credKey) - return err + return nil }) if err != nil { @@ -741,18 +733,16 @@ func (am *AccountManager) addRemoveCertfp(account, certfp string, add bool, hasP return errCredsExternallyManaged } - switch add { - case true: + if add { err = creds.AddCertfp(certfp) - default: + } else { err = creds.RemoveCertfp(certfp) } - if err != nil { return err } - if creds.Empty() { + if creds.Empty() && !hasPrivs { return errEmptyCredentials } diff --git a/irc/database.go b/irc/database.go index 92e4ba1e..dc5a69fa 100644 --- a/irc/database.go +++ b/irc/database.go @@ -24,7 +24,8 @@ const ( // 'version' of the database schema keySchemaVersion = "db.version" // latest schema of the db - latestDbSchema = 23 + latestDbSchema = 22 + keyCloakSecret = "crypto.cloak_secret" ) @@ -1112,56 +1113,6 @@ func schemaChangeV21To22(config *Config, tx *buntdb.Tx) error { return nil } -func schemaChangeV22To23(config *Config, tx *buntdb.Tx) error { - type accountSettingsv23 struct { - NickEnforcement NickEnforcementMethod - AllowBouncer MulticlientAllowedSetting - AutoreplayMissed bool - AutoAway PersistentStatus - GitAuth bool - Email string - } - - var accounts []string - var serializedSettings []string - settingsPrefix := "account.settings " - err := tx.AscendGreaterOrEqual("", settingsPrefix, func(key, value string) bool { - if !strings.HasPrefix(key, settingsPrefix) { - return false - } - if value == "" { - return true - } - account := strings.TrimPrefix(key, settingsPrefix) - var settings accountSettingsv23 - err := json.Unmarshal([]byte(value), &settings) - if err != nil { - log.Printf("error (v22-23) processing settings for %s: %v\n", account, err) - return true - } - - b, err := json.Marshal(settings) - - if err != nil { - log.Printf("error (v22-23) processing settings for %s: %v\n", account, err) - return true - } - - accounts = append(accounts, account) - serializedSettings = append(serializedSettings, string(b)) - return true - }) - - if err != nil { - log.Printf("error (v22-23) processing db transaction (invalid index): %v\n", err) - } - - for i, account := range accounts { - tx.Set(settingsPrefix+account, serializedSettings[i], nil) - } - return nil -} - func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) { for _, change := range allChanges { if initialVersion == change.InitialVersion { @@ -1277,9 +1228,4 @@ var allChanges = []SchemaChange{ TargetVersion: 22, Changer: schemaChangeV21To22, }, - { - InitialVersion: 22, - TargetVersion: 23, - Changer: schemaChangeV22To23, - }, } diff --git a/irc/nickserv.go b/irc/nickserv.go index b80bb8fc..548a5f77 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -421,6 +421,15 @@ func displaySetting(service *ircService, settingName string, settings AccountSet service.Notice(rb, client.t("Multiclient functionality is currently enabled for your account")) } } + case "always-on": + stored := settings.AlwaysOn + actual := persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, stored) + service.Notice(rb, fmt.Sprintf(client.t("Your stored always-on setting is: %s"), userPersistentStatusToString(stored))) + if actual { + service.Notice(rb, client.t("Given current server settings, your client is always-on")) + } else { + service.Notice(rb, client.t("Given current server settings, your client is not always-on")) + } case "auto-away": stored := settings.AutoAway alwaysOn := persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, settings.AlwaysOn) @@ -491,6 +500,22 @@ func nsSetHandler(service *ircService, server *Server, client *Client, command s if err == nil { finalSettings.NickEnforcement = method // success } + case "autoreplay-lines": + var newValue *int + if strings.ToLower(params[1]) != "default" { + val, err_ := strconv.Atoi(params[1]) + if err_ != nil || val < 0 { + err = errInvalidParams + break + } + newValue = new(int) + *newValue = val + } + munger = func(in AccountSettings) (out AccountSettings, err error) { + out = in + out.AutoreplayLines = newValue + return + } case "multiclient": var newValue MulticlientAllowedSetting if strings.ToLower(params[1]) == "default" { @@ -521,6 +546,39 @@ func nsSetHandler(service *ircService, server *Server, client *Client, command s return } } + case "always-on": + // #821: it's problematic to alter the value of always-on if you're not + // the (actual or potential) always-on client yourself. make an exception + // for `saset` to give operators an escape hatch (any consistency problems + // can probably be fixed by restarting the server): + if command != "saset" { + details := client.Details() + if details.nick != details.accountName { + err = errNickAccountMismatch + } + } + if err == nil { + var newValue PersistentStatus + newValue, err = persistentStatusFromString(params[1]) + // "opt-in" and "opt-out" don't make sense as user preferences + if err == nil && newValue != PersistentOptIn && newValue != PersistentOptOut { + munger = func(in AccountSettings) (out AccountSettings, err error) { + out = in + out.AlwaysOn = newValue + return + } + } + } + case "autoreplay-missed": + var newValue bool + newValue, err = utils.StringToBool(params[1]) + if err == nil { + munger = func(in AccountSettings) (out AccountSettings, err error) { + out = in + out.AutoreplayMissed = newValue + return + } + } case "auto-away": var newValue PersistentStatus newValue, err = persistentStatusFromString(params[1])