From 2d522fa3fa7d8a10472d738f8852f1c1932cfbe0 Mon Sep 17 00:00:00 2001 From: vsdutka Date: Thu, 25 Feb 2016 17:35:33 +0300 Subject: [PATCH] Initial commit --- .gitignore | 2 + logger.go | 95 +++++++++++++++++++++ main.go | 182 +++++++++++++++++++++++++++++++++++++++ server.go | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++ version.conf | 1 + version.go | 5 ++ 6 files changed, 519 insertions(+) create mode 100644 logger.go create mode 100644 main.go create mode 100644 server.go create mode 100644 version.conf create mode 100644 version.go diff --git a/.gitignore b/.gitignore index cd2946a..a4a65d2 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,5 @@ $RECYCLE.BIN/ Network Trash Folder Temporary Items .apdisk +*.pem +http2https.exe~ diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..53b82de --- /dev/null +++ b/logger.go @@ -0,0 +1,95 @@ +// logger +package main + +import ( + "github.com/kardianos/osext" + "log" + "net/http" + "os" + "path/filepath" + "strings" + "time" +) + +type statusWriter struct { + http.ResponseWriter + status int + length int +} + +func (w *statusWriter) WriteHeader(status int) { + w.status = status + w.ResponseWriter.WriteHeader(status) +} + +func (w *statusWriter) Write(b []byte) (int, error) { + if w.status == 0 { + w.status = 200 + } + w.length = len(b) + return w.ResponseWriter.Write(b) +} + +var logChan = make(chan string, 10000) + +func init() { + go func() { + const fmtFileName = "${app_path}\\log\\ex${date}.log" + var ( + lastLogging = time.Time{} + logFile *os.File + err error + str string + ) + defer func() { + if logFile != nil { + logFile.Close() + } + }() + + basePath := "" + exeName, err := osext.Executable() + + if err == nil { + exeName, err = filepath.Abs(exeName) + if err == nil { + basePath = filepath.Dir(exeName) + } + } + + for { + select { + case str = <-logChan: + { + if lastLogging.Format("2006_01_02") != time.Now().Format("2006_01_02") { + if logFile != nil { + logFile.Close() + } + fileName := os.Expand(fmtFileName, func(key string) string { + switch strings.ToUpper(key) { + case "APP_PATH": + return basePath + case "DATE": + return time.Now().Format("2006_01_02") + default: + return "" + } + }) + dir, _ := filepath.Split(fileName) + os.MkdirAll(dir, os.ModeDir) + + logFile, err = os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatalln(err) + } + } + lastLogging = time.Now() + logFile.WriteString(str) + } + } + } + }() +} +func writeToLog(msg string) { + logChan <- msg +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..a4989bf --- /dev/null +++ b/main.go @@ -0,0 +1,182 @@ +// main +package main + +//go:generate C:\!Dev\GOPATH\src\github.com\vsdutka\gover\gover.exe +import ( + "flag" + "fmt" + "github.com/kardianos/service" + _ "golang.org/x/tools/go/ssa" + "log" + "os" + "runtime" + "sync" +) + +var ( + logger service.Logger + loggerLock sync.Mutex + verFlag *bool + svcFlag *string + listenPortFlag *int + destHostFlag *string + destPortFlag *int + destCertFlag *string + destKeyFlag *string + destKeyPassFlag *string + confServiceName string + confServiceDispName string +) + +func logInfof(format string, a ...interface{}) error { + loggerLock.Lock() + defer loggerLock.Unlock() + if logger != nil { + return logger.Infof(format, a...) + } + return nil +} +func logError(v ...interface{}) error { + loggerLock.Lock() + defer loggerLock.Unlock() + if logger != nil { + return logger.Error(v) + } + return nil +} + +// Program structures. +// Define Start and Stop methods. +type program struct { + exit chan struct{} +} + +func (p *program) Start(s service.Service) error { + if service.Interactive() { + logInfof("Service \"%s\" is running in terminal.", confServiceDispName) + } else { + logInfof("Service \"%s\" is running under service manager.", confServiceDispName) + } + p.exit = make(chan struct{}) + + // Start should not block. Do the actual work async. + go p.run() + return nil +} +func (p *program) run() { + startServer() + logInfof("Service \"%s\" is started.", confServiceDispName) + for { + select { + case <-p.exit: + return + } + } +} +func (p *program) Stop(s service.Service) error { + // Any work in Stop should be quick, usually a few seconds at most. + logInfof("Service \"%s\" is stopping.", confServiceDispName) + stopServer() + logInfof("Service \"%s\" is stopped.", confServiceDispName) + close(p.exit) + return nil +} + +// Service setup. +// Define service config. +// Create the service. +// Setup the logger. +// Handle service controls (optional). +// Run the service. +func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) + + flag.Usage = usage + verFlag = flag.Bool("version", false, "Show version") + svcFlag = flag.String("service", "", fmt.Sprintf("Control the system service. Valid actions: %q\n", service.ControlAction)) + listenPortFlag = flag.Int("listen_port", 13777, "Listening port") + destHostFlag = flag.String("dest_host", "", "Destination host name") + destPortFlag = flag.Int("dest_port", 443, "Destination port") + destCertFlag = flag.String("dest_cert", "", "Destination certificate file name") + destKeyFlag = flag.String("dest_key", "", "Destination key file name") + destKeyPassFlag = flag.String("dest_key_pass", "", "Destination key password") + flag.Parse() + + if *verFlag == true { + fmt.Println("Version: ", VERSION) + fmt.Println("Build: ", BUILD_DATE) + os.Exit(0) + } + + if *destHostFlag == "" { + usage() + os.Exit(2) + } + + confServiceName = fmt.Sprintf("http2https_%v", *listenPortFlag) + confServiceDispName = fmt.Sprintf("%s for \"%s:%v\"", serviceDisplayName, *destHostFlag, *destPortFlag) + + svcConfig := &service.Config{ + Name: confServiceName, + DisplayName: confServiceDispName, + Description: confServiceDispName, + Arguments: []string{ + fmt.Sprintf("-listen_port=%v", *listenPortFlag), + fmt.Sprintf("-dest_host=%s", *destHostFlag), + fmt.Sprintf("-dest_port=%v", *destPortFlag), + fmt.Sprintf("-dest_cert=%s", *destCertFlag), + fmt.Sprintf("-dest_key=%s", *destKeyFlag), + fmt.Sprintf("-dest_key_pass=%s", *destKeyPassFlag), + }, + } + + prg := &program{} + s, err := service.New(prg, svcConfig) + if err != nil { + log.Fatal(err) + } + errs := make(chan error, 5) + func() { + loggerLock.Lock() + defer loggerLock.Unlock() + logger, err = s.Logger(errs) + if err != nil { + log.Fatal(err) + } + }() + + go func() { + for { + err := <-errs + if err != nil { + log.Print(err) + } + } + }() + + if len(*svcFlag) != 0 { + err := service.Control(s, *svcFlag) + if err != nil { + log.Printf("Valid actions: %q\n", service.ControlAction) + log.Fatal(err) + } + return + } + err = s.Run() + if err != nil { + logError(err) + } +} + +const serviceDisplayName = `HTTP to HTTPS tunneling service` +const usageTemplate = `Http2HttpS is ` + serviceDisplayName + ` + +Usage: http2https commands + +The commands are: +` + +func usage() { + fmt.Fprintln(os.Stderr, usageTemplate) + flag.PrintDefaults() +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..70f9849 --- /dev/null +++ b/server.go @@ -0,0 +1,234 @@ +// server +package main + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "strings" + "time" +) + +func startServer() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + + //r.URL.Path = strings.ToLower(r.URL.Path) + + start := time.Now() + writer := statusWriter{w, 0, 0} + proxy(&writer, r) + end := time.Now() + latency := end.Sub(start) + statusCode := writer.status + length := writer.length + user, _, ok := r.BasicAuth() + if !ok { + user = "-" + } + url := r.URL.Path + + params := r.Form.Encode() + if params != "" { + url = url + "?" + params + } + + writeToLog(fmt.Sprintf("%s, %s, %s, %s, %s, %s, %d, %d, %d, %d, %s, %s, %v\r\n", + r.RemoteAddr, + user, + end.Format("2006.01.02"), + end.Format("15:04:05.000000000"), + r.Proto, + r.Host, + length, + r.ContentLength, + time.Since(start)/time.Millisecond, + statusCode, + r.Method, + url, + latency, + )) + }) + + if err := http.ListenAndServe(fmt.Sprintf(":%v", *listenPortFlag), nil); err != nil { + logError(err) + } +} + +func stopServer() { + +} + +func proxy(w http.ResponseWriter, r *http.Request) { + newURL := fmt.Sprintf("https://%s:%v%s", *destHostFlag, *destPortFlag, r.URL.Path+"?"+r.URL.RawQuery) + + var ( + err error + newReq *http.Request + newResp *http.Response + cert tls.Certificate + ) + + newReq, err = http.NewRequest(r.Method, newURL, r.Body) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error: %s!", err.Error()) + return + } + copyHeaders(newReq.Header, r.Header) + + cert, err = loadX509KeyPair(*destCertFlag, *destKeyFlag, *destKeyPassFlag) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error: %s!", err.Error()) + return + } + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true, + //RootCAs: roots, + Certificates: []tls.Certificate{cert}, + }, + } + client := &http.Client{Transport: tr} + + newResp, err = client.Do(newReq) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error: %s!", err.Error()) + return + } + copyHeaders(w.Header(), newResp.Header) + w.WriteHeader(newResp.StatusCode) + io.Copy(w, newResp.Body) + newResp.Body.Close() +} + +func loadX509KeyPair(certFile, keyFile, pw string) (cert tls.Certificate, err error) { + certPEMBlock, err := ioutil.ReadFile(certFile) + if err != nil { + return + } + keyPEMBlock, err := ioutil.ReadFile(keyFile) + if err != nil { + return + } + return X509KeyPair(certPEMBlock, keyPEMBlock, []byte(pw)) +} + +func X509KeyPair(certPEMBlock, keyPEMBlock, pw []byte) (cert tls.Certificate, err error) { + var certDERBlock *pem.Block + for { + certDERBlock, certPEMBlock = pem.Decode(certPEMBlock) + if certDERBlock == nil { + break + } + if certDERBlock.Type == "CERTIFICATE" { + cert.Certificate = append(cert.Certificate, certDERBlock.Bytes) + } + } + + if len(cert.Certificate) == 0 { + err = errors.New("crypto/tls: failed to parse certificate PEM data") + return + } + var keyDERBlock *pem.Block + for { + keyDERBlock, keyPEMBlock = pem.Decode(keyPEMBlock) + if keyDERBlock == nil { + err = errors.New("crypto/tls: failed to parse key PEM data") + return + } + if x509.IsEncryptedPEMBlock(keyDERBlock) { + out, err2 := x509.DecryptPEMBlock(keyDERBlock, pw) + if err2 != nil { + err = err2 + return + } + keyDERBlock.Bytes = out + break + } + if keyDERBlock.Type == "PRIVATE KEY" || strings.HasSuffix(keyDERBlock.Type, " PRIVATE KEY") { + break + } + } + + cert.PrivateKey, err = parsePrivateKey(keyDERBlock.Bytes) + if err != nil { + return + } + // We don't need to parse the public key for TLS, but we so do anyway + // to check that it looks sane and matches the private key. + x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + return + } + + switch pub := x509Cert.PublicKey.(type) { + case *rsa.PublicKey: + priv, ok := cert.PrivateKey.(*rsa.PrivateKey) + if !ok { + err = errors.New("crypto/tls: private key type does not match public key type") + return + } + if pub.N.Cmp(priv.N) != 0 { + err = errors.New("crypto/tls: private key does not match public key") + return + } + case *ecdsa.PublicKey: + priv, ok := cert.PrivateKey.(*ecdsa.PrivateKey) + if !ok { + err = errors.New("crypto/tls: private key type does not match public key type") + return + + } + if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 { + err = errors.New("crypto/tls: private key does not match public key") + return + } + default: + err = errors.New("crypto/tls: unknown public key algorithm") + return + } + return +} + +// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates +// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys. +// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three. +func parsePrivateKey(der []byte) (crypto.PrivateKey, error) { + if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { + return key, nil + } + if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { + switch key := key.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey: + return key, nil + default: + return nil, errors.New("crypto/tls: found unknown private key type in PKCS#8 wrapping") + } + } + if key, err := x509.ParseECPrivateKey(der); err == nil { + return key, nil + } + + return nil, errors.New("crypto/tls: failed to parse private key") +} + +func copyHeaders(dst, src http.Header) { + for k, _ := range dst { + dst.Del(k) + } + for k, vs := range src { + for _, v := range vs { + dst.Add(k, v) + } + } +} diff --git a/version.conf b/version.conf new file mode 100644 index 0000000..3b50e85 --- /dev/null +++ b/version.conf @@ -0,0 +1 @@ +{"Version.Major":1,"Version.Minor":0,"Version.Build":52} \ No newline at end of file diff --git a/version.go b/version.go new file mode 100644 index 0000000..8a4ca56 --- /dev/null +++ b/version.go @@ -0,0 +1,5 @@ +package main +const ( + VERSION = "1.0.52" + BUILD_DATE = "24-02-2016 16:53:52.6072527+03:00" +) \ No newline at end of file