Changeset 139 in code


Ignore:
Timestamp:
Mar 25, 2020, 8:40:08 AM (5 years ago)
Author:
delthas
Message:

Add MODE arguments support

  • Add RPL_ISUPPORT support with CHANMODES, CHANTYPES, PREFIX parsing
  • Add support for channel mode state with mode arguments
  • Add upstream support for RPL_UMODEIS, RPL_CHANNELMODEIS
  • Request channel MODE on upstream channel JOIN
  • Use sane default channel mode and channel mode types
Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/bridge.go

    r132 r139  
    3030        // TODO: send multiple members in each message
    3131        for nick, membership := range ch.Members {
    32                 s := dc.marshalNick(ch.conn, nick)
    33                 if membership != 0 {
    34                         s = string(membership) + s
    35                 }
     32                s := membership.String() + dc.marshalNick(ch.conn, nick)
    3633
    3734                dc.SendMessage(&irc.Message{
  • trunk/downstream.go

    r137 r139  
    852852                }
    853853
    854                 uc, upstreamName, err := dc.unmarshalEntity(name)
    855                 if err != nil {
    856                         return err
    857                 }
    858 
    859                 if uc.isChannel(upstreamName) {
    860                         // TODO: handle MODE channel mode arguments
    861                         if modeStr != "" {
    862                                 uc.SendMessage(&irc.Message{
    863                                         Command: "MODE",
    864                                         Params:  []string{upstreamName, modeStr},
    865                                 })
    866                         } else {
    867                                 ch, ok := uc.channels[upstreamName]
    868                                 if !ok {
    869                                         return ircError{&irc.Message{
    870                                                 Command: irc.ERR_NOSUCHCHANNEL,
    871                                                 Params:  []string{dc.nick, name, "No such channel"},
    872                                         }}
    873                                 }
    874 
    875                                 dc.SendMessage(&irc.Message{
    876                                         Prefix:  dc.srv.prefix(),
    877                                         Command: irc.RPL_CHANNELMODEIS,
    878                                         Params:  []string{dc.nick, name, string(ch.modes)},
    879                                 })
    880                         }
    881                 } else {
    882                         if name != dc.nick {
    883                                 return ircError{&irc.Message{
    884                                         Command: irc.ERR_USERSDONTMATCH,
    885                                         Params:  []string{dc.nick, "Cannot change mode for other users"},
    886                                 }}
    887                         }
    888 
     854                if name == dc.nick {
    889855                        if modeStr != "" {
    890856                                dc.forEachUpstream(func(uc *upstreamConn) {
     
    901867                                })
    902868                        }
     869                        return nil
     870                }
     871
     872                uc, upstreamName, err := dc.unmarshalEntity(name)
     873                if err != nil {
     874                        return err
     875                }
     876
     877                if !uc.isChannel(upstreamName) {
     878                        return ircError{&irc.Message{
     879                                Command: irc.ERR_USERSDONTMATCH,
     880                                Params:  []string{dc.nick, "Cannot change mode for other users"},
     881                        }}
     882                }
     883
     884                if modeStr != "" {
     885                        params := []string{upstreamName, modeStr}
     886                        params = append(params, msg.Params[2:]...)
     887                        uc.SendMessage(&irc.Message{
     888                                Command: "MODE",
     889                                Params:  params,
     890                        })
     891                } else {
     892                        ch, ok := uc.channels[upstreamName]
     893                        if !ok {
     894                                return ircError{&irc.Message{
     895                                        Command: irc.ERR_NOSUCHCHANNEL,
     896                                        Params:  []string{dc.nick, name, "No such channel"},
     897                                }}
     898                        }
     899
     900                        if ch.modes == nil {
     901                                // we haven't received the initial RPL_CHANNELMODEIS yet
     902                                // ignore the request, we will broadcast the modes later when we receive RPL_CHANNELMODEIS
     903                                return nil
     904                        }
     905
     906                        modeStr, modeParams := ch.modes.Format()
     907                        params := []string{dc.nick, name, modeStr}
     908                        params = append(params, modeParams...)
     909
     910                        dc.SendMessage(&irc.Message{
     911                                Prefix:  dc.srv.prefix(),
     912                                Command: irc.RPL_CHANNELMODEIS,
     913                                Params:  params,
     914                        })
    903915                }
    904916        case "WHO":
  • trunk/irc.go

    r128 r139  
    1616)
    1717
    18 type modeSet string
     18type userModes string
    1919
    20 func (ms modeSet) Has(c byte) bool {
     20func (ms userModes) Has(c byte) bool {
    2121        return strings.IndexByte(string(ms), c) >= 0
    2222}
    2323
    24 func (ms *modeSet) Add(c byte) {
     24func (ms *userModes) Add(c byte) {
    2525        if !ms.Has(c) {
    26                 *ms += modeSet(c)
     26                *ms += userModes(c)
    2727        }
    2828}
    2929
    30 func (ms *modeSet) Del(c byte) {
     30func (ms *userModes) Del(c byte) {
    3131        i := strings.IndexByte(string(*ms), c)
    3232        if i >= 0 {
     
    3535}
    3636
    37 func (ms *modeSet) Apply(s string) error {
     37func (ms *userModes) Apply(s string) error {
    3838        var plusMinus byte
    3939        for i := 0; i < len(s); i++ {
     
    5454        return nil
    5555}
     56
     57type channelModeType byte
     58
     59// standard channel mode types, as explained in https://modern.ircdocs.horse/#mode-message
     60const (
     61        // modes that add or remove an address to or from a list
     62        modeTypeA channelModeType = iota
     63        // modes that change a setting on a channel, and must always have a parameter
     64        modeTypeB
     65        // modes that change a setting on a channel, and must have a parameter when being set, and no parameter when being unset
     66        modeTypeC
     67        // modes that change a setting on a channel, and must not have a parameter
     68        modeTypeD
     69)
     70
     71var stdChannelModes = map[byte]channelModeType{
     72        'b': modeTypeA, // ban list
     73        'e': modeTypeA, // ban exception list
     74        'I': modeTypeA, // invite exception list
     75        'k': modeTypeB, // channel key
     76        'l': modeTypeC, // channel user limit
     77        'i': modeTypeD, // channel is invite-only
     78        'm': modeTypeD, // channel is moderated
     79        'n': modeTypeD, // channel has no external messages
     80        's': modeTypeD, // channel is secret
     81        't': modeTypeD, // channel has protected topic
     82}
     83
     84type channelModes map[byte]string
     85
     86func (cm channelModes) Apply(modeTypes map[byte]channelModeType, modeStr string, arguments ...string) error {
     87        nextArgument := 0
     88        var plusMinus byte
     89        for i := 0; i < len(modeStr); i++ {
     90                mode := modeStr[i]
     91                if mode == '+' || mode == '-' {
     92                        plusMinus = mode
     93                        continue
     94                }
     95                if plusMinus != '+' && plusMinus != '-' {
     96                        return fmt.Errorf("malformed modestring %q: missing plus/minus", modeStr)
     97                }
     98
     99                mt, ok := modeTypes[mode]
     100                if !ok {
     101                        continue
     102                }
     103                if mt == modeTypeB || (mt == modeTypeC && plusMinus == '+') {
     104                        if plusMinus == '+' {
     105                                var argument string
     106                                // some sentitive arguments (such as channel keys) can be omitted for privacy
     107                                // (this will only happen for RPL_CHANNELMODEIS, never for MODE messages)
     108                                if nextArgument < len(arguments) {
     109                                        argument = arguments[nextArgument]
     110                                }
     111                                cm[mode] = argument
     112                        } else {
     113                                delete(cm, mode)
     114                        }
     115                        nextArgument++
     116                } else if mt == modeTypeC || mt == modeTypeD {
     117                        if plusMinus == '+' {
     118                                cm[mode] = ""
     119                        } else {
     120                                delete(cm, mode)
     121                        }
     122                }
     123        }
     124        return nil
     125}
     126
     127func (cm channelModes) Format() (modeString string, parameters []string) {
     128        var modesWithValues strings.Builder
     129        var modesWithoutValues strings.Builder
     130        parameters = make([]string, 0, 16)
     131        for mode, value := range cm {
     132                if value != "" {
     133                        modesWithValues.WriteString(string(mode))
     134                        parameters = append(parameters, value)
     135                } else {
     136                        modesWithoutValues.WriteString(string(mode))
     137                }
     138        }
     139        modeString = "+" + modesWithValues.String() + modesWithoutValues.String()
     140        return
     141}
     142
     143const stdChannelTypes = "#&+!"
    56144
    57145type channelStatus byte
     
    75163}
    76164
    77 type membership byte
     165type membership struct {
     166        Mode   byte
     167        Prefix byte
     168}
    78169
    79 const (
    80         membershipFounder   membership = '~'
    81         membershipProtected membership = '&'
    82         membershipOperator  membership = '@'
    83         membershipHalfOp    membership = '%'
    84         membershipVoice     membership = '+'
    85 )
     170var stdMemberships = []membership{
     171        {'q', '~'}, // founder
     172        {'a', '&'}, // protected
     173        {'o', '@'}, // operator
     174        {'h', '%'}, // halfop
     175        {'v', '+'}, // voice
     176}
    86177
    87 const stdMembershipPrefixes = "~&@%+"
    88 
    89 func (m membership) String() string {
    90         if m == 0 {
     178func (m *membership) String() string {
     179        if m == nil {
    91180                return ""
    92181        }
    93         return string(m)
    94 }
    95 
    96 func parseMembershipPrefix(s string) (prefix membership, nick string) {
    97         // TODO: any prefix from PREFIX RPL_ISUPPORT
    98         if strings.IndexByte(stdMembershipPrefixes, s[0]) >= 0 {
    99                 return membership(s[0]), s[1:]
    100         } else {
    101                 return 0, s
    102         }
     182        return string(m.Prefix)
    103183}
    104184
  • trunk/upstream.go

    r131 r139  
    2222        TopicTime time.Time
    2323        Status    channelStatus
    24         modes     modeSet
    25         Members   map[string]membership
     24        modes     channelModes
     25        Members   map[string]*membership
    2626        complete  bool
    2727}
     
    3939        serverName            string
    4040        availableUserModes    string
    41         availableChannelModes string
    42         channelModesWithParam string
     41        availableChannelModes map[byte]channelModeType
     42        availableChannelTypes string
     43        availableMemberships  []membership
    4344
    4445        registered bool
     
    4748        realname   string
    4849        closed     bool
    49         modes      modeSet
     50        modes      userModes
    5051        channels   map[string]*upstreamChannel
    5152        caps       map[string]string
     
    7374        outgoing := make(chan *irc.Message, 64)
    7475        uc := &upstreamConn{
    75                 network:  network,
    76                 logger:   logger,
    77                 net:      netConn,
    78                 irc:      irc.NewConn(netConn),
    79                 srv:      network.user.srv,
    80                 user:     network.user,
    81                 outgoing: outgoing,
    82                 ring:     NewRing(network.user.srv.RingCap),
    83                 channels: make(map[string]*upstreamChannel),
    84                 caps:     make(map[string]string),
     76                network:               network,
     77                logger:                logger,
     78                net:                   netConn,
     79                irc:                   irc.NewConn(netConn),
     80                srv:                   network.user.srv,
     81                user:                  network.user,
     82                outgoing:              outgoing,
     83                ring:                  NewRing(network.user.srv.RingCap),
     84                channels:              make(map[string]*upstreamChannel),
     85                caps:                  make(map[string]string),
     86                availableChannelTypes: stdChannelTypes,
     87                availableChannelModes: stdChannelModes,
     88                availableMemberships:  stdMemberships,
    8589        }
    8690
     
    131135
    132136func (uc *upstreamConn) isChannel(entity string) bool {
    133         for _, r := range entity {
    134                 switch r {
    135                 // TODO: support upstream ISUPPORT channel prefixes
    136                 case '#', '&', '+', '!':
    137                         return true
    138                 }
    139                 break
     137        if i := strings.IndexByte(uc.availableChannelTypes, entity[0]); i >= 0 {
     138                return true
    140139        }
    141140        return false
     141}
     142
     143func (uc *upstreamConn) parseMembershipPrefix(s string) (membership *membership, nick string) {
     144        for _, m := range uc.availableMemberships {
     145                if m.Prefix == s[0] {
     146                        return &m, s[1:]
     147                }
     148        }
     149        return nil, s
    142150}
    143151
     
    150158                })
    151159                return nil
    152         case "MODE":
    153                 var name, modeStr string
    154                 if err := parseMessageParams(msg, &name, &modeStr); err != nil {
    155                         return err
    156                 }
    157 
    158                 if !uc.isChannel(name) { // user mode change
    159                         if name != uc.nick {
    160                                 return fmt.Errorf("received MODE message for unknown nick %q", name)
    161                         }
    162                         return uc.modes.Apply(modeStr)
    163                 } else { // channel mode change
    164                         // TODO: handle MODE channel mode arguments
    165                         ch, err := uc.getChannel(name)
    166                         if err != nil {
    167                                 return err
    168                         }
    169                         if err := ch.modes.Apply(modeStr); err != nil {
    170                                 return err
    171                         }
    172 
    173                         uc.forEachDownstream(func(dc *downstreamConn) {
    174                                 dc.SendMessage(&irc.Message{
    175                                         Prefix:  dc.marshalUserPrefix(uc, msg.Prefix),
    176                                         Command: "MODE",
    177                                         Params:  []string{dc.marshalChannel(uc, name), modeStr},
    178                                 })
    179                         })
    180                 }
    181160        case "NOTICE":
    182161                uc.logger.Print(msg)
     
    347326                }
    348327        case irc.RPL_MYINFO:
    349                 if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, &uc.availableChannelModes); err != nil {
    350                         return err
    351                 }
    352                 if len(msg.Params) > 5 {
    353                         uc.channelModesWithParam = msg.Params[5]
     328                if err := parseMessageParams(msg, nil, &uc.serverName, nil, &uc.availableUserModes, nil); err != nil {
     329                        return err
     330                }
     331        case irc.RPL_ISUPPORT:
     332                if err := parseMessageParams(msg, nil, nil); err != nil {
     333                        return err
     334                }
     335                for _, token := range msg.Params[1 : len(msg.Params)-1] {
     336                        negate := false
     337                        parameter := token
     338                        value := ""
     339                        if strings.HasPrefix(token, "-") {
     340                                negate = true
     341                                token = token[1:]
     342                        } else {
     343                                if i := strings.IndexByte(token, '='); i >= 0 {
     344                                        parameter = token[:i]
     345                                        value = token[i+1:]
     346                                }
     347                        }
     348                        if !negate {
     349                                switch parameter {
     350                                case "CHANMODES":
     351                                        parts := strings.SplitN(value, ",", 5)
     352                                        if len(parts) < 4 {
     353                                                return fmt.Errorf("malformed ISUPPORT CHANMODES value: %v", value)
     354                                        }
     355                                        modes := make(map[byte]channelModeType)
     356                                        for i, mt := range []channelModeType{modeTypeA, modeTypeB, modeTypeC, modeTypeD} {
     357                                                for j := 0; j < len(parts[i]); j++ {
     358                                                        mode := parts[i][j]
     359                                                        modes[mode] = mt
     360                                                }
     361                                        }
     362                                        uc.availableChannelModes = modes
     363                                case "CHANTYPES":
     364                                        uc.availableChannelTypes = value
     365                                case "PREFIX":
     366                                        if value == "" {
     367                                                uc.availableMemberships = nil
     368                                        } else {
     369                                                if value[0] != '(' {
     370                                                        return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
     371                                                }
     372                                                sep := strings.IndexByte(value, ')')
     373                                                if sep < 0 || len(value) != sep*2 {
     374                                                        return fmt.Errorf("malformed ISUPPORT PREFIX value: %v", value)
     375                                                }
     376                                                memberships := make([]membership, len(value)/2-1)
     377                                                for i := range memberships {
     378                                                        memberships[i] = membership{
     379                                                                Mode:   value[i+1],
     380                                                                Prefix: value[sep+i+1],
     381                                                        }
     382                                                }
     383                                                uc.availableMemberships = memberships
     384                                        }
     385                                }
     386                        } else {
     387                                // TODO: handle ISUPPORT negations
     388                        }
    354389                }
    355390        case "NICK":
     
    400435                                        Name:    ch,
    401436                                        conn:    uc,
    402                                         Members: make(map[string]membership),
     437                                        Members: make(map[string]*membership),
    403438                                }
     439
     440                                uc.SendMessage(&irc.Message{
     441                                        Command: "MODE",
     442                                        Params:  []string{ch},
     443                                })
    404444                        } else {
    405445                                ch, err := uc.getChannel(ch)
     
    407447                                        return err
    408448                                }
    409                                 ch.Members[msg.Prefix.Name] = 0
     449                                ch.Members[msg.Prefix.Name] = nil
    410450                        }
    411451
     
    509549                        })
    510550                })
     551        case "MODE":
     552                var name, modeStr string
     553                if err := parseMessageParams(msg, &name, &modeStr); err != nil {
     554                        return err
     555                }
     556
     557                if !uc.isChannel(name) { // user mode change
     558                        if name != uc.nick {
     559                                return fmt.Errorf("received MODE message for unknown nick %q", name)
     560                        }
     561                        return uc.modes.Apply(modeStr)
     562                        // TODO: notify downstreams about user mode change?
     563                } else { // channel mode change
     564                        ch, err := uc.getChannel(name)
     565                        if err != nil {
     566                                return err
     567                        }
     568
     569                        if ch.modes != nil {
     570                                if err := ch.modes.Apply(uc.availableChannelModes, modeStr, msg.Params[2:]...); err != nil {
     571                                        return err
     572                                }
     573                        }
     574
     575                        uc.forEachDownstream(func(dc *downstreamConn) {
     576                                params := []string{dc.marshalChannel(uc, name), modeStr}
     577                                params = append(params, msg.Params[2:]...)
     578
     579                                dc.SendMessage(&irc.Message{
     580                                        Prefix:  dc.marshalUserPrefix(uc, msg.Prefix),
     581                                        Command: "MODE",
     582                                        Params:  params,
     583                                })
     584                        })
     585                }
     586        case irc.RPL_UMODEIS:
     587                if err := parseMessageParams(msg, nil); err != nil {
     588                        return err
     589                }
     590                modeStr := ""
     591                if len(msg.Params) > 1 {
     592                        modeStr = msg.Params[1]
     593                }
     594
     595                uc.modes = ""
     596                if err := uc.modes.Apply(modeStr); err != nil {
     597                        return err
     598                }
     599                // TODO: send RPL_UMODEIS to downstream connections when applicable
     600        case irc.RPL_CHANNELMODEIS:
     601                var channel string
     602                if err := parseMessageParams(msg, nil, &channel); err != nil {
     603                        return err
     604                }
     605                modeStr := ""
     606                if len(msg.Params) > 2 {
     607                        modeStr = msg.Params[2]
     608                }
     609
     610                ch, err := uc.getChannel(channel)
     611                if err != nil {
     612                        return err
     613                }
     614
     615                firstMode := ch.modes == nil
     616                ch.modes = make(map[byte]string)
     617                if err := ch.modes.Apply(uc.availableChannelModes, modeStr, msg.Params[3:]...); err != nil {
     618                        return err
     619                }
     620                if firstMode {
     621                        modeStr, modeParams := ch.modes.Format()
     622
     623                        uc.forEachDownstream(func(dc *downstreamConn) {
     624                                params := []string{dc.nick, dc.marshalChannel(uc, channel), modeStr}
     625                                params = append(params, modeParams...)
     626
     627                                dc.SendMessage(&irc.Message{
     628                                        Prefix:  dc.srv.prefix(),
     629                                        Command: irc.RPL_CHANNELMODEIS,
     630                                        Params:  params,
     631                                })
     632                        })
     633                }
    511634        case rpl_topicwhotime:
    512635                var name, who, timeStr string
     
    541664
    542665                for _, s := range strings.Split(members, " ") {
    543                         membership, nick := parseMembershipPrefix(s)
     666                        membership, nick := uc.parseMembershipPrefix(s)
    544667                        ch.Members[nick] = membership
    545668                }
     
    680803                        channelList := make([]string, len(channels))
    681804                        for i, channel := range channels {
    682                                 prefix, channel := parseMembershipPrefix(channel)
     805                                prefix, channel := uc.parseMembershipPrefix(channel)
    683806                                channel = dc.marshalChannel(uc, channel)
    684807                                channelList[i] = prefix.String() + channel
Note: See TracChangeset for help on using the changeset viewer.