Browse Source

SSH Framework Finished. TODO: More Commands

zry 2 years ago
parent
commit
6feca008d7

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 43 - 0
Commands/CommandFuncsBundle.go

@@ -0,0 +1,43 @@
+package Commands
+
+import (
+	"golang.org/x/crypto/ssh/terminal"
+	"git.swzry.com/NSMCServerLauncher/Utils"
+	"path"
+	"fmt"
+)
+
+func CMD_pwd_C(args []string, term *terminal.Terminal,context *Utils.UserContextType)  {
+	term.Write([]byte("PWD: " + context.PWD + "\n"))
+}
+
+func CMD_cd_C(args []string, term *terminal.Terminal,context *Utils.UserContextType)  {
+	stargs := Utils.RestrictQuotedArgs(args)
+	fmt.Println(stargs)
+	switch len(stargs) {
+	case 0:
+		term.Write([]byte("PWD: " + context.PWD + "\n"))
+		break
+	default:
+		tpath := "/"
+		if stargs[0][0] == '/'{
+			tpath = path.Join("/",stargs[0][1:])
+		}else{
+			tpath = path.Join(context.PWD,stargs[0])
+		}
+		
+		context.PWD = tpath
+		//term.Write([]byte("PWD: " + context.PWD + "\n"))
+		// TODO: Change PWD
+	}
+}
+
+func CMD_mcstat_C(args []string, term *terminal.Terminal,context *Utils.UserContextType)  {
+	term.Write([]byte("Minecraft Server Not Running."))
+	// TODO: Minecraft Status Display
+}
+
+func CMD_sz_C(args []string, term *terminal.Terminal,context *Utils.UserContextType)  {
+	term.Write([]byte("Not Support Yet."))
+	// TODO: ZMODEM Transfer
+}

+ 74 - 0
Commands/CommandRegistry.go

@@ -0,0 +1,74 @@
+package Commands
+
+import (
+	"git.swzry.com/NSMCServerLauncher/Terminal"
+	"golang.org/x/crypto/ssh/terminal"
+	"git.swzry.com/NSMCServerLauncher/Utils"
+)
+
+func RegistExtendCommands()  {
+	Terminal.RegistCommand("pwd",Terminal.RegistedCommand{
+		CmdFunc:  CMD_pwd_C,
+		HelpFunc: func(topic string, term *terminal.Terminal) {},
+		Intro:"Show current work path",
+		Usage:"pwd",
+	})
+	Terminal.RegistCommand("cd",Terminal.RegistedCommand{
+		CmdFunc:  CMD_cd_C,
+		HelpFunc: func(topic string, term *terminal.Terminal) {},
+		Intro:"Change current work path",
+		Usage:"cd <path>",
+	})
+	Terminal.RegistCommand("mcstat",Terminal.RegistedCommand{
+		CmdFunc:  CMD_mcstat_C,
+		HelpFunc: func(topic string, term *terminal.Terminal) {
+			//term.Write([]byte("Disconnect to the server.\n"))
+		},
+		Intro:"Show the Minecraft server running status.",
+		Usage:"mcstat",
+	})
+	Terminal.RegistCommand("sz",Terminal.RegistedCommand{
+		CmdFunc:  CMD_sz_C,
+		HelpFunc: func(topic string, term *terminal.Terminal) {
+			term.Write([]byte("This command can help you download file from server using ZMODEM\n\n"))
+			term.Write([]byte("ZMODEM is a file transfer protocol that can be carried on SSH/Telnet or other protocol.\n"))
+			term.Write([]byte("(See also: https://en.wikipedia.org/wiki/ZMODEM)\n\n"))
+		},
+		Intro:"Download file from server using ZMODEM",
+		Usage:"sz <conf|log|mc> <path_to_file>",
+	})
+}
+
+func RegistInnerCommands()  {
+	Terminal.RegistCommand("help",Terminal.RegistedCommand{
+		CmdFunc: func(args []string, term *terminal.Terminal,context *Utils.UserContextType) {},
+		HelpFunc: func(topic string, term *terminal.Terminal) {
+			//term.Write([]byte("Disconnect to the server.\n"))
+		},
+		Intro:"Show help informations.",
+		Usage:"help\nhelp <command> [topic]",
+	})
+	Terminal.RegistCommand("about",Terminal.RegistedCommand{
+		CmdFunc: func(args []string, term *terminal.Terminal,context *Utils.UserContextType) {},
+		HelpFunc: func(topic string, term *terminal.Terminal) {
+			//term.Write([]byte("About this system.\n"))
+		},
+		Intro:"About this system.",
+		Usage:"about",
+	})
+	Terminal.RegistCommand("exit",Terminal.RegistedCommand{
+		CmdFunc: func(args []string, term *terminal.Terminal,context *Utils.UserContextType) {},
+		HelpFunc: func(topic string, term *terminal.Terminal) {
+			//term.Write([]byte("Disconnect to the server.\n"))
+		},
+		Intro:"Disconnect to the server.",
+		Usage:"exit",
+	})
+}
+
+func RegistAllCommands()  {
+	Terminal.CommandSystemInit()
+	RegistInnerCommands()
+	RegistExtendCommands()
+	Terminal.GenerateHelpCommandList()
+}

+ 150 - 0
Logger/Logger.go

