diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1f2d1d --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Created by .ignore support plugin (hsz.mobi) +### Go template +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +.idea + +config.yaml + +.ssh-tunnel.yaml +ssh-tunnel.yaml +main \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f637216 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,84 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish to make via issue, +email, or any other method with the owners of this repository before making a change. + +Please note we have a code of conduct, please follow it in all your interactions with the project. + +## Pull Request Process + +1. Ensure any install or build dependencies are removed before the end of the layer when doing a + build. +2. Update the README.md with details of changes to the interface, this includes new environment + variables, exposed ports, useful file locations and container parameters. +3. Increase the version numbers in any examples files and the README.md to the new version that this + Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). +4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you + do not have permission to do that, you may request the second reviewer to merge it for you. + +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +### Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e8f8de4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2018] [Raúl Sampedro] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a54f469 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# SSH Tunnel + +Emulate `ssh -D` behavior even if `AllowTcpForwarding` is disabled by administrator in `sshd_config`. This tool creates +the tunnel sending serialized data through STDIN to a remote process and receiving the response trought STDOUT on a +normal SSH session channel. + +## Getting Started + +These instructions will get you a copy of the project up and running on your local machine for development and testing +purposes. + +### Prerequisites + +You only need [golang](https://golang.org/dl/) to use this tool. + +### Installing + +Just install the command line tool + +``` +go install github.com/rsrdesarrollo/ssh-tunnel +``` + +### Usage + +*TBD* + +### TODO + +* Support Public key authentication. +* Improve configuration file. +* Add more command options to control binding ports. + +## Contributing + +Please read [CONTRIBUTING.md](https://github.com/rsrdesarrollo/ssh-tunnel/contributors) for details on our code of +conduct, and the process for submitting pull requests to this project. + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the +[tags on this repository](https://github.com/your/project/tags). + +## Authors + +* **Raúl Sampedro** - *Initial work* - [@rsrdesarrollo](https://github.com/rsrdesarrollo) + +## License + +This project is licensed under the Apache License Version 2.0- see the [LICENSE.md](LICENSE.md) file for details diff --git a/agent/agent.go b/agent/agent.go new file mode 100644 index 0000000..6e53fc4 --- /dev/null +++ b/agent/agent.go @@ -0,0 +1,157 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package agent + +import ( + "github.com/armon/go-socks5" + "github.com/rsrdesarrollo/ssh-tunnel/common" + "github.com/rsrdesarrollo/ssh-tunnel/models" + "log" + "net" + "os" + "time" +) + +type agent struct { + channelOpen bool + sockFilePath string + inChannel chan models.DataMessage + outChannel chan models.DataMessage +} + +func newAgent() agent { + return agent{ + channelOpen: false, + sockFilePath: "./daemon_" + common.RandStringRunes(10), + inChannel: make(chan models.DataMessage, 10), + outChannel: make(chan models.DataMessage, 10), + } +} + +func (a *agent) close() { + a.channelOpen = false +} + +func (a *agent) startSocksServer() { + conf := &socks5.Config{ + Logger: log.New(os.Stderr, "", log.LstdFlags), + } + + server, err := socks5.New(conf) + + if err != nil { + common.Logger.Fatal("ERROR Creating socks socksServer: " + err.Error()) + } + + common.Logger.Info("Handling sock connection") + + ln, err := net.Listen("tcp", "127.0.1.1:8888") + + if err != nil { + common.Logger.Fatal("Failed to bind local port " + err.Error()) + } + + common.Logger.Info("Socks porxy listening on 127.0.1.1:8888") + + for { + conn, err := ln.Accept() + if err != nil{ + common.Logger.Fatal("Error accepting socks connection: " + err.Error()) + } + + common.Logger.Info("New socks client from ", conn.RemoteAddr().String()) + go server.ServeConn(conn) + }} + +func (a *agent) handleInOutData() { + clientsMap := make(map[string]models.Client) + + go func() { + common.ReadInputData(a.inChannel, os.Stdin) + a.close() + }() + go func() { + common.WriteOutputData(a.outChannel, os.Stdout) + a.close() + }() + + for a.channelOpen { + msg := <-a.inChannel + client, prs := clientsMap[msg.ClientId] + + if prs == false { + //conn, err := net.Dial("unix", a.sockFilePath) "tcp", "127.0.1.1:8888" + conn, err := net.Dial("tcp", "127.0.1.1:8888") + + if err != nil { + common.Logger.Error("Connection dial error: ", err) + } + + client = models.Client{ + Id: msg.ClientId, + Conn: conn, + OutChann: a.outChannel, + } + + clientsMap[msg.ClientId] = client + + go common.ReadFromClientToChannel(client, a.outChannel) + } + + if len(msg.Data) == 0 { + client.Conn.Close() + delete(clientsMap, msg.ClientId) + } else { + var writed = 0 + for writed < len(msg.Data){ + wn, err := client.Conn.Write(msg.Data[writed:]) + writed += wn + + if err != nil { + client.Close() + delete(clientsMap, msg.ClientId) + break + } + } + + } + } + +} + +func Run() { + + agent := newAgent() + + onExit := func() { + selfFilePath, _ := os.Executable() + os.Remove(agent.sockFilePath) + os.Remove(selfFilePath) + } + + defer onExit() + common.ExitCallback(onExit) + + go agent.startSocksServer() + + time.Sleep(1 * time.Second) + agent.channelOpen = true + + go agent.handleInOutData() + + for agent.channelOpen { + time.Sleep(1 * time.Second) + } +} diff --git a/cmd/agent.go b/cmd/agent.go new file mode 100644 index 0000000..c581ba7 --- /dev/null +++ b/cmd/agent.go @@ -0,0 +1,34 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "github.com/rsrdesarrollo/ssh-tunnel/agent" + "github.com/spf13/cobra" +) + +// agentCmd represents the agent command +var agentCmd = &cobra.Command{ + Use: "agent", + Short: "Run as remote agent process", + Long: `Run as remote agent process`, + Run: func(cmd *cobra.Command, args []string) { + agent.Run() + }, +} + +func init() { + rootCmd.AddCommand(agentCmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..e24819d --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,77 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "os" + + "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "ssh-tunnel", + Short: "Generate SSH Dynamic Tunnels when AllowTcpForwarding is off", + Long: `This tool aims to create a Dynamic Tunnel trougth a shell channel +of SSH using stdin and stdout to transmiti information`, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ssh-tunnel.yaml)") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + viper.SetDefault("RemoteHost", "127.0.0.1:22") + viper.SetDefault("User", "root") + viper.SetDefault("Password", "toor") + + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + // Search config in home directory with name ".ssh-tunnel" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".ssh-tunnel") + } + + viper.AutomaticEnv() // read in environment variables that match + viper.ReadInConfig() +} diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 0000000..4eaf599 --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,40 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "github.com/rsrdesarrollo/ssh-tunnel/server" + "github.com/spf13/cobra" +) + +// serverCmd represents the server command +var serverCmd = &cobra.Command{ + Use: "server", + Short: "A brief description of your command", + Long: ``, + Run: func(cmd *cobra.Command, args []string) { + server.Run() + }, +} + +func init() { + rootCmd.AddCommand(serverCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // serverCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..efbbc6b --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,33 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) +func init() { + rootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of Hugo", + Long: `All software has versions. This is Hugo's`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("ssh-tunnel v0.0.1") + }, +} \ No newline at end of file diff --git a/common/gracefullStop.go b/common/gracefullStop.go new file mode 100644 index 0000000..cc62ad6 --- /dev/null +++ b/common/gracefullStop.go @@ -0,0 +1,39 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "os" + "os/signal" + "syscall" +) + +func ExitCallback(callBack func()) { + + var gracefulStop = make(chan os.Signal) + + signal.Notify(gracefulStop, syscall.SIGTERM) + signal.Notify(gracefulStop, syscall.SIGINT) + signal.Notify(gracefulStop, syscall.SIGPIPE) + signal.Notify(gracefulStop, syscall.SIGKILL) + signal.Notify(gracefulStop, syscall.SIGQUIT) + signal.Notify(gracefulStop, syscall.SIGHUP) + + go func() { + <-gracefulStop + callBack() + os.Exit(0) + }() +} diff --git a/common/ioChannelHandlers.go b/common/ioChannelHandlers.go new file mode 100644 index 0000000..76b0654 --- /dev/null +++ b/common/ioChannelHandlers.go @@ -0,0 +1,87 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "bufio" + "encoding/json" + "github.com/rsrdesarrollo/ssh-tunnel/models" + "io" +) + +func ReadInputData(inChannel chan models.DataMessage, reader io.Reader) { + inReader := bufio.NewReader(reader) + + Logger.Debug("Reading from io.Reader to inChannel") + + for { + var inMsg models.DataMessage + line, err := inReader.ReadBytes('\n') + if err != nil || len(line) == 0 { + Logger.Error("Read ERROR: ", err) + break + } + + err = json.Unmarshal(line, &inMsg) + if err != nil { + Logger.Error("Unmarshal ERROR: ", err) + continue + } + + inChannel <- inMsg + } + +} + +func WriteOutputData(outChannel chan models.DataMessage, writer io.Writer) { + + Logger.Debug("Writing from outChannel to io.Writer") + + for { + outMsg := <-outChannel + data, err := json.Marshal(outMsg) + + if err != nil { + Logger.Error("Marshal ERROR: ", err) + } + + data = append(data, '\n') + writed := 0 + for writed < len(data){ + wn, err := writer.Write(data[writed:]) + writed += wn + + if err != nil { + Logger.Error("Write ERROR: ", err) + break + } + } + } +} +func ReadFromClientToChannel(client models.Client, outChannel chan models.DataMessage) { + for { + data := make([]byte, 1024) + readed, err := client.Conn.Read(data) + if err != nil { + client.Close() + break + } + + outChannel <- models.DataMessage{ + ClientId: client.Id, + Data: data[:readed], + } + } +} diff --git a/common/logger.go b/common/logger.go new file mode 100644 index 0000000..7f60f14 --- /dev/null +++ b/common/logger.go @@ -0,0 +1,36 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "github.com/op/go-logging" + "os" +) + +var Logger = logging.MustGetLogger("ssh-tunnel") + +func init() { + var format = logging.MustStringFormatter( + `%{color}%{time:15:04:05.000} %{program:10s} - %{shortfunc:-20s} ▶ %{level:-8s} %{id:03x}%{color:reset} %{message}`, + ) + + stderrBackend := logging.NewLogBackend(os.Stderr, "", 0) + stderrBackendFormater := logging.NewBackendFormatter(stderrBackend, format) + + stderrBackendLeveled := logging.AddModuleLevel(stderrBackendFormater) + stderrBackendLeveled.SetLevel(logging.DEBUG, "") + + logging.SetBackend(stderrBackendLeveled) +} diff --git a/common/randString.go b/common/randString.go new file mode 100644 index 0000000..6715cc2 --- /dev/null +++ b/common/randString.go @@ -0,0 +1,34 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "math/rand" + "time" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + +func RandStringRunes(length int) string { + b := make([]rune, length) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..224b8b7 --- /dev/null +++ b/main.go @@ -0,0 +1,21 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import "github.com/rsrdesarrollo/ssh-tunnel/cmd" + +func main() { + cmd.Execute() +} diff --git a/models/client.go b/models/client.go new file mode 100644 index 0000000..2532d33 --- /dev/null +++ b/models/client.go @@ -0,0 +1,31 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import "net" + +type Client struct { + Id string + Conn net.Conn + OutChann chan DataMessage +} + +func (c *Client) Close() { + c.OutChann<- DataMessage{ + ClientId: c.Id, + Data: []byte {}, + } + c.Conn.Close() +} \ No newline at end of file diff --git a/models/dataMessage.go b/models/dataMessage.go new file mode 100644 index 0000000..d1c5b56 --- /dev/null +++ b/models/dataMessage.go @@ -0,0 +1,20 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +type DataMessage struct { + ClientId string `json:"i"` + Data []byte `json:"d"` +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..25ce5aa --- /dev/null +++ b/server/server.go @@ -0,0 +1,207 @@ +// Copyright © 2018 Raul Sampedro +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "errors" + "github.com/rsrdesarrollo/ssh-tunnel/common" + "github.com/rsrdesarrollo/ssh-tunnel/models" + "github.com/spf13/viper" + "golang.org/x/crypto/ssh" + "net" + "os" + "sync" +) + +type tunnel struct { + isOpen bool + sshClient *ssh.Client + clients map[string]models.Client + clientsLock *sync.Mutex + inChan chan models.DataMessage + outChan chan models.DataMessage +} + +func newTunnel() *tunnel { + return &tunnel{ + isOpen: false, + clients: make(map[string]models.Client), + clientsLock: &sync.Mutex{}, + inChan: make(chan models.DataMessage, 10), + outChan: make(chan models.DataMessage, 10), + } +} + +func (t *tunnel) openTunnel() error { + var err error + + config := &ssh.ClientConfig{ + User: viper.GetString("User"), + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Auth: []ssh.AuthMethod{ + ssh.Password(viper.GetString("Password")), + }, + } + + t.sshClient, err = ssh.Dial("tcp", viper.GetString("RemoteHost"), config) + + if err != nil { + return errors.New("Dial error: " + err.Error()) + } + + defer t.sshClient.Close() + + session, err := t.sshClient.NewSession() + if err != nil { + return errors.New("Failed to create session: " + err.Error()) + } + + selfFilePath, _ := os.Executable() + selfFile, err := os.Open(selfFilePath) + session.Stdin = selfFile + + if err != nil { + return errors.New("Failed to open current binary " + err.Error()) + } + + err = session.Run("cat > ./daemon") + + session, err = t.sshClient.NewSession() + if err != nil { + return errors.New("Failed to create session: " + err.Error()) + } + + err = session.Run("chmod +x ./daemon") + session.Close() + + if err != nil { + return errors.New("Failed to make daemon executable " + err.Error()) + } + + session, err = t.sshClient.NewSession() + defer session.Close() + + if err != nil { + return errors.New("Failed to create session: " + err.Error()) + } + + remoteStdIn, err := session.StdinPipe() + if err != nil { + return errors.New("Failed to pipe STDIN on session: " + err.Error()) + } + + remoteStdOut, err := session.StdoutPipe() + if err != nil { + return errors.New("Failed to pipe STDOUT on session: " + err.Error()) + } + + session.Stderr = os.Stderr + + go common.ReadInputData(t.inChan, remoteStdOut) + go common.WriteOutputData(t.outChan, remoteStdIn) + + session.Run("./daemon agent") + + t.isOpen = false + return errors.New("Remote process is dead") +} + +func (t *tunnel) handleClients() { + for { + msg := <-t.inChan + + t.clientsLock.Lock() + + client, prs := t.clients[msg.ClientId] + + if prs == false { + common.Logger.Warningf("Received data from closed client %s", client.Id) + // Send an empty message to close remote connection + t.outChan <- models.DataMessage{ + ClientId: client.Id, + Data: []byte{}, + } + + } else if len(msg.Data) == 0 { + client.Conn.Close() + delete(t.clients, msg.ClientId) + } else { + var writed = 0 + for writed < len(msg.Data){ + wn, err := client.Conn.Write(msg.Data[writed:]) + writed += wn + + if err != nil { + client.Close() + delete(t.clients, msg.ClientId) + + common.Logger.Errorf("Error Writing: %s\n", err.Error()) + break + } + } + + } + + t.clientsLock.Unlock() + } +} + +func Run() { + + ln, err := net.Listen("tcp", "127.0.0.1:8080") + + if err != nil { + panic("Failed to bind local port " + err.Error()) + } + + tunnel := newTunnel() + go func() { + err = tunnel.openTunnel() + + if err != nil { + common.Logger.Fatal("Failed to open tunnel ", err.Error()) + } + }() + + onExit := func() { + tunnel.sshClient.Close() + ln.Close() + } + + common.ExitCallback(onExit) + defer onExit() + + go tunnel.handleClients() + + for { + conn, err := ln.Accept() + if err != nil { + common.Logger.Fatalf("Error in conncetion accept: %s", err.Error()) + } + + common.Logger.Info("New connection from ", conn.RemoteAddr().String()) + + client := models.Client{ + Id: conn.RemoteAddr().String(), + Conn: conn, + OutChann: tunnel.outChan, + } + + tunnel.clientsLock.Lock() + tunnel.clients[client.Id] = client + tunnel.clientsLock.Unlock() + go common.ReadFromClientToChannel(client, tunnel.outChan) + } +}