Index: trunk/bridge.go
===================================================================
--- trunk/bridge.go	(revision 477)
+++ trunk/bridge.go	(revision 478)
@@ -55,5 +55,7 @@
 
 	var buf strings.Builder
-	for nick, memberships := range ch.Members {
+	for _, entry := range ch.Members.innerMap {
+		nick := entry.originalKey
+		memberships := entry.value.(*memberships)
 		s := memberships.Format(dc) + dc.marshalEntity(ch.conn.network, nick)
 
Index: trunk/downstream.go
===================================================================
--- trunk/downstream.go	(revision 477)
+++ trunk/downstream.go	(revision 478)
@@ -117,4 +117,5 @@
 	user        *user
 	nick        string
+	nickCM      string
 	rawUsername string
 	networkName string
@@ -193,5 +194,5 @@
 	// TODO: this doesn't account for nick changes
 	if net.conn != nil {
-		return nick == net.conn.nick
+		return net.casemap(nick) == net.conn.nickCM
 	}
 	// We're not currently connected to the upstream connection, so we don't
@@ -199,5 +200,5 @@
 	// configured nickname and hope it was the one being used when we were
 	// connected.
-	return nick == net.Nick
+	return net.casemap(nick) == net.casemap(net.Nick)
 }
 
@@ -211,4 +212,5 @@
 		return dc.nick
 	}
+	name = partialCasemap(net.casemap, name)
 	if dc.network != nil {
 		if dc.network != net {
@@ -224,4 +226,5 @@
 		return dc.prefix()
 	}
+	prefix.Name = partialCasemap(net.casemap, prefix.Name)
 	if dc.network != nil {
 		if dc.network != net {
@@ -359,6 +362,6 @@
 	}
 
-	delivered, ok := network.delivered[entity]
-	if !ok {
+	delivered := network.delivered.Value(entity)
+	if delivered == nil {
 		return
 	}
@@ -446,5 +449,6 @@
 			}}
 		}
-		if nick == serviceNick {
+		nickCM := casemapASCII(nick)
+		if nickCM == serviceNickCM {
 			return ircError{&irc.Message{
 				Command: irc.ERR_NICKNAMEINUSE,
@@ -453,4 +457,5 @@
 		}
 		dc.nick = nick
+		dc.nickCM = nickCM
 	case "USER":
 		if err := parseMessageParams(msg, &dc.rawUsername, nil, nil, &dc.realname); err != nil {
@@ -767,4 +772,5 @@
 		})
 		dc.nick = uc.nick
+		dc.nickCM = casemapASCII(dc.nick)
 	}
 }
@@ -912,4 +918,5 @@
 	isupport := []string{
 		fmt.Sprintf("CHATHISTORY=%v", dc.srv.HistoryLimit),
+		"CASEMAPPING=ascii",
 	}
 
