900 lines
25 KiB
Go
900 lines
25 KiB
Go
/* Vuls - Vulnerability Scanner
|
|
Copyright (C) 2016 Future Architect, Inc. Japan.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package report
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
"github.com/future-architect/vuls/models"
|
|
"github.com/google/subcommands"
|
|
"github.com/gosuri/uitable"
|
|
"github.com/jroimartin/gocui"
|
|
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
|
)
|
|
|
|
var scanResults models.ScanResults
|
|
var currentScanResult models.ScanResult
|
|
var currentCveInfo int
|
|
var currentDetailLimitY int
|
|
|
|
// RunTui execute main logic
|
|
func RunTui(results models.ScanResults) subcommands.ExitStatus {
|
|
scanResults = results
|
|
|
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
|
if err != nil {
|
|
log.Errorf("%s", err)
|
|
return subcommands.ExitFailure
|
|
}
|
|
defer g.Close()
|
|
|
|
g.SetManagerFunc(layout)
|
|
if err := keybindings(g); err != nil {
|
|
log.Errorf("%s", err)
|
|
return subcommands.ExitFailure
|
|
}
|
|
g.SelBgColor = gocui.ColorGreen
|
|
g.SelFgColor = gocui.ColorBlack
|
|
g.Cursor = true
|
|
|
|
if err := g.MainLoop(); err != nil {
|
|
g.Close()
|
|
log.Errorf("%s", err)
|
|
os.Exit(1)
|
|
}
|
|
return subcommands.ExitSuccess
|
|
}
|
|
|
|
func keybindings(g *gocui.Gui) (err error) {
|
|
errs := []error{}
|
|
|
|
// Move beetween views
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyTab, gocui.ModNone, nextView))
|
|
// errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlH, gocui.ModNone, previousView))
|
|
// errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlL, gocui.ModNone, nextView))
|
|
// errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowRight, gocui.ModAlt, nextView))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeySpace, gocui.ModNone, cursorPageDown))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlN, gocui.ModNone, cursorDown))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlP, gocui.ModNone, cursorUp))
|
|
errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, nextView))
|
|
|
|
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
|
// errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, showMsg))
|
|
|
|
// summary
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyTab, gocui.ModNone, nextView))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlQ, gocui.ModNone, previousView))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlH, gocui.ModNone, previousView))
|
|
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlL, gocui.ModNone, nextView))
|
|
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowLeft, gocui.ModAlt, previousView))
|
|
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModAlt, nextView))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeySpace, gocui.ModNone, cursorPageDown))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
|
|
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyEnter, gocui.ModNone, nextView))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
|
|
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
|
|
|
|
// detail
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyTab, gocui.ModNone, nextView))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlQ, gocui.ModNone, previousView))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlH, gocui.ModNone, nextView))
|
|
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlL, gocui.ModNone, nextView))
|
|
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModAlt, previousView))
|
|
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeySpace, gocui.ModNone, cursorPageDown))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
|
|
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
|
|
errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, nextView))
|
|
|
|
// changelog
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyTab, gocui.ModNone, nextView))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlQ, gocui.ModNone, previousView))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlH, gocui.ModNone, nextView))
|
|
// errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlL, gocui.ModNone, nextView))
|
|
// errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModAlt, previousView))
|
|
// errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeySpace, gocui.ModNone, cursorPageDown))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
|
|
// errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
|
|
errs = append(errs, g.SetKeybinding("changelog", gocui.KeyEnter, gocui.ModNone, nextView))
|
|
|
|
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
|
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, showMsg))
|
|
|
|
//TODO Help Ctrl-h
|
|
|
|
errs = append(errs, g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit))
|
|
// errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, getLine))
|
|
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
|
|
|
for _, e := range errs {
|
|
if e != nil {
|
|
return e
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func nextView(g *gocui.Gui, v *gocui.View) error {
|
|
var err error
|
|
|
|
if v == nil {
|
|
_, err = g.SetCurrentView("side")
|
|
}
|
|
switch v.Name() {
|
|
case "side":
|
|
_, err = g.SetCurrentView("summary")
|
|
case "summary":
|
|
_, err = g.SetCurrentView("detail")
|
|
case "detail":
|
|
_, err = g.SetCurrentView("changelog")
|
|
case "changelog":
|
|
_, err = g.SetCurrentView("side")
|
|
default:
|
|
_, err = g.SetCurrentView("summary")
|
|
}
|
|
return err
|
|
}
|
|
|
|
func previousView(g *gocui.Gui, v *gocui.View) error {
|
|
var err error
|
|
|
|
if v == nil {
|
|
_, err = g.SetCurrentView("side")
|
|
}
|
|
switch v.Name() {
|
|
case "side":
|
|
_, err = g.SetCurrentView("side")
|
|
case "summary":
|
|
_, err = g.SetCurrentView("side")
|
|
case "detail":
|
|
_, err = g.SetCurrentView("summary")
|
|
case "changelog":
|
|
_, err = g.SetCurrentView("detail")
|
|
default:
|
|
_, err = g.SetCurrentView("side")
|
|
}
|
|
return err
|
|
}
|
|
|
|
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
|
|
switch v.Name() {
|
|
case "side":
|
|
yLimit = len(scanResults) - 1
|
|
if yLimit < nextY {
|
|
return false, yLimit
|
|
}
|
|
return true, yLimit
|
|
case "summary":
|
|
//TODO
|
|
// yLimit = len(currentScanResult.AllCves()) - 1
|
|
if yLimit < nextY {
|
|
return false, yLimit
|
|
}
|
|
return true, yLimit
|
|
case "detail":
|
|
if currentDetailLimitY < nextY {
|
|
return false, currentDetailLimitY
|
|
}
|
|
return true, currentDetailLimitY
|
|
case "changelog":
|
|
if currentDetailLimitY < nextY {
|
|
return false, currentDetailLimitY
|
|
}
|
|
return true, currentDetailLimitY
|
|
default:
|
|
return true, 0
|
|
}
|
|
}
|
|
|
|
func pageUpDownJumpCount(v *gocui.View) int {
|
|
var jump int
|
|
switch v.Name() {
|
|
case "side", "summary":
|
|
jump = 8
|
|
case "detail", "changelog":
|
|
jump = 30
|
|
default:
|
|
jump = 8
|
|
}
|
|
return jump
|
|
}
|
|
|
|
// redraw views
|
|
func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error {
|
|
switch v.Name() {
|
|
case "summary":
|
|
if err := redrawDetail(g); err != nil {
|
|
return err
|
|
}
|
|
if err := redrawChangelog(g); err != nil {
|
|
return err
|
|
}
|
|
case "side":
|
|
if err := changeHost(g, v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cursorDown(g *gocui.Gui, v *gocui.View) error {
|
|
if v != nil {
|
|
cx, cy := v.Cursor()
|
|
ox, oy := v.Origin()
|
|
// ok, := movable(v, oy+cy+1)
|
|
// _, maxY := v.Size()
|
|
ok, _ := movable(v, oy+cy+1)
|
|
// log.Info(cy, oy, maxY, yLimit)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
if err := v.SetCursor(cx, cy+1); err != nil {
|
|
if err := v.SetOrigin(ox, oy+1); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
onMovingCursorRedrawView(g, v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cursorMoveTop(g *gocui.Gui, v *gocui.View) error {
|
|
if v != nil {
|
|
cx, _ := v.Cursor()
|
|
v.SetCursor(cx, 0)
|
|
}
|
|
onMovingCursorRedrawView(g, v)
|
|
return nil
|
|
}
|
|
|
|
func cursorMoveBottom(g *gocui.Gui, v *gocui.View) error {
|
|
if v != nil {
|
|
_, maxY := v.Size()
|
|
cx, _ := v.Cursor()
|
|
v.SetCursor(cx, maxY-1)
|
|
}
|
|
onMovingCursorRedrawView(g, v)
|
|
return nil
|
|
}
|
|
|
|
func cursorMoveMiddle(g *gocui.Gui, v *gocui.View) error {
|
|
if v != nil {
|
|
_, maxY := v.Size()
|
|
cx, _ := v.Cursor()
|
|
v.SetCursor(cx, maxY/2)
|
|
}
|
|
onMovingCursorRedrawView(g, v)
|
|
return nil
|
|
}
|
|
|
|
func cursorPageDown(g *gocui.Gui, v *gocui.View) error {
|
|
jump := pageUpDownJumpCount(v)
|
|
|
|
if v != nil {
|
|
cx, cy := v.Cursor()
|
|
ox, oy := v.Origin()
|
|
ok, yLimit := movable(v, oy+cy+jump)
|
|
_, maxY := v.Size()
|
|
|
|
if !ok {
|
|
if yLimit < maxY {
|
|
v.SetCursor(cx, yLimit)
|
|
} else {
|
|
v.SetCursor(cx, maxY-1)
|
|
v.SetOrigin(ox, yLimit-maxY+1)
|
|
}
|
|
} else if yLimit < oy+jump+maxY {
|
|
if yLimit < maxY {
|
|
v.SetCursor(cx, yLimit)
|
|
} else {
|
|
v.SetOrigin(ox, yLimit-maxY+1)
|
|
v.SetCursor(cx, maxY-1)
|
|
}
|
|
} else {
|
|
v.SetCursor(cx, cy)
|
|
v.SetOrigin(ox, oy+jump)
|
|
}
|
|
onMovingCursorRedrawView(g, v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cursorUp(g *gocui.Gui, v *gocui.View) error {
|
|
if v != nil {
|
|
ox, oy := v.Origin()
|
|
cx, cy := v.Cursor()
|
|
if err := v.SetCursor(cx, cy-1); err != nil && 0 < oy {
|
|
if err := v.SetOrigin(ox, oy-1); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
onMovingCursorRedrawView(g, v)
|
|
return nil
|
|
}
|
|
|
|
func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
|
|
jump := pageUpDownJumpCount(v)
|
|
if v != nil {
|
|
cx, _ := v.Cursor()
|
|
ox, oy := v.Origin()
|
|
if err := v.SetOrigin(ox, oy-jump); err != nil {
|
|
v.SetOrigin(ox, 0)
|
|
v.SetCursor(cx, 0)
|
|
|
|
}
|
|
onMovingCursorRedrawView(g, v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func previousSummary(g *gocui.Gui, v *gocui.View) error {
|
|
if v != nil {
|
|
// cursor to summary
|
|
if _, err := g.SetCurrentView("summary"); err != nil {
|
|
return err
|
|
}
|
|
// move next line
|
|
if err := cursorUp(g, g.CurrentView()); err != nil {
|
|
return err
|
|
}
|
|
// cursor to detail
|
|
if _, err := g.SetCurrentView("detail"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func nextSummary(g *gocui.Gui, v *gocui.View) error {
|
|
if v != nil {
|
|
// cursor to summary
|
|
if _, err := g.SetCurrentView("summary"); err != nil {
|
|
return err
|
|
}
|
|
// move next line
|
|
if err := cursorDown(g, g.CurrentView()); err != nil {
|
|
return err
|
|
}
|
|
// cursor to detail
|
|
if _, err := g.SetCurrentView("detail"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func changeHost(g *gocui.Gui, v *gocui.View) error {
|
|
|
|
if err := g.DeleteView("summary"); err != nil {
|
|
return err
|
|
}
|
|
if err := g.DeleteView("detail"); err != nil {
|
|
return err
|
|
}
|
|
if err := g.DeleteView("changelog"); err != nil {
|
|
return err
|
|
}
|
|
|
|
_, cy := v.Cursor()
|
|
l, err := v.Line(cy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serverName := strings.TrimSpace(l)
|
|
|
|
for _, r := range scanResults {
|
|
if serverName == strings.TrimSpace(r.ServerInfoTui()) {
|
|
currentScanResult = r
|
|
break
|
|
}
|
|
}
|
|
|
|
if err := setSummaryLayout(g); err != nil {
|
|
return err
|
|
}
|
|
if err := setDetailLayout(g); err != nil {
|
|
return err
|
|
}
|
|
if err := setChangelogLayout(g); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func redrawDetail(g *gocui.Gui) error {
|
|
if err := g.DeleteView("detail"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := setDetailLayout(g); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func redrawChangelog(g *gocui.Gui) error {
|
|
if err := g.DeleteView("changelog"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := setChangelogLayout(g); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getLine(g *gocui.Gui, v *gocui.View) error {
|
|
var l string
|
|
var err error
|
|
|
|
_, cy := v.Cursor()
|
|
if l, err = v.Line(cy); err != nil {
|
|
l = ""
|
|
}
|
|
|
|
maxX, maxY := g.Size()
|
|
if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
fmt.Fprintln(v, l)
|
|
if _, err := g.SetCurrentView("msg"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func showMsg(g *gocui.Gui, v *gocui.View) error {
|
|
jump := 8
|
|
_, cy := v.Cursor()
|
|
_, oy := v.Origin()
|
|
ok, yLimit := movable(v, oy+cy+jump)
|
|
// maxX, maxY := v.Size()
|
|
_, maxY := v.Size()
|
|
|
|
l := fmt.Sprintf("cy: %d, oy: %d, maxY: %d, yLimit: %d, curCve %d, ok: %v", cy, oy, maxY, yLimit, currentCveInfo, ok)
|
|
// if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
|
if v, err := g.SetView("msg", 10, maxY/2, 10+50, maxY/2+2); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
fmt.Fprintln(v, l)
|
|
if _, err := g.SetCurrentView("msg"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func delMsg(g *gocui.Gui, v *gocui.View) error {
|
|
if err := g.DeleteView("msg"); err != nil {
|
|
return err
|
|
}
|
|
if _, err := g.SetCurrentView("summary"); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func quit(g *gocui.Gui, v *gocui.View) error {
|
|
return gocui.ErrQuit
|
|
}
|
|
|
|
func layout(g *gocui.Gui) error {
|
|
if err := setSideLayout(g); err != nil {
|
|
return err
|
|
}
|
|
if err := setSummaryLayout(g); err != nil {
|
|
return err
|
|
}
|
|
if err := setDetailLayout(g); err != nil {
|
|
return err
|
|
}
|
|
if err := setChangelogLayout(g); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setSideLayout(g *gocui.Gui) error {
|
|
_, maxY := g.Size()
|
|
if v, err := g.SetView("side", -1, -1, 40, int(float64(maxY)*0.2)); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
v.Highlight = true
|
|
|
|
for _, result := range scanResults {
|
|
fmt.Fprintln(v, result.ServerInfoTui())
|
|
}
|
|
if len(scanResults) == 0 {
|
|
return fmt.Errorf("No scan results")
|
|
}
|
|
currentScanResult = scanResults[0]
|
|
if _, err := g.SetCurrentView("side"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setSummaryLayout(g *gocui.Gui) error {
|
|
maxX, maxY := g.Size()
|
|
if v, err := g.SetView("summary", 40, -1, maxX, int(float64(maxY)*0.2)); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
|
|
lines := summaryLines()
|
|
fmt.Fprintf(v, lines)
|
|
|
|
v.Highlight = true
|
|
v.Editable = false
|
|
v.Wrap = false
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func summaryLines() string {
|
|
stable := uitable.New()
|
|
stable.MaxColWidth = 1000
|
|
stable.Wrap = false
|
|
|
|
if len(currentScanResult.Errors) != 0 {
|
|
return "Error: Scan with --debug to view the details"
|
|
}
|
|
|
|
//TODO
|
|
// indexFormat := ""
|
|
// if len(currentScanResult.AllCves()) < 10 {
|
|
// indexFormat = "[%1d]"
|
|
// } else if len(currentScanResult.AllCves()) < 100 {
|
|
// indexFormat = "[%2d]"
|
|
// } else {
|
|
// indexFormat = "[%3d]"
|
|
// }
|
|
|
|
// for i, d := range currentScanResult.AllCves() {
|
|
// var cols []string
|
|
// //TODO
|
|
// var summary string
|
|
// if cont, found := d.Get(models.NVD); found {
|
|
// summary = cont.Summary
|
|
// }
|
|
// var cvssScore string
|
|
// if d.CvssV2Score() <= 0 {
|
|
// cvssScore = "| ?"
|
|
// } else {
|
|
// cvssScore = fmt.Sprintf("| %4.1f", d.CvssV2Score())
|
|
// }
|
|
// cols = []string{
|
|
// fmt.Sprintf(indexFormat, i+1),
|
|
// d.VulnInfo.CveID,
|
|
// cvssScore,
|
|
// fmt.Sprintf("| %3d |", d.VulnInfo.Confidence.Score),
|
|
// summary,
|
|
// }
|
|
// // if config.Conf.Lang == "ja" && 0 < d.CveDetail.Jvn.CvssScore() {
|
|
// // summary := d.CveDetail.Jvn.CveTitle()
|
|
// // cols = []string{
|
|
// // fmt.Sprintf(indexFormat, i+1),
|
|
// // d.CveDetail.CveID,
|
|
// // fmt.Sprintf("| %4.1f",
|
|
// // d.CveDetail.CvssScore(config.Conf.Lang)),
|
|
// // fmt.Sprintf("| %3d |", d.VulnInfo.Confidence.Score),
|
|
// // summary,
|
|
// // }
|
|
// // } else {
|
|
// // summary := d.CveDetail.Nvd.CveSummary()
|
|
|
|
// // var cvssScore string
|
|
// // if d.CveDetail.CvssScore("en") <= 0 {
|
|
// // cvssScore = "| ?"
|
|
// // } else {
|
|
// // cvssScore = fmt.Sprintf("| %4.1f",
|
|
// // d.CveDetail.CvssScore(config.Conf.Lang))
|
|
// // }
|
|
|
|
// // cols = []string{
|
|
// // fmt.Sprintf(indexFormat, i+1),
|
|
// // d.CveDetail.CveID,
|
|
// // cvssScore,
|
|
// // fmt.Sprintf("| %3d |", d.VulnInfo.Confidence.Score),
|
|
// // summary,
|
|
// // }
|
|
// // }
|
|
|
|
// icols := make([]interface{}, len(cols))
|
|
// for j := range cols {
|
|
// icols[j] = cols[j]
|
|
// }
|
|
// stable.AddRow(icols...)
|
|
// }
|
|
return fmt.Sprintf("%s", stable)
|
|
}
|
|
|
|
func setDetailLayout(g *gocui.Gui) error {
|
|
maxX, maxY := g.Size()
|
|
|
|
summaryView, err := g.View("summary")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, cy := summaryView.Cursor()
|
|
_, oy := summaryView.Origin()
|
|
currentCveInfo = cy + oy
|
|
|
|
if v, err := g.SetView("detail", -1, int(float64(maxY)*0.2), int(float64(maxX)*0.5), maxY); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
text, err := detailLines()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprint(v, text)
|
|
v.Editable = false
|
|
v.Wrap = true
|
|
|
|
currentDetailLimitY = len(strings.Split(text, "\n")) - 1
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setChangelogLayout(g *gocui.Gui) error {
|
|
maxX, maxY := g.Size()
|
|
|
|
summaryView, err := g.View("summary")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, cy := summaryView.Cursor()
|
|
_, oy := summaryView.Origin()
|
|
currentCveInfo = cy + oy
|
|
|
|
if v, err := g.SetView("changelog", int(float64(maxX)*0.5), int(float64(maxY)*0.2), maxX, maxY); err != nil {
|
|
if err != gocui.ErrUnknownView {
|
|
return err
|
|
}
|
|
//TODO
|
|
// if len(currentScanResult.Errors) != 0 || len(currentScanResult.AllCves()) == 0 {
|
|
// return nil
|
|
// }
|
|
|
|
lines := []string{}
|
|
//TODO
|
|
// cveInfo := currentScanResult.AllCves()[currentCveInfo]
|
|
// for _, pack := range cveInfo.Packages {
|
|
// for _, p := range currentScanResult.Packages {
|
|
// if pack.Name == p.Name {
|
|
// lines = append(lines, formatOneChangelog(p), "\n")
|
|
// }
|
|
// }
|
|
// }
|
|
text := strings.Join(lines, "\n")
|
|
fmt.Fprint(v, text)
|
|
v.Editable = false
|
|
v.Wrap = true
|
|
|
|
currentDetailLimitY = len(strings.Split(text, "\n")) - 1
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type dataForTmpl struct {
|
|
CveID string
|
|
CvssScore string
|
|
CvssVector string
|
|
CvssSeverity string
|
|
Summary string
|
|
Confidence models.Confidence
|
|
CweURL string
|
|
VulnSiteLinks []string
|
|
References []cve.Reference
|
|
Packages []string
|
|
CpeNames []string
|
|
PublishedDate time.Time
|
|
LastModifiedDate time.Time
|
|
}
|
|
|
|
func detailLines() (string, error) {
|
|
if len(currentScanResult.Errors) != 0 {
|
|
return "", nil
|
|
}
|
|
|
|
//TODO
|
|
// if len(currentScanResult.AllCves()) == 0 {
|
|
// return "No vulnerable packages", nil
|
|
// }
|
|
// cveInfo := currentScanResult.AllCves()[currentCveInfo]
|
|
// cveID := cveInfo.VulnInfo.CveID
|
|
|
|
// tmpl, err := template.New("detail").Parse(detailTemplate())
|
|
// if err != nil {
|
|
// return "", err
|
|
// }
|
|
|
|
// var cvssSeverity, cvssVector, summary string
|
|
// var refs []cve.Reference
|
|
switch {
|
|
//TODO
|
|
// case config.Conf.Lang == "ja" &&
|
|
// 0 < cveInfo.CveDetail.Jvn.CvssScore():
|
|
// jvn := cveInfo.CveDetail.Jvn
|
|
// cvssSeverity = jvn.CvssSeverity()
|
|
// cvssVector = jvn.CvssVector()
|
|
// summary = fmt.Sprintf("%s\n%s", jvn.CveTitle(), jvn.CveSummary())
|
|
// refs = jvn.VulnSiteReferences()
|
|
default:
|
|
// var nvd *models.CveContent
|
|
//TODO
|
|
// if cont, found := cveInfo.Get(models.NVD); found {
|
|
// nvd = cont
|
|
// }
|
|
// cvssSeverity = nvd.CvssSeverity()
|
|
// cvssVector = nvd.CvssVector()
|
|
// summary = nvd.Summary
|
|
// refs = nvd.VulnSiteReferences()
|
|
}
|
|
|
|
//TODO
|
|
// cweURL := cweURL(cveInfo.CveDetail.CweID())
|
|
// links := []string{
|
|
// fmt.Sprintf("[NVD]( %s )", fmt.Sprintf("%s/%s", nvdBaseURL, cveID)),
|
|
// fmt.Sprintf("[MITRE]( %s )", fmt.Sprintf("%s%s", mitreBaseURL, cveID)),
|
|
// fmt.Sprintf("[CveDetais]( %s )", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)),
|
|
// fmt.Sprintf("[CVSSv2 Calc]( %s )", fmt.Sprintf(cvssV2CalcBaseURL, cveID)),
|
|
// fmt.Sprintf("[CVSSv3 Calc]( %s )", fmt.Sprintf(cvssV3CalcBaseURL, cveID)),
|
|
// }
|
|
// dlinks := distroLinks(cveInfo, currentScanResult.Family)
|
|
// for _, link := range dlinks {
|
|
// links = append(links, fmt.Sprintf("[%s]( %s )", link.title, link.url))
|
|
// }
|
|
|
|
//TODO
|
|
// var cvssScore string
|
|
// if cveInfo.CvssV2Score() == -1 {
|
|
// cvssScore = "?"
|
|
// // } else {
|
|
// // cvssScore = fmt.Sprintf("%4.1f", cveInfo.CveDetail.CvssScore(config.Conf.Lang))
|
|
// }
|
|
|
|
// packages := []string{}
|
|
// for _, pack := range cveInfo.Packages {
|
|
// packages = append(packages,
|
|
// fmt.Sprintf(
|
|
// "%s -> %s",
|
|
// pack.FormatCurrentVer(),
|
|
// pack.FormatNewVer()))
|
|
// }
|
|
|
|
// data := dataForTmpl{
|
|
// CveID: cveID,
|
|
// CvssScore: cvssScore,
|
|
// CvssSeverity: cvssSeverity,
|
|
// CvssVector: cvssVector,
|
|
// Summary: summary,
|
|
// Confidence: cveInfo.VulnInfo.Confidence,
|
|
// //TODO
|
|
// // CweURL: cweURL,
|
|
// VulnSiteLinks: links,
|
|
// References: refs,
|
|
// Packages: packages,
|
|
// CpeNames: cveInfo.CpeNames,
|
|
// }
|
|
|
|
buf := bytes.NewBuffer(nil) // create empty buffer
|
|
// if err := tmpl.Execute(buf, data); err != nil {
|
|
// return "", err
|
|
// }
|
|
|
|
return string(buf.Bytes()), nil
|
|
}
|
|
|
|
func detailTemplate() string {
|
|
return `
|
|
{{.CveID}}
|
|
==============
|
|
|
|
CVSS Score
|
|
--------------
|
|
|
|
{{.CvssScore}} ({{.CvssSeverity}}) {{.CvssVector}}
|
|
|
|
Summary
|
|
--------------
|
|
|
|
{{.Summary }}
|
|
|
|
Confidence
|
|
--------------
|
|
|
|
{{.Confidence }}
|
|
|
|
CWE
|
|
--------------
|
|
|
|
{{.CweURL }}
|
|
|
|
Package/CPE
|
|
--------------
|
|
|
|
{{range $pack := .Packages -}}
|
|
* {{$pack}}
|
|
{{end -}}
|
|
{{range $name := .CpeNames -}}
|
|
* {{$name}}
|
|
{{end}}
|
|
Links
|
|
--------------
|
|
|
|
{{range $link := .VulnSiteLinks -}}
|
|
* {{$link}}
|
|
{{end}}
|
|
References
|
|
--------------
|
|
|
|
{{range .References -}}
|
|
* [{{.Source}}]( {{.Link}} )
|
|
{{end}}
|
|
|
|
`
|
|
}
|