2022-02-22 21:45:32 -05:00
|
|
|
package i2pdotonion
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-02-28 22:46:49 -05:00
|
|
|
"crypto"
|
|
|
|
"crypto/ed25519"
|
2022-05-21 11:26:10 -04:00
|
|
|
"crypto/rand"
|
2022-02-28 19:59:47 -05:00
|
|
|
"embed"
|
2022-02-22 21:45:32 -05:00
|
|
|
"fmt"
|
2022-02-28 19:59:47 -05:00
|
|
|
"io/fs"
|
|
|
|
"io/ioutil"
|
2022-02-22 21:45:32 -05:00
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
2022-02-25 00:30:41 -05:00
|
|
|
"os"
|
2022-02-25 00:17:12 -05:00
|
|
|
"path"
|
|
|
|
"path/filepath"
|
2022-02-28 19:59:47 -05:00
|
|
|
"strings"
|
2022-02-22 21:45:32 -05:00
|
|
|
|
|
|
|
"github.com/cretz/bine/tor"
|
|
|
|
)
|
|
|
|
|
2022-02-28 21:15:35 -05:00
|
|
|
//go:embed www/*
|
2022-02-28 19:59:47 -05:00
|
|
|
var content embed.FS
|
2022-02-25 00:30:41 -05:00
|
|
|
|
2022-02-22 21:45:32 -05:00
|
|
|
type I2POnionService struct {
|
|
|
|
OnionService net.Listener
|
2022-02-25 00:17:12 -05:00
|
|
|
ServeDir string
|
2022-02-28 22:46:49 -05:00
|
|
|
Keys crypto.PrivateKey
|
2022-02-25 00:17:12 -05:00
|
|
|
}
|
|
|
|
|
2022-02-28 19:59:47 -05:00
|
|
|
func NewOnionService(dir string) (*I2POnionService, error) {
|
2022-02-28 21:15:35 -05:00
|
|
|
ios := &I2POnionService{
|
|
|
|
ServeDir: filepath.Join(dir, "www"),
|
|
|
|
}
|
2022-02-28 19:59:47 -05:00
|
|
|
if err := ios.UnpackSite(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-02-28 22:46:49 -05:00
|
|
|
|
|
|
|
if file, err := os.Stat(ios.KeysPath()); err == nil && file.Mode().IsRegular() {
|
|
|
|
ios.Keys, err = torKeys(ios.KeysPath())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2022-02-28 19:59:47 -05:00
|
|
|
return ios, nil
|
2022-02-22 21:45:32 -05:00
|
|
|
}
|
|
|
|
|
2022-05-21 11:26:10 -04:00
|
|
|
func torKeys(addr string) (ed25519.PrivateKey, error) {
|
|
|
|
//(crypto.PrivateKey, error) {
|
2022-02-28 22:46:49 -05:00
|
|
|
//log.Infof("Starting and registering onion service, please wait a couple of minutes...")
|
|
|
|
//t, err := tor.Start(nil, nil)
|
|
|
|
//if err != nil {
|
|
|
|
// log.Fatalf("Unable to start Tor: %v", err)
|
|
|
|
//}
|
|
|
|
var keys *ed25519.PrivateKey
|
|
|
|
if _, err := os.Stat(addr + ".tor.private"); os.IsNotExist(err) {
|
2022-05-21 11:26:10 -04:00
|
|
|
_, tkeys, err := ed25519.GenerateKey(rand.Reader)
|
2022-02-28 22:46:49 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Unable to generate onion service key, %s", err)
|
|
|
|
}
|
|
|
|
keys = &tkeys
|
|
|
|
f, err := os.Create(addr + ".tor.private")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Unable to create Tor keys file for writing, %s", err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
2022-05-21 11:26:10 -04:00
|
|
|
_, err = f.Write(tkeys.Seed())
|
2022-02-28 22:46:49 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Unable to write Tor keys to disk, %s", err)
|
|
|
|
}
|
|
|
|
} else if err == nil {
|
|
|
|
tkeys, err := ioutil.ReadFile(addr + ".tor.private")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Unable to read Tor keys from disk")
|
|
|
|
}
|
|
|
|
k := ed25519.NewKeyFromSeed(tkeys)
|
|
|
|
keys = &k
|
|
|
|
} else {
|
|
|
|
log.Fatalf("Unable to set up Tor keys, %s", err)
|
|
|
|
}
|
2022-05-21 11:26:10 -04:00
|
|
|
return *keys, nil
|
2022-02-28 22:46:49 -05:00
|
|
|
}
|
|
|
|
|
2022-02-22 21:45:32 -05:00
|
|
|
func (ios *I2POnionService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2022-02-25 00:17:12 -05:00
|
|
|
path := path.Clean(r.URL.Path)
|
|
|
|
if path == "/" {
|
|
|
|
path = "/index.html"
|
|
|
|
}
|
|
|
|
path = filepath.Join(ios.ServeDir, path)
|
2022-02-25 00:30:41 -05:00
|
|
|
finfo, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if finfo.IsDir() {
|
|
|
|
http.NotFound(w, r)
|
|
|
|
}
|
2022-02-25 00:17:12 -05:00
|
|
|
http.ServeFile(w, r, path)
|
2022-02-22 21:45:32 -05:00
|
|
|
}
|
|
|
|
|
2022-02-25 00:30:41 -05:00
|
|
|
func (ios *I2POnionService) StandardHTML() string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2022-02-22 21:45:32 -05:00
|
|
|
func (ios *I2POnionService) Listen(net, addr string) (net.Listener, error) {
|
|
|
|
if ios.OnionService != nil {
|
|
|
|
return ios.OnionService, nil
|
|
|
|
}
|
|
|
|
fmt.Println("Starting and registering onion service, please wait a couple of minutes...")
|
2022-02-28 23:54:11 -05:00
|
|
|
t, err := tor.Start(nil, &tor.StartConf{
|
2022-05-21 11:26:10 -04:00
|
|
|
RetainTempDataDir: false,
|
2022-02-28 23:54:11 -05:00
|
|
|
})
|
|
|
|
t.DeleteDataDirOnClose = true
|
2022-02-22 21:45:32 -05:00
|
|
|
if err != nil {
|
2022-02-28 22:46:49 -05:00
|
|
|
return nil, fmt.Errorf("Unable to start Tor: %v", err)
|
2022-02-22 21:45:32 -05:00
|
|
|
}
|
2022-02-28 22:46:49 -05:00
|
|
|
//var err error
|
|
|
|
listenCtx := context.Background()
|
|
|
|
// Create a v3 onion service to listen on any port but show as 6667
|
2022-05-21 11:26:10 -04:00
|
|
|
keys, err := torKeys(addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-02-28 22:46:49 -05:00
|
|
|
ios.OnionService, err = t.Listen(
|
|
|
|
listenCtx,
|
|
|
|
&tor.ListenConf{
|
|
|
|
Version3: true,
|
|
|
|
RemotePorts: []int{80},
|
2022-05-21 11:26:10 -04:00
|
|
|
Key: keys,
|
2022-02-28 22:46:49 -05:00
|
|
|
},
|
|
|
|
)
|
2022-05-21 11:26:10 -04:00
|
|
|
onionAddr := ios.OnionService.Addr()
|
|
|
|
if onionAddr == nil {
|
|
|
|
return nil, fmt.Errorf("Unable to get onion service address")
|
|
|
|
}
|
|
|
|
log.Printf("Onion service listening on %s", onionAddr)
|
|
|
|
ioutil.WriteFile("tor.public", []byte(onionAddr.String()), 0644)
|
2022-02-22 21:45:32 -05:00
|
|
|
if err != nil {
|
2022-02-28 22:46:49 -05:00
|
|
|
return nil, fmt.Errorf("Unable to listen on Tor: %v", err)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Unable to write Tor public key to disk, %s", err)
|
2022-02-22 21:45:32 -05:00
|
|
|
}
|
|
|
|
return ios.OnionService, nil
|
|
|
|
}
|
|
|
|
|
2022-02-28 22:46:49 -05:00
|
|
|
func (ios *I2POnionService) KeysPath() string {
|
|
|
|
return filepath.Join(filepath.Dir(filepath.Dir(ios.ServeDir)), "service.tor.private")
|
|
|
|
}
|
|
|
|
|
2022-02-25 00:17:12 -05:00
|
|
|
func (ios *I2POnionService) Serve(l net.Listener) error {
|
|
|
|
ios.OnionService = l
|
2022-02-28 21:15:35 -05:00
|
|
|
log.Printf("Serve: %s", ios.OnionService.Addr())
|
2022-02-25 00:17:12 -05:00
|
|
|
return http.Serve(ios.OnionService, ios)
|
|
|
|
}
|
|
|
|
|
2022-02-22 21:45:32 -05:00
|
|
|
func (ios *I2POnionService) ListenAndServe() error {
|
|
|
|
var err error
|
|
|
|
ios.OnionService, err = ios.Listen("", "")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-02-28 21:15:35 -05:00
|
|
|
log.Printf("ListenAndServe: %s", ios.OnionService.Addr())
|
2022-02-22 21:45:32 -05:00
|
|
|
return http.Serve(ios.OnionService, ios)
|
|
|
|
}
|
2022-02-28 19:59:47 -05:00
|
|
|
|
|
|
|
func (ios *I2POnionService) UnpackSite() error {
|
2022-02-28 21:15:35 -05:00
|
|
|
docroot := ios.ServeDir
|
2022-07-15 18:19:53 -04:00
|
|
|
fmt.Fprintf(os.Stderr, "UnpackSite: %s", docroot)
|
2022-02-28 19:59:47 -05:00
|
|
|
if dir, err := os.Stat(docroot); err == nil && dir.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
2022-02-28 21:15:35 -05:00
|
|
|
os.MkdirAll(docroot, 0755)
|
2022-02-28 19:59:47 -05:00
|
|
|
//unpack the contents to the docroot
|
|
|
|
return fs.WalkDir(content, ".", func(embedpath string, d fs.DirEntry, err error) error {
|
|
|
|
fp := filepath.Join(docroot)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if d.IsDir() {
|
2022-02-28 21:15:35 -05:00
|
|
|
os.MkdirAll(filepath.Join(fp, strings.Replace(embedpath, "www", "", -1)), 0755)
|
2022-02-28 19:59:47 -05:00
|
|
|
} else {
|
|
|
|
fullpath := path.Join(embedpath)
|
|
|
|
bytes, err := content.ReadFile(fullpath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-02-28 21:15:35 -05:00
|
|
|
unpack := filepath.Join(fp, strings.Replace(embedpath, "www", "", -1))
|
2022-02-28 19:59:47 -05:00
|
|
|
if err := ioutil.WriteFile(unpack, bytes, 0644); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|