commit 2e0c27c9a1d912f7b6bac6049ba0f7a1937b2e1d Author: lair repo key Date: Sat Feb 23 21:21:30 2019 -0500 New upstream version 0.0~git20190224.6dc102d diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dde6ad3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 idk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fdf7d81 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ + +apt-transport-i2phttp: + go build \ + -a \ + -tags netgo \ + -ldflags '-w -extldflags "-static"' + +install: apt-transport-i2phttp + install -m755 apt-transport-i2phttp /usr/lib/apt/methods/i2p + +clean: + go clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..336a7a8 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +apt-transport-i2phttp, HTTP-based I2P Transport for apt +======================================================= + +This is a simple transport for downloading debian packages from a repository +over i2p. It uses the built-in HTTP proxy or one you configure. It's a +modified version of [diocles/apt-tranport-http-golang](https://github.com/diocles/apt-transport-http-golang), +a plain HTTP Transport for apt. + +Besides that, I think most would agree that it is simpler to use an apt +transport to detect when a package should be retrieved from an i2p service. +Especially in cases where the user is mixing packages from Tor, I2P, and +Clearnet sources, this process can become confusing and involve configuring +multiple applications along with apt. Instead, apt-transport-i2phttp works with +other apt transports like apt-transport-tor and even apt-transport-i2p(A SAM +based alternate i2p transport), requiring no configuration on the vast majority +of systems. + +To install it: +-------------- + +As long as you have an i2p router installed the http proxy should be enabled +by default. You can just: + + make build && sudo make install + +to install ./apt-transport-i2phttp to /usr/lib/apt/methods/i2p, requiring no +additional configuration. + +To use it: +--------- + +To add an eepSite to your sources.list, for example(This example site is down, +I'll have a new one for you to use shortly): + + deb i2p://http://wnhxwrq4fkn3cov6bnqsdaniubeo3625rmsm53yaz336bxvtiqeq.b32.i2p/deb-pkg rolling main diff --git a/main.go b/main.go new file mode 100644 index 0000000..30399a2 --- /dev/null +++ b/main.go @@ -0,0 +1,248 @@ +package main + +import ( + "bufio" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "strconv" + "strings" +) + +type Message struct { + Status string + StatusCode int + Header Header + Exit int +} + +type Header map[string][]string + +func (h Header) Add(key, value string) { + h[key] = append(h[key], value) +} + +func (h Header) Get(key string) string { + if value, ok := h[key]; ok { + if len(value) > 0 { + return value[0] + } + } + return "" +} + +func (m *Message) String() string { + s := []string{m.Status} + for k, values := range m.Header { + for _, v := range values { + s = append(s, k+": "+v) + } + } + return strings.Join(s, "\n") + "\n\n" +} + +func ReadInConfig() { + if os.Getenv("APT_TRANSPORT_I2PHTTP_CONF") == "" { + os.Setenv("APT_TRANSPORT_I2PHTTP_CONF", "/etc/apt-transport-i2phttp/i2p.conf") + } else { + os.Setenv("APT_TRANSPORT_I2PHTTP_CONF", os.Getenv("APT_TRANSPORT_I2PHTTP_CONF")) + } + if _, err := os.Stat(os.Getenv("APT_TRANSPORT_I2PHTTP_CONF")); os.IsNotExist(err) { + os.Setenv("I2P_HTTP_PROXY", "http://127.0.0.1:4444") + os.Setenv("HTTP_PROXY", "http://127.0.0.1:4444") + } else if err != nil { + log.Fatal(err) + } else { + bytes, err := ioutil.ReadFile(os.Getenv("APT_TRANSPORT_I2PHTTP_CONF")) + if err != nil { + log.Fatal(err) + } + host := "127.0.0.1" + port := "4444" + for _, val := range strings.Split(string(bytes), "\n") { + v := strings.Split(val, "=") + if len(v) == 2 { + if v[0] == "host" { + host = v[0] + } + if v[0] == "port" { + port = v[0] + } + } + } + os.Setenv("I2P_HTTP_PROXY", "http://"+host+":"+port) + os.Setenv("HTTP_PROXY", "http://"+host+":"+port) + } + if os.Getenv("I2P_HTTP_PROXY") == "" { + os.Setenv("I2P_HTTP_PROXY", "http://127.0.0.1:4444") + os.Setenv("HTTP_PROXY", "http://127.0.0.1:4444") + } else { + os.Setenv("HTTP_PROXY", os.Getenv("I2P_HTTP_PROXY")) + } +} + +func main() { + ReadInConfig() + c := make(chan *Message) + go output(c) + sendCapabilities(c) + + stdin := bufio.NewScanner(os.Stdin) + for stdin.Scan() { + line := stdin.Text() + if line == "" { + continue + } + s := strings.SplitN(line, " ", 2) + code, err := strconv.Atoi(s[0]) + if err != nil { + fmt.Println("Malformed message!") + os.Exit(100) + } + request := &Message{ + Status: line, + StatusCode: code, + Header: Header{}, + } + + for stdin.Scan() { + line := stdin.Text() + + if line == "" { + process(c, request) + break + } + s := strings.SplitN(line, ": ", 2) + request.Header.Add(s[0], s[1]) + } + } +} + +func output(c <-chan *Message) { + for { + m := <-c + os.Stdout.Write([]byte(m.String())) + if m.Exit != 0 { + os.Exit(m.Exit) + } + } +} + +func sendCapabilities(c chan<- *Message) { + caps := &Message{ + Status: "100 Capabilities", + Header: Header{}, + } + + caps.Header.Add("Version", "1.2") + caps.Header.Add("Pipeline", "true") + caps.Header.Add("Send-Config", "true") + + c <- caps +} + +func process(c chan<- *Message, m *Message) { + switch m.StatusCode { + case 600: + go fetch(c, m) + case 601: + // TODO: parse config? + default: + fail := &Message{ + Status: "401 General Failure", + Header: Header{}, + Exit: 100, + } + fail.Header.Add("Message", "Status code not implemented") + + c <- fail + } +} + +func fetch(c chan<- *Message, m *Message) { + uri := m.Header.Get("URI") + filename := m.Header.Get("Filename") + + // TODO: If-Modified-Since + + file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + c <- &Message{ + Status: "400 URI Failure", + Header: Header{ + "Message": []string{"Could not open file: " + err.Error()}, + "URI": []string{uri}, + }, + } + return + } + defer file.Close() + + // TODO: Fix bug with appending to existing files + // TODO: implement range requests if file already exists + + realURI := strings.TrimPrefix(uri, "i2p://") + + resp, err := http.Get(realURI) + if err != nil { + c <- &Message{ + Status: "400 URI Failure", + Header: Header{ + "Message": []string{"Could not fetch URI: " + err.Error()}, + "URI": []string{uri}, + }, + } + return + } + defer resp.Body.Close() + + started := &Message{ + Status: "200 URI Start", + Header: Header{ + "URI": []string{uri}, + }, + } + // TODO: add Last-Modified header + + c <- started + + md5Hash := md5.New() + sha1Hash := sha1.New() + sha256Hash := sha256.New() + sha512Hash := sha512.New() + + if _, err = io.Copy(io.MultiWriter(file, md5Hash, sha1Hash, sha256Hash, sha512Hash), resp.Body); err != nil { + c <- &Message{ + Status: "400 URI Failure", + Header: Header{ + "Message": []string{"Could not write file: " + err.Error()}, + "URI": []string{uri}, + }, + } + return + } + + success := &Message{ + Status: "201 URI Done", + Header: Header{}, + } + success.Header.Add("URI", uri) + success.Header.Add("Filename", filename) + // TODO Size, Last-Modified + md5Hex := hex.EncodeToString(md5Hash.Sum(nil)[:]) + success.Header.Add("MD5-Hash", md5Hex) + success.Header.Add("MD5Sum-Hash", md5Hex) + success.Header.Add("SHA1-Hash", hex.EncodeToString(sha1Hash.Sum(nil)[:])) + success.Header.Add("SHA256-Hash", hex.EncodeToString(sha256Hash.Sum(nil)[:])) + success.Header.Add("SHA512-Hash", hex.EncodeToString(sha512Hash.Sum(nil)[:])) + + c <- success +}