@@ -961,9 +968,11 @@
 
 	dc.forEachUpstream(func(uc *upstreamConn) {
-		for _, ch := range uc.channels {
+		for _, entry := range uc.channels.innerMap {
+			ch := entry.value.(*upstreamChannel)
 			if !ch.complete {
 				continue
 			}
-			if record, ok := uc.network.channels[ch.Name]; ok && record.Detached {
+			record := uc.network.channels.Value(ch.Name)
+			if record != nil && record.Detached {
 				continue
 			}
@@ -988,6 +997,8 @@
 
 		// Fast-forward history to last message
-		for target, delivered := range net.delivered {
-			if ch, ok := net.channels[target]; ok && ch.Detached {
+		for target, entry := range net.delivered.innerMap {
+			delivered := entry.value.(map[string]string)
+			ch := net.channels.Value(target)
+			if ch != nil && ch.Detached {
 				continue
 			}
@@ -1020,5 +1031,6 @@
 
 func (dc *downstreamConn) sendNetworkBacklog(net *network) {
-	for target := range net.delivered {
+	for _, entry := range net.delivered.innerMap {
+		target := entry.originalKey
 		dc.sendTargetBacklog(net, target)
 	}
@@ -1029,9 +1041,9 @@
 		return
 	}
-	if ch, ok := net.channels[target]; ok && ch.Detached {
+	if ch := net.channels.Value(target); ch != nil && ch.Detached {
 		return
 	}
-	delivered, ok := net.delivered[target]
-	if !ok {
+	delivered := net.delivered.Value(target)
+	if delivered == nil {
 		return
 	}
@@ -1159,5 +1171,5 @@
 			}}
 		}
-		if nick == serviceNick {
+		if casemapASCII(nick) == serviceNickCM {
 			return ircError{&irc.Message{
 				Command: irc.ERR_NICKNAMEINUSE,
@@ -1195,4 +1207,5 @@
 			})
 			dc.nick = nick
+			dc.nickCM = casemapASCII(dc.nick)
 		}
 	case "JOIN":
@@ -1227,7 +1240,6 @@
 			})
 
-			var ch *Channel
-			var ok bool
-			if ch, ok = uc.network.channels[upstreamName]; ok {
+			ch := uc.network.channels.Value(upstreamName)
+			if ch != nil {
 				// Don't clear the channel key if there's one set
 				// TODO: add a way to unset the channel key
@@ -1241,5 +1253,5 @@
 					Key:  key,
 				}
-				uc.network.channels[upstreamName] = ch
+				uc.network.channels.SetValue(upstreamName, ch)
 			}
 			if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
@@ -1265,7 +1277,6 @@
 
 			if strings.EqualFold(reason, "detach") {
-				var ch *Channel
-				var ok bool
-				if ch, ok = uc.network.channels[upstreamName]; ok {
+				ch := uc.network.channels.Value(upstreamName)
+				if ch != nil {
 					uc.network.detach(ch)
 				} else {
@@ -1274,5 +1285,5 @@
 						Detached: true,
 					}
-					uc.network.channels[upstreamName] = ch
+					uc.network.channels.SetValue(upstreamName, ch)
 				}
 				if err := dc.srv.db.StoreChannel(uc.network.ID, ch); err != nil {
@@ -1361,5 +1372,5 @@
 		}
 
-		if name == dc.nick {
+		if casemapASCII(name) == dc.nickCM {
 			if modeStr != "" {
 				dc.forEachUpstream(func(uc *upstreamConn) {
@@ -1399,6 +1410,6 @@
 			})
 		} else {
-			ch, ok := uc.channels[upstreamName]
-			if !ok {
+			ch := uc.channels.Value(upstreamName)
+			if ch == nil {
 				return ircError{&irc.Message{
 					Command: irc.ERR_NOSUCHCHANNEL,
@@ -1436,5 +1447,5 @@
 		}
 
-		uc, upstreamChannel, err := dc.unmarshalEntity(channel)
+		uc, upstreamName, err := dc.unmarshalEntity(channel)
 		if err != nil {
 			return err
@@ -1445,12 +1456,12 @@
 			uc.SendMessageLabeled(dc.id, &irc.Message{
 				Command: "TOPIC",
-				Params:  []string{upstreamChannel, topic},
+				Params:  []string{upstreamName, topic},
 			})
 		} else { // getting topic
-			ch, ok := uc.channels[upstreamChannel]
-			if !ok {
+			ch := uc.channels.Value(upstreamName)
+			if ch == nil {
 				return ircError{&irc.Message{
 					Command: irc.ERR_NOSUCHCHANNEL,
-					Params:  []string{dc.nick, upstreamChannel, "No such channel"},
+					Params:  []string{dc.nick, upstreamName, "No such channel"},
 				}}
 			}
@@ -1514,11 +1525,11 @@
 		channels := strings.Split(msg.Params[0], ",")
 		for _, channel := range channels {
-			uc, upstreamChannel, err := dc.unmarshalEntity(channel)
+			uc, upstreamName, err := dc.unmarshalEntity(channel)
 			if err != nil {
 				return err
 			}
 
-			ch, ok := uc.channels[upstreamChannel]
-			if ok {
+			ch := uc.channels.Value(upstreamName)
+			if ch != nil {
 				sendNames(dc, ch)
 			} else {
@@ -1526,5 +1537,5 @@
 				uc.SendMessageLabeled(dc.id, &irc.Message{
 					Command: "NAMES",
-					Params:  []string{upstreamChannel},
+					Params:  []string{upstreamName},
 				})
 			}
@@ -1543,6 +1554,7 @@
 		// TODO: support WHO masks
 		entity := msg.Params[0]
-
-		if entity == dc.nick {
+		entityCM := casemapASCII(entity)
+
+		if entityCM == dc.nickCM {
 			// TODO: support AWAY (H/G) in self WHO reply
 			dc.SendMessage(&irc.Message{
@@ -1558,5 +1570,5 @@
 			return nil
 		}
-		if entity == serviceNick {
+		if entityCM == serviceNickCM {
 			dc.SendMessage(&irc.Message{
 				Prefix:  dc.srv.prefix(),
@@ -1609,5 +1621,5 @@
 		}
 
-		if mask == dc.nick {
+		if casemapASCII(mask) == dc.nickCM {
 			dc.SendMessage(&irc.Message{
 				Prefix:  dc.srv.prefix(),
Index: trunk/irc.go
===================================================================
--- trunk/irc.go	(revision 477)
+++ trunk/irc.go	(revision 478)
@@ -122,10 +122,11 @@
 				}
 				member := arguments[nextArgument]
-				if _, ok := ch.Members[member]; ok {
+				m := ch.Members.Value(member)
+				if m != nil {
 					if plusMinus == '+' {
-						ch.Members[member].Add(ch.conn.availableMemberships, membership)
+						m.Add(ch.conn.availableMemberships, membership)
 					} else {
 						// TODO: for upstreams without multi-prefix, query the user modes again
-						ch.Members[member].Remove(membership)
+						m.Remove(membership)
 					}
 				}
@@ -419,2 +420,192 @@
 	return cmd, params, true
 }
+
+type casemapping func(string) string
+
+func casemapNone(name string) string {
+	return name
+}
+
+// CasemapASCII of name is the canonical representation of name according to the
+// ascii casemapping.
+func casemapASCII(name string) string {
+	var sb strings.Builder
+	sb.Grow(len(name))
+	for _, r := range name {
+		if 'A' <= r && r <= 'Z' {
+			r += 'a' - 'A'
+		}
+		sb.WriteRune(r)
+	}
+	return sb.String()
+}
+
+// casemapRFC1459 of name is the canonical representation of name according to the
+// rfc1459 casemapping.
+func casemapRFC1459(name string) string {
+	var sb strings.Builder
+	sb.Grow(len(name))
+	for _, r := range name {
+		if 'A' <= r && r <= 'Z' {
+			r += 'a' - 'A'
+		} else if r == '{' {
+			r = '['
+		} else if r == '}' {
+			r = ']'
+		} else if r == '\\' {
+			r = '|'
+		} else if r == '~' {
+			r = '^'
+		}
+		sb.WriteRune(r)
+	}
+	return sb.String()
+}
+
+// casemapRFC1459Strict of name is the canonical representation of name
+// according to the rfc1459-strict casemapping.
+func casemapRFC1459Strict(name string) string {
+	var sb strings.Builder
+	sb.Grow(len(name))
+	for _, r := range name {
+		if 'A' <= r && r <= 'Z' {
+			r += 'a' - 'A'
+		} else if r == '{' {
+			r = '['
+		} else if r == '}' {
+			r = ']'
+		} else if r == '\\' {
+			r = '|'
+		}
+		sb.WriteRune(r)
+	}
+	return sb.String()
+}
+
+func parseCasemappingToken(tokenValue string) (casemap casemapping, ok bool) {
+	switch tokenValue {
+	case "ascii":
+		casemap = casemapASCII
+	case "rfc1459":
+		casemap = casemapRFC1459
+	case "rfc1459-strict":
+		casemap = casemapRFC1459Strict
+	default:
+		return nil, false
+	}
+	return casemap, true
+}
+
+func partialCasemap(higher casemapping, name string) string {
+	nameFullyCM := higher(name)
+	var sb strings.Builder
+	sb.Grow(len(name))
+	for i, r := range nameFullyCM {
+		if 'a' <= r && r <= 'z' {
+			r = rune(name[i])
+		}
+		sb.WriteRune(r)
+	}
+	return sb.String()
+}
+
+type casemapMap struct {
+	innerMap map[string]casemapEntry
+	casemap  casemapping
+}
+
+type casemapEntry struct {
+	originalKey string
+	value       interface{}
+}
+
+func newCasemapMap(size int) casemapMap {
+	return casemapMap{
+		innerMap: make(map[string]casemapEntry, size),
+		casemap:  casemapNone,
+	}
+}
+
+func (cm *casemapMap) OriginalKey(name string) (key string, ok bool) {
+	entry, ok := cm.innerMap[cm.casemap(name)]
+	if !ok {
+		return "", false
+	}
+	return entry.originalKey, true
+}
+
+func (cm *casemapMap) Has(name string) bool {
+	_, ok := cm.innerMap[cm.casemap(name)]
+	return ok
+}
+
+func (cm *casemapMap) Len() int {
+	return len(cm.innerMap)
+}
+
+func (cm *casemapMap) SetValue(name string, value interface{}) {
+	nameCM := cm.casemap(name)
+	entry, ok := cm.innerMap[nameCM]
+	if !ok {
+		cm.innerMap[nameCM] = casemapEntry{
+			originalKey: name,
+			value:       value,
+		}
+		return
+	}
+	entry.value = value
+	cm.innerMap[nameCM] = entry
+}
+
+func (cm *casemapMap) Delete(name string) {
+	delete(cm.innerMap, cm.casemap(name))
+}
+
+func (cm *casemapMap) SetCasemapping(newCasemap casemapping) {
+	cm.casemap = newCasemap
+	newInnerMap := make(map[string]casemapEntry, len(cm.innerMap))
+	for _, entry := range cm.innerMap {
+		newInnerMap[cm.casemap(entry.originalKey)] = entry
+	}
+	cm.innerMap = newInnerMap
+}
+
+type upstreamChannelCasemapMap struct{ casemapMap }
+
+func (cm *upstreamChannelCasemapMap) Value(name string) *upstreamChannel {
+	entry, ok := cm.innerMap[cm.casemap(name)]
+	if !ok {
+		return nil
+	}
+	return entry.value.(*upstreamChannel)
+}
+
+type channelCasemapMap struct{ casemapMap }
+
+func (cm *channelCasemapMap) Value(name string) *Channel {
+	entry, ok := cm.innerMap[cm.casemap(name)]
+	if !ok {
+		return nil
+	}
+	return entry.value.(*Channel)
+}
+
+type membershipsCasemapMap struct{ casemapMap }
+
+func (cm *membershipsCasemapMap) Value(name string) *memberships {
+	entry, ok := cm.innerMap[cm.casemap(name)]
+	if !ok {
+		return nil
+	}
+	return entry.value.(*memberships)
+}
+
+type mapStringStringCasemapMap struct{ casemapMap }
+
+func (cm *mapStringStringCasemapMap) Value(name string) map[string]string {
+	entry, ok := cm.innerMap[cm.casemap(name)]
+	if !ok {
+		return nil
+	}
+	return entry.value.(map[string]string)
+}
Index: trunk/service.go
===================================================================
--- trunk/service.go	(revision 477)
+++ trunk/service.go	(revision 478)
@@ -28,4 +28,5 @@
 
 const serviceNick = "BouncerServ"
+const serviceNickCM = "bouncerserv"
 const serviceRealname = "soju bouncer service"
 
@@ -409,5 +410,5 @@
 				statuses = append(statuses, "connected")
 			}
-			details = fmt.Sprintf("%v channels", len(uc.channels))
+			details = fmt.Sprintf("%v channels", uc.channels.Len())
 		} else {
 			statuses = append(statuses, "disconnected")
@@ -769,6 +770,6 @@
 	}
 
-	ch, ok := uc.network.channels[upstreamName]
-	if !ok {
+	ch := uc.network.channels.Value(upstreamName)
+	if ch == nil {
 		return fmt.Errorf("unknown channel %q", name)
 	}
Index: trunk/upstream.go
===================================================================
--- trunk/upstream.go	(revision 477)
+++ trunk/upstream.go	(revision 478)
@@ -49,5 +49,5 @@
 	modes        channelModes
 	creationTime string
-	Members      map[string]*memberships
+	Members      membershipsCasemapMap
 	complete     bool
 	detachTimer  *time.Timer
@@ -87,8 +87,9 @@
 	registered    bool
 	nick          string
+	nickCM        string
 	username      string
 	realname      string
 	modes         userModes
-	channels      map[string]*upstreamChannel
+	channels      upstreamChannelCasemapMap
 	supportedCaps map[string]string
 	caps          map[string]bool
@@ -99,4 +100,6 @@
 	saslClient  sasl.Client
 	saslStarted bool
+
+	casemapIsSet bool
 
 	// set of LIST commands in progress, per downstream
@@ -187,5 +190,5 @@
 		network:                  network,
 		user:                     network.user,
-		channels:                 make(map[string]*upstreamChannel),
+		channels:                 upstreamChannelCasemapMap{newCasemapMap(0)},
 		supportedCaps:            make(map[string]string),
 		caps:                     make(map[string]bool),
@@ -214,6 +217,6 @@
 
 func (uc *upstreamConn) getChannel(name string) (*upstreamChannel, error) {
-	ch, ok := uc.channels[name]
-	if !ok {
+	ch := uc.channels.Value(name)
+	if ch == nil {
 		return nil, fmt.Errorf("unknown channel %q", name)
 	}
@@ -223,4 +226,8 @@
 func (uc *upstreamConn) isChannel(entity string) bool {
 	return strings.ContainsRune(uc.availableChannelTypes, rune(entity[0]))
+}
+
+func (uc *upstreamConn) isOurNick(nick string) bool {
+	return uc.nickCM == uc.network.casemap(nick)
 }
 
@@ -414,9 +421,10 @@
 		} else { // regular user message
 			target := entity
-			if target == uc.nick {
+			if uc.isOurNick(target) {
 				target = msg.Prefix.Name
 			}
 
-			if ch, ok := uc.network.channels[target]; ok {
+			ch := uc.network.channels.Value(target)
+			if ch != nil {
 				if ch.Detached {
 					uc.handleDetachedMessage(msg.Prefix.Name, text, ch)
@@ -591,7 +599,8 @@
 		uc.logger.Printf("connection registered")
 
-		if len(uc.network.channels) > 0 {
+		if uc.network.channels.Len() > 0 {
 			var channels, keys []string
-			for _, ch := range uc.network.channels {
+			for _, entry := range uc.network.channels.innerMap {
+				ch := entry.value.(*Channel)
 				channels = append(channels, ch.Name)
 				keys = append(keys, ch.Key)
@@ -635,4 +644,12 @@
 			var err error
 			switch parameter {
+			case "CASEMAPPING":
+				casemap, ok := parseCasemappingToken(value)
+				if !ok {
+					casemap = casemapRFC1459
+				}
+				uc.network.updateCasemapping(casemap)
+				uc.nickCM = uc.network.casemap(uc.nick)
+				uc.casemapIsSet = true
 			case "CHANMODES":
 				if !negate {
@@ -672,4 +689,12 @@
 			}
 		})
+	case irc.ERR_NOMOTD, irc.RPL_ENDOFMOTD:
+		if !uc.casemapIsSet {
+			// upstream did not send any CASEMAPPING token, thus
+			// we assume it implements the old RFCs with rfc1459.
+			uc.casemapIsSet = true
+			uc.network.updateCasemapping(casemapRFC1459)
+			uc.nickCM = uc.network.casemap(uc.nick)
+		}
 	case "BATCH":
 		var tag string
@@ -717,14 +742,17 @@
 
 		me := false
-		if msg.Prefix.Name == uc.nick {
+		if uc.isOurNick(msg.Prefix.Name) {
 			uc.logger.Printf("changed nick from %q to %q", uc.nick, newNick)
 			me = true
 			uc.nick = newNick
-		}
-
-		for _, ch := range uc.channels {
-			if memberships, ok := ch.Members[msg.Prefix.Name]; ok {
-				delete(ch.Members, msg.Prefix.Name)
-				ch.Members[newNick] = memberships
+			uc.nickCM = uc.network.casemap(uc.nick)
+		}
+
+		for _, entry := range uc.channels.innerMap {
+			ch := entry.value.(*upstreamChannel)
+			memberships := ch.Members.Value(msg.Prefix.Name)
+			if memberships != nil {
+				ch.Members.Delete(msg.Prefix.Name)
+				ch.Members.SetValue(newNick, memberships)
 				uc.appendLog(ch.Name, msg)
 			}
@@ -751,11 +779,13 @@
 
 		for _, ch := range strings.Split(channels, ",") {
-			if msg.Prefix.Name == uc.nick {
+			if uc.isOurNick(msg.Prefix.Name) {
 				uc.logger.Printf("joined channel %q", ch)
-				uc.channels[ch] = &upstreamChannel{
+				members := membershipsCasemapMap{newCasemapMap(0)}
+				members.casemap = uc.network.casemap
+				uc.channels.SetValue(ch, &upstreamChannel{
 					Name:    ch,
 					conn:    uc,
-					Members: make(map[string]*memberships),
-				}
+					Members: members,
+				})
 				uc.updateChannelAutoDetach(ch)
 
@@ -769,5 +799,5 @@
 					return err
 				}
-				ch.Members[msg.Prefix.Name] = &memberships{}
+				ch.Members.SetValue(msg.Prefix.Name, &memberships{})
 			}
 
@@ -787,8 +817,9 @@
 
 		for _, ch := range strings.Split(channels, ",") {
-			if msg.Prefix.Name == uc.nick {
+			if uc.isOurNick(msg.Prefix.Name) {
 				uc.logger.Printf("parted channel %q", ch)
-				if uch, ok := uc.channels[ch]; ok {
-					delete(uc.channels, ch)
+				uch := uc.channels.Value(ch)
+				if uch != nil {
+					uc.channels.Delete(ch)
 					uch.updateAutoDetach(0)
 				}
@@ -798,5 +829,5 @@
 					return err
 				}
-				delete(ch.Members, msg.Prefix.Name)
+				ch.Members.Delete(msg.Prefix.Name)
 			}
 
@@ -815,7 +846,7 @@
 		}
 
-		if user == uc.nick {
+		if uc.isOurNick(user) {
 			uc.logger.Printf("kicked from channel %q by %s", channel, msg.Prefix.Name)
-			delete(uc.channels, channel)
+			uc.channels.Delete(channel)
 		} else {
 			ch, err := uc.getChannel(channel)
@@ -823,5 +854,5 @@
 				return err
 			}
-			delete(ch.Members, user)
+			ch.Members.Delete(user)
 		}
 
@@ -832,11 +863,12 @@
 		}
 
-		if msg.Prefix.Name == uc.nick {
+		if uc.isOurNick(msg.Prefix.Name) {
 			uc.logger.Printf("quit")
 		}
 
-		for _, ch := range uc.channels {
-			if _, ok := ch.Members[msg.Prefix.Name]; ok {
-				delete(ch.Members, msg.Prefix.Name)
+		for _, entry := range uc.channels.innerMap {
+			ch := entry.value.(*upstreamChannel)
+			if ch.Members.Has(msg.Prefix.Name) {
+				ch.Members.Delete(msg.Prefix.Name)
 
 				uc.appendLog(ch.Name, msg)
@@ -909,5 +941,6 @@
 			uc.appendLog(ch.Name, msg)
 
-			if ch, ok := uc.network.channels[name]; !ok || !ch.Detached {
+			c := uc.network.channels.Value(name)
+			if c == nil || !c.Detached {
 				uc.forEachDownstream(func(dc *downstreamConn) {
 					params := make([]string, len(msg.Params))
@@ -965,5 +998,6 @@
 		}
 		if firstMode {
-			if c, ok := uc.network.channels[channel]; !ok || !c.Detached {
+			c := uc.network.channels.Value(channel)
+			if c == nil || !c.Detached {
 				modeStr, modeParams := ch.modes.Format()
 
@@ -1062,6 +1096,6 @@
 		}
 
-		ch, ok := uc.channels[name]
-		if !ok {
+		ch := uc.channels.Value(name)
+		if ch == nil {
 			// NAMES on a channel we have not joined, forward to downstream
 			uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
@@ -1091,5 +1125,5 @@
 		for _, s := range splitSpace(members) {
 			memberships, nick := uc.parseMembershipPrefix(s)
-			ch.Members[nick] = memberships
+			ch.Members.SetValue(nick, memberships)
 		}
 	case irc.RPL_ENDOFNAMES:
@@ -1099,6 +1133,6 @@
 		}
 
-		ch, ok := uc.channels[name]
-		if !ok {
+		ch := uc.channels.Value(name)
+		if ch == nil {
 			// NAMES on a channel we have not joined, forward to downstream
 			uc.forEachDownstreamByID(downstreamID, func(dc *downstreamConn) {
@@ -1119,5 +1153,6 @@
 		ch.complete = true
 
-		if c, ok := uc.network.channels[name]; !ok || !c.Detached {
+		c := uc.network.channels.Value(name)
+		if c == nil || !c.Detached {
 			uc.forEachDownstream(func(dc *downstreamConn) {
 				forwardChannel(dc, ch)
@@ -1273,5 +1308,5 @@
 		}
 
-		weAreInvited := nick == uc.nick
+		weAreInvited := uc.isOurNick(nick)
 
 		uc.forEachDownstream(func(dc *downstreamConn) {
@@ -1396,5 +1431,5 @@
 	case irc.RPL_LUSERCLIENT, irc.RPL_LUSEROP, irc.RPL_LUSERUNKNOWN, irc.RPL_LUSERCHANNELS, irc.RPL_LUSERME:
 		// Ignore
-	case irc.RPL_MOTDSTART, irc.RPL_MOTD, irc.RPL_ENDOFMOTD:
+	case irc.RPL_MOTDSTART, irc.RPL_MOTD:
 		// Ignore
 	case irc.RPL_LISTSTART:
@@ -1602,4 +1637,5 @@
 func (uc *upstreamConn) register() {
 	uc.nick = uc.network.Nick
+	uc.nickCM = uc.network.casemap(uc.nick)
 	uc.username = uc.network.GetUsername()
 	uc.realname = uc.network.GetRealname()
@@ -1706,11 +1742,12 @@
 
 	detached := false
-	if ch, ok := uc.network.channels[entity]; ok {
+	if ch := uc.network.channels.Value(entity); ch != nil {
 		detached = ch.Detached
 	}
 
-	delivered, ok := uc.network.delivered[entity]
-	if !ok {
-		lastID, err := uc.user.msgStore.LastMsgID(uc.network, entity, time.Now())
+	delivered := uc.network.delivered.Value(entity)
+	entityCM := uc.network.casemap(entity)
+	if delivered == nil {
+		lastID, err := uc.user.msgStore.LastMsgID(uc.network, entityCM, time.Now())
 		if err != nil {
 			uc.logger.Printf("failed to log message: failed to get last message ID: %v", err)
@@ -1719,5 +1756,5 @@
 
 		delivered = make(map[string]string)
-		uc.network.delivered[entity] = delivered
+		uc.network.delivered.SetValue(entity, delivered)
 
 		for clientName, _ := range uc.network.offlineClients {
@@ -1734,5 +1771,5 @@
 	}
 
-	msgID, err := uc.user.msgStore.Append(uc.network, entity, msg)
+	msgID, err := uc.user.msgStore.Append(uc.network, entityCM, msg)
 	if err != nil {
 		uc.logger.Printf("failed to log message: %v", err)
@@ -1755,5 +1792,6 @@
 
 	// Don't forward messages if it's a detached channel
-	if ch, ok := uc.network.channels[target]; ok && ch.Detached {
+	ch := uc.network.channels.Value(target)
+	if ch != nil && ch.Detached {
 		return
 	}
@@ -1790,8 +1828,12 @@
 
 func (uc *upstreamConn) updateChannelAutoDetach(name string) {
-	if uch, ok := uc.channels[name]; ok {
-		if ch, ok := uc.network.channels[name]; ok && !ch.Detached {
-			uch.updateAutoDetach(ch.DetachAfter)
-		}
-	}
-}
+	uch := uc.channels.Value(name)
+	if uch == nil {
+		return
+	}
+	ch := uc.network.channels.Value(name)
+	if ch == nil || ch.Detached {
+		return
+	}
+	uch.updateAutoDetach(ch.DetachAfter)
+}
Index: trunk/user.go
===================================================================
--- trunk/user.go	(revision 477)
+++ trunk/user.go	(revision 478)
@@ -62,15 +62,16 @@
 
 	conn           *upstreamConn
-	channels       map[string]*Channel
-	delivered      map[string]map[string]string // entity -> client name -> msg ID
-	offlineClients map[string]struct{}          // indexed by client name
+	channels       channelCasemapMap
+	delivered      mapStringStringCasemapMap // entity -> client name -> msg ID
+	offlineClients map[string]struct{}       // indexed by client name
 	lastError      error
+	casemap        casemapping
 }
 
 func newNetwork(user *user, record *Network, channels []Channel) *network {
-	m := make(map[string]*Channel, len(channels))
+	m := channelCasemapMap{newCasemapMap(0)}
 	for _, ch := range channels {
 		ch := ch
-		m[ch.Name] = &ch
+		m.SetValue(ch.Name, &ch)
 	}
 
@@ -80,6 +81,7 @@
 		stopped:        make(chan struct{}),
 		channels:       m,
-		delivered:      make(map[string]map[string]string),
+		delivered:      mapStringStringCasemapMap{newCasemapMap(0)},
 		offlineClients: make(map[string]struct{}),
+		casemap:        casemapRFC1459,
 	}
 }
@@ -186,5 +188,6 @@
 
 	if net.conn != nil {
-		if uch, ok := net.conn.channels[ch.Name]; ok {
+		uch := net.conn.channels.Value(ch.Name)
+		if uch != nil {
 			uch.updateAutoDetach(0)
 		}
@@ -211,5 +214,5 @@
 	var uch *upstreamChannel
 	if net.conn != nil {
-		uch = net.conn.channels[ch.Name]
+		uch = net.conn.channels.Value(ch.Name)
 
 		net.conn.updateChannelAutoDetach(ch.Name)
@@ -232,10 +235,11 @@
 
 func (net *network) deleteChannel(name string) error {
-	ch, ok := net.channels[name]
-	if !ok {
+	ch := net.channels.Value(name)
+	if ch == nil {
 		return fmt.Errorf("unknown channel %q", name)
 	}
 	if net.conn != nil {
-		if uch, ok := net.conn.channels[ch.Name]; ok {
+		uch := net.conn.channels.Value(ch.Name)
+		if uch != nil {
 			uch.updateAutoDetach(0)
 		}
@@ -245,6 +249,19 @@
 		return err
 	}
-	delete(net.channels, name)
+	net.channels.Delete(name)
 	return nil
+}
+
+func (net *network) updateCasemapping(newCasemap casemapping) {
+	net.casemap = newCasemap
+	net.channels.SetCasemapping(newCasemap)
+	net.delivered.SetCasemapping(newCasemap)
+	if net.conn != nil {
+		net.conn.channels.SetCasemapping(newCasemap)
+		for _, entry := range net.conn.channels.innerMap {
+			uch := entry.value.(*upstreamChannel)
+			uch.Members.SetCasemapping(newCasemap)
+		}
+	}
 }
 
@@ -411,6 +428,6 @@
 		case eventChannelDetach:
 			uc, name := e.uc, e.name
-			c, ok := uc.network.channels[name]
-			if !ok || c.Detached {
+			c := uc.network.channels.Value(name)
+			if c == nil || c.Detached {
 				continue
 			}
@@ -500,5 +517,6 @@
 	uc.endPendingLISTs(true)
 
-	for _, uch := range uc.channels {
+	for _, entry := range uc.channels.innerMap {
+		uch := entry.value.(*upstreamChannel)
 		uch.updateAutoDetach(0)
 	}
@@ -571,6 +589,7 @@
 	// Most network changes require us to re-connect to the upstream server
 
-	channels := make([]Channel, 0, len(network.channels))
-	for _, ch := range network.channels {
+	channels := make([]Channel, 0, network.channels.Len())
+	for _, entry := range network.channels.innerMap {
+		ch := entry.value.(*Channel)
 		channels = append(channels, *ch)
 	}
