From 168de6871afdd60f2bc1c529170033ef56307d7c Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Fri, 24 Sep 2021 00:06:19 +0300 Subject: [PATCH] UCSPI-TCP support --- doc/index.texi | 3 ++ doc/ucspi.texi | 30 +++++++++++ main.go | 59 ++++++++++++++------- ucspi.go | 136 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 19 deletions(-) create mode 100644 doc/ucspi.texi create mode 100644 ucspi.go diff --git a/doc/index.texi b/doc/index.texi index fe2ad4e..93ee48a 100644 --- a/doc/index.texi +++ b/doc/index.texi @@ -52,6 +52,7 @@ but nearly all the code was rewritten. It has huge differences: @item No package overwriting ability (as PyPI does too) @item Graceful HTTP-server shutdown @item Atomic packages store on filesystem +@item @url{https://cr.yp.to/ucspi-tcp.html, UCSPI-TCP} compatible mode @end itemize Also it contains @file{contrib/pyshop2packages.sh} migration script for @@ -71,6 +72,7 @@ Please send questions, bug reports and patches to @url{gocheese@@cypherpunks.ru} * Install:: * Usage:: * Password authentication: Passwords. +* UCSPI-TCP:: * TLS support: TLS. * Storage format: Storage. @end menu @@ -78,6 +80,7 @@ Please send questions, bug reports and patches to @url{gocheese@@cypherpunks.ru} @include install.texi @include usage.texi @include passwords.texi +@include ucspi.texi @include tls.texi @include storage.texi diff --git a/doc/ucspi.texi b/doc/ucspi.texi new file mode 100644 index 0000000..56fb85f --- /dev/null +++ b/doc/ucspi.texi @@ -0,0 +1,30 @@ +@node UCSPI-TCP +@unnumbered UCSPI-TCP + +You can use GoCheese as UCSPI-TCP service. For example running it also +under @command{daemontools}: + +@example +# mkdir -p /var/service/.gocheese/log +# cd /var/service/.gocheese + +# cat > run < passwd ) & +umask 022 +mkdir -p packages +exec setuidgid gocheese tcpserver -DRH -l 0 ::0 8080 \ + gocheese -ucspi -passwd passwd +EOF + +# cat > log/run < + +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 ( + "io" + "net" + "net/http" + "os" + "sync" + "time" +) + +var ( + aLongTimeAgo = time.Unix(1, 0) + UCSPIJob sync.WaitGroup +) + +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 { + eof chan struct{} +} + +type ReadResult struct { + n int + err error +} + +func (conn *UCSPIConn) Read(b []byte) (int, error) { + c := make(chan ReadResult) + go func() { + n, err := os.Stdin.Read(b) + c <- ReadResult{n, err} + }() + select { + case res := <-c: + return res.n, res.err + case <-conn.eof: + return 0, io.EOF + } +} + +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.Stdout.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 { + // An ugly hack to forcefully terminate pending read. + // net/http calls SetReadDeadline(aLongTimeAgo), but file + // descriptors are not capable to exit immediately that way. + if t.Equal(aLongTimeAgo) { + conn.eof <- struct{}{} + } + 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 + conn := UCSPIConn{eof: make(chan struct{}, 1)} + return &conn, 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: + UCSPIJob.Add(1) + case http.StateClosed: + UCSPIJob.Done() + } +} -- 2.44.0