Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
ikr4-m committed Aug 6, 2024
2 parents 5786966 + abec0fa commit f408cc4
Show file tree
Hide file tree
Showing 21 changed files with 1,049 additions and 8 deletions.
8 changes: 1 addition & 7 deletions .github/workflows/build-image-broker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,9 @@ on:
- master

jobs:
build-broker:
uses: ./.github/workflows/build-push.yml
with:
folder_path: server/
secrets: inherit

build-sshd:
uses: ./.github/workflows/build-push.yml
with:
folder_path: tunnel_image/
folder_path: .
images_name: ghcr.io/${{ github.repository }}-sshd-tunnel
secrets: inherit
28 changes: 28 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Build tunnel
FROM docker.io/library/golang:latest AS build
WORKDIR /build
COPY . .

RUN make build-tunnel; \
chmod +x mdrop-tunnel;

# Create Private Environment
FROM docker.io/library/alpine:latest
LABEL org.opencontainers.image.authors="Ikramullah <[email protected]>,Syahrial Agni Prasetya <[email protected]>"
WORKDIR /

RUN set -ex; \
apk update; \
apk add openssh bash;

COPY ./tunnel.conf /etc/ssh/sshd_config.d/
COPY ./entrypoint.sh .
COPY --from=build /build/mdrop-tunnel /usr/bin/mdrop-tunnel

RUN adduser tunnel -HD; \
passwd tunnel -d; \
ssh-keygen -A;

EXPOSE 22

ENTRYPOINT [ "/entrypoint.sh" ]
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm:7 linux:arm:5 linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 android:arm64

restore:
go get -C ./cmd/mdrop-client
go get -C ./cmd/mdrop-tunnel-tools
go get -C ./internal

build-client: restore
go build -C ./cmd/mdrop-client -ldflags="-linkmode external -extldflags -static -w -s" -o "../../mdrop"

build-tunnel: restore
go build -C ./cmd/mdrop-tunnel-tools -ldflags="-linkmode external -extldflags -static -w -s" -o "../../mdrop-tunnel"

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# mdrop

TCP-over-SSH P2P Tunneling Based File Transfer. Inspired by ShareDrop and Apple AirDrop. Written on Golang and C#.
TCP-over-SSH P2P Tunneling Based File Transfer. Inspired by ShareDrop and Apple AirDrop.

*Reserved.*
89 changes: 89 additions & 0 deletions cmd/mdrop-client/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package main

import (
"bufio"
"errors"
"fmt"
"os"
"strconv"
"strings"

"github.com/mplus-oss/mdrop/internal"
)

func AuthCommand() {
errChan := make(chan error, 0)

// Set prompt
config, err := authPrompt()
if err != nil {
internal.PrintErrorWithExit("authPromptError", err, 1)
}

// Connect to tunnel
fmt.Println("Connecting to tunnel...")
sshConfig := internal.SSHParameter{
ConfigFile: config,
Command: []string{"ping"},
}
go func() {
sshOutput, err := StartShellTunnel(sshConfig, false)
if err != nil {
errChan <- err
}
if strings.Contains(sshOutput, "Pong!") {
errChan <- nil
} else {
errChan <- errors.New("Unexpected output from Tunnel")
}
}()
err = <-errChan
if err != nil {
internal.PrintErrorWithExit("authTunnelError", err, 1)
}

// Write config file
err = config.WriteConfig()
if err != nil {
internal.PrintErrorWithExit("authWriteConfig", err, 1)
}
}

func authPrompt() (config internal.ConfigFile, err error) {
reader := bufio.NewReader(os.Stdin)

fmt.Print("Hostname: ")
hostname, err := reader.ReadString('\n')
if err != nil {
return internal.ConfigFile{}, err
}
hostname = strings.Replace(hostname, "\n", "", -1)

fmt.Print("Port [22]: ")
port, err := reader.ReadString('\n')
if err != nil {
return internal.ConfigFile{}, err
}
port = strings.Replace(port, "\n", "", -1)
if port == "" {
port = "22"
}
portInt, err := strconv.Atoi(port)
if err != nil {
return internal.ConfigFile{}, err
}

fmt.Print("Proxy [Set blank if none]: ")
proxy, err := reader.ReadString('\n')
if err != nil {
return internal.ConfigFile{}, err
}
proxy = strings.Replace(proxy, "\n", "", -1)

config = internal.ConfigFile{
Host: hostname,
Port: portInt,
Proxy: proxy,
}
return config, nil
}
177 changes: 177 additions & 0 deletions cmd/mdrop-client/get_command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package main

import (
"bufio"
"crypto/sha256"
"errors"
"flag"
"fmt"
"io"
"net/http"
"os"
"strings"

"github.com/mplus-oss/mdrop/internal"
"github.com/schollz/progressbar/v3"
)

