main
Raw Download raw file
  1package cmd
  2
  3import (
  4	"context"
  5	"encoding/json"
  6	"fmt"
  7	"mysh/pkg/cache"
  8	"mysh/pkg/mythic"
  9	"mysh/pkg/mythic/api"
 10	"strconv"
 11	"strings"
 12	"time"
 13
 14	"github.com/spf13/cobra"
 15)
 16
 17var (
 18	taskViewRawOutput bool
 19	taskViewDetails   bool
 20)
 21
 22var taskViewCmd = &cobra.Command{
 23	Use:     "task-view <task_id>",
 24	Aliases: []string{"tv"},
 25	Short:   "Show details for a specific task",
 26	Long:    "Display detailed information and response for a specific task by its ID.",
 27	Args:    cobra.ExactArgs(1),
 28	RunE:    runTaskView,
 29}
 30
 31func init() {
 32	rootCmd.AddCommand(taskViewCmd)
 33	taskViewCmd.Flags().BoolVarP(&taskViewRawOutput, "raw", "r", false, "Output only raw response bytes")
 34	taskViewCmd.Flags().BoolVarP(&taskViewDetails, "details", "d", false, "Show only task details (no response)")
 35}
 36
 37// formatJSONForDisplay formats JSON with proper indentation for display
 38func formatJSONForDisplay(jsonStr, indent string) string {
 39	if jsonStr == "" {
 40		return ""
 41	}
 42
 43	// Try to parse and format as JSON
 44	var parsed interface{}
 45	if err := json.Unmarshal([]byte(jsonStr), &parsed); err != nil {
 46		// If it's not valid JSON, return as-is with indent
 47		return indent + jsonStr
 48	}
 49
 50	// Format with indentation
 51	formatted, err := json.MarshalIndent(parsed, indent, "  ")
 52	if err != nil {
 53		// If formatting fails, return original with indent
 54		return indent + jsonStr
 55	}
 56
 57	return string(formatted)
 58}
 59
 60func runTaskView(cmd *cobra.Command, args []string) error {
 61	if err := validateConfig(); err != nil {
 62		return err
 63	}
 64
 65	taskID, err := strconv.Atoi(args[0])
 66	if err != nil {
 67		return fmt.Errorf("invalid task ID: %s", args[0])
 68	}
 69
 70	client := mythic.NewClient(mythicURL, token, insecure, socksProxy)
 71	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 72	defer cancel()
 73
 74	// Initialize cache
 75	taskCache, err := cache.New(mythicURL)
 76	if err != nil {
 77		// If cache initialization fails, continue without caching
 78		taskCache = nil
 79	}
 80
 81	var task *mythic.Task
 82
 83	// Try to get from cache first if available
 84	if taskCache != nil {
 85		if cachedTask, found := taskCache.GetCachedTask(taskID, mythicURL); found {
 86			task = cachedTask
 87		}
 88	}
 89
 90	// If not in cache, fetch from server
 91	if task == nil {
 92		task, err = client.GetTaskResponse(ctx, taskID)
 93		if err != nil {
 94			return fmt.Errorf("failed to get task %d: %w", taskID, err)
 95		}
 96
 97		// Cache the result if task is completed and cache is available
 98		if taskCache != nil {
 99			taskCache.CacheTask(task, mythicURL)
100		}
101	}
102
103	// If raw output requested, only print the response
104	if taskViewRawOutput {
105		if task.Response != "" {
106			fmt.Print(task.Response)
107			// Ensure output ends with a newline
108			if !strings.HasSuffix(task.Response, "\n") {
109				fmt.Print("\n")
110			}
111		}
112		return nil
113	}
114
115	// Get callback information for enhanced details
116	var callback *mythic.Callback
117	if task.CallbackID > 0 {
118		callback, _ = api.FindActiveCallback(ctx, client, task.CallbackID)
119	}
120
121	// Show enhanced task details header
122	fmt.Printf("Task Details (ID: %d)\n", task.DisplayID)
123	fmt.Println(strings.Repeat("=", 60))
124
125	// Basic task info
126	if task.Command != "" {
127		fmt.Printf("Command:     %s\n", task.Command)
128	} else {
129		fmt.Printf("Command:     (not available)\n")
130	}
131	fmt.Printf("Status:      %s\n", task.Status)
132	fmt.Printf("Completed:   %t\n", task.Completed)
133
134	// Operator and agent info
135	if task.Operator.Username != "" {
136		fmt.Printf("Operator:    %s\n", task.Operator.Username)
137	}
138	if task.AgentTaskID != "" {
139		fmt.Printf("Agent Task ID: %s\n", task.AgentTaskID)
140	}
141
142	// Parameters section with proper JSON formatting
143	hasParams := task.OriginalParams != "" || task.DisplayParams != "" || task.Params != ""
144	if hasParams {
145		fmt.Printf("\nAgent Parameters:\n")
146
147		// Show original parameters (JSON) if available
148		if task.OriginalParams != "" {
149			fmt.Printf("  Original (JSON):\n    %s\n", task.OriginalParams)
150		}
151
152		// Show display parameters if different from original
153		if task.DisplayParams != "" && task.DisplayParams != task.OriginalParams {
154			fmt.Printf("  Display Format:\n    %s\n", task.DisplayParams)
155		}
156
157		// Show processed parameters if different from both above
158		if task.Params != "" && task.Params != task.DisplayParams && task.Params != task.OriginalParams {
159			fmt.Printf("  Processed:\n    %s\n", task.Params)
160		}
161
162		// If only basic params available, show those
163		if task.OriginalParams == "" && task.DisplayParams == "" && task.Params != "" {
164			fmt.Printf("  Parameters:\n    %s\n", task.Params)
165		}
166
167		// If no parameters at all
168		if !hasParams {
169			fmt.Printf("  (No parameters)\n")
170		}
171	}
172
173	// Task metadata
174	fmt.Printf("\nTask Metadata:\n")
175	if task.ParameterGroupName != "" {
176		fmt.Printf("  Parameter Group:  %s\n", task.ParameterGroupName)
177	}
178	if task.TaskingLocation != "" {
179		fmt.Printf("  Tasking Location: %s\n", task.TaskingLocation)
180	}
181	if task.IsInteractiveTask {
182		fmt.Printf("  Interactive Task: Yes (Type: %d)\n", task.InteractiveTaskType)
183	}
184	if task.ParentTaskID > 0 {
185		fmt.Printf("  Parent Task:     %d\n", task.ParentTaskID)
186	}
187	if task.Token.TokenID > 0 {
188		fmt.Printf("  Token ID:        %d\n", task.Token.TokenID)
189	}
190
191	// Callback information
192	fmt.Printf("\nCallback Information:\n")
193	if callback != nil {
194		fmt.Printf("  Callback ID:     %d\n", task.CallbackID)
195		fmt.Printf("  Host/User:       %s@%s\n", callback.User, callback.Host)
196		fmt.Printf("  Process:         %s (PID: %d)\n", callback.ProcessName, callback.PID)
197		if callback.Description != "" {
198			fmt.Printf("  Description:     %s\n", callback.Description)
199		}
200		agentType := callback.Payload.PayloadType.Name
201		if agentType != "" {
202			fmt.Printf("  Agent Type:      %s\n", agentType)
203		}
204	} else {
205		fmt.Printf("  Callback ID:     %d\n", task.CallbackID)
206	}
207
208	// Timing information
209	fmt.Printf("\nTiming:\n")
210	if task.Timestamp != "" {
211		if t, err := time.Parse(time.RFC3339, task.Timestamp); err == nil {
212			fmt.Printf("  Created:         %s\n", t.Format("2006-01-02 15:04:05 MST"))
213		} else {
214			fmt.Printf("  Created:         %s\n", task.Timestamp)
215		}
216	}
217
218	// Technical details
219	fmt.Printf("\nTechnical Details:\n")
220	fmt.Printf("  Internal ID:     %d\n", task.ID)
221
222	// If --details flag is set, only show the header
223	if taskViewDetails {
224		return nil
225	}
226
227	// Show response section
228	fmt.Println("\nResponse:")
229	fmt.Println(strings.Repeat("-", 40))
230	if task.Response != "" {
231		fmt.Println(task.Response)
232	} else {
233		fmt.Println("(No response yet)")
234	}
235
236	return nil
237}