├── .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 | --------------------------------------------------------------------------------