Changeset 532 in code


Ignore:
Timestamp:
May 25, 2021, 2:42:51 PM (4 years ago)
Author:
contact
Message:

Implement the soju.im/bouncer-networks extension

Location:
trunk
Files:
2 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/downstream.go

    r529 r532  
    5858}}
    5959
     60func parseBouncerNetID(s string) (int64, error) {
     61        id, err := strconv.ParseInt(s, 10, 64)
     62        if err != nil {
     63                return 0, ircError{&irc.Message{
     64                        Command: "FAIL",
     65                        Params:  []string{"BOUNCER", "INVALID_NETID", s, "Invalid network ID"},
     66                }}
     67        }
     68        return id, nil
     69}
     70
    6071// ' ' and ':' break the IRC message wire format, '@' and '!' break prefixes,
    6172// '*' and '?' break masks
     
    6576// capabilities.
    6677var permanentDownstreamCaps = map[string]string{
    67         "batch":         "",
    68         "cap-notify":    "",
    69         "echo-message":  "",
    70         "invite-notify": "",
    71         "message-tags":  "",
    72         "sasl":          "PLAIN",
    73         "server-time":   "",
     78        "batch":                    "",
     79        "soju.im/bouncer-networks": "",
     80        "cap-notify":               "",
     81        "echo-message":             "",
     82        "invite-notify":            "",
     83        "message-tags":             "",
     84        "sasl":                     "PLAIN",
     85        "server-time":              "",
    7486}
    7587
     
    169181        if dc.network != nil {
    170182                f(dc.network)
    171         } else {
     183        } else if !dc.caps["soju.im/bouncer-networks"] {
    172184                dc.user.forEachNetwork(f)
    173185        }
     
    175187
    176188func (dc *downstreamConn) forEachUpstream(f func(*upstreamConn)) {
     189        if dc.network == nil && dc.caps["soju.im/bouncer-networks"] {
     190                return
     191        }
    177192        dc.user.forEachUpstream(func(uc *upstreamConn) {
    178193                if dc.network != nil && uc.network != dc.network {
     
    558573                        })
    559574                }
     575        case "BOUNCER":
     576                var subcommand string
     577                if err := parseMessageParams(msg, &subcommand); err != nil {
     578                        return err
     579                }
     580
     581                switch strings.ToUpper(subcommand) {
     582                case "BIND":
     583                        var idStr string
     584                        if err := parseMessageParams(msg, nil, &idStr); err != nil {
     585                                return err
     586                        }
     587
     588                        if dc.registered {
     589                                return ircError{&irc.Message{
     590                                        Command: "FAIL",
     591                                        Params:  []string{"BOUNCER", "REGISTRATION_IS_COMPLETED", "BIND", "Cannot bind bouncer network after registration"},
     592                                }}
     593                        }
     594                        if dc.user == nil {
     595                                return ircError{&irc.Message{
     596                                        Command: "FAIL",
     597                                        Params:  []string{"BOUNCER", "ACCOUNT_REQUIRED", "BIND", "Authentication needed to bind to bouncer network"},
     598                                }}
     599                        }
     600
     601                        id, err := parseBouncerNetID(idStr)
     602                        if err != nil {
     603                                return err
     604                        }
     605
     606                        var match *network
     607                        dc.user.forEachNetwork(func(net *network) {
     608                                if net.ID == id {
     609                                        match = net
     610                                }
     611                        })
     612                        if match == nil {
     613                                return ircError{&irc.Message{
     614                                        Command: "FAIL",
     615                                        Params:  []string{"BOUNCER", "INVALID_NETID", idStr, "Unknown network ID"},
     616                                }}
     617                        }
     618
     619                        dc.networkName = match.GetName()
     620                }
    560621        default:
    561622                dc.logger.Printf("unhandled message: %v", msg)
     
    910971                fmt.Sprintf("CHATHISTORY=%v", dc.srv.HistoryLimit),
    911972                "CASEMAPPING=ascii",
     973        }
     974
     975        if dc.network != nil {
     976                isupport = append(isupport, fmt.Sprintf("BOUNCER_NETID=%v", dc.network.ID))
    912977        }
    913978
     
    19071972                        Params:  []string{"-" + batchRef},
    19081973                })
     1974        case "BOUNCER":
     1975                var subcommand string
     1976                if err := parseMessageParams(msg, &subcommand); err != nil {
     1977                        return err
     1978                }
     1979
     1980                switch strings.ToUpper(subcommand) {
     1981                case "LISTNETWORKS":
     1982                        dc.SendMessage(&irc.Message{
     1983                                Prefix:  dc.srv.prefix(),
     1984                                Command: "BATCH",
     1985                                Params:  []string{"+networks", "bouncer-networks"},
     1986                        })
     1987                        dc.user.forEachNetwork(func(network *network) {
     1988                                id := fmt.Sprintf("%v", network.ID)
     1989
     1990                                state := "disconnected"
     1991                                if uc := network.conn; uc != nil {
     1992                                        state = "connected"
     1993                                }
     1994
     1995                                attrs := irc.Tags{
     1996                                        "name":  irc.TagValue(network.GetName()),
     1997                                        "state": irc.TagValue(state),
     1998                                }
     1999
     2000                                dc.SendMessage(&irc.Message{
     2001                                        Tags:    irc.Tags{"batch": irc.TagValue("networks")},
     2002                                        Prefix:  dc.srv.prefix(),
     2003                                        Command: "BOUNCER",
     2004                                        Params:  []string{"NETWORK", id, attrs.String()},
     2005                                })
     2006                        })
     2007                        dc.SendMessage(&irc.Message{
     2008                                Prefix:  dc.srv.prefix(),
     2009                                Command: "BATCH",
     2010                                Params:  []string{"-networks"},
     2011                        })
     2012                case "ADDNETWORK":
     2013                        var attrsStr string
     2014                        if err := parseMessageParams(msg, nil, &attrsStr); err != nil {
     2015                                return err
     2016                        }
     2017                        attrs := irc.ParseTags(attrsStr)
     2018
     2019                        host, ok := attrs.GetTag("host")
     2020                        if !ok {
     2021                                return ircError{&irc.Message{
     2022                                        Command: "FAIL",
     2023                                        Params:  []string{"BOUNCER", "NEED_ATTRIBUTE", subcommand, "host", "Missing required host attribute"},
     2024                                }}
     2025                        }
     2026
     2027                        addr := host
     2028                        if port, ok := attrs.GetTag("port"); ok {
     2029                                addr += ":" + port
     2030                        }
     2031
     2032                        if tlsStr, ok := attrs.GetTag("tls"); ok && tlsStr == "0" {
     2033                                addr = "irc+insecure://" + tlsStr
     2034                        }
     2035
     2036                        nick, ok := attrs.GetTag("nickname")
     2037                        if !ok {
     2038                                nick = dc.nick
     2039                        }
     2040
     2041                        username, _ := attrs.GetTag("username")
     2042                        realname, _ := attrs.GetTag("realname")
     2043
     2044                        // TODO: reject unknown attributes
     2045
     2046                        record := &Network{
     2047                                Addr:     addr,
     2048                                Nick:     nick,
     2049                                Username: username,
     2050                                Realname: realname,
     2051                        }
     2052                        network, err := dc.user.createNetwork(record)
     2053                        if err != nil {
     2054                                return ircError{&irc.Message{
     2055                                        Command: "FAIL",
     2056                                        Params:  []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to create network: %v", err)},
     2057                                }}
     2058                        }
     2059
     2060                        dc.SendMessage(&irc.Message{
     2061                                Prefix:  dc.srv.prefix(),
     2062                                Command: "BOUNCER",
     2063                                Params:  []string{"ADDNETWORK", fmt.Sprintf("%v", network.ID)},
     2064                        })
     2065                case "CHANGENETWORK":
     2066                        var idStr, attrsStr string
     2067                        if err := parseMessageParams(msg, nil, &idStr, &attrsStr); err != nil {
     2068                                return err
     2069                        }
     2070                        id, err := parseBouncerNetID(idStr)
     2071                        if err != nil {
     2072                                return err
     2073                        }
     2074                        attrs := irc.ParseTags(attrsStr)
     2075
     2076                        net := dc.user.getNetworkByID(id)
     2077                        if net == nil {
     2078                                return ircError{&irc.Message{
     2079                                        Command: "FAIL",
     2080                                        Params:  []string{"BOUNCER", "INVALID_NETID", idStr, "Invalid network ID"},
     2081                                }}
     2082                        }
     2083
     2084                        record := net.Network // copy network record because we'll mutate it
     2085                        for k, v := range attrs {
     2086                                s := string(v)
     2087                                switch k {
     2088                                // TODO: host, port, tls
     2089                                case "nickname":
     2090                                        record.Nick = s
     2091                                case "username":
     2092                                        record.Username = s
     2093                                case "realname":
     2094                                        record.Realname = s
     2095                                default:
     2096                                        return ircError{&irc.Message{
     2097                                                Command: "FAIL",
     2098                                                Params:  []string{"BOUNCER", "UNKNOWN_ATTRIBUTE", subcommand, k, "Unknown attribute"},
     2099                                        }}
     2100                                }
     2101                        }
     2102
     2103                        _, err = dc.user.updateNetwork(&record)
     2104                        if err != nil {
     2105                                return ircError{&irc.Message{
     2106                                        Command: "FAIL",
     2107                                        Params:  []string{"BOUNCER", "UNKNOWN_ERROR", subcommand, fmt.Sprintf("Failed to update network: %v", err)},
     2108                                }}
     2109                        }
     2110
     2111                        dc.SendMessage(&irc.Message{
     2112                                Prefix:  dc.srv.prefix(),
     2113                                Command: "BOUNCER",
     2114                                Params:  []string{"CHANGENETWORK", idStr},
     2115                        })
     2116                case "DELNETWORK":
     2117                        var idStr string
     2118                        if err := parseMessageParams(msg, nil, &idStr); err != nil {
     2119                                return err
     2120                        }
     2121                        id, err := parseBouncerNetID(idStr)
     2122                        if err != nil {
     2123                                return err
     2124                        }
     2125
     2126                        net := dc.user.getNetworkByID(id)
     2127                        if net == nil {
     2128                                return ircError{&irc.Message{
     2129                                        Command: "FAIL",
     2130                                        Params:  []string{"BOUNCER", "INVALID_NETID", idStr, "Invalid network ID"},
     2131                                }}
     2132                        }
     2133
     2134                        if err := dc.user.deleteNetwork(net.ID); err != nil {
     2135                                return err
     2136                        }
     2137
     2138                        dc.SendMessage(&irc.Message{
     2139                                Prefix:  dc.srv.prefix(),
     2140                                Command: "BOUNCER",
     2141                                Params:  []string{"DELNETWORK", idStr},
     2142                        })
     2143                default:
     2144                        return ircError{&irc.Message{
     2145                                Command: "FAIL",
     2146                                Params:  []string{"BOUNCER", "UNKNOWN_COMMAND", subcommand, "Unknown subcommand"},
     2147                        }}
     2148                }
    19092149        default:
    19102150                dc.logger.Printf("unhandled message: %v", msg)
  • trunk/user.go

    r501 r532  
    142142func (net *network) forEachDownstream(f func(*downstreamConn)) {
    143143        net.user.forEachDownstream(func(dc *downstreamConn) {
     144                if dc.network == nil && dc.caps["soju.im/bouncer-networks"] {
     145                        return
     146                }
    144147                if dc.network != nil && dc.network != net {
    145148                        return
     
    512515                        uc.updateAway()
    513516
     517                        netIDStr := fmt.Sprintf("%v", uc.network.ID)
    514518                        uc.forEachDownstream(func(dc *downstreamConn) {
    515519                                dc.updateSupportedCaps()
    516                                 sendServiceNOTICE(dc, fmt.Sprintf("connected to %s", uc.network.GetName()))
     520
     521                                if dc.caps["soju.im/bouncer-networks"] {
     522                                        dc.SendMessage(&irc.Message{
     523                                                Prefix:  dc.srv.prefix(),
     524                                                Command: "BOUNCER",
     525                                                Params:  []string{"NETWORK", netIDStr, "status=connected"},
     526                                        })
     527                                } else {
     528                                        sendServiceNOTICE(dc, fmt.Sprintf("connected to %s", uc.network.GetName()))
     529                                }
    517530
    518531                                dc.updateNick()
     
    641654        }
    642655
     656        netIDStr := fmt.Sprintf("%v", uc.network.ID)
    643657        uc.forEachDownstream(func(dc *downstreamConn) {
    644658                dc.updateSupportedCaps()
     659
     660                if dc.caps["soju.im/bouncer-networks"] {
     661                        dc.SendMessage(&irc.Message{
     662                                Prefix:  dc.srv.prefix(),
     663                                Command: "BOUNCER",
     664                                Params:  []string{"NETWORK", netIDStr, "status=disconnected"},
     665                        })
     666                }
    645667        })
    646668
    647669        if uc.network.lastError == nil {
    648670                uc.forEachDownstream(func(dc *downstreamConn) {
    649                         sendServiceNOTICE(dc, fmt.Sprintf("disconnected from %s", uc.network.GetName()))
     671                        if !dc.caps["soju.im/bouncer-networks"] {
     672                                sendServiceNOTICE(dc, fmt.Sprintf("disconnected from %s", uc.network.GetName()))
     673                        }
    650674                })
    651675        }
     
    701725
    702726        u.addNetwork(network)
     727
     728        // TODO: broadcast network status
     729        idStr := fmt.Sprintf("%v", network.ID)
     730        u.forEachDownstream(func(dc *downstreamConn) {
     731                if dc.caps["soju.im/bouncer-networks"] {
     732                        dc.SendMessage(&irc.Message{
     733                                Prefix:  dc.srv.prefix(),
     734                                Command: "BOUNCER",
     735                                Params:  []string{"NETWORK", idStr, "network=" + network.GetName()},
     736                        })
     737                }
     738        })
    703739
    704740        return network, nil
     
    755791        u.addNetwork(updatedNetwork)
    756792
     793        // TODO: broadcast BOUNCER NETWORK notifications
     794
    757795        return updatedNetwork, nil
    758796}
     
    769807
    770808        u.removeNetwork(network)
     809
     810        idStr := fmt.Sprintf("%v", network.ID)
     811        u.forEachDownstream(func(dc *downstreamConn) {
     812                if dc.caps["soju.im/bouncer-networks"] {
     813                        dc.SendMessage(&irc.Message{
     814                                Prefix:  dc.srv.prefix(),
     815                                Command: "BOUNCER",
     816                                Params:  []string{"NETWORK", idStr, "*"},
     817                        })
     818                }
     819        })
     820
    771821        return nil
    772822}
Note: See TracChangeset for help on using the changeset viewer.