Compare commits
36 Commits
4d50ed111a
...
5863040f93
Author | SHA1 | Date | |
---|---|---|---|
5863040f93 | |||
f891e5ca63 | |||
8344b09c4f | |||
1238233556 | |||
9c31fb69df | |||
a51c247d69 | |||
f26e6361f3 | |||
4073b7329f | |||
6421892ef3 | |||
a1994865a9 | |||
c285f3a266 | |||
e2c34afbc6 | |||
e2c8fb6e33 | |||
5c7ac9a92b | |||
3fee7e8051 | |||
09d7a10b69 | |||
e9bcd0fa53 | |||
3815795d59 | |||
fd1538251a | |||
ffad1f15a5 | |||
765b741a67 | |||
ab66a60703 | |||
9ee07873ea | |||
7ee7dc5f9b | |||
fea801ac7a | |||
cbdbfc3d64 | |||
3610f98d67 | |||
e77495f316 | |||
2f841d214f | |||
051bbedc2f | |||
404aa8c9cc | |||
90129ee2bc | |||
50e7f7dca5 | |||
3322fe2851 | |||
208a8fcc7e | |||
2d287752d4 |
2
NEWS
2
NEWS
@ -29,6 +29,8 @@
|
|||||||
|
|
||||||
* Added a web frontend for xC called xP
|
* Added a web frontend for xC called xP
|
||||||
|
|
||||||
|
* Added a Go port of xD called xS
|
||||||
|
|
||||||
|
|
||||||
1.5.0 (2021-12-21) "The Show Must Go On"
|
1.5.0 (2021-12-21) "The Show Must Go On"
|
||||||
|
|
||||||
|
@ -47,8 +47,9 @@ What it notably doesn't support is online changes to configuration, any limits
|
|||||||
besides the total number of connections and mode `+l`, or server linking
|
besides the total number of connections and mode `+l`, or server linking
|
||||||
(which also means no services).
|
(which also means no services).
|
||||||
|
|
||||||
This program has been https://git.janouch.name/p/haven/src/branch/master/hid[
|
xS
|
||||||
ported to Go] in a different project, and development continues over there.
|
--
|
||||||
|
The IRC daemon again, this time ported to Go, additionally supporting WEBIRC.
|
||||||
|
|
||||||
xB
|
xB
|
||||||
--
|
--
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
BEGIN {
|
BEGIN {
|
||||||
# The message catalog is a by-product
|
# The message catalog is a by-product
|
||||||
msg = "xD.msg"
|
msg = "xD.msg"
|
||||||
print "$quote \"" > msg;
|
print "$quote \"" > msg
|
||||||
print "$set 1" > msg;
|
print "$set 1" > msg
|
||||||
}
|
}
|
||||||
|
|
||||||
/^[0-9]+ *IRC_(ERR|RPL)_[A-Z]+ *".*"$/ {
|
/^[0-9]+ *IRC_(ERR|RPL)_[A-Z]+ *".*"$/ {
|
||||||
match($0, /".*"/);
|
match($0, /".*"/)
|
||||||
ids[$1] = $2;
|
ids[$1] = $2
|
||||||
texts[$2] = substr($0, RSTART, RLENGTH);
|
texts[$2] = substr($0, RSTART, RLENGTH)
|
||||||
print $1 " " texts[$2] > msg
|
print $1 " " texts[$2] > msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
.POSIX:
|
.POSIX:
|
||||||
.SUFFIXES:
|
.SUFFIXES:
|
||||||
|
AWK = env LC_ALL=C awk
|
||||||
|
|
||||||
outputs = xP proto.go public/proto.js public/mithril.js
|
outputs = xP proto.go public/proto.js public/mithril.js
|
||||||
all: $(outputs) public/ircfmt.woff2
|
all: $(outputs) public/ircfmt.woff2
|
||||||
@ -7,11 +8,11 @@ all: $(outputs) public/ircfmt.woff2
|
|||||||
xP: xP.go proto.go
|
xP: xP.go proto.go
|
||||||
go build -o $@
|
go build -o $@
|
||||||
proto.go: ../xC-gen-proto.awk ../xC-gen-proto-go.awk ../xC-proto
|
proto.go: ../xC-gen-proto.awk ../xC-gen-proto-go.awk ../xC-proto
|
||||||
awk -f ../xC-gen-proto.awk -f ../xC-gen-proto-go.awk ../xC-proto > $@
|
$(AWK) -f ../xC-gen-proto.awk -f ../xC-gen-proto-go.awk ../xC-proto > $@
|
||||||
public/proto.js: ../xC-gen-proto.awk ../xC-gen-proto-js.awk ../xC-proto
|
public/proto.js: ../xC-gen-proto.awk ../xC-gen-proto-js.awk ../xC-proto
|
||||||
awk -f ../xC-gen-proto.awk -f ../xC-gen-proto-js.awk ../xC-proto > $@
|
$(AWK) -f ../xC-gen-proto.awk -f ../xC-gen-proto-js.awk ../xC-proto > $@
|
||||||
public/ircfmt.woff2: gen-ircfmt.awk
|
public/ircfmt.woff2: gen-ircfmt.awk
|
||||||
awk -v Output=$@ -f gen-ircfmt.awk
|
$(AWK) -v Output=$@ -f gen-ircfmt.awk
|
||||||
public/mithril.js:
|
public/mithril.js:
|
||||||
curl -Lo $@ https://unpkg.com/mithril/mithril.js
|
curl -Lo $@ https://unpkg.com/mithril/mithril.js
|
||||||
clean:
|
clean:
|
||||||
|
2
xS/.gitignore
vendored
Normal file
2
xS/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/xS
|
||||||
|
/xS-replies.go
|
13
xS/Makefile
Normal file
13
xS/Makefile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.POSIX:
|
||||||
|
.SUFFIXES:
|
||||||
|
AWK = env LC_ALL=C awk
|
||||||
|
|
||||||
|
outputs = xS xS-replies.go
|
||||||
|
all: $(outputs)
|
||||||
|
|
||||||
|
xS: xS.go xS-replies.go
|
||||||
|
go build -o $@
|
||||||
|
xS-replies.go: xS-gen-replies.awk xS-replies
|
||||||
|
$(AWK) -f xS-gen-replies.awk xS-replies > $@
|
||||||
|
clean:
|
||||||
|
rm -f $(outputs)
|
20
xS/xS-gen-replies.awk
Executable file
20
xS/xS-gen-replies.awk
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/awk -f
|
||||||
|
/^[0-9]+ *(ERR|RPL)_[A-Z]+ *".*"$/ {
|
||||||
|
match($0, /".*"/)
|
||||||
|
ids[$1] = $2
|
||||||
|
texts[$2] = substr($0, RSTART, RLENGTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
print "package main"
|
||||||
|
print ""
|
||||||
|
print "const ("
|
||||||
|
for (i in ids)
|
||||||
|
printf("\t%s = %s\n", ids[i], i)
|
||||||
|
print ")"
|
||||||
|
print ""
|
||||||
|
print "var defaultReplies = map[int]string{"
|
||||||
|
for (i in ids)
|
||||||
|
print "\t" ids[i] ": " texts[ids[i]] ","
|
||||||
|
print "}"
|
||||||
|
}
|
87
xS/xS-replies
Normal file
87
xS/xS-replies
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
1 RPL_WELCOME ":Welcome to the Internet Relay Network %s!%s@%s"
|
||||||
|
2 RPL_YOURHOST ":Your host is %s, running version %s"
|
||||||
|
3 RPL_CREATED ":This server was created %s"
|
||||||
|
4 RPL_MYINFO "%s %s %s %s"
|
||||||
|
5 RPL_ISUPPORT "%s :are supported by this server"
|
||||||
|
211 RPL_STATSLINKINFO "%s %d %d %d %d %d %d"
|
||||||
|
212 RPL_STATSCOMMANDS "%s %d %d %d"
|
||||||
|
219 RPL_ENDOFSTATS "%c :End of STATS report"
|
||||||
|
221 RPL_UMODEIS "+%s"
|
||||||
|
242 RPL_STATSUPTIME ":Server Up %d days %d:%02d:%02d"
|
||||||
|
251 RPL_LUSERCLIENT ":There are %d users and %d services on %d servers"
|
||||||
|
252 RPL_LUSEROP "%d :operator(s) online"
|
||||||
|
253 RPL_LUSERUNKNOWN "%d :unknown connection(s)"
|
||||||
|
254 RPL_LUSERCHANNELS "%d :channels formed"
|
||||||
|
255 RPL_LUSERME ":I have %d clients and %d servers"
|
||||||
|
301 RPL_AWAY "%s :%s"
|
||||||
|
302 RPL_USERHOST ":%s"
|
||||||
|
303 RPL_ISON ":%s"
|
||||||
|
305 RPL_UNAWAY ":You are no longer marked as being away"
|
||||||
|
306 RPL_NOWAWAY ":You have been marked as being away"
|
||||||
|
311 RPL_WHOISUSER "%s %s %s * :%s"
|
||||||
|
312 RPL_WHOISSERVER "%s %s :%s"
|
||||||
|
313 RPL_WHOISOPERATOR "%s :is an IRC operator"
|
||||||
|
314 RPL_WHOWASUSER "%s %s %s * :%s"
|
||||||
|
315 RPL_ENDOFWHO "%s :End of WHO list"
|
||||||
|
317 RPL_WHOISIDLE "%s %d :seconds idle"
|
||||||
|
318 RPL_ENDOFWHOIS "%s :End of WHOIS list"
|
||||||
|
319 RPL_WHOISCHANNELS "%s :%s"
|
||||||
|
322 RPL_LIST "%s %d :%s"
|
||||||
|
323 RPL_LISTEND ":End of LIST"
|
||||||
|
324 RPL_CHANNELMODEIS "%s +%s"
|
||||||
|
329 RPL_CREATIONTIME "%s %d"
|
||||||
|
331 RPL_NOTOPIC "%s :No topic is set"
|
||||||
|
332 RPL_TOPIC "%s :%s"
|
||||||
|
333 RPL_TOPICWHOTIME "%s %s %d"
|
||||||
|
341 RPL_INVITING "%s %s"
|
||||||
|
346 RPL_INVITELIST "%s %s"
|
||||||
|
347 RPL_ENDOFINVITELIST "%s :End of channel invite list"
|
||||||
|
348 RPL_EXCEPTLIST "%s %s"
|
||||||
|
349 RPL_ENDOFEXCEPTLIST "%s :End of channel exception list"
|
||||||
|
351 RPL_VERSION "%s.%d %s :%s"
|
||||||
|
352 RPL_WHOREPLY "%s %s %s %s %s %s :%d %s"
|
||||||
|
353 RPL_NAMREPLY "%c %s :%s"
|
||||||
|
364 RPL_LINKS "%s %s :%d %s"
|
||||||
|
365 RPL_ENDOFLINKS "%s :End of LINKS list"
|
||||||
|
366 RPL_ENDOFNAMES "%s :End of NAMES list"
|
||||||
|
367 RPL_BANLIST "%s %s"
|
||||||
|
368 RPL_ENDOFBANLIST "%s :End of channel ban list"
|
||||||
|
369 RPL_ENDOFWHOWAS "%s :End of WHOWAS"
|
||||||
|
372 RPL_MOTD ":- %s"
|
||||||
|
375 RPL_MOTDSTART ":- %s Message of the day - "
|
||||||
|
376 RPL_ENDOFMOTD ":End of MOTD command"
|
||||||
|
391 RPL_TIME "%s :%s"
|
||||||
|
401 ERR_NOSUCHNICK "%s :No such nick/channel"
|
||||||
|
402 ERR_NOSUCHSERVER "%s :No such server"
|
||||||
|
403 ERR_NOSUCHCHANNEL "%s :No such channel"
|
||||||
|
404 ERR_CANNOTSENDTOCHAN "%s :Cannot send to channel"
|
||||||
|
406 ERR_WASNOSUCHNICK "%s :There was no such nickname"
|
||||||
|
409 ERR_NOORIGIN ":No origin specified"
|
||||||
|
410 ERR_INVALIDCAPCMD "%s :%s"
|
||||||
|
411 ERR_NORECIPIENT ":No recipient given (%s)"
|
||||||
|
412 ERR_NOTEXTTOSEND ":No text to send"
|
||||||
|
421 ERR_UNKNOWNCOMMAND "%s: Unknown command"
|
||||||
|
422 ERR_NOMOTD ":MOTD File is missing"
|
||||||
|
423 ERR_NOADMININFO "%s :No administrative info available"
|
||||||
|
431 ERR_NONICKNAMEGIVEN ":No nickname given"
|
||||||
|
432 ERR_ERRONEOUSNICKNAME "%s :Erroneous nickname"
|
||||||
|
433 ERR_NICKNAMEINUSE "%s :Nickname is already in use"
|
||||||
|
441 ERR_USERNOTINCHANNEL "%s %s :They aren't on that channel"
|
||||||
|
442 ERR_NOTONCHANNEL "%s :You're not on that channel"
|
||||||
|
443 ERR_USERONCHANNEL "%s %s :is already on channel"
|
||||||
|
445 ERR_SUMMONDISABLED ":SUMMON has been disabled"
|
||||||
|
446 ERR_USERSDISABLED ":USERS has been disabled"
|
||||||
|
451 ERR_NOTREGISTERED ":You have not registered"
|
||||||
|
461 ERR_NEEDMOREPARAMS "%s :Not enough parameters"
|
||||||
|
462 ERR_ALREADYREGISTERED ":Unauthorized command (already registered)"
|
||||||
|
467 ERR_KEYSET "%s :Channel key already set"
|
||||||
|
471 ERR_CHANNELISFULL "%s :Cannot join channel (+l)"
|
||||||
|
472 ERR_UNKNOWNMODE "%c :is unknown mode char to me for %s"
|
||||||
|
473 ERR_INVITEONLYCHAN "%s :Cannot join channel (+i)"
|
||||||
|
474 ERR_BANNEDFROMCHAN "%s :Cannot join channel (+b)"
|
||||||
|
475 ERR_BADCHANNELKEY "%s :Cannot join channel (+k)"
|
||||||
|
476 ERR_BADCHANMASK "%s :Bad Channel Mask"
|
||||||
|
481 ERR_NOPRIVILEGES ":Permission Denied- You're not an IRC operator"
|
||||||
|
482 ERR_CHANOPRIVSNEEDED "%s :You're not channel operator"
|
||||||
|
501 ERR_UMODEUNKNOWNFLAG ":Unknown MODE flag"
|
||||||
|
502 ERR_USERSDONTMATCH ":Cannot change mode for other users"
|
168
xS/xS_test.go
Normal file
168
xS/xS_test.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2015 - 2018, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSplitString(t *testing.T) {
|
||||||
|
var splitStringTests = []struct {
|
||||||
|
s, delims string
|
||||||
|
ignoreEmpty bool
|
||||||
|
result []string
|
||||||
|
}{
|
||||||
|
{",a,,bc", ",", false, []string{"", "a", "", "bc"}},
|
||||||
|
{",a,,bc", ",", true, []string{"a", "bc"}},
|
||||||
|
{"a,;bc,", ",;", false, []string{"a", "", "bc", ""}},
|
||||||
|
{"a,;bc,", ",;", true, []string{"a", "bc"}},
|
||||||
|
{"", ",", false, []string{""}},
|
||||||
|
{"", ",", true, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, d := range splitStringTests {
|
||||||
|
got := splitString(d.s, d.delims, d.ignoreEmpty)
|
||||||
|
if !reflect.DeepEqual(got, d.result) {
|
||||||
|
t.Errorf("case %d: %v should be %v\n", i, got, d.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func socketpair() (*os.File, *os.File, error) {
|
||||||
|
pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// See go #24331, this makes 1.11 use the internal poller
|
||||||
|
// while there wasn't a way to achieve that before.
|
||||||
|
if err := syscall.SetNonblock(int(pair[0]), true); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := syscall.SetNonblock(int(pair[1]), true); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fa := os.NewFile(uintptr(pair[0]), "a")
|
||||||
|
if fa == nil {
|
||||||
|
return nil, nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
fb := os.NewFile(uintptr(pair[1]), "b")
|
||||||
|
if fb == nil {
|
||||||
|
fa.Close()
|
||||||
|
return nil, nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
return fa, fb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetectTLS(t *testing.T) {
|
||||||
|
detectTLSFromFunc := func(t *testing.T, writer func(net.Conn)) bool {
|
||||||
|
// net.Pipe doesn't use file descriptors, we need a socketpair.
|
||||||
|
sockA, sockB, err := socketpair()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer sockA.Close()
|
||||||
|
defer sockB.Close()
|
||||||
|
|
||||||
|
fcB, err := net.FileConn(sockB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
go writer(fcB)
|
||||||
|
|
||||||
|
fcA, err := net.FileConn(sockA)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sc, err := fcA.(syscall.Conn).SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return detectTLS(sc)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("SSL_2.0", func(t *testing.T) {
|
||||||
|
if !detectTLSFromFunc(t, func(fc net.Conn) {
|
||||||
|
// The obsolete, useless, unsupported SSL 2.0 record format.
|
||||||
|
_, _ = fc.Write([]byte{0x80, 0x01, 0x01})
|
||||||
|
}) {
|
||||||
|
t.Error("could not detect SSL")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("crypto_tls", func(t *testing.T) {
|
||||||
|
if !detectTLSFromFunc(t, func(fc net.Conn) {
|
||||||
|
conn := tls.Client(fc, &tls.Config{InsecureSkipVerify: true})
|
||||||
|
_ = conn.Handshake()
|
||||||
|
}) {
|
||||||
|
t.Error("could not detect TLS")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("text", func(t *testing.T) {
|
||||||
|
if detectTLSFromFunc(t, func(fc net.Conn) {
|
||||||
|
_, _ = fc.Write([]byte("ПРЕВЕД"))
|
||||||
|
}) {
|
||||||
|
t.Error("detected UTF-8 as TLS")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("EOF", func(t *testing.T) {
|
||||||
|
type connCloseWriter interface {
|
||||||
|
net.Conn
|
||||||
|
CloseWrite() error
|
||||||
|
}
|
||||||
|
if detectTLSFromFunc(t, func(fc net.Conn) {
|
||||||
|
_ = fc.(connCloseWriter).CloseWrite()
|
||||||
|
}) {
|
||||||
|
t.Error("detected EOF as TLS")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIRC(t *testing.T) {
|
||||||
|
msg := ircParseMessage(
|
||||||
|
`@first=a\:\s\r\n\\;2nd :srv hi there :good m8 :how are you?`)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(msg.tags, map[string]string{
|
||||||
|
"first": "a; \r\n\\",
|
||||||
|
"2nd": "",
|
||||||
|
}) {
|
||||||
|
t.Error("tags parsed incorrectly")
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.nick != "srv" || msg.user != "" || msg.host != "" {
|
||||||
|
t.Error("server name parsed incorrectly")
|
||||||
|
}
|
||||||
|
if msg.command != "hi" {
|
||||||
|
t.Error("command name parsed incorrectly")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(msg.params,
|
||||||
|
[]string{"there", "good m8 :how are you?"}) {
|
||||||
|
t.Error("params parsed incorrectly")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ircEqual("[fag]^", "{FAG}~") {
|
||||||
|
t.Error("string case comparison not according to RFC 2812")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: More tests.
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user