1// Copyright (c) 2012-2014 Jeremy Latt 2// Copyright (c) 2014-2015 Edmund Huber 3// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net> 4// released under the MIT license 5 6package irc 7 8import ( 9 "bytes" 10 "crypto/tls" 11 "crypto/x509" 12 "errors" 13 "fmt" 14 "io" 15 "log" 16 "net" 17 "os" 18 "path/filepath" 19 "reflect" 20 "regexp" 21 "runtime" 22 "strconv" 23 "strings" 24 "time" 25 26 "code.cloudfoundry.org/bytefmt" 27 "github.com/ergochat/irc-go/ircfmt" 28 "gopkg.in/yaml.v2" 29 30 "github.com/ergochat/ergo/irc/caps" 31 "github.com/ergochat/ergo/irc/cloaks" 32 "github.com/ergochat/ergo/irc/connection_limits" 33 "github.com/ergochat/ergo/irc/custime" 34 "github.com/ergochat/ergo/irc/email" 35 "github.com/ergochat/ergo/irc/isupport" 36 "github.com/ergochat/ergo/irc/jwt" 37 "github.com/ergochat/ergo/irc/languages" 38 "github.com/ergochat/ergo/irc/logger" 39 "github.com/ergochat/ergo/irc/modes" 40 "github.com/ergochat/ergo/irc/mysql" 41 "github.com/ergochat/ergo/irc/passwd" 42 "github.com/ergochat/ergo/irc/utils" 43) 44 45// here's how this works: exported (capitalized) members of the config structs 46// are defined in the YAML file and deserialized directly from there. They may 47// be postprocessed and overwritten by LoadConfig. Unexported (lowercase) members 48// are derived from the exported members in LoadConfig. 49 50// TLSListenConfig defines configuration options for listening on TLS. 51type TLSListenConfig struct { 52 Cert string 53 Key string 54 Proxy bool // XXX: legacy key: it's preferred to specify this directly in listenerConfigBlock 55} 56 57// This is the YAML-deserializable type of the value of the `Server.Listeners` map 58type listenerConfigBlock struct { 59 // normal TLS configuration, with a single certificate: 60 TLS TLSListenConfig 61 // SNI configuration, with multiple certificates: 62 TLSCertificates []TLSListenConfig `yaml:"tls-certificates"` 63 MinTLSVersion string `yaml:"min-tls-version"` 64 Proxy bool 65 Tor bool 66 STSOnly bool `yaml:"sts-only"` 67 WebSocket bool 68 HideSTS bool `yaml:"hide-sts"` 69} 70 71type HistoryCutoff uint 72 73const ( 74 HistoryCutoffDefault HistoryCutoff = iota 75 HistoryCutoffNone 76 HistoryCutoffRegistrationTime 77 HistoryCutoffJoinTime 78) 79 80func historyCutoffToString(restriction HistoryCutoff) string { 81 switch restriction { 82 case HistoryCutoffDefault: 83 return "default" 84 case HistoryCutoffNone: 85 return "none" 86 case HistoryCutoffRegistrationTime: 87 return "registration-time" 88 case HistoryCutoffJoinTime: 89 return "join-time" 90 default: 91 return "" 92 } 93} 94 95func historyCutoffFromString(str string) (result HistoryCutoff, err error) { 96 switch strings.ToLower(str) { 97 case "default": 98 return HistoryCutoffDefault, nil 99 case "none", "disabled", "off", "false": 100 return HistoryCutoffNone, nil 101 case "registration-time": 102 return HistoryCutoffRegistrationTime, nil 103 case "join-time": 104 return HistoryCutoffJoinTime, nil 105 default: 106 return HistoryCutoffDefault, errInvalidParams 107 } 108} 109 110type PersistentStatus uint 111 112const ( 113 PersistentUnspecified PersistentStatus = iota 114 PersistentDisabled 115 PersistentOptIn 116 PersistentOptOut 117 PersistentMandatory 118) 119 120func persistentStatusToString(status PersistentStatus) string { 121 switch status { 122 case PersistentUnspecified: 123 return "default" 124 case PersistentDisabled: 125 return "disabled" 126 case PersistentOptIn: 127 return "opt-in" 128 case PersistentOptOut: 129 return "opt-out" 130 case PersistentMandatory: 131 return "mandatory" 132 default: 133 return "" 134 } 135} 136 137func persistentStatusFromString(status string) (PersistentStatus, error) { 138 switch strings.ToLower(status) { 139 case "default": 140 return PersistentUnspecified, nil 141 case "": 142 return PersistentDisabled, nil 143 case "opt-in": 144 return PersistentOptIn, nil 145 case "opt-out": 146 return PersistentOptOut, nil 147 case "mandatory": 148 return PersistentMandatory, nil 149 default: 150 b, err := utils.StringToBool(status) 151 if b { 152 return PersistentMandatory, err 153 } else { 154 return PersistentDisabled, err 155 } 156 } 157} 158 159func (ps *PersistentStatus) UnmarshalYAML(unmarshal func(interface{}) error) error { 160 var orig string 161 var err error 162 if err = unmarshal(&orig); err != nil { 163 return err 164 } 165 result, err := persistentStatusFromString(orig) 166 if err == nil { 167 if result == PersistentUnspecified { 168 result = PersistentDisabled 169 } 170 *ps = result 171 } else { 172 err = fmt.Errorf("invalid value `%s` for server persistence status: %w", orig, err) 173 } 174 return err 175} 176 177func persistenceEnabled(serverSetting, clientSetting PersistentStatus) (enabled bool) { 178 if serverSetting == PersistentDisabled { 179 return false 180 } else if serverSetting == PersistentMandatory { 181 return true 182 } else if clientSetting == PersistentDisabled { 183 return false 184 } else if clientSetting == PersistentMandatory { 185 return true 186 } else if serverSetting == PersistentOptOut { 187 return true 188 } else { 189 return false 190 } 191} 192 193type HistoryStatus uint 194 195const ( 196 HistoryDefault HistoryStatus = iota 197 HistoryDisabled 198 HistoryEphemeral 199 HistoryPersistent 200) 201 202func historyStatusFromString(str string) (status HistoryStatus, err error) { 203 switch strings.ToLower(str) { 204 case "default": 205 return HistoryDefault, nil 206 case "ephemeral": 207 return HistoryEphemeral, nil 208 case "persistent": 209 return HistoryPersistent, nil 210 default: 211 b, err := utils.StringToBool(str) 212 if b { 213 return HistoryPersistent, err 214 } else { 215 return HistoryDisabled, err 216 } 217 } 218} 219 220func historyStatusToString(status HistoryStatus) string { 221 switch status { 222 case HistoryDefault: 223 return "default" 224 case HistoryDisabled: 225 return "disabled" 226 case HistoryEphemeral: 227 return "ephemeral" 228 case HistoryPersistent: 229 return "persistent" 230 default: 231 return "" 232 } 233} 234 235// XXX you must have already checked History.Enabled before calling this 236func historyEnabled(serverSetting PersistentStatus, localSetting HistoryStatus) (result HistoryStatus) { 237 switch serverSetting { 238 case PersistentMandatory: 239 return HistoryPersistent 240 case PersistentOptOut: 241 if localSetting == HistoryDefault { 242 return HistoryPersistent 243 } else { 244 return localSetting 245 } 246 case PersistentOptIn: 247 switch localSetting { 248 case HistoryPersistent: 249 return HistoryPersistent 250 case HistoryEphemeral, HistoryDefault: 251 return HistoryEphemeral 252 default: 253 return HistoryDisabled 254 } 255 case PersistentDisabled: 256 if localSetting == HistoryDisabled { 257 return HistoryDisabled 258 } else { 259 return HistoryEphemeral 260 } 261 default: 262 // PersistentUnspecified: shouldn't happen because the deserializer converts it 263 // to PersistentDisabled 264 if localSetting == HistoryDefault { 265 return HistoryEphemeral 266 } else { 267 return localSetting 268 } 269 } 270} 271 272type MulticlientConfig struct { 273 Enabled bool 274 AllowedByDefault bool `yaml:"allowed-by-default"` 275 AlwaysOn PersistentStatus `yaml:"always-on"` 276 AutoAway PersistentStatus `yaml:"auto-away"` 277 AlwaysOnExpiration custime.Duration `yaml:"always-on-expiration"` 278} 279 280type throttleConfig struct { 281 Enabled bool 282 Duration time.Duration 283 MaxAttempts int `yaml:"max-attempts"` 284} 285 286type ThrottleConfig struct { 287 throttleConfig 288} 289 290func (t *ThrottleConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err error) { 291 // note that this technique only works if the zero value of the struct 292 // doesn't need any postprocessing (because if the field is omitted entirely 293 // from the YAML, then UnmarshalYAML won't be called at all) 294 if err = unmarshal(&t.throttleConfig); err != nil { 295 return 296 } 297 if !t.Enabled { 298 t.MaxAttempts = 0 // limit of 0 means disabled 299 } 300 return 301} 302 303type AccountConfig struct { 304 Registration AccountRegistrationConfig 305 AuthenticationEnabled bool `yaml:"authentication-enabled"` 306 AdvertiseSCRAM bool `yaml:"advertise-scram"` // undocumented, see #1782 307 RequireSasl struct { 308 Enabled bool 309 Exempted []string 310 exemptedNets []net.IPNet 311 } `yaml:"require-sasl"` 312 DefaultUserModes *string `yaml:"default-user-modes"` 313 defaultUserModes modes.Modes 314 LoginThrottling ThrottleConfig `yaml:"login-throttling"` 315 SkipServerPassword bool `yaml:"skip-server-password"` 316 LoginViaPassCommand bool `yaml:"login-via-pass-command"` 317 NickReservation struct { 318 Enabled bool 319 AdditionalNickLimit int `yaml:"additional-nick-limit"` 320 Method NickEnforcementMethod 321 AllowCustomEnforcement bool `yaml:"allow-custom-enforcement"` 322 // RenamePrefix is the legacy field, GuestFormat is the new version 323 RenamePrefix string `yaml:"rename-prefix"` 324 GuestFormat string `yaml:"guest-nickname-format"` 325 guestRegexp *regexp.Regexp 326 guestRegexpFolded *regexp.Regexp 327 ForceGuestFormat bool `yaml:"force-guest-format"` 328 ForceNickEqualsAccount bool `yaml:"force-nick-equals-account"` 329 ForbidAnonNickChanges bool `yaml:"forbid-anonymous-nick-changes"` 330 } `yaml:"nick-reservation"` 331 Multiclient MulticlientConfig 332 Bouncer *MulticlientConfig // # handle old name for 'multiclient' 333 VHosts VHostConfig 334 AuthScript AuthScriptConfig `yaml:"auth-script"` 335} 336 337type ScriptConfig struct { 338 Enabled bool 339 Command string 340 Args []string 341 Timeout time.Duration 342 KillTimeout time.Duration `yaml:"kill-timeout"` 343 MaxConcurrency uint `yaml:"max-concurrency"` 344} 345 346type AuthScriptConfig struct { 347 ScriptConfig `yaml:",inline"` 348 Autocreate bool 349} 350 351// AccountRegistrationConfig controls account registration. 352type AccountRegistrationConfig struct { 353 Enabled bool 354 AllowBeforeConnect bool `yaml:"allow-before-connect"` 355 Throttling ThrottleConfig 356 // new-style (v2.4 email verification config): 357 EmailVerification email.MailtoConfig `yaml:"email-verification"` 358 // old-style email verification config, with "callbacks": 359 LegacyEnabledCallbacks []string `yaml:"enabled-callbacks"` 360 LegacyCallbacks struct { 361 Mailto email.MailtoConfig 362 } `yaml:"callbacks"` 363 VerifyTimeout custime.Duration `yaml:"verify-timeout"` 364 BcryptCost uint `yaml:"bcrypt-cost"` 365} 366 367type VHostConfig struct { 368 Enabled bool 369 MaxLength int `yaml:"max-length"` 370 ValidRegexpRaw string `yaml:"valid-regexp"` 371 validRegexp *regexp.Regexp 372} 373 374type NickEnforcementMethod int 375 376const ( 377 // NickEnforcementOptional is the zero value; it serializes to 378 // "optional" in the yaml config, and "default" as an arg to `NS ENFORCE`. 379 // in both cases, it means "defer to the other source of truth", i.e., 380 // in the config, defer to the user's custom setting, and as a custom setting, 381 // defer to the default in the config. if both are NickEnforcementOptional then 382 // there is no enforcement. 383 // XXX: these are serialized as numbers in the database, so beware of collisions 384 // when refactoring (any numbers currently in use must keep their meanings, or 385 // else be fixed up by a schema change) 386 NickEnforcementOptional NickEnforcementMethod = iota 387 NickEnforcementNone 388 NickEnforcementStrict 389) 390 391func nickReservationToString(method NickEnforcementMethod) string { 392 switch method { 393 case NickEnforcementOptional: 394 return "default" 395 case NickEnforcementNone: 396 return "none" 397 case NickEnforcementStrict: 398 return "strict" 399 default: 400 return "" 401 } 402} 403 404func nickReservationFromString(method string) (NickEnforcementMethod, error) { 405 switch strings.ToLower(method) { 406 case "default": 407 return NickEnforcementOptional, nil 408 case "optional": 409 return NickEnforcementOptional, nil 410 case "none": 411 return NickEnforcementNone, nil 412 case "strict": 413 return NickEnforcementStrict, nil 414 default: 415 return NickEnforcementOptional, fmt.Errorf("invalid nick-reservation.method value: %s", method) 416 } 417} 418 419func (nr *NickEnforcementMethod) UnmarshalYAML(unmarshal func(interface{}) error) error { 420 var orig string 421 var err error 422 if err = unmarshal(&orig); err != nil { 423 return err 424 } 425 method, err := nickReservationFromString(orig) 426 if err == nil { 427 *nr = method 428 } else { 429 err = fmt.Errorf("invalid value `%s` for nick enforcement method: %w", orig, err) 430 } 431 return err 432} 433 434func (cm *Casemapping) UnmarshalYAML(unmarshal func(interface{}) error) (err error) { 435 var orig string 436 if err = unmarshal(&orig); err != nil { 437 return err 438 } 439 440 var result Casemapping 441 switch strings.ToLower(orig) { 442 case "ascii": 443 result = CasemappingASCII 444 case "precis", "rfc7613", "rfc8265": 445 result = CasemappingPRECIS 446 case "permissive", "fun": 447 result = CasemappingPermissive 448 default: 449 return fmt.Errorf("invalid casemapping value: %s", orig) 450 } 451 *cm = result 452 return nil 453} 454 455// OperClassConfig defines a specific operator class. 456type OperClassConfig struct { 457 Title string 458 WhoisLine string 459 Extends string 460 Capabilities []string 461} 462 463// OperConfig defines a specific operator's configuration. 464type OperConfig struct { 465 Class string 466 Vhost string 467 WhoisLine string `yaml:"whois-line"` 468 Password string 469 Fingerprint *string // legacy name for certfp, #1050 470 Certfp string 471 Auto bool 472 Hidden bool 473 Modes string 474} 475 476// Various server-enforced limits on data size. 477type Limits struct { 478 AwayLen int `yaml:"awaylen"` 479 ChanListModes int `yaml:"chan-list-modes"` 480 ChannelLen int `yaml:"channellen"` 481 IdentLen int `yaml:"identlen"` 482 KickLen int `yaml:"kicklen"` 483 MonitorEntries int `yaml:"monitor-entries"` 484 NickLen int `yaml:"nicklen"` 485 TopicLen int `yaml:"topiclen"` 486 WhowasEntries int `yaml:"whowas-entries"` 487 RegistrationMessages int `yaml:"registration-messages"` 488 Multiline struct { 489 MaxBytes int `yaml:"max-bytes"` 490 MaxLines int `yaml:"max-lines"` 491 } 492} 493 494// STSConfig controls the STS configuration/ 495type STSConfig struct { 496 Enabled bool 497 Duration custime.Duration 498 Port int 499 Preload bool 500 STSOnlyBanner string `yaml:"sts-only-banner"` 501 bannerLines []string 502} 503 504// Value returns the STS value to advertise in CAP 505func (sts *STSConfig) Value() string { 506 val := fmt.Sprintf("duration=%d", int(time.Duration(sts.Duration).Seconds())) 507 if sts.Enabled && sts.Port > 0 { 508 val += fmt.Sprintf(",port=%d", sts.Port) 509 } 510 if sts.Enabled && sts.Preload { 511 val += ",preload" 512 } 513 return val 514} 515 516type FakelagConfig struct { 517 Enabled bool 518 Window time.Duration 519 BurstLimit uint `yaml:"burst-limit"` 520 MessagesPerWindow uint `yaml:"messages-per-window"` 521 Cooldown time.Duration 522} 523 524type TorListenersConfig struct { 525 Listeners []string // legacy only 526 RequireSasl bool `yaml:"require-sasl"` 527 Vhost string 528 MaxConnections int `yaml:"max-connections"` 529 ThrottleDuration time.Duration `yaml:"throttle-duration"` 530 MaxConnectionsPerDuration int `yaml:"max-connections-per-duration"` 531} 532 533// Config defines the overall configuration. 534type Config struct { 535 AllowEnvironmentOverrides bool `yaml:"allow-environment-overrides"` 536 537 Network struct { 538 Name string 539 } 540 541 Server struct { 542 Password string 543 passwordBytes []byte 544 Name string 545 nameCasefolded string 546 Listeners map[string]listenerConfigBlock 547 UnixBindMode os.FileMode `yaml:"unix-bind-mode"` 548 TorListeners TorListenersConfig `yaml:"tor-listeners"` 549 WebSockets struct { 550 AllowedOrigins []string `yaml:"allowed-origins"` 551 allowedOriginRegexps []*regexp.Regexp 552 } 553 // they get parsed into this internal representation: 554 trueListeners map[string]utils.ListenerConfig 555 STS STSConfig 556 LookupHostnames *bool `yaml:"lookup-hostnames"` 557 lookupHostnames bool 558 ForwardConfirmHostnames bool `yaml:"forward-confirm-hostnames"` 559 CheckIdent bool `yaml:"check-ident"` 560 CoerceIdent string `yaml:"coerce-ident"` 561 MOTD string 562 motdLines []string 563 MOTDFormatting bool `yaml:"motd-formatting"` 564 Relaymsg struct { 565 Enabled bool 566 Separators string 567 AvailableToChanops bool `yaml:"available-to-chanops"` 568 } 569 ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` 570 proxyAllowedFromNets []net.IPNet 571 WebIRC []webircConfig `yaml:"webirc"` 572 MaxSendQString string `yaml:"max-sendq"` 573 MaxSendQBytes int 574 Compatibility struct { 575 ForceTrailing *bool `yaml:"force-trailing"` 576 forceTrailing bool 577 SendUnprefixedSasl bool `yaml:"send-unprefixed-sasl"` 578 AllowTruncation *bool `yaml:"allow-truncation"` 579 allowTruncation bool 580 } 581 isupport isupport.List 582 IPLimits connection_limits.LimiterConfig `yaml:"ip-limits"` 583 Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"` 584 SecureNetDefs []string `yaml:"secure-nets"` 585 secureNets []net.IPNet 586 supportedCaps *caps.Set 587 supportedCapsWithoutSTS *caps.Set 588 capValues caps.Values 589 Casemapping Casemapping 590 EnforceUtf8 bool `yaml:"enforce-utf8"` 591 OutputPath string `yaml:"output-path"` 592 IPCheckScript ScriptConfig `yaml:"ip-check-script"` 593 OverrideServicesHostname string `yaml:"override-services-hostname"` 594 MaxLineLen int `yaml:"max-line-len"` 595 SuppressLusers bool `yaml:"suppress-lusers"` 596 } 597 598 Roleplay struct { 599 Enabled bool 600 RequireChanops bool `yaml:"require-chanops"` 601 RequireOper bool `yaml:"require-oper"` 602 AddSuffix *bool `yaml:"add-suffix"` 603 addSuffix bool 604 } 605 606 Extjwt struct { 607 Default jwt.JwtServiceConfig `yaml:",inline"` 608 Services map[string]jwt.JwtServiceConfig `yaml:"services"` 609 } 610 611 Languages struct { 612 Enabled bool 613 Path string 614 Default string 615 } 616 617 languageManager *languages.Manager 618 619 Datastore struct { 620 Path string 621 AutoUpgrade bool 622 MySQL mysql.Config 623 } 624 625 Accounts AccountConfig 626 627 Channels struct { 628 DefaultModes *string `yaml:"default-modes"` 629 defaultModes modes.Modes 630 MaxChannelsPerClient int `yaml:"max-channels-per-client"` 631 OpOnlyCreation bool `yaml:"operator-only-creation"` 632 Registration struct { 633 Enabled bool 634 OperatorOnly bool `yaml:"operator-only"` 635 MaxChannelsPerAccount int `yaml:"max-channels-per-account"` 636 } 637 ListDelay time.Duration `yaml:"list-delay"` 638 InviteExpiration custime.Duration `yaml:"invite-expiration"` 639 } 640 641 OperClasses map[string]*OperClassConfig `yaml:"oper-classes"` 642 643 Opers map[string]*OperConfig 644 645 // parsed operator definitions, unexported so they can't be defined 646 // directly in YAML: 647 operators map[string]*Oper 648 649 Logging []logger.LoggingConfig 650 651 Debug struct { 652 RecoverFromErrors *bool `yaml:"recover-from-errors"` 653 recoverFromErrors bool 654 PprofListener string `yaml:"pprof-listener"` 655 } 656 657 Limits Limits 658 659 Fakelag FakelagConfig 660 661 History struct { 662 Enabled bool 663 ChannelLength int `yaml:"channel-length"` 664 ClientLength int `yaml:"client-length"` 665 AutoresizeWindow custime.Duration `yaml:"autoresize-window"` 666 AutoreplayOnJoin int `yaml:"autoreplay-on-join"` 667 ChathistoryMax int `yaml:"chathistory-maxmessages"` 668 ZNCMax int `yaml:"znc-maxmessages"` 669 Restrictions struct { 670 ExpireTime custime.Duration `yaml:"expire-time"` 671 // legacy key, superceded by QueryCutoff: 672 EnforceRegistrationDate_ bool `yaml:"enforce-registration-date"` 673 QueryCutoff string `yaml:"query-cutoff"` 674 queryCutoff HistoryCutoff 675 GracePeriod custime.Duration `yaml:"grace-period"` 676 } 677 Persistent struct { 678 Enabled bool 679 UnregisteredChannels bool `yaml:"unregistered-channels"` 680 RegisteredChannels PersistentStatus `yaml:"registered-channels"` 681 DirectMessages PersistentStatus `yaml:"direct-messages"` 682 } 683 Retention struct { 684 AllowIndividualDelete bool `yaml:"allow-individual-delete"` 685 EnableAccountIndexing bool `yaml:"enable-account-indexing"` 686 } 687 TagmsgStorage struct { 688 Default bool 689 Whitelist []string 690 Blacklist []string 691 } `yaml:"tagmsg-storage"` 692 } 693 694 Filename string 695} 696 697// OperClass defines an assembled operator class. 698type OperClass struct { 699 Title string 700 WhoisLine string `yaml:"whois-line"` 701 Capabilities utils.StringSet // map to make lookups much easier 702} 703 704// OperatorClasses returns a map of assembled operator classes from the given config. 705func (conf *Config) OperatorClasses() (map[string]*OperClass, error) { 706 fixupCapability := func(capab string) string { 707 return strings.TrimPrefix(strings.TrimPrefix(capab, "oper:"), "local_") // #868, #1442 708 } 709 710 ocs := make(map[string]*OperClass) 711 712 // loop from no extends to most extended, breaking if we can't add any more 713 lenOfLastOcs := -1 714 for { 715 if lenOfLastOcs == len(ocs) { 716 return nil, errors.New("OperClasses contains a looping dependency, or a class extends from a class that doesn't exist") 717 } 718 lenOfLastOcs = len(ocs) 719 720 var anyMissing bool 721 for name, info := range conf.OperClasses { 722 _, exists := ocs[name] 723 _, extendsExists := ocs[info.Extends] 724 if exists { 725 // class already exists 726 continue 727 } else if len(info.Extends) > 0 && !extendsExists { 728 // class we extend on doesn't exist 729 _, exists := conf.OperClasses[info.Extends] 730 if !exists { 731 return nil, fmt.Errorf("Operclass [%s] extends [%s], which doesn't exist", name, info.Extends) 732 } 733 anyMissing = true 734 continue 735 } 736 737 // create new operclass 738 var oc OperClass 739 oc.Capabilities = make(utils.StringSet) 740 741 // get inhereted info from other operclasses 742 if len(info.Extends) > 0 { 743 einfo := ocs[info.Extends] 744 745 for capab := range einfo.Capabilities { 746 oc.Capabilities.Add(fixupCapability(capab)) 747 } 748 } 749 750 // add our own info 751 oc.Title = info.Title 752 if oc.Title == "" { 753 oc.Title = "IRC operator" 754 } 755 for _, capab := range info.Capabilities { 756 oc.Capabilities.Add(fixupCapability(capab)) 757 } 758 if len(info.WhoisLine) > 0 { 759 oc.WhoisLine = info.WhoisLine 760 } else { 761 oc.WhoisLine = "is a" 762 if strings.Contains(strings.ToLower(string(oc.Title[0])), "aeiou") { 763 oc.WhoisLine += "n" 764 } 765 oc.WhoisLine += " " 766 oc.WhoisLine += oc.Title 767 } 768 769 ocs[name] = &oc 770 } 771 772 if !anyMissing { 773 // we've got every operclass! 774 break 775 } 776 } 777 778 return ocs, nil 779} 780 781// Oper represents a single assembled operator's config. 782type Oper struct { 783 Name string 784 Class *OperClass 785 WhoisLine string 786 Vhost string 787 Pass []byte 788 Certfp string 789 Auto bool 790 Hidden bool 791 Modes []modes.ModeChange 792} 793 794func (oper *Oper) HasRoleCapab(capab string) bool { 795 return oper != nil && oper.Class.Capabilities.Has(capab) 796} 797 798// Operators returns a map of operator configs from the given OperClass and config. 799func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error) { 800 operators := make(map[string]*Oper) 801 for name, opConf := range conf.Opers { 802 var oper Oper 803 804 // oper name 805 name, err := CasefoldName(name) 806 if err != nil { 807 return nil, fmt.Errorf("Could not casefold oper name: %s", err.Error()) 808 } 809 oper.Name = name 810 811 if opConf.Password != "" { 812 oper.Pass, err = decodeLegacyPasswordHash(opConf.Password) 813 if err != nil { 814 return nil, fmt.Errorf("Oper %s has an invalid password hash: %s", oper.Name, err.Error()) 815 } 816 } 817 certfp := opConf.Certfp 818 if certfp == "" && opConf.Fingerprint != nil { 819 certfp = *opConf.Fingerprint 820 } 821 if certfp != "" { 822 oper.Certfp, err = utils.NormalizeCertfp(certfp) 823 if err != nil { 824 return nil, fmt.Errorf("Oper %s has an invalid fingerprint: %s", oper.Name, err.Error()) 825 } 826 } 827 oper.Auto = opConf.Auto 828 oper.Hidden = opConf.Hidden 829 830 if oper.Pass == nil && oper.Certfp == "" { 831 return nil, fmt.Errorf("Oper %s has neither a password nor a fingerprint", name) 832 } 833 834 oper.Vhost = opConf.Vhost 835 if oper.Vhost != "" && !conf.Accounts.VHosts.validRegexp.MatchString(oper.Vhost) { 836 return nil, fmt.Errorf("Oper %s has an invalid vhost: `%s`", name, oper.Vhost) 837 } 838 class, exists := oc[opConf.Class] 839 if !exists { 840 return nil, fmt.Errorf("Could not load operator [%s] - they use operclass [%s] which does not exist", name, opConf.Class) 841 } 842 oper.Class = class 843 if len(opConf.WhoisLine) > 0 { 844 oper.WhoisLine = opConf.WhoisLine 845 } else { 846 oper.WhoisLine = class.WhoisLine 847 } 848 modeStr := strings.TrimSpace(opConf.Modes) 849 modeChanges, unknownChanges := modes.ParseUserModeChanges(strings.Split(modeStr, " ")...) 850 if len(unknownChanges) > 0 { 851 return nil, fmt.Errorf("Could not load operator [%s] due to unknown modes %v", name, unknownChanges) 852 } 853 oper.Modes = modeChanges 854 855 // successful, attach to list of opers 856 operators[name] = &oper 857 } 858 return operators, nil 859} 860 861func loadTlsConfig(config listenerConfigBlock) (tlsConfig *tls.Config, err error) { 862 var certificates []tls.Certificate 863 if len(config.TLSCertificates) != 0 { 864 // SNI configuration with multiple certificates 865 for _, certPairConf := range config.TLSCertificates { 866 cert, err := loadCertWithLeaf(certPairConf.Cert, certPairConf.Key) 867 if err != nil { 868 return nil, err 869 } 870 certificates = append(certificates, cert) 871 } 872 } else if config.TLS.Cert != "" { 873 // normal configuration with one certificate 874 cert, err := loadCertWithLeaf(config.TLS.Cert, config.TLS.Key) 875 if err != nil { 876 return nil, err 877 } 878 certificates = append(certificates, cert) 879 } else { 880 // plaintext! 881 return nil, nil 882 } 883 clientAuth := tls.RequestClientCert 884 if config.WebSocket { 885 // if Chrome receives a server request for a client certificate 886 // on a websocket connection, it will immediately disconnect: 887 // https://bugs.chromium.org/p/chromium/issues/detail?id=329884 888 // work around this behavior: 889 clientAuth = tls.NoClientCert 890 } 891 result := tls.Config{ 892 Certificates: certificates, 893 ClientAuth: clientAuth, 894 MinVersion: tlsMinVersionFromString(config.MinTLSVersion), 895 } 896 return &result, nil 897} 898 899func tlsMinVersionFromString(version string) uint16 { 900 version = strings.ToLower(version) 901 version = strings.TrimPrefix(version, "v") 902 switch version { 903 case "1", "1.0": 904 return tls.VersionTLS10 905 case "1.1": 906 return tls.VersionTLS11 907 case "1.2": 908 return tls.VersionTLS12 909 case "1.3": 910 return tls.VersionTLS13 911 default: 912 // tls package will fill in a sane value, currently 1.0 913 return 0 914 } 915} 916 917func loadCertWithLeaf(certFile, keyFile string) (cert tls.Certificate, err error) { 918 // LoadX509KeyPair: "On successful return, Certificate.Leaf will be nil because 919 // the parsed form of the certificate is not retained." tls.Config: 920 // "Note: if there are multiple Certificates, and they don't have the 921 // optional field Leaf set, certificate selection will incur a significant 922 // per-handshake performance cost." 923 cert, err = tls.LoadX509KeyPair(certFile, keyFile) 924 if err != nil { 925 return 926 } 927 cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) 928 return 929} 930 931// prepareListeners populates Config.Server.trueListeners 932func (conf *Config) prepareListeners() (err error) { 933 if len(conf.Server.Listeners) == 0 { 934 return fmt.Errorf("No listeners were configured") 935 } 936 937 conf.Server.trueListeners = make(map[string]utils.ListenerConfig) 938 for addr, block := range conf.Server.Listeners { 939 var lconf utils.ListenerConfig 940 lconf.ProxyDeadline = RegisterTimeout 941 lconf.Tor = block.Tor 942 lconf.STSOnly = block.STSOnly 943 if lconf.STSOnly && !conf.Server.STS.Enabled { 944 return fmt.Errorf("%s is configured as a STS-only listener, but STS is disabled", addr) 945 } 946 lconf.TLSConfig, err = loadTlsConfig(block) 947 if err != nil { 948 return &CertKeyError{Err: err} 949 } 950 lconf.RequireProxy = block.TLS.Proxy || block.Proxy 951 lconf.WebSocket = block.WebSocket 952 if lconf.WebSocket && !conf.Server.EnforceUtf8 { 953 return fmt.Errorf("enabling a websocket listener requires the use of server.enforce-utf8") 954 } 955 lconf.HideSTS = block.HideSTS 956 conf.Server.trueListeners[addr] = lconf 957 } 958 return nil 959} 960 961func (config *Config) processExtjwt() (err error) { 962 // first process the default service, which may be disabled 963 err = config.Extjwt.Default.Postprocess() 964 if err != nil { 965 return 966 } 967 // now process the named services. it is an error if any is disabled 968 // also, normalize the service names to lowercase 969 services := make(map[string]jwt.JwtServiceConfig, len(config.Extjwt.Services)) 970 for service, sConf := range config.Extjwt.Services { 971 err := sConf.Postprocess() 972 if err != nil { 973 return err 974 } 975 if !sConf.Enabled() { 976 return fmt.Errorf("no keys enabled for extjwt service %s", service) 977 } 978 services[strings.ToLower(service)] = sConf 979 } 980 config.Extjwt.Services = services 981 return nil 982} 983 984// LoadRawConfig loads the config without doing any consistency checks or postprocessing 985func LoadRawConfig(filename string) (config *Config, err error) { 986 data, err := os.ReadFile(filename) 987 if err != nil { 988 return nil, err 989 } 990 991 err = yaml.Unmarshal(data, &config) 992 if err != nil { 993 return nil, err 994 } 995 return 996} 997 998// convert, e.g., "ALLOWED_ORIGINS" to "allowed-origins" 999func screamingSnakeToKebab(in string) (out string) { 1000 var buf strings.Builder 1001 for i := 0; i < len(in); i++ { 1002 c := in[i] 1003 switch { 1004 case c == '_': 1005 buf.WriteByte('-') 1006 case 'A' <= c && c <= 'Z': 1007 buf.WriteByte(c + ('a' - 'A')) 1008 default: 1009 buf.WriteByte(c) 1010 } 1011 } 1012 return buf.String() 1013} 1014 1015func isExported(field reflect.StructField) bool { 1016 return field.PkgPath == "" // https://golang.org/pkg/reflect/#StructField 1017} 1018 1019// errors caused by config overrides 1020type configPathError struct { 1021 name string 1022 desc string 1023 fatalErr error 1024} 1025 1026func (ce *configPathError) Error() string { 1027 if ce.fatalErr != nil { 1028 return fmt.Sprintf("Couldn't apply config override `%s`: %s: %v", ce.name, ce.desc, ce.fatalErr) 1029 } 1030 return fmt.Sprintf("Couldn't apply config override `%s`: %s", ce.name, ce.desc) 1031} 1032 1033func mungeFromEnvironment(config *Config, envPair string) (applied bool, err *configPathError) { 1034 equalIdx := strings.IndexByte(envPair, '=') 1035 name, value := envPair[:equalIdx], envPair[equalIdx+1:] 1036 if strings.HasPrefix(name, "ERGO__") { 1037 name = strings.TrimPrefix(name, "ERGO__") 1038 } else if strings.HasPrefix(name, "ORAGONO__") { 1039 name = strings.TrimPrefix(name, "ORAGONO__") 1040 } else { 1041 return false, nil 1042 } 1043 pathComponents := strings.Split(name, "__") 1044 for i, pathComponent := range pathComponents { 1045 pathComponents[i] = screamingSnakeToKebab(pathComponent) 1046 } 1047 1048 v := reflect.Indirect(reflect.ValueOf(config)) 1049 t := v.Type() 1050 for _, component := range pathComponents { 1051 if component == "" { 1052 return false, &configPathError{name, "invalid", nil} 1053 } 1054 if v.Kind() != reflect.Struct { 1055 return false, &configPathError{name, "index into non-struct", nil} 1056 } 1057 var nextField reflect.StructField 1058 success := false 1059 n := t.NumField() 1060 // preferentially get a field with an exact yaml tag match, 1061 // then fall back to case-insensitive comparison of field names 1062 for i := 0; i < n; i++ { 1063 field := t.Field(i) 1064 if isExported(field) && field.Tag.Get("yaml") == component { 1065 nextField = field 1066 success = true 1067 break 1068 } 1069 } 1070 if !success { 1071 for i := 0; i < n; i++ { 1072 field := t.Field(i) 1073 if isExported(field) && strings.ToLower(field.Name) == component { 1074 nextField = field 1075 success = true 1076 break 1077 } 1078 } 1079 } 1080 if !success { 1081 return false, &configPathError{name, fmt.Sprintf("couldn't resolve path component: `%s`", component), nil} 1082 } 1083 v = v.FieldByName(nextField.Name) 1084 // dereference pointer field if necessary, initialize new value if necessary 1085 if v.Kind() == reflect.Ptr { 1086 if v.IsNil() { 1087 v.Set(reflect.New(v.Type().Elem())) 1088 } 1089 v = reflect.Indirect(v) 1090 } 1091 t = v.Type() 1092 } 1093 yamlErr := yaml.Unmarshal([]byte(value), v.Addr().Interface()) 1094 if yamlErr != nil { 1095 return false, &configPathError{name, "couldn't deserialize YAML", yamlErr} 1096 } 1097 return true, nil 1098} 1099 1100// LoadConfig loads the given YAML configuration file. 1101func LoadConfig(filename string) (config *Config, err error) { 1102 config, err = LoadRawConfig(filename) 1103 if err != nil { 1104 return nil, err 1105 } 1106 1107 if config.AllowEnvironmentOverrides { 1108 for _, envPair := range os.Environ() { 1109 applied, envErr := mungeFromEnvironment(config, envPair) 1110 if envErr != nil { 1111 if envErr.fatalErr != nil { 1112 return nil, envErr 1113 } else { 1114 log.Println(envErr.Error()) 1115 } 1116 } else if applied { 1117 log.Printf("applied environment override: %s\n", envPair) 1118 } 1119 } 1120 } 1121 1122 config.Filename = filename 1123 1124 if config.Network.Name == "" { 1125 return nil, errors.New("Network name missing") 1126 } 1127 if config.Server.Name == "" { 1128 return nil, errors.New("Server name missing") 1129 } 1130 if !utils.IsServerName(config.Server.Name) { 1131 return nil, errors.New("Server name must match the format of a hostname") 1132 } 1133 config.Server.nameCasefolded = strings.ToLower(config.Server.Name) 1134 if config.Datastore.Path == "" { 1135 return nil, errors.New("Datastore path missing") 1136 } 1137 //dan: automagically fix identlen until a few releases in the future (from now, 0.12.0), being a newly-introduced limit 1138 if config.Limits.IdentLen < 1 { 1139 config.Limits.IdentLen = 20 1140 } 1141 if config.Limits.NickLen < 1 || config.Limits.ChannelLen < 2 || config.Limits.AwayLen < 1 || config.Limits.KickLen < 1 || config.Limits.TopicLen < 1 { 1142 return nil, errors.New("One or more limits values are too low") 1143 } 1144 if config.Limits.RegistrationMessages == 0 { 1145 config.Limits.RegistrationMessages = 1024 1146 } 1147 if config.Server.MaxLineLen < DefaultMaxLineLen { 1148 config.Server.MaxLineLen = DefaultMaxLineLen 1149 } 1150 if config.Datastore.MySQL.Enabled { 1151 if config.Limits.NickLen > mysql.MaxTargetLength || config.Limits.ChannelLen > mysql.MaxTargetLength { 1152 return nil, fmt.Errorf("to use MySQL, nick and channel length limits must be %d or lower", mysql.MaxTargetLength) 1153 } 1154 } 1155 1156 if config.Server.CoerceIdent != "" { 1157 if config.Server.CheckIdent { 1158 return nil, errors.New("Can't configure both check-ident and coerce-ident") 1159 } 1160 if config.Server.CoerceIdent[0] != '~' { 1161 return nil, errors.New("coerce-ident value must start with a ~") 1162 } 1163 if !isIdent(config.Server.CoerceIdent[1:]) { 1164 return nil, errors.New("coerce-ident must be valid as an IRC user/ident field") 1165 } 1166 } 1167 1168 config.Server.supportedCaps = caps.NewCompleteSet() 1169 config.Server.capValues = make(caps.Values) 1170 1171 err = config.prepareListeners() 1172 if err != nil { 1173 return nil, fmt.Errorf("failed to prepare listeners: %v", err) 1174 } 1175 1176 for _, glob := range config.Server.WebSockets.AllowedOrigins { 1177 globre, err := utils.CompileGlob(glob, false) 1178 if err != nil { 1179 return nil, fmt.Errorf("invalid websocket allowed-origin expression: %s", glob) 1180 } 1181 config.Server.WebSockets.allowedOriginRegexps = append(config.Server.WebSockets.allowedOriginRegexps, globre) 1182 } 1183 1184 if config.Server.STS.Enabled { 1185 if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 { 1186 return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port) 1187 } 1188 if config.Server.STS.STSOnlyBanner != "" { 1189 for _, line := range strings.Split(config.Server.STS.STSOnlyBanner, "\n") { 1190 config.Server.STS.bannerLines = append(config.Server.STS.bannerLines, strings.TrimSpace(line)) 1191 } 1192 } else { 1193 config.Server.STS.bannerLines = []string{fmt.Sprintf("This server is only accessible over TLS. Please reconnect using TLS on port %d.", config.Server.STS.Port)} 1194 } 1195 } else { 1196 config.Server.supportedCaps.Disable(caps.STS) 1197 config.Server.STS.Duration = 0 1198 } 1199 // set this even if STS is disabled 1200 config.Server.capValues[caps.STS] = config.Server.STS.Value() 1201 1202 config.Server.lookupHostnames = utils.BoolDefaultTrue(config.Server.LookupHostnames) 1203 1204 // process webirc blocks 1205 var newWebIRC []webircConfig 1206 for _, webirc := range config.Server.WebIRC { 1207 // skip webirc blocks with no hosts (such as the example one) 1208 if len(webirc.Hosts) == 0 { 1209 continue 1210 } 1211 1212 err = webirc.Populate() 1213 if err != nil { 1214 return nil, fmt.Errorf("Could not parse WebIRC config: %s", err.Error()) 1215 } 1216 newWebIRC = append(newWebIRC, webirc) 1217 } 1218 config.Server.WebIRC = newWebIRC 1219 1220 if config.Limits.Multiline.MaxBytes <= 0 { 1221 config.Server.supportedCaps.Disable(caps.Multiline) 1222 } else { 1223 var multilineCapValue string 1224 if config.Limits.Multiline.MaxLines == 0 { 1225 multilineCapValue = fmt.Sprintf("max-bytes=%d", config.Limits.Multiline.MaxBytes) 1226 } else { 1227 multilineCapValue = fmt.Sprintf("max-bytes=%d,max-lines=%d", config.Limits.Multiline.MaxBytes, config.Limits.Multiline.MaxLines) 1228 } 1229 config.Server.capValues[caps.Multiline] = multilineCapValue 1230 } 1231 1232 // handle legacy name 'bouncer' for 'multiclient' section: 1233 if config.Accounts.Bouncer != nil { 1234 config.Accounts.Multiclient = *config.Accounts.Bouncer 1235 } 1236 1237 if !config.Accounts.Multiclient.Enabled { 1238 config.Accounts.Multiclient.AlwaysOn = PersistentDisabled 1239 } else if config.Accounts.Multiclient.AlwaysOn >= PersistentOptOut { 1240 config.Accounts.Multiclient.AllowedByDefault = true 1241 } 1242 1243 if !config.Accounts.NickReservation.Enabled { 1244 config.Accounts.NickReservation.ForceNickEqualsAccount = false 1245 } 1246 1247 if config.Accounts.NickReservation.ForceNickEqualsAccount && !config.Accounts.Multiclient.Enabled { 1248 return nil, errors.New("force-nick-equals-account requires enabling multiclient as well") 1249 } 1250 1251 // handle guest format, including the legacy key rename-prefix 1252 if config.Accounts.NickReservation.GuestFormat == "" { 1253 renamePrefix := config.Accounts.NickReservation.RenamePrefix 1254 if renamePrefix == "" { 1255 renamePrefix = "Guest-" 1256 } 1257 config.Accounts.NickReservation.GuestFormat = renamePrefix + "*" 1258 } 1259 config.Accounts.NickReservation.guestRegexp, config.Accounts.NickReservation.guestRegexpFolded, err = compileGuestRegexp(config.Accounts.NickReservation.GuestFormat, config.Server.Casemapping) 1260 if err != nil { 1261 return nil, err 1262 } 1263 1264 var newLogConfigs []logger.LoggingConfig 1265 for _, logConfig := range config.Logging { 1266 // methods 1267 methods := make(map[string]bool) 1268 for _, method := range strings.Split(logConfig.Method, " ") { 1269 if len(method) > 0 { 1270 methods[strings.ToLower(method)] = true 1271 } 1272 } 1273 if methods["file"] && logConfig.Filename == "" { 1274 return nil, errors.New("Logging configuration specifies 'file' method but 'filename' is empty") 1275 } 1276 logConfig.MethodFile = methods["file"] 1277 logConfig.MethodStdout = methods["stdout"] 1278 logConfig.MethodStderr = methods["stderr"] 1279 1280 // levels 1281 level, exists := logger.LogLevelNames[strings.ToLower(logConfig.LevelString)] 1282 if !exists { 1283 return nil, fmt.Errorf("Could not translate log leve [%s]", logConfig.LevelString) 1284 } 1285 logConfig.Level = level 1286 1287 // types 1288 for _, typeStr := range strings.Split(logConfig.TypeString, " ") { 1289 if len(typeStr) == 0 { 1290 continue 1291 } 1292 if typeStr == "-" { 1293 return nil, errors.New("Encountered logging type '-' with no type to exclude") 1294 } 1295 if typeStr[0] == '-' { 1296 typeStr = typeStr[1:] 1297 logConfig.ExcludedTypes = append(logConfig.ExcludedTypes, typeStr) 1298 } else { 1299 logConfig.Types = append(logConfig.Types, typeStr) 1300 } 1301 } 1302 if len(logConfig.Types) < 1 { 1303 return nil, errors.New("Logger has no types to log") 1304 } 1305 1306 newLogConfigs = append(newLogConfigs, logConfig) 1307 } 1308 config.Logging = newLogConfigs 1309 1310 if config.Accounts.Registration.EmailVerification.Enabled { 1311 err := config.Accounts.Registration.EmailVerification.Postprocess(config.Server.Name) 1312 if err != nil { 1313 return nil, err 1314 } 1315 } else { 1316 // TODO: this processes the legacy "callback" config, clean this up in 2.5 or later 1317 // TODO: also clean up the legacy "inline" MTA config format (from ee05a4324dfde) 1318 mailtoEnabled := false 1319 for _, name := range config.Accounts.Registration.LegacyEnabledCallbacks { 1320 if name == "mailto" { 1321 mailtoEnabled = true 1322 break 1323 } 1324 } 1325 if mailtoEnabled { 1326 config.Accounts.Registration.EmailVerification = config.Accounts.Registration.LegacyCallbacks.Mailto 1327 config.Accounts.Registration.EmailVerification.Enabled = true 1328 err := config.Accounts.Registration.EmailVerification.Postprocess(config.Server.Name) 1329 if err != nil { 1330 return nil, err 1331 } 1332 } 1333 } 1334 1335 config.Accounts.defaultUserModes = ParseDefaultUserModes(config.Accounts.DefaultUserModes) 1336 1337 if config.Server.Password != "" { 1338 config.Server.passwordBytes, err = decodeLegacyPasswordHash(config.Server.Password) 1339 if err != nil { 1340 return nil, err 1341 } 1342 if config.Accounts.LoginViaPassCommand && !config.Accounts.SkipServerPassword { 1343 return nil, errors.New("Using a server password and login-via-pass-command requires skip-server-password as well") 1344 } 1345 // #1634: accounts.registration.allow-before-connect is an auth bypass 1346 // for configurations that start from default and then enable server.password 1347 config.Accounts.Registration.AllowBeforeConnect = false 1348 } 1349 1350 if config.Accounts.RequireSasl.Enabled { 1351 // minor gotcha: Tor listeners will typically be loopback and 1352 // therefore exempted from require-sasl. if require-sasl is enabled 1353 // for non-Tor (non-local) connections, enable it for Tor as well: 1354 config.Server.TorListeners.RequireSasl = true 1355 } 1356 config.Accounts.RequireSasl.exemptedNets, err = utils.ParseNetList(config.Accounts.RequireSasl.Exempted) 1357 if err != nil { 1358 return nil, fmt.Errorf("Could not parse require-sasl exempted nets: %v", err.Error()) 1359 } 1360 1361 config.Server.proxyAllowedFromNets, err = utils.ParseNetList(config.Server.ProxyAllowedFrom) 1362 if err != nil { 1363 return nil, fmt.Errorf("Could not parse proxy-allowed-from nets: %v", err.Error()) 1364 } 1365 1366 config.Server.secureNets, err = utils.ParseNetList(config.Server.SecureNetDefs) 1367 if err != nil { 1368 return nil, fmt.Errorf("Could not parse secure-nets: %v\n", err.Error()) 1369 } 1370 1371 rawRegexp := config.Accounts.VHosts.ValidRegexpRaw 1372 if rawRegexp != "" { 1373 regexp, err := regexp.Compile(rawRegexp) 1374 if err == nil { 1375 config.Accounts.VHosts.validRegexp = regexp 1376 } else { 1377 log.Printf("invalid vhost regexp: %s\n", err.Error()) 1378 } 1379 } 1380 if config.Accounts.VHosts.validRegexp == nil { 1381 config.Accounts.VHosts.validRegexp = defaultValidVhostRegex 1382 } 1383 1384 saslCapValue := "PLAIN,EXTERNAL,SCRAM-SHA-256" 1385 // TODO(#1782) clean this up: 1386 if !config.Accounts.AdvertiseSCRAM { 1387 saslCapValue = "PLAIN,EXTERNAL" 1388 } 1389 config.Server.capValues[caps.SASL] = saslCapValue 1390 if !config.Accounts.AuthenticationEnabled { 1391 config.Server.supportedCaps.Disable(caps.SASL) 1392 } 1393 1394 if !config.Accounts.Registration.Enabled { 1395 config.Server.supportedCaps.Disable(caps.AccountRegistration) 1396 } else { 1397 var registerValues []string 1398 if config.Accounts.Registration.AllowBeforeConnect { 1399 registerValues = append(registerValues, "before-connect") 1400 } 1401 if config.Accounts.Registration.EmailVerification.Enabled { 1402 registerValues = append(registerValues, "email-required") 1403 } 1404 if config.Accounts.RequireSasl.Enabled { 1405 registerValues = append(registerValues, "account-required") 1406 } 1407 if len(registerValues) != 0 { 1408 config.Server.capValues[caps.AccountRegistration] = strings.Join(registerValues, ",") 1409 } 1410 } 1411 1412 maxSendQBytes, err := bytefmt.ToBytes(config.Server.MaxSendQString) 1413 if err != nil { 1414 return nil, fmt.Errorf("Could not parse maximum SendQ size (make sure it only contains whole numbers): %s", err.Error()) 1415 } 1416 config.Server.MaxSendQBytes = int(maxSendQBytes) 1417 1418 config.languageManager, err = languages.NewManager(config.Languages.Enabled, config.Languages.Path, config.Languages.Default) 1419 if err != nil { 1420 return nil, fmt.Errorf("Could not load languages: %s", err.Error()) 1421 } 1422 config.Server.capValues[caps.Languages] = config.languageManager.CapValue() 1423 1424 if config.Server.Relaymsg.Enabled { 1425 for _, char := range protocolBreakingNameCharacters { 1426 if strings.ContainsRune(config.Server.Relaymsg.Separators, char) { 1427 return nil, fmt.Errorf("RELAYMSG separators cannot include the characters %s", protocolBreakingNameCharacters) 1428 } 1429 } 1430 config.Server.capValues[caps.Relaymsg] = config.Server.Relaymsg.Separators 1431 } else { 1432 config.Server.supportedCaps.Disable(caps.Relaymsg) 1433 } 1434 1435 config.Debug.recoverFromErrors = utils.BoolDefaultTrue(config.Debug.RecoverFromErrors) 1436 1437 // process operator definitions, store them to config.operators 1438 operclasses, err := config.OperatorClasses() 1439 if err != nil { 1440 return nil, err 1441 } 1442 opers, err := config.Operators(operclasses) 1443 if err != nil { 1444 return nil, err 1445 } 1446 config.operators = opers 1447 1448 // parse default channel modes 1449 config.Channels.defaultModes = ParseDefaultChannelModes(config.Channels.DefaultModes) 1450 1451 if config.Accounts.Registration.BcryptCost == 0 { 1452 config.Accounts.Registration.BcryptCost = passwd.DefaultCost 1453 } 1454 1455 if config.Channels.MaxChannelsPerClient == 0 { 1456 config.Channels.MaxChannelsPerClient = 100 1457 } 1458 if config.Channels.Registration.MaxChannelsPerAccount == 0 { 1459 config.Channels.Registration.MaxChannelsPerAccount = 15 1460 } 1461 1462 config.Server.Compatibility.forceTrailing = utils.BoolDefaultTrue(config.Server.Compatibility.ForceTrailing) 1463 config.Server.Compatibility.allowTruncation = utils.BoolDefaultTrue(config.Server.Compatibility.AllowTruncation) 1464 1465 config.loadMOTD() 1466 1467 // in the current implementation, we disable history by creating a history buffer 1468 // with zero capacity. but the `enabled` config option MUST be respected regardless 1469 // of this detail 1470 if !config.History.Enabled { 1471 config.History.ChannelLength = 0 1472 config.History.ClientLength = 0 1473 config.Server.supportedCaps.Disable(caps.Chathistory) 1474 config.Server.supportedCaps.Disable(caps.EventPlayback) 1475 config.Server.supportedCaps.Disable(caps.ZNCPlayback) 1476 } 1477 1478 if !config.History.Enabled || !config.History.Persistent.Enabled { 1479 config.History.Persistent.Enabled = false 1480 config.History.Persistent.UnregisteredChannels = false 1481 config.History.Persistent.RegisteredChannels = PersistentDisabled 1482 config.History.Persistent.DirectMessages = PersistentDisabled 1483 } 1484 1485 if config.History.Persistent.Enabled && !config.Datastore.MySQL.Enabled { 1486 return nil, fmt.Errorf("You must configure a MySQL server in order to enable persistent history") 1487 } 1488 1489 if config.History.ZNCMax == 0 { 1490 config.History.ZNCMax = config.History.ChathistoryMax 1491 } 1492 1493 if config.History.Restrictions.QueryCutoff != "" { 1494 config.History.Restrictions.queryCutoff, err = historyCutoffFromString(config.History.Restrictions.QueryCutoff) 1495 if err != nil { 1496 return nil, fmt.Errorf("invalid value of history.query-restrictions: %w", err) 1497 } 1498 } else { 1499 if config.History.Restrictions.EnforceRegistrationDate_ { 1500 config.History.Restrictions.queryCutoff = HistoryCutoffRegistrationTime 1501 } else { 1502 config.History.Restrictions.queryCutoff = HistoryCutoffNone 1503 } 1504 } 1505 1506 config.Roleplay.addSuffix = utils.BoolDefaultTrue(config.Roleplay.AddSuffix) 1507 1508 config.Datastore.MySQL.ExpireTime = time.Duration(config.History.Restrictions.ExpireTime) 1509 config.Datastore.MySQL.TrackAccountMessages = config.History.Retention.EnableAccountIndexing 1510 if config.Datastore.MySQL.MaxConns == 0 { 1511 // #1622: not putting an upper limit on the number of MySQL connections is 1512 // potentially dangerous. as a naive heuristic, assume they're running on the 1513 // same machine: 1514 config.Datastore.MySQL.MaxConns = runtime.NumCPU() 1515 } 1516 1517 config.Server.Cloaks.Initialize() 1518 if config.Server.Cloaks.Enabled { 1519 if !utils.IsHostname(config.Server.Cloaks.Netname) { 1520 return nil, fmt.Errorf("Invalid netname for cloaked hostnames: %s", config.Server.Cloaks.Netname) 1521 } 1522 } 1523 1524 err = config.processExtjwt() 1525 if err != nil { 1526 return nil, err 1527 } 1528 1529 // now that all postprocessing is complete, regenerate ISUPPORT: 1530 err = config.generateISupport() 1531 if err != nil { 1532 return nil, err 1533 } 1534 1535 // #1428: Tor listeners should never see STS 1536 config.Server.supportedCapsWithoutSTS = caps.NewSet() 1537 config.Server.supportedCapsWithoutSTS.Union(config.Server.supportedCaps) 1538 config.Server.supportedCapsWithoutSTS.Disable(caps.STS) 1539 1540 return config, nil 1541} 1542 1543func (config *Config) getOutputPath(filename string) string { 1544 return filepath.Join(config.Server.OutputPath, filename) 1545} 1546 1547func (config *Config) isRelaymsgIdentifier(nick string) bool { 1548 if !config.Server.Relaymsg.Enabled { 1549 return false 1550 } 1551 1552 for _, char := range config.Server.Relaymsg.Separators { 1553 if strings.ContainsRune(nick, char) { 1554 return true 1555 } 1556 } 1557 return false 1558} 1559 1560// setISupport sets up our RPL_ISUPPORT reply. 1561func (config *Config) generateISupport() (err error) { 1562 maxTargetsString := strconv.Itoa(maxTargets) 1563 1564 // add RPL_ISUPPORT tokens 1565 isupport := &config.Server.isupport 1566 isupport.Initialize() 1567 isupport.Add("AWAYLEN", strconv.Itoa(config.Limits.AwayLen)) 1568 isupport.Add("BOT", "B") 1569 isupport.Add("CASEMAPPING", "ascii") 1570 isupport.Add("CHANLIMIT", fmt.Sprintf("%s:%d", chanTypes, config.Channels.MaxChannelsPerClient)) 1571 isupport.Add("CHANMODES", chanmodesToken) 1572 if config.History.Enabled && config.History.ChathistoryMax > 0 { 1573 isupport.Add("draft/CHATHISTORY", strconv.Itoa(config.History.ChathistoryMax)) 1574 } 1575 isupport.Add("CHANNELLEN", strconv.Itoa(config.Limits.ChannelLen)) 1576 isupport.Add("CHANTYPES", chanTypes) 1577 isupport.Add("ELIST", "U") 1578 isupport.Add("EXCEPTS", "") 1579 if config.Extjwt.Default.Enabled() || len(config.Extjwt.Services) != 0 { 1580 isupport.Add("EXTJWT", "1") 1581 } 1582 isupport.Add("EXTBAN", ",m") 1583 isupport.Add("FORWARD", "f") 1584 isupport.Add("INVEX", "") 1585 isupport.Add("KICKLEN", strconv.Itoa(config.Limits.KickLen)) 1586 isupport.Add("MAXLIST", fmt.Sprintf("beI:%s", strconv.Itoa(config.Limits.ChanListModes))) 1587 isupport.Add("MAXTARGETS", maxTargetsString) 1588 isupport.Add("MODES", "") 1589 isupport.Add("MONITOR", strconv.Itoa(config.Limits.MonitorEntries)) 1590 isupport.Add("NETWORK", config.Network.Name) 1591 isupport.Add("NICKLEN", strconv.Itoa(config.Limits.NickLen)) 1592 isupport.Add("PREFIX", "(qaohv)~&@%+") 1593 if config.Roleplay.Enabled { 1594 isupport.Add("RPCHAN", "E") 1595 isupport.Add("RPUSER", "E") 1596 } 1597 isupport.Add("STATUSMSG", "~&@%+") 1598 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)) 1599 isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen)) 1600 if config.Server.Casemapping == CasemappingPRECIS { 1601 isupport.Add("UTF8MAPPING", precisUTF8MappingToken) 1602 } 1603 if config.Server.EnforceUtf8 { 1604 isupport.Add("UTF8ONLY", "") 1605 } 1606 isupport.Add("WHOX", "") 1607 1608 err = isupport.RegenerateCachedReply() 1609 return 1610} 1611 1612// Diff returns changes in supported caps across a rehash. 1613func (config *Config) Diff(oldConfig *Config) (addedCaps, removedCaps *caps.Set) { 1614 addedCaps = caps.NewSet() 1615 removedCaps = caps.NewSet() 1616 if oldConfig == nil { 1617 return 1618 } 1619 1620 if oldConfig.Server.capValues[caps.Languages] != config.Server.capValues[caps.Languages] { 1621 // XXX updated caps get a DEL line and then a NEW line with the new value 1622 addedCaps.Add(caps.Languages) 1623 removedCaps.Add(caps.Languages) 1624 } 1625 1626 if !oldConfig.Accounts.AuthenticationEnabled && config.Accounts.AuthenticationEnabled { 1627 addedCaps.Add(caps.SASL) 1628 } else if oldConfig.Accounts.AuthenticationEnabled && !config.Accounts.AuthenticationEnabled { 1629 removedCaps.Add(caps.SASL) 1630 } 1631 1632 if oldConfig.Limits.Multiline.MaxBytes != 0 && config.Limits.Multiline.MaxBytes == 0 { 1633 removedCaps.Add(caps.Multiline) 1634 } else if oldConfig.Limits.Multiline.MaxBytes == 0 && config.Limits.Multiline.MaxBytes != 0 { 1635 addedCaps.Add(caps.Multiline) 1636 } else if oldConfig.Limits.Multiline != config.Limits.Multiline { 1637 removedCaps.Add(caps.Multiline) 1638 addedCaps.Add(caps.Multiline) 1639 } 1640 1641 if oldConfig.Server.STS.Enabled != config.Server.STS.Enabled || oldConfig.Server.capValues[caps.STS] != config.Server.capValues[caps.STS] { 1642 // XXX: STS is always removed by CAP NEW sts=duration=0, not CAP DEL 1643 // so the appropriate notify is always a CAP NEW; put it in addedCaps for any change 1644 addedCaps.Add(caps.STS) 1645 } 1646 1647 return 1648} 1649 1650// determine whether we need to resize / create / destroy 1651// the in-memory history buffers: 1652func (config *Config) historyChangedFrom(oldConfig *Config) bool { 1653 return config.History.Enabled != oldConfig.History.Enabled || 1654 config.History.ChannelLength != oldConfig.History.ChannelLength || 1655 config.History.ClientLength != oldConfig.History.ClientLength || 1656 config.History.AutoresizeWindow != oldConfig.History.AutoresizeWindow || 1657 config.History.Persistent != oldConfig.History.Persistent 1658} 1659 1660func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard, folded *regexp.Regexp, err error) { 1661 if strings.Count(guestFormat, "?") != 0 || strings.Count(guestFormat, "*") != 1 { 1662 err = errors.New("guest format must contain 1 '*' and no '?'s") 1663 return 1664 } 1665 1666 standard, err = utils.CompileGlob(guestFormat, true) 1667 if err != nil { 1668 return 1669 } 1670 1671 starIndex := strings.IndexByte(guestFormat, '*') 1672 initial := guestFormat[:starIndex] 1673 final := guestFormat[starIndex+1:] 1674 initialFolded, err := casefoldWithSetting(initial, casemapping) 1675 if err != nil { 1676 return 1677 } 1678 finalFolded, err := casefoldWithSetting(final, casemapping) 1679 if err != nil { 1680 return 1681 } 1682 folded, err = utils.CompileGlob(fmt.Sprintf("%s*%s", initialFolded, finalFolded), false) 1683 return 1684} 1685 1686func (config *Config) loadMOTD() error { 1687 if config.Server.MOTD != "" { 1688 file, err := os.Open(config.Server.MOTD) 1689 if err != nil { 1690 return err 1691 } 1692 defer file.Close() 1693 contents, err := io.ReadAll(file) 1694 if err != nil { 1695 return err 1696 } 1697 1698 lines := bytes.Split(contents, []byte{'\n'}) 1699 for i, line := range lines { 1700 lineToSend := string(bytes.TrimRight(line, "\r\n")) 1701 if len(lineToSend) == 0 && i == len(lines)-1 { 1702 // if the last line of the MOTD was properly terminated with \n, 1703 // there's no need to send a blank line to clients 1704 continue 1705 } 1706 if config.Server.MOTDFormatting { 1707 lineToSend = ircfmt.Unescape(lineToSend) 1708 } 1709 // "- " is the required prefix for MOTD 1710 lineToSend = fmt.Sprintf("- %s", lineToSend) 1711 config.Server.motdLines = append(config.Server.motdLines, lineToSend) 1712 } 1713 } 1714 return nil 1715} 1716