Index: trunk/downstream.go
===================================================================
--- trunk/downstream.go	(revision 683)
+++ trunk/downstream.go	(revision 684)
@@ -229,4 +229,5 @@
 	"MAXTARGETS":    true,
 	"MODES":         true,
+	"MONITOR":       true,
 	"NAMELEN":       true,
 	"NETWORK":       true,
@@ -265,4 +266,6 @@
 	lastBatchRef uint64
 
+	monitored casemapMap
+
 	saslServer sasl.Server
 }
@@ -277,4 +280,5 @@
 		supportedCaps: make(map[string]string),
 		caps:          make(map[string]bool),
+		monitored:     newCasemapMap(0),
 	}
 	dc.hostname = remoteAddr
@@ -2254,4 +2258,87 @@
 			Params:  []string{upstreamUser, upstreamChannel},
 		})
+	case "MONITOR":
+		// MONITOR is unsupported in multi-upstream mode
+		uc := dc.upstream()
+		if uc == nil {
+			return newUnknownCommandError(msg.Command)
+		}
+
+		var subcommand string
+		if err := parseMessageParams(msg, &subcommand); err != nil {
+			return err
+		}
+
+		switch strings.ToUpper(subcommand) {
+		case "+", "-":
+			var targets string
+			if err := parseMessageParams(msg, nil, &targets); err != nil {
+				return err
+			}
+			for _, target := range strings.Split(targets, ",") {
+				if subcommand == "+" {
+					// Hard limit, just to avoid having downstreams fill our map
+					if len(dc.monitored.innerMap) >= 1000 {
+						dc.SendMessage(&irc.Message{
+							Prefix:  dc.srv.prefix(),
+							Command: irc.ERR_MONLISTFULL,
+							Params:  []string{dc.nick, "1000", target, "Bouncer monitor list is full"},
+						})
+						continue
+					}
+
+					dc.monitored.SetValue(target, nil)
+
+					if uc.monitored.Has(target) {
+						cmd := irc.RPL_MONOFFLINE
+						if online := uc.monitored.Value(target); online {
+							cmd = irc.RPL_MONONLINE
+						}
+
+						dc.SendMessage(&irc.Message{
+							Prefix:  dc.srv.prefix(),
+							Command: cmd,
+							Params:  []string{dc.nick, target},
+						})
+					}
+				} else {
+					dc.monitored.Delete(target)
+				}
+			}
+			uc.updateMonitor()
+		case "C": // clear
+			dc.monitored = newCasemapMap(0)
+			uc.updateMonitor()
+		case "L": // list
+			// TODO: be less lazy and pack the list
+			for _, entry := range dc.monitored.innerMap {
+				dc.SendMessage(&irc.Message{
+					Prefix:  dc.srv.prefix(),
+					Command: irc.RPL_MONLIST,
+					Params:  []string{dc.nick, entry.originalKey},
+				})
+			}
+			dc.SendMessage(&irc.Message{
+				Prefix:  dc.srv.prefix(),
+				Command: irc.RPL_ENDOFMONLIST,
+				Params:  []string{dc.nick, "End of MONITOR list"},
+			})
+		case "S": // status
+			// TODO: be less lazy and pack the lists
+			for _, entry := range dc.monitored.innerMap {
+				target := entry.originalKey
+
+				cmd := irc.RPL_MONOFFLINE
+				if online := uc.monitored.Value(target); online {
+					cmd = irc.RPL_MONONLINE
+				}
+
+				dc.SendMessage(&irc.Message{
+					Prefix:  dc.srv.prefix(),
+					Command: cmd,
+					Params:  []string{dc.nick, target},
+				})
+			}
+		}
 	case "CHATHISTORY":
 		var subcommand string
Index: trunk/irc.go
===================================================================
--- trunk/irc.go	(revision 683)
+++ trunk/irc.go	(revision 684)
@@ -409,4 +409,34 @@
 }
 
+func generateMonitor(subcmd string, targets []string) []*irc.Message {
+	maxLength := maxMessageLength - len("MONITOR "+subcmd+" ")
+
+	var msgs []*irc.Message
+	var buf []string
+	n := 0
+	for _, target := range targets {
+		if n+len(target)+1 > maxLength {
+			msgs = append(msgs, &irc.Message{
+				Command: "MONITOR",
+				Params:  []string{subcmd, strings.Join(buf, ",")},
+			})
+			buf = buf[:0]
+			n = 0
+		}
+
+		buf = append(buf, target)
+		n += len(target) + 1
+	}
+
+	if len(buf) > 0 {
+		msgs = append(msgs, &irc.Message{
+			Command: "MONITOR",
+			Params:  []string{subcmd, strings.Join(buf, ",")},
+		})
+	}
+
+	return msgs
+}
+
 type joinSorter struct {
 	channels []string
@@ -635,4 +665,14 @@
 }
 
+type monitorCasemapMap struct{ casemapMap }
+
+func (cm *monitorCasemapMap) Value(name string) (online bool) {
+	entry, ok := cm.innerMap[cm.casemap(name)]
+	if !ok {
+		return false
+	}
+	return entry.value.(bool)
+}
+
 func isWordBoundary(r rune) bool {
 	switch r {
Index: trunk/upstream.go
===================================================================
--- trunk/upstream.go	(revision 683)
+++ trunk/upstream.go	(revision 684)
@@ -104,4 +104,5 @@
 	account       string
 	nextLabelID   uint64
+	monitored     monitorCasemapMap
 
 	saslClient  sasl.Client
@@ -210,4 +211,5 @@
 		isupport:              make(map[string]*string),
 		pendingCmds:           make(map[string][]pendingUpstreamCommand),
+		monitored:             monitorCasemapMap{newCasemapMap(0)},
 	}
 	return uc, nil
@@ -1414,4 +1416,47 @@
 			})
 		})
