├── .github
└── workflows
│ └── auto-assign.yml
├── .gitignore
├── .nojekyll
├── CONTRIBUTING.html
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── accept.go
├── auth.go
├── auth
└── main.go
├── client.go
├── client_test.go
├── conn.go
├── datagram.go
├── debian
├── changelog
├── compat
├── control
├── copyright
├── gbp.conf
├── rules
├── source
│ └── format
└── watch
├── dest.go
├── dial.go
├── go.mod
├── go.sum
├── i2plogo.png
├── index.html
├── naming.go
├── naming_test.go
├── options.go
├── options_test.go
├── raw.go
├── replyParser.go
├── replyParser_test.go
├── rw.go
├── rwc.go
├── samsocks
└── main.go
├── sessions.go
├── showhider.css
├── stream.go
└── style.css
/.github/workflows/auto-assign.yml:
--------------------------------------------------------------------------------
1 | name: Auto Assign
2 | on:
3 | issues:
4 | types: [opened]
5 | pull_request:
6 | types: [opened]
7 | jobs:
8 | run:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | issues: write
12 | pull-requests: write
13 | steps:
14 | - name: 'Auto-assign issue'
15 | uses: pozil/auto-assign-issue@v1
16 | with:
17 | repo-token:${{ secrets.GITHUB_TOKEN }}
18 | assignees: eyedeekay
19 | numOfAssignee: 1
20 |
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | itp-golang-github-eyedeekay-gosam.txt
24 | .pc
25 | deb/
26 | samsocks/samsocks
27 | *.private
28 | *.public.txt
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eyedeekay/goSam/dedd16f35293222759814648c3044148a3d884fd/.nojekyll
--------------------------------------------------------------------------------
/CONTRIBUTING.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | goSam
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
43 |
44 | /
45 |
46 |
47 | How to make contributions to goSam
48 |
49 |
50 | Welcome to goSam, the easy-to-use http client for i2p. We’re glad you’re here
51 | and interested in contributing. Here’s some help getting started.
52 |
53 |
54 | Table of Contents
55 |
56 |
57 |
58 | (1) Environment
59 |
60 |
61 | (2) Testing
62 |
63 |
64 | (3) Filing Issues/Reporting Bugs/Making Suggestions
65 |
66 |
67 | (4) Contributing Code/Style Guide
68 |
69 |
70 | (a) Adding i2cp and tunnel Options
71 |
72 |
73 | (b) Writing Tests
74 |
75 |
76 | © Style
77 |
78 |
79 | (d) Other kinds of modification?
80 |
81 |
82 |
83 |
84 | (5) Conduct
85 |
86 |
87 |
88 | (1) Environment
89 |
90 |
91 | goSam is a simple go library. You are free to use an IDE if you wish, but all
92 | that is required to build and test the library are a go compiler and the gofmt
93 | tool. Git is the version control system. All the files in the library are in a
94 | single root directory. Invoking go build from this directory not generate any
95 | files.
96 |
97 |
98 | (2) Testing
99 |
100 |
101 | Tests are implemented using the standard go “testing” library in files named
102 | “file_test.go,” so tests of the client go in client_test.go, name lookups
103 | in naming_test.go, et cetera. Everything that can be tested, should be tested.
104 |
105 |
106 | Testing is done by running
107 |
108 | go test
109 |
110 |
111 | More information about designing tests is below in the
112 |
113 | Contributing Code/Style Guide
114 |
115 | section below.
116 |
117 |
118 | (3) Filing issues/Reporting bugs/Making suggestions
119 |
120 |
121 | If you discover the library doing something you don’t think is right, please let
122 | us know! Just filing an issue here is OK.
123 |
124 |
125 | If you need to suggest a feature, we’re happy to hear from you too. Filing an
126 | issue will give us a place to discuss how it’s implemented openly and publicly.
127 |
128 |
129 | Please file an issue for your new code contributions in order to provide us with
130 | a place to discuss them for inclusion.
131 |
132 |
133 | (4) Contributing Code/Style Guide
134 |
135 |
136 | Welcome new coders. We have good news for you, this library is really easy to
137 | contribute to. The easiest contributions take the form of i2cp and tunnel
138 | options.
139 |
140 |
141 | (a) Adding i2cp and tunnel Options
142 |
143 |
144 | First, add a variable to store the state of your new option. For example, the
145 | existing variables are in the Client class
146 |
147 | here:
148 |
149 |
150 |
151 | i2cp and tunnel options are added in a highly uniform process of basically three
152 | steps. First, you create a functional argument in the options.go file, in the
153 | form:
154 |
155 | // SetOPTION sets $OPTION
156 | func SetOPTION(arg type) func(*Client) error { // arg type
157 | return func(c *Client) error { // pass a client to the inner function and declare error return function
158 | if arg == valid { // validate the argument
159 | c.option = s // set the variable to the argument value
160 | return nil // if option is set successfully return nil error
161 | }
162 | return fmt.Errorf("Invalid argument:" arg) // return a descriptive error if arg is invalid
163 | }
164 | }
165 |
166 |
167 |
168 | example
169 |
170 |
171 |
172 | Next, you create a getter which prepares the option. Regardless of the type of
173 | option that is set, these must return strings representing valid i2cp options.
174 |
175 | //return the OPTION as a string.
176 | func (c *Client) option() string {
177 | return fmt.Sprintf("i2cp.option=%d", c.option)
178 | }
179 |
180 |
181 |
182 | example
183 |
184 |
185 |
186 | Lastly, you’ll need to add it to the allOptions function and the
187 | Client.NewClient() function. To add it to allOptions, it looks like this:
188 |
189 | //return all options as string ready for passing to sendcmd
190 | func (c *Client) allOptions() string {
191 | return c.inlength() + " " +
192 | c.outlength() + " " +
193 | ... //other options removed from example for brevity
194 | c.option()
195 | }
196 |
197 | //return all options as string ready for passing to sendcmd
198 | func (c *Client) NewClient() (*Client, error) {
199 | return NewClientFromOptions(
200 | SetHost(c.host),
201 | SetPort(c.port),
202 | ... //other options removed from example for brevity
203 | SetCompression(c.compression),
204 | setlastaddr(c.lastaddr),
205 | setid(c.id),
206 | )
207 | }
208 |
209 |
210 |
211 | example
212 |
213 |
214 |
215 | (b) Writing Tests
216 |
217 |
218 | Before the feature can be added, you’ll need to add a test for it to
219 | options_test.go. To do this, just add your new option to the long TestOptions
220 | functions in options_test.go.
221 |
222 | func TestOptionHost(t *testing.T) {
223 | client, err := NewClientFromOptions(
224 | SetHost("127.0.0.1"),
225 | SetPort("7656"),
226 | ... //other options removed from example for brevity
227 | SetCloseIdleTime(300001),
228 | )
229 | if err != nil {
230 | t.Fatalf("NewClientFromOptions() Error: %q\n", err)
231 | }
232 | if result, err := client.validCreate(); err != nil {
233 | t.Fatalf(err.Error())
234 | } else {
235 | t.Log(result)
236 | }
237 | client.CreateStreamSession("")
238 | if err := client.Close(); err != nil {
239 | t.Fatalf("client.Close() Error: %q\n", err)
240 | }
241 | }
242 |
243 | func TestOptionPortInt(t *testing.T) {
244 | client, err := NewClientFromOptions(
245 | SetHost("127.0.0.1"),
246 | SetPortInt(7656),
247 | ... //other options removed from example for brevity
248 | SetUnpublished(true),
249 | )
250 | if err != nil {
251 | t.Fatalf("NewClientFromOptions() Error: %q\n", err)
252 | }
253 | if result, err := client.validCreate(); err != nil {
254 | t.Fatalf(err.Error())
255 | } else {
256 | t.Log(result)
257 | }
258 | client.CreateStreamSession("")
259 | if err := client.Close(); err != nil {
260 | t.Fatalf("client.Close() Error: %q\n", err)
261 | }
262 | }
263 |
264 |
265 |
266 | If any of these tasks fail, then the test should fail.
267 |
268 |
269 | © Style
270 |
271 |
272 | It’s pretty simple to make sure the code style is right, just run gofmt over it
273 | to adjust the indentation, and golint over it to ensure that your comments are
274 | of the correct form for the documentation generator.
275 |
276 |
277 | (d) Other kinds of modification?
278 |
279 |
280 | It may be useful to extend goSam in other ways. Since there’s not a
281 | one-size-fits-all uniform way of dealing with these kinds of changes, open an
282 | issue for discussion and
283 |
284 |
285 | (5) Conduct
286 |
287 |
288 | This is a small-ish, straightforward library intended to enable a clear
289 | technical task. We should be able to be civil with eachother, and give and
290 | accept criticism contructively and respectfully.
291 |
292 |
293 | This document was drawn from the examples given by Mozilla
294 |
295 | here
296 |
297 |
298 |
299 |
300 |
301 | Get the source code:
302 |
303 |
304 |
311 |
312 |
313 |
314 | Show license
315 |
316 |
317 |
318 |
The MIT License (MIT)
319 |
320 | Copyright (c) 2014 Henry
321 |
322 | Permission is hereby granted, free of charge, to any person obtaining a copy
323 | of this software and associated documentation files (the "Software"), to deal
324 | in the Software without restriction, including without limitation the rights
325 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
326 | copies of the Software, and to permit persons to whom the Software is
327 | furnished to do so, subject to the following conditions:
328 |
329 | The above copyright notice and this permission notice shall be included in all
330 | copies or substantial portions of the Software.
331 |
332 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
333 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
334 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
335 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
336 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
337 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
338 | SOFTWARE.
339 |
340 |
341 |
342 | Hide license
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
356 |
357 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | How to make contributions to goSam
2 | ==================================
3 |
4 | Welcome to goSam, the easy-to-use http client for i2p. We're glad you're here
5 | and interested in contributing. Here's some help getting started.
6 |
7 | Table of Contents
8 | -----------------
9 |
10 | * (1) Environment
11 | * (2) Testing
12 | * (3) Filing Issues/Reporting Bugs/Making Suggestions
13 | * (4) Contributing Code/Style Guide
14 | - (a) Adding i2cp and tunnel Options
15 | - (b) Writing Tests
16 | - (c) Style
17 | - (d) Other kinds of modification?
18 | * (5) Conduct
19 |
20 | ### (1) Environment
21 |
22 | goSam is a simple go library. You are free to use an IDE if you wish, but all
23 | that is required to build and test the library are a go compiler and the gofmt
24 | tool. Git is the version control system. All the files in the library are in a
25 | single root directory. Invoking go build from this directory not generate any
26 | files.
27 |
28 | ### (2) Testing
29 |
30 | Tests are implemented using the standard go "testing" library in files named
31 | "file\_test.go," so tests of the client go in client\_test.go, name lookups
32 | in naming\_test.go, et cetera. Everything that can be tested, should be tested.
33 |
34 | Testing is done by running
35 |
36 | go test
37 |
38 | More information about designing tests is below in the
39 | **Contributing Code/Style Guide** section below.
40 |
41 | ### (3) Filing issues/Reporting bugs/Making suggestions
42 |
43 | If you discover the library doing something you don't think is right, please let
44 | us know! Just filing an issue here is OK.
45 |
46 | If you need to suggest a feature, we're happy to hear from you too. Filing an
47 | issue will give us a place to discuss how it's implemented openly and publicly.
48 |
49 | Please file an issue for your new code contributions in order to provide us with
50 | a place to discuss them for inclusion.
51 |
52 | ### (4) Contributing Code/Style Guide
53 |
54 | Welcome new coders. We have good news for you, this library is really easy to
55 | contribute to. The easiest contributions take the form of i2cp and tunnel
56 | options.
57 |
58 | #### (a) Adding i2cp and tunnel Options
59 |
60 | First, add a variable to store the state of your new option. For example, the
61 | existing variables are in the Client class [here:](https://github.com/cryptix/goSam/blob/701d7fcf03ddb354262fe213163dcf6f202a24f1/client.go#L29)
62 |
63 | i2cp and tunnel options are added in a highly uniform process of basically three
64 | steps. First, you create a functional argument in the options.go file, in the
65 | form:
66 |
67 | ``` Go
68 | // SetOPTION sets $OPTION
69 | func SetOPTION(arg type) func(*Client) error { // arg type
70 | return func(c *Client) error { // pass a client to the inner function and declare error return function
71 | if arg == valid { // validate the argument
72 | c.option = s // set the variable to the argument value
73 | return nil // if option is set successfully return nil error
74 | }
75 | return fmt.Errorf("Invalid argument:" arg) // return a descriptive error if arg is invalid
76 | }
77 | }
78 | ```
79 |
80 | [example](https://github.com/cryptix/goSam/blob/701d7fcf03ddb354262fe213163dcf6f202a24f1/options.go#L187)
81 |
82 | Next, you create a getter which prepares the option. Regardless of the type of
83 | option that is set, these must return strings representing valid i2cp options.
84 |
85 | ``` Go
86 | //return the OPTION as a string.
87 | func (c *Client) option() string {
88 | return fmt.Sprintf("i2cp.option=%d", c.option)
89 | }
90 | ```
91 |
92 | [example](https://github.com/cryptix/goSam/blob/701d7fcf03ddb354262fe213163dcf6f202a24f1/options.go#L299)
93 |
94 | Lastly, you'll need to add it to the allOptions function and the
95 | Client.NewClient() function. To add it to allOptions, it looks like this:
96 |
97 | ``` Go
98 | //return all options as string ready for passing to sendcmd
99 | func (c *Client) allOptions() string {
100 | return c.inlength() + " " +
101 | c.outlength() + " " +
102 | ... //other options removed from example for brevity
103 | c.option()
104 | }
105 | ```
106 |
107 | ``` Go
108 | //return all options as string ready for passing to sendcmd
109 | func (c *Client) NewClient() (*Client, error) {
110 | return NewClientFromOptions(
111 | SetHost(c.host),
112 | SetPort(c.port),
113 | ... //other options removed from example for brevity
114 | SetCompression(c.compression),
115 | setlastaddr(c.lastaddr),
116 | setid(c.id),
117 | )
118 | }
119 | ```
120 |
121 | [example](https://github.com/cryptix/goSam/blob/701d7fcf03ddb354262fe213163dcf6f202a24f1/options.go#L333)
122 |
123 | #### (b) Writing Tests
124 |
125 | Before the feature can be added, you'll need to add a test for it to
126 | options_test.go. To do this, just add your new option to the long TestOptions
127 | functions in options_test.go.
128 |
129 | ``` Go
130 | func TestOptionHost(t *testing.T) {
131 | client, err := NewClientFromOptions(
132 | SetHost("127.0.0.1"),
133 | SetPort("7656"),
134 | ... //other options removed from example for brevity
135 | SetCloseIdleTime(300001),
136 | )
137 | if err != nil {
138 | t.Fatalf("NewClientFromOptions() Error: %q\n", err)
139 | }
140 | if result, err := client.validCreate(); err != nil {
141 | t.Fatalf(err.Error())
142 | } else {
143 | t.Log(result)
144 | }
145 | client.CreateStreamSession("")
146 | if err := client.Close(); err != nil {
147 | t.Fatalf("client.Close() Error: %q\n", err)
148 | }
149 | }
150 |
151 | func TestOptionPortInt(t *testing.T) {
152 | client, err := NewClientFromOptions(
153 | SetHost("127.0.0.1"),
154 | SetPortInt(7656),
155 | ... //other options removed from example for brevity
156 | SetUnpublished(true),
157 | )
158 | if err != nil {
159 | t.Fatalf("NewClientFromOptions() Error: %q\n", err)
160 | }
161 | if result, err := client.validCreate(); err != nil {
162 | t.Fatalf(err.Error())
163 | } else {
164 | t.Log(result)
165 | }
166 | client.CreateStreamSession("")
167 | if err := client.Close(); err != nil {
168 | t.Fatalf("client.Close() Error: %q\n", err)
169 | }
170 | }
171 |
172 | ```
173 |
174 | If any of these tasks fail, then the test should fail.
175 |
176 | #### (c) Style
177 |
178 | It's pretty simple to make sure the code style is right, just run gofmt over it
179 | to adjust the indentation, and golint over it to ensure that your comments are
180 | of the correct form for the documentation generator.
181 |
182 | #### (d) Other kinds of modification?
183 |
184 | It may be useful to extend goSam in other ways. Since there's not a
185 | one-size-fits-all uniform way of dealing with these kinds of changes, open an
186 | issue for discussion and
187 |
188 | ### (5) Conduct
189 |
190 | This is a small-ish, straightforward library intended to enable a clear
191 | technical task. We should be able to be civil with eachother, and give and
192 | accept criticism contructively and respectfully.
193 |
194 | This document was drawn from the examples given by Mozilla
195 | [here](mozillascience.github.io/working-open-workshop/contributing/)
196 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Henry
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | USER_GH=go-i2p
3 | VERSION=0.32.9
4 | packagename=gosam
5 |
6 | echo: fmt
7 | @echo "type make version to do release $(VERSION)"
8 |
9 | version:
10 | github-release release -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION) -d "version $(VERSION)"
11 |
12 | del:
13 | github-release delete -s $(GITHUB_TOKEN) -u $(USER_GH) -r $(packagename) -t v$(VERSION)
14 |
15 | tar:
16 | tar --exclude .git \
17 | --exclude .go \
18 | --exclude bin \
19 | --exclude examples \
20 | -cJvf ../$(packagename)_$(VERSION).orig.tar.xz .
21 |
22 | link:
23 | rm -f ../goSam
24 | ln -sf . ../goSam
25 |
26 | fmt:
27 | gofmt -w -s *.go */*.go
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | goSam
2 | =====
3 |
4 | A go library for using the [I2P](https://geti2p.net/en/) Simple Anonymous
5 | Messaging ([SAM version 3.0](https://geti2p.net/en/docs/api/samv3)) bridge. It
6 | has support for all streaming features SAM version 3.2.
7 |
8 | STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days. I am primarily maintaining functionality. This is widely used and easy to use, but thusfar, mostly by me. It sees a lot of testing and no breaking changes to the API are expected.
9 |
10 | [](https://goreportcard.com/report/github.com/go-i2p/gosam)
11 |
12 | ## Installation
13 | ```
14 | go get github.com/go-i2p/gosam
15 | ```
16 |
17 | ## Using it for HTTP Transport
18 |
19 | `Client.Dial` implements `net.Dial` so you can use go's library packages like http.
20 |
21 | ```go
22 | package main
23 |
24 | import (
25 | "io"
26 | "log"
27 | "net/http"
28 | "os"
29 |
30 | "github.com/go-i2p/gosam"
31 | )
32 |
33 | func main() {
34 | // create a default sam client
35 | sam, err := goSam.NewDefaultClient()
36 | checkErr(err)
37 |
38 | log.Println("Client Created")
39 |
40 | // create a transport that uses SAM to dial TCP Connections
41 | tr := &http.Transport{
42 | Dial: sam.Dial,
43 | }
44 |
45 | // create a client using this transport
46 | client := &http.Client{Transport: tr}
47 |
48 | // send a get request
49 | resp, err := client.Get("http://stats.i2p/")
50 | checkErr(err)
51 | defer resp.Body.Close()
52 |
53 | log.Printf("Get returned %+v\n", resp)
54 |
55 | // create a file for the response
56 | file, err := os.Create("stats.html")
57 | checkErr(err)
58 | defer file.Close()
59 |
60 | // copy the response to the file
61 | _, err = io.Copy(file, resp.Body)
62 | checkErr(err)
63 |
64 | log.Println("Done.")
65 | }
66 |
67 | func checkErr(err error) {
68 | if err != nil {
69 | log.Fatal(err)
70 | }
71 | }
72 |
73 | ```
74 |
75 | ### Using SAM by default, as a proxy for all HTTP Clients used by a Go application
76 |
77 | This will make the SAM transport dialer the default for all HTTP clients.
78 |
79 | ```go
80 | package main
81 |
82 | import (
83 | "io"
84 | "log"
85 | "net/http"
86 | "os"
87 |
88 | "github.com/go-i2p/gosam"
89 | )
90 |
91 | func main() {
92 | sam, err := goSam.NewDefaultClient()
93 | checkErr(err)
94 |
95 | log.Println("Client Created")
96 |
97 | // create a transport that uses SAM to dial TCP Connections
98 | httpClient := &http.Client{
99 | Transport: &http.Transport{
100 | Dial: sam.Dial,
101 | },
102 | }
103 |
104 | http.DefaultClient = httpClient
105 | return nil
106 | }
107 |
108 | func checkErr(err error) {
109 | if err != nil {
110 | log.Fatal(err)
111 | }
112 | }
113 | ```
114 |
115 | ## Using it as a SOCKS proxy
116 |
117 | `client` also implements a resolver compatible with
118 | [`getlantern/go-socks5`](https://github.com/getlantern/go-socks5),
119 | making it very easy to implement a SOCKS5 server.
120 |
121 | ```go
122 | package main
123 |
124 | import (
125 | "flag"
126 |
127 | "github.com/go-i2p/gosam"
128 | "github.com/getlantern/go-socks5"
129 | "log"
130 | )
131 |
132 | var (
133 | samaddr = flag.String("sam", "127.0.0.1:7656", "SAM API address to use")
134 | socksaddr = flag.String("socks", "127.0.0.1:7675", "SOCKS address to use")
135 | )
136 |
137 | func main() {
138 | sam, err := goSam.NewClient(*samaddr)
139 | if err != nil {
140 | panic(err)
141 | }
142 | log.Println("Client Created")
143 |
144 | // create a transport that uses SAM to dial TCP Connections
145 | conf := &socks5.Config{
146 | Dial: sam.DialContext,
147 | Resolver: sam,
148 | }
149 | server, err := socks5.New(conf)
150 | if err != nil {
151 | panic(err)
152 | }
153 |
154 | // Create SOCKS5 proxy on localhost port 8000
155 | if err := server.ListenAndServe("tcp", *socksaddr); err != nil {
156 | panic(err)
157 | }
158 | }
159 | ```
160 |
161 | ### .deb package
162 |
163 | A package for installing this on Debian is buildable, and a version for Ubuntu
164 | is available as a PPA and mirrored via i2p. To build the deb package, from the
165 | root of this repository with the build dependencies installed(git, i2p, go,
166 | debuild) run the command
167 |
168 | debuild -us -uc
169 |
170 | to produce an unsigned deb for personal use only. For packagers,
171 |
172 | debuild -S
173 |
174 | will produce a viable source package for use with Launchpad PPA's and other
175 | similar systems.
176 |
177 | ### TODO
178 |
179 | * Improve recovery on failed sockets
180 | * Implement `STREAM FORWARD`
181 | * Implement datagrams (Repliable and Anon)
182 |
183 |
184 |
--------------------------------------------------------------------------------
/accept.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | )
7 |
8 | // AcceptI2P creates a new Client and accepts a connection on it
9 | func (c *Client) AcceptI2P() (net.Conn, error) {
10 | listener, err := c.Listen()
11 | if err != nil {
12 | return nil, err
13 | }
14 | return listener.Accept()
15 | }
16 |
17 | // Listen creates a new Client and returns a net.listener which *must* be started
18 | // with Accept
19 | func (c *Client) Listen() (net.Listener, error) {
20 | return c.ListenI2P(c.destination)
21 | }
22 |
23 | // ListenI2P creates a new Client and returns a net.listener which *must* be started
24 | // with Accept
25 | func (c *Client) ListenI2P(dest string) (net.Listener, error) {
26 | var err error
27 | c.destination, err = c.CreateStreamSession(dest)
28 | d := c.destination
29 | if err != nil {
30 | return nil, err
31 | }
32 | fmt.Println("Listening on destination:", c.Base32()+".b32.i2p")
33 |
34 | c, err = c.NewClient(c.id)
35 | if err != nil {
36 | return nil, err
37 | }
38 | c.destination = d
39 |
40 | if c.debug {
41 | c.SamConn = WrapConn(c.SamConn)
42 | }
43 |
44 | return c, nil
45 | }
46 |
47 | // Accept accepts a connection on a listening gosam.Client(Implements net.Listener)
48 | // or, if the connection isn't listening yet, just calls AcceptI2P for compatibility
49 | // with older versions.
50 | func (c *Client) Accept() (net.Conn, error) {
51 | if c.id == 0 {
52 | return c.AcceptI2P()
53 | }
54 | resp, err := c.StreamAccept()
55 | if err != nil {
56 | return nil, err
57 | }
58 |
59 | fmt.Println("Accept Resp:", resp)
60 |
61 | return c.SamConn, nil
62 | }
63 |
--------------------------------------------------------------------------------
/auth.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import "fmt"
4 |
5 | // SetupAuth sends the AUTH ENABLE command and immediately sets up a new Username and
6 | // Password from the arguments
7 | func (c *Client) SetupAuth(user, password string) error {
8 | r, err := c.sendCmd("AUTH ENABLE\n")
9 | if err != nil {
10 | return err
11 | }
12 | if r.Topic != "AUTH" {
13 | return fmt.Errorf("SetupAuth Unknown Reply: %+v\n", r)
14 | }
15 | r, err = c.sendCmd("AUTH %s %s\n", user, password)
16 | if err != nil {
17 | return err
18 | }
19 | if r.Topic != "AUTH" {
20 | return fmt.Errorf("SetupAuth Unknown Reply: %+v\n", r)
21 | }
22 | return nil
23 | }
24 |
25 | // TeardownAuth sends the AUTH DISABLE command but does not remove the Username and
26 | // Password from the client PasswordManager
27 | func (c *Client) TeardownAuth() error {
28 | r, err := c.sendCmd("AUTH DISABLE\n")
29 | if err != nil {
30 | return err
31 | }
32 | if r.Topic != "AUTH" {
33 | return fmt.Errorf("TeardownAuth Unknown Reply: %+v\n", r)
34 | }
35 | return nil
36 | }
37 |
38 | func (c *Client) RemoveAuthUser(user string) error {
39 | r, err := c.sendCmd("AUTH REMOVE %s\n", user)
40 | if err != nil {
41 | return err
42 | }
43 | if r.Topic != "AUTH" {
44 | return fmt.Errorf("RemoveAuthUser Unknown Reply: %+v\n", r)
45 | }
46 | return nil
47 | }
48 |
--------------------------------------------------------------------------------
/auth/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/go-i2p/gosam"
8 | )
9 |
10 | /**
11 | THIS is a freestanding test for SAMv3.2 AUTH commands using gosam. It's
12 | intended to be run separate from the other tests so that you don't accidentally end
13 | up setting SAM session passwords and leaving them in the PasswordManager if a test
14 | fails for some reason before you can remove them.
15 | **/
16 |
17 | func main() {
18 | client, err := gosam.NewClientFromOptions()
19 | if err != nil {
20 | client, err = gosam.NewClientFromOptions(
21 | gosam.SetUser("user"),
22 | gosam.SetPass("password"),
23 | )
24 | fmt.Println("Looks like you restarted the I2P router before sending AUTH DISABLE.")
25 | fmt.Println("This probably means that your SAM Bridge is in a broken state where it can't")
26 | fmt.Println("accept HELLO or AUTH commands anymore. You should fix this by removing the")
27 | fmt.Println("sam.auth=true entry from sam.config.")
28 | err = client.TeardownAuth()
29 | if err != nil {
30 | fmt.Println(err)
31 | }
32 | fmt.Println(err)
33 | panic(err)
34 | }
35 | err = client.SetupAuth("user", "password")
36 | if err != nil {
37 | log.Println(err)
38 | }
39 | client2, err := gosam.NewDefaultClient()
40 | if err != nil {
41 | log.Println(err)
42 | }
43 | conn, err := client2.Dial("", "idk.i2p")
44 | if err != nil {
45 | fmt.Println(err)
46 | }
47 | conn.Close()
48 | err = client.RemoveAuthUser("user")
49 | if err != nil {
50 | panic(err)
51 | }
52 | //fmt.Println(r)
53 | err = client.TeardownAuth()
54 | if err != nil {
55 | panic(err)
56 | }
57 | //r, err = client.NewDestination()
58 | }
59 |
--------------------------------------------------------------------------------
/client.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "bufio"
5 | "crypto/sha256"
6 | "encoding/base32"
7 | "encoding/base64"
8 | "encoding/binary"
9 | "fmt"
10 | "math"
11 | "math/rand"
12 | "net"
13 | "strings"
14 | "sync"
15 | "time"
16 |
17 | "github.com/go-i2p/i2pkeys"
18 | //samkeys "github.com/go-i2p/gosam/compat"
19 | )
20 |
21 | // A Client represents a single Connection to the SAM bridge
22 | type Client struct {
23 | host string
24 | port string
25 | fromport string
26 | toport string
27 | user string
28 | pass string
29 |
30 | SamConn net.Conn // Control socket
31 | SamDGConn net.PacketConn // Datagram socket
32 | rd *bufio.Reader
33 | // d *Client
34 |
35 | sigType string
36 | destination string
37 |
38 | inLength uint
39 | inVariance int
40 | inQuantity uint
41 | inBackups uint
42 |
43 | outLength uint
44 | outVariance int
45 | outQuantity uint
46 | outBackups uint
47 |
48 | dontPublishLease bool
49 | encryptLease bool
50 | leaseSetEncType string
51 |
52 | reduceIdle bool
53 | reduceIdleTime uint
54 | reduceIdleQuantity uint
55 |
56 | closeIdle bool
57 | closeIdleTime uint
58 |
59 | compress bool
60 |
61 | debug bool
62 | mutex sync.Mutex
63 | //NEVER, EVER modify lastaddr or id yourself. They are used internally only.
64 | id int32
65 | sammin int
66 | sammax int
67 | }
68 |
69 | // SAMsigTypes is a slice of the available signature types
70 | var SAMsigTypes = []string{
71 | "SIGNATURE_TYPE=DSA_SHA1",
72 | "SIGNATURE_TYPE=ECDSA_SHA256_P256",
73 | "SIGNATURE_TYPE=ECDSA_SHA384_P384",
74 | "SIGNATURE_TYPE=ECDSA_SHA512_P521",
75 | "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519",
76 | }
77 |
78 | var ValidSAMCommands = []string{
79 | "HELLO",
80 | "SESSION",
81 | "STREAM",
82 | }
83 |
84 | var (
85 | i2pB64enc *base64.Encoding = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~")
86 | i2pB32enc *base32.Encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
87 | )
88 |
89 | // NewDefaultClient creates a new client, connecting to the default host:port at localhost:7656
90 | func NewDefaultClient() (*Client, error) {
91 | return NewClient("localhost:7656")
92 | }
93 |
94 | // NewClient creates a new client, connecting to a specified port
95 | func NewClient(addr string) (*Client, error) {
96 | return NewClientFromOptions(SetAddr(addr))
97 | }
98 |
99 | func NewID() int32 {
100 | id := rand.Int31n(math.MaxInt32)
101 | fmt.Printf("Initializing new ID: %d\n", id)
102 | return id
103 | }
104 |
105 | // NewID generates a random number to use as an tunnel name
106 | func (c *Client) NewID() int32 {
107 | if c.id == 0 {
108 | c.id = NewID()
109 | }
110 | return c.id
111 | }
112 |
113 | // Destination returns the full destination of the local tunnel
114 | func (c *Client) Destination() string {
115 | return c.destination
116 | }
117 |
118 | // Base32 returns the base32 of the local tunnel
119 | func (c *Client) Base32() string {
120 | // hash := sha256.New()
121 | b64, err := i2pB64enc.DecodeString(c.Base64())
122 | if err != nil {
123 | return ""
124 | }
125 | //hash.Write([]byte(b64))
126 | var s []byte
127 | for _, e := range sha256.Sum256(b64) {
128 | s = append(s, e)
129 | }
130 | return strings.ToLower(strings.Replace(i2pB32enc.EncodeToString(s), "=", "", -1))
131 | }
132 |
133 | func (c *Client) base64() []byte {
134 | if c.destination != "" {
135 | s, _ := i2pB64enc.DecodeString(c.destination)
136 | alen := binary.BigEndian.Uint16(s[385:387])
137 | return s[:387+alen]
138 | }
139 | return []byte("")
140 | }
141 |
142 | // Base64 returns the base64 of the local tunnel
143 | func (c *Client) Base64() string {
144 | return i2pB64enc.EncodeToString(c.base64())
145 | }
146 |
147 | // NewClientFromOptions creates a new client, connecting to a specified port
148 | func NewClientFromOptions(opts ...func(*Client) error) (*Client, error) {
149 | var c Client
150 | c.host = "127.0.0.1"
151 | c.port = "7656"
152 | c.inLength = 3
153 | c.inVariance = 0
154 | c.inQuantity = 3
155 | c.inBackups = 1
156 | c.outLength = 3
157 | c.outVariance = 0
158 | c.outQuantity = 3
159 | c.outBackups = 1
160 | c.dontPublishLease = true
161 | c.encryptLease = false
162 | c.reduceIdle = false
163 | c.reduceIdleTime = 300000
164 | c.reduceIdleQuantity = 1
165 | c.closeIdle = true
166 | c.closeIdleTime = 600000
167 | c.debug = false
168 | c.sigType = SAMsigTypes[4]
169 | c.id = 0
170 | c.destination = ""
171 | c.leaseSetEncType = "4,0"
172 | c.fromport = ""
173 | c.toport = ""
174 | c.sammin = 0
175 | c.sammax = 1
176 | for _, o := range opts {
177 | if err := o(&c); err != nil {
178 | return nil, err
179 | }
180 | }
181 | c.id = c.NewID()
182 | conn, err := net.DialTimeout("tcp", c.samaddr(), 15*time.Minute)
183 | if err != nil {
184 | return nil, err
185 | }
186 | if c.debug {
187 | conn = WrapConn(conn)
188 | }
189 | c.SamConn = conn
190 | c.rd = bufio.NewReader(conn)
191 | return &c, c.hello()
192 | }
193 |
194 | // ID returns a the current ID of the client as a string
195 | func (p *Client) ID() string {
196 | return fmt.Sprintf("%d", p.NewID())
197 | }
198 |
199 | // Addr returns the address of the client as a net.Addr
200 | func (p *Client) Addr() net.Addr {
201 | keys := i2pkeys.I2PAddr(p.Destination())
202 | return keys
203 | }
204 |
205 | func (p *Client) LocalAddr() net.Addr {
206 | return p.Addr()
207 | }
208 |
209 | // LocalKeys returns the local keys of the client as a fully-fledged i2pkeys.I2PKeys
210 | func (p *Client) PrivateAddr() i2pkeys.I2PKeys {
211 | //keys := i2pkeys.I2PAddr(p.Destination())
212 | keys := i2pkeys.NewKeys(i2pkeys.I2PAddr(p.base64()), p.Destination())
213 | return keys
214 | }
215 |
216 | // return the combined host:port of the SAM bridge
217 | func (c *Client) samaddr() string {
218 | return fmt.Sprintf("%s:%s", c.host, c.port)
219 | }
220 |
221 | // send the initial handshake command and check that the reply is ok
222 | func (c *Client) hello() error {
223 |
224 | r, err := c.sendCmd("HELLO VERSION MIN=3.%d MAX=3.%d %s %s\n", c.sammin, c.sammax, c.getUser(), c.getPass())
225 | if err != nil {
226 | return err
227 | }
228 |
229 | if r.Topic != "HELLO" {
230 | return fmt.Errorf("Client Hello Unknown Reply: %+v\n", r)
231 | }
232 |
233 | if r.Pairs["RESULT"] != "OK" {
234 | return fmt.Errorf("Handshake did not succeed\nReply:%+v\n", r)
235 | }
236 |
237 | return nil
238 | }
239 |
240 | // helper to send one command and parse the reply by sam
241 | func (c *Client) sendCmd(str string, args ...interface{}) (*Reply, error) {
242 | if _, err := fmt.Fprintf(c.SamConn, str, args...); err != nil {
243 | return nil, err
244 | }
245 |
246 | line, err := c.rd.ReadString('\n')
247 | if err != nil {
248 | return nil, err
249 | }
250 |
251 | return parseReply(line)
252 | }
253 |
254 | // Close the underlying socket to SAM
255 | func (c *Client) Close() error {
256 | c.rd = nil
257 | return c.SamConn.Close()
258 | }
259 |
260 | // NewClient generates an exact copy of the client with the same options, but
261 | // re-does all the handshaky business so that Dial can pick up right where it
262 | // left off, should the need arise.
263 | func (c *Client) NewClient(id int32) (*Client, error) {
264 | return NewClientFromOptions(
265 | SetHost(c.host),
266 | SetPort(c.port),
267 | SetDebug(c.debug),
268 | SetInLength(c.inLength),
269 | SetOutLength(c.outLength),
270 | SetInVariance(c.inVariance),
271 | SetOutVariance(c.outVariance),
272 | SetInQuantity(c.inQuantity),
273 | SetOutQuantity(c.outQuantity),
274 | SetInBackups(c.inBackups),
275 | SetOutBackups(c.outBackups),
276 | SetUnpublished(c.dontPublishLease),
277 | SetEncrypt(c.encryptLease),
278 | SetReduceIdle(c.reduceIdle),
279 | SetReduceIdleTime(c.reduceIdleTime),
280 | SetReduceIdleQuantity(c.reduceIdleQuantity),
281 | SetCloseIdle(c.closeIdle),
282 | SetCloseIdleTime(c.closeIdleTime),
283 | SetCompression(c.compress),
284 | setid(id),
285 | )
286 | }
287 |
--------------------------------------------------------------------------------
/client_test.go:
--------------------------------------------------------------------------------
1 | //go:build nettest
2 | // +build nettest
3 |
4 | package gosam
5 |
6 | import "testing"
7 |
8 | import (
9 | "fmt"
10 | // "math"
11 | // "math/rand"
12 | // "time"
13 | //"log"
14 | "net/http"
15 | //"github.com/go-i2p/sam3/helper"
16 | //"github.com/go-i2p/i2pkeys"
17 | )
18 |
19 | func HelloServer(w http.ResponseWriter, r *http.Request) {
20 | fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
21 | }
22 |
23 | /*func TestCompositeClient(t *testing.T) {
24 | listener, err := sam.I2PListener("testservice"+fmt.Sprintf("%d", rand.Int31n(math.MaxInt32)), "127.0.0.1:7656", "testkeys")
25 | if err != nil {
26 | t.Fatalf("Listener() Error: %q\n", err)
27 | }
28 | defer listener.Close()
29 | http.HandleFunc("/", HelloServer)
30 | go http.Serve(listener, nil)
31 |
32 | listener2, err := sam.I2PListener("testservice"+fmt.Sprintf("%d", rand.Int31n(math.MaxInt32)), "127.0.0.1:7656", "testkeys2")
33 | if err != nil {
34 | t.Fatalf("Listener() Error: %q\n", err)
35 | }
36 | defer listener2.Close()
37 | // http.HandleFunc("/", HelloServer)
38 | go http.Serve(listener2, nil)
39 |
40 | listener3, err := sam.I2PListener("testservice"+fmt.Sprintf("%d", rand.Int31n(math.MaxInt32)), "127.0.0.1:7656", "testkeys3")
41 | if err != nil {
42 | t.Fatalf("Listener() Error: %q\n", err)
43 | }
44 | defer listener3.Close()
45 | // http.HandleFunc("/", HelloServer)
46 | go http.Serve(listener3, nil)
47 |
48 | sam, err := NewClientFromOptions(SetDebug(false))
49 | if err != nil {
50 | t.Fatalf("NewDefaultClient() Error: %q\n", err)
51 | }
52 | tr := &http.Transport{
53 | Dial: sam.Dial,
54 | }
55 | client := &http.Client{Transport: tr}
56 | defer sam.Close()
57 | x := 0
58 | for x < 15 {
59 | time.Sleep(time.Second * 2)
60 | t.Log("waiting a little while for services to register", (30 - (x * 2)))
61 | x++
62 | }
63 | go func() {
64 | resp, err := client.Get("http://" + listener.Addr().(i2pkeys.I2PAddr).Base32())
65 | if err != nil {
66 | t.Fatalf("Get Error test 1: %q\n", err)
67 | }
68 | defer resp.Body.Close()
69 | }()
70 | //time.Sleep(time.Second * 15)
71 | go func() {
72 | resp, err := client.Get("http://" + listener2.Addr().(i2pkeys.I2PAddr).Base32())
73 | if err != nil {
74 | t.Fatalf("Get Error test 2: %q\n", err)
75 | }
76 | defer resp.Body.Close()
77 | }()
78 | //time.Sleep(time.Second * 15)
79 | go func() {
80 | resp, err := client.Get("http://" + listener3.Addr().(i2pkeys.I2PAddr).Base32())
81 | if err != nil {
82 | t.Fatalf("Get Error test 3: %q\n", err)
83 | }
84 | defer resp.Body.Close()
85 | }()
86 |
87 | time.Sleep(time.Second * 45)
88 | }*/
89 |
90 | func TestClientHello(t *testing.T) {
91 | client, err := NewClientFromOptions(SetDebug(false))
92 | if err != nil {
93 | t.Fatalf("NewDefaultClient() Error: %q\n", err)
94 | }
95 | t.Log(client.Base32())
96 | if err := client.Close(); err != nil {
97 | t.Fatalf("client.Close() Error: %q\n", err)
98 | }
99 | }
100 |
101 | func TestNewDestination(t *testing.T) {
102 | client, err := NewClientFromOptions(SetDebug(false))
103 | if err != nil {
104 | t.Fatalf("NewDefaultClient() Error: %q\n", err)
105 | }
106 | t.Log(client.Base32())
107 | if s, p, err := client.NewDestination(SAMsigTypes[3]); err != nil {
108 | t.Error(err)
109 | } else {
110 | t.Log(s, p)
111 | }
112 | if err := client.Close(); err != nil {
113 | t.Fatalf("client.Close() Error: %q\n", err)
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/conn.go:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2014 Henry
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | package gosam
26 |
27 | import (
28 | "log"
29 | "net"
30 | "time"
31 | )
32 |
33 | // Conn Read data from the connection, writes data to te connection
34 | // and logs the data in-between.
35 | type Conn struct {
36 | RWC
37 | conn net.Conn
38 | }
39 |
40 | // WrapConn wraps a net.Conn in a Conn.
41 | func WrapConn(c net.Conn) *Conn {
42 | wrap := Conn{
43 | conn: c,
44 | }
45 | wrap.Reader = NewReadLogger("<", c)
46 | wrap.Writer = NewWriteLogger(">", c)
47 | wrap.RWC.c = c
48 | return &wrap
49 | }
50 |
51 | // LocalAddr returns the local address of the connection.
52 | func (c *Conn) LocalAddr() net.Addr {
53 | return c.conn.LocalAddr()
54 | }
55 |
56 | // RemoteAddr returns the remote address of the connection.
57 | func (c *Conn) RemoteAddr() net.Addr {
58 | return c.conn.RemoteAddr()
59 | }
60 |
61 | // SetDeadline sets the read and write deadlines associated with the connection
62 | func (c *Conn) SetDeadline(t time.Time) error {
63 | log.Println("WARNING: SetDeadline() not sure this works")
64 | return c.conn.SetDeadline(t)
65 | }
66 |
67 | // SetReadDeadline sets the read deadline associated with the connection
68 | func (c *Conn) SetReadDeadline(t time.Time) error {
69 | log.Println("WARNING: SetReadDeadline() not sure this works")
70 | return c.conn.SetReadDeadline(t)
71 | }
72 |
73 | // SetWriteDeadline sets the write deadline associated with the connection
74 | func (c *Conn) SetWriteDeadline(t time.Time) error {
75 | log.Println("WARNING: SetWriteDeadline() not sure this works")
76 | return c.conn.SetWriteDeadline(t)
77 | }
78 |
--------------------------------------------------------------------------------
/datagram.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "net"
5 | "time"
6 | )
7 |
8 | // DatagramConn
9 | type DatagramConn struct {
10 | RWC
11 | conn net.PacketConn
12 | RAddr net.Addr
13 | }
14 |
15 | // WrapConn wraps a net.PacketConn in a DatagramConn.
16 | func WrapPacketConn(c net.Conn) *Conn {
17 | wrap := Conn{
18 | conn: c,
19 | }
20 | wrap.Reader = NewReadLogger("<", c)
21 | wrap.Writer = NewWriteLogger(">", c)
22 | wrap.RWC.c = c
23 | return &wrap
24 | }
25 |
26 | func (d *DatagramConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
27 | return d.conn.ReadFrom(p)
28 | }
29 | func (d *DatagramConn) Read(b []byte) (n int, err error) {
30 | n, _, err = d.ReadFrom(b)
31 | return n, err
32 | }
33 | func (d *DatagramConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
34 | return d.conn.WriteTo(p, addr)
35 | }
36 | func (d *DatagramConn) Write(b []byte) (n int, err error) {
37 | n, err = d.WriteTo(b, d.RemoteAddr())
38 | return n, err
39 | }
40 | func (d *DatagramConn) Close() error {
41 | return d.conn.Close()
42 | }
43 | func (d *DatagramConn) LocalAddr() net.Addr {
44 | return d.conn.LocalAddr()
45 | }
46 | func (d *DatagramConn) RemoteAddr() net.Addr {
47 | return d.RAddr
48 | }
49 | func (d *DatagramConn) SetDeadline(t time.Time) error {
50 | return d.conn.SetDeadline(t)
51 | }
52 | func (d *DatagramConn) SetReadDeadline(t time.Time) error {
53 | return d.conn.SetReadDeadline(t)
54 | }
55 | func (d *DatagramConn) SetWriteDeadline(t time.Time) error {
56 | return d.conn.SetWriteDeadline(t)
57 | }
58 |
59 | var dgt net.PacketConn = &DatagramConn{}
60 |
61 | //func (c *Client) DatagramSend()
62 |
--------------------------------------------------------------------------------
/debian/changelog:
--------------------------------------------------------------------------------
1 | golang-github-eyedeekay-gosam (0.32.30) UNRELEASED; urgency=medium
2 |
3 | * Improve the defaults
4 | * Handle errors more correctly
5 | * Gracefully handle hangups by creating a new session
6 | * Set empty destinations to TRANSIENT when dialing
7 |
8 | -- idk Sun, 03 Nov 2020 16:12:04 -0500
9 |
10 | golang-github-eyedeekay-gosam (0.32.29) UNRELEASED; urgency=medium
11 |
12 | * Maintenance updates
13 |
14 | -- idk Mon, 23 Nov 2020 20:40:40 -0500
15 |
16 | golang-github-eyedeekay-gosam (0.32.28) UNRELEASED; urgency=medium
17 |
18 | * Maintenance updates
19 |
20 | -- idk Thu, 12 Sept 2020 22:44:27 -0500
21 |
22 | golang-github-eyedeekay-gosam (0.32.27) UNRELEASED; urgency=medium
23 |
24 | * Add a Resolve function to fulfill SOCKS5 proxy requirements
25 |
26 | -- idk Sun, 13 Sept 2020 04:48:27 -0500
27 |
28 | golang-github-eyedeekay-gosam (0.32.26) UNRELEASED; urgency=medium
29 |
30 | * Fix mistaken-identity issue with listeners
31 |
32 | -- idk Thu, 03 Sept 2020 08:17:40 -0500
33 |
34 | golang-github-eyedeekay-gosam (0.32.25) UNRELEASED; urgency=medium
35 |
36 | * Support dual-keys by default in all future versions
37 |
38 | -- idk Thu, 03 Sept 2020 04:25:04 -0500
39 |
40 | golang-github-eyedeekay-gosam (0.32.24) UNRELEASED; urgency=medium
41 |
42 | * Improve the mutex thingy
43 |
44 | -- idk Tue, 25 Aug 2020 04:52:11 -0500
45 |
46 | golang-github-eyedeekay-gosam (0.32.23) UNRELEASED; urgency=medium
47 |
48 | * Protect Dial with a mutex to fix a lookup bug
49 |
50 | -- idk Tue, 25 Aug 2020 10:29:26 -0500
51 |
52 | golang-github-eyedeekay-gosam (0.3.2.1) bionic; urgency=medium
53 |
54 | * Get rid of the debug directory, just move it into the source
55 | * Get rid of the old example, just use the one in the README
56 |
57 | -- idk Sat, 08 Dec 2019 19:11:41 -0500
58 |
59 | golang-github-eyedeekay-gosam (0.3.2.0) bionic; urgency=medium
60 |
61 | * Enable persistent destinations
62 | * Make Base32 and Base64 addresses retrievable from client
63 | * bug fixes
64 |
65 | -- idk Fri, 18 May 2019 18:12:21 -0500
66 |
67 | golang-github-eyedeekay-gosam (0.1.1) bionic; urgency=medium
68 |
69 | * Incorporate all the recent bug-fixes and improvements and stabilize.
70 |
71 | -- idk Fri, 15 Mar 2019 14:46:21 -0500
72 |
73 | golang-github-eyedeekay-gosam (0.1.0+git20190221.2896c83ubuntu1+nmu2ubuntu1) bionic; urgency=medium
74 |
75 | * only run the offline tests by default
76 |
77 | -- idk Thu, 28 Feb 2019 20:35:51 -0500
78 |
79 | golang-github-eyedeekay-gosam (0.1.0+git20190221.2896c83ubuntu1+nmu2) bionic; urgency=medium
80 |
81 | [ idk ]
82 | * incorporate cryptix-debug without the counter
83 | * incorporate cryptix-debug without the counter
84 | * fix module
85 |
86 | -- idk Thu, 28 Feb 2019 20:32:58 -0500
87 |
88 | golang-github-eyedeekay-gosam (0.1.0+git20190221.2896c83ubuntu1+nmu1) bionic; urgency=medium
89 |
90 | * add sid
91 | *
92 |
93 | -- idk Thu, 28 Feb 2019 18:52:01 -0500
94 |
95 | golang-github-eyedeekay-gosam (0.1.0+git20190221.2896c83ubuntu1) bionic; urgency=medium
96 |
97 | [ idk ]
98 | * Update changelog for 0.1.0+git20190221.2896c83-1 release
99 | * add signature type support
100 | * Don't check version if handshake succeeds, just accept that OK is OK
101 | * Don't check version if handshake succeeds, just accept that OK is OK
102 | * make dialer context aware and avoid redundant session create's
103 | * make dialer context aware and avoid redundant session create's
104 | * correct CONTRIBUTING.md
105 | * correct CONTRIBUTING.md
106 | * correct CONTRIBUTING.md
107 | * explicitly et invalid initial lastaddr
108 | * remove 'commented-out' id
109 | * Initial release (Closes: TODO)
110 |
111 | [ idk ]
112 | * add bionic
113 |
114 | -- idk Thu, 28 Feb 2019 18:51:23 -0500
115 |
--------------------------------------------------------------------------------
/debian/compat:
--------------------------------------------------------------------------------
1 | 11
2 |
--------------------------------------------------------------------------------
/debian/control:
--------------------------------------------------------------------------------
1 | Source: golang-github-eyedeekay-gosam
2 | Section: devel
3 | Priority: optional
4 | Maintainer: idk
5 | Uploaders: idk
6 | Build-Depends: debhelper (>= 11),
7 | dh-golang,
8 | golang-any,
9 | i2pd | i2p,
10 | git,
11 | Standards-Version: 4.2.1
12 | Homepage: https://github.com/eyedeekay/gosam
13 | Vcs-Browser: https:/github.com/eyedeekay/gosam
14 | Vcs-Git: https://github.com/eyedeekay/gosam.git
15 | XS-Go-Import-Path: github.com/eyedeekay/gosam
16 | Testsuite: autopkgtest-pkg-go
17 | Launchpad-Bugs-Fixed: #1818159
18 |
19 | Package: golang-github-eyedeekay-gosam-dev
20 | Architecture: all
21 | Depends: ${misc:Depends},
22 | i2pd | i2p
23 | Description: A go library for using the I2P (https://geti2p.net/en/)
24 | Simple Anonymous Messaging (SAM version 3.1
25 | (https://geti2p.net/en/docs/api/samv3)) bridge
26 | .
27 | Launchpad-Bugs-Fixed: #1818159
28 |
--------------------------------------------------------------------------------
/debian/copyright:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Upstream-Name: gosam
3 | Source: https://github.com/eyedeekay/gosam
4 | Files-Excluded:
5 | Godeps/_workspace
6 |
7 | Files: *
8 | Copyright: 2014 cryptix
9 | Copyright: 2018 idk
10 | License: GPL-2.0
11 |
12 | Files: debug
13 | Copyright: 2014 cryptix
14 | LICENSE: MIT
15 |
16 | Files: debian/*
17 | Copyright: 2019 idk
18 | License: GPL-2.0
19 | Comment: Debian packaging is licensed under the same terms as upstream
20 |
21 | License: GPL-2.0
22 | TODO
23 |
--------------------------------------------------------------------------------
/debian/gbp.conf:
--------------------------------------------------------------------------------
1 | [DEFAULT]
2 | pristine-tar = False
3 |
--------------------------------------------------------------------------------
/debian/rules:
--------------------------------------------------------------------------------
1 | #!/usr/bin/make -f
2 |
3 | %:
4 | dh $@ --buildsystem=golang --with=golang
5 |
--------------------------------------------------------------------------------
/debian/source/format:
--------------------------------------------------------------------------------
1 | 3.0 (native)
2 |
--------------------------------------------------------------------------------
/debian/watch:
--------------------------------------------------------------------------------
1 | version=4
2 | opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/golang-github-eyedeekay-gosam-\$1\.tar\.gz/,\
3 | uversionmangle=s/(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$/\$1~\$2\$3/ \
4 | https://github.com/eyedeekay/gosam/tags .*/v?(\d\S*)\.tar\.gz
5 |
--------------------------------------------------------------------------------
/dest.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | func validateKindInner(kind string) string {
10 | if strings.HasPrefix(kind, "SIGNATURE_TYPE=") {
11 | return kind
12 | }
13 | return "SIGNATURE_TYPE=" + kind
14 | }
15 |
16 | func validateKind(kind string) (string, error) {
17 | //convert kind to int
18 | kint, err := strconv.Atoi(kind)
19 | if err != nil {
20 | for _, k := range SAMsigTypes {
21 | if strings.HasSuffix(k, kind) {
22 | return validateKindInner(kind), nil
23 | }
24 | }
25 | }
26 | if kint >= 0 && kint <= 7 {
27 | return validateKindInner(kind), nil
28 | }
29 | return "SIGNATURE_TYPE=7", fmt.Errorf("Invalid sigType: %s", kind)
30 | }
31 |
32 | // Generate a new destination and return the base64 encoded string
33 | func (c *Client) NewDestination(kind ...string) (string, string, error) {
34 | if len(kind) == 0 {
35 | kind = []string{"7"}
36 | } else {
37 | var err error
38 | kind[0], err = validateKind(kind[0])
39 | if err != nil {
40 | if c.debug {
41 | fmt.Println(err)
42 | }
43 | }
44 | }
45 | r, err := c.sendCmd("DEST GENERATE %s\n", kind[0])
46 | if err != nil {
47 | return "", "", err
48 | }
49 | if r.Topic != "DEST" {
50 | return "", "", fmt.Errorf("NewDestination Unknown Reply: %+v\n", r)
51 | }
52 | return r.Pairs["PRIV"], r.Pairs["PUB"], nil
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/dial.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "net"
8 | "strings"
9 | )
10 |
11 | // DialContext implements the net.DialContext function and can be used for http.Transport
12 | func (c *Client) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
13 | c.mutex.Lock()
14 | defer c.mutex.Unlock()
15 | errCh := make(chan error, 1)
16 | connCh := make(chan net.Conn, 1)
17 | go func() {
18 | if conn, err := c.DialContextFree(network, addr); err != nil {
19 | errCh <- err
20 | } else if ctx.Err() != nil {
21 | log.Println(ctx)
22 | errCh <- ctx.Err()
23 | } else {
24 | connCh <- conn
25 | }
26 | }()
27 | select {
28 | case err := <-errCh:
29 | return nil, err
30 | case conn := <-connCh:
31 | return conn, nil
32 | case <-ctx.Done():
33 | return nil, ctx.Err()
34 | }
35 |
36 | }
37 |
38 | func (c *Client) Dial(network, addr string) (net.Conn, error) {
39 | return c.DialContext(context.TODO(), network, addr)
40 | }
41 |
42 | // DialContextFree implements the net.Dial function and can be used for http.Transport
43 | func (c *Client) DialContextFree(network, addr string) (net.Conn, error) {
44 | if network == "tcp" || network == "tcp6" || network == "tcp4" {
45 | return c.DialStreamingContextFree(addr)
46 | }
47 | if network == "udp" || network == "udp6" || network == "udp4" {
48 | return c.DialDatagramContextFree(addr)
49 | }
50 | if network == "raw" || network == "ip" {
51 | return c.DialDatagramContextFree(addr)
52 | }
53 | return c.DialStreamingContextFree(addr)
54 | }
55 |
56 | // DialDatagramContextFree is a "Dialer" for "Client-Like" Datagram connections.
57 | // It is also not finished. If you need datagram support right now, use sam3.
58 | func (c *Client) DialDatagramContextFree(addr string) (*DatagramConn, error) {
59 | portIdx := strings.Index(addr, ":")
60 | if portIdx >= 0 {
61 | addr = addr[:portIdx]
62 | }
63 | addr, err := c.Lookup(addr)
64 | if err != nil {
65 | log.Printf("LOOKUP DIALER ERROR %s %s", addr, err)
66 | return nil, err
67 | }
68 |
69 | return nil, fmt.Errorf("Datagram support is not finished yet, come back later`")
70 | }
71 |
72 | // DialStreamingContextFree is a "Dialer" for "Client-Like" Streaming connections.
73 | func (c *Client) DialStreamingContextFree(addr string) (net.Conn, error) {
74 | portIdx := strings.Index(addr, ":")
75 | if portIdx >= 0 {
76 | addr = addr[:portIdx]
77 | }
78 | addr, err := c.Lookup(addr)
79 | if err != nil {
80 | log.Printf("LOOKUP DIALER ERROR %s %s", addr, err)
81 | return nil, err
82 | }
83 |
84 | if c.destination == "" {
85 | c.destination, err = c.CreateStreamSession(c.destination)
86 | if err != nil {
87 | return nil, err
88 | }
89 | }
90 |
91 | d, err := c.NewClient(c.NewID())
92 | if err != nil {
93 | return nil, err
94 | }
95 | err = d.StreamConnect(addr)
96 | if err != nil {
97 | return nil, err
98 | }
99 | return d.SamConn, nil
100 | }
101 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/go-i2p/gosam
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/getlantern/go-socks5 v0.0.0-20171114193258-79d4dd3e2db5
7 | github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4
8 | )
9 |
10 | require (
11 | github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
12 | github.com/getlantern/errors v1.0.1 // indirect
13 | github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect
14 | github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
15 | github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
16 | github.com/getlantern/iptool v0.0.0-20210721034953-519bf8ce0147 // indirect
17 | github.com/getlantern/netx v0.0.0-20211206143627-7ccfeb739cbd // indirect
18 | github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 // indirect
19 | github.com/go-stack/stack v1.8.0 // indirect
20 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
21 | github.com/sirupsen/logrus v1.9.3 // indirect
22 | go.uber.org/atomic v1.7.0 // indirect
23 | go.uber.org/multierr v1.6.0 // indirect
24 | go.uber.org/zap v1.19.1 // indirect
25 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
2 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
7 | github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
8 | github.com/getlantern/errors v1.0.1 h1:XukU2whlh7OdpxnkXhNH9VTLVz0EVPGKDV5K0oWhvzw=
9 | github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
10 | github.com/getlantern/fdcount v0.0.0-20190912142506-f89afd7367c4 h1:JdD4XSaT6/j6InM7MT1E4WRvzR8gurxfq53A3ML3B/Q=
11 | github.com/getlantern/fdcount v0.0.0-20190912142506-f89afd7367c4/go.mod h1:XZwE+iIlAgr64OFbXKFNCllBwV4wEipPx8Hlo2gZdbM=
12 | github.com/getlantern/go-socks5 v0.0.0-20171114193258-79d4dd3e2db5 h1:RBKofGGMt2k6eGBwX8mky9qunjL+KnAp9JdzXjiRkRw=
13 | github.com/getlantern/go-socks5 v0.0.0-20171114193258-79d4dd3e2db5/go.mod h1:kGHRXch95rnGLHjER/GhhFiHvfnqNz7KqWD9kGfATHY=
14 | github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f/go.mod h1:ZyIjgH/1wTCl+B+7yH1DqrWp6MPJqESmwmEQ89ZfhvA=
15 | github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 h1:NlQedYmPI3pRAXJb+hLVVDGqfvvXGRPV8vp7XOjKAZ0=
16 | github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65/go.mod h1:+ZU1h+iOVqWReBpky6d5Y2WL0sF2Llxu+QcxJFs2+OU=
17 | github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
18 | github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
19 | github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
20 | github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
21 | github.com/getlantern/iptool v0.0.0-20210721034953-519bf8ce0147 h1:/4ibPEIbC7c786Ec5Z8QqTti8MAjjTp/LmfuF6frVDM=
22 | github.com/getlantern/iptool v0.0.0-20210721034953-519bf8ce0147/go.mod h1:hfspzdRcvJ130tpTPL53/L92gG0pFtvQ6ln35ppwhHE=
23 | github.com/getlantern/mockconn v0.0.0-20200818071412-cb30d065a848 h1:2MhMMVBTnaHrst6HyWFDhwQCaJ05PZuOv1bE2gN8WFY=
24 | github.com/getlantern/mockconn v0.0.0-20200818071412-cb30d065a848/go.mod h1:+F5GJ7qGpQ03DBtcOEyQpM30ix4BLswdaojecFtsdy8=
25 | github.com/getlantern/mtime v0.0.0-20200417132445-23682092d1f7 h1:03J6Cb42EG06lHgpOFGm5BOax4qFqlSbSeKO2RGrj2g=
26 | github.com/getlantern/mtime v0.0.0-20200417132445-23682092d1f7/go.mod h1:GfzwugvtH7YcmNIrHHizeyImsgEdyL88YkdnK28B14c=
27 | github.com/getlantern/netx v0.0.0-20211206143627-7ccfeb739cbd h1:z5IehLDMqMwJ0oeFIaMHhySRU8r1lRMh7WQ0Wn0LioA=
28 | github.com/getlantern/netx v0.0.0-20211206143627-7ccfeb739cbd/go.mod h1:WEXF4pfIfnHBUAKwLa4DW7kcEINtG6wjUkbL2btwXZQ=
29 | github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
30 | github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 h1:QthAQCekS1YOeYWSvoHI6ZatlG4B+GBDLxV/2ZkBsTA=
31 | github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
32 | github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4 h1:LRjaRCzg1ieGKZjELlaIg06Fx04RHzQLsWMYp1H6PQ4=
33 | github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4/go.mod h1:m5TlHjPZrU5KbTd7Lr+I2rljyC6aJ88HdkeMQXV0U0E=
34 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
35 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
36 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
37 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
38 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
39 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
40 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
41 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
42 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
43 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
45 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
46 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
47 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
48 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
49 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
50 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
51 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
52 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
53 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
54 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
55 | go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
56 | go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
57 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
58 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
59 | go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
60 | go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
61 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
62 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
63 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
64 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
65 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
66 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
67 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
68 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
69 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
70 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
71 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
72 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
73 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
74 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
75 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
76 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
77 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
78 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
79 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
80 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
81 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
82 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
83 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
84 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
85 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
86 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
87 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
88 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
89 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
90 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
91 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
92 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
93 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
94 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
95 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
96 |
--------------------------------------------------------------------------------
/i2plogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eyedeekay/goSam/dedd16f35293222759814648c3044148a3d884fd/i2plogo.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | goSam
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
43 |
44 | /
45 |
46 |
47 | goSam
48 |
49 |
50 | A go library for using the
51 |
52 | I2P
53 |
54 | Simple Anonymous
55 | Messaging (
56 |
57 | SAM version 3.0
58 |
59 | ) bridge. It
60 | has support for all streaming features SAM version 3.2.
61 |
62 |
63 | STATUS: This project is maintained. I will respond to issues, pull requests, and feature requests within a few days. I am primarily maintaining functionality. This is widely used and easy to use, but thusfar, mostly by me. It sees a lot of testing and no breaking changes to the API are expected.
64 |
65 |
66 | Installation
67 |
68 | go get github.com/eyedeekay/goSam
69 |
70 |
71 | Using it for HTTP Transport
72 |
73 |
74 |
75 | Client.Dial
76 |
77 | implements
78 |
79 | net.Dial
80 |
81 | so you can use go’s library packages like http.
82 |
83 | package main
84 |
85 | import (
86 | "io"
87 | "log"
88 | "net/http"
89 | "os"
90 |
91 | "github.com/cryptix/goSam"
92 | )
93 |
94 | func main() {
95 | // create a default sam client
96 | sam, err := goSam.NewDefaultClient()
97 | checkErr(err)
98 |
99 | log.Println("Client Created")
100 |
101 | // create a transport that uses SAM to dial TCP Connections
102 | tr := &http.Transport{
103 | Dial: sam.Dial,
104 | }
105 |
106 | // create a client using this transport
107 | client := &http.Client{Transport: tr}
108 |
109 | // send a get request
110 | resp, err := client.Get("http://stats.i2p/")
111 | checkErr(err)
112 | defer resp.Body.Close()
113 |
114 | log.Printf("Get returned %+v\n", resp)
115 |
116 | // create a file for the response
117 | file, err := os.Create("stats.html")
118 | checkErr(err)
119 | defer file.Close()
120 |
121 | // copy the response to the file
122 | _, err = io.Copy(file, resp.Body)
123 | checkErr(err)
124 |
125 | log.Println("Done.")
126 | }
127 |
128 | func checkErr(err error) {
129 | if err != nil {
130 | log.Fatal(err)
131 | }
132 | }
133 |
134 |
135 |
136 | Using SAM by default, as a proxy for all HTTP Clients used by a Go application
137 |
138 |
139 | This will make the SAM transport dialer the default for all HTTP clients.
140 |
141 | package main
142 |
143 | import (
144 | "io"
145 | "log"
146 | "net/http"
147 | "os"
148 |
149 | "github.com/cryptix/goSam"
150 | )
151 |
152 | func main() {
153 | sam, err := goSam.NewDefaultClient()
154 | checkErr(err)
155 |
156 | log.Println("Client Created")
157 |
158 | // create a transport that uses SAM to dial TCP Connections
159 | httpClient := &http.Client{
160 | Transport: &http.Transport{
161 | Dial: sam.Dial,
162 | },
163 | }
164 |
165 | http.DefaultClient = httpClient
166 | return nil
167 | }
168 |
169 | func checkErr(err error) {
170 | if err != nil {
171 | log.Fatal(err)
172 | }
173 | }
174 |
175 |
176 | Using it as a SOCKS proxy
177 |
178 |
179 |
180 | client
181 |
182 | also implements a resolver compatible with
183 |
184 |
185 | getlantern/go-socks5
186 |
187 |
188 | ,
189 | making it very easy to implement a SOCKS5 server.
190 |
191 | package main
192 |
193 | import (
194 | "flag"
195 |
196 | "github.com/eyedeekay/goSam"
197 | "github.com/getlantern/go-socks5"
198 | "log"
199 | )
200 |
201 | var (
202 | samaddr = flag.String("sam", "127.0.0.1:7656", "SAM API address to use")
203 | socksaddr = flag.String("socks", "127.0.0.1:7675", "SOCKS address to use")
204 | )
205 |
206 | func main() {
207 | sam, err := goSam.NewClient(*samaddr)
208 | if err != nil {
209 | panic(err)
210 | }
211 | log.Println("Client Created")
212 |
213 | // create a transport that uses SAM to dial TCP Connections
214 | conf := &socks5.Config{
215 | Dial: sam.DialContext,
216 | Resolver: sam,
217 | }
218 | server, err := socks5.New(conf)
219 | if err != nil {
220 | panic(err)
221 | }
222 |
223 | // Create SOCKS5 proxy on localhost port 8000
224 | if err := server.ListenAndServe("tcp", *socksaddr); err != nil {
225 | panic(err)
226 | }
227 | }
228 |
229 |
230 | .deb package
231 |
232 |
233 | A package for installing this on Debian is buildable, and a version for Ubuntu
234 | is available as a PPA and mirrored via i2p. To build the deb package, from the
235 | root of this repository with the build dependencies installed(git, i2p, go,
236 | debuild) run the command
237 |
238 | debuild -us -uc
239 |
240 |
241 | to produce an unsigned deb for personal use only. For packagers,
242 |
243 | debuild -S
244 |
245 |
246 | will produce a viable source package for use with Launchpad PPA’s and other
247 | similar systems.
248 |
249 |
250 | TODO
251 |
252 |
253 |
254 | Improve recovery on failed sockets
255 |
256 |
257 | Implement
258 |
259 | STREAM FORWARD
260 |
261 |
262 |
263 | Implement datagrams (Repliable and Anon)
264 |
265 |
266 |
267 |
268 |
269 | Get the source code:
270 |
271 |
272 |
279 |
280 |
281 |
282 | Show license
283 |
284 |
285 |
286 |
The MIT License (MIT)
287 |
288 | Copyright (c) 2014 Henry
289 |
290 | Permission is hereby granted, free of charge, to any person obtaining a copy
291 | of this software and associated documentation files (the "Software"), to deal
292 | in the Software without restriction, including without limitation the rights
293 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
294 | copies of the Software, and to permit persons to whom the Software is
295 | furnished to do so, subject to the following conditions:
296 |
297 | The above copyright notice and this permission notice shall be included in all
298 | copies or substantial portions of the Software.
299 |
300 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
301 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
302 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
303 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
304 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
305 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
306 | SOFTWARE.
307 |
308 |
309 |
310 | Hide license
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
324 |
325 |
--------------------------------------------------------------------------------
/naming.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "net"
8 | "os"
9 | )
10 |
11 | // Lookup askes SAM for the internal i2p address from name
12 | func (c *Client) Lookup(name string) (string, error) {
13 | r, err := c.sendCmd("NAMING LOOKUP NAME=%s\n", name)
14 | if err != nil {
15 | return "", nil
16 | }
17 |
18 | // TODO: move check into sendCmd()
19 | if r.Topic != "NAMING" || r.Type != "REPLY" {
20 | return "", fmt.Errorf("Naming Unknown Reply: %s, %s\n", r.Topic, r.Type)
21 | }
22 |
23 | result := r.Pairs["RESULT"]
24 | if result != "OK" {
25 | return "", ReplyError{result, r}
26 | }
27 |
28 | if r.Pairs["NAME"] != name {
29 | // somehow different on i2pd
30 | if r.Pairs["NAME"] != "ME" {
31 | return "", fmt.Errorf("Lookup() Replyed to another name.\nWanted:%s\nGot: %+v\n", name, r)
32 | }
33 | fmt.Fprintln(os.Stderr, "WARNING: Lookup() Replyed to another name. assuming i2pd c++ fluke")
34 | }
35 |
36 | return r.Pairs["VALUE"], nil
37 | }
38 |
39 | func (c *Client) forward(client, conn net.Conn) {
40 | defer client.Close()
41 | defer conn.Close()
42 | go func() {
43 | // defer client.Close()
44 | // defer conn.Close()
45 | io.Copy(client, conn)
46 | }()
47 | go func() {
48 | // defer client.Close()
49 | // defer conn.Close()
50 | io.Copy(conn, client)
51 | }()
52 | }
53 |
54 | func (c *Client) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
55 | // if c.lastaddr == "invalid" || c.lastaddr != name {
56 | client, err := c.DialContext(ctx, "", name)
57 | if err != nil {
58 | return ctx, nil, err
59 | }
60 | ln, err := net.Listen("tcp", "127.0.0.1:")
61 | if err != nil {
62 | return ctx, nil, err
63 | }
64 | go func() {
65 | for {
66 | conn, err := ln.Accept()
67 | if err != nil {
68 | fmt.Println(err.Error())
69 | }
70 | go c.forward(client, conn)
71 | }
72 | }()
73 | // }
74 | return ctx, nil, nil
75 | }
76 |
--------------------------------------------------------------------------------
/naming_test.go:
--------------------------------------------------------------------------------
1 | //go:build nettest
2 | // +build nettest
3 |
4 | package gosam
5 |
6 | import (
7 | "fmt"
8 | "testing"
9 | )
10 |
11 | func TestClientLookupInvalid(t *testing.T) {
12 | var err error
13 |
14 | client, err := NewClientFromOptions(SetDebug(false))
15 | if err != nil {
16 | t.Fatalf("NewDefaultClient() Error: %q\n", err)
17 | }
18 |
19 | addr, err := client.Lookup(`!(@#)`)
20 | if addr != "" || err == nil {
21 | t.Error("client.Lookup() should throw an error.")
22 | }
23 |
24 | repErr, ok := err.(ReplyError)
25 | if !ok {
26 | t.Fatalf("client.Lookup() should return a ReplyError")
27 | }
28 | if repErr.Result != ResultKeyNotFound {
29 | t.Errorf("client.Lookup() should throw an ResultKeyNotFound error.\nGot:%+v%s%s\n", repErr, "!=", ResultKeyNotFound)
30 | }
31 | if err := client.Close(); err != nil {
32 | t.Fatalf("client.Close() Error: %q\n", err)
33 | }
34 | }
35 |
36 | func TestClientLookupValid(t *testing.T) {
37 | client, err := NewDefaultClient()
38 | if err != nil {
39 | fmt.Printf("NewDefaultClient() should not throw an error.\n%s\n", err)
40 | return
41 | }
42 |
43 | addr, err := client.Lookup("zzz.i2p")
44 | if err != nil {
45 | fmt.Printf("client.Lookup() should not throw an error.\n%s\n", err)
46 | return
47 | }
48 |
49 | if addr == `GKapJ8koUcBj~jmQzHsTYxDg2tpfWj0xjQTzd8BhfC9c3OS5fwPBNajgF-eOD6eCjFTqTlorlh7Hnd8kXj1qblUGXT-tDoR9~YV8dmXl51cJn9MVTRrEqRWSJVXbUUz9t5Po6Xa247Vr0sJn27R4KoKP8QVj1GuH6dB3b6wTPbOamC3dkO18vkQkfZWUdRMDXk0d8AdjB0E0864nOT~J9Fpnd2pQE5uoFT6P0DqtQR2jsFvf9ME61aqLvKPPWpkgdn4z6Zkm-NJOcDz2Nv8Si7hli94E9SghMYRsdjU-knObKvxiagn84FIwcOpepxuG~kFXdD5NfsH0v6Uri3usE3XWD7Pw6P8qVYF39jUIq4OiNMwPnNYzy2N4mDMQdsdHO3LUVh~DEppOy9AAmEoHDjjJxt2BFBbGxfdpZCpENkwvmZeYUyNCCzASqTOOlNzdpne8cuesn3NDXIpNnqEE6Oe5Qm5YOJykrX~Vx~cFFT3QzDGkIjjxlFBsjUJyYkFjBQAEAAcAAA==` {
50 | t.Log("Success")
51 | } else {
52 | t.Errorf("Address of zzz.i2p != \nGKapJ8koUcBj~jmQzHsTYxDg2tpfWj0xjQTzd8BhfC9c3OS5fwPBNajgF-eOD6eCjFTqTlorlh7Hnd8kXj1qblUGXT-tDoR9~YV8dmXl51cJn9MVTRrEqRWSJVXbUUz9t5Po6Xa247Vr0sJn27R4KoKP8QVj1GuH6dB3b6wTPbOamC3dkO18vkQkfZWUdRMDXk0d8AdjB0E0864nOT~J9Fpnd2pQE5uoFT6P0DqtQR2jsFvf9ME61aqLvKPPWpkgdn4z6Zkm-NJOcDz2Nv8Si7hli94E9SghMYRsdjU-knObKvxiagn84FIwcOpepxuG~kFXdD5NfsH0v6Uri3usE3XWD7Pw6P8qVYF39jUIq4OiNMwPnNYzy2N4mDMQdsdHO3LUVh~DEppOy9AAmEoHDjjJxt2BFBbGxfdpZCpENkwvmZeYUyNCCzASqTOOlNzdpne8cuesn3NDXIpNnqEE6Oe5Qm5YOJykrX~Vx~cFFT3QzDGkIjjxlFBsjUJyYkFjBQAEAAcAAA==\n, check to see if it changed, %s", addr)
53 | }
54 |
55 | fmt.Println("Address of zzz.i2p:")
56 | // Addresses change all the time
57 | fmt.Println(addr)
58 |
59 | // Output:
60 | //Address of zzz.i2p:
61 | //
62 | }
63 |
--------------------------------------------------------------------------------
/options.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | // Option is a client Option
10 | type Option func(*Client) error
11 |
12 | // SetAddr sets a clients's address in the form host:port or host, port
13 | func SetAddr(s ...string) func(*Client) error {
14 | return func(c *Client) error {
15 | if len(s) == 1 {
16 | split := strings.SplitN(s[0], ":", 2)
17 | if len(split) == 2 {
18 | if i, err := strconv.Atoi(split[1]); err == nil {
19 | if i < 65536 {
20 | c.host = split[0]
21 | c.port = split[1]
22 | return nil
23 | }
24 | return fmt.Errorf("Invalid port")
25 | }
26 | return fmt.Errorf("Invalid port; non-number")
27 | }
28 | return fmt.Errorf("Invalid address; use host:port %s", split)
29 | } else if len(s) == 2 {
30 | if i, err := strconv.Atoi(s[1]); err == nil {
31 | if i < 65536 {
32 | c.host = s[0]
33 | c.port = s[1]
34 | return nil
35 | }
36 | return fmt.Errorf("Invalid port")
37 | }
38 | return fmt.Errorf("Invalid port; non-number")
39 | } else {
40 | return fmt.Errorf("Invalid address")
41 | }
42 | }
43 | }
44 |
45 | // SetAddrMixed sets a clients's address in the form host, port(int)
46 | func SetAddrMixed(s string, i int) func(*Client) error {
47 | return func(c *Client) error {
48 | if i < 65536 && i > 0 {
49 | c.host = s
50 | c.port = strconv.Itoa(i)
51 | return nil
52 | }
53 | return fmt.Errorf("Invalid port")
54 | }
55 | }
56 |
57 | // SetHost sets the host of the client's SAM bridge
58 | func SetHost(s string) func(*Client) error {
59 | return func(c *Client) error {
60 | c.host = s
61 | return nil
62 | }
63 | }
64 |
65 | // SetUser sets the username for authentication during the SAM HELLO phase
66 | func SetUser(s string) func(*Client) error {
67 | return func(c *Client) error {
68 | c.user = s
69 | return nil
70 | }
71 | }
72 |
73 | // SetUser sets the password for authentication during the SAM HELLO phase
74 | func SetPass(s string) func(*Client) error {
75 | return func(c *Client) error {
76 | c.pass = s
77 | return nil
78 | }
79 | }
80 |
81 | func SetSAMMinVersion(i int) func(*Client) error {
82 | return func(c *Client) error {
83 | if i < 0 {
84 | return fmt.Errorf("SAM version must be greater than or equal to 0")
85 | }
86 | if i > 3 {
87 | return fmt.Errorf("SAM version must be less than or equal to 3")
88 | }
89 | c.sammin = i
90 | return nil
91 | }
92 | }
93 |
94 | func SetSAMMaxVersion(i int) func(*Client) error {
95 | return func(c *Client) error {
96 | if i < 0 {
97 | return fmt.Errorf("SAM version must be greater than or equal to 0")
98 | }
99 | if i > 3 {
100 | return fmt.Errorf("SAM version must be less than or equal to 3")
101 | }
102 | c.sammin = i
103 | return nil
104 | }
105 | }
106 |
107 | // SetLocalDestination sets the local destination of the tunnel from a private
108 | // key
109 | func SetLocalDestination(s string) func(*Client) error {
110 | return func(c *Client) error {
111 | c.destination = s
112 | return nil
113 | }
114 | }
115 |
116 | func setid(s int32) func(*Client) error {
117 | return func(c *Client) error {
118 | c.id = s
119 | return nil
120 | }
121 | }
122 |
123 | // SetPort sets the port of the client's SAM bridge using a string
124 | func SetPort(s string) func(*Client) error {
125 | return func(c *Client) error {
126 | port, err := strconv.Atoi(s)
127 | if err != nil {
128 | return fmt.Errorf("Invalid port; non-number")
129 | }
130 | if port < 65536 && port > -1 {
131 | c.port = s
132 | return nil
133 | }
134 | return fmt.Errorf("Invalid port")
135 | }
136 | }
137 |
138 | // SetPortInt sets the port of the client's SAM bridge using a string
139 | func SetPortInt(i int) func(*Client) error {
140 | return func(c *Client) error {
141 | if i < 65536 && i > -1 {
142 | c.port = strconv.Itoa(i)
143 | return nil
144 | }
145 | return fmt.Errorf("Invalid port")
146 | }
147 | }
148 |
149 | // SetFromPort sets the port of the client's SAM bridge using a string
150 | func SetFromPort(s string) func(*Client) error {
151 | return func(c *Client) error {
152 | port, err := strconv.Atoi(s)
153 | if err != nil {
154 | return fmt.Errorf("Invalid port; non-number")
155 | }
156 | if port < 65536 && port > -1 {
157 | c.fromport = s
158 | return nil
159 | }
160 | return fmt.Errorf("Invalid port")
161 | }
162 | }
163 |
164 | // SetFromPortInt sets the port of the client's SAM bridge using a string
165 | func SetFromPortInt(i int) func(*Client) error {
166 | return func(c *Client) error {
167 | if i < 65536 && i > -1 {
168 | c.fromport = strconv.Itoa(i)
169 | return nil
170 | }
171 | return fmt.Errorf("Invalid port")
172 | }
173 | }
174 |
175 | // SetToPort sets the port of the client's SAM bridge using a string
176 | func SetToPort(s string) func(*Client) error {
177 | return func(c *Client) error {
178 | port, err := strconv.Atoi(s)
179 | if err != nil {
180 | return fmt.Errorf("Invalid port; non-number")
181 | }
182 | if port < 65536 && port > -1 {
183 | c.toport = s
184 | return nil
185 | }
186 | return fmt.Errorf("Invalid port")
187 | }
188 | }
189 |
190 | // SetToPortInt sets the port of the client's SAM bridge using a string
191 | func SetToPortInt(i int) func(*Client) error {
192 | return func(c *Client) error {
193 | if i < 65536 && i > -1 {
194 | c.fromport = strconv.Itoa(i)
195 | return nil
196 | }
197 | return fmt.Errorf("Invalid port")
198 | }
199 | }
200 |
201 | // SetDebug enables debugging messages
202 | func SetDebug(b bool) func(*Client) error {
203 | return func(c *Client) error {
204 | //c.debug = b
205 | c.debug = true
206 | return nil
207 | }
208 | }
209 |
210 | // SetInLength sets the number of hops inbound
211 | func SetInLength(u uint) func(*Client) error {
212 | return func(c *Client) error {
213 | if u < 7 {
214 | c.inLength = u
215 | return nil
216 | }
217 | return fmt.Errorf("Invalid inbound tunnel length")
218 | }
219 | }
220 |
221 | // SetOutLength sets the number of hops outbound
222 | func SetOutLength(u uint) func(*Client) error {
223 | return func(c *Client) error {
224 | if u < 7 {
225 | c.outLength = u
226 | return nil
227 | }
228 | return fmt.Errorf("Invalid outbound tunnel length")
229 | }
230 | }
231 |
232 | // SetInVariance sets the variance of a number of hops inbound
233 | func SetInVariance(i int) func(*Client) error {
234 | return func(c *Client) error {
235 | if i < 7 && i > -7 {
236 | c.inVariance = i
237 | return nil
238 | }
239 | return fmt.Errorf("Invalid inbound tunnel length")
240 | }
241 | }
242 |
243 | // SetOutVariance sets the variance of a number of hops outbound
244 | func SetOutVariance(i int) func(*Client) error {
245 | return func(c *Client) error {
246 | if i < 7 && i > -7 {
247 | c.outVariance = i
248 | return nil
249 | }
250 | return fmt.Errorf("Invalid outbound tunnel variance")
251 | }
252 | }
253 |
254 | // SetInQuantity sets the inbound tunnel quantity
255 | func SetInQuantity(u uint) func(*Client) error {
256 | return func(c *Client) error {
257 | if u <= 16 {
258 | c.inQuantity = u
259 | return nil
260 | }
261 | return fmt.Errorf("Invalid inbound tunnel quantity")
262 | }
263 | }
264 |
265 | // SetOutQuantity sets the outbound tunnel quantity
266 | func SetOutQuantity(u uint) func(*Client) error {
267 | return func(c *Client) error {
268 | if u <= 16 {
269 | c.outQuantity = u
270 | return nil
271 | }
272 | return fmt.Errorf("Invalid outbound tunnel quantity")
273 | }
274 | }
275 |
276 | // SetInBackups sets the inbound tunnel backups
277 | func SetInBackups(u uint) func(*Client) error {
278 | return func(c *Client) error {
279 | if u < 6 {
280 | c.inBackups = u
281 | return nil
282 | }
283 | return fmt.Errorf("Invalid inbound tunnel backup quantity")
284 | }
285 | }
286 |
287 | // SetOutBackups sets the inbound tunnel backups
288 | func SetOutBackups(u uint) func(*Client) error {
289 | return func(c *Client) error {
290 | if u < 6 {
291 | c.outBackups = u
292 | return nil
293 | }
294 | return fmt.Errorf("Invalid outbound tunnel backup quantity")
295 | }
296 | }
297 |
298 | // SetUnpublished tells the router to not publish the client leaseset
299 | func SetUnpublished(b bool) func(*Client) error {
300 | return func(c *Client) error {
301 | c.dontPublishLease = b
302 | return nil
303 | }
304 | }
305 |
306 | // SetEncrypt tells the router to use an encrypted leaseset
307 | func SetEncrypt(b bool) func(*Client) error {
308 | return func(c *Client) error {
309 | c.encryptLease = b
310 | return nil
311 | }
312 | }
313 |
314 | // SetLeaseSetEncType tells the router to use an encrypted leaseset of a specific type.
315 | // defaults to 4,0
316 | func SetLeaseSetEncType(b string) func(*Client) error {
317 | return func(c *Client) error {
318 | c.leaseSetEncType = b
319 | return nil
320 | }
321 | }
322 |
323 | // SetReduceIdle sets the created tunnels to be reduced during extended idle time to avoid excessive resource usage
324 | func SetReduceIdle(b bool) func(*Client) error {
325 | return func(c *Client) error {
326 | c.reduceIdle = b
327 | return nil
328 | }
329 | }
330 |
331 | // SetReduceIdleTime sets time to wait before the tunnel quantity is reduced
332 | func SetReduceIdleTime(u uint) func(*Client) error {
333 | return func(c *Client) error {
334 | if u > 299999 {
335 | c.reduceIdleTime = u
336 | return nil
337 | }
338 | return fmt.Errorf("Invalid reduce idle time %v", u)
339 | }
340 | }
341 |
342 | // SetReduceIdleQuantity sets number of tunnels to keep alive during an extended idle period
343 | func SetReduceIdleQuantity(u uint) func(*Client) error {
344 | return func(c *Client) error {
345 | if u < 5 {
346 | c.reduceIdleQuantity = u
347 | return nil
348 | }
349 | return fmt.Errorf("Invalid reduced tunnel quantity %v", u)
350 | }
351 | }
352 |
353 | // SetCloseIdle sets the tunnels to close after a specific amount of time
354 | func SetCloseIdle(b bool) func(*Client) error {
355 | return func(c *Client) error {
356 | c.closeIdle = b
357 | return nil
358 | }
359 | }
360 |
361 | // SetCloseIdleTime sets the time in milliseconds to wait before closing tunnels
362 | func SetCloseIdleTime(u uint) func(*Client) error {
363 | return func(c *Client) error {
364 | if u > 299999 {
365 | c.closeIdleTime = u
366 | return nil
367 | }
368 | return fmt.Errorf("Invalid close idle time %v", u)
369 | }
370 | }
371 |
372 | // SetCompression sets the tunnels to close after a specific amount of time
373 | func SetCompression(b bool) func(*Client) error {
374 | return func(c *Client) error {
375 | c.compress = b
376 | return nil
377 | }
378 | }
379 |
380 | /* SAM v 3.1 Options*/
381 |
382 | // SetSignatureType tells gosam to pass SAM a signature_type parameter with one
383 | // of the following values:
384 | //
385 | // "SIGNATURE_TYPE=DSA_SHA1",
386 | // "SIGNATURE_TYPE=ECDSA_SHA256_P256",
387 | // "SIGNATURE_TYPE=ECDSA_SHA384_P384",
388 | // "SIGNATURE_TYPE=ECDSA_SHA512_P521",
389 | // "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519",
390 | //
391 | // or an empty string
392 | func SetSignatureType(s string) func(*Client) error {
393 | return func(c *Client) error {
394 | if s == "" {
395 | c.sigType = ""
396 | return nil
397 | }
398 | for _, valid := range SAMsigTypes {
399 | if s == valid {
400 | c.sigType = valid
401 | return nil
402 | }
403 | }
404 | return fmt.Errorf("Invalid signature type specified at construction time")
405 | }
406 | }
407 |
408 | // return the from port as a string.
409 | func (c *Client) from() string {
410 | if c.fromport == "FROM_PORT=0" {
411 | return ""
412 | }
413 | if c.fromport == "0" {
414 | return ""
415 | }
416 | if c.fromport == "" {
417 | return ""
418 | }
419 | return fmt.Sprintf(" FROM_PORT=%v ", c.fromport)
420 | }
421 |
422 | // return the to port as a string.
423 | func (c *Client) to() string {
424 | if c.fromport == "TO_PORT=0" {
425 | return ""
426 | }
427 | if c.fromport == "0" {
428 | return ""
429 | }
430 | if c.toport == "" {
431 | return ""
432 | }
433 | return fmt.Sprintf(" TO_PORT=%v ", c.toport)
434 | }
435 |
436 | // return the signature type as a string.
437 | func (c *Client) sigtype() string {
438 | return fmt.Sprintf(" %s ", c.sigType)
439 | }
440 |
441 | // return the inbound length as a string.
442 | func (c *Client) inlength() string {
443 | return fmt.Sprintf(" inbound.length=%d ", c.inLength)
444 | }
445 |
446 | // return the outbound length as a string.
447 | func (c *Client) outlength() string {
448 | return fmt.Sprintf(" outbound.length=%d ", c.outLength)
449 | }
450 |
451 | // return the inbound length variance as a string.
452 | func (c *Client) invariance() string {
453 | return fmt.Sprintf(" inbound.lengthVariance=%d ", c.inVariance)
454 | }
455 |
456 | // return the outbound length variance as a string.
457 | func (c *Client) outvariance() string {
458 | return fmt.Sprintf(" outbound.lengthVariance=%d ", c.outVariance)
459 | }
460 |
461 | // return the inbound tunnel quantity as a string.
462 | func (c *Client) inquantity() string {
463 | return fmt.Sprintf(" inbound.quantity=%d ", c.inQuantity)
464 | }
465 |
466 | // return the outbound tunnel quantity as a string.
467 | func (c *Client) outquantity() string {
468 | return fmt.Sprintf(" outbound.quantity=%d ", c.outQuantity)
469 | }
470 |
471 | // return the inbound tunnel quantity as a string.
472 | func (c *Client) inbackups() string {
473 | return fmt.Sprintf(" inbound.backupQuantity=%d ", c.inQuantity)
474 | }
475 |
476 | // return the outbound tunnel quantity as a string.
477 | func (c *Client) outbackups() string {
478 | return fmt.Sprintf(" outbound.backupQuantity=%d ", c.outQuantity)
479 | }
480 |
481 | func (c *Client) encryptlease() string {
482 | if c.encryptLease {
483 | return " i2cp.encryptLeaseSet=true "
484 | }
485 | return " i2cp.encryptLeaseSet=false "
486 | }
487 |
488 | func (c *Client) leasesetenctype() string {
489 | if c.encryptLease {
490 | return fmt.Sprintf(" i2cp.leaseSetEncType=%s ", c.leaseSetEncType)
491 | }
492 | return " i2cp.leaseSetEncType=4,0 "
493 | }
494 |
495 | func (c *Client) dontpublishlease() string {
496 | if c.dontPublishLease {
497 | return " i2cp.dontPublishLeaseSet=true "
498 | }
499 | return " i2cp.dontPublishLeaseSet=false "
500 | }
501 |
502 | func (c *Client) closeonidle() string {
503 | if c.closeIdle {
504 | return " i2cp.closeOnIdle=true "
505 | }
506 | return " i2cp.closeOnIdle=false "
507 | }
508 |
509 | func (c *Client) closeidletime() string {
510 | return fmt.Sprintf(" i2cp.closeIdleTime=%d ", c.closeIdleTime)
511 | }
512 |
513 | func (c *Client) reduceonidle() string {
514 | if c.reduceIdle {
515 | return " i2cp.reduceOnIdle=true "
516 | }
517 | return " i2cp.reduceOnIdle=false "
518 | }
519 |
520 | func (c *Client) reduceidletime() string {
521 | return fmt.Sprintf(" i2cp.reduceIdleTime=%d ", c.reduceIdleTime)
522 | }
523 |
524 | func (c *Client) reduceidlecount() string {
525 | return fmt.Sprintf(" i2cp.reduceIdleQuantity=%d ", c.reduceIdleQuantity)
526 | }
527 |
528 | func (c *Client) compression() string {
529 | if c.compress {
530 | return " i2cp.gzip=true "
531 | }
532 | return " i2cp.gzip=false "
533 | }
534 |
535 | // return all options as string ready for passing to sendcmd
536 | func (c *Client) allOptions() string {
537 | return c.inlength() +
538 | c.outlength() +
539 | c.invariance() +
540 | c.outvariance() +
541 | c.inquantity() +
542 | c.outquantity() +
543 | c.inbackups() +
544 | c.outbackups() +
545 | c.dontpublishlease() +
546 | c.encryptlease() +
547 | c.leasesetenctype() +
548 | c.reduceonidle() +
549 | c.reduceidletime() +
550 | c.reduceidlecount() +
551 | c.closeonidle() +
552 | c.closeidletime() +
553 | c.compression()
554 | }
555 |
556 | // Print return all options as string
557 | func (c *Client) Print() string {
558 | return c.inlength() +
559 | c.outlength() +
560 | c.invariance() +
561 | c.outvariance() +
562 | c.inquantity() +
563 | c.outquantity() +
564 | c.inbackups() +
565 | c.outbackups() +
566 | c.dontpublishlease() +
567 | c.encryptlease() +
568 | c.leasesetenctype() +
569 | c.reduceonidle() +
570 | c.reduceidletime() +
571 | c.reduceidlecount() +
572 | c.closeonidle() +
573 | c.closeidletime() +
574 | c.compression()
575 | }
576 |
577 | func (c *Client) getUser() string {
578 | if c.user == "" {
579 | return ""
580 | }
581 | return fmt.Sprintf("USER=%s", c.user)
582 | }
583 |
584 | func (c *Client) getPass() string {
585 | if c.pass == "" {
586 | return ""
587 | }
588 | return fmt.Sprintf("PASSWORD=%s", c.pass)
589 | }
590 |
--------------------------------------------------------------------------------
/options_test.go:
--------------------------------------------------------------------------------
1 | //go:build nettest
2 | // +build nettest
3 |
4 | package gosam
5 |
6 | import (
7 | "fmt"
8 | "math"
9 | "math/rand"
10 | //"net/http"
11 | "strings"
12 | "testing"
13 | )
14 |
15 | // helper to validate sendCmd inputs
16 | func (c *Client) validCmd(str string, args ...interface{}) (string, error) {
17 | if s := fmt.Sprintf(str, args...); strings.Contains(s, "\n") {
18 | sl := strings.Split(s, "\n")
19 | if len(sl) == 2 {
20 | if sl[1] != "" {
21 | return sl[1], fmt.Errorf("Error, there should be no options after the newline")
22 | }
23 | for li, in := range sl {
24 | fmt.Println(li, in)
25 | }
26 | return s, nil
27 | }
28 | return "", fmt.Errorf("Error, invalid length: %d", len(sl))
29 | }
30 | return "", fmt.Errorf("Error, invalid input")
31 | }
32 |
33 | func (c *Client) validCreate() (string, error) {
34 | id := rand.Int31n(math.MaxInt32)
35 | result, err := c.validCmd("SESSION CREATE STYLE=STREAM ID=%d DESTINATION=%s %s\n", id, "abc.i2p", c.allOptions())
36 | return result, err
37 | }
38 |
39 | func TestOptionAddrString(t *testing.T) {
40 | client, err := NewClientFromOptions(SetAddr("127.0.0.1:7656"), SetDebug(false))
41 | if err != nil {
42 | t.Fatalf("NewClientFromOptions() Error: %q\n", err)
43 | }
44 | if result, err := client.validCreate(); err != nil {
45 | t.Fatalf(err.Error())
46 | } else {
47 | t.Log(result)
48 | }
49 | _, err = client.CreateStreamSession("")
50 | if err := client.Close(); err != nil {
51 | t.Fatalf("client.Close() Error: %q\n", err)
52 | }
53 | /* fmt.Printf("\t destination- %s \n", dest)
54 | fmt.Printf("\t address64- %s \t", client.Base64())
55 | fmt.Printf("\t address- %s \t", client.Base32())*/
56 | }
57 |
58 | func TestOptionAddrStringLh(t *testing.T) {
59 | client, err := NewClientFromOptions(SetAddr("localhost:7656"), SetDebug(false))
60 | if err != nil {
61 | t.Fatalf("NewClientFromOptions() Error: %q\n", err)
62 | }
63 | if result, err := client.validCreate(); err != nil {
64 | t.Fatalf(err.Error())
65 | } else {
66 | t.Log(result)
67 | }
68 | _, err = client.CreateStreamSession("")
69 | if err := client.Close(); err != nil {
70 | t.Fatalf("client.Close() Error: %q\n", err)
71 | }
72 | /* fmt.Printf("\t destination- %s \n", dest)
73 | fmt.Printf("\t address64- %s \t", client.Base64())
74 | fmt.Printf("\t address- %s \t", client.Base32())*/
75 | }
76 |
77 | func TestOptionAddrSlice(t *testing.T) {
78 | client, err := NewClientFromOptions(SetAddr("127.0.0.1", "7656"), SetDebug(false))
79 | if err != nil {
80 | t.Fatalf("NewClientFromOptions() Error: %q\n", err)
81 | }
82 | if result, err := client.validCreate(); err != nil {
83 | t.Fatalf(err.Error())
84 | } else {
85 | t.Log(result)
86 | }
87 | _, err = client.CreateStreamSession("")
88 | if err := client.Close(); err != nil {
89 | t.Fatalf("client.Close() Error: %q\n", err)
90 | }
91 | /* fmt.Printf("\t destination- %s \n", dest)
92 | fmt.Printf("\t address64- %s \t", client.Base64())
93 | fmt.Printf("\t address- %s \t", client.Base32())*/
94 | }
95 |
96 | func TestOptionAddrMixedSlice(t *testing.T) {
97 | client, err := NewClientFromOptions(SetAddrMixed("127.0.0.1", 7656), SetDebug(false))
98 | if err != nil {
99 | t.Fatalf("NewClientFromOptions() Error: %q\n", err)
100 | }
101 | if result, err := client.validCreate(); err != nil {
102 | t.Fatalf(err.Error())
103 | } else {
104 | t.Log(result)
105 | }
106 | _, err = client.CreateStreamSession("")
107 | if err := client.Close(); err != nil {
108 | t.Fatalf("client.Close() Error: %q\n", err)
109 | }
110 | /* fmt.Printf("\t destination- %s \n", dest)
111 | fmt.Printf("\t address64- %s \t", client.Base64())
112 | fmt.Printf("\t address- %s \t", client.Base32())*/
113 | }
114 |
115 | func TestOptionHost(t *testing.T) {
116 | client, err := NewClientFromOptions(
117 | SetHost("127.0.0.1"),
118 | SetPort("7656"),
119 | SetInLength(3),
120 | SetOutLength(3),
121 | SetInVariance(1),
122 | SetOutVariance(1),
123 | SetInQuantity(6),
124 | SetOutQuantity(6),
125 | SetInBackups(2),
126 | SetOutBackups(2),
127 | SetEncrypt(true),
128 | SetDebug(false),
129 | SetUnpublished(true),
130 | SetReduceIdle(true),
131 | SetReduceIdleTime(300001),
132 | SetReduceIdleQuantity(4),
133 | SetCloseIdle(true),
134 | SetCloseIdleTime(300001),
135 | )
136 | if err != nil {
137 | t.Fatalf("NewClientFromOptions() Error: %q\n", err)
138 | }
139 | if result, err := client.validCreate(); err != nil {
140 | t.Fatalf(err.Error())
141 | } else {
142 | t.Log(result)
143 | }
144 | _, err = client.CreateStreamSession("")
145 | if err := client.Close(); err != nil {
146 | t.Fatalf("client.Close() Error: %q\n", err)
147 | }
148 | /* fmt.Printf("\t destination- %s \n", dest)
149 | fmt.Printf("\t address64- %s \t", client.Base64())
150 | fmt.Printf("\t address- %s \t", client.Base32())*/
151 | }
152 |
153 | func TestOptionPortInt(t *testing.T) {
154 | client, err := NewClientFromOptions(
155 | SetHost("127.0.0.1"),
156 | SetPortInt(7656),
157 | SetInLength(3),
158 | SetOutLength(3),
159 | SetInVariance(1),
160 | SetOutVariance(1),
161 | SetInQuantity(6),
162 | SetOutQuantity(6),
163 | SetInBackups(2),
164 | SetOutBackups(2),
165 | SetEncrypt(true),
166 | SetDebug(false),
167 | SetUnpublished(true),
168 | SetReduceIdle(true),
169 | SetReduceIdleTime(300001),
170 | SetReduceIdleQuantity(4),
171 | SetCloseIdle(true),
172 | SetCloseIdleTime(300001),
173 | )
174 | if err != nil {
175 | t.Fatalf("NewClientFromOptions() Error: %q\n", err)
176 | }
177 | if result, err := client.validCreate(); err != nil {
178 | t.Fatalf(err.Error())
179 | } else {
180 | t.Log(result)
181 | }
182 | _, err = client.CreateStreamSession("")
183 | if err := client.Close(); err != nil {
184 | t.Fatalf("client.Close() Error: %q\n", err)
185 | }
186 | /* fmt.Printf("\t destination- %s \n", dest)
187 | fmt.Printf("\t address64- %s \t", client.Base64())
188 | fmt.Printf("\t address- %s \t", client.Base32())*/
189 | }
190 |
--------------------------------------------------------------------------------
/raw.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
--------------------------------------------------------------------------------
/replyParser.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // The Possible Results send by SAM
9 | const (
10 | ResultOk = "OK" //Operation completed successfully
11 | ResultCantReachPeer = "CANT_REACH_PEER" //The peer exists, but cannot be reached
12 | ResultDuplicatedID = "DUPLICATED_ID" //If the nickname is already associated with a session :
13 | ResultDuplicatedDest = "DUPLICATED_DEST" //The specified Destination is already in use
14 | ResultI2PError = "I2P_ERROR" //A generic I2P error (e.g. I2CP disconnection, etc.)
15 | ResultInvalidKey = "INVALID_KEY" //The specified key is not valid (bad format, etc.)
16 | ResultKeyNotFound = "KEY_NOT_FOUND" //The naming system can't resolve the given name
17 | ResultPeerNotFound = "PEER_NOT_FOUND" //The peer cannot be found on the network
18 | ResultTimeout = "TIMEOUT" // Timeout while waiting for an event (e.g. peer answer)
19 | )
20 |
21 | // A ReplyError is a custom error type, containing the Result and full Reply
22 | type ReplyError struct {
23 | Result string
24 | Reply *Reply
25 | }
26 |
27 | func (r ReplyError) Error() string {
28 | return fmt.Sprintf("ReplyError: Result:%s - Reply:%+v", r.Result, r.Reply)
29 | }
30 |
31 | // Reply is the parsed result of a SAM command, containing a map of all the key-value pairs
32 | type Reply struct {
33 | Topic string
34 | Type string
35 | From string
36 | To string
37 |
38 | Pairs map[string]string
39 | }
40 |
41 | func parseReply(line string) (*Reply, error) {
42 | line = strings.TrimSpace(line)
43 | parts := strings.Split(line, " ")
44 | if len(parts) < 3 {
45 | return nil, fmt.Errorf("Malformed Reply.\n%s\n", line)
46 | }
47 | preParseReply := func() []string {
48 | val := ""
49 | quote := false
50 | for _, v := range parts {
51 | if strings.Contains(v, "=\"") {
52 | quote = true
53 | }
54 | if strings.Contains(v, "\"\n") || strings.Contains(v, "\" ") {
55 | quote = false
56 | }
57 | if quote {
58 | val += v + "_"
59 | } else {
60 | val += v + " "
61 | }
62 | }
63 | return strings.Split(strings.TrimSuffix(strings.TrimSpace(val), "_"), " ")
64 | }
65 | parts = preParseReply()
66 |
67 | r := &Reply{
68 | Topic: parts[0],
69 | Type: parts[1],
70 | Pairs: make(map[string]string, len(parts)-2),
71 | }
72 |
73 | for _, v := range parts[2:] {
74 | if strings.Contains(v, "FROM_PORT") {
75 | if v != "FROM_PORT=0" {
76 | r.From = v
77 | }
78 | } else if strings.Contains(v, "TO_PORT") {
79 | if v != "TO_PORT=0" {
80 | r.To = v
81 | }
82 | } else {
83 | kvPair := strings.SplitN(v, "=", 2)
84 | if kvPair != nil {
85 | if len(kvPair) != 2 {
86 | return nil, fmt.Errorf("Malformed key-value-pair len != 2.\n%s\n", kvPair)
87 | }
88 | }
89 | r.Pairs[kvPair[0]] = kvPair[len(kvPair)-1]
90 | }
91 | }
92 |
93 | return r, nil
94 | }
95 |
--------------------------------------------------------------------------------
/replyParser_test.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | var validCases = []struct {
8 | Input string
9 | Expected Reply
10 | }{
11 | // hello handshake reply
12 | {
13 | "HELLO REPLY RESULT=OK VERSION=3.0\n",
14 | Reply{
15 | Topic: "HELLO",
16 | Type: "REPLY",
17 | Pairs: map[string]string{
18 | "RESULT": "OK",
19 | "VERSION": "3.0",
20 | },
21 | },
22 | },
23 | // result of a naming lookup
24 | {
25 | "NAMING REPLY RESULT=OK NAME=zzz.i2p VALUE=SomeValueForTesting\n",
26 | Reply{
27 | Topic: "NAMING",
28 | Type: "REPLY",
29 | Pairs: map[string]string{
30 | "RESULT": "OK",
31 | "NAME": "zzz.i2p",
32 | "VALUE": "SomeValueForTesting",
33 | },
34 | },
35 | },
36 | // result of a b32.i2p naming lookup
37 | {
38 | "NAMING REPLY RESULT=OK NAME=gkso46tc47hdua2kva5zahj3unmyh6ia7bv5oc2ybn2hmeowpz7a.b32.i2p VALUE=mlHQraXLpcE7A4MVeVniRHM~2yoaW1fOYVKj3ZiNTe4UPIAlIReYUMHSnZnloFWX7bh2OoEg08JBGoSQPtGkCZjqSBmfeDdMqtwbZ~~sok-jNo4e5rWnfCOHYYPVcuE8jB~7M5ioJzk8QZRqh3AjCQsKBUZgTzUfGlP12j3GtAf5C9iAGxTTB1sGE96752EKP0dzyGOs4NAujwkgm6NzVoqlkXD~fognUrQOeG~~OqChsAeqIRqj40FsJmsJREmZ4GhjFAqzLUQ4hMpKSbqMI~wtfjeNs-WKtM7pCD09uwSmYwW84wu-WxLGZiIt2GKmbPv~JrqYFNv9EM0SzUonAF8pw9GAhUn8-26kkgCXTs05Kut7NuQHghu3jHfS-frlPmAt-Uu5T4ZcLiHiFrnG2lYTtjxBFXh7W72IBncHSixhVhd4lYM7REKFj7G~5ttW9EBeL1unbNYWiQOEQjtGlmwxYt~~2EV16w339aQQ~S~69-tS6vFA1n2DgkMdg06pBQAEAAEAAA==\n",
39 | Reply{
40 | Topic: "NAMING",
41 | Type: "REPLY",
42 | Pairs: map[string]string{
43 | "RESULT": "OK",
44 | "NAME": "gkso46tc47hdua2kva5zahj3unmyh6ia7bv5oc2ybn2hmeowpz7a.b32.i2p",
45 | "VALUE": "mlHQraXLpcE7A4MVeVniRHM~2yoaW1fOYVKj3ZiNTe4UPIAlIReYUMHSnZnloFWX7bh2OoEg08JBGoSQPtGkCZjqSBmfeDdMqtwbZ~~sok-jNo4e5rWnfCOHYYPVcuE8jB~7M5ioJzk8QZRqh3AjCQsKBUZgTzUfGlP12j3GtAf5C9iAGxTTB1sGE96752EKP0dzyGOs4NAujwkgm6NzVoqlkXD~fognUrQOeG~~OqChsAeqIRqj40FsJmsJREmZ4GhjFAqzLUQ4hMpKSbqMI~wtfjeNs-WKtM7pCD09uwSmYwW84wu-WxLGZiIt2GKmbPv~JrqYFNv9EM0SzUonAF8pw9GAhUn8-26kkgCXTs05Kut7NuQHghu3jHfS-frlPmAt-Uu5T4ZcLiHiFrnG2lYTtjxBFXh7W72IBncHSixhVhd4lYM7REKFj7G~5ttW9EBeL1unbNYWiQOEQjtGlmwxYt~~2EV16w339aQQ~S~69-tS6vFA1n2DgkMdg06pBQAEAAEAAA==",
46 | },
47 | },
48 | },
49 | // session status reply
50 | {
51 | "SESSION STATUS RESULT=I2P_ERROR MESSAGE=TheMessageFromI2p\n",
52 | Reply{
53 | Topic: "SESSION",
54 | Type: "STATUS",
55 | Pairs: map[string]string{
56 | "RESULT": "I2P_ERROR",
57 | "MESSAGE": "TheMessageFromI2p",
58 | },
59 | },
60 | },
61 | {
62 | "STREAM STATUS RESULT=CANT_REACH_PEER\n",
63 | Reply{
64 | Topic: "STREAM",
65 | Type: "STATUS",
66 | Pairs: map[string]string{
67 | "RESULT": "CANT_REACH_PEER",
68 | },
69 | },
70 | },
71 | }
72 |
73 | func TestParseReplyValidCases(t *testing.T) {
74 | for _, tcase := range validCases {
75 | parsed, err := parseReply(tcase.Input)
76 | if err != nil {
77 | t.Fatalf("parseReply should not throw an error!\n%s", err)
78 | }
79 |
80 | if parsed.Topic != tcase.Expected.Topic {
81 | t.Fatalf("Wrong Topic. Got %s expected %s", parsed.Topic, tcase.Expected.Topic)
82 | }
83 |
84 | if len(parsed.Pairs) != len(tcase.Expected.Pairs) {
85 | t.Fatalf("Wrong amount of Pairs. Got %d expected 3", len(parsed.Pairs))
86 | }
87 |
88 | for expK, expV := range tcase.Expected.Pairs {
89 | if expV != parsed.Pairs[expK] {
90 | t.Fatalf("Wrong %s.\nGot '%s'\nExpected '%s'", expK, parsed.Pairs[expK], expV)
91 | }
92 | }
93 | }
94 | }
95 |
96 | func TestParseInvalidReply(t *testing.T) {
97 | str := "asd asd="
98 |
99 | r, err := parseReply(str)
100 | if err == nil {
101 | t.Fatalf("Should throw an error.r:%v\n", r)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/rw.go:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2014 Henry
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | package gosam
26 |
27 | import (
28 | "encoding/hex"
29 | "io"
30 | "log"
31 | )
32 |
33 | /*
34 | Copy of testing/iotest Read- and WriteLogger, but using %q instead of %x for printing
35 | */
36 |
37 | type writeLogger struct {
38 | prefix string
39 | w io.Writer
40 | }
41 |
42 | func (l *writeLogger) Write(p []byte) (n int, err error) {
43 | n, err = l.w.Write(p)
44 | if err != nil {
45 | log.Printf("%s %q: %v", l.prefix, string(p[0:n]), err)
46 | } else {
47 | log.Printf("%s %q", l.prefix, string(p[0:n]))
48 | }
49 | return
50 | }
51 |
52 | // NewWriteLogger returns a writer that behaves like w except
53 | // that it logs (using log.Printf) each write to standard error,
54 | // printing the prefix and the hexadecimal data written.
55 | func NewWriteLogger(prefix string, w io.Writer) io.Writer {
56 | return &writeLogger{prefix, w}
57 | }
58 |
59 | type readLogger struct {
60 | prefix string
61 | r io.Reader
62 | }
63 |
64 | func (l *readLogger) Read(p []byte) (n int, err error) {
65 | n, err = l.r.Read(p)
66 | if err != nil {
67 | log.Printf("%s %q: %v", l.prefix, string(p[0:n]), err)
68 | } else {
69 | log.Printf("%s %q", l.prefix, string(p[0:n]))
70 | }
71 | return
72 | }
73 |
74 | // NewReadLogger returns a reader that behaves like r except
75 | // that it logs (using log.Print) each read to standard error,
76 | // printing the prefix and the hexadecimal data written.
77 | func NewReadLogger(prefix string, r io.Reader) io.Reader {
78 | return &readLogger{prefix, r}
79 | }
80 |
81 | type readHexLogger struct {
82 | prefix string
83 | r io.Reader
84 | }
85 |
86 | func (l *readHexLogger) Read(p []byte) (n int, err error) {
87 | n, err = l.r.Read(p)
88 | if err != nil {
89 | log.Printf("%s (%d bytes) Error: %v", l.prefix, n, err)
90 | } else {
91 | log.Printf("%s (%d bytes)", l.prefix, n)
92 | }
93 | log.Print("\n" + hex.Dump(p[:n]))
94 | return
95 | }
96 |
97 | // NewReadHexLogger returns a reader that behaves like r except
98 | // that it logs to stderr using ecoding/hex.
99 | func NewReadHexLogger(prefix string, r io.Reader) io.Reader {
100 | return &readHexLogger{prefix, r}
101 | }
102 |
--------------------------------------------------------------------------------
/rwc.go:
--------------------------------------------------------------------------------
1 | /*
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2014 Henry
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 | */
24 |
25 | package gosam
26 |
27 | import (
28 | "io"
29 | //"github.com/miolini/datacounter"
30 | )
31 |
32 | type RWC struct {
33 | io.Reader
34 | io.Writer
35 | c io.Closer
36 | }
37 |
38 | func WrapRWC(c io.ReadWriteCloser) io.ReadWriteCloser {
39 | rl := NewReadLogger("<", c)
40 | wl := NewWriteLogger(">", c)
41 |
42 | return &RWC{
43 | Reader: rl,
44 | Writer: wl,
45 | c: c,
46 | }
47 | }
48 |
49 | func (c *RWC) Close() error {
50 | return c.c.Close()
51 | }
52 |
53 | /*
54 | type Counter struct {
55 | io.Reader
56 | io.Writer
57 | c io.Closer
58 |
59 | Cr *datacounter.ReaderCounter
60 | Cw *datacounter.WriterCounter
61 | }
62 |
63 | func WrapCounter(c io.ReadWriteCloser) *Counter {
64 | rc := datacounter.NewReaderCounter(c)
65 | wc := datacounter.NewWriterCounter(c)
66 |
67 | return &Counter{
68 | Reader: rc,
69 | Writer: wc,
70 | c: c,
71 |
72 | Cr: rc,
73 | Cw: wc,
74 | }
75 | }
76 |
77 | func (c *Counter) Close() error {
78 | return c.c.Close()
79 | }
80 | */
81 |
--------------------------------------------------------------------------------
/samsocks/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 |
6 | "github.com/go-i2p/gosam"
7 | "github.com/getlantern/go-socks5"
8 | "log"
9 | )
10 |
11 | var (
12 | samaddr = flag.String("sam", "127.0.0.1:7656", "SAM API address to use")
13 | socksaddr = flag.String("socks", "127.0.0.1:7675", "SOCKS address to use")
14 | )
15 |
16 | func main() {
17 | sam, err := gosam.NewClient(*samaddr)
18 | if err != nil {
19 | panic(err)
20 | }
21 | log.Println("Client Created")
22 |
23 | // create a transport that uses SAM to dial TCP Connections
24 | conf := &socks5.Config{
25 | Dial: sam.DialContext,
26 | Resolver: sam,
27 | }
28 | server, err := socks5.New(conf)
29 | if err != nil {
30 | panic(err)
31 | }
32 |
33 | // Create SOCKS5 proxy on localhost port 8000
34 | if err := server.ListenAndServe("tcp", *socksaddr); err != nil {
35 | panic(err)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/sessions.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "fmt"
5 | // "math"
6 | "math/rand"
7 | "time"
8 | )
9 |
10 | func init() {
11 | rand.Seed(time.Now().UnixNano())
12 | }
13 |
14 | // CreateSession creates a new Session of type style, with an optional destination.
15 | // an empty destination is interpreted as "TRANSIENT"
16 | // Returns the destination for the new Client or an error.
17 | func (c *Client) CreateSession(style, dest string) (string, error) {
18 | if dest == "" {
19 | dest = "TRANSIENT"
20 | }
21 | // c.id = id
22 | r, err := c.sendCmd(
23 | "SESSION CREATE STYLE=%s ID=%s DESTINATION=%s %s %s %s %s \n",
24 | style,
25 | c.ID(),
26 | dest,
27 | c.from(),
28 | c.to(),
29 | c.sigtype(),
30 | c.allOptions(),
31 | )
32 | if err != nil {
33 | return "", err
34 | }
35 |
36 | // TODO: move check into sendCmd()
37 | if r.Topic != "SESSION" || r.Type != "STATUS" {
38 | return "", fmt.Errorf("Session Unknown Reply: %+v\n", r)
39 | }
40 |
41 | result := r.Pairs["RESULT"]
42 | if result != "OK" {
43 | return "", ReplyError{ResultKeyNotFound, r}
44 | }
45 | c.destination = r.Pairs["DESTINATION"]
46 | return c.destination, nil
47 | }
48 |
49 | // CreateStreamSession creates a new STREAM Session.
50 | // Returns the Id for the new Client.
51 | func (c *Client) CreateStreamSession(dest string) (string, error) {
52 | return c.CreateSession("STREAM", dest)
53 | }
54 |
55 | // CreateDatagramSession creates a new DATAGRAM Session.
56 | // Returns the Id for the new Client.
57 | func (c *Client) CreateDatagramSession(dest string) (string, error) {
58 | return c.CreateSession("DATAGRAM", dest)
59 | }
60 |
61 | // CreateRawSession creates a new RAW Session.
62 | // Returns the Id for the new Client.
63 | func (c *Client) CreateRawSession(dest string) (string, error) {
64 | return c.CreateSession("RAW", dest)
65 | }
66 |
--------------------------------------------------------------------------------
/showhider.css:
--------------------------------------------------------------------------------
1 | /* edgar showhider CSS file */
2 | #show {display:none; }
3 | #hide {display:block; }
4 | #show:target {display: block; }
5 | #hide:target {display: none; }
6 |
7 | #shownav {display:none; }
8 | #hidenav {display:block; }
9 | #shownav:target {display: block; }
10 | #hidenav:target {display: none; }
11 |
12 | #donate {display:none; }
13 | #hidedonate {display:block; }
14 | #donate:target {display: block; }
15 | #hidedonate:target {display: none; }
16 |
--------------------------------------------------------------------------------
/stream.go:
--------------------------------------------------------------------------------
1 | package gosam
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // StreamConnect asks SAM for a TCP-Like connection to dest, has to be called on a new Client
8 | func (c *Client) StreamConnect(dest string) error {
9 | if dest == "" {
10 | return nil
11 | }
12 | r, err := c.sendCmd("STREAM CONNECT ID=%s DESTINATION=%s %s %s\n", c.ID(), dest, c.from(), c.to())
13 | if err != nil {
14 | return err
15 | }
16 |
17 | // TODO: move check into sendCmd()
18 | if r.Topic != "STREAM" || r.Type != "STATUS" {
19 | return fmt.Errorf("Stream Connect Unknown Reply: %+v\n", r)
20 | }
21 |
22 | result := r.Pairs["RESULT"]
23 | if result != "OK" {
24 | return ReplyError{result, r}
25 | }
26 |
27 | return nil
28 | }
29 |
30 | // StreamAccept asks SAM to accept a TCP-Like connection
31 | func (c *Client) StreamAccept() (*Reply, error) {
32 | r, err := c.sendCmd("STREAM ACCEPT ID=%s SILENT=false\n", c.ID())
33 | if err != nil {
34 | return nil, err
35 | }
36 |
37 | // TODO: move check into sendCmd()
38 | if r.Topic != "STREAM" || r.Type != "STATUS" {
39 | return nil, fmt.Errorf("Stream Accept Unknown Reply: %+v\n", r)
40 | }
41 |
42 | result := r.Pairs["RESULT"]
43 | if result != "OK" {
44 | return nil, ReplyError{result, r}
45 | }
46 |
47 | return r, nil
48 | }
49 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | /* edgar default CSS file */
2 |
3 | body {
4 | font-family: "Roboto";
5 | font-family: monospace;
6 | text-align: justify;
7 | background-color: #373636;
8 | color: whitesmoke;
9 | font-size: 1.15em;
10 | }
11 |
12 | ul {
13 | width: 55%;
14 | display: block;
15 | }
16 |
17 | ol {
18 | width: 55%;
19 | display: block;
20 | }
21 |
22 | li {
23 | margin-top: 1%;
24 | }
25 |
26 | p {
27 | max-width: 90%;
28 | margin-top: 1%;
29 | margin-left: 3%;
30 | margin-right: 3%;
31 | }
32 |
33 | img {
34 | float: left;
35 | top: 5%;
36 | left: 5%;
37 | max-width: 60%;
38 | display: inline;
39 | padding-right: 2%;
40 | }
41 |
42 | .inline {
43 | display: inline;
44 | }
45 |
46 | .link-button:focus {
47 | outline: none;
48 | }
49 |
50 | .link-button:active {
51 | color: red;
52 | }
53 |
54 | code {
55 | font-family: monospace;
56 | border-radius: 5%;
57 | padding: 1%;
58 | border-color: darkgray;
59 | font-size: .9em;
60 | }
61 |
62 | a {
63 | color: #C6D9FE;
64 | padding: 1%;
65 | }
66 |
67 | ul li {
68 | color: #C6D9FE;
69 | }
70 |
71 | iframe {
72 | background: aliceblue;
73 | border-radius: 15%;
74 | margin: 2%;
75 | }
76 |
77 | .container {
78 | width: 36vw;
79 | height: 64vh;
80 | display: inline-block;
81 | margin: 0;
82 | padding: 0;
83 | }
84 |
85 | .editor-toolbar a {
86 | display: inline-block;
87 | text-align: center;
88 | text-decoration: none !important;
89 | color: whitesmoke !important;
90 | }
91 |
92 | #feed {
93 | width: 60vw;
94 | height: unset !important;
95 | margin: 0;
96 | padding: 0;
97 | float: right;
98 | background-color: #373636;
99 | color: whitesmoke;
100 | border: #C6D9FE solid 1px;
101 | }
102 |
103 | .thread-post,
104 | .thread {
105 | color: whitesmoke !important;
106 | background-color: #373636;
107 | border: 1px solid darkgray;
108 | font-size: inherit;
109 | padding-top: 1%;
110 | padding-bottom: 1%;
111 | }
112 |
113 | .thread-post {
114 | margin-left: 4%;
115 | }
116 |
117 | input {
118 | text-align: center;
119 | color: whitesmoke !important;
120 | background-color: #373636;
121 | border: 1px solid darkgray;
122 | font: normal normal normal 14px/1 FontAwesome;
123 | font-size: inherit;
124 | padding-top: 1%;
125 | padding-bottom: 1%;
126 | }
127 |
128 | .thread-hash {
129 | text-align: right;
130 | color: whitesmoke !important;
131 | background-color: #373636;
132 | border: 1px solid darkgray;
133 | font-size: inherit;
134 | padding-top: 1%;
135 | padding-bottom: 1%;
136 | }
137 |
138 | .post-body {
139 | text-align: left;
140 | color: whitesmoke !important;
141 | font-size: inherit;
142 | padding-top: 1%;
143 | padding-bottom: 1%;
144 | }
145 | #show {display:none; }
146 | #hide {display:block; }
147 | #show:target {display: block; }
148 | #hide:target {display: none; }
149 |
150 | #shownav {display:none; }
151 | #hidenav {display:block; }
152 | #shownav:target {display: block; }
153 | #hidenav:target {display: none; }
154 |
155 | #navbar {
156 | float: right;
157 | width: 15%;
158 | }
159 | #returnhome {
160 | font-size: xxx-large;
161 | display: inline;
162 | }
163 | h1 {
164 | display: inline;
165 | }
166 |
--------------------------------------------------------------------------------