@@ -0,0 +1,150 @@
+package Logger
+
+import (
+	"fmt"
+	"path/filepath"
+	"path"
+	"gopkg.in/natefinch/lumberjack.v2"
+	"time"
+	"git.swzry.com/NSMCServerLauncher/Utils"
+)
+
+type LogFileWriter struct{
+	lumberjackLogger lumberjack.Logger
+	withTime bool
+	prefix string
+	enable bool
+	time_format_string string
+}
+
+func (this *LogFileWriter) Write(data []byte) (int, error) {
+	if(this.enable){
+		if(this.withTime){
+			this.lumberjackLogger.Write([]byte(fmt.Sprintf("[%s]%s",time.Now().Format(this.time_format_string),this.prefix)))
+		}else {
+			if this.prefix != ""{
+				this.lumberjackLogger.Write([]byte(this.prefix))
+			}
+		}
+		return this.lumberjackLogger.Write(data)
+	}else {
+		return len(data),nil
+	}
+}
+
+var Log TypeOfLoggerSystem
+
+type TypeOfLoggerSystem struct {
+	SysDebug    LogFileWriter
+	SysInfo     LogFileWriter
+	SysWarning  LogFileWriter
+	SysCritical LogFileWriter
+	SysFatal    LogFileWriter
+	MCLOut      LogFileWriter
+	MCLErr      LogFileWriter
+	SSH         LogFileWriter
+}
+
+func InitLogger() error {
+	logdir,err := filepath.Abs(LoggerConf.logdir)
+	if err != nil {
+		fmt.Println("[StdOutLog][Fatal Error] Failed Get Log Dir: ",err)
+		return err
+	}
+	if err := Utils.MkDirIfNotExist(logdir); err != nil {return err}
+
+	syslbj := lumberjack.Logger{
+		Filename:   path.Join(LoggerConf.logdir,LoggerConf.syslogcfg.Filename),
+		MaxSize:    LoggerConf.syslogcfg.MaxSize,
+		MaxAge:     LoggerConf.syslogcfg.MaxAge,
+		MaxBackups: LoggerConf.syslogcfg.MaxBackups,
+		LocalTime:  LoggerConf.syslogcfg.LocalTime,
+		Compress:   LoggerConf.syslogcfg.Compress,
+	}
+	mcoutlbj := lumberjack.Logger{
+		Filename:   path.Join(LoggerConf.logdir,LoggerConf.mcloutlogcfg.Filename),
+		MaxSize:    LoggerConf.mcloutlogcfg.MaxSize,
+		MaxAge:     LoggerConf.mcloutlogcfg.MaxAge,
+		MaxBackups: LoggerConf.mcloutlogcfg.MaxBackups,
+		LocalTime:  LoggerConf.mcloutlogcfg.LocalTime,
+		Compress:   LoggerConf.mcloutlogcfg.Compress,
+	}
+	mcerrlbj := lumberjack.Logger{
+		Filename:   path.Join(LoggerConf.logdir,LoggerConf.mclerrlogcfg.Filename),
+		MaxSize:    LoggerConf.mclerrlogcfg.MaxSize,
+		MaxAge:     LoggerConf.mclerrlogcfg.MaxAge,
+		MaxBackups: LoggerConf.mclerrlogcfg.MaxBackups,
+		LocalTime:  LoggerConf.mclerrlogcfg.LocalTime,
+		Compress:   LoggerConf.mclerrlogcfg.Compress,
+	}
+	sshlbj := lumberjack.Logger{
+		Filename:   path.Join(LoggerConf.logdir,LoggerConf.sshlogcfg.Filename),
+		MaxSize:    LoggerConf.sshlogcfg.MaxSize,
+		MaxAge:     LoggerConf.sshlogcfg.MaxAge,
+		MaxBackups: LoggerConf.sshlogcfg.MaxBackups,
+		LocalTime:  LoggerConf.sshlogcfg.LocalTime,
+		Compress:   LoggerConf.sshlogcfg.Compress,
+	}
+
+
+	Log = TypeOfLoggerSystem{
+		SysCritical: LogFileWriter{
+			lumberjackLogger:syslbj,
+			enable:true,
+			prefix:"<CRITICAL>",
+			withTime: true,
+			time_format_string:LoggerConf.time_format,
+		},
+		SysFatal: LogFileWriter{
+			lumberjackLogger:syslbj,
+			enable:true,
+			prefix:"<FATAL>",
+			withTime: true,
+			time_format_string:LoggerConf.time_format,
+		},
+		SysInfo: LogFileWriter{
+			lumberjackLogger:syslbj,
+			enable:true,
+			prefix:"<INFO>",
+			withTime: true,
+			time_format_string:LoggerConf.time_format,
+		},
+		SysWarning: LogFileWriter{
+			lumberjackLogger:syslbj,
+			enable:true,
+			prefix:"<WARNING>",
+			withTime: true,
+			time_format_string:LoggerConf.time_format,
+		},
+		SysDebug: LogFileWriter{
+			lumberjackLogger:syslbj,
+			enable:LoggerConf.debug_log,
+			prefix:"<DEBUG>",
+			withTime: true,
+			time_format_string:LoggerConf.time_format,
+		},
+		MCLOut: LogFileWriter{
+			lumberjackLogger:mcoutlbj,
+			enable:true,
+			prefix:"",
+			withTime: false,
+			time_format_string:LoggerConf.time_format,
+		},
+		MCLErr: LogFileWriter{
+			lumberjackLogger:mcerrlbj,
+			enable:true,
+			prefix:"",
+			withTime: false,
+			time_format_string:LoggerConf.time_format,
+		},
+		SSH: LogFileWriter{
+			lumberjackLogger:sshlbj,
+			enable:true,
+			prefix:"",
+			withTime: true,
+			time_format_string:LoggerConf.time_format,
+		},
+	}
+
+	return nil
+}

+ 121 - 0
Logger/LoggerConfParser.go

