package main /* * 本检测工具基于SCD文档管理系统第二版本开发 * 本版本中引入了crc校验及ccd提取外部程序,该程序由C++编写,在部署时需要根据目标机器进行源码编译。其源程序在scdcrc目录下,编译时可参考目录的编译说明。 * 本版本中引入了java开发的xml校验(根据xsd校验)外部程序,该程序由java编写,在部署时需要在目标机器上安装jdk1.8。务必(!!!)并将操作系统安装或者设置为中文语言,设置命令如下: * 1:全局配置修改。vi /etc/sysconfig/i18n 输入LANG="zh_CN.UTF-8"或者LANG="en_US.UTF-8",执行source /etc/sysconfig/i18n,重启电脑生效 * 2:临时修改。export LANG="zh_CN.UTF-8" 或者 export LANG="en_US.UTF-8" * 3: 查看liunx系统当前语言。命令: echo $LANG */ // ***************** 重新生成api接口文档,终端运行生成命令:bee run -gendoc=true -downdoc=true *************** // ***************** 如果生成swagger文档时失败,可运行:bee generate docs生成,可查看详情生成过程 *************** // ***************** 其他异常问题可尝试执行:swag init *************** // @title SCD检测工具API // @version 1.0.0 // @description SCD文档管理系统接口文档。本文档仅用于本项目的前端接口说明 // @termsOfService http://swagger.io/terms/ // @contact.name liling // @contact.url http://www.swagger.io/support // @contact.email 3116246@qq.com // @license.name none // @license.url - // @host localhost:9527 // @BasePath /api // @securityDefinitions.token token AUTH import ( "bytes" "crypto/md5" "encoding/binary" "encoding/hex" "fmt" "io" "io/fs" "net" "net/url" "os" "os/exec" "path/filepath" "scd_check_tools/arrayex" "scd_check_tools/conf" _ "scd_check_tools/conf" "scd_check_tools/db" "scd_check_tools/email" "scd_check_tools/global" "scd_check_tools/logger" "scd_check_tools/models/bo" "scd_check_tools/models/enum" "scd_check_tools/monitor" "scd_check_tools/sms" "scd_check_tools/test" "scd_check_tools/upgrade" //"syscall" //"regexp" "runtime" "scd_check_tools/mqtt" "scd_check_tools/tools" "sort" "strconv" "strings" "time" "github.com/astaxie/beego" //"github.com/astaxie/beego/logs" "github.com/astaxie/beego/context" _ "scd_check_tools/routers" //"github.com/shopspring/decimal" _ "scd_check_tools/docs" _ "scd_check_tools/models/enum" ) var ( cpunum = runtime.NumCPU() - 1 ) func main() { runtime.GOMAXPROCS(cpunum) logger.Logger.Init() logger.Logger.Println(tools.NowTime() + " 当前操作系统:" + string(runtime.GOOS) + " " + runtime.GOARCH) conf.LoadAppConf() dbtype := tools.IsEmpty(conf.GlobalConfig["dbtype"]) runmode := tools.IsEmpty(conf.GlobalConfig["runmode"]) if runmode == "" { runmode = "dev" } if dbtype == "" || dbtype == "mysql" { db.ConnDB("conf/mysql-" + runmode + ".cnf") } else if dbtype == "sqlite" { db.ConnSqlite("conf/db.cnf") } go mqtt.Start() if runmode == "dev" { //自动生成接口权限定义 new(ApiDoc).Run() } new(ApiDoc).CacheApiDoc() bo.LoadSysParam() //缓存系统代码定义 bo.LoadAndCacheGlobalCode() monitor.StartDataMonitor() monitor.StartSystem() new(bo.TaskMgr).ChangeErr2Normal() //初始化短信对象 smspf := conf.GlobalConfig["sms.platform"] if smspf != "" { smsSend := sms.GetSmsInstance(smspf) smsSend.AppId = conf.GlobalConfig["sms."+smspf+".appid"] smsSend.AppKey = conf.GlobalConfig["sms."+smspf+".appkey"] smsSend.SignName = conf.GlobalConfig["sms.signname"] } //启动邮件发送对象 email.EmailConfig.Load() //发送邮件调用参考: //err := new(email.Send).SendEmail("3116246@qq.com", "测试", "这是邮件发送测试") logger.Logger.Println("****** 当前程序运行版本:" + upgrade.GetVersion() + " 运行端口:" + conf.GlobalConfig["appport"] + " 运行环境:" + runmode + " ******") logger.Logger.Println("如果需要重新生成api接口文档,终端运行生成命令:bee run -gendoc=true -downdoc=true") go test.Start("") go func() { //将生成的swagger文件复制到web目录下 dirchar := string(os.PathSeparator) swaggerdir, err := os.Stat("." + dirchar + "swagger") if os.IsNotExist(err) { //项目中没有启用swagger时,不处理 return } if swaggerdir.IsDir() { filepath.Walk("."+dirchar+"swagger", func(pathitem string, info fs.FileInfo, err error) error { if info.IsDir() { return nil } src, _ := os.Open(pathitem) defer src.Close() dst, _ := os.Create("." + dirchar + "static" + dirchar + "swagger" + dirchar + info.Name()) defer dst.Close() io.Copy(dst, src) return nil }) } }() if string(runtime.GOOS) == "windows" { if runmode == "prod" { go func() { //先杀掉残留的Web2Cs.exe进程 killcmd := exec.Command("taskkill.exe", "/f", "/im", "Web2Cs.exe") killcmd.Start() time.Sleep(1 * time.Second) dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) cmd := exec.Command("cmd.exe", "/c", "start "+dir+"/static/pc/Web2Cs.exe") cmd.Start() //隐藏CMD窗口 //如果编译的非windows版本,需要注释下面的代码 /* kernel32 := syscall.NewLazyDLL("kernel32.dll") user32 := syscall.NewLazyDLL("user32.dll") getConsoleWindow := kernel32.NewProc("GetConsoleWindow") hideWin := user32.NewProc("ShowWindowAsync") winHander, _, err := getConsoleWindow.Call() if winHander == 0 { logger.Logger.Error(err) return } code, _, err := hideWin.Call(winHander, 0) if code != 1 { logger.Logger.Error(err) return } */ }() //检查Web2Cs.exe是否运行中,未运行时退出当前程序 go func() { for { time.Sleep(3 * time.Second) param := []string{"/C", "wmic process list brief| findstr Web2Cs.exe"} cmd := exec.Command("cmd", param...) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { logger.Logger.Error(err) } resultOut, _ := string(stdout.Bytes()), string(stderr.Bytes()) if strings.ReplaceAll(resultOut, " ", "") == "" { break } } os.Exit(0) }() } } if conf.GlobalConfig["appport"] != "" { go bo.CheckSystemUser() beego.SetLevel(beego.LevelDebug) //beego.SetLogger(logs.AdapterFile, "{\"filename\": \"beego_log.log\"}") beego.SetStaticPath("/", "static/login.html") beego.SetStaticPath("/login", "static/login.html") beego.SetStaticPath("/logout", "static/login.html") beego.InsertFilter("/api/*", beego.BeforeRouter, AuthFilter) beego.Run(":" + conf.GlobalConfig["appport"]) } } func BytesToInt(bys []byte) int { bytebuff := bytes.NewBuffer(bys) var data int64 binary.Read(bytebuff, binary.BigEndian, &data) return int(data) } var AuthFilter = func(ctx *context.Context) { ip := ctx.Request.Header.Get("X-Real-IP") if net.ParseIP(ip) == nil { ips := ctx.Request.Header.Get("X-Forward-For") for _, i := range strings.Split(ips, ",") { if net.ParseIP(i) != nil { ip = i break } } if ip == "" { ip, _, _ = net.SplitHostPort(ctx.Request.RemoteAddr) } } if ip != "" { //当前IP是否需要检查访问授权,默认为需要 isCheckThisIP := true if _, exist := global.AccessedIps.Load(ip); exist { //IP已经访问过系统,则无需检查 isCheckThisIP = false } if isCheckThisIP && global.AllowAccessIps != "*" { //判断当前客户端是否在允许范围内 ips := strings.Split(global.AllowAccessIps, ",") allowLogin := false for _, iplimt := range ips { if iplimt == ip { allowLogin = true break } tmppos := strings.Index(iplimt, ".*") if tmppos > 1 { //ip段 if len(ip) < tmppos { continue } if ip[:tmppos] == iplimt[:tmppos] { allowLogin = true break } } } if !allowLogin { userInfo := map[string]interface{}{} userInfo["ip"] = ip userInfo["name"] = "系统内置访问控制" new(bo.SystemLog).Fail(enum.AuditType_Client, enum.LogType_commit, enum.OptEventType_System, enum.OptEventLevel_Hight, fmt.Sprintf("未授权终端(IP:%s)尝试访问系统被拦截", ip), userInfo) //c.Data["json"] = c.WarpError("系统限制:未授权的访问!") //c.ServeJSON() ctx.Redirect(430, "/static/430.html") return } } //将IP缓存到允许队列中,如果更改了允许访问的IP配置时,需要重新初始化该队列 global.AccessedIps.Store(ip, 1) } u, err := url.Parse(strings.ReplaceAll(ctx.Request.RequestURI, "//", "/")) if err != nil { ctx.Redirect(500, "/error") return } uri := strings.ToLower(u.Path) if uri == "/api/logout" || uri == "/api/version" || uri == "/api/keep-alive" || arrayex.IndexOf(global.NoAuthRouter, uri) > -1 { return } var token string token = ctx.Request.Header.Get("Authorization") if token == "" { logger.Logger.Println("接口" + ctx.Request.RequestURI + "token标识缺失") ctx.ResponseWriter.WriteHeader(401) return } token = strings.ReplaceAll(strings.ReplaceAll(token, "Bearer ", ""), " ", "") if token == "" { logger.Logger.Println("接口" + ctx.Request.RequestURI + "token标识无效") ctx.ResponseWriter.WriteHeader(401) return } //校验该token有没有当前接口的访问权限 if !bo.HasApiAccess(token, uri) { logger.Logger.Println("接口" + ctx.Request.RequestURI + "不能被token:" + token + "访问") ctx.ResponseWriter.WriteHeader(401) return } nonce := ctx.Request.Header.Get("auth_nonce") //判断是不是特殊的token(后台layui接口不能校验) if strings.HasPrefix(nonce, "police-s-admin-") && len(nonce) == 26 { return } //验证请求时间戳和随机数,防数据重放 tim := ctx.Request.Header.Get("auth_time") if tim == "" { logger.Logger.Println("接口" + ctx.Request.RequestURI + " auth_time标识缺失") ctx.ResponseWriter.WriteHeader(401) //ctx.Redirect(301, "/login") return } nowhaomiao := time.Now().Unix() tim2 := tim[0:10] timint, er := strconv.Atoi(tim2) //接口请求有效时长为2秒内 //log.Println(fmt.Sprintf("now:%d tim:%d", nowhaomiao, timint)) if er != nil || (int(nowhaomiao)-timint) > 4 { logger.Logger.Println("接口" + ctx.Request.RequestURI + "请求超时。" + tim2 + ":" + strconv.Itoa(int(nowhaomiao))) ctx.ResponseWriter.WriteHeader(401) //ctx.Redirect(301, "/login") return } if nonce == "" { logger.Logger.Println("接口" + ctx.Request.RequestURI + " auth_nonce标识缺失") ctx.ResponseWriter.WriteHeader(401) //ctx.Redirect(301, "/login") return } kv, has := global.GoCahce.Get(nonce) if has == true || kv != nil { logger.Logger.Println("接口" + ctx.Request.RequestURI + " auth_nonce已存在,本次请求可能是接口重放攻击") ctx.ResponseWriter.WriteHeader(401) //ctx.Redirect(301, "/login") return } sign := ctx.Request.Header.Get("sign") if sign == "" { logger.Logger.Println("接口" + ctx.Request.RequestURI + "签名缺失") ctx.ResponseWriter.WriteHeader(401) //ctx.Redirect(301, "/login") return } //获取请求的所有参数,并进行签名对比,防数据篡改 signParalist := map[string]string{"auth_time": tim, "auth_nonce": nonce} reqPara1 := ctx.Request.URL.Query() for k, _ := range reqPara1 { signParalist[(k)] = tools.IsEmpty(reqPara1[k][0]) } if ctx.Request.MultipartForm == nil || len(ctx.Request.MultipartForm.File) == 0 { reqPara2 := ctx.Request.PostForm for k, _ := range reqPara2 { signParalist[(k)] = tools.IsEmpty(reqPara2[k][0]) } } var entiy []string var keys []string for key := range signParalist { keys = append(keys, (key)) } sort.Stable(sort.StringSlice(keys)) for _, name := range keys { /*tmpStr := url.QueryEscape(signParalist[name]) tmpStr = strings.ReplaceAll(tmpStr, "%28", "(") tmpStr = strings.ReplaceAll(tmpStr, "%29", ")")*/ entiy = append(entiy, name+"="+signParalist[name]) } newParalist := strings.Join(entiy, "&") // + token h := md5.New() h.Write([]byte(newParalist)) cipherStr := h.Sum(nil) signstr := hex.EncodeToString(cipherStr) // 输出加密结果 if sign != signstr { logger.Logger.Println("接口" + ctx.Request.RequestURI + "签名不一致,请检查参数值中是否有特殊字符。原始签名:" + sign + " 后台签名:" + signstr) logger.Logger.Println("接口" + ctx.Request.RequestURI + "签名参数:") logger.Logger.Println(newParalist) ctx.ResponseWriter.WriteHeader(401) //ctx.Redirect(401, "/login") return } //请求验证全部通过,更新session err = bo.UpdateSession(token) if err != nil { if uri != "/login" { logger.Logger.Error("接口" + ctx.Request.RequestURI + " token已过期:" + token) ctx.ResponseWriter.WriteHeader(401) //ctx.Redirect(401, "/login") } return } //3秒过期的随机数 global.GoCahce.Set(nonce, nonce, 3*time.Second) }