initial commit

This commit is contained in:
2020-03-04 01:17:40 -05:00
commit 83642bd04c
10 changed files with 601 additions and 0 deletions

13
LICENSE.txt Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

6
README.txt Normal file
View File

@ -0,0 +1,6 @@
yagsl
=====
This is experimental and expect lots of breaking changes.
Do not use this in production or at all.

74
sambridge/naming.go Normal file
View File

@ -0,0 +1,74 @@
package sambridge
import (
"errors"
"fmt"
"strings"
"sync"
)
const (
resultOk string = "OK"
invalidKey string = "INVALID_KEY"
keyNotFound string = "KEY_NOT_FOUND"
)
var (
errKeyNotFound = errors.New("Key not found")
errUnknownNamingLookup = errors.New("Unknown naming lookup error")
)
type reply struct {
name string
value string
err error
}
type namingHandler struct {
incoming chan reply
m sync.Mutex
}
func (m *SAMBridge) Lookup(name string) (string, error) {
var lookupReply reply
m.newNamingHandler()
go m.Send(fmt.Sprintf("NAMING LOOKUP NAME=%s\n", name))
for reply := range m.namingHandler.incoming {
lookupReply = reply
break
}
if lookupReply.err != nil {
return name, lookupReply.err
}
return lookupReply.value, nil
}
func (m *SAMBridge) namingReply(line string) {
reply := reply{}
fields := strings.Fields(line)
m.namingHandler.m.Lock()
reply.name = strings.SplitN(fields[3], "=", 2)[1]
switch strings.SplitN(fields[2], "=", 2)[1] { // result
case resultOk:
reply.err = nil
reply.value = strings.SplitN(fields[4], "=", 2)[1]
case invalidKey:
reply.err = errInvalidKey
case keyNotFound:
reply.err = errKeyNotFound
default:
reply.err = errUnknownNamingLookup
}
m.namingHandler.incoming <- reply
m.namingHandler.m.Unlock()
}
func (m *SAMBridge) newNamingHandler() {
if m.namingHandler == nil {
t := new(namingHandler)
t.incoming = make(chan reply)
m.namingHandler = t
}
}

96
sambridge/sambridge.go Normal file
View File

@ -0,0 +1,96 @@
package sambridge
import (
"bufio"
"errors"
"log"
"net"
"strings"
)
type SAMBridge struct {
Options SamOptions
Conn net.Conn
namingHandler *namingHandler
destHandler *destHandler
SessionReply chan SessionReply
StreamReply chan StreamReply
}
type SamOptions struct {
Address string
User string
Password string
}
const handshakeReply string = "HELLO VERSION MIN=3.0 MAX=3.3\n"
func (s *SAMBridge) Start() {
s.Options.Address = "127.0.0.1:7656"
err := s.dialSAMBridge()
if err != nil {
panic(err.Error())
}
r := bufio.NewReader(s.Conn)
for {
line, _ := r.ReadString('\n')
if line != "" {
log.Printf("DEBUG line: %s", line)
}
if strings.Contains(line, " ") {
first := strings.Fields(line)[0]
switch first {
case "HELLO":
go s.handshakeReply(line)
case "SESSION":
s.sessionReply(line)
case "STREAM":
// TODO: Wait for an "OK" STREAM result and as soon as that happens break out of the loop so we can panic on unknown sam lines
s.streamReply(line)
case "DEST":
s.destReply(line)
case "NAMING":
s.namingReply(line)
case "PING":
go s.pingReply(line)
default:
//panic("Unknown SAM line encountered")
}
}
}
}
func (s *SAMBridge) dialSAMBridge() error {
c, err := net.Dial("tcp", s.Options.Address)
s.Conn = c
s.sendHandshake()
return err
}
func (s SAMBridge) Send(line string) error {
if s.Conn != nil {
var sb strings.Builder
sb.WriteString(line)
if !strings.HasSuffix(line, "\n") {
sb.WriteString("\n")
}
w := bufio.NewWriter(s.Conn)
_, err := w.WriteString(sb.String())
err = w.Flush()
return err
}
return errors.New("Conn was closed")
}
func (s SAMBridge) sendHandshake() error {
return s.Send(handshakeReply)
}
func (s SAMBridge) handshakeReply(reply string) error {
if !strings.HasPrefix(reply, "HELLO REPLY RESULT=OK") {
return errHandshakeFailed
}
return nil
}

57
sambridge/sessionreply.go Normal file
View File

