From 88e89726fc19cd0e722d2526cbbf28afc5571127 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Wed, 19 May 2021 22:10:51 +0300 Subject: [PATCH] UCSPI-TCP support --- README | 5 ++- main.go | 62 +++++++++++++++++++------- service/uploader/run | 2 +- ucspi.go | 103 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 17 deletions(-) create mode 100644 ucspi.go diff --git a/README b/README index 30e9c38..4f2abcb 100644 --- a/README +++ b/README @@ -11,10 +11,13 @@ You can upload files with curl: and verify integrity locally: - b2sum -a blake2b somedata.tar.gpg + b2sum somedata.tar.gpg You can enable mail notification with -notify-to and -notify-from options. It is advisable to run it under daemontools-like supervisor (http://cr.yp.to/daemontools.html). Example service/uploader provided. +You can also run it as UCSPI-TCP service (https://cr.yp.to/ucspi-tcp.html), +like in service/uploader example. + uploader is free software: see the file COPYING for copying conditions. diff --git a/main.go b/main.go index f97f4df..a6109ac 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,7 @@ import ( "os/exec" "strconv" "strings" + "sync" "time" "go.cypherpunks.ru/recfile" @@ -57,7 +58,7 @@ var ( Example command line usage:
 $ curl -F file=@somedata.tar.gpg [-F comment="optional comment"] http://.../upload/
-$ b2sum -a blake2b somedata.tar.gpg # to verify checksum
+$ b2sum somedata.tar.gpg # to verify BLAKE2b-512 checksum
 

@@ -68,9 +69,11 @@ $ b2sum -a blake2b somedata.tar.gpg # to verify checksum
`)) NotifyFromAddr *string NotifyToAddr *string + Jobs sync.WaitGroup ) func notify(tai, filename string, size int64, comment string) { + defer Jobs.Done() if *NotifyToAddr == "" { return } @@ -181,41 +184,70 @@ func upload(w http.ResponseWriter, r *http.Request) { } io.Copy(w, &rec) log.Println(r.RemoteAddr, tai, fnOrig, n, sum) + + rec.Reset() + wr = recfile.NewWriter(&rec) + if _, err = wr.WriteFields( + recfile.Field{Name: "Filename", Value: fnOrig}, + ); err != nil { + log.Println(r.RemoteAddr, tai, fnOrig, n, sum, err) + return + } + + var comment []byte p, err = mr.NextPart() if err != nil || p.FormName() != CommentFieldName { - go notify(fnOrig, tai, n, "") - return + goto Notify } - comment, err := ioutil.ReadAll(p) + comment, err = ioutil.ReadAll(p) if err != nil || len(comment) == 0 { - go notify(tai, fnOrig, n, "") - return + goto Notify } - ioutil.WriteFile(tai+".txt", comment, os.FileMode(0666)) + wr.WriteFieldMultiline("Comment", strings.Split(string(comment), "\n")) + +Notify: + Jobs.Add(1) go notify(tai, fnOrig, n, string(comment)) + ioutil.WriteFile(tai+".rec", rec.Bytes(), os.FileMode(0666)) } func main() { + doUCSPI := flag.Bool("ucspi", false, "Work as UCSPI-TCP service") bind := flag.String("bind", "[::]:8086", "Address to bind to") conns := flag.Int("conns", 2, "Maximal number of connections") NotifyFromAddr = flag.String("notify-from", "uploader@example.com", "Address notifications are sent to") NotifyToAddr = flag.String("notify-to", "", "Address notifications are sent from") flag.Parse() log.SetFlags(log.Lshortfile) - log.SetOutput(os.Stdout) + if !*doUCSPI { + log.SetOutput(os.Stdout) + } if len(*NotifyFromAddr) == 0 && len(*NotifyToAddr) > 0 { log.Fatalln("notify-from address can not be empty, if notify-to is set") } - ln, err := net.Listen("tcp", *bind) - if err != nil { - log.Fatalln(err) - } - log.Println("listening", *bind) - ln = netutil.LimitListener(ln, *conns) s := &http.Server{ ReadHeaderTimeout: 10 * time.Second, MaxHeaderBytes: 10 * (1 << 10), } + s.SetKeepAlivesEnabled(false) http.HandleFunc("/upload/", upload) - s.Serve(ln) + if *doUCSPI { + ln := &UCSPI{} + s.ConnState = connStater + err := s.Serve(ln) + if _, ok := err.(UCSPIAlreadyAccepted); !ok { + log.Fatalln(err) + } + Jobs.Wait() + } else { + ln, err := net.Listen("tcp", *bind) + if err != nil { + log.Fatalln(err) + } + log.Println("listening", *bind) + ln = netutil.LimitListener(ln, *conns) + if err = s.Serve(ln); err != nil { + log.Fatalln(err) + } + } } diff --git a/service/uploader/run b/service/uploader/run index 087fe57..0685e17 100755 --- a/service/uploader/run +++ b/service/uploader/run @@ -1,4 +1,4 @@ #!/bin/sh -e cd /path/to/incoming -exec setuidgid uploader /path/to/uploader -bind "[::]:8086" \ +exec setuidgid uploader tcpserver -DRH -l 0 ::0 8086 /path/to/uploader -ucspi \ -notify-from uploader@example.com -notify-to admin@example.com diff --git a/ucspi.go b/ucspi.go new file mode 100644 index 0000000..74c5b79 --- /dev/null +++ b/ucspi.go @@ -0,0 +1,103 @@ +/* +uploader -- simplest form file uploader +Copyright (C) 2018-2021 Sergey Matveev + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package main + +import ( + "net" + "net/http" + "os" + "time" +) + +type UCSPIAddr struct { + ip string + port string +} + +func (addr *UCSPIAddr) Network() string { return "tcp" } + +func (addr *UCSPIAddr) String() string { return addr.ip + ":" + addr.port } + +type UCSPIConn struct{} + +func (conn *UCSPIConn) Read(b []byte) (int, error) { return os.Stdin.Read(b) } + +func (conn *UCSPIConn) Write(b []byte) (int, error) { return os.Stdout.Write(b) } + +func (conn *UCSPIConn) Close() error { + if err := os.Stdin.Close(); err != nil { + return err + } + return os.Stdin.Close() +} + +func (conn *UCSPIConn) LocalAddr() net.Addr { + return &UCSPIAddr{ip: os.Getenv("TCPLOCALIP"), port: os.Getenv("TCPLOCALPORT")} +} + +func (conn *UCSPIConn) RemoteAddr() net.Addr { + return &UCSPIAddr{ip: os.Getenv("TCPREMOTEIP"), port: os.Getenv("TCPREMOTEPORT")} +} + +func (conn *UCSPIConn) SetDeadline(t time.Time) error { + if err := os.Stdin.SetReadDeadline(t); err != nil { + return err + } + return os.Stdout.SetWriteDeadline(t) +} + +func (conn *UCSPIConn) SetReadDeadline(t time.Time) error { + return os.Stdin.SetReadDeadline(t) +} + +func (conn *UCSPIConn) SetWriteDeadline(t time.Time) error { + return os.Stdout.SetWriteDeadline(t) +} + +type UCSPI struct{ accepted bool } + +type UCSPIAlreadyAccepted struct{} + +func (u UCSPIAlreadyAccepted) Error() string { + return "already accepted" +} + +func (ucspi *UCSPI) Accept() (net.Conn, error) { + if ucspi.accepted { + return nil, UCSPIAlreadyAccepted{} + } + ucspi.accepted = true + return &UCSPIConn{}, nil +} + +func (ucspi *UCSPI) Close() error { + return nil +} + +func (ucspi *UCSPI) Addr() net.Addr { + return &UCSPIAddr{ip: os.Getenv("TCPLOCALIP"), port: os.Getenv("TCPLOCALPORT")} +} + +func connStater(conn net.Conn, connState http.ConnState) { + switch connState { + case http.StateNew: + Jobs.Add(1) + case http.StateClosed: + Jobs.Done() + } +} -- 2.44.0