├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── agent
└── agent.go
├── cli
├── agent.go
├── root.go
├── server.go
└── version.go
├── common
├── channelForwarder.go
├── client.go
└── dataMessage.go
├── config_sample.yml
├── logo_full.png
├── main.go
├── server
└── server.go
├── utils
├── gracefullStop.go
├── logger.go
└── randString.go
└── version
└── info.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Go template
3 | # Binaries for programs and plugins
4 | *.exe
5 | *.exe~
6 | *.dll
7 | *.so
8 | *.dylib
9 |
10 | # Test binary, build with `go test -c`
11 | *.test
12 |
13 | # Output of the go coverage tool, specifically when used with LiteIDE
14 | *.out
15 |
16 | .idea
17 |
18 | config.yaml
19 |
20 | .ssh-tunnel.yaml
21 | ssh-tunnel.yaml
22 | main
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
11 | build.
12 | 2. Update the README.md with details of changes to the interface, this includes new environment
13 | variables, exposed ports, useful file locations and container parameters.
14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
17 | do not have permission to do that, you may request the second reviewer to merge it for you.
18 |
19 | ## Code of Conduct
20 |
21 | ### Our Pledge
22 |
23 | In the interest of fostering an open and welcoming environment, we as
24 | contributors and maintainers pledge to making participation in our project and
25 | our community a harassment-free experience for everyone, regardless of age, body
26 | size, disability, ethnicity, gender identity and expression, level of experience,
27 | nationality, personal appearance, race, religion, or sexual identity and
28 | orientation.
29 |
30 | ### Our Standards
31 |
32 | Examples of behavior that contributes to creating a positive environment
33 | include:
34 |
35 | * Using welcoming and inclusive language
36 | * Being respectful of differing viewpoints and experiences
37 | * Gracefully accepting constructive criticism
38 | * Focusing on what is best for the community
39 | * Showing empathy towards other community members
40 |
41 | Examples of unacceptable behavior by participants include:
42 |
43 | * The use of sexualized language or imagery and unwelcome sexual attention or
44 | advances
45 | * Trolling, insulting/derogatory comments, and personal or political attacks
46 | * Public or private harassment
47 | * Publishing others' private information, such as a physical or electronic
48 | address, without explicit permission
49 | * Other conduct which could reasonably be considered inappropriate in a
50 | professional setting
51 |
52 | ### Our Responsibilities
53 |
54 | Project maintainers are responsible for clarifying the standards of acceptable
55 | behavior and are expected to take appropriate and fair corrective action in
56 | response to any instances of unacceptable behavior.
57 |
58 | Project maintainers have the right and responsibility to remove, edit, or
59 | reject comments, commits, code, wiki edits, issues, and other contributions
60 | that are not aligned to this Code of Conduct, or to ban temporarily or
61 | permanently any contributor for other behaviors that they deem inappropriate,
62 | threatening, offensive, or harmful.
63 |
64 | ### Scope
65 |
66 | This Code of Conduct applies both within project spaces and in public spaces
67 | when an individual is representing the project or its community. Examples of
68 | representing a project or community include using an official project e-mail
69 | address, posting via an official social media account, or acting as an appointed
70 | representative at an online or offline event. Representation of a project may be
71 | further defined and clarified by project maintainers.
72 |
73 |
74 | Project maintainers who do not follow or enforce the Code of Conduct in good
75 | faith may face temporary or permanent repercussions as determined by other
76 | members of the project's leadership.
77 |
78 | ### Attribution
79 |
80 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
81 | available at [http://contributor-covenant.org/version/1/4][version]
82 |
83 | [homepage]: http://contributor-covenant.org
84 | [version]: http://contributor-covenant.org/version/1/4/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [2018] [Raúl Sampedro]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Emulate `ssh -D` behavior even if `AllowTcpForwarding` is disabled by administrator in `sshd_config`. This tool creates
6 | the tunnel sending serialized data through STDIN to a remote process (which is running a Socks5 Server) and receiving
7 | the response trought STDOUT on a normal SSH session channel.
8 |
9 | ### Authors
10 | * **Raúl Sampedro** - [@rsrdesarrollo](https://www.linkedin.com/in/rsrdesarrollo/) - Initial Work
11 |
12 | ## Getting Started
13 |
14 | These instructions will get you a copy of the project up and running on your local machine for development and testing
15 | purposes.
16 |
17 | ### Prerequisites
18 |
19 | You only need [golang (>=1.10)](https://golang.org/dl/) to build this tool.
20 |
21 | ### Installing
22 |
23 | Just install the command line tool
24 |
25 | ```
26 | go get github.com/rsrdesarrollo/SaSSHimi
27 | ```
28 |
29 | ### Usage
30 |
31 | Just run it as a normal ssh client
32 |
33 | ```
34 | SaSSHimi server user@localhost
35 | ```
36 |
37 | You can fing more help using `--help`
38 |
39 | ```
40 | $ SaSSHimi server --help
41 |
42 | Run local server to create tunnels
43 |
44 | Usage:
45 | SaSSHimi server [flags]
46 |
47 | Flags:
48 | --bind string Help message for toggle (default "127.0.0.1:1080")
49 | -h, --help help for server
50 | -i, --identity_file string Path to private key
51 |
52 | Global Flags:
53 | --config string config file (default is $HOME/.SaSSHimi.yaml)
54 | -v, --verbose count verbose level
55 | ```
56 |
57 | ### Configuration File
58 |
59 | Like SSH, SaSSHimi has a configuration file where you can set some basic config for your most common hosts.
60 | You can find a sample of the syntax of this file in [config_sample.yml](config_sample.yml).
61 |
62 | By default SaSSHimi try to find this config file at `~/.SaSSHimi.yaml`. You can change this behaviour by using the
63 | `--config` flag.
64 |
65 | **ONLY USE PASSWORDS IN THE CONFIG AT YOUR OWN RISK**
66 |
67 | ### TODO
68 |
69 | - [x] Support Public key authentication.
70 | - [ ] Support Enc Private Keys.
71 | - [x] Improve configuration file.
72 | - [x] Add more command options to control binding ports.
73 | - [ ] Implement known_hosts support
74 |
75 | ## Contributing
76 |
77 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of
78 | conduct, and the process for submitting pull requests to this project.
79 |
80 | ## Versioning
81 |
82 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the
83 | [tags on this repository](https://github.com/rsrdesarrollo/SaSSHimi/tags).
84 |
85 | ## License
86 |
87 | This project is licensed under the Apache License Version 2.0- see the [LICENSE](LICENSE) file for details
88 |
89 | ## Acknowledgments
90 |
91 | - [@maramarillophotography](https://www.instagram.com/maramarillophotography/) for such an amazing logo ;)
92 |
--------------------------------------------------------------------------------
/agent/agent.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package agent
16 |
17 | import (
18 | "github.com/armon/go-socks5"
19 | "github.com/elazarl/goproxy"
20 | "github.com/rsrdesarrollo/SaSSHimi/common"
21 | "github.com/rsrdesarrollo/SaSSHimi/utils"
22 | "log"
23 | "net"
24 | "net/http"
25 | "os"
26 | "sync"
27 | "time"
28 | )
29 |
30 | type agent struct {
31 | common.ChannelForwarder
32 | sockFilePath string
33 | sockFamily string
34 | }
35 |
36 | func newAgent() agent {
37 | return agent{
38 | ChannelForwarder: common.ChannelForwarder{
39 | OutChannel: make(chan *common.DataMessage, 10),
40 | InChannel: make(chan *common.DataMessage, 10),
41 | Reader: os.Stdin,
42 | Writer: os.Stdout,
43 | ChannelOpen: false,
44 | Clients: make(map[string]*common.Client),
45 | ClientsLock: &sync.Mutex{},
46 | },
47 | sockFamily: "unix",
48 | sockFilePath: "./daemon_" + utils.RandStringRunes(10),
49 | }
50 | }
51 |
52 | func (a *agent) runProxyServer(done chan struct{}, useHttpProxy bool) {
53 | ln, err := net.Listen(a.sockFamily, a.sockFilePath)
54 |
55 | if err != nil {
56 | utils.Logger.Fatal("Failed to bind local socket " + err.Error())
57 | }
58 |
59 | utils.Logger.Noticef("Remote proxy server bind at [%s] %s", a.sockFamily, a.sockFilePath)
60 |
61 | if useHttpProxy {
62 | proxy := goproxy.NewProxyHttpServer()
63 |
64 | done <- struct{}{}
65 |
66 | http.Serve(ln, proxy)
67 | } else {
68 | conf := &socks5.Config{
69 | Logger: log.New(os.Stderr, "", log.LstdFlags),
70 | }
71 |
72 | server, err := socks5.New(conf)
73 |
74 | if err != nil {
75 | utils.Logger.Error("ERROR Creating socks socksServer: " + err.Error())
76 | }
77 |
78 | done <- struct{}{}
79 | err = server.Serve(ln)
80 |
81 | if err != nil {
82 | utils.Logger.Error("ERROR Running socks socksServer: " + err.Error())
83 | }
84 | }
85 | }
86 |
87 | func (a *agent) handleInOutData() {
88 | for a.ChannelOpen {
89 | msg := <-a.InChannel
90 |
91 | if msg.KeepAlive {
92 | continue
93 | }
94 |
95 | if msg.CloseChannel {
96 | a.Close()
97 | break
98 | }
99 |
100 | a.ClientsLock.Lock()
101 | client, prs := a.Clients[msg.ClientId]
102 |
103 | if prs == false {
104 | conn, err := net.Dial(a.sockFamily, a.sockFilePath)
105 |
106 | if err != nil {
107 | utils.Logger.Error("Connection dial error: ", err)
108 | a.ClientsLock.Unlock()
109 | continue
110 | }
111 |
112 | client = common.NewClient(
113 | msg.ClientId,
114 | conn,
115 | a.OutChannel,
116 | )
117 |
118 | utils.Logger.Debug("New connection to socks proxy from", conn.LocalAddr().String(), "for client", client.Id)
119 | a.Clients[msg.ClientId] = client
120 |
121 | go client.ReadFromClientToChannel()
122 | }
123 | a.ClientsLock.Unlock()
124 |
125 | if msg.CloseClient {
126 | utils.Logger.Debug("Closing client sock connection for ", client.Id)
127 |
128 | a.ClientsLock.Lock()
129 | delete(a.Clients, msg.ClientId)
130 | a.ClientsLock.Unlock()
131 |
132 | continue
133 | }
134 |
135 | // While receiving data from dead clients ingore it until remote end confirms closure
136 | if !client.IsDead() {
137 | err := client.Write(msg.Data)
138 |
139 | if err != nil {
140 | utils.Logger.Error("Error writing to client connection: ", err.Error())
141 |
142 | client.Terminate()
143 | client.NotifyEOF(true)
144 | }
145 | }
146 |
147 | }
148 | }
149 |
150 | func Run(useHttpProxy bool) {
151 |
152 | agent := newAgent()
153 |
154 | onExit := func() {
155 | utils.Logger.Notice("Agent is closing")
156 | selfFilePath, _ := os.Executable()
157 | os.Remove(agent.sockFilePath)
158 | os.Remove(selfFilePath)
159 | }
160 |
161 | defer onExit()
162 | utils.ExitCallback(onExit)
163 |
164 | proxyReady := make(chan struct{})
165 | go agent.runProxyServer(proxyReady, useHttpProxy)
166 | <-proxyReady
167 |
168 | agent.ChannelOpen = true
169 |
170 | go agent.ReadInputData()
171 | go agent.WriteOutputData()
172 |
173 | go agent.handleInOutData()
174 |
175 | for agent.ChannelOpen {
176 | time.Sleep(1 * time.Second)
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/cli/agent.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package cli
16 |
17 | import (
18 | "github.com/rsrdesarrollo/SaSSHimi/agent"
19 | "github.com/spf13/cobra"
20 | )
21 |
22 | var useHttpProxy bool
23 |
24 | // agentCmd represents the agent command
25 | var agentCmd = &cobra.Command{
26 | Use: "agent",
27 | Short: "Run as remote agent process",
28 | Run: func(cmd *cobra.Command, args []string) {
29 | agent.Run(useHttpProxy)
30 | },
31 | }
32 |
33 | func init() {
34 | rootCmd.AddCommand(agentCmd)
35 |
36 | agentCmd.Flags().BoolVar(&useHttpProxy, "use-http", false, "Use HTTP proxy instead of HTTP")
37 | }
38 |
--------------------------------------------------------------------------------
/cli/root.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package cli
16 |
17 | import (
18 | "fmt"
19 | "github.com/op/go-logging"
20 | "os"
21 |
22 | "github.com/mitchellh/go-homedir"
23 | "github.com/spf13/cobra"
24 | "github.com/spf13/viper"
25 | )
26 |
27 | var cfgFile string
28 | var verboseLevel int
29 |
30 | // rootCmd represents the base command when called without any subcommands
31 | var rootCmd = &cobra.Command{
32 | Use: os.Args[0],
33 | Short: "Generate SSH Dynamic Tunnels when AllowTcpForwarding is off",
34 | Long: `This tool aims to create a Dynamic Tunnel trougth a shell channel
35 | of SSH using stdin and stdout to transmiti information`,
36 | }
37 |
38 | // Execute adds all child cli to the root command and sets flags appropriately.
39 | // This is called by main.main(). It only needs to happen once to the rootCmd.
40 | func Execute() {
41 | if err := rootCmd.Execute(); err != nil {
42 | fmt.Println(err)
43 | os.Exit(1)
44 | }
45 | }
46 |
47 | func init() {
48 | cobra.OnInitialize(initConfig)
49 |
50 | // Here you will define your flags and configuration settings.
51 | // Cobra supports persistent flags, which, if defined here,
52 | // will be global for your application.
53 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.SaSSHimi.yaml)")
54 | rootCmd.PersistentFlags().CountVarP(&verboseLevel, "verbose", "v", "verbose level")
55 | }
56 |
57 | // initConfig reads in config file and ENV variables if set.
58 | func initConfig() {
59 |
60 | if cfgFile != "" {
61 | // Use config file from the flag.
62 | viper.SetConfigFile(cfgFile)
63 | } else {
64 | // Find home directory.
65 | home, err := homedir.Dir()
66 | if err != nil {
67 | fmt.Println(err)
68 | os.Exit(1)
69 | }
70 | // Search config in home directory with name ".ssh-tunnel" (without extension).
71 | viper.AddConfigPath(home)
72 | viper.SetConfigName(".SaSSHimi")
73 | }
74 |
75 | viper.AutomaticEnv() // read in environment variables that match
76 | viper.ReadInConfig()
77 |
78 | if verboseLevel == 0 {
79 | logging.SetLevel(logging.NOTICE, "SaSSHimi")
80 | } else if verboseLevel == 1 {
81 | logging.SetLevel(logging.INFO, "SaSSHimi")
82 | } else {
83 | logging.SetLevel(logging.DEBUG, "SaSSHimi")
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/cli/server.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package cli
16 |
17 | import (
18 | "github.com/rsrdesarrollo/SaSSHimi/server"
19 | "github.com/rsrdesarrollo/SaSSHimi/utils"
20 | "github.com/spf13/cobra"
21 | "github.com/spf13/viper"
22 | "strings"
23 | )
24 |
25 | var bindAddress string
26 | var idFile string
27 |
28 | // serverCmd represents the server command
29 | var serverCmd = &cobra.Command{
30 | Use: "server ",
31 | Short: "Run local server to create tunnels",
32 | Long: ``,
33 | Args: cobra.ExactArgs(1),
34 | Run: func(cmd *cobra.Command, args []string) {
35 | tokens := strings.Split(args[0], "@")
36 |
37 | user, remoteHost := strings.Join(tokens[:len(tokens)-1], "@"), tokens[len(tokens)-1]
38 |
39 | subv := viper.Sub(remoteHost)
40 |
41 | if subv == nil {
42 | subv = viper.GetViper()
43 | }
44 |
45 | utils.Logger.Debug("Parsed User:", user)
46 | utils.Logger.Debug("Parsed Remote Host:", remoteHost)
47 |
48 | if user != "" {
49 | subv.Set("User", user)
50 | }
51 |
52 | subv.SetDefault("RemoteHost", remoteHost)
53 | subv.SetDefault("PrivateKey", idFile)
54 |
55 | server.Run(subv, bindAddress, verboseLevel)
56 | },
57 | }
58 |
59 | func init() {
60 | rootCmd.AddCommand(serverCmd)
61 |
62 | serverCmd.Flags().StringVar(&bindAddress, "bind", "127.0.0.1:1080", "Set local bind address and port")
63 | serverCmd.Flags().StringVarP(&idFile, "identity_file", "i", "", "Path to private key")
64 | }
65 |
--------------------------------------------------------------------------------
/cli/version.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package cli
16 |
17 | import (
18 | "fmt"
19 | "github.com/rsrdesarrollo/SaSSHimi/version"
20 | "github.com/spf13/cobra"
21 | )
22 |
23 | func init() {
24 | rootCmd.AddCommand(versionCmd)
25 | }
26 |
27 | var versionCmd = &cobra.Command{
28 | Use: "version",
29 | Short: "Print the version number of Hugo",
30 | Long: `All software has versions. This is Hugo's`,
31 | Run: func(cmd *cobra.Command, args []string) {
32 | fmt.Println(version.ToolName, version.VersionTag)
33 | fmt.Println("Created by", version.Author)
34 | fmt.Println(version.RepoURL)
35 | },
36 | }
37 |
--------------------------------------------------------------------------------
/common/channelForwarder.go:
--------------------------------------------------------------------------------
1 | package common
2 |
3 | import (
4 | "encoding/gob"
5 | "github.com/rsrdesarrollo/SaSSHimi/utils"
6 | "io"
7 | "sync"
8 | "time"
9 | )
10 |
11 | type ChannelForwarder struct {
12 | InChannel chan *DataMessage
13 | OutChannel chan *DataMessage
14 | Reader io.Reader
15 | Writer io.Writer
16 | ChannelOpen bool
17 |
18 | NotifyCousure chan struct{}
19 |
20 | Clients map[string]*Client
21 | ClientsLock *sync.Mutex
22 | }
23 |
24 | func (c *ChannelForwarder) ReadInputData() {
25 | decoder := gob.NewDecoder(c.Reader)
26 |
27 | utils.Logger.Debug("Reading from io.Reader to InChannel")
28 |
29 | for c.ChannelOpen {
30 | var inMsg DataMessage
31 | err := decoder.Decode(&inMsg)
32 | if err != nil {
33 | utils.Logger.Error("Read ERROR: ", err)
34 | break
35 | }
36 | c.InChannel <- &inMsg
37 | }
38 |
39 | c.Close()
40 | }
41 |
42 | func (c *ChannelForwarder) WriteOutputData() {
43 | encoder := gob.NewEncoder(c.Writer)
44 |
45 | utils.Logger.Debug("Writing from OutChannel to io.Writer")
46 |
47 | for c.ChannelOpen {
48 | outMsg := <-c.OutChannel
49 | err := encoder.Encode(outMsg)
50 |
51 | if err != nil {
52 | utils.Logger.Error("Write ERROR: ", err)
53 | break
54 | }
55 | }
56 |
57 | c.Close()
58 | }
59 |
60 | func (c *ChannelForwarder) Close() {
61 | c.ChannelOpen = false
62 | }
63 |
64 | func (c *ChannelForwarder) Terminate() {
65 | msg := NewMessage("", nil)
66 | msg.CloseChannel = true
67 |
68 | c.OutChannel <- msg
69 | }
70 |
71 | func (c *ChannelForwarder) KeepAlive(){
72 | for c.ChannelOpen {
73 | c.sendKeepAlive()
74 | time.Sleep(30 * time.Second)
75 | }
76 | }
77 |
78 | func (c *ChannelForwarder) sendKeepAlive() {
79 | msg := NewMessage("", nil)
80 | msg.KeepAlive = true
81 |
82 | c.OutChannel <- msg
83 | }
84 |
--------------------------------------------------------------------------------
/common/client.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package common
16 |
17 | import (
18 | "github.com/rsrdesarrollo/SaSSHimi/utils"
19 | "net"
20 | "sync"
21 | )
22 |
23 | type Client struct {
24 | Id string
25 | conn net.Conn
26 | outChann chan *DataMessage
27 | inChann chan *DataMessage
28 | readyToClose bool
29 | isDead bool
30 | clientMutex *sync.Mutex
31 | }
32 |
33 | func (c *Client) IsDead() bool {
34 | return c.isDead
35 | }
36 |
37 | func (c *Client) ReadyToClose() bool {
38 | return c.readyToClose
39 | }
40 |
41 | func (c *Client) SetReadyToClose(readyToClose bool) {
42 | c.readyToClose = readyToClose
43 | }
44 |
45 | func NewClient(id string, conn net.Conn, outChannel chan *DataMessage) *Client {
46 | return &Client{
47 | Id: id,
48 | conn: conn,
49 | outChann: outChannel,
50 | readyToClose: false,
51 | clientMutex: &sync.Mutex{},
52 | }
53 | }
54 |
55 | func (c *Client) Terminate() {
56 | c.isDead = true
57 | c.conn.Close()
58 | }
59 |
60 | func (c *Client) Close() {
61 | var mustBeClosed bool
62 |
63 | c.clientMutex.Lock()
64 | if c.ReadyToClose() {
65 | mustBeClosed = true
66 | } else {
67 | mustBeClosed = false
68 | c.readyToClose = true
69 |
70 | utils.Logger.Debug("First attempt to close", c.Id)
71 | }
72 | c.clientMutex.Unlock()
73 |
74 | if mustBeClosed {
75 | utils.Logger.Debug("Really closing", c.Id)
76 | c.conn.Close()
77 | }
78 |
79 | }
80 |
81 | func (c *Client) Write(data []byte) error {
82 | var writed = 0
83 | for writed < len(data) {
84 | wn, err := c.conn.Write(data)
85 | writed += wn
86 |
87 | if writed < len(data) {
88 | utils.Logger.Debugf("******* Need second write of %d bytes on client %s", len(data)-writed, c.Id)
89 | }
90 |
91 | if err != nil {
92 | return err
93 | }
94 | }
95 | return nil
96 | }
97 |
98 | func (c *Client) NotifyEOF(isDead bool) {
99 | msg := NewMessage(c.Id, []byte{})
100 | if !isDead {
101 | msg.CloseClient = true
102 | } else {
103 | msg.DeadClient = isDead
104 | }
105 | c.outChann <- msg
106 | }
107 |
108 | func (c *Client) ReadFromClientToChannel() {
109 | for {
110 | data := make([]byte, 1024)
111 | readed, err := c.conn.Read(data)
112 | if err != nil {
113 | c.Close()
114 | c.NotifyEOF(false)
115 | break
116 | }
117 |
118 | c.outChann <- NewMessage(c.Id, data[:readed])
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/common/dataMessage.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package common
16 |
17 | func NewMessage(clientId string, data []byte) *DataMessage {
18 | return &DataMessage{
19 | ClientId: clientId,
20 | Data: data,
21 | CloseClient: false,
22 | DeadClient: false,
23 | CloseChannel: false,
24 | }
25 | }
26 |
27 | type DataMessage struct {
28 | ClientId string
29 | CloseClient bool
30 | DeadClient bool
31 | Data []byte
32 | CloseChannel bool
33 | KeepAlive bool
34 | }
35 |
--------------------------------------------------------------------------------
/config_sample.yml:
--------------------------------------------------------------------------------
1 | custom_name:
2 | User: "myuser"
3 | Password: "mysecret"
4 | RemoteHost: "example2.com:22443"
5 | custom_example_pk:
6 | User: "myuser"
7 | PrivateKey: "~/ssh/id_rsa"
8 | RemoteHost: "example2.com:22443"
--------------------------------------------------------------------------------
/logo_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TarlogicSecurity/SaSSHimi/e6e2b369322b6fd10a1192d032456915697dba6f/logo_full.png
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import "github.com/rsrdesarrollo/SaSSHimi/cli"
18 |
19 | func main() {
20 | cli.Execute()
21 | }
22 |
--------------------------------------------------------------------------------
/server/server.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package server
16 |
17 | import (
18 | "errors"
19 | "fmt"
20 | "github.com/rsrdesarrollo/SaSSHimi/common"
21 | "github.com/rsrdesarrollo/SaSSHimi/utils"
22 | "github.com/spf13/viper"
23 | "golang.org/x/crypto/ssh"
24 | "golang.org/x/crypto/ssh/terminal"
25 | "golang.org/x/sys/unix"
26 | "io/ioutil"
27 | "net"
28 | "os"
29 | user2 "os/user"
30 | "strings"
31 | "sync"
32 | "syscall"
33 | "time"
34 | )
35 |
36 | type tunnel struct {
37 | common.ChannelForwarder
38 | sshClient *ssh.Client
39 | sshSession *ssh.Session
40 | viper *viper.Viper
41 | }
42 |
43 | func newTunnel(viper *viper.Viper) *tunnel {
44 | return &tunnel{
45 | ChannelForwarder: common.ChannelForwarder{
46 | OutChannel: make(chan *common.DataMessage, 10),
47 | InChannel: make(chan *common.DataMessage, 10),
48 |
49 | ChannelOpen: true,
50 | ClientsLock: &sync.Mutex{},
51 | Clients: make(map[string]*common.Client),
52 |
53 | NotifyCousure: make(chan struct{}),
54 | },
55 | viper: viper,
56 | }
57 | }
58 |
59 | func (t *tunnel) getRemoteHost() string {
60 | remoteHost := t.viper.GetString("RemoteHost")
61 | if !strings.Contains(remoteHost, ":") {
62 | remoteHost = remoteHost + ":22"
63 | }
64 |
65 | utils.Logger.Debug("SSH Remote Host:", remoteHost)
66 | return remoteHost
67 | }
68 |
69 | func (t *tunnel) getUsername() string {
70 | user := t.viper.GetString("User")
71 | if user == "" {
72 | user, _ := user2.Current()
73 | return user.Name
74 | }
75 | utils.Logger.Debug("SSH User:", user)
76 | return user
77 | }
78 |
79 | func (t *tunnel) getPassword() string {
80 | password := t.viper.GetString("Password")
81 | if password == "" {
82 | fmt.Printf("%s@%s's password: ", t.getUsername(), t.getRemoteHost())
83 | bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
84 | fmt.Println("")
85 | password = string(bytePassword)
86 | }
87 | return password
88 | }
89 |
90 | func (t *tunnel) getPublicKey() ssh.Signer {
91 | pkFilePath := t.viper.GetString("PrivateKey")
92 |
93 | if pkFilePath == "" {
94 | return nil
95 | }
96 |
97 | key, err := ioutil.ReadFile(pkFilePath)
98 | if err != nil {
99 | utils.Logger.Fatalf("unable to read private key: %v", err)
100 | }
101 |
102 | // Create the Signer for this private key.
103 | signer, err := ssh.ParsePrivateKey(key)
104 | if err != nil {
105 | utils.Logger.Fatalf("unable to parse private key: %v", err)
106 | }
107 |
108 | return signer
109 | }
110 |
111 | func (t *tunnel) uploadForwarder() error {
112 | session, err := t.sshClient.NewSession()
113 | defer session.Close()
114 | if err != nil {
115 | return errors.New("Failed to create session: " + err.Error())
116 | }
117 |
118 | selfFilePath, _ := os.Executable()
119 | selfFile, err := os.Open(selfFilePath)
120 | session.Stdin = selfFile
121 |
122 | if err != nil {
123 | return errors.New("Failed to open current binary " + err.Error())
124 | }
125 |
126 | err = session.Run("cat > ./.daemon && chmod +x ./.daemon")
127 |
128 | return err
129 | }
130 |
131 | func (t *tunnel) openTunnel(verboseLevel int) error {
132 | var err error
133 |
134 | var authMethods = []ssh.AuthMethod{}
135 |
136 | pkSigner := t.getPublicKey()
137 | if pkSigner != nil {
138 | authMethods = append(authMethods, ssh.PublicKeys(pkSigner))
139 | }
140 | authMethods = append(authMethods, ssh.Password(t.getPassword()))
141 |
142 | config := &ssh.ClientConfig{
143 | User: t.getUsername(),
144 | HostKeyCallback: ssh.InsecureIgnoreHostKey(),
145 | Auth: authMethods,
146 | }
147 |
148 | t.sshClient, err = ssh.Dial("tcp", t.getRemoteHost(), config)
149 |
150 | if err != nil {
151 | return errors.New("Dial error: " + err.Error())
152 | }
153 |
154 | defer t.sshClient.Close()
155 |
156 | err = t.uploadForwarder()
157 | if err != nil {
158 | return errors.New("Failed to upload forwarder " + err.Error())
159 | }
160 |
161 | t.sshSession, err = t.sshClient.NewSession()
162 | defer t.sshSession.Close()
163 |
164 | if err != nil {
165 | return errors.New("Failed to create session: " + err.Error())
166 | }
167 |
168 | t.Writer, err = t.sshSession.StdinPipe()
169 | if err != nil {
170 | return errors.New("Failed to pipe STDIN on session: " + err.Error())
171 | }
172 |
173 | t.Reader, err = t.sshSession.StdoutPipe()
174 | if err != nil {
175 | return errors.New("Failed to pipe STDOUT on session: " + err.Error())
176 | }
177 |
178 | t.sshSession.Stderr = os.Stderr
179 |
180 | go t.ReadInputData()
181 | go t.WriteOutputData()
182 |
183 | utils.Logger.Notice("SSH Tunnel Open")
184 |
185 | var runCommand = "./.daemon agent %s"
186 | var commandOps = ""
187 |
188 | if verboseLevel != 0 {
189 | commandOps = "-" + strings.Repeat("v", verboseLevel)
190 | }
191 |
192 | t.sshSession.Run(fmt.Sprintf(runCommand, commandOps))
193 |
194 | t.ChannelOpen = false
195 | t.NotifyCousure <- struct{}{}
196 |
197 | return errors.New("Remote process is dead")
198 | }
199 |
200 | func (t *tunnel) handleClients() {
201 | for t.ChannelOpen {
202 | msg := <-t.InChannel
203 |
204 | if msg.KeepAlive {
205 | continue
206 | }
207 |
208 | t.ClientsLock.Lock()
209 |
210 | client, prs := t.Clients[msg.ClientId]
211 |
212 | if prs == false {
213 | utils.Logger.Warning("Received data from closed client", msg.ClientId)
214 | } else {
215 | if msg.DeadClient {
216 | // ACK for client termination
217 | client.NotifyEOF(false)
218 | client.Terminate()
219 | delete(t.Clients, msg.ClientId)
220 | } else if msg.CloseClient {
221 | client.Close()
222 | delete(t.Clients, msg.ClientId)
223 | } else if !client.IsDead() {
224 | err := client.Write(msg.Data)
225 |
226 | if err != nil {
227 | client.Terminate()
228 | client.NotifyEOF(true)
229 |
230 | utils.Logger.Errorf("Error Writing: %s\n", err.Error())
231 | }
232 |
233 | }
234 | }
235 |
236 | t.ClientsLock.Unlock()
237 | }
238 | }
239 |
240 | func Run(viper *viper.Viper, bindAddress string, verboseLevel int) {
241 |
242 | ln, err := net.Listen("tcp", bindAddress)
243 |
244 | if err != nil {
245 | panic("Failed to bind local port " + err.Error())
246 | }
247 |
248 | utils.Logger.Notice("Proxy bind at", bindAddress)
249 |
250 | tunnel := newTunnel(viper)
251 |
252 | termios, _ := unix.IoctlGetTermios(int(syscall.Stdin), unix.TCGETS)
253 | onExit := func() {
254 | unix.IoctlSetTermios(int(syscall.Stdin), unix.TCGETS, termios)
255 | tunnel.Terminate()
256 |
257 | utils.Logger.Notice("Waiting to remote process to clean up...")
258 | select {
259 | case <-tunnel.NotifyCousure:
260 | case <-time.After(5 * time.Second):
261 | tunnel.sshSession.Signal(ssh.SIGTERM)
262 | utils.Logger.Warning("Remote close timeout. Sending TERM signal.")
263 | }
264 |
265 | select {
266 | case <-tunnel.NotifyCousure:
267 | case <-time.After(5 * time.Second):
268 | utils.Logger.Error("Remote process don't respond. Force close channel.")
269 | utils.Logger.Error("IMPORTANT: This might leave files in remote host.")
270 | tunnel.sshSession.Close()
271 | }
272 |
273 | tunnel.sshClient.Close()
274 | ln.Close()
275 | }
276 |
277 | utils.ExitCallback(onExit)
278 |
279 | go func() {
280 | err = tunnel.openTunnel(verboseLevel)
281 |
282 | if err != nil {
283 | utils.Logger.Fatal("Failed to open tunnel ", err.Error())
284 | }
285 | }()
286 |
287 | go tunnel.handleClients()
288 | go tunnel.KeepAlive()
289 |
290 | for tunnel.ChannelOpen {
291 | conn, err := ln.Accept()
292 | if err != nil {
293 | utils.Logger.Fatalf("Error in conncetion accept: %s", err.Error())
294 | continue
295 | }
296 |
297 | utils.Logger.Debug("New connection from ", conn.RemoteAddr().String())
298 |
299 | client := common.NewClient(
300 | conn.RemoteAddr().String(),
301 | conn,
302 | tunnel.OutChannel,
303 | )
304 |
305 | tunnel.Clients[client.Id] = client
306 | go client.ReadFromClientToChannel()
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/utils/gracefullStop.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package utils
16 |
17 | import (
18 | "os"
19 | "os/signal"
20 | "syscall"
21 | )
22 |
23 | func ExitCallback(callBack func()) {
24 |
25 | var gracefulStop = make(chan os.Signal)
26 |
27 | signal.Notify(gracefulStop, syscall.SIGTERM)
28 | signal.Notify(gracefulStop, syscall.SIGINT)
29 | signal.Notify(gracefulStop, syscall.SIGKILL)
30 | signal.Notify(gracefulStop, syscall.SIGQUIT)
31 | signal.Notify(gracefulStop, syscall.SIGHUP)
32 |
33 | go func() {
34 | <-gracefulStop
35 | callBack()
36 | os.Exit(0)
37 | }()
38 | }
39 |
--------------------------------------------------------------------------------
/utils/logger.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package utils
16 |
17 | import (
18 | "github.com/op/go-logging"
19 | "os"
20 | )
21 |
22 | var Logger = logging.MustGetLogger("SaSSHimi")
23 |
24 | func init() {
25 | var format = logging.MustStringFormatter(
26 | `%{color}%{time:15:04:05.000} %{program:10s} - %{shortfunc:-20s} ▶ %{level:-8s} %{id:03x}%{color:reset} %{message}`,
27 | )
28 |
29 | logfile, _ := os.Open("/tmp/" + os.Args[0] + ".log")
30 |
31 | stderrBackend := logging.NewLogBackend(os.Stderr, "", 0)
32 | fileBackend := logging.NewLogBackend(logfile, "", 0)
33 |
34 | stderrBackendFormater := logging.NewBackendFormatter(stderrBackend, format)
35 |
36 | stderrBackendLeveled := logging.AddModuleLevel(stderrBackendFormater)
37 |
38 | logging.SetBackend(stderrBackendLeveled, fileBackend)
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/utils/randString.go:
--------------------------------------------------------------------------------
1 | // Copyright © 2018 Raul Sampedro
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package utils
16 |
17 | import (
18 | "math/rand"
19 | "time"
20 | )
21 |
22 | func init() {
23 | rand.Seed(time.Now().UnixNano())
24 | }
25 |
26 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
27 |
28 | func RandStringRunes(length int) string {
29 | b := make([]rune, length)
30 | for i := range b {
31 | b[i] = letterRunes[rand.Intn(len(letterRunes))]
32 | }
33 | return string(b)
34 | }
35 |
--------------------------------------------------------------------------------
/version/info.go:
--------------------------------------------------------------------------------
1 | package version
2 |
3 | var VersionTag = "v1.0.1"
4 | var ToolName = "SaSSHimi"
5 | var Author = "@rsrdesarrollo"
6 | var RepoURL = "https://github.com/rsrdesarrollo/SaSSHimi"
7 |
--------------------------------------------------------------------------------