@ -0,0 +1,57 @@
package sambridge
import (
"errors"
"strings"
)
type SessionReply struct {
Destination string
Err error
ID string
Message string
}
const (
duplicatedDest = "DUPLICATED_DEST"
duplicatedID = "DUPLICATED_ID"
)
var (
errHandshakeFailed = errors.New("Handshake failed")
errInvalidKey = errors.New("invalid key error")
errDuplicatedDest = errors.New("the destination is not a valid private destination key")
errDuplicatedID = errors.New("the destination is already in use")
errUnknownSession = errors.New("Unknown session error")
)
func (s SAMBridge) sessionReply(line string) {
reply := SessionReply{}
fields := strings.Fields(line)
switch strings.SplitN(fields[2], "=", 2)[1] { // result
case resultOk:
reply.Err = nil
if strings.HasPrefix(fields[3], "DESTINATION") {
reply.Destination = strings.SplitN(fields[3], "=", 2)[1]
}
if strings.HasPrefix(fields[3], "ID") {
reply.ID = strings.SplitN(fields[3], "=", 2)[1]
}
if len(fields) > 4 {
if strings.HasPrefix(fields[4], "MESSAGE") {
reply.Message = strings.SplitN(fields[4], "=", 2)[1]
}
}
case duplicatedID:
reply.Err = errDuplicatedID
case duplicatedDest:
reply.Err = errDuplicatedDest
case invalidKey:
reply.Err = errInvalidKey
default:
reply.Err = errUnknownSession
}
s.SessionReply <- reply
}

58
sambridge/streamreply.go Normal file
View File

@ -0,0 +1,58 @@
package sambridge
import (
"errors"
"log"
"strings"
)
type StreamReply struct {
Err error
Message string
}
const (
invalidID = "INVALID_ID"
i2pErr = "I2P_ERROR"
cantReachPeer = "CANT_REACH_PEER"
timeout = "TIMEOUT"
)
var (
errInvalidID = errors.New("invalid ID")
errI2PErr = errors.New("generic I2P error")
errCantReachPeer = errors.New("can't reach peer")
errTimeout = errors.New("timeout")
errUnknownStream = errors.New("unknown stream error")
)
func (s SAMBridge) streamReply(line string) {
log.Printf("DEBUG STREAMREPLY: %s", line)
reply := StreamReply{}
fields := strings.Fields(line)
switch strings.SplitN(fields[2], "=", 2)[1] { // result
case resultOk:
reply.Err = nil
case invalidID:
reply.Err = errInvalidID
case i2pErr:
reply.Err = errI2PErr
case cantReachPeer:
reply.Err = errCantReachPeer
case invalidKey:
reply.Err = errInvalidKey
case timeout:
reply.Err = errTimeout
default:
reply.Err = errUnknownStream
}
if len(fields) > 3 {
if strings.HasPrefix(fields[3], "MESSAGE") {
reply.Message = strings.SplitN(fields[3], "=", 2)[1]
}
}
s.StreamReply <- reply
}

View File

@ -0,0 +1,65 @@
package sambridge
import (
"errors"
"fmt"
"strings"
"sync"
)
type DestReply struct {
Pub string
Priv string
err error
}
type destHandler struct {
incoming chan DestReply
m sync.Mutex
}
func (m SAMBridge) DestGenerate(signatureType string) (DestReply, error) {
var destReply DestReply
m.newDestHandler()
if signatureType == "" {
signatureType = "DSA_SHA1"
}
m.Send(fmt.Sprintf("DEST GENERATE SIGNATURE_TYPE=%s", signatureType))
for reply := range m.destHandler.incoming {
destReply = reply
break
}
if destReply.err != nil {
return destReply, destReply.err
}
return destReply, nil
}
func (m SAMBridge) destReply(reply string) {
dest := DestReply{}
fields := strings.Fields(reply)
m.destHandler.m.Lock()
if fields[2] != "RESULT=OK" {
dest.err = errors.New(strings.TrimRight(strings.TrimLeft(strings.Join(fields[3:], " "), "MESSAGE=\""), "\""))
} else {
dest.Pub = strings.TrimPrefix(fields[2], "PUB=")
dest.Priv = strings.TrimPrefix(fields[3], "PRIV=")
dest.err = nil
}
m.destHandler.incoming <- dest
m.destHandler.m.Unlock()
}
func (s SAMBridge) pingReply(reply string) error {
s.Send(fmt.Sprintf("PONG %s", strings.TrimLeft(reply, "PING ")))
return nil
}
func (m *SAMBridge) newDestHandler() {
if m.destHandler == nil {
dh := destHandler{}
dh.incoming = make(chan DestReply)
m.destHandler = &dh
}
}

104
session/master.go Normal file
View File

