Index: /trunk/downstream.go
===================================================================
--- /trunk/downstream.go	(revision 292)
+++ /trunk/downstream.go	(revision 293)
@@ -269,5 +269,5 @@
 // marshalMessage re-formats a message coming from an upstream connection so
 // that it's suitable for being sent on this downstream connection. Only
-// messages that may appear in logs are supported.
+// messages that may appear in logs are supported, except MODE.
 func (dc *downstreamConn) marshalMessage(msg *irc.Message, net *network) *irc.Message {
 	msg = msg.Copy()
@@ -286,6 +286,4 @@
 		msg.Params[1] = dc.marshalEntity(net, msg.Params[1])
 	case "TOPIC":
-		msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
-	case "MODE":
 		msg.Params[0] = dc.marshalEntity(net, msg.Params[0])
 	case "QUIT":
Index: /trunk/irc.go
===================================================================
--- /trunk/irc.go	(revision 292)
+++ /trunk/irc.go	(revision 293)
@@ -85,7 +85,16 @@
 type channelModes map[byte]string
 
-func (cm channelModes) Apply(modeTypes map[byte]channelModeType, modeStr string, arguments ...string) error {
+// applyChannelModes parses a mode string and mode arguments from a MODE message,
+// and applies the corresponding channel mode and user membership changes on that channel.
+//
+// If ch.modes is nil, channel modes are not updated.
+//
+// needMarshaling is a list of indexes of mode arguments that represent entities
+// that must be marshaled when sent downstream.
+func applyChannelModes(ch *upstreamChannel, modeStr string, arguments []string) (needMarshaling map[int]struct{}, err error) {
+	needMarshaling = make(map[int]struct{}, len(arguments))
 	nextArgument := 0
 	var plusMinus byte
+outer:
 	for i := 0; i < len(modeStr); i++ {
 		mode := modeStr[i]
@@ -95,8 +104,28 @@
 		}
 		if plusMinus != '+' && plusMinus != '-' {
-			return fmt.Errorf("malformed modestring %q: missing plus/minus", modeStr)
-		}
-
-		mt, ok := modeTypes[mode]
+			return nil, fmt.Errorf("malformed modestring %q: missing plus/minus", modeStr)
+		}
+
+		for _, membership := range ch.conn.availableMemberships {
+			if membership.Mode == mode {
+				if nextArgument >= len(arguments) {
+					return nil, fmt.Errorf("malformed modestring %q: missing mode argument for %c%c", modeStr, plusMinus, mode)
+				}
+				member := arguments[nextArgument]
+				if _, ok := ch.Members[member]; ok {
+					if plusMinus == '+' {
+						ch.Members[member].Add(ch.conn.availableMemberships, membership)
+					} else {
+						// TODO: for upstreams without multi-prefix, query the user modes again
+						ch.Members[member].Remove(membership)
+					}
+				}
+				needMarshaling[nextArgument] = struct{}{}
+				nextArgument++
+				continue outer
+			}
+		}
+
+		mt, ok := ch.conn.availableChannelModes[mode]
 		if !ok {
 			continue
@@ -110,18 +139,22 @@
 					argument = arguments[nextArgument]
 				}
-				cm[mode] = argument
+				if ch.modes != nil {
+					ch.modes[mode] = argument
+				}
 			} else {
-				delete(cm, mode)
+				delete(ch.modes, mode)
 			}
 			nextArgument++
 		} else if mt == modeTypeC || mt == modeTypeD {
 			if plusMinus == '+' {
-				cm[mode] = ""
+				if ch.modes != nil {
+					ch.modes[mode] = ""
+				}
 			} else {
-				delete(cm, mode)
-			}
-		}
-	}
-	return nil
+				delete(ch.modes, mode)
+			}
+		}
+	}
+	return needMarshaling, nil
 }
 
Index: /trunk/upstream.go
===================================================================
--- /trunk/upstream.go	(revision 292)
+++ /trunk/upstream.go	(revision 293)
@@ -818,11 +818,28 @@
 			}
 
-			if ch.modes != nil {
-				if err := ch.modes.Apply(uc.availableChannelModes, modeStr, msg.Params[2:]...); err != nil {
-					return err
+			needMarshaling, err := applyChannelModes(ch, modeStr, msg.Params[2:])
+			if err != nil {
+				return err
+			}
+
+			uc.appendLog(ch.Name, msg)
+			uc.forEachDownstream(func(dc *downstreamConn) {
+				params := make([]string, len(msg.Params))
+				params[0] = dc.marshalEntity(uc.network, name)
+				params[1] = modeStr
+
+				copy(params[2:], msg.Params[2:])
+				for i, modeParam := range params[2:] {
+					if _, ok := needMarshaling[i]; ok {
+						params[2+i] = dc.marshalEntity(uc.network, modeParam)
+					}
 				}
-			}
-
-			uc.produce(ch.Name, msg, nil)
+
+				dc.SendMessage(&irc.Message{
+					Prefix:  dc.marshalUserPrefix(uc.network, msg.Prefix),
+					Command: "MODE",
+					Params:  params,
+				})
+			})
 		}
 	case irc.RPL_UMODEIS:
@@ -857,5 +874,5 @@
 		firstMode := ch.modes == nil
 		ch.modes = make(map[byte]string)
-		if err := ch.modes.Apply(uc.availableChannelModes, modeStr, msg.Params[3:]...); err != nil {
+		if _, err := applyChannelModes(ch, modeStr, msg.Params[3:]); err != nil {
 			return err
 		}
