Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save sh7ning/1139d1a62c362528212a00d9e7b61589 to your computer and use it in GitHub Desktop.
Save sh7ning/1139d1a62c362528212a00d9e7b61589 to your computer and use it in GitHub Desktop.
auto-login-mysql.go
package main

import (
	"fmt"

	"github.com/ThomasRooney/gexpect"
)

func main() {
	child, err := gexpect.Spawn(fmt.Sprintf("/usr/local/opt/mysql-client/bin/mysql -h 127.0.0.1 -u %s -p", "root"))
	if err != nil {
		panic(err)
	}
	child.Expect("Enter password")
	child.SendLine("123123\n")
	child.Expect("mysql>")
	fmt.Printf("mysql>")
	child.Interact()
	fmt.Printf("Done \n")
	child.Close()
}
@sh7ning
Copy link
Author

sh7ning commented Oct 25, 2019

package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"jumpserver/gauth"
	"os"
	"os/exec"
	"os/signal"
	"strings"

	"github.com/creack/pty"
	"github.com/joho/godotenv"
	"golang.org/x/crypto/ssh/terminal"
)

var (
	ptmx *os.File
	err  error
	cmd  *exec.Cmd
)

func main() {
	envFile := flag.String("c", ".env", ".env file")
	flag.Parse()

	loadEnv(*envFile)

	mfaCode := get2fa()
	c := ""

	//c = fmt.Sprintf("/usr/bin/ssh yadou")
	//c = fmt.Sprintf("/usr/bin/php -a")
	c = fmt.Sprintf("/usr/bin/ssh %s -p %s", os.Getenv("JUMPSERVER_DSN"), os.Getenv("JUMPSERVER_PORT"))

	warn("process cmd: %s", c)
	cmd = exec.Command("sh", "-c", c)
	ptmx, err = pty.Start(cmd)
	if err != nil {
		panic(err)
	}

	// Make sure to close the pty at the end.
	defer func() {
		err := ptmx.Close()
		if err != nil {
			panic(err)
		}
	}() // Best effort.
	//interact()
	//os.Exit(0)

	login(mfaCode)

	interact()
}

func login(mfaCode string) {
	for {
		chunk := make([]byte, 1024)

		n, err := ptmx.Read(chunk)
		if err != nil && err != io.EOF {
			panic(err)
		}
		if n != 0 {
			output := string(chunk[:n])
			fmt.Print(output)
			if strings.Contains(output, "Are you sure you want to continue connecting (yes/no)?") {
				fmt.Print(output)
				send("yes\n")
			}

			if !strings.Contains(output, "yes") {
				if strings.Contains(output, "password:") {
					send(os.Getenv("JUMPSERVER_PASSWORD") + "\n")
				}
			}

			if strings.Contains(output, "[MFA auth]:") {
				send(mfaCode + "\n")
			}

			if strings.Contains(output, "Opt>") {
				break
			}

			if strings.Contains(output, "data]#") {
				break
			}

			if strings.Contains(output, "Permission denied") {
				panic("login failed...")
			}
		}
	}
}

func interact() {
	fd := int(os.Stdin.Fd())
	state, err := terminal.MakeRaw(fd)
	if err != nil {
		panic("setting stdin to raw:" + err.Error())
	}

	isCmdRunning := true
	ctx, cancel := context.WithCancel(context.Background())

	go func() {
		_ = cmd.Wait()
		isCmdRunning = false

		if err := terminal.Restore(0, state); err != nil {
			fmt.Println("warning, failed to restore terminal:", err)
		}
		fmt.Println("")
		cancel()
		//fmt.Println("wait done")
	}()

	go func() {
		for {
			chunk := make([]byte, 1024)

			n, err := ptmx.Read(chunk)
			if err != nil && err != io.EOF {
				panic(err)
			}
			if n != 0 {
				output := string(chunk[:n])
				fmt.Print(output)
			}
		}
	}()
	//go io.Copy(os.Stdout, ptmx)
	go io.Copy(ptmx, os.Stdin)
	//go func() {
	//
	//	in := bufio.NewReader(os.Stdin)
	//	for {
	//		r, _, err := in.ReadRune()
	//		if err != nil {
	//			fmt.Println("stdin:", err)
	//			break
	//		}
	//		//fmt.Printf("key: %#v", r)
	//		send(string(r))
	//		//fmt.Printf("read rune %q\r\n", r)
	//	}
	//}()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	for {
		select {
		case <-ctx.Done():
			return

		case <-c:
			if !isCmdRunning {
				fmt.Printf("%#v", cmd.ProcessState.Exited())
				//fmt.Println("sign break")
				return
			}
			//fmt.Println("sign")
		}
	}
}

