├── .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 | 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 |
351 | 352 | 353 | I2P 354 | 355 |
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 | [![Go Report Card](https://goreportcard.com/badge/github.com/go-i2p/gosam)](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 | --------------------------------------------------------------------------------