@@ -0,0 +1,121 @@
+package Logger
+
+import (
+	"github.com/larspensjo/config"
+	"fmt"
+	"git.swzry.com/NSMCServerLauncher/Utils"
+)
+
+var LoggerConf NSMCSL_LoggerConf
+
+type NSMCSL_LoggerConfigSingleFileSection struct {
+	Filename string
+	MaxSize int
+	MaxAge int
+	MaxBackups int
+	LocalTime bool
+	Compress bool
+}
+
+type NSMCSL_LoggerConf struct {
+	logdir string
+	debug_log bool
+	time_format string
+	syslogcfg NSMCSL_LoggerConfigSingleFileSection
+	mcloutlogcfg NSMCSL_LoggerConfigSingleFileSection
+	mclerrlogcfg NSMCSL_LoggerConfigSingleFileSection
+	sshlogcfg NSMCSL_LoggerConfigSingleFileSection
+}
+
+func LoadLoggerConfig(loggerConfFile string) error {
+	cf,err := config.ReadDefault(loggerConfFile)
+	if (err != nil){
+		fmt.Println("Failed Read Log Config File '",loggerConfFile,"' :", err.Error())
+		return err
+	}
+	if r,err := cf.Bool("global","debug_log");err != nil{
+		fmt.Println("Failed Parse Log Config File '",loggerConfFile,"' For 'global.debug_log':", err.Error())
+		return err
+	}else{
+		LoggerConf.debug_log = r
+	}
+	if r,err := cf.String("global","log_dir");err != nil{
+		fmt.Println("Failed Parse Log Config File '",loggerConfFile,"' For 'global.log_dir':", err.Error())
+		return err
+	}else{
+		LoggerConf.logdir = Utils.StripSpaceAndQuote(r)
+	}
+	if r,err := cf.String("global","time_format");err != nil{
+		fmt.Println("Failed Parse Log Config File '",loggerConfFile,"' For 'global.time_format':", err.Error())
+		return err
+	}else{
+		LoggerConf.time_format = Utils.StripSpaceAndQuote(r)
+	}
+	if r,err := i_LoggerConfig_SingleLogParse(cf,"system");err != nil{
+		fmt.Println("		(At Log Config File '",loggerConfFile,"')")
+		return err
+	}else{
+		LoggerConf.syslogcfg = r
+	}
+	if r,err := i_LoggerConfig_SingleLogParse(cf,"mclauncher.stdout");err != nil{
+		fmt.Println("		(At Log Config File '",loggerConfFile,"')")
+		return err
+	}else{
+		LoggerConf.mcloutlogcfg = r
+	}
+	if r,err := i_LoggerConfig_SingleLogParse(cf,"mclauncher.stderr");err != nil{
+		fmt.Println("		(At Log Config File '",loggerConfFile,"')")
+		return err
+	}else{
+		LoggerConf.mclerrlogcfg = r
+	}
+	if r,err := i_LoggerConfig_SingleLogParse(cf,"ssh");err != nil{
+		fmt.Println("		(At Log Config File '",loggerConfFile,"')")
+		return err
+	}else{
+		LoggerConf.sshlogcfg = r
+	}
+
+	return nil
+}
+
+func i_LoggerConfig_SingleLogParse(cf *config.Config,secname string) (NSMCSL_LoggerConfigSingleFileSection,error){
+	sfs := NSMCSL_LoggerConfigSingleFileSection{}
+	if r,err := cf.String(secname,"Filename");err != nil{
+		fmt.Println("Failed Parse Log Config File For '",secname,".Filename':", err.Error())
+		return sfs,err
+	}else{
+		sfs.Filename = Utils.StripSpaceAndQuote(r)
+	}
+	if r,err := cf.Bool(secname,"Compress");err != nil{
+		fmt.Println("Failed Parse Log Config File For '",secname,".Compress':", err.Error())
+		return sfs,err
+	}else{
+		sfs.Compress = r
+	}
+	if r,err := cf.Bool(secname,"LocalTime");err != nil{
+		fmt.Println("Failed Parse Log Config File For '",secname,".LocalTime':", err.Error())
+		return sfs,err
+	}else{
+		sfs.LocalTime = r
+	}
+	if r,err := cf.Int(secname,"MaxAge");err != nil{
+		fmt.Println("Failed Parse Log Config File For '",secname,".MaxAge':", err.Error())
+		return sfs,err
+	}else{
+		sfs.MaxAge = r
+	}
+	if r,err := cf.Int(secname,"MaxSize");err != nil{
+		fmt.Println("Failed Parse Log Config File For '",secname,".MaxSize':", err.Error())
+		return sfs,err
+	}else{
+		sfs.MaxSize = r
+	}
+	if r,err := cf.Int(secname,"MaxBackups");err != nil{
+		fmt.Println("Failed Parse Log Config File For '",secname,".MaxBackups':", err.Error())
+		return sfs,err
+	}else{
+		sfs.MaxBackups = r
+	}
+	return sfs,nil
+}

+ 93 - 0
SSHServer/SSHConfParser.go

@@ -0,0 +1,93 @@
+package SSHServer
+
+import (
+	"github.com/larspensjo/config"
+	"fmt"
+	"git.swzry.com/NSMCServerLauncher/Utils"
+	"path/filepath"
+	"git.swzry.com/NSMCServerLauncher/Logger"
+)
+
+var SSHServerConf NSMCSL_SSHConf
+
+type NSMCSL_SSHConf struct {
+	bind_addr string
+	max_auth_tries int
+	host_key_file string
+	defult_key_length int
+	super_user string
+	passwd map[string]string
+}
+
+func LoadSSHConfig(loggerConfFile string) error {
+	cf,err := config.ReadDefault(loggerConfFile)
+	if (err != nil){
+		fmt.Println("Failed Read Log Config File '",loggerConfFile,"' :", err.Error())
+		return err
+	}
+	if r,err := cf.String("sshserver","bind_address");err != nil{
+		fmt.Println("Failed Parse Log Config File For 'sshserver.bind_address':", err.Error())
+		return err
+	}else{
+		SSHServerConf.bind_addr = Utils.StripSpaceAndQuote(r)
+	}
+	if r,err := cf.Int("auth","max_auth_tries");err != nil{
+		fmt.Println("Failed Parse Log Config File For 'auth.max_auth_tries':", err.Error())
+		return err
+	}else{
+		SSHServerConf.max_auth_tries = r
+	}
+	if r,err := cf.Int("sshserver","defult_key_length");err != nil{
+		fmt.Println("Failed Parse Log Config File For 'sshserver.defult_key_length':", err.Error())
+		return err
+	}else{
+		SSHServerConf.defult_key_length = r
+	}
+	if r,err := cf.String("sshserver","host_key_file");err != nil{
+		fmt.Println("Failed Parse Log Config File For 'sshserver.host_key_file':", err.Error())
+		return err
+	}else{
+		hostkey,err := filepath.Abs(Utils.StripSpaceAndQuote(r))
+		if err != nil {
+			fmt.Println("[StdOutLog][Fatal Error] Failed Get HostKey Dir: ",err)
+			return err
+		}
+		SSHServerConf.host_key_file = hostkey
+	}
+	err = GetUserPasswdList(cf)
+	if err != nil {
+		return err
+	}
+	if r,err := cf.String("auth","super_user");err != nil{
+		fmt.Println("Failed Parse Log Config File For 'auth.super_user':", err.Error())
+		return err
+	}else{
+		u := Utils.StripSpaceAndQuote(r)
+		_, ok := SSHServerConf.passwd[u]
+		if(ok){
+			SSHServerConf.super_user = u
+		}else {
+			fmt.Fprintln(&Logger.Log.SysWarning,"Super User '", u, "' Does Not Exist.")
+		}
+	}
+	return nil
+}
+
+func GetUserPasswdList(cf *config.Config) error{
+	SSHServerConf.passwd = make(map[string]string)
+	list,err := cf.SectionOptions("users")
+	if err != nil {
+		fmt.Println("[StdOutLog][Fatal Error] Failed Parse User List: ",err)
+		return err
+	}
+	for _,user := range list{
+		r, err := cf.String("users",user)
+		if(err != nil){
+			fmt.Fprintln(&Logger.Log.SysWarning,"Failed to Parse Password of User '", user, "'")
+			continue
+		}
+		SSHServerConf.passwd[user] = r
+		fmt.Fprintf(&Logger.Log.SysDebug,"User %v, Password: %v\n", user, r)
+	}
+	return nil
+}

+ 174 - 0
SSHServer/SSHServer.go

@@ -0,0 +1,174 @@
+package SSHServer
+
+import (
+	"git.swzry.com/NSMCServerLauncher/Utils"
+	"fmt"
+	"git.swzry.com/NSMCServerLauncher/Logger"
+	"crypto/rsa"
+	"crypto/rand"
+	"crypto/x509"
+	"encoding/pem"
+	"os"
+	"golang.org/x/crypto/ssh"
+	"io/ioutil"
+	"net"
+	"github.com/swzry/go.TSmap"
+	"golang.org/x/crypto/ssh/terminal"
+	"git.swzry.com/NSMCServerLauncher/Terminal"
+	"golang.org/x/crypto/bcrypt"
+)
+
+var mainThreadBlockChan chan byte
+var ServerConf *ssh.ServerConfig
+var _clientListRawMap map[interface{}]interface{}
+var ClientList TSmap.TSmap
+
+func StartSSHServer(mtbc chan byte)  {
+	mainThreadBlockChan = mtbc
+	if r,err := Utils.PathExists(SSHServerConf.host_key_file); ((!r) || (err != nil)) {
+		fmt.Fprintln(&Logger.Log.SysInfo,"Generating SSH Host Key......")
+		fmt.Fprintln(&Logger.Log.SysInfo,"(It may takes a long time. Please wait.)")
+		fmt.Println("Generating SSH Host Key......")
+		fmt.Println("(It may takes a long time. Please wait.)")
+		GenerateRSAKey(SSHServerConf.host_key_file, SSHServerConf.defult_key_length)
+	}
+	ServerConf = &ssh.ServerConfig{
+		NoClientAuth: false,
+		MaxAuthTries: SSHServerConf.max_auth_tries,
+		PasswordCallback: SSHPasswordCallback,
+		PublicKeyCallback: SSHPublicKeyCallback,
+		AuthLogCallback: SSHAuthLogCallback,
+		ServerVersion: "SSH-2.0-NSMCServerLauncher-SSH",
+	}
+	keyBytes, err := ioutil.ReadFile(SSHServerConf.host_key_file)
+	if(err != nil){
+		fmt.Fprintln(&Logger.Log.SysFatal,"Failed to Load Host Key File: ", err)
+		mainThreadBlockChan <- 1
+		return
+	}
+	key, err := ssh.ParsePrivateKey(keyBytes)
+	if(err != nil){
+		fmt.Fprintln(&Logger.Log.SysFatal,"Failed to Load Host Key: ", err)
+		mainThreadBlockChan <- 1
+		return
+	}
+	ServerConf.AddHostKey(key)
+	listener,err := net.Listen("tcp", SSHServerConf.bind_addr)
+	if(err != nil){
+		fmt.Fprintln(&Logger.Log.SysFatal,"Failed to Bind Address '",SSHServerConf.bind_addr,"': ", err)
+		mainThreadBlockChan <- 1
+		return
+	}
+	fmt.Fprintln(&Logger.Log.SysInfo,"Listening At '",listener.Addr().String(),"'.")
+	_clientListRawMap = make(map[interface{}]interface{})
+	ClientList = &TSmap.NewTSmap{
+		ConMap:_clientListRawMap,
+	}
+	for{
+		tcpConn,err := listener.Accept()
+		if(err == nil){
+			fmt.Fprintf(&Logger.Log.SSH,"New Client '%v' Entered.\n",tcpConn.RemoteAddr())
+			_, schan, reqchan, err := ssh.NewServerConn(tcpConn, ServerConf)
+			if(err != nil){
+				fmt.Fprintf(&Logger.Log.SSH,"Failed Handle Client '%v': %v\n",tcpConn.RemoteAddr(),err)
+				tcpConn.Close()
+				continue
+			}else {
+				ClientList.Set(tcpConn,&Utils.ClientConnection{
+					Channels: &TSmap.NewTSmap{
+						ConMap:make(map[interface{}]interface{}),
+					},
+				})
+				go ssh.DiscardRequests(reqchan)
+				go handleChannels(schan, tcpConn)
+			}
+		}
+	}
+}
+
+func handleChannels(ch <-chan ssh.NewChannel, conn net.Conn)  {
+	cnt := 0
+	chlist,ok := ClientList.Get(conn)
+	if(!ok){
+		fmt.Fprintf(&Logger.Log.SSH,"Failed Handle Client '%v': Channels Not Found.\n",conn.RemoteAddr())
+	}
+	for newchan := range ch {
+		cnt++;
+		fmt.Fprintf(&Logger.Log.SSH,"Client '%v', Channels %v, Handling.\n",conn.RemoteAddr(),cnt)
+		go handleChannel(newchan,conn,cnt,chlist.(*Utils.ClientConnection))
+	}
+}
+
+func handleChannel(nch ssh.NewChannel,conn net.Conn,num int,chlist *Utils.ClientConnection)  {
+	if(nch.ChannelType() == "session"){
+		ch,req,err := nch.Accept()
+		if(err != nil){
+			fmt.Fprintf(&Logger.Log.SSH,"Client '%v', Channels %v, Failed Handling : %v\n",conn.RemoteAddr(),num,err)
+			return
+		}else{
+			r:= <-req
+			r.Reply(true,nil)
+			chlist.Channels.Set(num,Utils.AvaliableChannel{
+				Channel: ch,
+				Term:    terminal.NewTerminal(ch,"NSMC >"),
+				UserContext: Utils.UserContextType{
+					PWD: "/",
+				},
+			})
+			go Terminal.HandlerTerminal(ClientList,conn, num)
+		}
+	}else{
+		nch.Reject(ssh.UnknownChannelType, "Unknown Channel Type")
+		fmt.Fprintf(&Logger.Log.SSH,"Client '%v', Channels %v, Rejected : Unknown Channel Type '%v'\n",conn.RemoteAddr(),num,nch.ChannelType())
+	}
+}
+
+func SSHPasswordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)  {
+	val, ok := SSHServerConf.passwd[conn.User()]
+	if(!ok){
+		fmt.Fprintf(&Logger.Log.SSH,"Client '%v' Auth Failed : Unknown User '%v'\n",conn.RemoteAddr(),conn.User())
+		return nil,fmt.Errorf("Password Error or User '%v' Does Not Exist..",conn.User())
+	}
+	err := bcrypt.CompareHashAndPassword([]byte(val),password)
+	if(err != nil){
+		fmt.Fprintf(&Logger.Log.SSH,"Client '%v', User '%v', Auth Failed : Password Error, Detail: '%v'\n",conn.RemoteAddr(),conn.User(),err)
+		return nil,fmt.Errorf("Password Error or User '%v' Does Not Exist..",conn.User())
+	}
+	return nil,nil
+}
+
+func SSHPublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error){
+	return nil,fmt.Errorf("PublicKey Auth Not Support Yet.")
+}
+
+func SSHAuthLogCallback(conn ssh.ConnMetadata, method string, err error){
+	fmt.Fprintf(&Logger.Log.SSH,"AuthFailed. Info: client='%v', user='%v', method='%v'. Err: %v\n",conn.RemoteAddr(),conn.User(),method,err)
+}
+
+func GenerateRSAKey(keyfn string,keyLength int) {
+	privateKey, err := rsa.GenerateKey(rand.Reader, keyLength)
+	if err != nil {
+		fmt.Fprintln(&Logger.Log.SysFatal,"Failed to Generate RSA Private Key: ", err)
+		mainThreadBlockChan <- 1
+		return
+	}
+	derStream := x509.MarshalPKCS1PrivateKey(privateKey)
+	block := &pem.Block{
+		Type:  "RSA PRIVATE KEY",
+		Bytes: derStream,
+	}
+	file, err := os.Create(keyfn)
+	if err != nil {
+		fmt.Fprintln(&Logger.Log.SysFatal,"Failed to Create Key File: ", err)
+		mainThreadBlockChan <- 1
+		return
+	}
+	err = pem.Encode(file, block)
+	if err != nil {
+		fmt.Fprintln(&Logger.Log.SysFatal,"Failed to Encode Key: ", err)
+		mainThreadBlockChan <- 1
+		return
+	}
+	fmt.Fprintln(&Logger.Log.SysInfo,"SSH Host Key Generated Successfully..")
+	fmt.Println("SSH Host Key Generated Successfully..")
+}

+ 113 - 0
Terminal/CommandParser.go

@@ -0,0 +1,113 @@
+package Terminal
+
+import (
+	"golang.org/x/crypto/ssh/terminal"
+	"strings"
+	"github.com/swzry/go.TSmap"
+	"fmt"
+	"git.swzry.com/NSMCServerLauncher/Logger"
+	"git.swzry.com/NSMCServerLauncher/Utils"
+)
+
+const HELP_MAX_LINE_WIDTH  = 80
+
+var CommandList TSmap.TSmap
+var HelpCommandList string
+
+func CommandSystemInit()  {
+	CommandList = &TSmap.NewTSmap{
+		ConMap: make(map[interface{}]interface{}),
+	}
+}
+
+func RegistCommand(name string, reginfo RegistedCommand)  {
+	CommandList.Set(name,reginfo)
+}
+
+func GenerateHelpCommandList(){
+	linecnt := 0
+	CommandList.ForEach(func(k, v interface{}) {
+		key,ok := k.(string)
+		if(!ok){
+			return
+		}
+		if len(key) + linecnt + 1 <= HELP_MAX_LINE_WIDTH {
+			HelpCommandList += " " + key
+			linecnt += (len(key) + 1)
+		}else{
+			HelpCommandList += "\n" + key
+			linecnt = len(key)
+		}
+	})
+}
+
+type RegistedCommand struct {
+	CmdFunc  func(args []string, term *terminal.Terminal,context *Utils.UserContextType)
+	HelpFunc func(topic string, term *terminal.Terminal)
+	Intro    string
+	Usage    string
+}
+
+func ExecuteCommandLine(cmdline string, term *terminal.Terminal,context *Utils.UserContextType){
+	splwords := strings.Split(cmdline," ")
+	if(splwords[0] == "help"){
+		GetHelp(splwords[1:],term)
+		return
+	}
+	vf,ok := CommandList.Get(splwords[0])
+	if(!ok){
+		term.Write([]byte("NSMC Server Launcher Shell: Invalid Command! Use 'help' command to get help.\n"))
+		return
+	}
+	rcv,ok := vf.(RegistedCommand)
+	if(!ok){
+		fmt.Fprintf(&Logger.Log.SysWarning,"Command '%v' Registry Invalid\n",splwords[0])
+		term.Write([]byte("NSMC Server Launcher Shell: Invalid Command!\n"))
+		return
+	}
+	rcv.CmdFunc(splwords[1:],term,context)
+	term.SetPrompt("NSMC " + context.PWD + " >")
+}
+
+func GetHelp(args []string, term *terminal.Terminal){
+	switch len(args) {
+	case 1:
+		vf,ok := CommandList.Get(args[0])
+		if(!ok){
+			term.Write([]byte("help: Invalid Command!\n"))
+			return
+		}
+		rcv,ok := vf.(RegistedCommand)
+		if(!ok){
+			fmt.Fprintf(&Logger.Log.SysWarning,"Command '%v' Registry Invalid\n",args[0])
+			term.Write([]byte("NSMC Server Launcher Shell: Invalid Command!\n"))
+			return
+		}
+		term.Write([]byte(fmt.Sprintf("%s - %s\n\nUsage:\n%s\n\n",args[0],rcv.Intro,rcv.Usage)))
+		rcv.HelpFunc("",term)
+		term.Write([]byte("\n"))
+		break
+	case 2:
+		vf,ok := CommandList.Get(args[0])
+		if(!ok){
+			term.Write([]byte("help: Invalid Command!\n"))
+			return
+		}
+		rcv,ok := vf.(RegistedCommand)
+		if(!ok){
+			fmt.Fprintf(&Logger.Log.SysWarning,"Command '%v' Registry Invalid\n",args[0])
+			term.Write([]byte("NSMC Server Launcher Shell: Invalid Command!\n"))
+			return
+		}
+		term.Write([]byte(fmt.Sprintf("Help Topic: %s -> %s\n\n",args[0],args[1])))
+		rcv.HelpFunc(args[1],term)
+		term.Write([]byte("\n"))
+		break
+	default:
+		term.Write([]byte("You can get detail infomations by using 'help <command>' or 'help <commmand> <topic>'\n\n"))
+		term.Write([]byte("Available Commands:\n"))
+		term.Write([]byte(HelpCommandList))
+		term.Write([]byte("\n\n"))
+		break
+	}
+}

+ 60 - 0
Terminal/Terminal.go

@@ -0,0 +1,60 @@
+package Terminal
+
+import (
+	"net"
+	"git.swzry.com/NSMCServerLauncher/Logger"
+	"fmt"
+	"github.com/swzry/go.TSmap"
+	"git.swzry.com/NSMCServerLauncher/Utils"
+)
+
+func HandlerTerminal(clientList TSmap.TSmap,conn net.Conn, chnum int){
+	clobj,ok := clientList.Get(conn)
+	if(!ok){
+		fmt.Fprintf(&Logger.Log.SSH,"Client '%v', Channels %v, Failed Handling Terminal : Client Not Found.\n",conn.RemoteAddr(),chnum)
+		return
+	}
+	clientConn, ok := clobj.(*Utils.ClientConnection)
+	if(!ok){
+		fmt.Fprintf(&Logger.Log.SSH,"Client '%v', Channels %v, Failed Handling Terminal : Internal Error: Can't Convert '%T' into 'Utils.ClientConnection'.\n",conn.RemoteAddr(),chnum,clobj)
+		return
+	}
+	chlist, ok := clientConn.Channels.(TSmap.TSmap)
+	if(!ok){
+		fmt.Fprintf(&Logger.Log.SSH,"Client '%v', Channels %v, Failed Handling Terminal : Internal Error: Can't Convert '%T' into 'TSMap.TSMap'.\n",conn.RemoteAddr(),chnum,clientConn.Channels)
+		return
+	}
+	chobj,ok := chlist.Get(chnum)
+	if(!ok){
+		fmt.Fprintf(&Logger.Log.SSH,"Client '%v', Channels %v, Failed Handling Terminal : Channel Not Found.\n",conn.RemoteAddr(),chnum)
+		return
+	}
+	channel,ok := chobj.(Utils.AvaliableChannel)
+	if(!ok){
+		fmt.Fprintf(&Logger.Log.SSH,"Client '%v', Channels %v, Failed Handling Terminal : Internal Error: Can't Convert '%T' into 'Utils.AvaliableChannel'.\n",conn.RemoteAddr(),chnum,chobj)
+		return
+	}
+	channel.Term.Write([]byte("NSMC Server Launcher V1.0 - Shell\n"))
+	channel.Term.SetPrompt("NSMC / >")
+	for {
+		line,err := channel.Term.ReadLine()
+		if( err != nil){
+			fmt.Fprintf(&Logger.Log.SSH,"Client '%v', Channels %v, Discard. Detail: %v.\n",conn.RemoteAddr(),chnum,err)
+			break
+		}
+		switch line {
+		case "exit":
+			clientList.Delete(conn)
+			conn.Close()
+			break
+		case "about":
+			channel.Term.Write([]byte("NSMC Server Launcher V1.0\n"))
+			channel.Term.Write([]byte("(https://git.swzry.com/zry/NSMCServerLauncher/)\n"))
+			channel.Term.Write([]byte("By ZRY (https://www.swzry.com/)\n\n"))
+			break
+		default:
+			ExecuteCommandLine(line,channel.Term,&channel.UserContext)
+			break;
+		}
+	}
+}

+ 21 - 0
Utils/Types.go

@@ -0,0 +1,21 @@
+package Utils
+
+import (
+	"github.com/swzry/go.TSmap"
+	"golang.org/x/crypto/ssh"
+	"golang.org/x/crypto/ssh/terminal"
+)
+
+type ClientConnection struct {
+	Channels TSmap.TSmap
+}
+
+type UserContextType struct {
+	PWD string
+}
+
+type AvaliableChannel struct {
+	Channel ssh.Channel
+	Term    *terminal.Terminal
+	UserContext UserContextType
+}

+ 86 - 0
Utils/Utils.go

@@ -0,0 +1,86 @@
+package Utils
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	"io"
+	"fmt"
+)
+
+func GetCurrentDirectory() (string,error) {
+	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
+	return strings.Replace(dir, "\\", "/", -1),err
+}
+
+func PathExists(path string) (bool, error) {
+	_, err := os.Stat(path)
+	if err == nil {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
+
+func CopyFile(dstName, srcName string) (written int64, err error) {
+	src, err := os.Open(srcName)
+	if err != nil {
+		return
+	}
+	defer src.Close()
+	dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
+	if err != nil {
+		return
+	}
+	defer dst.Close()
+	return io.Copy(dst, src)
+}
+
+func MkDirIfNotExist(path string) error{
+	if r,err := PathExists(path); ((!r) || (err != nil)) {
+		fmt.Printf("[StdOutLog][WARNING] Dir '%v' Not Exist! Will Create One.\r\n",path)
+		if err := os.MkdirAll(path,0777);err != nil {
+			fmt.Println("[StdOutLog][Fatal Error] Failed Create Dir '",path,"' : ",err)
+			return err
+		}
+	}
+	return nil
+}
+
+func StripSpaceAndQuote(path string) string{
+	s := strings.TrimSpace(path)
+	s = strings.Trim(s,`"`)
+	return s
+}
+
+func RestrictQuotedArgs(input []string) []string{
+	ostr := make([]string,0)
+	inqstr := ""
+	inQuoteFlag := false
+	for i := 0; i < len(input); i++ {
+		if inQuoteFlag{
+			if input[i][len(input[i])-1] == '"'{
+				inqstr += input[i][:(len(input[i])-1)]
+				ostr = append(ostr,StripSpaceAndQuote(inqstr))
+				inqstr = ""
+				inQuoteFlag = false
+			}else{
+				inqstr += input[i] + " "
+			}
+		}else{
+			if input[i][0] == '"'{
+				if input[i][len(input[i])-1] == '"'{
+					ostr = append(ostr,StripSpaceAndQuote(input[i]))
+				}else{
+					inQuoteFlag = true
+					inqstr += input[i][1:] + " "
+				}
+			}else{
+				ostr = append(ostr,StripSpaceAndQuote(input[i]))
+			}
+		}
+	}
+	return ostr
+}

+ 62 - 0
dist/sysfiles/conftpl/logger.conf

@@ -0,0 +1,62 @@
+[global]
+log_dir = "./data/log/"
+debug_log = true
+# The format of time display. e.g. "2006-01-02 15:04:05"
+# It is a strange way to express the format, but it is NOT MY FAULT.
+# It is just the STRANGE feature of GoLang.
+# See also https://golang.org/pkg/time/#Time.Format
+time_format = "2006-01-02 15:04:05.999999999 -0700 MST"
+
+# [system] For system log
+# [mclauncher.stdout] For minecraft server stdout log
+# [mclauncher.stderr] For minecraft server stderr log
+# [ssh] For ssh server log
+
+[system]
+# Filename is the file to write logs to.  Backup log files will be retained
+# in the same directory.
+Filename = "System.log"
+#MaxSize is the maximum size in megabytes of the log file before it gets
+# rotated.
+MaxSize = 20
+# MaxAge is the maximum number of days to retain old log files based on the
+# timestamp encoded in their filename.  Note that a day is defined as 24
+# hours and may not exactly correspond to calendar days due to daylight
+# savings, leap seconds, etc. The default is not to remove old log files
+# based on age.
+MaxAge = 365
+# MaxBackups is the maximum number of old log files to retain.  The default
+# is to retain all old log files (though MaxAge may still cause them to get
+# deleted.)
+MaxBackups = 300
+# LocalTime determines if the time used for formatting the timestamps in
+# backup files is the computer's local time.  The default is to use UTC
+# time.
+LocalTime = true
+# Compress determines if the rotated log files should be compressed
+# using gzip.
+Compress = true
+
+[mclauncher.stdout]
+Filename = "MCLauncherOut.log"
+MaxSize = 20
+MaxAge = 365
+MaxBackups = 300
+LocalTime = true
+Compress = true
+
+[mclauncher.stderr]
+Filename = "MCLauncherErr.log"
+MaxSize = 20
+MaxAge = 365
+MaxBackups = 300
+LocalTime = true
+Compress = true
+
+[ssh]
+Filename = "SSH.log"
+MaxSize = 20
+MaxAge = 365
+MaxBackups = 300
+LocalTime = true
+Compress = true

+ 28 - 0
dist/sysfiles/conftpl/sshd.conf

@@ -0,0 +1,28 @@
+[sshserver]
+bind_address = "0.0.0.0:2200"
+# The host key for SSH server. If it does not exist, a new
+# key will be generated.
+host_key_file = "./data/keys/sshhost.key"
+# If Host Key doesn't exist, how many bits will the new key
+# be generated.
+defult_key_length = 2048
+
+[auth]
+# The max times for trying bad passwords or certs.
+max_auth_tries = 3
+# The super user has permissions to manage users.
+super_user = admin
+
+# <username>:<bcrypt password>
+# One user per line.
+# You can use tools/bcryptGen to generated it.
+# e.g.:
+#
+# /home/gopath/git.swzry.com/zry/NSMCServerLauncher/tools $ ./bcryptGen admin admin
+# admin:$2a$10$F8XHjF/ZEtRF01VqAKBHOOO7cHXm3WcNY46Q..UWswXVlIEo23fuC
+# /home/gopath/git.swzry.com/zry/NSMCServerLauncher/tools $ ./bcryptGen test test123
+# test:$2a$10$jZ81hDAI4lTO5pBJBYbY4OJn5tuZAeqgFm.Kbe8waRfJpOxiGg.v.
+
+[users]
+admin:$2a$10$F8XHjF/ZEtRF01VqAKBHOOO7cHXm3WcNY46Q..UWswXVlIEo23fuC
+test:$2a$10$jZ81hDAI4lTO5pBJBYbY4OJn5tuZAeqgFm.Kbe8waRfJpOxiGg.v.

+ 86 - 0
main.go

@@ -0,0 +1,86 @@
+package main
+
+import (
+	"path"
+	"fmt"
+	"os"
+	"git.swzry.com/NSMCServerLauncher/Logger"
+	"git.swzry.com/NSMCServerLauncher/Utils"
+	"git.swzry.com/NSMCServerLauncher/SSHServer"
+	"git.swzry.com/NSMCServerLauncher/Commands"
+)
+
+var CurrentPath string
+var MainThreadBlockChan chan byte
+
+func main(){
+	cp,err := Utils.GetCurrentDirectory()
+	if(err != nil){
+		fmt.Println("[StdOutLog][Fatal Error] Failed Get PWD: ",err)
+		return
+	}
+	CurrentPath = cp
+	if r,err := Utils.PathExists(path.Join(CurrentPath,"sysfiles")); ((!r) || (err != nil)) {
+		fmt.Println("[StdOutLog][Fatal Error] System File Dir './sysfiles/' Not Exist! ")
+		return
+	}
+	isinit := false
+	if r,err := Utils.PathExists(path.Join(CurrentPath,"data")); ((!r) || (err != nil)) {
+		fmt.Println("[StdOutLog][WARNING] Dir './data/' Not Exist! Will Create One.")
+		isinit = true
+		if err := os.MkdirAll(path.Join(CurrentPath,"data"),0777);err != nil {
+			fmt.Println("[StdOutLog][Fatal Error] Failed Create Dir './data/' : ",err)
+			return
+		}
+	}
+	if r,err := Utils.PathExists(path.Join(CurrentPath,"data","conf")); ((!r) || (err != nil)) {
+		fmt.Println("[StdOutLog][WARNING] Config Dir './data/conf/' Not Exist! Will Create One.")
+		isinit = true
+		if err := os.MkdirAll(path.Join(CurrentPath,"data","conf"),0777);err != nil {
+			fmt.Println("[StdOutLog][Fatal Error] Failed Create Dir './data/conf/' : ",err)
+			return
+		}
+	}
+	if r,err := Utils.PathExists(path.Join(CurrentPath,"data","conf", "logger.conf")); ((!r) || (err != nil)) {
+		fmt.Println("[StdOutLog][WARNING] Config File './data/conf/logger.conf' Not Exist! Will Create A Template Config File..")
+		isinit = true
+		if _,err := Utils.CopyFile(path.Join(CurrentPath,"data","conf", "logger.conf"),path.Join(CurrentPath,"sysfiles","conftpl", "logger.conf"));err != nil {
+			fmt.Println("[StdOutLog][Fatal Error] Failed Copy Template Config File './data/conf/logger.conf' : ",err)
+			return
+		}
+	}
+	if r,err := Utils.PathExists(path.Join(CurrentPath,"data","conf", "sshd.conf")); ((!r) || (err != nil)) {
+		fmt.Println("[StdOutLog][WARNING] Config File './data/conf/sshd.conf' Not Exist! Will Create A Template Config File..")
+		isinit = true
+		if _,err := Utils.CopyFile(path.Join(CurrentPath,"data","conf", "sshd.conf"),path.Join(CurrentPath,"sysfiles","conftpl", "sshd.conf"));err != nil {
+			fmt.Println("[StdOutLog][Fatal Error] Failed Copy Template Config File './data/conf/sshd.conf' : ",err)
+			return
+		}
+	}
+	// TODO: Other Config File Process Code Here
+	if(isinit){
+		fmt.Println("[StdOutLog][WARNING] At this time, we had created template config files for you, you should edit them and restart this server..")
+		fmt.Println("[StdOutLog][INFO] Server Exit.")
+		return
+	}
+	if Logger.LoadLoggerConfig(path.Join(CurrentPath,"data","conf", "logger.conf")) != nil{
+		return
+	}
+	if Logger.InitLogger() != nil {
+		return
+	}
+	fmt.Fprintln(&Logger.Log.SysInfo,"NSMC Server Launcher System Start.")
+	if err := Utils.MkDirIfNotExist(path.Join(CurrentPath,"data","keys")); err != nil {
+		fmt.Println("[StdOutLog][Fatal Error] Failed Create Dir '",path.Join(CurrentPath,"data","keys"),"' : ",err)
+		return
+	}
+	if SSHServer.LoadSSHConfig(path.Join(CurrentPath,"data","conf", "sshd.conf")) != nil{
+		return
+	}
+	fmt.Fprintln(&Logger.Log.SysInfo,"Regist Commands....")
+	Commands.RegistAllCommands()
+	fmt.Fprintln(&Logger.Log.SysInfo,"Start SSH Server...")
+	go SSHServer.StartSSHServer(MainThreadBlockChan)
+	<-MainThreadBlockChan
+}
+

+ 20 - 0
tools/bcryptGen/bcryptGen.go

@@ -0,0 +1,20 @@
+package main
+
+import (
+	"os"
+	"fmt"
+	"golang.org/x/crypto/bcrypt"
+)
+
+func main()  {
+	if len(os.Args) != 3{
+		fmt.Println("Usage: bcryptGen <username> <password>")
+		return
+	}
+	psb,err := bcrypt.GenerateFromPassword([]byte(os.Args[2]),10)
+	if(err != nil){
+		fmt.Println("Err: ",err.Error())
+		return
+	}
+	fmt.Printf("%s:%s",os.Args[1],string(psb))
+}