+	case irc.RPL_MONONLINE, irc.RPL_MONOFFLINE:
+		var targetsStr string
+		if err := parseMessageParams(msg, nil, &targetsStr); err != nil {
+			return err
+		}
+		targets := strings.Split(targetsStr, ",")
+
+		online := msg.Command == irc.RPL_MONONLINE
+		for _, target := range targets {
+			prefix := irc.ParsePrefix(target)
+			uc.monitored.SetValue(prefix.Name, online)
+		}
+
+		uc.forEachDownstream(func(dc *downstreamConn) {
+			for _, target := range targets {
+				prefix := irc.ParsePrefix(target)
+				if dc.monitored.Has(prefix.Name) {
+					dc.SendMessage(&irc.Message{
+						Prefix:  dc.srv.prefix(),
+						Command: msg.Command,
+						Params:  []string{dc.nick, target},
+					})
+				}
+			}
+		})
+	case irc.ERR_MONLISTFULL:
+		var limit, targetsStr string
+		if err := parseMessageParams(msg, nil, &limit, &targetsStr); err != nil {
+			return err
+		}
+
+		targets := strings.Split(targetsStr, ",")
+		uc.forEachDownstream(func(dc *downstreamConn) {
+			for _, target := range targets {
+				if dc.monitored.Has(target) {
+					dc.SendMessage(&irc.Message{
+						Prefix:  dc.srv.prefix(),
+						Command: msg.Command,
+						Params:  []string{dc.nick, limit, target},
+					})
+				}
+			}
+		})
 	case irc.RPL_AWAY:
 		var nick, reason string
@@ -1913,2 +1958,51 @@
 	uch.updateAutoDetach(ch.DetachAfter)
 }
+
+func (uc *upstreamConn) updateMonitor() {
+	add := make(map[string]struct{})
+	var addList []string
+	seen := make(map[string]struct{})
+	uc.forEachDownstream(func(dc *downstreamConn) {
+		for targetCM := range dc.monitored.innerMap {
+			if !uc.monitored.Has(targetCM) {
+				if _, ok := add[targetCM]; !ok {
+					addList = append(addList, targetCM)
+				}
+				add[targetCM] = struct{}{}
+			} else {
+				seen[targetCM] = struct{}{}
+			}
+		}
+	})
+
+	removeAll := true
+	var removeList []string
+	for targetCM, entry := range uc.monitored.innerMap {
+		if _, ok := seen[targetCM]; ok {
+			removeAll = false
+		} else {
+			removeList = append(removeList, entry.originalKey)
+		}
+	}
+
+	// TODO: better handle the case where len(uc.monitored) + len(addList)
+	// exceeds the limit, probably by immediately sending ERR_MONLISTFULL?
+
+	if removeAll && len(addList) == 0 && len(removeList) > 0 {
+		// Optimization when the last MONITOR-aware downstream disconnects
+		uc.SendMessage(&irc.Message{
+			Command: "MONITOR",
+			Params:  []string{"C"},
+		})
+	} else {
+		msgs := generateMonitor("-", removeList)
+		msgs = append(msgs, generateMonitor("+", addList)...)
+		for _, msg := range msgs {
+			uc.SendMessage(msg)
+		}
+	}
+
+	for _, target := range removeList {
+		uc.monitored.Delete(target)
+	}
+}
Index: trunk/user.go
===================================================================
--- trunk/user.go	(revision 683)
+++ trunk/user.go	(revision 684)
@@ -343,11 +343,15 @@
 	net.channels.SetCasemapping(newCasemap)
 	net.delivered.m.SetCasemapping(newCasemap)
-	if net.conn != nil {
-		net.conn.channels.SetCasemapping(newCasemap)
-		for _, entry := range net.conn.channels.innerMap {
+	if uc := net.conn; uc != nil {
+		uc.channels.SetCasemapping(newCasemap)
+		for _, entry := range uc.channels.innerMap {
 			uch := entry.value.(*upstreamChannel)
 			uch.Members.SetCasemapping(newCasemap)
 		}
-	}
+		uc.monitored.SetCasemapping(newCasemap)
+	}
+	net.forEachDownstream(func(dc *downstreamConn) {
+		dc.monitored.SetCasemapping(newCasemap)
+	})
 }
 
@@ -520,4 +524,5 @@
 
 			uc.updateAway()
+			uc.updateMonitor()
 
 			netIDStr := fmt.Sprintf("%v", uc.network.ID)
@@ -589,4 +594,8 @@
 			dc := e.dc
 
+			if dc.network != nil {
+				dc.monitored.SetCasemapping(dc.network.casemap)
+			}
+
 			if err := dc.welcome(); err != nil {
 				dc.logger.Printf("failed to handle new registered connection: %v", err)
@@ -621,4 +630,5 @@
 			u.forEachUpstream(func(uc *upstreamConn) {
 				uc.updateAway()
+				uc.updateMonitor()
 			})
 		case eventDownstreamMessage:
