Index: /trunk/bridge.go
===================================================================
--- /trunk/bridge.go	(revision 291)
+++ /trunk/bridge.go	(revision 292)
@@ -39,6 +39,6 @@
 	downstreamName := dc.marshalEntity(ch.conn.network, ch.Name)
 
-	for nick, membership := range ch.Members {
-		s := membership.String() + dc.marshalEntity(ch.conn.network, nick)
+	for nick, memberships := range ch.Members {
+		s := memberships.Format(dc) + dc.marshalEntity(ch.conn.network, nick)
 
 		dc.SendMessage(&irc.Message{
Index: /trunk/downstream.go
===================================================================
--- /trunk/downstream.go	(revision 291)
+++ /trunk/downstream.go	(revision 292)
@@ -60,4 +60,11 @@
 	"sasl":         "PLAIN",
 	"server-time":  "",
+}
+
+// needAllDownstreamCaps is the list of downstream capabilities that
+// require support from all upstreams to be enabled
+var needAllDownstreamCaps = map[string]string{
+	"away-notify":  "",
+	"multi-prefix": "",
 }
 
@@ -597,13 +604,20 @@
 
 func (dc *downstreamConn) updateSupportedCaps() {
-	awayNotifySupported := true
+	supportedCaps := make(map[string]bool)
+	for cap := range needAllDownstreamCaps {
+		supportedCaps[cap] = true
+	}
 	dc.forEachUpstream(func(uc *upstreamConn) {
-		awayNotifySupported = awayNotifySupported && uc.caps["away-notify"]
+		for cap, supported := range supportedCaps {
+			supportedCaps[cap] = supported && uc.caps[cap]
+		}
 	})
 
-	if awayNotifySupported {
-		dc.setSupportedCap("away-notify", "")
-	} else {
-		dc.unsetSupportedCap("away-notify")
+	for cap, supported := range supportedCaps {
+		if supported {
+			dc.setSupportedCap(cap, needAllDownstreamCaps[cap])
+		} else {
+			dc.unsetSupportedCap(cap)
+		}
 	}
 }
Index: /trunk/irc.go
===================================================================
--- /trunk/irc.go	(revision 291)
+++ /trunk/irc.go	(revision 292)
@@ -177,9 +177,55 @@
 }
 
-func (m *membership) String() string {
-	if m == nil {
-		return ""
-	}
-	return string(m.Prefix)
+// memberships always sorted by descending membership rank
+type memberships []membership
+
+func (m *memberships) Add(availableMemberships []membership, newMembership membership) {
+	l := *m
+	i := 0
+	for _, availableMembership := range availableMemberships {
+		if i >= len(l) {
+			break
+		}
+		if l[i] == availableMembership {
+			if availableMembership == newMembership {
+				// we already have this membership
+				return
+			}
+			i++
+			continue
+		}
+		if availableMembership == newMembership {
+			break
+		}
+	}
+	// insert newMembership at i
+	l = append(l, membership{})
+	copy(l[i+1:], l[i:])
+	l[i] = newMembership
+	*m = l
+}
+
+func (m *memberships) Remove(oldMembership membership) {
+	l := *m
+	for i, currentMembership := range l {
+		if currentMembership == oldMembership {
+			*m = append(l[:i], l[i+1:]...)
+			return
+		}
+	}
+}
+
+func (m memberships) Format(dc *downstreamConn) string {
+	if !dc.caps["multi-prefix"] {
+		if len(m) == 0 {
+			return ""
+		}
+		return string(m[0].Prefix)
+	}
+	prefixes := make([]byte, len(m))
+	for i, membership := range m {
+		prefixes[i] = membership.Prefix
+	}
+	return string(prefixes)
 }
 
Index: /trunk/upstream.go
===================================================================
--- /trunk/upstream.go	(revision 291)
+++ /trunk/upstream.go	(revision 292)
@@ -25,4 +25,5 @@
 	"labeled-response": true,
 	"message-tags":     true,
+	"multi-prefix":     true,
 	"server-time":      true,
 }
@@ -37,5 +38,5 @@
 	modes        channelModes
 	creationTime string
-	Members      map[string]*membership
+	Members      map[string]*memberships
 	complete     bool
 }
@@ -230,11 +231,17 @@
 }
 
-func (uc *upstreamConn) parseMembershipPrefix(s string) (membership *membership, nick string) {
+func (uc *upstreamConn) parseMembershipPrefix(s string) (ms *memberships, nick string) {
+	memberships := make(memberships, 0, 4)
+	i := 0
 	for _, m := range uc.availableMemberships {
-		if m.Prefix == s[0] {
-			return &m, s[1:]
-		}
-	}
-	return nil, s
+		if i >= len(s) {
+			break
+		}
+		if s[i] == m.Prefix {
+			memberships = append(memberships, m)
+			i++
+		}
+	}
+	return &memberships, s[i:]
 }
 
@@ -645,7 +652,7 @@
 
 		for _, ch := range uc.channels {
-			if membership, ok := ch.Members[msg.Prefix.Name]; ok {
+			if memberships, ok := ch.Members[msg.Prefix.Name]; ok {
 				delete(ch.Members, msg.Prefix.Name)
-				ch.Members[newNick] = membership
+				ch.Members[newNick] = memberships
 				uc.appendLog(ch.Name, msg)
 				uc.appendHistory(ch.Name, msg)
@@ -674,5 +681,5 @@
 					Name:    ch,
 					conn:    uc,
-					Members: make(map[string]*membership),
+					Members: make(map[string]*memberships),
 				}
 
@@ -940,6 +947,6 @@
 				members := splitSpace(members)
 				for i, member := range members {
-					membership, nick := uc.parseMembershipPrefix(member)
-					members[i] = membership.String() + dc.marshalEntity(uc.network, nick)
+					memberships, nick := uc.parseMembershipPrefix(member)
+					members[i] = memberships.Format(dc) + dc.marshalEntity(uc.network, nick)
 				}
 				memberStr := strings.Join(members, " ")
@@ -961,6 +968,6 @@
 
 		for _, s := range splitSpace(members) {
-			membership, nick := uc.parseMembershipPrefix(s)
-			ch.Members[nick] = membership
+			memberships, nick := uc.parseMembershipPrefix(s)
+			ch.Members[nick] = memberships
 		}
 	case irc.RPL_ENDOFNAMES:
@@ -1113,5 +1120,5 @@
 				prefix, channel := uc.parseMembershipPrefix(channel)
 				channel = dc.marshalEntity(uc.network, channel)
-				channelList[i] = prefix.String() + channel
+				channelList[i] = prefix.Format(dc) + channel
 			}
 			channels := strings.Join(channelList, " ")
