Index: trunk/downstream.go
===================================================================
--- trunk/downstream.go	(revision 107)
+++ trunk/downstream.go	(revision 108)
@@ -6,4 +6,5 @@
 	"io"
 	"net"
+	"strconv"
 	"strings"
 	"sync"
@@ -72,4 +73,8 @@
 	network     *network // can be nil
 
+	negociatingCaps bool
+	capVersion      int
+	caps            map[string]bool
+
 	lock        sync.Mutex
 	ourMessages map[*irc.Message]struct{}
@@ -85,4 +90,5 @@
 		ringMessages: make(chan ringMessage),
 		closed:       make(chan struct{}),
+		caps:         make(map[string]bool),
 		ourMessages:  make(map[*irc.Message]struct{}),
 	}
@@ -329,10 +335,113 @@
 			return err
 		}
+	case "CAP":
+		var subCmd string
+		if err := parseMessageParams(msg, &subCmd); err != nil {
+			return err
+		}
+		subCmd = strings.ToUpper(subCmd)
+		if err := dc.handleCapCommand(subCmd, msg.Params[1:]); err != nil {
+			return err
+		}
 	default:
 		dc.logger.Printf("unhandled message: %v", msg)
 		return newUnknownCommandError(msg.Command)
 	}
-	if dc.rawUsername != "" && dc.nick != "" {
+	if dc.rawUsername != "" && dc.nick != "" && !dc.negociatingCaps {
 		return dc.register()
+	}
+	return nil
+}
+
+func (dc *downstreamConn) handleCapCommand(cmd string, args []string) error {
+	replyTo := dc.nick
+	if !dc.registered {
+		replyTo = "*"
+	}
+
+	switch cmd {
+	case "LS":
+		if len(args) > 0 {
+			var err error
+			if dc.capVersion, err = strconv.Atoi(args[0]); err != nil {
+				return err
+			}
+		}
+
+		var caps []string
+		/*if dc.capVersion >= 302 {
+			caps = append(caps, "sasl=PLAIN")
+		} else {
+			caps = append(caps, "sasl")
+		}*/
+
+		// TODO: multi-line replies
+		dc.SendMessage(&irc.Message{
+			Prefix:  dc.srv.prefix(),
+			Command: "CAP",
+			Params:  []string{replyTo, "LS", strings.Join(caps, " ")},
+		})
+
+		if !dc.registered {
+			dc.negociatingCaps = true
+		}
+	case "LIST":
+		var caps []string
+		for name := range dc.caps {
+			caps = append(caps, name)
+		}
+
+		// TODO: multi-line replies
+		dc.SendMessage(&irc.Message{
+			Prefix:  dc.srv.prefix(),
+			Command: "CAP",
+			Params:  []string{replyTo, "LIST", strings.Join(caps, " ")},
+		})
+	case "REQ":
+		if len(args) == 0 {
+			return ircError{&irc.Message{
+				Command: err_invalidcapcmd,
+				Params:  []string{replyTo, cmd, "Missing argument in CAP REQ command"},
+			}}
+		}
+
+		caps := strings.Fields(args[0])
+		ack := true
+		for _, name := range caps {
+			name = strings.ToLower(name)
+			enable := !strings.HasPrefix(name, "-")
+			if !enable {
+				name = strings.TrimPrefix(name, "-")
+			}
+
+			enabled := dc.caps[name]
+			if enable == enabled {
+				continue
+			}
+
+			switch name {
+			/*case "sasl":
+				dc.caps[name] = enable*/
+			default:
+				ack = false
+			}
+		}
+
+		reply := "NAK"
+		if ack {
+			reply = "ACK"
+		}
+		dc.SendMessage(&irc.Message{
+			Prefix:  dc.srv.prefix(),
+			Command: "CAP",
+			Params:  []string{replyTo, reply, args[0]},
+		})
+	case "END":
+		dc.negociatingCaps = false
+	default:
+		return ircError{&irc.Message{
+			Command: err_invalidcapcmd,
+			Params:  []string{replyTo, cmd, "Unknown CAP command"},
+		}}
 	}
 	return nil
Index: trunk/irc.go
===================================================================
--- trunk/irc.go	(revision 107)
+++ trunk/irc.go	(revision 108)
@@ -9,17 +9,18 @@
 
 const (
-	rpl_statsping    = "246"
-	rpl_localusers   = "265"
-	rpl_globalusers  = "266"
-	rpl_topicwhotime = "333"
-	rpl_loggedin     = "900"
-	rpl_loggedout    = "901"
-	err_nicklocked   = "902"
-	rpl_saslsuccess  = "903"
-	err_saslfail     = "904"
-	err_sasltoolong  = "905"
-	err_saslaborted  = "906"
-	err_saslalready  = "907"
-	rpl_saslmechs    = "908"
+	rpl_statsping     = "246"
+	rpl_localusers    = "265"
+	rpl_globalusers   = "266"
+	rpl_topicwhotime  = "333"
+	err_invalidcapcmd = "410"
+	rpl_loggedin      = "900"
+	rpl_loggedout     = "901"
+	err_nicklocked    = "902"
+	rpl_saslsuccess   = "903"
+	err_saslfail      = "904"
+	err_sasltoolong   = "905"
+	err_saslaborted   = "906"
+	err_saslalready   = "907"
+	rpl_saslmechs     = "908"
 )
 