@ -0,0 +1,104 @@
package session
import (
"errors"
"fmt"
"log"
"strconv"
"time"
"yagsl/sambridge"
)
const masterSessionStyle string = "MASTER"
var errMasterSessionFailed = errors.New("Failed to start MasterSession")
type MasterSession struct {
Style string
ID string
Destination string
Transient bool
Sam *sambridge.SAMBridge
*Session
Ready chan bool
Subsessions []Subsession
// i2cp and streaming options go here
}
func NewMasterSession(destination string) (MasterSession, error) {
sb := new(sambridge.SAMBridge)
sb.Options.Address = "127.0.0.1:7656"
go sb.Start()
ms := MasterSession{Style: "MASTER"}
ms.Ready = make(chan bool, 1)
if destination == "" {
destination = "TRANSIENT"
ms.Transient = true
}
ms.Sam = sb
ms.Sam.SessionReply = make(chan sambridge.SessionReply)
go ms.newSessionHandler()
ms.Sam.StreamReply = make(chan sambridge.StreamReply)
go ms.newStreamHandler()
ms.ID = "master" + strconv.FormatInt(time.Now().UTC().Unix(), 10)
ms.Destination = destination
time.Sleep(time.Second * 1)
ms.Sam.Send(fmt.Sprintf("SESSION CREATE STYLE=MASTER ID=%s DESTINATION=%s\n", ms.ID, ms.Destination))
for ready := range ms.Ready {
if ready {
return ms, nil
}
return ms, errMasterSessionFailed
}
return ms, nil
}
func (ms *MasterSession) NewSubsession(fromPort string, toPort string, listenPort string) Subsession {
ss := Subsession{}
ss.ID = "sub" + strconv.FormatInt(time.Now().UTC().Unix(), 10)
if listenPort != "" {
ms.Sam.Send(fmt.Sprintf("SESSION ADD STYLE=STREAM ID=%s FROM_PORT=%s LISTEN_PORT=%s SILENT=FALSE\n", ss.ID, fromPort, listenPort))
} else {
ms.Sam.Send(fmt.Sprintf("SESSION ADD STYLE=STREAM ID=%s FROM_PORT=%s SILENT=FALSE\n", ss.ID, fromPort))
}
ms.Subsessions = append(ms.Subsessions, ss)
return ss
}
func (ms *MasterSession) newSessionHandler() {
var sessionReply sambridge.SessionReply
for reply := range ms.Sam.SessionReply {
sessionReply = reply
break
}
if sessionReply.Err != nil {
ms.Ready <- false
}
ms.Destination = sessionReply.Destination
ms.Ready <- true
}
func (ms *MasterSession) newStreamHandler() {
var streamReply sambridge.StreamReply
for reply := range ms.Sam.StreamReply {
streamReply = reply
break
}
if streamReply.Err != nil {
log.Printf("StreamReply err: %s", streamReply.Err.Error())
} else {
log.Printf("StreamReply success!")
}
}

51
session/session.go Normal file
View File

@ -0,0 +1,51 @@
package session
import (
"log"
"strings"
"sync"
)
type Session struct {
Style string
ID string
Destination string
Options SessionOptions
}
type SessionOptions struct {
Port string
Host string
FromPort string
ToPort string
ListenPort string
ListenProtcol string
Header bool
// i2cp and streaming options go here
}
type sessionHandler struct {
incoming chan reply
m sync.Mutex
}
type reply struct {
full string
name string
value string
err error
}
func (m *Session) SessionCreate() {
}
func (s Session) sessionReply(reply string) error {
fields := strings.Fields(reply)
if strings.HasPrefix(fields[1], "RESULT=") {
log.Printf("Session status result: %s", fields[1:])
} else {
log.Printf("Got SESSION reply: %s", reply)
}
return nil
}

77
session/subsession.go Normal file
View File

@ -0,0 +1,77 @@
package session
import (
"errors"
"fmt"
"net"
"time"
"yagsl/sambridge"
)
type Subsession struct {
*Session
ID string
Style string
Sam *sambridge.SAMBridge
Ready chan (bool)
}
var (
errSubSessionFailed = errors.New("the subsession failed :(")
)
func NewStreamConnect(ID string, destination string, fromPort string) (Subsession, error) {
sb := new(sambridge.SAMBridge)
go sb.Start()
ss := Subsession{
Style: "STREAM",
ID: ID,
}
ss.Sam = sb
ss.Sam.SessionReply = make(chan sambridge.SessionReply)
go ss.newSessionHandler()
// TODO: Replace this handshake pause with a Ready callback to prevent a data race
time.Sleep(time.Second * 1)
// TODO: Refactor this sam.Send into a stream package w/a streamHandler and it's own connect message in that section
err := ss.Sam.Send(fmt.Sprintf("STREAM CONNECT ID=%s DESTINATION=%s FROM_PORT=%s TO_PORT=0 SILENT=FALSE\n", ID, destination, fromPort))
return ss, err
}
func NewStreamAccept(ID string) (Subsession, error) {
sb := new(sambridge.SAMBridge)
go sb.Start()
ss := Subsession{
Style: "STREAM",
ID: ID,
}
ss.Sam = sb
ss.Sam.SessionReply = make(chan sambridge.SessionReply)
go ss.newSessionHandler()
// TODO: Replace this handshake pause with a Ready callback to prevent a data race
time.Sleep(time.Second * 1)
// TODO: Refactor this sam.Send into a stream package w/a streamHandler and it's own connect message in that section
err := ss.Sam.Send(fmt.Sprintf("STREAM ACCEPT ID=%s SILENT=FALSE\n", ID))
return ss, err
}
func (ss *Subsession) newSessionHandler() {
var sessionReply sambridge.SessionReply
for reply := range ss.Sam.SessionReply {
sessionReply = reply
break
}
if sessionReply.Err != nil {
ss.Ready <- false
}
ss.Destination = sessionReply.Destination
ss.Ready <- true
}
func (ss *Subsession) Stop() error {
ss.Sam.Send(fmt.Sprintf("SESSION REMOVE ID=%s", ss.ID))
return nil
}
func (s *Subsession) Dial(network, addr string) (net.Conn, error) {
return s.Sam.Conn, nil
}