net_proxy/main.go
2023-04-01 18:07:44 +02:00

268 lines
6.6 KiB
Go
Executable file

package main
import "os"
import "encoding/json"
import "log"
import "crypto/tls"
import "net"
import "sync"
import "strconv"
import "strings"
import "github.com/emersion/go-sasl"
import "github.com/emersion/go-smtp"
import "io"
import "io/ioutil"
import "time"
import "errors"
// SMTP
type Backend struct {
cfg TRouteConfig
}
func (b *Backend) NewSession(_ *smtp.Conn) (smtp.Session, error) {
return &Session{ cfg : b.cfg }, nil
}
type Session struct {
username string
password string
from string
to string
data string
cfg TRouteConfig
}
func (s *Session) AuthPlain(username, password string) error {
s.username = username
s.password = password
return nil
}
func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
s.from = from
return nil
}
func (s *Session) Rcpt(to string) error {
s.to = to
return nil
}
func (s *Session) Data(r io.Reader) error {
if b, err := ioutil.ReadAll(r); err != nil {
return err
} else {
s.data += string(b)
return nil
}
}
func (s *Session) Reset() {
if Config.Is_verbose_log {
log.Println("smtp: ", s.username, " sends from ", s.from, " to ", s.to, " ", len(s.data), " bytes")
}
auth := sasl.NewPlainClient("", s.username, s.password)
to := []string{s.to}
err := smtp.SendMail(s.cfg.Destination + ":" + strconv.FormatUint(uint64(s.cfg.Port_out), 10), auth, s.from, to, strings.NewReader(s.data))
if err != nil {
log.Println("smtp: ", err)
}
}
func (s *Session) Logout() error {
s.username = ""
s.password = ""
s.from = ""
s.to = ""
s.data = ""
return nil
}
// TCP and general
type TRouteConfig struct {
Port_in uint16 `json:"port-in"`
Port_out uint16 `json:"port-out"`
Destination string `json:"destination"`
Type string `json:"type"`
}
type TConfig struct {
Routes []TRouteConfig `json:"routes"`
Is_verbose_log bool `json:"verbose-log"`
SMTP_data_limit int `json:"smtp-data-limit"`
SMTP_timeout int `json:"smtp-timeout"`
SMTP_max_recipients int `json:"smtp-max-recipients"`
}
type TState struct {
is_routing bool
}
var Config = TConfig {
Is_verbose_log : true,
}
var State = TState {
is_routing : false,
}
// functions
func load_config() {
content, err_file := os.ReadFile("./config.json")
if err_file != nil {
log.Fatal(err_file)
}
err_json := json.Unmarshal(content, &Config)
if err_json != nil {
log.Fatal(err_json)
}
}
func validate_config() {
/* no validation required at this point */
}
func is_net_closed(err error) bool {
switch {
case
errors.Is(err, net.ErrClosed),
errors.Is(err, io.EOF):
return true
default:
return false
}
}
func feedback_tcp(route TRouteConfig, in net.Conn, out net.Conn, wg sync.WaitGroup, is_active *bool) {
buffer := make([]byte, 1024)
defer wg.Done()
for State.is_routing {
n, err := out.Read(buffer)
if err != nil {
if is_net_closed(err) {
break
} else {
log.Println("tcp: ", err)
continue
}
} else {
if Config.Is_verbose_log {
log.Println("tcp: received ", n, " bytes")
}
if n > 0 {
_, err := in.Write(buffer[:n])
if err != nil {
if is_net_closed(err) {
break
} else {
log.Println("tcp: ", n, ": ", err)
continue
}
} else {
log.Println("tcp: relayed successfully")
}
}
}
}
log.Print("tcp: closing client")
*is_active = false
}
func handle_tcp(route TRouteConfig, in net.Conn, wg sync.WaitGroup) {
defer wg.Done()
defer in.Close()
tls_cfg := tls.Config{
PreferServerCipherSuites: true,
CipherSuites: []uint16 {
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
},
}
is_out_active := true
out, err := tls.Dial("tcp", route.Destination + ":" + strconv.FormatUint(uint64(route.Port_out), 10), &tls_cfg)
log.Print("tcp: opening client")
if err != nil {
log.Println(err)
return
}
defer out.Close()
wg.Add(1)
go feedback_tcp(route, in, out, wg, &is_out_active)
buffer := make([]byte, 1024)
for State.is_routing && is_out_active {
n, err := in.Read(buffer)
if err != nil {
log.Println("tcp: ", err)
return
}
if n > 0 {
_, err := out.Write(buffer[:n])
if err != nil {
log.Println("tcp: ", n, ": ", err)
return
}
}
}
}
func listen_route_tcp(route TRouteConfig, wg sync.WaitGroup) {
defer wg.Done()
server, err := net.Listen("tcp", ":" + strconv.FormatUint(uint64(route.Port_in), 10))
if err != nil {
log.Fatal(err)
}
defer server.Close()
var conwg sync.WaitGroup
for State.is_routing {
con, err := server.Accept()
if err != nil {
log.Println(err)
continue
}
conwg.Add(1)
go handle_tcp(route, con, conwg)
}
_ = server
conwg.Wait()
}
func listen_route_smtp(route TRouteConfig, wg sync.WaitGroup) {
defer wg.Done()
be := &Backend{ cfg : route }
s := smtp.NewServer(be)
s.Addr = ":" + strconv.FormatUint(uint64(route.Port_in), 10)
s.Domain = "localhost"
s.ReadTimeout = time.Duration(Config.SMTP_timeout) * time.Second
s.WriteTimeout = time.Duration(Config.SMTP_timeout) * time.Second
s.MaxMessageBytes = Config.SMTP_data_limit
s.MaxRecipients = Config.SMTP_max_recipients
s.AllowInsecureAuth = true
if err := s.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
func listen_route(route TRouteConfig, wg sync.WaitGroup) {
switch(route.Type) {
case "tcp":
listen_route_tcp(route, wg)
case "smtp":
listen_route_smtp(route, wg)
}
}
func run_listeners() {
var wg sync.WaitGroup
State.is_routing = true
if Config.Is_verbose_log {
log.Printf("starting %d routes", len(Config.Routes))
}
for _, route := range Config.Routes {
wg.Add(1)
go listen_route(route, wg)
}
wg.Wait()
}
func main() {
load_config()
validate_config()
run_listeners()
}