update at 2023/08/14
This commit is contained in:
38
sshsvc/.ssh/id_rsa
Normal file
38
sshsvc/.ssh/id_rsa
Normal file
@@ -0,0 +1,38 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
sshsvc/.ssh/id_rsa.pub
Normal file
1
sshsvc/.ssh/id_rsa.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCjzE0MHy0P2sQmG1OKvArwLEzR77BwxPp+4N9NKBz8lEyNch4zoHvoLDaUJKPBMwTrScZnxhvRJNde6hz5Z0QVxZolVKV95fMfgb9YICzDfPF3Bphae5q/PBYUJNfrC9SAjQeHV4CCO6xk6LV8O/Nfw8uaXwtuN3yfpUWd5mRWKMc3GHV4vxerfKoIQZN6etLhIEYibAhCJ21P0uoWzZl7ceLmkG+6qrRSbyC0bCnrN3+wjN4bwtflrEFJk3XQGuRbVsYLzux+mMm9p22+CLPRl/FUPF3x4/wKCsqr7JDSxx80HSPhk4g/1nCTpDR9hAfF6A9ww2va/k8jMeYPBO8Owe7erLEZzvg5WQAR3Wj05eJpJD99BeC91S5KW6AFQDfDHxWWprSibd/Jj9dtct2pK2ZxW0ph9OJ49ftRhzT1iVi4UnJwnG5/qLfwl6Tnx+qnQ1If/9lY/99erCTATzFTMSImKrRz7CnuPlcw8WkzCYpWKKe0kJLkL2cRD5Qi4yk= simon@simonzhangsz
|
||||
38
sshsvc/.ssh/private_key.pem
Normal file
38
sshsvc/.ssh/private_key.pem
Normal file
@@ -0,0 +1,38 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
135
sshsvc/config/config.go
Normal file
135
sshsvc/config/config.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"ems.agt/lib/global"
|
||||
"ems.agt/lib/log"
|
||||
"ems.agt/sshsvc/logmml"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Yaml struct of config
|
||||
type YamlConfig struct {
|
||||
Logger struct {
|
||||
File string `yaml:"file"`
|
||||
Level string `yaml:"level"`
|
||||
Duration int `yaml:"duration"`
|
||||
Count int `yaml:"count"`
|
||||
} `yaml:"logger"`
|
||||
|
||||
Logmml struct {
|
||||
File string `yaml:"file"`
|
||||
Duration int `yaml:"duration"`
|
||||
Count int `yaml:"count"`
|
||||
Level string `yaml:"level"`
|
||||
} `yaml:"logmml"`
|
||||
|
||||
Sshd struct {
|
||||
ListenAddr string `yaml:"listenAddr"`
|
||||
ListenPort uint16 `yaml:"listenPort"`
|
||||
PrivateKey string `yaml:"privateKey"`
|
||||
MaxConnNum uint8 `yaml:"maxConnNum"`
|
||||
Timeout uint16 `yaml:"timeout"`
|
||||
Session string `yaml:"session"`
|
||||
} `yaml:"sshd"`
|
||||
|
||||
Database struct {
|
||||
Type string `yaml:"type"`
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
Host string `yaml:"host"`
|
||||
Port string `yaml:"port"`
|
||||
Name string `yaml:"name"`
|
||||
Backup string `yaml:"backup"`
|
||||
} `yaml:"database"`
|
||||
|
||||
OMC struct {
|
||||
HttpUri string `yaml:"httpUri"`
|
||||
UserCrypt string `yaml:"userCrypt"`
|
||||
} `yaml:"omc"`
|
||||
}
|
||||
|
||||
var yamlConfig YamlConfig
|
||||
|
||||
func ReadConfig(configFile string) {
|
||||
yamlFile, err := os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
fmt.Println("Read yaml config file error:", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(yamlFile, &yamlConfig)
|
||||
if err != nil {
|
||||
fmt.Println("Unmarshal error:", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
}
|
||||
|
||||
func GetYamlConfig() *YamlConfig {
|
||||
return &yamlConfig
|
||||
}
|
||||
|
||||
func GetLogLevel() log.LogLevel {
|
||||
var logLevel log.LogLevel
|
||||
switch strings.ToLower(yamlConfig.Logger.Level) {
|
||||
case "trace":
|
||||
logLevel = log.LOG_TRACE
|
||||
case "info":
|
||||
logLevel = log.LOG_INFO
|
||||
case "debug":
|
||||
logLevel = log.LOG_DEBUG
|
||||
case "warn":
|
||||
logLevel = log.LOG_WARN
|
||||
case "error":
|
||||
logLevel = log.LOG_ERROR
|
||||
case "fatal":
|
||||
logLevel = log.LOG_FATAL
|
||||
case "off":
|
||||
logLevel = log.LOG_OFF
|
||||
default:
|
||||
logLevel = log.LOG_DEBUG
|
||||
}
|
||||
return logLevel
|
||||
}
|
||||
|
||||
func GetLogMmlLevel() logmml.LogLevel {
|
||||
var logLevel logmml.LogLevel
|
||||
switch strings.ToLower(yamlConfig.Logmml.Level) {
|
||||
case "cmd", "command":
|
||||
logLevel = logmml.LOG_CMD
|
||||
case "ret", "result":
|
||||
logLevel = logmml.LOG_RET
|
||||
default:
|
||||
logLevel = logmml.LOG_CMD
|
||||
}
|
||||
return logLevel
|
||||
}
|
||||
|
||||
func GetDefaultUserAgent() string {
|
||||
return "OMC-sshsvc/" + global.Version
|
||||
}
|
||||
|
||||
const DefaultConfigFile = "./etc/sshsvc.yaml"
|
||||
|
||||
func init() {
|
||||
cfile := flag.String("c", DefaultConfigFile, "config file")
|
||||
pv := flag.Bool("v", false, "print version")
|
||||
ph := flag.Bool("h", false, "print help")
|
||||
|
||||
flag.Parse()
|
||||
if *pv {
|
||||
fmt.Printf("OMC sshsvc version: %s\n%s\n%s\n\n", global.Version, global.BuildTime, global.GoVer)
|
||||
os.Exit(0)
|
||||
}
|
||||
if *ph {
|
||||
flag.Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
ReadConfig(*cfile)
|
||||
}
|
||||
50
sshsvc/etc/sshsvc.yaml
Normal file
50
sshsvc/etc/sshsvc.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
# file: log file name
|
||||
# level: /trace/debug/info/warn/error/fatal, default: debug
|
||||
# duration: rotation time with xx hours, example: 1/12/24 hours
|
||||
# count: rotation count of log, default is 30 rotation
|
||||
logger:
|
||||
file: d:/omc.git/goprojects/ems.agt/sshsvc/log/sshsvc.log
|
||||
level: trace
|
||||
duration: 24
|
||||
count: 30
|
||||
|
||||
# file: MML log file name
|
||||
# duration: rotation time with xx hours, example: 1/12/24 hours
|
||||
# count: rotation count of log, default is 30 rotation
|
||||
# level: cmd/ret log cmd/log cmd & result
|
||||
logmml:
|
||||
file: d:/omc.git/goprojects/ems.agt/sshsvc/mmllog/omcmml.log
|
||||
duration: 24
|
||||
count: 30
|
||||
level: ret
|
||||
|
||||
# ssh service listen ipv4/v6 and port, support multiple routines
|
||||
# ip: 0.0.0.0 or ::0, support IPv4/v6
|
||||
# session: single/multiple session for one user
|
||||
sshd:
|
||||
listenAddr: 0.0.0.0
|
||||
listenPort: 2222
|
||||
privateKey: ./.ssh/id_rsa
|
||||
maxConnNum: 20
|
||||
timeout: 1800
|
||||
session: multiple
|
||||
|
||||
database:
|
||||
type: mysql
|
||||
user: root
|
||||
password: 1000omc@kp!
|
||||
host: 127.0.0.1
|
||||
port: 33066
|
||||
name: omc_db
|
||||
|
||||
omc:
|
||||
httpUri: http://127.0.0.1:3040
|
||||
userCrypt: bcrypt
|
||||
|
||||
ne:
|
||||
port: 4100
|
||||
sleep: 200
|
||||
user: admin
|
||||
password: admin
|
||||
|
||||
|
||||
187
sshsvc/logmml/logmml.go
Normal file
187
sshsvc/logmml/logmml.go
Normal file
@@ -0,0 +1,187 @@
|
||||
// logger for omc/ems
|
||||
|
||||
package logmml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
// LogLevel defines a log level
|
||||
type LogLevel int
|
||||
|
||||
// enum all LogLevels
|
||||
const (
|
||||
// following level also match syslog.Priority value
|
||||
LOG_RET LogLevel = iota
|
||||
LOG_CMD
|
||||
LOG_OFF
|
||||
LOG_NODEF
|
||||
)
|
||||
|
||||
// default log options
|
||||
const (
|
||||
DEFAULT_LOG_PREFIX = "omc:mml"
|
||||
DEFAULT_LOG_FLAG = log.Ldate | log.Ltime | log.Lmsgprefix
|
||||
DEFAULT_LOG_LEVEL = LOG_CMD
|
||||
DEFAULT_CALL_DEPTH = 0
|
||||
)
|
||||
|
||||
// Logger is a logger interface
|
||||
type Logger interface {
|
||||
Ret(v ...interface{})
|
||||
Retf(format string, v ...interface{})
|
||||
Cmd(v ...interface{})
|
||||
Cmdf(format string, v ...interface{})
|
||||
|
||||
Level() LogLevel
|
||||
LevelString() string
|
||||
SetLevel(l LogLevel)
|
||||
}
|
||||
|
||||
var _ Logger = DiscardLogger{}
|
||||
|
||||
// DiscardLogger don't log implementation for ILogger
|
||||
type DiscardLogger struct{}
|
||||
|
||||
// Trace empty implementation
|
||||
func (DiscardLogger) Ret(v ...interface{}) {}
|
||||
|
||||
// Tracef empty implementation
|
||||
func (DiscardLogger) Retf(format string, v ...interface{}) {}
|
||||
|
||||
// Debug empty implementation
|
||||
func (DiscardLogger) Cmd(v ...interface{}) {}
|
||||
|
||||
// Debugf empty implementation
|
||||
func (DiscardLogger) Cmdf(format string, v ...interface{}) {}
|
||||
|
||||
// Level empty implementation
|
||||
func (DiscardLogger) Level() LogLevel {
|
||||
return LOG_NODEF
|
||||
}
|
||||
|
||||
// Level empty implementation
|
||||
func (DiscardLogger) LevelString() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetLevel empty implementation
|
||||
func (DiscardLogger) SetLevel(l LogLevel) {}
|
||||
|
||||
// EmsLogger is the default implment of ILogger
|
||||
type MMLLogger struct {
|
||||
RET *log.Logger
|
||||
CMD *log.Logger
|
||||
level LogLevel
|
||||
levelString []string
|
||||
//depth int
|
||||
}
|
||||
|
||||
var _ Logger = &MMLLogger{}
|
||||
|
||||
// NewEmsLogger2 let you customrize your logger prefix and flag
|
||||
func NewMmlLogger2(out io.Writer, prefix string, flag int) *MMLLogger {
|
||||
return NewMmlLogger3(out, prefix, flag, DEFAULT_LOG_LEVEL)
|
||||
}
|
||||
|
||||
// NewEmsLogger3 let you customrize your logger prefix and flag and logLevel
|
||||
func NewMmlLogger3(out io.Writer, prefix string, flag int, l LogLevel) *MMLLogger {
|
||||
return &MMLLogger{
|
||||
RET: log.New(out, fmt.Sprintf("[%s] [ret]: ", prefix), flag),
|
||||
CMD: log.New(out, fmt.Sprintf("[%s] [cmd]: ", prefix), flag),
|
||||
level: l,
|
||||
levelString: []string{"ret", "cmd"},
|
||||
//depth: DEFAULT_CALL_DEPTH,
|
||||
}
|
||||
}
|
||||
|
||||
// Trace implement ILogger
|
||||
func (s *MMLLogger) Ret(v ...interface{}) {
|
||||
if s.level <= LOG_RET {
|
||||
//_ = s.RET.Output(s.depth, fmt.Sprintln(v...))
|
||||
_ = s.RET.Output(0, fmt.Sprintln(v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Tracef implement ILogger
|
||||
func (s *MMLLogger) Retf(format string, v ...interface{}) {
|
||||
if s.level <= LOG_RET {
|
||||
_ = s.RET.Output(0, fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Debug implement ILogger
|
||||
func (s *MMLLogger) Cmd(v ...interface{}) {
|
||||
if s.level <= LOG_CMD {
|
||||
_ = s.CMD.Output(0, fmt.Sprintln(v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Debugf implement ILogger
|
||||
func (s *MMLLogger) Cmdf(format string, v ...interface{}) {
|
||||
if s.level <= LOG_CMD {
|
||||
_ = s.CMD.Output(0, fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Info implement ILogger
|
||||
|
||||
// Level implement ILogger
|
||||
func (s *MMLLogger) Level() LogLevel {
|
||||
return s.level
|
||||
}
|
||||
|
||||
// Level implement ILogger
|
||||
func (s *MMLLogger) LevelString() string {
|
||||
return s.levelString[s.level]
|
||||
}
|
||||
|
||||
// SetLevel implement ILogger
|
||||
func (s *MMLLogger) SetLevel(l LogLevel) {
|
||||
s.level = l
|
||||
}
|
||||
|
||||
var Elogger Logger
|
||||
|
||||
func InitMmlLogger(logFile string, period int, count int, prefix string, logLevel LogLevel) {
|
||||
logWriter := getLogWriter(logFile, period, count)
|
||||
Elogger = NewMmlLogger3(logWriter, prefix, DEFAULT_LOG_FLAG, logLevel)
|
||||
fmt.Printf("logFile=%s, period=%d, count=%d, prefix=%s, logLevel=%s\n", logFile, period, count, prefix, GetLevelString())
|
||||
}
|
||||
|
||||
// Trace implement ILogger
|
||||
func Ret(v ...interface{}) {
|
||||
Elogger.Ret(v...)
|
||||
}
|
||||
|
||||
// Tracef implement ILogger
|
||||
func Retf(format string, v ...interface{}) {
|
||||
Elogger.Retf(format, v...)
|
||||
}
|
||||
|
||||
// Debug implement ILogger
|
||||
func Cmd(v ...interface{}) {
|
||||
Elogger.Cmd(v...)
|
||||
}
|
||||
|
||||
// Debugf implement ILogger
|
||||
func Cmdf(format string, v ...interface{}) {
|
||||
Elogger.Cmdf(format, v...)
|
||||
}
|
||||
|
||||
// Level implement ILogger
|
||||
func GetLevel() LogLevel {
|
||||
return Elogger.Level()
|
||||
}
|
||||
|
||||
// Level implement ILogger
|
||||
func GetLevelString() string {
|
||||
return Elogger.LevelString()
|
||||
}
|
||||
|
||||
// SetLevel implement ILogger
|
||||
func SetLevel(l LogLevel) {
|
||||
Elogger.SetLevel(l)
|
||||
}
|
||||
71
sshsvc/logmml/partition.go
Normal file
71
sshsvc/logmml/partition.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package logmml
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
rotatelogs "github.com/lestrrat/go-file-rotatelogs"
|
||||
)
|
||||
|
||||
type WriteSyncer interface {
|
||||
io.Writer
|
||||
Sync() error
|
||||
}
|
||||
|
||||
// 得到LogWriter
|
||||
func getLogWriter(filePath string, period, count int) WriteSyncer {
|
||||
warnIoWriter := getWriter(filePath, period, count)
|
||||
return addSync(warnIoWriter)
|
||||
}
|
||||
|
||||
// 日志文件切割
|
||||
func getWriter(filename string, period, count int) io.Writer {
|
||||
// 保存日志count天,每period小时分割一次日志
|
||||
duration := time.Hour * time.Duration(period)
|
||||
var logfile string
|
||||
if period >= 24 {
|
||||
logfile = filename + "-%Y%m%d"
|
||||
} else {
|
||||
logfile = filename + "-%Y%m%d%H"
|
||||
}
|
||||
hook, err := rotatelogs.New(
|
||||
|
||||
logfile,
|
||||
rotatelogs.WithLinkName(filename),
|
||||
// rotatelogs.WithMaxAge(duration),
|
||||
rotatelogs.WithRotationCount(count),
|
||||
rotatelogs.WithRotationTime(duration),
|
||||
rotatelogs.WithLocation(time.Local),
|
||||
)
|
||||
|
||||
//保存日志30天,每1分钟分割一次日志
|
||||
/*
|
||||
hook, err := rotatelogs.New(
|
||||
filename+"_%Y%m%d%H%M.log",
|
||||
rotatelogs.WithLinkName(filename),
|
||||
rotatelogs.WithMaxAge(time.Hour*24*30),
|
||||
rotatelogs.WithRotationTime(time.Minute*1),
|
||||
)
|
||||
*/
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return hook
|
||||
}
|
||||
|
||||
func addSync(w io.Writer) WriteSyncer {
|
||||
switch w := w.(type) {
|
||||
case WriteSyncer:
|
||||
return w
|
||||
default:
|
||||
return writerWrapper{w}
|
||||
}
|
||||
}
|
||||
|
||||
type writerWrapper struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (w writerWrapper) Sync() error {
|
||||
return nil
|
||||
}
|
||||
18
sshsvc/makefile
Normal file
18
sshsvc/makefile
Normal file
@@ -0,0 +1,18 @@
|
||||
# Makefile for OMC-OMC-crontask project
|
||||
|
||||
PROJECT = OMC
|
||||
VERSION = 5GC16.1.1
|
||||
LIBDIR = ems.agt/lib
|
||||
BINNAME = sshsvc
|
||||
|
||||
.PHONY: build $(BINNAME)
|
||||
build $(BINNAME):
|
||||
go build -o $(BINNAME) -v -ldflags "-X '$(LIBDIR)/global.Version=$(VERSION)' \
|
||||
-X '$(LIBDIR)/global.BuildTime=`date`' \
|
||||
-X '$(LIBDIR)/global.GoVer=`go version`'"
|
||||
|
||||
run: $(BINNAME)
|
||||
./$(BINNAME)
|
||||
|
||||
clean:
|
||||
rm ./$(BINNAME)
|
||||
901
sshsvc/mml/parse.go.bak
Normal file
901
sshsvc/mml/parse.go.bak
Normal file
@@ -0,0 +1,901 @@
|
||||
package mml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"ems.agt/lib/dborm"
|
||||
"ems.agt/lib/global"
|
||||
"ems.agt/lib/log"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type Param struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type MmlCommand struct {
|
||||
Operation string `json:"operation"`
|
||||
Object string `json:"object"`
|
||||
Params []Param `json:"params"`
|
||||
PaList []string `json:"paList"`
|
||||
AaList []string `json:"aaList"`
|
||||
AaMap map[string]interface{} `json:"aaMap"`
|
||||
NaMap map[string]interface{} `json:"naMap"`
|
||||
AaUri []string `json:"aaUri"`
|
||||
AaLoc []string `json:"aaLoc"` // for loc parameter
|
||||
}
|
||||
|
||||
type MmlVar struct {
|
||||
Version string `json:"version"`
|
||||
Output string `json:"output"`
|
||||
Limit int `json:"limit"`
|
||||
User string `json:"user"`
|
||||
Token string `josn:"token"`
|
||||
}
|
||||
|
||||
var OmcMmlVar *MmlVar
|
||||
|
||||
func init() {
|
||||
OmcMmlVar = &MmlVar{
|
||||
Version: "16.1.1",
|
||||
Output: DefaultFormatType,
|
||||
Limit: 50,
|
||||
}
|
||||
}
|
||||
|
||||
func SetOmcMmlVarOutput(output string) {
|
||||
OmcMmlVar.Output = output
|
||||
}
|
||||
|
||||
func SetOmcMmlVarLimit(limit int) {
|
||||
OmcMmlVar.Limit = limit
|
||||
}
|
||||
|
||||
func splitByColon(str string) []string {
|
||||
return splitBy(str, ':')
|
||||
}
|
||||
|
||||
func splitByComma(str string) []string {
|
||||
return splitBy(str, ',')
|
||||
}
|
||||
|
||||
func splitBy(str string, sep rune) []string {
|
||||
var result []string
|
||||
var stack []string
|
||||
var current []rune
|
||||
var quotes, apoFlag bool = false, false
|
||||
|
||||
for _, c := range str {
|
||||
if c == '{' || c == '[' || (c == '\'' && apoFlag == false) || (c == '"' && quotes == false) { // "'"
|
||||
apoFlag = true
|
||||
quotes = true
|
||||
stack = append(stack, string(c))
|
||||
} else if c == '}' || c == ']' || (c == '\'' && apoFlag == true) || (c == '"' && quotes == true) {
|
||||
apoFlag = false
|
||||
quotes = false
|
||||
if len(stack) > 0 {
|
||||
stack = stack[:len(stack)-1]
|
||||
}
|
||||
}
|
||||
|
||||
if c == sep && len(stack) == 0 {
|
||||
result = append(result, string(current))
|
||||
current = []rune{}
|
||||
} else {
|
||||
current = append(current, c)
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, string(current))
|
||||
return result
|
||||
}
|
||||
|
||||
func ParseMMLCommand(mmlStr string, mmlComms *[]MmlCommand) error {
|
||||
log.Info("ParseMMLCommand processing ...")
|
||||
log.Debug("mmlStr: ", mmlStr)
|
||||
|
||||
mc := new(MmlCommand)
|
||||
reg := regexp.MustCompile(`\s*;\s*`)
|
||||
mmls := reg.Split(mmlStr, -1)
|
||||
for _, mml := range mmls {
|
||||
log.Trace("mml:", mml)
|
||||
if len(mml) == 0 {
|
||||
continue
|
||||
}
|
||||
//reg := regexp.MustCompile(`\s*:\s*`)
|
||||
//ms := reg.Split(mml, -1)
|
||||
ms := splitByColon(mml)
|
||||
if len(ms) < 1 || len(ms) > 2 {
|
||||
err := global.ErrMmlInvalidCommandFormat
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if len(ms) == 2 {
|
||||
cmd := strings.Trim(ms[0], " ")
|
||||
reg = regexp.MustCompile(`\s+`)
|
||||
cs := reg.Split(cmd, -1)
|
||||
//cs := strings.Split(cmd, " ")
|
||||
if len(cs) == 2 {
|
||||
mc.Operation = cs[0]
|
||||
mc.Object = cs[1]
|
||||
} else {
|
||||
err := global.ErrMmlInvalidCommandFormat
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
//reg = regexp.MustCompile(`\s*,\s*`)
|
||||
//reg = regexp.MustCompile(`(?U)(?<!\{[^{}]*),(?![^{}]*\})|(?<!\"[^\"]*),(?![^\"]*\")|(?<!\[[^\[\]]*),(?![^\[\]]*\])`)
|
||||
//reg = regexp.MustCompile(`,[^'"\(\)]+`)
|
||||
//params := reg.Split(ms[1], -1)
|
||||
params := splitByComma(strings.Trim(ms[1], " "))
|
||||
//params := strings.Split(ms[1], ",")
|
||||
for _, p := range params {
|
||||
log.Trace("p:", p)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
mc.PaList = append(mc.PaList, p)
|
||||
reg = regexp.MustCompile(`\s*=\s*`)
|
||||
pvs := reg.Split(p, -1)
|
||||
log.Trace("pvs:", pvs)
|
||||
if len(pvs) == 2 {
|
||||
mc.Params = append(mc.Params, Param{Name: pvs[0], Value: pvs[1]})
|
||||
} else {
|
||||
err := global.ErrMmlInvalidCommandFormat
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if len(ms) == 1 {
|
||||
cmd := ms[0]
|
||||
reg = regexp.MustCompile(`\s+`)
|
||||
cs := reg.Split(cmd, -1)
|
||||
//cs := strings.Split(cmd, " ")
|
||||
if len(cs) == 2 {
|
||||
mc.Operation = cs[0]
|
||||
mc.Object = cs[1]
|
||||
} else {
|
||||
err := global.ErrMmlInvalidCommandFormat
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := global.ErrMmlInvalidCommandFormat
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
err := ParseMMLAlias(mc)
|
||||
if err != nil {
|
||||
err := global.ErrMmlInvalidCommandFormat
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
*mmlComms = append(*mmlComms, *mc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseMMLAlias(mml *MmlCommand) error {
|
||||
where := fmt.Sprintf("operation='%s' AND object='%s'", mml.Operation, mml.Object)
|
||||
mc, err := dborm.XormGetMmlCommand("mml_command", where)
|
||||
if err != nil {
|
||||
log.Error("Failed to XormGetMmlCommand: ", err)
|
||||
return err
|
||||
}
|
||||
if mc == nil {
|
||||
err := errors.New("Not found mml map")
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
log.Debug("mml command: ", mc)
|
||||
aaMap := make(map[string]interface{})
|
||||
naMap := make(map[string]interface{})
|
||||
for _, pn := range mml.Params {
|
||||
log.Trace("pn: ", pn)
|
||||
for _, param := range mc.ParamJson {
|
||||
log.Trace("param: ", param)
|
||||
var pv string
|
||||
if pn.Name == param.Name {
|
||||
if param.Apostr == "true" {
|
||||
pv = fmt.Sprintf("'%v'", pn.Value)
|
||||
} else {
|
||||
pv = fmt.Sprintf("%v", pn.Value)
|
||||
}
|
||||
var aa, av string
|
||||
|
||||
if param.Alias != "" {
|
||||
aa = fmt.Sprintf("%s=%v", param.Alias, pv)
|
||||
av = fmt.Sprintf("%v", pv)
|
||||
switch param.Type {
|
||||
case "int":
|
||||
aaMap[param.Alias] = pn.Value
|
||||
naMap[param.Alias] = pn.Value
|
||||
default:
|
||||
aaMap[param.Alias] = pv
|
||||
naMap[param.Alias] = fmt.Sprintf("%v", pn.Value)
|
||||
}
|
||||
} else {
|
||||
aa = fmt.Sprintf("%s=%v", param.Name, pv)
|
||||
av = fmt.Sprintf("%v", pv)
|
||||
switch param.Type {
|
||||
case "int":
|
||||
aaMap[param.Name] = pn.Value
|
||||
naMap[param.Name] = pn.Value
|
||||
default:
|
||||
aaMap[param.Name] = pv
|
||||
naMap[param.Name] = fmt.Sprintf("%v", pn.Value)
|
||||
}
|
||||
}
|
||||
if param.Loc == "" || param.Loc == "true" {
|
||||
mml.AaLoc = append(mml.AaLoc, aa)
|
||||
mml.AaUri = append(mml.AaUri, av)
|
||||
}
|
||||
//mml.AaMap = append(mml.AaMap, aaMap)
|
||||
mml.AaList = append(mml.AaList, aa)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
mml.AaMap = aaMap
|
||||
mml.NaMap = naMap
|
||||
log.Trace("mml.AaMap: ", mml.AaMap)
|
||||
log.Trace("mml.NaMap: ", mml.NaMap)
|
||||
log.Trace("mml.AaList: ", mml.AaList)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseMMLParams(mmlComms *[]MmlCommand) error {
|
||||
for _, mml := range *mmlComms {
|
||||
where := fmt.Sprintf("operation='%s' AND object='%s'", mml.Operation, mml.Object)
|
||||
mc, err := dborm.XormGetMmlCommand("mml_command", where)
|
||||
if err != nil {
|
||||
log.Error("Failed to XormGetMmlCommand: ", err)
|
||||
return err
|
||||
}
|
||||
if mc == nil {
|
||||
err := errors.New("Not found mml map")
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
log.Debug("mml command: ", mc)
|
||||
for _, pn := range mml.Params {
|
||||
log.Trace("pn: ", pn)
|
||||
for _, param := range mc.ParamJson {
|
||||
log.Trace("param: ", param)
|
||||
var pv string
|
||||
if pn.Name == param.Name {
|
||||
if param.Apostr == "true" {
|
||||
pv = fmt.Sprintf("'%v'", pn.Value)
|
||||
} else {
|
||||
pv = fmt.Sprintf("%v", pn.Value)
|
||||
}
|
||||
var aa string
|
||||
aaMap := make(map[string]interface{})
|
||||
if param.Alias != "" {
|
||||
aa = fmt.Sprintf("%s=%v", param.Alias, pv)
|
||||
switch param.Type {
|
||||
case "int":
|
||||
aaMap[param.Alias] = pn.Value
|
||||
case "string":
|
||||
aaMap[param.Alias] = pv
|
||||
}
|
||||
} else {
|
||||
aa = fmt.Sprintf("%s=%v", param.Name, pv)
|
||||
switch param.Type {
|
||||
case "int":
|
||||
aaMap[param.Name] = pn.Value
|
||||
case "string":
|
||||
aaMap[param.Name] = pv
|
||||
}
|
||||
}
|
||||
//mml.AaMap = append(mml.AaMap, aaMap)
|
||||
mml.AaList = append(mml.AaList, aa)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Trace("mml.AaMap: ", mml.AaMap)
|
||||
log.Trace("mml.AaList: ", mml.AaList)
|
||||
*mmlComms = append(*mmlComms, mml)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRequestUri(httpUri string, mmlMap *dborm.MmlHttpMap, mml *MmlCommand) string {
|
||||
requestURI := fmt.Sprintf("%s%s", httpUri, mmlMap.URI)
|
||||
if mmlMap.ExtUri != "" && len(mml.AaUri) > 0 {
|
||||
extUri := strings.Join(mml.AaUri, "/")
|
||||
requestURI = requestURI + fmt.Sprintf(mmlMap.ExtUri, extUri)
|
||||
}
|
||||
if mmlMap.Params != "" {
|
||||
params := strings.Join(mml.AaLoc, "+and+")
|
||||
params = strings.ReplaceAll(params, " ", "+") // replace " " to "+"
|
||||
log.Trace("params:", params)
|
||||
if mmlMap.ParamTag == "SQL" && strings.TrimSpace(params) != "" {
|
||||
params = "+where+" + params
|
||||
}
|
||||
requestURI = fmt.Sprintf("%s%s%s", requestURI, mmlMap.Params, params)
|
||||
}
|
||||
return requestURI
|
||||
}
|
||||
|
||||
func TransMml2HttpReq(sshConn *ssh.ServerConn, httpUri, uerAgent string, mml *MmlCommand) (*[]byte, error) {
|
||||
log.Info("TransMml2HttpReq processing ...")
|
||||
log.Debug("mml: ", mml)
|
||||
|
||||
where := fmt.Sprintf("operation='%s' AND object='%s'", mml.Operation, mml.Object)
|
||||
mmlMap, err := dborm.XormGetMmlHttpMap("mml_http_map", where)
|
||||
if err != nil {
|
||||
log.Error("Failed to XormGetMmlHttpMap: ", err)
|
||||
return ParseErrorOutput(err), err
|
||||
}
|
||||
if mmlMap == nil {
|
||||
err := errors.New("Not found mml map")
|
||||
log.Error(err)
|
||||
return ParseErrorOutput(err), err
|
||||
}
|
||||
log.Trace("mmlMap: ", mmlMap)
|
||||
if mmlMap.Output == "" {
|
||||
mmlMap.Output = "{}"
|
||||
}
|
||||
outputJson := new(dborm.MmlOutput)
|
||||
err = json.Unmarshal([]byte(mmlMap.Output), outputJson)
|
||||
if err != nil {
|
||||
log.Error("Failed to Unmarshal:", err)
|
||||
return ParseErrorOutput(err), err
|
||||
}
|
||||
log.Trace("outputJson: ", outputJson)
|
||||
inputJson := new(dborm.MmlInput)
|
||||
log.Trace("mmlMap.Input: ", mmlMap.Input)
|
||||
if mmlMap.Input == "" {
|
||||
mmlMap.Input = "{}"
|
||||
}
|
||||
err = json.Unmarshal([]byte(mmlMap.Input), inputJson)
|
||||
if err != nil {
|
||||
log.Error("Failed to Unmarshal:", err)
|
||||
return ParseErrorOutput(err), err
|
||||
}
|
||||
log.Trace("inputJson: ", inputJson)
|
||||
|
||||
var requestURI string
|
||||
var output *[]byte
|
||||
client := resty.New()
|
||||
switch strings.ToLower(mmlMap.Method) {
|
||||
case "get":
|
||||
requestURI = parseRequestUri(httpUri, mmlMap, mml)
|
||||
log.Debugf("method: Get requestURI: %s", requestURI)
|
||||
response, err := client.R().
|
||||
EnableTrace().
|
||||
SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}).
|
||||
SetHeaders(map[string]string{"User-Agent": uerAgent}).
|
||||
SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
|
||||
Get(requestURI)
|
||||
if err != nil {
|
||||
log.Error("Failed to Get:", err)
|
||||
output = ParseErrorOutput(err)
|
||||
} else {
|
||||
output = ParseOutputResponse(outputJson, response)
|
||||
}
|
||||
case "post":
|
||||
requestURI = parseRequestUri(httpUri, mmlMap, mml)
|
||||
body := ParseInputBody(inputJson, mml)
|
||||
log.Debugf("method: Post requestURI: %s", requestURI)
|
||||
response, err := client.R().
|
||||
EnableTrace().
|
||||
SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}).
|
||||
SetHeaders(map[string]string{"User-Agent": uerAgent}).
|
||||
SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
|
||||
SetBody(*body).
|
||||
Post(requestURI)
|
||||
if err != nil {
|
||||
log.Error("Failed to Post:", err)
|
||||
output = ParseErrorOutput(err)
|
||||
} else {
|
||||
output = ParseOutputResponse(outputJson, response)
|
||||
}
|
||||
case "put":
|
||||
requestURI = parseRequestUri(httpUri, mmlMap, mml)
|
||||
body := ParseInputBody(inputJson, mml)
|
||||
log.Debugf("method: Put requestURI: %s", requestURI)
|
||||
response, err := client.R().
|
||||
EnableTrace().
|
||||
SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}).
|
||||
SetHeaders(map[string]string{"User-Agent": uerAgent}).
|
||||
SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
|
||||
SetBody(*body).
|
||||
Put(requestURI)
|
||||
if err != nil {
|
||||
log.Error("Failed to Put:", err)
|
||||
output = ParseErrorOutput(err)
|
||||
} else {
|
||||
output = ParseOutputResponse(outputJson, response)
|
||||
}
|
||||
case "delete":
|
||||
requestURI = parseRequestUri(httpUri, mmlMap, mml)
|
||||
log.Debugf("method: Delete requestURI: %s", requestURI)
|
||||
response, err := client.R().
|
||||
EnableTrace().
|
||||
SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}).
|
||||
SetHeaders(map[string]string{"User-Agent": uerAgent}).
|
||||
SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
|
||||
Delete(requestURI)
|
||||
if err != nil {
|
||||
log.Error("Failed to Delete:", err)
|
||||
output = ParseErrorOutput(err)
|
||||
} else {
|
||||
output = ParseOutputResponse(outputJson, response)
|
||||
}
|
||||
case "patch":
|
||||
requestURI = parseRequestUri(httpUri, mmlMap, mml)
|
||||
log.Debugf("method: patch requestURI: %s", requestURI)
|
||||
response, err := client.R().
|
||||
EnableTrace().
|
||||
SetHeaders(map[string]string{"accessToken": fmt.Sprintf("%x", sshConn.SessionID())}).
|
||||
SetHeaders(map[string]string{"User-Agent": uerAgent}).
|
||||
SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
|
||||
Patch(requestURI)
|
||||
if err != nil {
|
||||
log.Error("Failed to Patch:", err)
|
||||
output = ParseErrorOutput(err)
|
||||
} else {
|
||||
output = ParseOutputResponse(outputJson, response)
|
||||
}
|
||||
default:
|
||||
err := errors.New("not found mml command")
|
||||
log.Error(err)
|
||||
output = ParseErrorOutput(err)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
const (
|
||||
MaxMmlOutputBufferSize = 1000000
|
||||
FormatTypeJson = "json"
|
||||
FormatTypeTable = "table"
|
||||
DefaultFormatType = FormatTypeTable
|
||||
)
|
||||
|
||||
const (
|
||||
RetCodeSucceeded = 0
|
||||
RetCodeFailed = 0
|
||||
)
|
||||
|
||||
func ParseInputBody(inputJson *dborm.MmlInput, mml *MmlCommand) *[]byte {
|
||||
inputBody := make(map[string]interface{})
|
||||
log.Trace("mml.NaMap:", mml.NaMap)
|
||||
log.Trace("mml.AaMap:", mml.AaMap)
|
||||
if strings.ToLower(inputJson.BodyFmt) == "putdb" {
|
||||
for _, icol := range inputJson.Cols {
|
||||
log.Trace("icol:", icol)
|
||||
mml.NaMap[icol.Name] = icol.Value
|
||||
}
|
||||
inputBody[inputJson.BodyKey] = mml.NaMap
|
||||
} else {
|
||||
inputParams := make([]map[string]interface{}, 0)
|
||||
inputParams = append(inputParams, mml.NaMap)
|
||||
inputBody[inputJson.BodyKey] = inputParams
|
||||
}
|
||||
|
||||
body, err := json.Marshal(inputBody)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal:", err)
|
||||
}
|
||||
|
||||
log.Trace("inputBody:", inputBody)
|
||||
log.Trace("body:", string(body))
|
||||
return &body
|
||||
}
|
||||
|
||||
func ParseOutputResponse(outputJson *dborm.MmlOutput, response *resty.Response) *[]byte {
|
||||
var output []byte
|
||||
var str bytes.Buffer
|
||||
|
||||
switch response.StatusCode() {
|
||||
case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
|
||||
if OmcMmlVar.Output == FormatTypeJson {
|
||||
code := fmt.Sprintf("StatusCode = %d status %s\n\n", response.StatusCode(), response.Status())
|
||||
title := formatTitle(outputJson.Title)
|
||||
|
||||
json.Indent(&str, response.Body(), "", " ")
|
||||
log.Trace(str.String())
|
||||
|
||||
output = global.BytesCombine1([]byte(code), []byte(title), str.Bytes(), []byte("\n"))
|
||||
} else {
|
||||
log.Trace("Body:", string(response.Body()))
|
||||
mapDatas := make(map[string]interface{}, 0)
|
||||
|
||||
err := json.Unmarshal(response.Body(), &mapDatas)
|
||||
if err != nil {
|
||||
log.Error("Failed to json.Unmarshal:", err)
|
||||
//output = *ParseErrorOutput(err)
|
||||
output = *ParseErrorOutput(string(response.Body()))
|
||||
return &output
|
||||
}
|
||||
log.Trace("mapDatas:", mapDatas)
|
||||
switch strings.ToLower(outputJson.RetFmt) {
|
||||
case "getdb":
|
||||
if len(mapDatas) > 0 {
|
||||
var data interface{}
|
||||
for _, data = range mapDatas {
|
||||
log.Trace("data:", data)
|
||||
break
|
||||
}
|
||||
if len(data.([]interface{})) > 0 {
|
||||
table := (data.([]interface{}))[0]
|
||||
log.Trace("table:", table)
|
||||
|
||||
code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
|
||||
title := formatTitle(outputJson.Title)
|
||||
fmtResults := ParseTableOutput(outputJson, table)
|
||||
output = global.BytesCombine1([]byte(code), []byte(title), []byte(fmtResults))
|
||||
}
|
||||
}
|
||||
case "deletedb":
|
||||
var data interface{}
|
||||
for _, data = range mapDatas {
|
||||
log.Trace("data:", data)
|
||||
break
|
||||
}
|
||||
|
||||
if len(data.(map[string]interface{})) > 0 {
|
||||
table := data.(map[string]interface{})
|
||||
code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
|
||||
fmtResults := ParseDBOperOutput(outputJson, table)
|
||||
output = global.BytesCombine1([]byte(code), []byte(fmtResults))
|
||||
}
|
||||
case "postdb":
|
||||
var data interface{}
|
||||
for _, data = range mapDatas {
|
||||
log.Trace("data:", data)
|
||||
break
|
||||
}
|
||||
|
||||
if len(data.(map[string]interface{})) > 0 {
|
||||
table := data.(map[string]interface{})
|
||||
code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
|
||||
fmtResults := ParseDBOperOutput(outputJson, table)
|
||||
output = global.BytesCombine1([]byte(code), []byte(fmtResults))
|
||||
}
|
||||
case "putdb":
|
||||
var data interface{}
|
||||
for _, data = range mapDatas {
|
||||
log.Trace("data:", data)
|
||||
break
|
||||
}
|
||||
if len(data.(map[string]interface{})) > 0 {
|
||||
table := data.(map[string]interface{})
|
||||
code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
|
||||
fmtResults := ParseDBOperOutput(outputJson, table)
|
||||
output = global.BytesCombine1([]byte(code), []byte(fmtResults))
|
||||
}
|
||||
case "getnf":
|
||||
if len(mapDatas) > 0 {
|
||||
var data interface{}
|
||||
for _, data = range mapDatas {
|
||||
log.Trace("data:", data)
|
||||
break
|
||||
}
|
||||
if len(data.([]interface{})) > 0 {
|
||||
//table := (data.([]interface{}))[0]
|
||||
//log.Trace("table:", table)
|
||||
|
||||
code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
|
||||
title := formatTitle(outputJson.Title)
|
||||
fmtResults := ParseNFTableOutput(outputJson, data)
|
||||
output = global.BytesCombine1([]byte(code), []byte(title), []byte(fmtResults))
|
||||
}
|
||||
}
|
||||
default:
|
||||
code := fmt.Sprintf(outputJson.RetMsg, RetCodeSucceeded)
|
||||
output = global.BytesCombine1([]byte(code))
|
||||
}
|
||||
|
||||
}
|
||||
default:
|
||||
if OmcMmlVar.Output == FormatTypeJson {
|
||||
code := fmt.Sprintf("StatusCode = %d status %s\n\n", response.StatusCode(), response.Status())
|
||||
//title := formatTitle("Network Element Information")
|
||||
|
||||
json.Indent(&str, response.Body(), "", " ")
|
||||
log.Trace(str.String())
|
||||
|
||||
output = global.BytesCombine1([]byte(code), str.Bytes(), []byte("\n"))
|
||||
} else {
|
||||
log.Trace("Body:", string(response.Body()))
|
||||
mapResults := make(map[string]interface{}, 0)
|
||||
|
||||
err := json.Unmarshal(response.Body(), &mapResults)
|
||||
if err != nil {
|
||||
log.Error("Failed to json.Unmarshal:", err)
|
||||
output = *ParseErrorOutput(string(response.Body()))
|
||||
} else {
|
||||
log.Trace("mapResults:", mapResults)
|
||||
errResult := mapResults["error"]
|
||||
log.Trace("errResult:", errResult)
|
||||
if len(errResult.(map[string]interface{})) > 0 {
|
||||
errCode, _ := strconv.Atoi(fmt.Sprintf("%v", errResult.(map[string]interface{})["errorCode"]))
|
||||
errorInfo := errResult.(map[string]interface{})["errorInfo"]
|
||||
output = []byte(fmt.Sprintf(outputJson.ErrMsg, errCode, errorInfo))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &output
|
||||
}
|
||||
|
||||
func ParseDBOperOutput(outputJson *dborm.MmlOutput, cols any) string {
|
||||
var colOutput []dborm.ColOutput = outputJson.Cols
|
||||
var value, retFmtCols string
|
||||
if len(cols.(map[string]interface{})) > 0 {
|
||||
if len(colOutput) > 0 {
|
||||
coln := colOutput[0].Name
|
||||
value = fmt.Sprintf("%v", cols.(map[string]interface{})[coln])
|
||||
log.Tracef("coln:%s value:%s", coln, value)
|
||||
retFmtCols = colOutput[0].Display + " = " + value + "\n\n"
|
||||
}
|
||||
}
|
||||
|
||||
return retFmtCols
|
||||
}
|
||||
|
||||
func ParseNFTableOutput(outputJson *dborm.MmlOutput, cols any) string {
|
||||
var colOutput []dborm.ColOutput
|
||||
var fmtColName string
|
||||
var colName []string
|
||||
var spaceNum int = 1
|
||||
var alignmentM, alignmentSN, alignmentSV string = "Left", "Right", "Left"
|
||||
|
||||
if outputJson.SepSpaceNum != 0 {
|
||||
spaceNum = outputJson.SepSpaceNum
|
||||
}
|
||||
if outputJson.AlignmentM != "" {
|
||||
alignmentM = outputJson.AlignmentM
|
||||
}
|
||||
if outputJson.AlignmentSN != "" {
|
||||
alignmentSN = outputJson.AlignmentSN
|
||||
}
|
||||
if outputJson.AlignmentSV != "" {
|
||||
alignmentSV = outputJson.AlignmentSV
|
||||
}
|
||||
|
||||
maxLength := math.MinInt64
|
||||
for _, coln := range outputJson.Cols {
|
||||
log.Trace("coln:", coln)
|
||||
|
||||
if len(coln.Display) > maxLength {
|
||||
maxLength = len(coln.Display)
|
||||
}
|
||||
if coln.Length < len(coln.Display) {
|
||||
coln.Length = len(coln.Display)
|
||||
}
|
||||
|
||||
colName = append(colName, ParseAlignmentOutput(coln.Length, alignmentM, coln.Display))
|
||||
colOutput = append(colOutput, coln)
|
||||
}
|
||||
fmtColName = formatLineBySpace(&colName, spaceNum)
|
||||
log.Trace("fmtColName:", fmtColName)
|
||||
|
||||
var retFmtCols string
|
||||
var fmtColValues []string
|
||||
var numberResult int
|
||||
// for _, colnvs := range cols.([]interface{}) {
|
||||
// log.Trace("colnvs:", colnvs)
|
||||
// if colnvs == nil {
|
||||
// break
|
||||
// }
|
||||
numberResult = len(cols.([]interface{}))
|
||||
|
||||
if numberResult == 1 && outputJson.SingleList == true {
|
||||
colnv := cols.([]interface{})[0]
|
||||
log.Trace("colnv:", colnv)
|
||||
|
||||
var fmtNV []string
|
||||
for _, coln := range colOutput {
|
||||
fmtName := ParseAlignmentOutput(maxLength, alignmentSN, coln.Display)
|
||||
log.Tracef("alignmentSN:%s fmtName:%s", alignmentSN, fmtName)
|
||||
value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
|
||||
fmtValue := ParseAlignmentOutput(coln.Length, alignmentSV, value)
|
||||
fmtNV = append(fmtNV, fmtName+": "+fmtValue)
|
||||
}
|
||||
|
||||
fmtResults := strings.Join(fmtNV, "\n")
|
||||
log.Tracef("fmtResults:\n%s", fmtResults)
|
||||
fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
|
||||
retFmtCols = fmtResults + "\n\n" + fmtEnd
|
||||
log.Tracef("retFmtCols:\n%s", retFmtCols)
|
||||
return retFmtCols
|
||||
} else {
|
||||
for i := 0; i < numberResult; i++ {
|
||||
colnv := cols.([]interface{})[i]
|
||||
log.Trace("colnv:", colnv)
|
||||
var colValues []string
|
||||
var newVal []string
|
||||
for _, coln := range colOutput {
|
||||
value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
|
||||
if len(coln.Alias) != 0 {
|
||||
enumVal, _ := strconv.Atoi(value)
|
||||
value = parseEnumAlias(&(coln.Alias), enumVal)
|
||||
}
|
||||
newVal = append(newVal, ParseAlignmentOutput(coln.Length, alignmentM, value))
|
||||
}
|
||||
colValues = append(colValues, formatLineBySpace(&newVal, spaceNum))
|
||||
log.Trace("colValues:", colValues)
|
||||
fmtColValues = append(fmtColValues, strings.Join(colValues, "\n"))
|
||||
log.Trace("fmtColValues:", fmtColValues)
|
||||
}
|
||||
fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
|
||||
retFmtCols = fmtColName + "\n\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd
|
||||
log.Tracef("retFmtCols:\n%s", retFmtCols)
|
||||
return retFmtCols
|
||||
}
|
||||
}
|
||||
|
||||
func ParseTableOutput(outputJson *dborm.MmlOutput, cols any) string {
|
||||
var colOutput []dborm.ColOutput
|
||||
var fmtColName string
|
||||
var colName []string
|
||||
var spaceNum int = 1
|
||||
var alignmentM, alignmentSN, alignmentSV string = "Left", "Right", "Left"
|
||||
|
||||
if outputJson.SepSpaceNum != 0 {
|
||||
spaceNum = outputJson.SepSpaceNum
|
||||
}
|
||||
if outputJson.AlignmentM != "" {
|
||||
alignmentM = outputJson.AlignmentM
|
||||
}
|
||||
if outputJson.AlignmentSN != "" {
|
||||
alignmentSN = outputJson.AlignmentSN
|
||||
}
|
||||
if outputJson.AlignmentSV != "" {
|
||||
alignmentSV = outputJson.AlignmentSV
|
||||
}
|
||||
|
||||
maxLength := math.MinInt64
|
||||
for _, coln := range outputJson.Cols {
|
||||
log.Trace("coln:", coln)
|
||||
|
||||
if len(coln.Display) > maxLength {
|
||||
maxLength = len(coln.Display)
|
||||
}
|
||||
if coln.Length < len(coln.Display) {
|
||||
coln.Length = len(coln.Display)
|
||||
}
|
||||
|
||||
colName = append(colName, ParseAlignmentOutput(coln.Length, alignmentM, coln.Display))
|
||||
colOutput = append(colOutput, coln)
|
||||
}
|
||||
fmtColName = formatLineBySpace(&colName, spaceNum)
|
||||
log.Trace("fmtColName:", fmtColName)
|
||||
|
||||
var retFmtCols string
|
||||
var fmtColValues []string
|
||||
var numberResult int
|
||||
for _, colnvs := range cols.(map[string]interface{}) {
|
||||
log.Trace("colnvs:", colnvs)
|
||||
if colnvs == nil {
|
||||
break
|
||||
}
|
||||
numberResult = len(colnvs.([]interface{}))
|
||||
|
||||
if numberResult == 1 && outputJson.SingleList == true {
|
||||
colnv := colnvs.([]interface{})[0]
|
||||
log.Trace("colnv:", colnv)
|
||||
|
||||
var fmtNV []string
|
||||
for _, coln := range colOutput {
|
||||
fmtName := ParseAlignmentOutput(maxLength, alignmentSN, coln.Display)
|
||||
log.Tracef("alignmentSN:%s fmtName:%s", alignmentSN, fmtName)
|
||||
value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
|
||||
fmtValue := ParseAlignmentOutput(coln.Length, alignmentSV, value)
|
||||
fmtNV = append(fmtNV, fmtName+": "+fmtValue)
|
||||
}
|
||||
|
||||
fmtResults := strings.Join(fmtNV, "\n")
|
||||
log.Tracef("fmtResults:\n%s", fmtResults)
|
||||
fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
|
||||
retFmtCols = fmtResults + "\n\n" + fmtEnd
|
||||
log.Tracef("retFmtCols:\n%s", retFmtCols)
|
||||
return retFmtCols
|
||||
} else {
|
||||
for i := 0; i < numberResult; i++ {
|
||||
colnv := colnvs.([]interface{})[i]
|
||||
log.Trace("colnv:", colnv)
|
||||
var colValues []string
|
||||
var newVal []string
|
||||
for _, coln := range colOutput {
|
||||
value := fmt.Sprintf("%v", colnv.(map[string]interface{})[coln.Name])
|
||||
if len(coln.Alias) != 0 {
|
||||
enumVal, _ := strconv.Atoi(value)
|
||||
value = parseEnumAlias(&(coln.Alias), enumVal)
|
||||
}
|
||||
newVal = append(newVal, ParseAlignmentOutput(coln.Length, alignmentM, value))
|
||||
}
|
||||
colValues = append(colValues, formatLineBySpace(&newVal, spaceNum))
|
||||
log.Trace("colValues:", colValues)
|
||||
fmtColValues = append(fmtColValues, strings.Join(colValues, "\n"))
|
||||
log.Trace("fmtColValues:", fmtColValues)
|
||||
}
|
||||
fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
|
||||
retFmtCols = fmtColName + "\n\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd
|
||||
log.Tracef("retFmtCols:\n%s", retFmtCols)
|
||||
return retFmtCols
|
||||
}
|
||||
}
|
||||
fmtEnd := fmt.Sprintf(outputJson.End, numberResult)
|
||||
retFmtCols = fmtColName + "\n" + strings.Join(fmtColValues, "\n") + "\n\n" + fmtEnd
|
||||
log.Tracef("retFmtCols:\n%s", retFmtCols)
|
||||
return retFmtCols
|
||||
}
|
||||
|
||||
func parseEnumAlias(alias *[]string, enumVal int) string {
|
||||
return (*alias)[enumVal]
|
||||
}
|
||||
|
||||
func formatLineBySpace(strArray *[]string, spaceNum int) string {
|
||||
space := strings.Repeat(" ", spaceNum)
|
||||
return strings.Join(*strArray, space)
|
||||
}
|
||||
|
||||
func ParseAlignmentOutput(length int, alignment string, str string) string {
|
||||
spaceLen := length - len(str)
|
||||
if spaceLen < 0 {
|
||||
log.Warnf("len(str=%s)=%d more length=%d", str, len(str), length)
|
||||
spaceLen = 0
|
||||
}
|
||||
var retStr string
|
||||
switch alignment {
|
||||
case "Left":
|
||||
suffix := strings.Repeat(" ", spaceLen)
|
||||
retStr = str + suffix
|
||||
case "Right":
|
||||
prefix := strings.Repeat(" ", spaceLen)
|
||||
retStr = prefix + str
|
||||
log.Tracef("retStr:%s", retStr)
|
||||
case "Middle":
|
||||
prefix := strings.Repeat(" ", int(math.Ceil(float64(spaceLen)/2)))
|
||||
suffix := strings.Repeat(" ", int(math.Floor(float64(spaceLen)/2)))
|
||||
retStr = prefix + str + suffix
|
||||
}
|
||||
log.Tracef("length=%d, spaceLne=%d, alignment=%s, str=%s, retStr=%s", length, spaceLen, alignment, str, retStr)
|
||||
return retStr
|
||||
}
|
||||
|
||||
func ParseErrorOutput(err any) *[]byte {
|
||||
var output []byte
|
||||
|
||||
var formatType string = DefaultFormatType
|
||||
if formatType == FormatTypeJson {
|
||||
output = []byte(fmt.Sprintf("ErrorCode = 1 Error message: %v\n\n", err))
|
||||
} else {
|
||||
output = []byte(fmt.Sprintf("RetCode = -1 operation failed: %v\n\n", err))
|
||||
}
|
||||
|
||||
return &output
|
||||
}
|
||||
|
||||
func formatTitle(title string) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(title)
|
||||
builder.WriteString("\n")
|
||||
for i := 0; i < len(title); i++ {
|
||||
builder.WriteString("-")
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func formatTableOutput() {
|
||||
|
||||
}
|
||||
BIN
sshsvc/sshsvc
Normal file
BIN
sshsvc/sshsvc
Normal file
Binary file not shown.
301
sshsvc/sshsvc.go
Normal file
301
sshsvc/sshsvc.go
Normal file
@@ -0,0 +1,301 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"ems.agt/lib/dborm"
|
||||
"ems.agt/lib/global"
|
||||
"ems.agt/lib/log"
|
||||
"ems.agt/lib/mmlp"
|
||||
"ems.agt/sshsvc/config"
|
||||
"ems.agt/sshsvc/logmml"
|
||||
|
||||
//"github.com/gliderlabs/ssh"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var connNum int = 0
|
||||
var conf *config.YamlConfig
|
||||
|
||||
func init() {
|
||||
conf = config.GetYamlConfig()
|
||||
log.InitLogger(conf.Logger.File, conf.Logger.Duration, conf.Logger.Count, "omc:sshsvc", config.GetLogLevel())
|
||||
fmt.Printf("OMC sshsvc version: %s\n", global.Version)
|
||||
log.Infof("========================= OMC sshsvc startup =========================")
|
||||
log.Infof("OMC sshsvc version: %s %s %s", global.Version, global.BuildTime, global.GoVer)
|
||||
db := conf.Database
|
||||
err := dborm.InitDbClient(db.Type, db.User, db.Password, db.Host, db.Port, db.Name)
|
||||
if err != nil {
|
||||
fmt.Println("dborm.initDbClient err:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logmml.InitMmlLogger(conf.Logmml.File, conf.Logmml.Duration, conf.Logmml.Count, "omc", config.GetLogMmlLevel())
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 生成SSH密钥对
|
||||
privateKeyBytes, err := os.ReadFile(conf.Sshd.PrivateKey)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to ReadFile:", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
privateKey, err := ssh.ParsePrivateKey(privateKeyBytes)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to ParsePrivateKey", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
|
||||
// 配置SSH服务器
|
||||
serverConfig := &ssh.ServerConfig{
|
||||
PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||
// 这里可以进行密码验证逻辑,例如检查用户名和密码是否匹配
|
||||
validUser, _, err := dborm.XormCheckLoginUser(conn.User(), string(password), conf.OMC.UserCrypt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if validUser == true {
|
||||
sessionToken := fmt.Sprintf("%x", conn.SessionID()) // Generate new token to session ID
|
||||
sourceAddr := conn.RemoteAddr().String()
|
||||
timeOut := uint32(conf.Sshd.Timeout)
|
||||
sessionMode := conf.Sshd.Session
|
||||
log.Debugf("token:%s sourceAddr:%s", sessionToken, sourceAddr)
|
||||
affected, err := dborm.XormInsertSession(conn.User(), sourceAddr, sessionToken, timeOut, sessionMode)
|
||||
if err != nil {
|
||||
log.Error("Failed to insert Session table:", err)
|
||||
return nil, err
|
||||
}
|
||||
if affected == -1 {
|
||||
err := errors.New("Failed to get session")
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if conn.User() == "admin" && string(password) == "123456" {
|
||||
// return nil, nil
|
||||
// }
|
||||
return nil, fmt.Errorf("invalid user or password")
|
||||
},
|
||||
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
// 这里可以进行公钥验证逻辑,例如检查用户的公钥是否在允许的公钥列表中
|
||||
return nil, fmt.Errorf("public key authentication is failed")
|
||||
},
|
||||
}
|
||||
|
||||
serverConfig.AddHostKey(privateKey)
|
||||
|
||||
// 启动SSH服务器
|
||||
hostUri := fmt.Sprintf("%s:%d", conf.Sshd.ListenAddr, conf.Sshd.ListenPort)
|
||||
listener, err := net.Listen("tcp", hostUri)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to Listen: ", err)
|
||||
os.Exit(4)
|
||||
}
|
||||
|
||||
fmt.Printf("MML SSH server startup, listen port:%d\n", conf.Sshd.ListenPort)
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to Accept: ", err)
|
||||
os.Exit(5)
|
||||
}
|
||||
|
||||
go handleSSHConnection(conn, serverConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSSHConnection(conn net.Conn, serverConfig *ssh.ServerConfig) {
|
||||
// SSH握手
|
||||
sshConn, chans, reqs, err := ssh.NewServerConn(conn, serverConfig)
|
||||
if err != nil {
|
||||
log.Error("Failed to NewServerConn: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("SSH connect accepted,client version:%s,user:%s", sshConn.ClientVersion(), sshConn.User())
|
||||
|
||||
// 处理SSH请求
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
// 处理SSH通道
|
||||
for newChannel := range chans {
|
||||
if newChannel.ChannelType() != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, "unsupported channel type")
|
||||
continue
|
||||
}
|
||||
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Error("Failed to NewServerConn: ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if connNum > int(conf.Sshd.MaxConnNum) {
|
||||
log.Error("Maximum number of connections exceeded")
|
||||
//conn.Write([]byte("Reach max connections"))
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
connNum++
|
||||
|
||||
go handleSSHChannel(conn, sshConn, channel, requests)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSSHChannel(conn net.Conn, sshConn *ssh.ServerConn, channel ssh.Channel, requests <-chan *ssh.Request) {
|
||||
for req := range requests {
|
||||
switch req.Type {
|
||||
case "exec":
|
||||
// 执行远程命令
|
||||
command := strings.TrimSpace(string(req.Payload))[4:]
|
||||
//cmd := exec.Command(
|
||||
cmd := exec.Command("cmd", "/C", command)
|
||||
cmd.Stdin = channel
|
||||
cmd.Stdout = channel
|
||||
cmd.Stderr = channel.Stderr()
|
||||
log.Trace("command:", command)
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Error("Failed to cmd.Run: ", err)
|
||||
}
|
||||
|
||||
channel.SendRequest("exit-status", false, []byte{0, 0, 0, 0})
|
||||
channel.Close()
|
||||
closeConnection(conn)
|
||||
// case "shell":
|
||||
// // 处理交互式shell会话
|
||||
// // 在这里添加您的处理逻辑,例如启动一个shell进程并将其连接到channel
|
||||
// // 请注意,处理交互式shell会话需要更复杂的逻辑,您可能需要使用类似于pty包来处理终端相关的操作
|
||||
// channel.Write([]byte("交互式shell会话已启动\n"))
|
||||
// channel.Close()
|
||||
// handleSSHShell(user, channel)
|
||||
case "pty-req":
|
||||
log.Info("A pty-req processing...")
|
||||
req.Reply(true, nil)
|
||||
handleSSHShell(sshConn, channel)
|
||||
channel.SendRequest("exit-status", false, []byte{0, 0, 0, 0})
|
||||
channel.Close()
|
||||
closeConnection(conn)
|
||||
log.Info("Channel closed")
|
||||
default:
|
||||
req.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func closeConnection(conn net.Conn) {
|
||||
conn.Close()
|
||||
connNum--
|
||||
}
|
||||
|
||||
func handleSSHShell(sshConn *ssh.ServerConn, channel ssh.Channel) {
|
||||
//conf = config.GetYamlConfig()
|
||||
// 检查通道是否支持终端
|
||||
|
||||
omcMmlVar := &mmlp.MmlVar{
|
||||
Version: "16.1.1",
|
||||
Output: mmlp.DefaultFormatType,
|
||||
Limit: 50,
|
||||
User: sshConn.User(),
|
||||
SessionToken: fmt.Sprintf("%x", sshConn.SessionID()),
|
||||
HttpUri: conf.OMC.HttpUri,
|
||||
UserAgent: config.GetDefaultUserAgent(),
|
||||
}
|
||||
|
||||
term := term.NewTerminal(channel, fmt.Sprintf("[%s@omc]> ", omcMmlVar.User))
|
||||
// 启动交互式shell会话
|
||||
for {
|
||||
line, err := term.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
log.Error("Failed to read line: ", err)
|
||||
break
|
||||
}
|
||||
|
||||
cmdline := strings.TrimSpace(line)
|
||||
if cmdline != "" {
|
||||
logmml.Cmd(cmdline)
|
||||
}
|
||||
var response string
|
||||
switch cmdline {
|
||||
case "exit", "quit":
|
||||
goto exitLoop
|
||||
case "":
|
||||
goto continueLoop
|
||||
case "help":
|
||||
response = fmt.Sprintf("Usage: %s\n", line)
|
||||
term.Write([]byte(response))
|
||||
goto continueLoop
|
||||
|
||||
case "dsp variables":
|
||||
response = fmt.Sprintf("version: %s\n Output: %s\n", omcMmlVar.Version, omcMmlVar.Output)
|
||||
term.Write([]byte(response))
|
||||
goto continueLoop
|
||||
|
||||
case "set mml output=json":
|
||||
// mmlp.SetOmcMmlVarOutput("json")
|
||||
omcMmlVar.Output = "json"
|
||||
response = fmt.Sprintf("set ok, mmlVar.output = %s\n", omcMmlVar.Output)
|
||||
term.Write([]byte(response))
|
||||
goto continueLoop
|
||||
|
||||
case "set mml output=table":
|
||||
// mmlp.SetOmcMmlVarOutput("table")
|
||||
omcMmlVar.Output = "table"
|
||||
response = fmt.Sprintf("set ok, mmlVar.output = %s\n", omcMmlVar.Output)
|
||||
term.Write([]byte(response))
|
||||
goto continueLoop
|
||||
|
||||
default:
|
||||
var mmlCmds []mmlp.MmlCommand
|
||||
mmlLine := strings.TrimSpace(line)
|
||||
if err = mmlp.ParseMMLCommand(mmlLine, &mmlCmds); err != nil {
|
||||
response = fmt.Sprintf("parse command error: %v\n", err)
|
||||
term.Write([]byte(response))
|
||||
goto continueLoop
|
||||
}
|
||||
// if err = mmlp.ParseMMLParams(&mmlCmds); err != nil {
|
||||
// response := fmt.Sprintf("#2 parse command error: %v\n", err)
|
||||
// term.Write([]byte(response))
|
||||
// }
|
||||
|
||||
for _, mmlCmd := range mmlCmds {
|
||||
output, err := mmlp.TransMml2HttpReq(omcMmlVar, &mmlCmd)
|
||||
if err != nil {
|
||||
response = fmt.Sprintf("translate MML command error: %v\n", err)
|
||||
term.Write([]byte(response))
|
||||
goto continueLoop
|
||||
}
|
||||
response = string(*output)
|
||||
term.Write(*output)
|
||||
}
|
||||
goto continueLoop
|
||||
}
|
||||
continueLoop:
|
||||
if response != "" {
|
||||
logmml.Ret(response)
|
||||
}
|
||||
continue
|
||||
exitLoop:
|
||||
token := fmt.Sprintf("%x", sshConn.SessionID())
|
||||
_, err = dborm.XormLogoutUpdateSession(token)
|
||||
if err != nil {
|
||||
log.Error("Failed to XormLogoutUpdateSession:", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
251
sshsvc/sshsvc.go.1
Normal file
251
sshsvc/sshsvc.go.1
Normal file
@@ -0,0 +1,251 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 生成SSH密钥对
|
||||
privateKeyBytes, err := os.ReadFile("../.ssh/id_rsa")
|
||||
if err != nil {
|
||||
log.Fatal("无法读取私钥文件:", err)
|
||||
}
|
||||
|
||||
privateKey, err := ssh.ParsePrivateKey(privateKeyBytes)
|
||||
if err != nil {
|
||||
log.Fatal("无法解析私钥:", err)
|
||||
}
|
||||
|
||||
// 配置SSH服务器
|
||||
config := &ssh.ServerConfig{
|
||||
PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||
// 这里可以进行密码验证逻辑,例如检查用户名和密码是否匹配
|
||||
if conn.User() == "admin" && string(password) == "123456" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("密码错误")
|
||||
},
|
||||
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
// 这里可以进行公钥验证逻辑,例如检查用户的公钥是否在允许的公钥列表中
|
||||
return nil, fmt.Errorf("公钥验证失败")
|
||||
},
|
||||
}
|
||||
|
||||
config.AddHostKey(privateKey)
|
||||
|
||||
// 启动SSH服务器
|
||||
listener, err := net.Listen("tcp", "192.168.2.119:2222")
|
||||
if err != nil {
|
||||
log.Fatal("无法监听端口:", err)
|
||||
}
|
||||
|
||||
log.Println("SSH服务器已启动,监听端口:2222")
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("无法接受连接:", err)
|
||||
}
|
||||
|
||||
go handleSSHConnection(conn, config)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSSHConnection(conn net.Conn, config *ssh.ServerConfig) {
|
||||
// SSH握手
|
||||
sshConn, chans, reqs, err := ssh.NewServerConn(conn, config)
|
||||
if err != nil {
|
||||
log.Println("SSH握手失败:", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("SSH连接已建立,客户端版本:%s,用户:%s", sshConn.ClientVersion(), sshConn.User())
|
||||
|
||||
// 处理SSH请求
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
// 处理SSH通道
|
||||
for newChannel := range chans {
|
||||
if newChannel.ChannelType() != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, "不支持的通道类型")
|
||||
continue
|
||||
}
|
||||
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Println("无法接受通道:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go handleSSHChannel(channel, requests)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSSHChannel(channel ssh.Channel, requests <-chan *ssh.Request) {
|
||||
for req := range requests {
|
||||
switch req.Type {
|
||||
case "exec":
|
||||
// 执行远程命令
|
||||
cmd := exec.Command("cmd", "/c", strings.TrimSpace(string(req.Payload)))
|
||||
cmd.Stdin = channel
|
||||
cmd.Stdout = channel
|
||||
cmd.Stderr = channel.Stderr()
|
||||
log.Println("cmd:", strings.TrimSpace(string(req.Payload)))
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Println("无法执行命令:", err)
|
||||
}
|
||||
|
||||
channel.SendRequest("exit-status", false, []byte{0, 0, 0, 0})
|
||||
channel.Close()
|
||||
case "shell":
|
||||
// // 处理交互式shell会话
|
||||
// // 在这里添加您的处理逻辑,例如启动一个shell进程并将其连接到channel
|
||||
// // 请注意,处理交互式shell会话需要更复杂的逻辑,您可能需要使用类似于pty包来处理终端相关的操作
|
||||
// channel.Write([]byte("交互式shell会话已启动\n"))
|
||||
// channel.Close()
|
||||
handleSSHShell(channel)
|
||||
default:
|
||||
req.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleSSHShell(channel ssh.Channel) {
|
||||
defer channel.Close()
|
||||
|
||||
// 检查通道是否支持终端
|
||||
term := terminal.NewTerminal(channel, "> ")
|
||||
|
||||
// 启动交互式shell会话
|
||||
for {
|
||||
line, err := term.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
log.Println("Failed to read line: ", err)
|
||||
break
|
||||
}
|
||||
|
||||
// 在这里处理输入的命令
|
||||
// 您可以根据需要添加更多的逻辑
|
||||
if strings.TrimSpace(line) == "exit" {
|
||||
break
|
||||
}
|
||||
|
||||
response := fmt.Sprintf("You entered: %s\n", line)
|
||||
term.Write([]byte(response))
|
||||
}
|
||||
}
|
||||
|
||||
// func main() {
|
||||
// // 读取 SSH 私钥文件
|
||||
// privateKeyBytes, err := os.ReadFile("../.ssh/private_key.pem")
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to load private key: %v", err)
|
||||
// }
|
||||
|
||||
// // 解析私钥
|
||||
// privateKey, err := ssh.ParsePrivateKey(privateKeyBytes)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to parse private key: %v", err)
|
||||
// }
|
||||
|
||||
// // 配置 SSH 服务器
|
||||
// config := &ssh.ServerConfig{
|
||||
// PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||
// // 在此处验证密码
|
||||
// if string(password) == "123456" {
|
||||
// return nil, nil
|
||||
// }
|
||||
// return nil, fmt.Errorf("password rejected for %s", conn.User())
|
||||
// },
|
||||
// }
|
||||
|
||||
// // 添加私钥到配置
|
||||
// config.AddHostKey(privateKey)
|
||||
|
||||
// // 启动 SSH 服务器
|
||||
// listener, err := net.Listen("tcp", "0.0.0.0:2222")
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to listen on 2222: %v", err)
|
||||
// }
|
||||
|
||||
// log.Println("SSH server listening on 0.0.0.0:2222")
|
||||
|
||||
// for {
|
||||
// conn, err := listener.Accept()
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to accept incoming connection: %v", err)
|
||||
// }
|
||||
|
||||
// // 处理 SSH 连接
|
||||
// go func() {
|
||||
// sshConn, chans, reqs, err := ssh.NewServerConn(conn, config)
|
||||
// if err != nil {
|
||||
// log.Printf("Failed to handshake: %v", err)
|
||||
// return
|
||||
// }
|
||||
|
||||
// log.Printf("SSH connection established from %s", sshConn.RemoteAddr())
|
||||
|
||||
// // 处理 SSH 请求
|
||||
// go ssh.DiscardRequests(reqs)
|
||||
|
||||
// // 处理 SSH 通道
|
||||
// for newChannel := range chans {
|
||||
// if newChannel.ChannelType() != "session" {
|
||||
// newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
// continue
|
||||
// }
|
||||
|
||||
// channel, requests, err := newChannel.Accept()
|
||||
// if err != nil {
|
||||
// log.Printf("Failed to accept channel: %v", err)
|
||||
// continue
|
||||
// }
|
||||
|
||||
// // 处理 SSH 会话
|
||||
// go func(in <-chan *ssh.Request) {
|
||||
// for req := range in {
|
||||
// log.Printf("Received out-of-band request: %v", string(req))
|
||||
// }
|
||||
// }(requests)
|
||||
|
||||
// // 示例:执行远程命令
|
||||
// go func() {
|
||||
// defer channel.Close()
|
||||
|
||||
// command := "echo 'Hello, SSH!'"
|
||||
// output, err := execCommand(command)
|
||||
// if err != nil {
|
||||
// log.Printf("Failed to execute command: %v", err)
|
||||
// return
|
||||
// }
|
||||
|
||||
// _, err = channel.Write([]byte(output))
|
||||
// if err != nil {
|
||||
// log.Printf("Failed to write output: %v", err)
|
||||
// return
|
||||
// }
|
||||
// }()
|
||||
// }
|
||||
// }()
|
||||
// }
|
||||
// }
|
||||
|
||||
// func execCommand(command string) (string, error) {
|
||||
// // 在此处执行远程命令并返回结果
|
||||
// return "hello ssh", nil
|
||||
// }
|
||||
77
sshsvc/sshsvc.go.ok
Normal file
77
sshsvc/sshsvc.go.ok
Normal file
@@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/gliderlabs/ssh"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// func main() {
|
||||
// ssh.Handle(func(s ssh.Session) {
|
||||
// io.WriteString(s, "Hello world\n")
|
||||
// })
|
||||
|
||||
// log.Fatal(ssh.ListenAndServe(":2222", nil, ssh.HostKeyFile("../.ssh/id_rsa")))
|
||||
// }
|
||||
|
||||
// func main() {
|
||||
// ssh.ListenAndServe(":2222", nil, ssh.HostKeyFile("../.ssh/id_rsa"),
|
||||
// ssh.PasswordAuth(func(ctx ssh.Context, pass string) bool {
|
||||
// return pass == "123456"
|
||||
// }),
|
||||
// )
|
||||
// }
|
||||
|
||||
func main() {
|
||||
ssh.Handle(func(s ssh.Session) {
|
||||
|
||||
term := terminal.NewTerminal(s, "> ")
|
||||
for {
|
||||
line, err := term.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// 在这里处理输入的命令
|
||||
// 您可以根据需要添加更多的逻辑
|
||||
if strings.TrimSpace(line) == "exit" {
|
||||
break
|
||||
}
|
||||
|
||||
log.Println(line)
|
||||
if line != "" {
|
||||
term.Write(append([]byte(line), '\n'))
|
||||
}
|
||||
}
|
||||
log.Println("terminal closed")
|
||||
})
|
||||
|
||||
log.Println("starting ssh server on port 2222...")
|
||||
ssh.ListenAndServe(":2222", nil, ssh.HostKeyFile("../.ssh/id_rsa"),
|
||||
ssh.PasswordAuth(func(ctx ssh.Context, pass string) bool {
|
||||
return pass == "123456"
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func Handle(sess ssh.Session) {
|
||||
term := terminal.NewTerminal(sess, "> ")
|
||||
for {
|
||||
line, err := term.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// 在这里处理输入的命令
|
||||
// 您可以根据需要添加更多的逻辑
|
||||
if strings.TrimSpace(line) == "exit" {
|
||||
break
|
||||
}
|
||||
|
||||
log.Println(line)
|
||||
if line != "" {
|
||||
term.Write(append([]byte(line), '\n'))
|
||||
}
|
||||
}
|
||||
log.Println("terminal closed")
|
||||
}
|
||||
Reference in New Issue
Block a user