]> Sergey Matveev's repositories - uploader.git/commitdiff
UCSPI-TCP support v1.3.0
authorSergey Matveev <stargrave@stargrave.org>
Wed, 19 May 2021 19:10:51 +0000 (22:10 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Wed, 19 May 2021 19:10:51 +0000 (22:10 +0300)
README
main.go
service/uploader/run
ucspi.go [new file with mode: 0644]

diff --git a/README b/README
index 30e9c388c190be3d441bc8034e92bf6367f9093a..4f2abcba60381b68b0c92b2cfba300426a469233 100644 (file)
--- 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 f97f4df68647fcd4ce7b5a18a17d997577b2a86a..a6109acfb6345cffcf9dae244549398394ec5237 100644 (file)
--- 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:
 <pre>
 $ 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
 </pre>
 <form enctype="multipart/form-data" action="/upload/" method="post">
 <label for="file">File to upload:</label><br/>
@@ -68,9 +69,11 @@ $ b2sum -a blake2b somedata.tar.gpg # to verify checksum
 </form></body></html>`))
        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)
+               }
+       }
 }
index 087fe57dd5b179b01dcf00d64bcb62eef2dd6f8f..0685e178aab2b4c5d991e71dfd8acdb2959c516f 100755 (executable)
@@ -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 (file)
index 0000000..74c5b79
--- /dev/null
+++ b/ucspi.go
@@ -0,0 +1,103 @@
+/*
+uploader -- simplest form file uploader
+Copyright (C) 2018-2021 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+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()
+       }
+}