adding skeleton functionality
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -22,3 +22,5 @@ _testmain.go
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
/go-syndie
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
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
|
||||
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