Permalink
Fetching contributors…
Cannot retrieve contributors at this time
957 lines (851 sloc) 26.1 KB
/* Vuls - Vulnerability Scanner
Copyright (C) 2016 Future Corporation , 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"
"sort"
"strings"
"text/template"
"time"
"github.com/future-architect/vuls/alert"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/google/subcommands"
"github.com/gosuri/uitable"
"github.com/jroimartin/gocui"
)
var scanResults models.ScanResults
var currentScanResult models.ScanResult
var vinfos []models.VulnInfo
var currentVinfo int
var currentDetailLimitY int
var currentChangelogLimitY int
// RunTui execute main logic
func RunTui(results models.ScanResults) subcommands.ExitStatus {
scanResults = results
sort.Slice(scanResults, func(i, j int) bool {
if scanResults[i].ServerName == scanResults[j].ServerName {
return scanResults[i].Container.Name < scanResults[j].Container.Name
}
return scanResults[i].ServerName < scanResults[j].ServerName
})
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
util.Log.Errorf("%s", err)
return subcommands.ExitFailure
}
defer g.Close()
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
util.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()
util.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":
yLimit = len(currentScanResult.ScannedCves) - 1
if yLimit < nextY {
return false, yLimit
}
return true, yLimit
case "detail":
// if currentDetailLimitY < nextY {
// return false, currentDetailLimitY
// }
return true, currentDetailLimitY
case "changelog":
// if currentChangelogLimitY < nextY {
// return false, currentChangelogLimitY
// }
return true, currentChangelogLimitY
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)
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)
}
cx, cy := v.Cursor()
ox, oy := v.Origin()
debug(g, fmt.Sprintf("%v, %v, %v, %v", cx, cy, ox, oy))
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
vinfos = r.ScannedCves.ToSortedSlice()
break
}
}
if err := setSummaryLayout(g); err != nil {
return err
}
if err := setDetailLayout(g); err != nil {
return err
}
return setChangelogLayout(g)
}
func redrawDetail(g *gocui.Gui) error {
if err := g.DeleteView("detail"); err != nil {
return err
}
return setDetailLayout(g)
}
func redrawChangelog(g *gocui.Gui) error {
if err := g.DeleteView("changelog"); err != nil {
return err
}
return setChangelogLayout(g)
}
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, currentVinfo, 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
}
_, err := g.SetCurrentView("summary")
return err
}
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
}
return setChangelogLayout(g)
}
func debug(g *gocui.Gui, str string) error {
if config.Conf.Debug {
maxX, maxY := g.Size()
if _, err := g.View("debug"); err != gocui.ErrUnknownView {
g.DeleteView("debug")
}
if v, err := g.SetView("debug", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
fmt.Fprintf(v, str)
}
}
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]
vinfos = scanResults[0].ScannedCves.ToSortedSlice()
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(currentScanResult)
fmt.Fprintf(v, lines)
v.Highlight = true
v.Editable = false
v.Wrap = false
}
return nil
}
func summaryLines(r models.ScanResult) string {
stable := uitable.New()
stable.MaxColWidth = 1000
stable.Wrap = false
if len(r.Errors) != 0 {
return "Error: Scan with --debug to view the details"
}
indexFormat := ""
if len(r.ScannedCves) < 10 {
indexFormat = "[%1d]"
} else if len(r.ScannedCves) < 100 {
indexFormat = "[%2d]"
} else {
indexFormat = "[%3d]"
}
for i, vinfo := range r.ScannedCves.ToSortedSlice() {
max := vinfo.MaxCvssScore().Value.Score
cvssScore := "| "
if 0 < max {
cvssScore = fmt.Sprintf("| %4.1f", max)
}
packname := vinfo.AffectedPackages.FormatTuiSummary()
packname += strings.Join(vinfo.CpeURIs, ", ")
alert := " "
if vinfo.AlertDict.HasAlert() {
alert = "! "
}
var cols []string
cols = []string{
fmt.Sprintf(indexFormat, i+1),
alert + vinfo.CveID,
cvssScore + " |",
fmt.Sprintf("%8s |", vinfo.AttackVector()),
fmt.Sprintf("%7s |", vinfo.PatchStatus(r.Packages)),
packname,
}
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()
currentVinfo = 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 {
summaryView, err := g.View("summary")
if err != nil {
return err
}
maxX, maxY := g.Size()
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
}
if len(currentScanResult.Errors) != 0 || len(currentScanResult.ScannedCves) == 0 {
return nil
}
lines := []string{
"Affected Packages, Processes",
"============================",
}
_, cy := summaryView.Cursor()
_, oy := summaryView.Origin()
currentVinfo = cy + oy
vinfo := vinfos[currentVinfo]
vinfo.AffectedPackages.Sort()
for _, affected := range vinfo.AffectedPackages {
// packages detected by OVAL may not be actually installed
if pack, ok := currentScanResult.Packages[affected.Name]; ok {
var line string
if pack.Repository != "" {
line = fmt.Sprintf("* %s (%s)",
pack.FormatVersionFromTo(affected.NotFixedYet, affected.FixState),
pack.Repository)
} else {
line = fmt.Sprintf("* %s",
pack.FormatVersionFromTo(affected.NotFixedYet, affected.FixState),
)
}
lines = append(lines, line)
if len(pack.AffectedProcs) != 0 {
for _, p := range pack.AffectedProcs {
lines = append(lines, fmt.Sprintf(" * PID: %s %s", p.PID, p.Name))
}
} else {
// lines = append(lines, fmt.Sprintf(" * No affected process"))
}
}
}
sort.Strings(vinfo.CpeURIs)
for _, uri := range vinfo.CpeURIs {
lines = append(lines, "* "+uri)
}
for _, adv := range vinfo.DistroAdvisories {
lines = append(lines, "\n",
"Advisories",
"==========",
)
lines = append(lines, adv.Format())
}
if len(vinfo.Exploits) != 0 {
lines = append(lines, "\n",
"Exploit Codes",
"=============",
)
for _, exploit := range vinfo.Exploits {
lines = append(lines, fmt.Sprintf("* [%s](%s)", exploit.Description, exploit.URL))
}
}
if len(vinfo.AlertDict.En) > 0 {
lines = append(lines, "\n",
"USCERT Alert",
"=============",
)
for _, alert := range vinfo.AlertDict.En {
lines = append(lines, fmt.Sprintf("* [%s](%s)", alert.Title, alert.URL))
}
}
if len(vinfo.AlertDict.Ja) > 0 {
lines = append(lines, "\n",
"JPCERT Alert",
"=============",
)
for _, alert := range vinfo.AlertDict.Ja {
if config.Conf.Lang == "ja" {
lines = append(lines, fmt.Sprintf("* [%s](%s)", alert.Title, alert.URL))
} else {
lines = append(lines, fmt.Sprintf("* [JPCERT](%s)", alert.URL))
}
}
}
if currentScanResult.IsDeepScanMode() {
lines = append(lines, "\n",
"ChangeLogs",
"==========",
)
for _, affected := range vinfo.AffectedPackages {
pack := currentScanResult.Packages[affected.Name]
for _, p := range currentScanResult.Packages {
if pack.Name == p.Name {
lines = append(lines, p.FormatChangelog(), "\n")
}
}
}
}
text := strings.Join(lines, "\n")
fmt.Fprint(v, text)
v.Editable = false
v.Wrap = true
currentChangelogLimitY = len(strings.Split(text, "\n")) - 1
}
return nil
}
type dataForTmpl struct {
CveID string
Cvsses string
Exploits []models.Exploit
Summary string
Mitigation string
Confidences models.Confidences
Cwes []models.CweDictEntry
Alerts []alert.Alert
Links []string
References []models.Reference
Packages []string
CpeURIs []string
PublishedDate time.Time
LastModifiedDate time.Time
}
func detailLines() (string, error) {
r := currentScanResult
if len(r.Errors) != 0 {
return "", nil
}
if len(r.ScannedCves) == 0 {
return "No vulnerable packages", nil
}
tmpl, err := template.New("detail").Parse(mdTemplate)
if err != nil {
return "", err
}
vinfo := vinfos[currentVinfo]
links := []string{vinfo.CveContents.SourceLinks(
config.Conf.Lang, r.Family, vinfo.CveID)[0].Value,
vinfo.Cvss2CalcURL(),
vinfo.Cvss3CalcURL()}
for _, url := range vinfo.VendorLinks(r.Family) {
links = append(links, url)
}
refs := []models.Reference{}
for _, rr := range vinfo.CveContents.References(r.Family) {
for _, ref := range rr.Value {
if ref.Source == "" {
ref.Source = "-"
}
refs = append(refs, ref)
}
}
summary := vinfo.Summaries(r.Lang, r.Family)[0]
mitigation := vinfo.Mitigations(r.Family)[0]
table := uitable.New()
table.MaxColWidth = maxColWidth
table.Wrap = true
scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores(r.Family)...)
var cols []interface{}
for _, score := range scores {
if score.Value.Score == 0 && score.Value.Severity == "" {
continue
}
scoreStr := "-"
if 0 < score.Value.Score {
scoreStr = fmt.Sprintf("%3.1f", score.Value.Score)
}
scoreVec := fmt.Sprintf("%s/%s", scoreStr, score.Value.Vector)
cols = []interface{}{
scoreVec,
score.Value.Severity,
score.Type,
}
table.AddRow(cols...)
}
uniqCweIDs := vinfo.CveContents.UniqCweIDs(r.Family)
cwes := []models.CweDictEntry{}
for _, cweID := range uniqCweIDs {
if strings.HasPrefix(cweID.Value, "CWE-") {
if dict, ok := r.CweDict[strings.TrimPrefix(cweID.Value, "CWE-")]; ok {
cwes = append(cwes, dict)
}
}
}
data := dataForTmpl{
CveID: vinfo.CveID,
Cvsses: fmt.Sprintf("%s\n", table),
Summary: fmt.Sprintf("%s (%s)", summary.Value, summary.Type),
Mitigation: fmt.Sprintf("%s (%s)", mitigation.Value, mitigation.Type),
Confidences: vinfo.Confidences,
Cwes: cwes,
Links: util.Distinct(links),
References: refs,
}
buf := bytes.NewBuffer(nil) // create empty buffer
if err := tmpl.Execute(buf, data); err != nil {
return "", err
}
return string(buf.Bytes()), nil
}
const mdTemplate = `
{{.CveID}}
================
CVSS Scores
-----------
{{.Cvsses }}
Summary
-----------
{{.Summary }}
Mitigation
-----------
{{.Mitigation }}
Links
-----------
{{range $link := .Links -}}
* {{$link}}
{{end}}
CWE
-----------
{{range .Cwes -}}
* {{.En.CweID}} [{{.En.Name}}](https://cwe.mitre.org/data/definitions/{{.En.CweID}}.html)
{{end}}
{{range $name := .CpeURIs -}}
* {{$name}}
{{end}}
Confidence
-----------
{{range $confidence := .Confidences -}}
* {{$confidence.DetectionMethod}}
{{end}}
References
-----------
{{range .References -}}
* [{{.Source}}]({{.Link}})
{{end}}
`