Fully eradicate: histserv and znc (tested working)

This commit is contained in:
kayos@tcp.direct 2021-12-26 04:06:03 -08:00
parent 261104f067
commit 6760da5d68
7 changed files with 1 additions and 547 deletions

@ -890,67 +890,9 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
// TODO #259 can be implemented as Flush(false) (i.e., nonblocking) while holding joinPartMutex
rb.Flush(true)
channel.autoReplayHistory(client, rb, message.Msgid)
return nil, ""
}
func (channel *Channel) autoReplayHistory(client *Client, rb *ResponseBuffer, skipMsgid string) {
// autoreplay any messages as necessary
var items []history.Item
hasAutoreplayTimestamps := false
var start, end time.Time
if rb.session.zncPlaybackTimes.ValidFor(channel.NameCasefolded()) {
hasAutoreplayTimestamps = true
start, end = rb.session.zncPlaybackTimes.start, rb.session.zncPlaybackTimes.end
} else if !rb.session.autoreplayMissedSince.IsZero() {
// we already checked for history caps in `playReattachMessages`
hasAutoreplayTimestamps = true
start = time.Now().UTC()
end = rb.session.autoreplayMissedSince
}
if hasAutoreplayTimestamps {
_, seq, _ := channel.server.GetHistorySequence(channel, client, "")
if seq != nil {
zncMax := channel.server.Config().History.ZNCMax
items, _ = seq.Between(history.Selector{Time: start}, history.Selector{Time: end}, zncMax)
}
} else if !rb.session.HasHistoryCaps() {
var replayLimit int
customReplayLimit := client.AccountSettings().AutoreplayLines
if customReplayLimit != nil {
replayLimit = *customReplayLimit
maxLimit := channel.server.Config().History.ChathistoryMax
if maxLimit < replayLimit {
replayLimit = maxLimit
}
} else {
replayLimit = channel.server.Config().History.AutoreplayOnJoin
}
if 0 < replayLimit {
_, seq, _ := channel.server.GetHistorySequence(channel, client, "")
if seq != nil {
items, _ = seq.Between(history.Selector{}, history.Selector{}, replayLimit)
}
}
}
// remove the client's own JOIN line from the replay
numItems := len(items)
for i := len(items) - 1; 0 <= i; i-- {
if items[i].Message.Msgid == skipMsgid {
// zero'ed items will not be replayed because their `Type` field is not recognized
items[i] = history.Item{}
numItems--
break
}
}
if 0 < numItems {
channel.replayHistoryItems(rb, items, false)
rb.Flush(true)
}
}
// plays channel join messages (the JOIN line, topic, and names) to a session.
// this is used when attaching a new session to an existing client that already has
// channels, and also when one session of a client initiates a JOIN and the other
@ -1029,119 +971,6 @@ func (channel *Channel) Part(client *Client, message string, rb *ResponseBuffer)
client.server.logger.Debug("channels", fmt.Sprintf("%s left channel %s", details.nick, chname))
}
func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.Item, chathistoryCommand bool) {
// send an empty batch if necessary, as per the CHATHISTORY spec
chname := channel.Name()
client := rb.target
eventPlayback := rb.session.capabilities.Has(caps.EventPlayback)
extendedJoin := rb.session.capabilities.Has(caps.ExtendedJoin)
var playJoinsAsPrivmsg bool
if !eventPlayback {
if chathistoryCommand {
playJoinsAsPrivmsg = true
} else {
switch client.AccountSettings().ReplayJoins {
case ReplayJoinsCommandsOnly:
playJoinsAsPrivmsg = false
case ReplayJoinsAlways:
playJoinsAsPrivmsg = true
}
}
}
batchID := rb.StartNestedHistoryBatch(chname)
defer rb.EndNestedBatch(batchID)
for _, item := range items {
nick := NUHToNick(item.Nick)
switch item.Type {
case history.Privmsg:
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.IsBot, item.Tags, "PRIVMSG", chname, item.Message)
case history.Notice:
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.IsBot, item.Tags, "NOTICE", chname, item.Message)
case history.Tagmsg:
if eventPlayback {
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.IsBot, item.Tags, "TAGMSG", chname, item.Message)
} else if chathistoryCommand {
// #1676, we have to send something here or else it breaks pagination
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, fmt.Sprintf(client.t("%s sent a TAGMSG"), nick))
}
case history.Join:
if eventPlayback {
if extendedJoin {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "JOIN", chname, item.AccountName, item.Params[0])
} else {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "JOIN", chname)
}
} else {
if !playJoinsAsPrivmsg {
continue // #474
}
var message string
if item.AccountName == "*" {
message = fmt.Sprintf(client.t("%s joined the channel"), nick)
} else {
message = fmt.Sprintf(client.t("%[1]s [account: %[2]s] joined the channel"), nick, item.AccountName)
}
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
}
case history.Part:
if eventPlayback {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "PART", chname, item.Message.Message)
} else {
if !playJoinsAsPrivmsg {
continue // #474
}
message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
}
case history.Kick:
if eventPlayback {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "KICK", chname, item.Params[0], item.Message.Message)
} else {
message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Params[0], item.Message.Message)
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
}
case history.Quit:
if eventPlayback {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "QUIT", item.Message.Message)
} else {
if !playJoinsAsPrivmsg {
continue // #474
}
message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
}
case history.Nick:
if eventPlayback {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "NICK", item.Params[0])
} else {
message := fmt.Sprintf(client.t("%[1]s changed nick to %[2]s"), nick, item.Params[0])
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
}
case history.Topic:
if eventPlayback {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "TOPIC", chname, item.Message.Message)
} else {
message := fmt.Sprintf(client.t("%[1]s set the channel topic to: %[2]s"), nick, item.Message.Message)
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
}
case history.Mode:
params := make([]string, len(item.Message.Split)+1)
params[0] = chname
for i, pair := range item.Message.Split {
params[i+1] = pair.Message
}
if eventPlayback {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "MODE", params...)
} else {
message := fmt.Sprintf(client.t("%[1]s set channel modes: %[2]s"), nick, strings.Join(params[1:], " "))
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
}
}
}
}
// SendTopic sends the channel topic to the given client.
// `sendNoTopic` controls whether RPL_NOTOPIC is sent when the topic is unset
func (channel *Channel) SendTopic(client *Client, rb *ResponseBuffer, sendNoTopic bool) {

@ -174,7 +174,6 @@ type Session struct {
registrationMessages int
zncPlaybackTimes *zncPlaybackTimes
autoreplayMissedSince time.Time
batch MultilineBatch
@ -730,15 +729,8 @@ func (client *Client) playReattachMessages(session *Session) {
// because those caps disable autoreplay-on-join and they haven't sent the relevant
// *playback PRIVMSG or CHATHISTORY command yet
rb := NewResponseBuffer(session)
channel.autoReplayHistory(client, rb, "")
rb.Send(true)
}
if !session.autoreplayMissedSince.IsZero() && !hasHistoryCaps {
rb := NewResponseBuffer(session)
zncPlayPrivmsgsFromAll(client, rb, time.Now().UTC(), session.autoreplayMissedSince)
rb.Send(true)
}
session.autoreplayMissedSince = time.Time{}
}
//
@ -856,73 +848,6 @@ func (session *Session) Ping() {
session.Send(nil, "", "PING", session.client.Nick())
}
func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.Item, target string, chathistoryCommand bool) {
var batchID string
details := client.Details()
nick := details.nick
if target == "" {
target = nick
}
batchID = rb.StartNestedHistoryBatch(target)
isSelfMessage := func(item *history.Item) bool {
// XXX: Params[0] is the message target. if the source of this message is an in-memory
// buffer, then it's "" for an incoming message and the recipient's nick for an outgoing
// message. if the source of the message is mysql, then mysql only sees one copy of the
// message, and it's the version with the recipient's nick filled in. so this is an
// incoming message if Params[0] (the recipient's nick) equals the client's nick:
return item.Params[0] != "" && item.Params[0] != nick
}
hasEventPlayback := rb.session.capabilities.Has(caps.EventPlayback)
hasTags := rb.session.capabilities.Has(caps.MessageTags)
for _, item := range items {
var command string
switch item.Type {
case history.Invite:
if isSelfMessage(&item) {
continue
}
if hasEventPlayback {
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "INVITE", nick, item.Message.Message)
} else {
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", fmt.Sprintf(client.t("%[1]s invited you to channel %[2]s"), NUHToNick(item.Nick), item.Message.Message))
}
continue
case history.Privmsg:
command = "PRIVMSG"
case history.Notice:
command = "NOTICE"
case history.Tagmsg:
if hasEventPlayback && hasTags {
command = "TAGMSG"
} else if chathistoryCommand {
// #1676: send something for TAGMSG; we can't discard it entirely
// because it'll break pagination
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", fmt.Sprintf(client.t("%[1]s sent you a TAGMSG"), NUHToNick(item.Nick)))
} else {
continue
}
default:
// see #1676, this shouldn't happen
continue
}
var tags map[string]string
if hasTags {
tags = item.Tags
}
if !isSelfMessage(&item) {
rb.AddSplitMessageFromClient(item.Nick, item.AccountName, item.IsBot, tags, command, nick, item.Message)
} else {
// this message was sent *from* the client to another nick; the target is item.Params[0]
// substitute client's current nickmask in case client changed nick
rb.AddSplitMessageFromClient(details.nickMask, item.AccountName, item.IsBot, tags, command, item.Params[0], item.Message)
}
}
rb.EndNestedBatch(batchID)
}
// IdleTime returns how long this client's been idle.
func (client *Client) IdleTime() time.Duration {
client.stateMutex.RLock()

@ -2020,9 +2020,8 @@ func dispatchMessageToTarget(client *Client, tags map[string]string, histType hi
} else {
lowercaseTarget := strings.ToLower(target)
service, isService := OragonoServices[lowercaseTarget]
_, isZNC := zncHandlers[lowercaseTarget]
if isService || isZNC {
if isService {
details := client.Details()
rb.addEchoMessage(tags, details.nickMask, details.accountName, command, target, message)
if histType != history.Privmsg {
@ -2030,8 +2029,6 @@ func dispatchMessageToTarget(client *Client, tags map[string]string, histType hi
}
if isService {
servicePrivmsgHandler(service, server, client, message.Message, rb)
} else if isZNC {
zncPrivmsgHandler(client, lowercaseTarget, message.Message, rb)
}
return
}
@ -3315,17 +3312,6 @@ func whowasHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respo
return false
}
// ZNC <module> [params]
func zncHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
params := msg.Params[1:]
// #1205: compatibility with Palaver, which sends `ZNC *playback :play ...`
if len(params) == 1 && strings.IndexByte(params[0], ' ') != -1 {
params = strings.Fields(params[0])
}
zncModuleHandler(client, msg.Params[0], params, rb)
return false
}
// fake handler for unknown commands
func unknownCommandHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
var message string

@ -1,36 +0,0 @@
// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
const (
histservHelp = `HistServ is a remnant of bad code. There is no history on ircd.chat.`
)
func histservEnabled(config *Config) bool {
return config.History.Enabled
}
func historyComplianceEnabled(config *Config) bool {
return config.History.Enabled && config.History.Persistent.Enabled && config.History.Retention.EnableAccountIndexing
}
var (
histservCommands = map[string]*serviceCommand{
"forget": {
handler: histservForgetHandler,
help: `Syntax: $bFORGET <account>$b
FORGET deletes all history messages sent by an account.`,
helpShort: `$bFORGET$b doesn't do anything because there is no history here.`,
capabs: []string{"history"},
enabled: histservEnabled,
minParams: 1,
maxParams: 1,
},
}
)
func histservForgetHandler(service *ircService, server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
service.Notice(rb, client.t("ircd.chat does not keep history."))
}

@ -1,17 +0,0 @@
// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
import (
"testing"
"time"
)
func TestZncTimestampParser(t *testing.T) {
assertEqual(zncWireTimeToTime("1558338348.988"), time.Unix(1558338348, 988000000).UTC(), t)
assertEqual(zncWireTimeToTime("1558338348.9"), time.Unix(1558338348, 900000000).UTC(), t)
assertEqual(zncWireTimeToTime("1558338348"), time.Unix(1558338348, 0).UTC(), t)
assertEqual(zncWireTimeToTime(".988"), time.Unix(0, 988000000).UTC(), t)
assertEqual(zncWireTimeToTime("garbage"), time.Unix(0, 0).UTC(), t)
}

@ -83,13 +83,6 @@ var (
Commands: hostservCommands,
HelpBanner: hostservHelp,
}
histservService = &ircService{
Name: "HistServ",
ShortName: "HISTSERV",
CommandAliases: []string{"HISTSERV"},
Commands: histservCommands,
HelpBanner: histservHelp,
}
)
// OragonoServices all services, by lowercase name
@ -97,7 +90,6 @@ var OragonoServices = map[string]*ircService{
"nickserv": nickservService,
"chanserv": chanservService,
"hostserv": hostservService,
"histserv": histservService,
}
func (service *ircService) Notice(rb *ResponseBuffer, text string) {

@ -1,225 +0,0 @@
// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
import (
"fmt"
"strconv"
"strings"
"time"
"git.tcp.direct/ircd/ircd/irc/history"
"git.tcp.direct/ircd/ircd/irc/utils"
)
const (
// #829, also see "Case 2" in the "three cases" below:
zncPlaybackCommandExpiration = time.Second * 30
zncPrefix = "*playback!znc@znc.in"
maxDMTargetsForAutoplay = 128
)
type zncCommandHandler func(client *Client, command string, params []string, rb *ResponseBuffer)
var zncHandlers = map[string]zncCommandHandler{
"*playback": zncPlaybackHandler,
}
func zncPrivmsgHandler(client *Client, command string, privmsg string, rb *ResponseBuffer) {
zncModuleHandler(client, command, strings.Fields(privmsg), rb)
}
func zncModuleHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
command = strings.ToLower(command)
if subHandler, ok := zncHandlers[command]; ok {
subHandler(client, command, params, rb)
} else {
nick := rb.target.Nick()
rb.Add(nil, client.server.name, "NOTICE", nick, fmt.Sprintf(client.t("Oragono does not emulate the ZNC module %s"), command))
rb.Add(nil, "*status!znc@znc.in", "NOTICE", nick, fmt.Sprintf(client.t("No such module [%s]"), command))
}
}
// "number of seconds (floating point for millisecond precision) elapsed since January 1, 1970"
func zncWireTimeToTime(str string) (result time.Time) {
var secondsPortion, fracPortion string
dot := strings.IndexByte(str, '.')
if dot == -1 {
secondsPortion = str
} else {
secondsPortion = str[:dot]
fracPortion = str[dot:]
}
seconds, _ := strconv.ParseInt(secondsPortion, 10, 64)
fraction, _ := strconv.ParseFloat(fracPortion, 64)
return time.Unix(seconds, int64(fraction*1000000000)).UTC()
}
func timeToZncWireTime(t time.Time) (result string) {
secs := t.Unix()
nano := t.UnixNano() - (secs * 1000000000)
return fmt.Sprintf("%d.%d", secs, nano)
}
type zncPlaybackTimes struct {
start time.Time
end time.Time
targets utils.StringSet // nil for "*" (everything), otherwise the channel names
setAt time.Time
}
func (z *zncPlaybackTimes) ValidFor(target string) bool {
if z == nil {
return false
}
if time.Now().Sub(z.setAt) > zncPlaybackCommandExpiration {
return false
}
if z.targets == nil {
return true
}
return z.targets.Has(target)
}
// https://wiki.znc.in/Playback
func zncPlaybackHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
if len(params) == 0 {
return
}
switch strings.ToLower(params[0]) {
case "play":
zncPlaybackPlayHandler(client, command, params, rb)
case "list":
zncPlaybackListHandler(client, command, params, rb)
default:
return
}
}
// PRIVMSG *playback :play <target> [lower_bound] [upper_bound]
// e.g., PRIVMSG *playback :play * 1558374442
func zncPlaybackPlayHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
if len(params) < 2 || len(params) > 4 {
return
}
targetString := params[1]
now := time.Now().UTC()
var start, end time.Time
switch len(params) {
case 2:
// #1205: this should have the same semantics as `LATEST *`
case 3:
// #831: this should have the same semantics as `LATEST timestamp=qux`,
// or equivalently `BETWEEN timestamp=$now timestamp=qux`, as opposed to
// `AFTER timestamp=qux` (this matters in the case where there are
// more than znc-maxmessages available)
start = now
end = zncWireTimeToTime(params[2])
case 4:
start = zncWireTimeToTime(params[2])
end = zncWireTimeToTime(params[3])
}
var targets utils.StringSet
var nickTargets []string
// three cases:
// 1. the user's PMs get played back immediately upon receiving this
// 2. if this is a new connection (from the server's POV), save the information
// and use it to process subsequent joins. (This is the Textual behavior:
// first send the playback PRIVMSG, then send the JOIN lines.)
// 3. if this is a reattach (from the server's POV), immediately play back
// history for channels that the client is already joined to. In this scenario,
// there are three total attempts to play the history:
// 3.1. During the initial reattach (no-op because the *playback privmsg
// hasn't been received yet, but they negotiated the znc.in/playback
// cap so we know we're going to receive it later)
// 3.2 Upon receiving the *playback privmsg, i.e., now: we should play
// the relevant history lines
// 3.3 When the client sends a subsequent redundant JOIN line for those
// channels; redundant JOIN is a complete no-op so we won't replay twice
playPrivmsgs := false
if params[1] == "*" {
playPrivmsgs = true // XXX nil `targets` means "every channel"
} else {
targets = make(utils.StringSet)
for _, targetName := range strings.Split(targetString, ",") {
if strings.HasPrefix(targetName, "#") {
if cfTarget, err := CasefoldChannel(targetName); err == nil {
targets.Add(cfTarget)
}
} else {
if cfNick, err := CasefoldName(targetName); err == nil {
nickTargets = append(nickTargets, cfNick)
}
}
}
}
if playPrivmsgs {
zncPlayPrivmsgsFromAll(client, rb, start, end)
}
rb.session.zncPlaybackTimes = &zncPlaybackTimes{
start: start,
end: end,
targets: targets,
setAt: time.Now().UTC(),
}
for _, channel := range client.Channels() {
if targets == nil || targets.Has(channel.NameCasefolded()) {
channel.autoReplayHistory(client, rb, "")
rb.Flush(true)
}
}
for _, cfNick := range nickTargets {
zncPlayPrivmsgsFrom(client, rb, cfNick, start, end)
rb.Flush(true)
}
}
func zncPlayPrivmsgsFrom(client *Client, rb *ResponseBuffer, target string, start, end time.Time) {
_, sequence, err := client.server.GetHistorySequence(nil, client, target)
if sequence == nil || err != nil {
return
}
zncMax := client.server.Config().History.ZNCMax
items, err := sequence.Between(history.Selector{Time: start}, history.Selector{Time: end}, zncMax)
if err == nil && len(items) != 0 {
client.replayPrivmsgHistory(rb, items, target, false)
}
}
func zncPlayPrivmsgsFromAll(client *Client, rb *ResponseBuffer, start, end time.Time) {
zncMax := client.server.Config().History.ZNCMax
items, err := client.privmsgsBetween(start, end, maxDMTargetsForAutoplay, zncMax)
if err == nil && len(items) != 0 {
client.replayPrivmsgHistory(rb, items, "", false)
}
}
// PRIVMSG *playback :list
func zncPlaybackListHandler(client *Client, command string, params []string, rb *ResponseBuffer) {
limit := client.server.Config().History.ChathistoryMax
correspondents, err := client.listTargets(history.Selector{}, history.Selector{}, limit)
if err != nil {
client.server.logger.Error("internal", "couldn't get history for ZNC list", err.Error())
return
}
nick := client.Nick()
for _, correspondent := range correspondents {
stamp := timeToZncWireTime(correspondent.Time)
unfoldedTarget := client.server.UnfoldName(correspondent.CfName)
rb.Add(nil, zncPrefix, "PRIVMSG", nick, fmt.Sprintf("%s 0 %s", unfoldedTarget, stamp))
}
}