func GetCommand(args []string) {
errChan := make(chan error, 1)

reader := bufio.NewReader(os.Stdin)
flag := flag.NewFlagSet("mdrop get", flag.ExitOnError)
var (
help = flag.Bool("help", false, "Print this message")
fileNameOpt = flag.String("file", "", "Set filename")
localPort = flag.Int("localPort", 6000, "Specified sender port remoted on local")
)
flag.Parse(args)

token := flag.Arg(0)
if *help || token == "" {
fmt.Println("Command: mdrop get [options] <token>")
flag.Usage()
os.Exit(1)
}

// Parse token
sender := TokenTransferJSON{}
err := sender.ParseToken(token, &sender)
if err != nil {
internal.PrintErrorWithExit("getParseTokenError", err, 1)
}

// Parse Config File
var config internal.ConfigFile
err = config.ParseConfig(&config)
if err != nil {
internal.PrintErrorWithExit("getParseConfigError", err, 1)
}
if sender.Host != config.Host {
internal.PrintErrorWithExit("getHostNotMatch", errors.New("Host not match"), 1)
}

// Create tunnel before remote
fmt.Println("Connecting to tunnel for fetch port...")
sshConfig := internal.SSHParameter{
ConfigFile: config,
Command: []string{"connect"},
LocalPort: *localPort,
RemotePort: sender.RemotePort,
IsRemote: false,
}
go func() {
_, err := StartShellTunnel(sshConfig, true)
if err != nil {
errChan <- err
}
}()

// Check tunnel
fmt.Println("Connecting to sender...")
GetTcpReadyConnect(*localPort)
client := http.Client{}
resp, err := client.Get(
fmt.Sprintf("http://localhost:%v/checksum", *localPort),
)
if err != nil {
internal.PrintErrorWithExit("sendHttpClientChecksum", err, 1)
}
if resp.StatusCode != http.StatusOK {
internal.PrintErrorWithExit("sendHttpClientResponseChecksum", err, 1)
}
checksumBytes, err := io.ReadAll(resp.Body)
if err != nil {
internal.PrintErrorWithExit("sendHttpClientReadChecksum", err, 1)
}
checksum := string(checksumBytes)

resp, err = client.Post(
fmt.Sprintf("http://localhost:%v/receive", *localPort),
"binary/octet-stream",
nil,
)
if err != nil {
internal.PrintErrorWithExit("sendHttpClient", err, 1)
}
if resp.StatusCode != http.StatusOK {
internal.PrintErrorWithExit("sendHttpClientResponse", err, 1)
}
defer resp.Body.Close()

// Set filename from header or from output
fileName := resp.Header.Get("X-Attachment-Name")
if fileName == "" {
internal.PrintErrorWithExit("sendHttpClientInvalidAttachmentName", err, 1)
}
if *fileNameOpt != "" {
fileName = *fileNameOpt
}
fmt.Println("File found:", fileName)

// Check if there's duplicate file
filePath, err := os.Getwd()
if err != nil {
internal.PrintErrorWithExit("sendFileWorkDir", err, 1)
}
if fileStatus, _ := os.Stat(filePath+"/"+fileName); fileStatus != nil {
fmt.Print("There's duplicate file. Action? [(R)eplace/R(e)name/(C)ancel] [Default: R] -> ")
prompt, err := reader.ReadString('\n')
if err != nil {
internal.PrintErrorWithExit("sendPromptError", err, 1)
}
prompt = strings.Replace(prompt, "\n", "", -1)
if strings.ToLower(prompt) == "c" {
internal.PrintErrorWithExit("sendPromptCancel", errors.New("Canceled by action"), 0)
}
if strings.ToLower(prompt) == "e" {
fmt.Print("Change filename ["+fileName+"]: ")
prompt, err = reader.ReadString('\n')
if err != nil {
internal.PrintErrorWithExit("sendPromptError", err, 1)
}
prompt = strings.Replace(prompt, "\n", "", -1)
if prompt == fileName {
internal.PrintErrorWithExit("sendPromptDuplicateFilename", errors.New("Canceled by action"), 0)
}
fileName = prompt
}
}
filePath += "/"+fileName

// Create file
file, err := os.Create(filePath)
if err != nil {
internal.PrintErrorWithExit("sendFileCreation", err, 1)
}

// Downloading file
fmt.Println("Downloading...")
bar := progressbar.DefaultBytes(resp.ContentLength, fileName)
_, err = io.Copy(io.MultiWriter(bar, file), resp.Body)
if err != nil {
errMsg := err.Error()
if strings.Contains(errMsg, "EOF") {
err = errors.New("Broken pipe from sender because forced close or terminated.")
}
internal.PrintErrorWithExit("sendStreamFile", err, 1)
}

// Check checksum
fmt.Println("Checking checksum...")
fileDownloaded, err := os.Open(filePath)
if err != nil {
internal.PrintErrorWithExit("checksumFileOpen", err, 1)
}
hash := sha256.New()
if _, err := io.Copy(hash, fileDownloaded); err != nil {
internal.PrintErrorWithExit("checksumHashSum", err, 1)
}
checksumLocal := fmt.Sprintf("%x", hash.Sum(nil))
if checksumLocal != checksum {
internal.PrintErrorWithExit("checksumMismatch", errors.New("Checksum mismatch with sender"), 1)
}
fileDownloaded.Close()

fmt.Println("Download success!")
}
30 changes: 30 additions & 0 deletions cmd/mdrop-client/get_tcp_connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"errors"
"fmt"
"net"
"strconv"
"time"

"github.com/mplus-oss/mdrop/internal"
)

func GetTcpReadyConnect(localPort int) {
isConnected := false
for i := 1; i <= 60; i++ {
timeout := time.Second
_, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(localPort)), timeout)
if err == nil {
isConnected = true
break
}
time.Sleep(timeout)
}

if isConnected {
fmt.Println("Tunnel is ready!")
} else {
internal.PrintErrorWithExit("getTcpReadyConnectError", errors.New("Tunnel is timeout."), 1)
}
}
Loading

0 comments on commit f408cc4

Please sign in to comment.