//
// Copyright (c) 2018, Přemysl Eric Janouch
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// hnc is a netcat-alike that shuts down properly.
package main
import (
"crypto/tls"
"flag"
"fmt"
"io"
"net"
"os"
)
// #include
import "C"
func isatty(fd uintptr) bool { return C.isatty(C.int(fd)) != 0 }
func log(format string, args ...interface{}) {
msg := fmt.Sprintf(format+"\n", args...)
if isatty(os.Stderr.Fd()) {
msg = "\x1b[0;1;31m" + msg + "\x1b[m"
}
os.Stderr.WriteString(msg)
}
var (
flagTLS = flag.Bool("tls", false, "connect using TLS")
flagCRLF = flag.Bool("crlf", false, "translate LF into CRLF")
)
// Network connection that can shut down the write end.
type connCloseWriter interface {
net.Conn
CloseWrite() error
}
func dial(address string) (connCloseWriter, error) {
if *flagTLS {
return tls.Dial("tcp", address, &tls.Config{
InsecureSkipVerify: true,
})
}
transport, err := net.Dial("tcp", address)
if err != nil {
return nil, err
}
return transport.(connCloseWriter), nil
}
func expand(raw []byte) []byte {
if !*flagCRLF {
return raw
}
var res []byte
for _, b := range raw {
if b == '\n' {
res = append(res, '\r')
}
res = append(res, b)
}
return res
}
// Asynchronously delivered result of io.Reader.
type readResult struct {
b []byte
err error
}
func read(r io.Reader, ch chan<- readResult) {
defer close(ch)
for {
var buf [8192]byte
n, err := r.Read(buf[:])
ch <- readResult{buf[:n], err}
if err != nil {
break
}
}
}
func main() {
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(),
"Usage: %s [OPTION]... HOST PORT\n"+
"Connect to a remote host over TCP/IP.\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() != 2 {
flag.Usage()
os.Exit(2)
}
conn, err := dial(net.JoinHostPort(flag.Arg(0), flag.Arg(1)))
if err != nil {
log("dial: %s", err)
os.Exit(1)
}
fromUser := make(chan readResult)
go read(os.Stdin, fromUser)
fromConn := make(chan readResult)
go read(conn, fromConn)
for fromUser != nil || fromConn != nil {
select {
case result := <-fromUser:
if len(result.b) > 0 {
if _, err := conn.Write(expand(result.b)); err != nil {
log("remote: %s", err)
}
}
if result.err != nil {
log("stdin: %s", result.err)
fromUser = nil
if err := conn.CloseWrite(); err != nil {
log("remote: %s", err)
}
}
case result := <-fromConn:
if len(result.b) > 0 {
if _, err := os.Stdout.Write(result.b); err != nil {
log("stdout: %s", err)
}
}
if result.err != nil {
log("remote: %s", result.err)
fromConn = nil
}
}
}
}