func get2fa() string {
	secret := os.Getenv("JUMPSERVER_SECRET")
	if secret == "" {
		panic("empty secret")
	}
	code, err := gauth.NewGoogleAuth().GetCode(secret)
	if err != nil {
		panic(err)
	}

	return code
}

func send(cmd string) {
	_, err := ptmx.Write([]byte(cmd))
	if err != nil {
		panic(err)
	}
}

func warn(format string, v ...interface{}) {
	fmt.Printf("\033[33m[Warn] "+format+"\033[0m\n", v...)
}

func loadEnv(file string) {
	err := godotenv.Load(file)
	if err != nil {
		warn("Error loading .env file: %s", file)
	}
}

@sh7ning
Copy link
Author

sh7ning commented Oct 25, 2019

package gauth

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base32"
	"encoding/binary"
	"fmt"
	"strings"
	"time"
)

type GoogleAuth struct {
}

func NewGoogleAuth() *GoogleAuth {
	return &GoogleAuth{}
}

func (googleAuth *GoogleAuth) un() int64 {
	return time.Now().UnixNano() / 1000 / 30
}

func (googleAuth *GoogleAuth) hmacSha1(key, data []byte) []byte {
	h := hmac.New(sha1.New, key)
	if total := len(data); total > 0 {
		h.Write(data)
	}
	return h.Sum(nil)
}

func (googleAuth *GoogleAuth) base32encode(src []byte) string {
	return base32.StdEncoding.EncodeToString(src)
}

func (googleAuth *GoogleAuth) base32decode(s string) ([]byte, error) {
	return base32.StdEncoding.DecodeString(s)
}

func (googleAuth *GoogleAuth) toBytes(value int64) []byte {
	var result []byte
	mask := int64(0xFF)
	shifts := [8]uint16{56, 48, 40, 32, 24, 16, 8, 0}
	for _, shift := range shifts {
		result = append(result, byte((value>>shift)&mask))
	}
	return result
}

func (googleAuth *GoogleAuth) toUint32(bts []byte) uint32 {
	return (uint32(bts[0]) << 24) + (uint32(bts[1]) << 16) +
		(uint32(bts[2]) << 8) + uint32(bts[3])
}

func (googleAuth *GoogleAuth) oneTimePassword(key []byte, data []byte) uint32 {
	hash := googleAuth.hmacSha1(key, data)
	offset := hash[len(hash)-1] & 0x0F
	hashParts := hash[offset : offset+4]
	hashParts[0] = hashParts[0] & 0x7F
	number := googleAuth.toUint32(hashParts)
	return number % 1000000
}

func (googleAuth *GoogleAuth) GetSecret() string {
	var buf bytes.Buffer
	binary.Write(&buf, binary.BigEndian, googleAuth.un())
	return strings.ToUpper(googleAuth.base32encode(googleAuth.hmacSha1(buf.Bytes(), nil)))
}

func (googleAuth *GoogleAuth) GetCode(secret string) (string, error) {
	secretUpper := strings.ToUpper(secret)
	secretKey, err := googleAuth.base32decode(secretUpper)
	if err != nil {
		return "", err
	}
	number := googleAuth.oneTimePassword(secretKey, googleAuth.toBytes(time.Now().Unix()/30))
	return fmt.Sprintf("%06d", number), nil
}

func (googleAuth *GoogleAuth) GetQrcode(user, secret string) string {
	return fmt.Sprintf("otpauth://totp/%s?secret=%s", user, secret)
}

func (googleAuth *GoogleAuth) GetQrcodeUrl(user, secret string) string {
	qrcode := googleAuth.GetQrcode(user, secret)
	return fmt.Sprintf("http://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=%s", qrcode)
}

func (googleAuth *GoogleAuth) VerifyCode(secret, code string) (bool, error) {
	_code, err := googleAuth.GetCode(secret)
	fmt.Println(_code, code, err)
	if err != nil {
		return false, err
	}
	return _code == code, nil
}

@sh7ning
Copy link
Author

sh7ning commented Oct 25, 2019

ssh 有个小bug...跳板机不能展示出有权限的服务器列表....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment