Skip to content

Commit

Permalink
feat: Add user-configurable Kismet endpoint and launch options
Browse files Browse the repository at this point in the history
- Add --kismet-endpoint flag to specify custom Kismet server address
- Implement --skip-kismet flag to use existing Kismet instance
- Refactor KismetClient to use configurable endpoint
- Update documentation and usage instructions

This change allows users to connect to remote Kismet instances
and gives flexibility to use pre-existing Kismet processes,
improving usability in various network setups.
  • Loading branch information
GobiasSomeCoffeeCo committed Oct 12, 2024
1 parent cda89ea commit 7a5ceb8
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 33 deletions.
31 changes: 19 additions & 12 deletions kismet.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"log"
"net/http"
"os"
"os/exec"
"sync"
"time"
Expand Down Expand Up @@ -45,7 +46,7 @@ type KismetPayload struct {
}

// Function to fetch device info from Kismet
func FetchDeviceInfo(mac string) (*DeviceInfo, error) {
func FetchDeviceInfo(mac string, kismetEndpoint string) (*DeviceInfo, error) {
postJson := KismetPayload{
Fields: [][]string{
{"kismet.device.base.macaddr", "base.macaddr"},
Expand All @@ -65,7 +66,9 @@ func FetchDeviceInfo(mac string) (*DeviceInfo, error) {
return nil, err
}

req, err := CreateRequest("POST", "http://127.0.0.1:2501/devices/last-time/-5/devices.json", bytes.NewBuffer(jsonData))
kismetEndpoint = fmt.Sprintf("http://%s/devices/last-time/-5/devices.json", kismetEndpoint)

req, err := CreateRequest("POST", kismetEndpoint, bytes.NewBuffer(jsonData))
if err != nil {
log.Printf("Error creating request: %v", err)
return nil, err
Expand Down Expand Up @@ -134,7 +137,7 @@ func FetchDeviceInfo(mac string) (*DeviceInfo, error) {
return nil, errDeviceNotFound
}

func FindValidTarget(targets []*TargetItem) (string, string, *TargetItem, error) {
func FindValidTarget(targets []*TargetItem, kismetEndpoint string) (string, string, *TargetItem, error) {
// Prepare the payload for Kismet API request
postJson := KismetPayload{
Fields: [][]string{
Expand All @@ -150,8 +153,10 @@ func FindValidTarget(targets []*TargetItem) (string, string, *TargetItem, error)
return "", "", nil, fmt.Errorf("error marshaling JSON: %v", err)
}

kismetEndpoint = fmt.Sprintf("http://%s/devices/last-time/-5/devices.json", kismetEndpoint)

// Create the HTTP POST request
req, err := CreateRequest("POST", "http://127.0.0.1:2501/devices/last-time/-5/devices.json", bytes.NewBuffer(jsonData))
req, err := CreateRequest("POST", kismetEndpoint, bytes.NewBuffer(jsonData))
if err != nil {
return "", "", nil, fmt.Errorf("error creating request: %v", err)
}
Expand Down Expand Up @@ -281,8 +286,9 @@ func CreateRequest(method, url string, body io.Reader) (*http.Request, error) {
}

// Function to get UUID for a specific interface
func GetUUIDForInterface(interfaceName string) (string, error) {
req, err := CreateRequest("GET", "http://127.0.0.1:2501/datasource/all_sources.json", nil)
func GetUUIDForInterface(interfaceName string, kismetEndpoint string) (string, error) {
kismetEndpoint = fmt.Sprintf("http://%s/datasource/all_sources.json", kismetEndpoint)
req, err := CreateRequest("GET", kismetEndpoint, nil)
if err != nil {
return "", err
}
Expand All @@ -298,6 +304,7 @@ func GetUUIDForInterface(interfaceName string) (string, error) {
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
log.Printf("Failed to get data sources: %s", string(body))
os.Exit(1)
return "", fmt.Errorf("failed to get data sources: %s", string(body))
}

Expand All @@ -320,10 +327,10 @@ func GetUUIDForInterface(interfaceName string) (string, error) {
return "", fmt.Errorf("UUID not found for interface %s", interfaceName)
}

func hopChannel(uuid string) error {
url := fmt.Sprintf("http://127.0.0.1:2501/datasource/by-uuid/%s/set_hop.cmd", uuid)
func hopChannel(uuid string, kismetEndpoint string) error {
kismetEndpoint = fmt.Sprintf("http://%s/datasource/by-uuid/%s/set_hop.cmd", kismetEndpoint, uuid)

req, err := CreateRequest("POST", url, nil)
req, err := CreateRequest("POST", kismetEndpoint, nil)
if err != nil {
log.Printf("Failed to create request: %v", err)
return fmt.Errorf("failed to create request: %v", err)
Expand All @@ -347,8 +354,8 @@ func hopChannel(uuid string) error {
}

// Function to lock the channel for a specific interface UUID
func lockChannel(uuid, channel string) error {
url := fmt.Sprintf("http://127.0.0.1:2501/datasource/by-uuid/%s/set_channel.cmd", uuid)
func lockChannel(uuid, channel, kismetEndpoint string) error {
kismetEndpoint = fmt.Sprintf("http://%s/datasource/by-uuid/%s/set_channel.cmd", kismetEndpoint, uuid)

payload := map[string]string{"channel": channel}
jsonData, err := json.Marshal(payload)
Expand All @@ -357,7 +364,7 @@ func lockChannel(uuid, channel string) error {
return fmt.Errorf("failed to marshal JSON: %v", err)
}

req, err := CreateRequest("POST", url, bytes.NewBuffer(jsonData))
req, err := CreateRequest("POST", kismetEndpoint, bytes.NewBuffer(jsonData))
if err != nil {
log.Printf("Failed to create request: %v", err)
return fmt.Errorf("failed to create request: %v", err)
Expand Down
40 changes: 29 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"log"
"os"
"regexp"
"strings"
Expand All @@ -15,15 +16,12 @@ import (
)

func formatMAC(mac string) (string, error) {
// Remove all non-hexadecimal characters
cleanMAC := regexp.MustCompile(`[^0-9A-Fa-f]`).ReplaceAllString(mac, "")

// Check if the cleaned MAC has exactly 12 characters
if len(cleanMAC) != 12 {
return "", fmt.Errorf("invalid MAC address: %s", mac)
}

// Format the MAC address
formattedMAC := strings.ToUpper(fmt.Sprintf("%s:%s:%s:%s:%s:%s",
cleanMAC[0:2], cleanMAC[2:4], cleanMAC[4:6],
cleanMAC[6:8], cleanMAC[8:10], cleanMAC[10:12]))
Expand All @@ -41,6 +39,8 @@ func main() {
pflag.StringSliceP("ssid", "s", []string{}, "SSID of the device(s)")
pflag.StringSliceP("interface", "i", []string{}, "Interface name")
pflag.StringP("config", "c", "", "Path to config file")
pflag.StringP("kismet-endpoint", "u", "127.0.0.1:2501", "Kismet server endpoint ip:port")
skipKismet := pflag.BoolP("skip-kismet", "k", false, "Skip launching Kismet (use if kismet is already running)")
pflag.Parse()

configPath := viper.GetString("config")
Expand All @@ -57,9 +57,21 @@ func main() {
os.Exit(1)
}

viper.BindPFlag("required.target_mac", pflag.Lookup("mac"))
viper.BindPFlag("required.interface", pflag.Lookup("interface"))
viper.BindPFlag("optional.target_ssid", pflag.Lookup("ssid"))
if err := viper.BindPFlag("required.target_mac", pflag.Lookup("mac")); err != nil {
log.Printf("Error in parsing MAC flag/config: %v", err)
}

if err := viper.BindPFlag("required.interface", pflag.Lookup("interface")); err != nil {
log.Printf("Error in parsing interface flag/config: %v", err)
}

if err := viper.BindPFlag("optional.kismet_endpoint", pflag.Lookup("kismet-endpoint")); err != nil {
log.Printf("Error in parsing kismet-endpoint flag/config: %v", err)
}

if err := viper.BindPFlag("optional.target_ssid", pflag.Lookup("ssid")); err != nil {
log.Printf("Error in parsing 'ssid' flag/config: %v", err)
}

// Read MACs and SSIDs from Viper
rawTargetMACs := viper.GetStringSlice("required.target_mac")
Expand Down Expand Up @@ -95,14 +107,20 @@ func main() {
ignoreList: []string{},
windowWidth: 80,
targetList: list.New([]list.Item{}, list.NewDefaultDelegate(), 40, 10),
kismetEndpoint: viper.GetString("optional.kismet_endpoint"),
}

kismet, err := LaunchKismet(m.iface)
if err != nil {
fmt.Println("Kismet couldn't launch. Check the interface.")
os.Exit(1)
if *skipKismet {
m.kismet = nil
} else {
kismet, err := LaunchKismet(m.iface)
if err != nil {
fmt.Println("Kismet couldn't launch. Please ensure Kimset is installed and in your $PATH.")
os.Exit(1)
}

m.kismet = kismet
}
m.kismet = kismet

time.Sleep(3 * time.Second)

Expand Down
28 changes: 18 additions & 10 deletions tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
"time"
Expand Down Expand Up @@ -38,13 +39,14 @@ type Model struct {
realTimeOutput []string
windowWidth int
targetList list.Model
kismetEndpoint string
}

func (m *Model) Init() tea.Cmd {
return tickCmd()
}

// Add a message to the real-time output, ensuring we only keep the last 25 messages
// Add a message to the real-time output, ensuring we only keep the last 7 messages
func (m *Model) addRealTimeOutput(message string) {
m.realTimeOutput = append(m.realTimeOutput, message)
if len(m.realTimeOutput) > 7 {
Expand All @@ -53,16 +55,22 @@ func (m *Model) addRealTimeOutput(message string) {
}

func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
uuid, err := GetUUIDForInterface(m.iface[0])
uuid, err := GetUUIDForInterface(m.iface[0], m.kismetEndpoint)
if err != nil {
log.Printf("Failed to get UUID: %v", err)
log.Printf("Failed to get UUID: %v\n\rPlease check your config and make sure your interface is correct.", err)
os.Exit(1)
}

switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
m.kismet.Process.Kill()
if m.kismet != nil {
err := m.kismet.Process.Kill()
if err != nil {
log.Printf("Unable to kill Kismet process. Please check if Kismet is still running.")
}
}
return m, tea.Quit
case "up", "k", "down", "j":
var cmd tea.Cmd
Expand All @@ -85,7 +93,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.lockedTarget.ChannelLocked = false
m.channelLocked = false

err := hopChannel(uuid)
err := hopChannel(uuid, m.kismetEndpoint)
if err != nil {
log.Printf("Error hopping channel: %v", err)
m.addRealTimeOutput(fmt.Sprintf("Error hopping channel: %v", err))
Expand Down Expand Up @@ -119,7 +127,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.addRealTimeOutput("Continuing search for new target...")
m.channelLocked = false
}
err := hopChannel(uuid)
err := hopChannel(uuid, m.kismetEndpoint)
if err != nil {
log.Printf("Error hopping channel: %v", err)
}
Expand All @@ -139,7 +147,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

case tickMsg:
if m.lockedTarget == nil {
value, channel, targetItem, _ := FindValidTarget(m.targets)
value, channel, targetItem, _ := FindValidTarget(m.targets, m.kismetEndpoint)
if value != "" {
m.lockedTarget = targetItem
m.channel = channel
Expand All @@ -149,7 +157,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

if m.lockedTarget != nil {
// Fetch dynamic info periodically
deviceInfo, err := FetchDeviceInfo(m.lockedTarget.Value)
deviceInfo, err := FetchDeviceInfo(m.lockedTarget.Value, m.kismetEndpoint)
if err != nil && err != errDeviceNotFound {
log.Printf("Error fetching device info: %v", err)
}
Expand All @@ -160,7 +168,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

// Lock the channel if not already locked
if !m.channelLocked {
if err := lockChannel(uuid, m.channel); err != nil {
if err := lockChannel(uuid, m.channel, m.kismetEndpoint); err != nil {
m.addRealTimeOutput(fmt.Sprintf("Failed to lock channel: %v", err))
} else {
m.channelLocked = true
Expand Down Expand Up @@ -307,7 +315,7 @@ func (m *Model) renderRSSIOverTimeChart(width int) string {
builder.WriteString("┘\n")

return lipgloss.NewStyle().
Border(lipgloss.NormalBorder()).
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("63")).
Padding(1, 2).
Width(width - 4).
Expand Down

0 comments on commit 7a5ceb8

Please sign in to comment.