- Timestamp:
- Nov 21, 2021, 3:10:54 PM (4 years ago)
- Location:
- trunk
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/downstream.go
r723 r724 245 245 } 246 246 247 type downstreamSASL struct { 248 server sasl.Server 249 plainUsername, plainPassword string 250 } 251 247 252 type downstreamConn struct { 248 253 conn … … 268 273 supportedCaps map[string]string 269 274 caps map[string]bool 275 sasl *downstreamSASL 270 276 271 277 lastBatchRef uint64 272 278 273 279 monitored casemapMap 274 275 saslServer sasl.Server276 280 } 277 281 … … 687 691 } 688 692 case "AUTHENTICATE": 689 if !dc.caps["sasl"] { 690 return ircError{&irc.Message{ 693 credentials, err := dc.handleAuthenticateCommand(msg) 694 if err != nil { 695 return err 696 } else if credentials == nil { 697 break 698 } 699 700 if err := dc.authenticate(ctx, credentials.plainUsername, credentials.plainPassword); err != nil { 701 dc.logger.Printf("SASL authentication error: %v", err) 702 dc.endSASL(&irc.Message{ 691 703 Prefix: dc.srv.prefix(), 692 704 Command: irc.ERR_SASLFAIL, 693 Params: []string{"*", "AUTHENTICATE requires the \"sasl\" capability to be enabled"}, 694 }} 695 } 696 if len(msg.Params) == 0 { 697 return ircError{&irc.Message{ 698 Prefix: dc.srv.prefix(), 699 Command: irc.ERR_SASLFAIL, 700 Params: []string{"*", "Missing AUTHENTICATE argument"}, 701 }} 702 } 703 704 var resp []byte 705 if msg.Params[0] == "*" { 706 dc.saslServer = nil 707 return ircError{&irc.Message{ 708 Prefix: dc.srv.prefix(), 709 Command: irc.ERR_SASLABORTED, 710 Params: []string{"*", "SASL authentication aborted"}, 711 }} 712 } else if dc.saslServer == nil { 713 mech := strings.ToUpper(msg.Params[0]) 714 switch mech { 715 case "PLAIN": 716 dc.saslServer = sasl.NewPlainServer(sasl.PlainAuthenticator(func(identity, username, password string) error { 717 // TODO: we can't use the command context here, because it 718 // gets cancelled once the command handler returns. SASL 719 // might take multiple AUTHENTICATE commands to complete. 720 return dc.authenticate(context.TODO(), username, password) 721 })) 722 default: 723 return ircError{&irc.Message{ 724 Prefix: dc.srv.prefix(), 725 Command: irc.ERR_SASLFAIL, 726 Params: []string{"*", fmt.Sprintf("Unsupported SASL mechanism %q", mech)}, 727 }} 728 } 729 } else if msg.Params[0] == "+" { 730 resp = nil 731 } else { 732 // TODO: multi-line messages 733 var err error 734 resp, err = base64.StdEncoding.DecodeString(msg.Params[0]) 735 if err != nil { 736 dc.saslServer = nil 737 return ircError{&irc.Message{ 738 Prefix: dc.srv.prefix(), 739 Command: irc.ERR_SASLFAIL, 740 Params: []string{"*", "Invalid base64-encoded response"}, 741 }} 742 } 743 } 744 745 challenge, done, err := dc.saslServer.Next(resp) 746 if err != nil { 747 dc.saslServer = nil 748 if ircErr, ok := err.(ircError); ok && ircErr.Message.Command == irc.ERR_PASSWDMISMATCH { 749 return ircError{&irc.Message{ 750 Prefix: dc.srv.prefix(), 751 Command: irc.ERR_SASLFAIL, 752 Params: []string{"*", ircErr.Message.Params[1]}, 753 }} 754 } 755 dc.SendMessage(&irc.Message{ 756 Prefix: dc.srv.prefix(), 757 Command: irc.ERR_SASLFAIL, 758 Params: []string{"*", "SASL error"}, 759 }) 760 return fmt.Errorf("SASL authentication failed: %v", err) 761 } else if done { 762 dc.saslServer = nil 763 // Technically we should send RPL_LOGGEDIN here. However we use 764 // RPL_LOGGEDIN to mirror the upstream connection status. Let's see 765 // how many clients that breaks. See: 766 // https://github.com/ircv3/ircv3-specifications/pull/476 767 dc.SendMessage(&irc.Message{ 768 Prefix: dc.srv.prefix(), 769 Command: irc.RPL_SASLSUCCESS, 770 Params: []string{dc.nick, "SASL authentication successful"}, 771 }) 772 } else { 773 challengeStr := "+" 774 if len(challenge) > 0 { 775 challengeStr = base64.StdEncoding.EncodeToString(challenge) 776 } 777 778 // TODO: multi-line messages 779 dc.SendMessage(&irc.Message{ 780 Prefix: dc.srv.prefix(), 781 Command: "AUTHENTICATE", 782 Params: []string{challengeStr}, 783 }) 784 } 705 Params: []string{"Authentication failed"}, 706 }) 707 break 708 } 709 710 // Technically we should send RPL_LOGGEDIN here. However we use 711 // RPL_LOGGEDIN to mirror the upstream connection status. Let's 712 // see how many clients that breaks. See: 713 // https://github.com/ircv3/ircv3-specifications/pull/476 714 dc.endSASL(nil) 785 715 case "BOUNCER": 786 716 var subcommand string … … 952 882 } 953 883 884 func (dc *downstreamConn) handleAuthenticateCommand(msg *irc.Message) (result *downstreamSASL, err error) { 885 defer func() { 886 if err != nil { 887 dc.sasl = nil 888 } 889 }() 890 891 if !dc.caps["sasl"] { 892 return nil, ircError{&irc.Message{ 893 Prefix: dc.srv.prefix(), 894 Command: irc.ERR_SASLFAIL, 895 Params: []string{"*", "AUTHENTICATE requires the \"sasl\" capability to be enabled"}, 896 }} 897 } 898 if len(msg.Params) == 0 { 899 return nil, ircError{&irc.Message{ 900 Prefix: dc.srv.prefix(), 901 Command: irc.ERR_SASLFAIL, 902 Params: []string{"*", "Missing AUTHENTICATE argument"}, 903 }} 904 } 905 if msg.Params[0] == "*" { 906 return nil, ircError{&irc.Message{ 907 Prefix: dc.srv.prefix(), 908 Command: irc.ERR_SASLABORTED, 909 Params: []string{"*", "SASL authentication aborted"}, 910 }} 911 } 912 913 var resp []byte 914 if dc.sasl == nil { 915 mech := strings.ToUpper(msg.Params[0]) 916 var server sasl.Server 917 switch mech { 918 case "PLAIN": 919 server = sasl.NewPlainServer(sasl.PlainAuthenticator(func(identity, username, password string) error { 920 dc.sasl.plainUsername = username 921 dc.sasl.plainPassword = password 922 return nil 923 })) 924 default: 925 return nil, ircError{&irc.Message{ 926 Prefix: dc.srv.prefix(), 927 Command: irc.ERR_SASLFAIL, 928 Params: []string{"*", fmt.Sprintf("Unsupported SASL mechanism %q", mech)}, 929 }} 930 } 931 932 dc.sasl = &downstreamSASL{server: server} 933 } else { 934 // TODO: multi-line messages 935 if msg.Params[0] == "+" { 936 resp = nil 937 } else if resp, err = base64.StdEncoding.DecodeString(msg.Params[0]); err != nil { 938 return nil, ircError{&irc.Message{ 939 Prefix: dc.srv.prefix(), 940 Command: irc.ERR_SASLFAIL, 941 Params: []string{"*", "Invalid base64-encoded response"}, 942 }} 943 } 944 } 945 946 challenge, done, err := dc.sasl.server.Next(resp) 947 if err != nil { 948 return nil, err 949 } else if done { 950 return dc.sasl, nil 951 } else { 952 challengeStr := "+" 953 if len(challenge) > 0 { 954 challengeStr = base64.StdEncoding.EncodeToString(challenge) 955 } 956 957 // TODO: multi-line messages 958 dc.SendMessage(&irc.Message{ 959 Prefix: dc.srv.prefix(), 960 Command: "AUTHENTICATE", 961 Params: []string{challengeStr}, 962 }) 963 return nil, nil 964 } 965 } 966 967 func (dc *downstreamConn) endSASL(msg *irc.Message) { 968 if dc.sasl == nil { 969 return 970 } 971 972 dc.sasl = nil 973 974 if msg != nil { 975 dc.SendMessage(msg) 976 } else { 977 dc.SendMessage(&irc.Message{ 978 Prefix: dc.srv.prefix(), 979 Command: irc.RPL_SASLSUCCESS, 980 Params: []string{dc.nick, "SASL authentication successful"}, 981 }) 982 } 983 } 984 954 985 func (dc *downstreamConn) setSupportedCap(name, value string) { 955 986 prevValue, hasPrev := dc.supportedCaps[name] … … 1142 1173 } 1143 1174 1144 if dc.saslServer != nil { 1145 dc.saslServer = nil 1146 dc.SendMessage(&irc.Message{ 1175 if dc.sasl != nil { 1176 dc.endSASL(&irc.Message{ 1147 1177 Prefix: dc.srv.prefix(), 1148 1178 Command: irc.ERR_SASLABORTED, … … 2331 2361 Params: []string{upstreamUser, upstreamChannel}, 2332 2362 }) 2363 case "AUTHENTICATE": 2364 // Post-connection-registration AUTHENTICATE is unsupported in 2365 // multi-upstream mode, or if the upstream doesn't support SASL 2366 uc := dc.upstream() 2367 if uc == nil || !uc.caps["sasl"] { 2368 return ircError{&irc.Message{ 2369 Prefix: dc.srv.prefix(), 2370 Command: irc.ERR_SASLFAIL, 2371 Params: []string{dc.nick, "Upstream network authentication not supported"}, 2372 }} 2373 } 2374 2375 credentials, err := dc.handleAuthenticateCommand(msg) 2376 if err != nil { 2377 return err 2378 } 2379 2380 if credentials != nil { 2381 if uc.saslClient != nil { 2382 dc.endSASL(&irc.Message{ 2383 Prefix: dc.srv.prefix(), 2384 Command: irc.ERR_SASLFAIL, 2385 Params: []string{dc.nick, "Another authentication attempt is already in progress"}, 2386 }) 2387 return nil 2388 } 2389 2390 uc.logger.Printf("starting post-registration SASL PLAIN authentication with username %q", credentials.plainUsername) 2391 uc.saslClient = sasl.NewPlainClient("", credentials.plainUsername, credentials.plainPassword) 2392 uc.enqueueCommand(dc, &irc.Message{ 2393 Command: "AUTHENTICATE", 2394 Params: []string{"PLAIN"}, 2395 }) 2396 } 2333 2397 case "MONITOR": 2334 2398 // MONITOR is unsupported in multi-upstream mode … … 2701 2765 func (dc *downstreamConn) handleNickServPRIVMSG(ctx context.Context, uc *upstreamConn, text string) { 2702 2766 username, password, ok := parseNickServCredentials(text, uc.nick) 2703 if !ok { 2704 return 2705 } 2706 2707 // User may have e.g. EXTERNAL mechanism configured. We do not want to 2708 // automatically erase the key pair or any other credentials. 2709 if uc.network.SASL.Mechanism != "" && uc.network.SASL.Mechanism != "PLAIN" { 2710 return 2711 } 2712 2713 dc.logger.Printf("auto-saving NickServ credentials with username %q", username) 2714 n := uc.network 2715 n.SASL.Mechanism = "PLAIN" 2716 n.SASL.Plain.Username = username 2717 n.SASL.Plain.Password = password 2718 if err := dc.srv.db.StoreNetwork(ctx, dc.user.ID, &n.Network); err != nil { 2719 dc.logger.Printf("failed to save NickServ credentials: %v", err) 2767 if ok { 2768 uc.network.autoSaveSASLPlain(ctx, username, password) 2720 2769 } 2721 2770 } -
trunk/upstream.go
r722 r724 32 32 "message-tags": true, 33 33 "multi-prefix": true, 34 "sasl": true, 34 35 "server-time": true, 35 36 "setname": true, … … 294 295 Params: []string{dc.nick, mask, "End of /WHO"}, 295 296 }) 297 case "AUTHENTICATE": 298 dc.endSASL(&irc.Message{ 299 Prefix: dc.srv.prefix(), 300 Command: irc.ERR_SASLABORTED, 301 Params: []string{dc.nick, "SASL authentication aborted"}, 302 }) 296 303 default: 297 304 panic(fmt.Errorf("Unsupported pending command %q", pendingCmd.msg.Command)) … … 312 319 func (uc *upstreamConn) enqueueCommand(dc *downstreamConn, msg *irc.Message) { 313 320 switch msg.Command { 314 case "LIST", "WHO" :321 case "LIST", "WHO", "AUTHENTICATE": 315 322 // Supported 316 323 default: … … 613 620 uc.saslStarted = false 614 621 615 uc.SendMessage(&irc.Message{ 616 Command: "CAP", 617 Params: []string{"END"}, 618 }) 622 if dc, _ := uc.dequeueCommand("AUTHENTICATE"); dc != nil && dc.sasl != nil { 623 if msg.Command == irc.RPL_SASLSUCCESS { 624 uc.network.autoSaveSASLPlain(context.TODO(), dc.sasl.plainUsername, dc.sasl.plainPassword) 625 } 626 627 dc.endSASL(msg) 628 } 629 630 if !uc.registered { 631 uc.SendMessage(&irc.Message{ 632 Command: "CAP", 633 Params: []string{"END"}, 634 }) 635 } 619 636 case irc.RPL_WELCOME: 620 637 uc.registered = true … … 1705 1722 } 1706 1723 1707 if uc.requestSASL() && !uc.caps["sasl"] {1708 requestCaps = append(requestCaps, "sasl")1709 }1710 1711 1724 if len(requestCaps) == 0 { 1712 1725 return … … 1750 1763 switch name { 1751 1764 case "sasl": 1765 if !uc.requestSASL() { 1766 return nil 1767 } 1752 1768 if !ok { 1753 1769 uc.logger.Printf("server refused to acknowledge the SASL capability") -
trunk/user.go
r722 r724 405 405 } 406 406 407 func (net *network) autoSaveSASLPlain(ctx context.Context, username, password string) { 408 // User may have e.g. EXTERNAL mechanism configured. We do not want to 409 // automatically erase the key pair or any other credentials. 410 if net.SASL.Mechanism != "" && net.SASL.Mechanism != "PLAIN" { 411 return 412 } 413 414 net.logger.Printf("auto-saving SASL PLAIN credentials with username %q", username) 415 net.SASL.Mechanism = "PLAIN" 416 net.SASL.Plain.Username = username 417 net.SASL.Plain.Password = password 418 if err := net.user.srv.db.StoreNetwork(ctx, net.user.ID, &net.Network); err != nil { 419 net.logger.Printf("failed to save SASL PLAIN credentials: %v", err) 420 } 421 } 422 407 423 type user struct { 408 424 User
Note:
See TracChangeset
for help on using the changeset viewer.