adding skeleton functionality
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -22,3 +22,5 @@ _testmain.go
|
|||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
*.prof
|
*.prof
|
||||||
|
|
||||||
|
/go-syndie
|
||||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2016 Keith Petkus
|
Copyright (c) 2016 Keith Petkus <keith@keithp.net>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -1 +1,6 @@
|
|||||||
# go-syndie
|
# go-syndie
|
||||||
|
Major WIP, not much to see here. Expect major refactoring and breaking changes.
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
* http://github.com/Sirupsen/logrus
|
||||||
|
* http://github.com/jackpal/bencode-go
|
37
header.go
Normal file
37
header.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// SyndieHeader holds a Syndie header that contains version and pairs fields:
|
||||||
|
/*
|
||||||
|
Author AuthenticationMask TargetChannel PostURI References Tags OverwriteURI ForceNewThread
|
||||||
|
RefuseReplies Cancel Subject BodyKey BodyKeyPromptSalt BodyKeyPrompt Identity EncryptKey Name
|
||||||
|
Description Edition PublicPosting PublicReplies AuthorizedKeys ManagerKeys Archives ChannelReadKeys Expiration
|
||||||
|
*/
|
||||||
|
type SyndieHeader struct {
|
||||||
|
Version string
|
||||||
|
Author string
|
||||||
|
AuthenticationMask string
|
||||||
|
TargetChannel string
|
||||||
|
PostURI URI
|
||||||
|
References []URI
|
||||||
|
Tags []string
|
||||||
|
OverwriteURI URI
|
||||||
|
ForceNewThread bool
|
||||||
|
RefuseReplies bool
|
||||||
|
Cancel []URI
|
||||||
|
Subject string
|
||||||
|
BodyKey string
|
||||||
|
BodyKeyPromptSalt string
|
||||||
|
BodyKeyPrompt string
|
||||||
|
Identity string
|
||||||
|
EncryptKey string
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Edition int
|
||||||
|
PublicPosting bool
|
||||||
|
PublicReplies bool
|
||||||
|
AuthorizedKeys []string
|
||||||
|
ManagerKeys []string
|
||||||
|
Archives []URI
|
||||||
|
ChannelReadKeys []string
|
||||||
|
Expiration string
|
||||||
|
}
|
13
main.go
Normal file
13
main.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Printf("go-syndie: startup.")
|
||||||
|
payload := SyndiePayload{}
|
||||||
|
payload.OpenFile(os.Args[1])
|
||||||
|
}
|
173
payload.go
Normal file
173
payload.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SyndiePayload holds a Syndie Payload that contains a header and trailer field
|
||||||
|
type SyndiePayload struct {
|
||||||
|
Header []byte
|
||||||
|
Trailer []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message holds the reference to a Syndie header and trailer
|
||||||
|
type Message struct {
|
||||||
|
Head *SyndieHeader
|
||||||
|
Body *SyndieTrailer
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshallTrailer marshalls a SyndiePayload into a SyndieTrailer with various struct fields
|
||||||
|
func (payload *SyndiePayload) MarshallTrailer() *SyndieTrailer {
|
||||||
|
bs := payload.Trailer
|
||||||
|
trailer := SyndieTrailer{}
|
||||||
|
if bytes.HasPrefix(bs, []byte("Size=")) {
|
||||||
|
line := strings.Split(string(bs)[len("Size="):], "\n")
|
||||||
|
size, err := strconv.Atoi(line[0])
|
||||||
|
rest := strings.Join(line[1:], "")
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"at": "(payload) MarshallTrailer",
|
||||||
|
"size": size,
|
||||||
|
"line": line,
|
||||||
|
"reason": "parsing error",
|
||||||
|
}).Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
trailer.size = size
|
||||||
|
trailer.body = []byte(rest)
|
||||||
|
} else {
|
||||||
|
panic("Invalid trailer marshalling attempted")
|
||||||
|
}
|
||||||
|
return &trailer
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshallHeader marshalls a SyndiePayload into a SyndieHeader with various struct fields
|
||||||
|
func (payload *SyndiePayload) MarshallHeader() *SyndieHeader {
|
||||||
|
header := SyndieHeader{}
|
||||||
|
str := strings.SplitAfter(string(payload.Header), "\n")
|
||||||
|
header.Version = str[0]
|
||||||
|
for _, h := range str {
|
||||||
|
switch strings.Split(h, "=")[0] {
|
||||||
|
case "Author":
|
||||||
|
header.Author = strings.Split(h, "=")[1]
|
||||||
|
case "AuthenticationMask":
|
||||||
|
header.AuthenticationMask = strings.Split(h, "=")[1]
|
||||||
|
case "TargetChannel":
|
||||||
|
header.TargetChannel = strings.Split(h, "=")[1]
|
||||||
|
case "PostURI":
|
||||||
|
// header.PostURI = strings.Split(h, "=")[1:]
|
||||||
|
case "References":
|
||||||
|
// header.References = strings.Split(h, "=")[1:]
|
||||||
|
case "Tags":
|
||||||
|
header.Tags = strings.Split(h, "=")[1:]
|
||||||
|
case "OverwriteURI":
|
||||||
|
// header.OverwriteURI = strings.Split(h, "=")[1:]
|
||||||
|
case "ForceNewThread":
|
||||||
|
if strings.Contains(strings.Split(h, "=")[1], "true") {
|
||||||
|
header.ForceNewThread = true
|
||||||
|
}
|
||||||
|
case "RefuseReplies":
|
||||||
|
if strings.Contains(strings.Split(h, "=")[1], "true") {
|
||||||
|
header.RefuseReplies = true
|
||||||
|
}
|
||||||
|
case "Cancel":
|
||||||
|
// header.Cancel = strings.Split(h, "=")[1:]
|
||||||
|
case "Subject":
|
||||||
|
header.Subject = strings.Split(h, "=")[1]
|
||||||
|
case "BodyKey":
|
||||||
|
header.BodyKey = strings.Split(h, "=")[1]
|
||||||
|
case "BodyKeyPromptSalt":
|
||||||
|
header.BodyKeyPromptSalt = strings.Split(h, "=")[1]
|
||||||
|
case "BodyKeyPrompt":
|
||||||
|
header.BodyKeyPrompt = strings.Split(h, "=")[1]
|
||||||
|
case "Identity":
|
||||||
|
header.Identity = strings.Split(h, "=")[1]
|
||||||
|
case "EncryptKey":
|
||||||
|
header.EncryptKey = strings.Split(h, "=")[1]
|
||||||
|
case "Name":
|
||||||
|
header.Name = strings.Split(h, "=")[1]
|
||||||
|
case "Description":
|
||||||
|
header.Description = strings.Split(h, "=")[1]
|
||||||
|
case "Edition":
|
||||||
|
i, err := strconv.Atoi(strings.TrimRight(strings.Split(h, "=")[1], "\n"))
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"at": "(payload) MarshallHeader strconv",
|
||||||
|
"i": i,
|
||||||
|
"reason": "conversion error",
|
||||||
|
}).Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
header.Edition = i
|
||||||
|
case "PublicPosting":
|
||||||
|
if strings.Contains(strings.Split(h, "=")[1], "true") {
|
||||||
|
header.PublicPosting = true
|
||||||
|
}
|
||||||
|
case "PublicReplies":
|
||||||
|
if strings.Contains(strings.Split(h, "=")[1], "true") {
|
||||||
|
header.PublicReplies = true
|
||||||
|
}
|
||||||
|
case "AuthorizedKeys":
|
||||||
|
header.AuthorizedKeys = strings.Split(h, "=")[1:]
|
||||||
|
case "ManagerKeys":
|
||||||
|
header.ManagerKeys = strings.Split(h, "=")[1:]
|
||||||
|
case "Archives":
|
||||||
|
// header.Archives = strings.Split(h, "=")[1:]
|
||||||
|
case "ChannelReadKeys":
|
||||||
|
header.ChannelReadKeys = strings.Split(h, "=")[1:]
|
||||||
|
case "Expiration":
|
||||||
|
header.Expiration = strings.Split(h, "=")[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &header
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse marshalls a raw []byte representation of a Syndie Message into header and trailer fields
|
||||||
|
func (payload *SyndiePayload) Parse(bs []byte) {
|
||||||
|
if bytes.HasPrefix(bs, []byte("Syndie.Message.1.0")) {
|
||||||
|
payload.Header = bs
|
||||||
|
} else {
|
||||||
|
rest := append(payload.Trailer[:], bs...)
|
||||||
|
payload.Trailer = rest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//OpenFile tries to open a SyndiePayload
|
||||||
|
func (payload *SyndiePayload) OpenFile(s string) {
|
||||||
|
file, err := os.Open(s)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"at": "(main)",
|
||||||
|
"file": s,
|
||||||
|
"reason": "failed to open file",
|
||||||
|
}).Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
message := Message{}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
buf := make([]byte, 0, 64*1024)
|
||||||
|
scanner.Buffer(buf, 1024*1024)
|
||||||
|
scanner.Split(newlineDelimiter)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
payload.Parse(scanner.Bytes())
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"at": "(main) scanner.Scan",
|
||||||
|
"payload_parsed": scanner.Bytes(),
|
||||||
|
"reason": "invalid input scanned",
|
||||||
|
}).Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
message.Head = payload.MarshallHeader()
|
||||||
|
message.Body = payload.MarshallTrailer()
|
||||||
|
|
||||||
|
// TODO: Lookup the channel key from the URI and attempt to decrypt
|
||||||
|
log.Printf("Dumping contents: %s", message.Body.DecryptAES(""))
|
||||||
|
}
|
72
trailer.go
Normal file
72
trailer.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SyndieTrailer contains a Syndie trailer that contains version and pairs fields
|
||||||
|
type SyndieTrailer struct {
|
||||||
|
size int
|
||||||
|
body []byte
|
||||||
|
authorizationSig string
|
||||||
|
authenticationSig string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessagePayload holds the following: rand(nonzero) padding + 0 + internalSize + totalSize + data + rand
|
||||||
|
type MessagePayload struct {
|
||||||
|
internalSize int
|
||||||
|
totalSize int
|
||||||
|
decrypted []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptAES decrypts a SyndieTrailer into a messagePayload
|
||||||
|
func (trailer *SyndieTrailer) DecryptAES(key string) MessagePayload {
|
||||||
|
var found bool
|
||||||
|
|
||||||
|
inner := MessagePayload{}
|
||||||
|
k, err := I2PEncoding.DecodeString(key)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"at": "(trailer) DecryptAES, DecodeString",
|
||||||
|
"key": key,
|
||||||
|
"reason": "unable to convert key into b64",
|
||||||
|
}).Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
block, err := aes.NewCipher([]byte(k))
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"at": "(trailer) DecryptAES, NewCipher",
|
||||||
|
"key": key,
|
||||||
|
"block": block,
|
||||||
|
"reason": "invalid block AES cipher",
|
||||||
|
}).Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
decrypter := cipher.NewCBCDecrypter(block, trailer.body[:16])
|
||||||
|
decrypted := make([]byte, len(trailer.body[:trailer.size]))
|
||||||
|
decrypter.CryptBlocks(decrypted, trailer.body[:trailer.size])
|
||||||
|
for i := range decrypted {
|
||||||
|
if !found && decrypted[i] == 0x0 {
|
||||||
|
is := int(binary.BigEndian.Uint32(decrypted[i+1 : +i+5]))
|
||||||
|
ts := int(binary.BigEndian.Uint32(decrypted[i+5 : +i+9]))
|
||||||
|
if trailer.size != ts+16 {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"at": "(trailer) DecryptAES",
|
||||||
|
"trailer_size": trailer.size,
|
||||||
|
"is": is,
|
||||||
|
"ts": ts + 16,
|
||||||
|
"reason": "payload size did not match envelope size",
|
||||||
|
}).Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.internalSize = is
|
||||||
|
inner.totalSize = ts
|
||||||
|
inner.decrypted = decrypted[i+10 : is]
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inner
|
||||||
|
}
|
129
uri.go
Normal file
129
uri.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/jackpal/bencode-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
URI defines the URIs safely passable within syndie, capable of referencing specific resources.
|
||||||
|
They contain one of four reference types, plus a bencoded set of attributes:
|
||||||
|
*/
|
||||||
|
type URI struct {
|
||||||
|
RefType string
|
||||||
|
Name string `bencode:",omitempty"`
|
||||||
|
Desc string `bencode:",omitempty"`
|
||||||
|
Tag []string `bencode:",omitempty"`
|
||||||
|
Author string `bencode:",omitempty"`
|
||||||
|
Net string `bencode:",omitempty"`
|
||||||
|
ReadKeyType string `bencode:",omitempty"`
|
||||||
|
ReadKeyData string `bencode:",omitempty"`
|
||||||
|
PostKeyType string `bencode:",omitempty"`
|
||||||
|
PostKeyData string `bencode:",omitempty"`
|
||||||
|
URL string `bencode:",omitempty"`
|
||||||
|
Channel string `bencode:",omitempty"`
|
||||||
|
MessageID int `bencode:",omitempty"`
|
||||||
|
Page int `bencode:",omitempty"`
|
||||||
|
Attachment int `bencode:",omitempty"`
|
||||||
|
Scope []string `bencode:",omitempty"`
|
||||||
|
PostByScope []string `bencode:",omitempty"`
|
||||||
|
Age int `bencode:",omitempty"`
|
||||||
|
AgeLocal int `bencode:",omitempty"`
|
||||||
|
UnreadOnly bool `bencode:",omitempty"`
|
||||||
|
TagInclude []string `bencode:",omitempty"`
|
||||||
|
TagRequire []string `bencode:",omitempty"`
|
||||||
|
TagExclude []string `bencode:",omitempty"`
|
||||||
|
TagMessages bool `bencode:",omitempty"`
|
||||||
|
PageMin int `bencode:",omitempty"`
|
||||||
|
PageMax int `bencode:",omitempty"`
|
||||||
|
AttachMin int `bencode:",omitempty"`
|
||||||
|
AttachMax int `bencode:",omitempty"`
|
||||||
|
RefMin int `bencode:",omitempty"`
|
||||||
|
RefMax int `bencode:",omitempty"`
|
||||||
|
KeyMin int `bencode:",omitempty"`
|
||||||
|
KeyMax int `bencode:",omitempty"`
|
||||||
|
Encrypted bool `bencode:",omitempty"`
|
||||||
|
PBE bool `bencode:",omitempty"`
|
||||||
|
Private bool `bencode:",omitempty"`
|
||||||
|
Public bool `bencode:",omitempty"`
|
||||||
|
Authorized bool `bencode:",omitempty"`
|
||||||
|
Threaded bool `bencode:",omitempty"`
|
||||||
|
Keyword string `bencode:",omitempty"`
|
||||||
|
Body string `bencode:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// colonDelimiter returns data for a scanner delimited by a colon
|
||||||
|
func colonDelimiter(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
|
log.Printf("data was %s", data)
|
||||||
|
if atEOF && len(data) == 0 {
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
if i := bytes.Index(data, []byte{58}); i >= 0 {
|
||||||
|
return i + 1, data[0:i], nil
|
||||||
|
}
|
||||||
|
if atEOF {
|
||||||
|
return len(data), data, nil
|
||||||
|
}
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// trimSyndieURI trims "urn:", "urn:syndie", and "syndie:" from the left of a string
|
||||||
|
func trimSyndieURI(in string) string {
|
||||||
|
if strings.HasPrefix(in, "urn:syndie:") {
|
||||||
|
in = strings.Join(strings.Split(in, "urn:syndie:")[1:], "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(in, "urn:") {
|
||||||
|
in = strings.Join(strings.Split(in, "urn:")[1:], "")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(in, "syndie:") {
|
||||||
|
in = strings.Join(strings.Split(in, "syndie:")[1:], "")
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareURI checks a string for an invalid URI and ommits the refType before bencoding the rest.
|
||||||
|
func prepareURI(in string) (out string, err error) {
|
||||||
|
if len(in) < 3 {
|
||||||
|
return in, errors.New("invalid URI")
|
||||||
|
}
|
||||||
|
in = trimSyndieURI(in)
|
||||||
|
switch strings.Split(strings.ToLower(in), ":")[0] {
|
||||||
|
case "url", "channel", "search", "archive", "text":
|
||||||
|
// Drop the RefType to prepare for bencode
|
||||||
|
return strings.Join(strings.Split(in, ":")[1:], ":"), nil
|
||||||
|
default:
|
||||||
|
return in, errors.New("invalid URI refType: " + in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshall takes a URI as string and returns a populated URI
|
||||||
|
func (u *URI) Marshall(s string) *URI {
|
||||||
|
if len(s) < 3 {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"at": "(uri) Marshall",
|
||||||
|
"reason": "URI was too short to process",
|
||||||
|
}).Fatalf("URI was too short to process")
|
||||||
|
return &URI{}
|
||||||
|
}
|
||||||
|
s = trimSyndieURI(s)
|
||||||
|
u.RefType = strings.Split(s, ":")[0]
|
||||||
|
prepared, err := prepareURI(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
r := bytes.NewReader([]byte(prepared))
|
||||||
|
|
||||||
|
berr := bencode.Unmarshal(r, &u)
|
||||||
|
if berr != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"at": "(uri) Marshall",
|
||||||
|
"reason": "error while parsing bencode",
|
||||||
|
}).Fatalf("%s", berr)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
27
util.go
Normal file
27
util.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: depend on go-i2p/common
|
||||||
|
|
||||||
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~"
|
||||||
|
|
||||||
|
// I2PEncoding returns an I2P compatible base64 encoding based on a custom alphabet
|
||||||
|
var I2PEncoding *base64.Encoding = base64.NewEncoding(alphabet)
|
||||||
|
|
||||||
|
// newlineDelimiter returns data for a scanner delimited by \n\n
|
||||||
|
func newlineDelimiter(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
|
if atEOF && len(data) == 0 {
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
if i := bytes.Index(data, []byte{'\n', '\n'}); i >= 0 {
|
||||||
|
return i + 2, data[0:i], nil
|
||||||
|
}
|
||||||
|
if atEOF {
|
||||||
|
return len(data), data, nil
|
||||||
|
}
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
Reference in New Issue
Block a user