-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkismet.go
424 lines (356 loc) · 12.4 KB
/
kismet.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"sync"
"time"
"github.com/spf13/viper"
)
const (
MinRSSI = -120 // Minimum RSSI value for progress bar
MaxRSSI = -20 // Maximum RSSI value for progress bar
)
var (
cachedUser string
cachedPassword string
credentialsErr error
once sync.Once // Ensures credentials are fetched only once
errDeviceNotFound = errors.New("device not found") // Error to match on
)
type DeviceInfo struct {
RSSI int // Signal strength
Channel string // Operating channel
Manufacturer string // Manufacturer of the device
SSID string // SSID of the device (if applicable)
Crypt string // Encryption type
Type string // Device type (AP, Client, etc.)
AssociatedClients map[string]string // Map of associated client MAC addresses
}
// API response structure
type KismetPayload struct {
Fields [][]string `json:"fields"`
}
// Function to fetch device info from the Kismet API and returns a *DeviceInfo object
func FetchDeviceInfo(mac string, kismetEndpoint string) (*DeviceInfo, error) {
postJson := KismetPayload{
Fields: [][]string{
{"kismet.device.base.macaddr", "base.macaddr"},
{"kismet.device.base.channel", "base.channel"},
{"kismet.device.base.signal/kismet.common.signal.last_signal", "RSSI"},
{"kismet.device.base.manuf", "Make"},
{"dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.ssid", "SSID"},
{"kismet.device.base.crypt", "Crypt"},
{"kismet.device.base.type", "Type"},
{"dot11.device/dot11.device.associated_client_map", "AssociatedClients"},
},
}
jsonData, err := json.Marshal(postJson)
if err != nil {
log.Printf("Error marshaling JSON: %v", err)
return nil, err
}
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
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
// Log the error but do not return it to the user
log.Printf("Error making request to Kismet API: %v", err)
return nil, nil // Return nil to indicate no data but no critical error
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
var devices []map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&devices); err != nil {
log.Printf("Error decoding response: %v", err)
return nil, err
}
for _, device := range devices {
// Check if the MAC address matches
if macAddr, ok := device["base.macaddr"].(string); ok && macAddr == mac {
deviceInfo := &DeviceInfo{
RSSI: MinRSSI, // Default RSSI value
Channel: "",
Manufacturer: "Unknown",
SSID: "Unknown",
Crypt: "Unknown",
Type: "Unknown",
AssociatedClients: map[string]string{},
}
// Extract fields
if rssiVal, ok := device["RSSI"].(float64); ok {
deviceInfo.RSSI = int(rssiVal)
}
if channelVal, ok := device["base.channel"].(string); ok {
deviceInfo.Channel = channelVal
}
if makeVal, ok := device["Make"].(string); ok {
deviceInfo.Manufacturer = makeVal
}
if ssidVal, ok := device["SSID"].(string); ok {
deviceInfo.SSID = ssidVal
}
if cryptVal, ok := device["Crypt"].(string); ok {
deviceInfo.Crypt = cryptVal
}
if typeVal, ok := device["Type"].(string); ok {
deviceInfo.Type = typeVal
}
// Extract associated clients (if any)
if associatedClientsVal, ok := device["AssociatedClients"].(map[string]interface{}); ok {
for clientMac, assoc := range associatedClientsVal {
deviceInfo.AssociatedClients[clientMac] = fmt.Sprintf("%v", assoc)
}
}
return deviceInfo, nil
}
}
}
return nil, errDeviceNotFound
}
// Finds a valid MAC or SSID and returns a MAC, channel, *TargetItem, error
func FindValidTarget(targets []*TargetItem, kismetEndpoint string) (string, string, *TargetItem, error) {
// Prepare the payload for Kismet API request
postJson := KismetPayload{
Fields: [][]string{
{"kismet.device.base.macaddr", "base.macaddr"},
{"kismet.device.base.channel", "base.channel"},
{"dot11.device/dot11.device.last_beaconed_ssid_record/dot11.advertisedssid.ssid", "SSID"},
},
}
// Marshal the payload to JSON
jsonData, err := json.Marshal(postJson)
if err != nil {
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", kismetEndpoint, bytes.NewBuffer(jsonData))
if err != nil {
return "", "", nil, fmt.Errorf("error creating request: %v", err)
}
// Send the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", "", nil, fmt.Errorf("error making request to Kismet API: %v", err)
}
defer resp.Body.Close()
// Check the response status code
if resp.StatusCode != http.StatusOK {
return "", "", nil, fmt.Errorf("kismet API returned status code %d", resp.StatusCode)
}
// Decode the response body
var devices []map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&devices); err != nil {
return "", "", nil, fmt.Errorf("error decoding response: %v", err)
}
// Iterate over targets
for _, target := range targets {
if target.IsIgnored() {
continue
}
// Iterate over devices
for _, device := range devices {
// Extract device fields
deviceMac, _ := device["base.macaddr"].(string)
deviceChannel, _ := device["base.channel"].(string)
// deviceSSID, _ := device["SSID"].(string)
if target.TType == MAC {
if deviceMac == target.Value {
return target.Value, deviceChannel, target, nil
}
} else if target.TType == SSID {
if ssidVal, ok := device["SSID"].(string); ok && ssidVal == target.Value {
macAddr, _ := device["base.macaddr"].(string)
channel, ok := device["base.channel"].(string)
if ok {
newTarget := target // Create a copy of the target
newTarget.OriginalValue = target.Value // Store the original SSID
newTarget.TType = SSID
newTarget.Value = macAddr // Set the value to the MAC address
return macAddr, channel, newTarget, nil
}
}
}
}
}
// No valid target found
return "", "", nil, nil
}
// Function to lazily pull credentials and store them in global variables so we're not unnecessarily pulling them for every api query.
func getCachedCredentials() (string, string, error) {
once.Do(func() {
cachedUser, cachedPassword, credentialsErr = getCredentials()
})
return cachedUser, cachedPassword, credentialsErr
}
// Function to get credentials from configuration
func getCredentials() (string, string, error) {
user := viper.GetString("credentials.user")
password := viper.GetString("credentials.password")
if user == "" || password == "" {
return "", "", fmt.Errorf("user or password not provided in the configuration")
}
return user, password, nil
}
// Launch Kismet automatically without user interaction
func LaunchKismet(ifaces []string) (*exec.Cmd, error) {
log.Println("Launching Kismet...")
// Initialize the arguments with kismet command
args := []string{}
// Add -c before each interface
for _, iface := range ifaces {
args = append(args, "-c", iface)
}
// Create the command with the dynamically built args
cmd := exec.Command("kismet", args...)
// Redirecting stdout and stderr to /dev/null to suppress output
cmd.Stdout = nil
cmd.Stderr = nil
if err := cmd.Start(); err != nil {
return cmd, fmt.Errorf("failed to start Kismet: %v", err)
}
log.Println("Kismet launched successfully")
return cmd, nil
}
// Function to create an HTTP request with credentials
func CreateRequest(method, url string, body io.Reader) (*http.Request, error) {
user, password, err := getCachedCredentials()
if err != nil {
return nil, err
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, fmt.Errorf("failed to create request: %v", err)
}
q := req.URL.Query()
q.Add("user", user)
q.Add("password", password)
req.URL.RawQuery = q.Encode()
req.Header.Set("Content-Type", "application/json")
return req, nil
}
// Function to get UUID for a specific interface
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
}
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Do(req)
if err != nil {
log.Printf("Error getting data sources: %v", err)
return "", fmt.Errorf("failed to get data sources: %v", err)
}
defer resp.Body.Close()
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))
}
body, _ := io.ReadAll(resp.Body)
var sources []map[string]interface{}
if err := json.Unmarshal(body, &sources); err != nil {
log.Printf("Error decoding JSON: %v", err)
return "", fmt.Errorf("failed to decode JSON: %v", err)
}
for _, source := range sources {
if source["kismet.datasource.interface"] == interfaceName {
if uuid, ok := source["kismet.datasource.uuid"].(string); ok {
return uuid, nil
}
}
}
return "", fmt.Errorf("UUID not found for interface %s", interfaceName)
}
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", kismetEndpoint, nil)
if err != nil {
log.Printf("Failed to create request: %v", err)
return fmt.Errorf("failed to create request: %v", err)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Printf("Failed to send request: %v", err)
return fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
log.Printf("Failed to unlock channel: %s", string(body))
return fmt.Errorf("failed to unlock channel: %s", string(body))
}
return nil
}
// Function to lock the channel for a specific interface 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)
if err != nil {
log.Printf("Failed to marshal JSON: %v", err)
return fmt.Errorf("failed to marshal JSON: %v", err)
}
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)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Printf("Failed to send request: %v", err)
return fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
log.Printf("Failed to lock channel: %s", string(body))
return fmt.Errorf("failed to lock channel: %s", string(body))
}
return nil
}
// Fetches all device data from the Kismet API
func FetchAllDevices(kismetEndpoint string) ([]map[string]interface{}, error) {
kismetEndpoint = fmt.Sprintf("http://%s/devices/last-time/-5/devices.json", kismetEndpoint)
// Use CreateRequest instead of http.NewRequest to include authentication
req, err := CreateRequest("GET", kismetEndpoint, nil)
if err != nil {
log.Printf("Error creating request: %v", err)
return nil, err
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Printf("Error making request to Kismet API: %v", err)
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
bodyString := string(bodyBytes)
return nil, fmt.Errorf("kismet API returned status code %d: %s", resp.StatusCode, bodyString)
}
var devices []map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&devices); err != nil {
log.Printf("Error decoding response: %v", err)
return nil, err
}
return devices, nil
}