summaryrefslogtreecommitdiff
path: root/xS
diff options
context:
space:
mode:
Diffstat (limited to 'xS')
-rw-r--r--xS/main.go336
1 files changed, 329 insertions, 7 deletions
diff --git a/xS/main.go b/xS/main.go
index 75f4231..4c9176b 100644
--- a/xS/main.go
+++ b/xS/main.go
@@ -556,14 +556,40 @@ func initiateQuit() {
quitting = true
}
-// TODO: ircChannelCreate
+func ircChannelCreate(name string) *channel {
+ ch := &channel{
+ name: name,
+ created: time.Now().UnixNano(),
+ userLimit: -1,
+ }
+ channels[ircToCanon(name)] = ch
+ return ch
+}
func ircChannelDestroyIfEmpty(ch *channel) {
- // TODO
+ if len(ch.userModes) == 0 {
+ delete(channels, ircToCanon(ch.name))
+ }
}
+// TODO: Improve the name as it takes mode +q into account.
func ircSendToRoommates(c *client, message string) {
- // TODO
+ targets := make(map[*client]bool)
+ for _, ch := range channels {
+ _, present := ch.userModes[c]
+ if !present || 0 != ch.modes&ircChanModeQuiet {
+ continue
+ }
+ for client := range ch.userModes {
+ targets[client] = true
+ }
+ }
+
+ for roommate := range targets {
+ if roommate != c {
+ roommate.send(message)
+ }
+ }
}
// --- Clients (continued) -----------------------------------------------------
@@ -1662,13 +1688,248 @@ func ircHandleTOPIC(msg *message, c *client) {
ircChannelMulticast(ch, message, nil)
}
-// TODO: All the various real command handlers.
+func ircTryPart(c *client, target string, reason string) {
+ if reason == "" {
+ reason = c.nickname
+ }
-func ircHandleX(msg *message, c *client) {
+ ch := channels[ircToCanon(target)]
+ if ch == nil {
+ c.sendReply(ERR_NOSUCHCHANNEL, target)
+ return
+ }
+
+ if _, present := ch.userModes[c]; !present {
+ c.sendReply(ERR_NOTONCHANNEL, target)
+ return
+ }
+
+ message := fmt.Sprintf(":%s@%s@%s PART %s :%s",
+ c.nickname, c.username, c.hostname, target, reason)
+ if 0 == ch.modes&ircChanModeQuiet {
+ ircChannelMulticast(ch, message, nil)
+ } else {
+ c.send(message)
+ }
+
+ delete(ch.userModes, c)
+ ircChannelDestroyIfEmpty(ch)
+}
+
+func ircPartAllChannels(c *client) {
+ for _, ch := range channels {
+ if _, present := ch.userModes[c]; present {
+ ircTryPart(c, ch.name, "")
+ }
+ }
+}
+
+func ircHandlePART(msg *message, c *client) {
if len(msg.params) < 1 {
c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
return
}
+
+ reason := ""
+ if len(msg.params) > 1 {
+ reason = msg.params[1]
+ }
+
+ for _, target := range splitString(msg.params[0], ",", true) {
+ ircTryPart(c, target, reason)
+ }
+}
+
+// TODO: Undo the rename from channelName to target, also in ircTryPart.
+func ircTryKick(c *client, target, nick, reason string) {
+ ch := channels[ircToCanon(target)]
+ if ch == nil {
+ c.sendReply(ERR_NOSUCHCHANNEL, target)
+ return
+ }
+
+ if modes, present := ch.userModes[c]; !present {
+ c.sendReply(ERR_NOTONCHANNEL, target)
+ return
+ } else if 0 == modes&ircChanModeOperator {
+ c.sendReply(ERR_CHANOPRIVSNEEDED, target)
+ return
+ }
+
+ client := users[ircToCanon(nick)]
+ if _, present := ch.userModes[client]; client == nil || !present {
+ c.sendReply(ERR_USERNOTINCHANNEL, nick, target)
+ return
+ }
+
+ message := fmt.Sprintf(":%s@%s@%s KICK %s %s :%s",
+ c.nickname, c.username, c.hostname, target, nick, reason)
+ if 0 == ch.modes&ircChanModeQuiet {
+ ircChannelMulticast(ch, message, nil)
+ } else {
+ c.send(message)
+ }
+
+ delete(ch.userModes, client)
+ ircChannelDestroyIfEmpty(ch)
+}
+
+func ircHandleKICK(msg *message, c *client) {
+ if len(msg.params) < 2 {
+ c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
+ return
+ }
+
+ reason := c.nickname
+ if len(msg.params) > 2 {
+ reason = msg.params[2]
+ }
+
+ targetChannels := splitString(msg.params[0], ",", true)
+ targetUsers := splitString(msg.params[1], ",", true)
+
+ if len(channels) == 1 {
+ for i := 0; i < len(targetUsers); i++ {
+ ircTryKick(c, targetChannels[0], targetUsers[i], reason)
+ }
+ } else {
+ for i := 0; i < len(channels) && i < len(targetUsers); i++ {
+ ircTryKick(c, targetChannels[i], targetUsers[i], reason)
+ }
+ }
+}
+
+func ircSendInviteNotifications(ch *channel, c, target *client) {
+ for client := range ch.userModes {
+ if client != target && 0 != client.capsEnabled&ircCapInviteNotify {
+ client.sendf(":%s!%s@%s INVITE %s %s",
+ c.nickname, c.username, c.hostname, target.nickname, ch.name)
+ }
+ }
+}
+
+func ircHandleINVITE(msg *message, c *client) {
+ if len(msg.params) < 2 {
+ c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
+ return
+ }
+
+ target, channelName := msg.params[0], msg.params[1]
+ client := users[ircToCanon(target)]
+ if client == nil {
+ c.sendReply(ERR_NOSUCHNICK, target)
+ return
+ }
+
+ if ch := channels[ircToCanon(channelName)]; ch != nil {
+ invitingModes, invitingPresent := ch.userModes[c]
+ if !invitingPresent {
+ c.sendReply(ERR_NOTONCHANNEL, channelName)
+ return
+ }
+ if _, present := ch.userModes[client]; present {
+ c.sendReply(ERR_USERONCHANNEL, target, channelName)
+ return
+ }
+
+ if 0 != invitingModes&ircChanModeOperator {
+ client.invites[ircToCanon(channelName)] = true
+ } else if 0 != ch.modes&ircChanModeInviteOnly {
+ c.sendReply(ERR_CHANOPRIVSNEEDED, channelName)
+ return
+ }
+
+ // It's not specified when and how we should send out invite-notify.
+ if 0 != ch.modes&ircChanModeInviteOnly {
+ ircSendInviteNotifications(ch, c, client)
+ }
+ }
+
+ client.sendf(":%s!%s@%s INVITE %s %s",
+ c.nickname, c.username, c.hostname, client.nickname, channelName)
+ if client.awayMessage != "" {
+ c.sendReply(RPL_AWAY, client.nickname, client.awayMessage)
+ }
+ c.sendReply(RPL_INVITING, client.nickname, channelName)
+}
+
+func ircTryJoin(c *client, channelName, key string) {
+ ch := channels[ircToCanon(channelName)]
+ var userMode uint
+ if ch == nil {
+ if !ircIsValidChannelName(channelName) {
+ c.sendReply(ERR_BADCHANMASK, channelName)
+ return
+ }
+ ch = ircChannelCreate(channelName)
+ userMode = ircChanModeOperator
+ } else if _, present := ch.userModes[c]; present {
+ return
+ }
+
+ _, invitedByChanop := c.invites[ircToCanon(channelName)]
+ if 0 != ch.modes&ircChanModeInviteOnly && c.inMaskList(ch.inviteList) &&
+ !invitedByChanop {
+ c.sendReply(ERR_INVITEONLYCHAN, channelName)
+ return
+ }
+ if ch.key != "" && (key == "" || key != ch.key) {
+ c.sendReply(ERR_BADCHANNELKEY, channelName)
+ return
+ }
+ if ch.userLimit != -1 && len(ch.userModes) >= ch.userLimit {
+ c.sendReply(ERR_CHANNELISFULL, channelName)
+ return
+ }
+ if c.inMaskList(ch.banList) && !c.inMaskList(ch.exceptionList) &&
+ !invitedByChanop {
+ c.sendReply(ERR_BANNEDFROMCHAN, channelName)
+ return
+ }
+
+ // Destroy any invitation as there's no other way to get rid of it.
+ delete(c.invites, ircToCanon(channelName))
+
+ ch.userModes[c] = userMode
+
+ message := fmt.Sprintf(":%s!%s@%s JOIN %s",
+ c.nickname, c.username, c.hostname, channelName)
+ if 0 == ch.modes&ircChanModeQuiet {
+ ircChannelMulticast(ch, message, nil)
+ } else {
+ c.send(message)
+ }
+
+ ircSendRPLTOPIC(c, ch)
+ ircSendRPLNAMREPLY(c, ch, nil)
+ c.sendReply(RPL_ENDOFNAMES, ch.name)
+}
+
+func ircHandleJOIN(msg *message, c *client) {
+ if len(msg.params) < 1 {
+ c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
+ return
+ }
+
+ if msg.params[0] == "0" {
+ ircPartAllChannels(c)
+ return
+ }
+
+ targetChannels := splitString(msg.params[0], ",", true)
+
+ var keys []string
+ if len(msg.params) > 1 {
+ keys = splitString(msg.params[1], ",", true)
+ }
+
+ for i, name := range targetChannels {
+ key := ""
+ if i < len(keys) {
+ key = keys[i]
+ }
+ ircTryJoin(c, name, key)
+ }
}
func ircHandleSUMMON(msg *message, c *client) {
@@ -1679,11 +1940,63 @@ func ircHandleUSERS(msg *message, c *client) {
c.sendReply(ERR_USERSDISABLED)
}
+func ircHandleAWAY(msg *message, c *client) {
+ if len(msg.params) < 1 {
+ c.awayMessage = ""
+ c.sendReply(RPL_UNAWAY)
+ } else {
+ c.awayMessage = msg.params[0]
+ c.sendReply(RPL_NOWAWAY)
+ }
+}
+
+func ircHandleISON(msg *message, c *client) {
+ if len(msg.params) < 1 {
+ c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
+ return
+ }
+
+ var on []string
+ for _, nick := range msg.params {
+ if client := users[ircToCanon(nick)]; client != nil {
+ on = append(on, nick)
+ }
+ }
+ c.sendReply(RPL_ISON, strings.Join(on, " "))
+}
+
+func ircHandleADMIN(msg *message, c *client) {
+ if len(msg.params) > 0 && !isThisMe(msg.params[0]) {
+ c.sendReply(ERR_NOSUCHSERVER, msg.params[0])
+ return
+ }
+ c.sendReply(ERR_NOADMININFO, serverName)
+}
+
+// TODO: All the remaining command handlers.
+
+func ircHandleX(msg *message, c *client) {
+ if len(msg.params) < 1 {
+ c.sendReply(ERR_NEEDMOREPARAMS, msg.command)
+ return
+ }
+}
+
+func ircHandleDIE(msg *message, c *client) {
+ if 0 == c.mode&ircUserModeOperator {
+ c.sendReply(ERR_NOPRIVILEGES)
+ return
+ }
+ if !quitting {
+ initiateQuit()
+ }
+}
+
// -----------------------------------------------------------------------------
// TODO: Add an index for IRC_ERR_NOSUCHSERVER validation?
// TODO: Add a minimal parameter count?
-// TODO: Add a field for oper-only commands?
+// TODO: Add a field for oper-only commands? Use flags?
var ircHandlers = map[string]*ircCommand{
"CAP": {false, ircHandleCAP, 0, 0},
"PASS": {false, ircHandlePASS, 0, 0},
@@ -1700,16 +2013,25 @@ var ircHandlers = map[string]*ircCommand{
"VERSION": {true, ircHandleVERSION, 0, 0},
"USERS": {true, ircHandleUSERS, 0, 0},
"SUMMON": {true, ircHandleSUMMON, 0, 0},
+ "AWAY": {true, ircHandleAWAY, 0, 0},
+ "ADMIN": {true, ircHandleADMIN, 0, 0},
"MODE": {true, ircHandleMODE, 0, 0},
"PRIVMSG": {true, ircHandlePRIVMSG, 0, 0},
"NOTICE": {true, ircHandleNOTICE, 0, 0},
+ "JOIN": {true, ircHandleJOIN, 0, 0},
+ "PART": {true, ircHandlePART, 0, 0},
+ "KICK": {true, ircHandleKICK, 0, 0},
+ "INVITE": {true, ircHandleINVITE, 0, 0},
"TOPIC": {true, ircHandleTOPIC, 0, 0},
"LIST": {true, ircHandleLIST, 0, 0},
"NAMES": {true, ircHandleNAMES, 0, 0},
"WHO": {true, ircHandleWHO, 0, 0},
"WHOIS": {true, ircHandleWHOIS, 0, 0},
"WHOWAS": {true, ircHandleWHOWAS, 0, 0},
+ "ISON": {true, ircHandleISON, 0, 0},
+
+ "DIE": {true, ircHandleDIE, 0, 0},
}
func ircProcessMessage(c *client, msg *message, raw string) {
@@ -1739,7 +2061,7 @@ func ircProcessMessage(c *client, msg *message, raw string) {
}
}
-// --- ? -----------------------------------------------------------------------
+// --- Network I/O -------------------------------------------------------------
// Handle the results from initializing the client's connection.
func (c *client) onPrepared(host string, isTLS bool) {