Files
psycep ce5ca7f159 module: rename to github.com/mandiant/gopacket
Fixes #6. The module was declared as `module gopacket` in go.mod,
which is not a canonical import path. External projects could not
`go get github.com/mandiant/gopacket` to use any of the 24
protocol packages as a library; the only workaround was to clone
the repo and add a `replace` directive to their own go.mod.

This commit:
- Sets `module github.com/mandiant/gopacket` in go.mod.
- Rewrites 434 import statements across 157 files from the bare
  `gopacket/...` prefix to `github.com/mandiant/gopacket/...`.

Pure mechanical change, no behavior difference. Makes the README's
library story (pkg/ directory, 24 reusable packages) actually
usable from external code.
2026-04-22 10:27:18 -05:00

154 lines
4.4 KiB
Go

// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"fmt"
"log"
"os"
"strconv"
"time"
"github.com/mandiant/gopacket/pkg/flags"
"github.com/mandiant/gopacket/pkg/ldap"
"github.com/mandiant/gopacket/pkg/session"
)
var (
requestUser = flag.String("user", "", "Requests data for specific user")
allUsers = flag.Bool("all", false, "Return all users, including those with no email addresses and disabled accounts")
)
func main() {
opts := flags.Parse()
if opts.TargetStr == "" {
flag.Usage()
os.Exit(1)
}
target, creds, err := session.ParseTargetString(opts.TargetStr)
if err != nil {
log.Fatalf("[-] Error parsing target string: %v", err)
}
opts.ApplyToSession(&target, &creds)
if !opts.NoPass {
if err := session.EnsurePassword(&creds); err != nil {
log.Fatal(err)
}
}
// Initialize Client
client := ldap.NewClient(target, &creds)
defer client.Close()
// Connect
fmt.Printf("[*] Connecting to %s...\n", target.Addr())
if err := client.Connect(false); err != nil {
log.Fatalf("[-] Connection failed: %v", err)
}
// Login
if creds.Domain != "" && creds.Hash == "" && !creds.UseKerberos && os.Getenv("GOPACKET_NO_UPN") == "" {
creds.Username = fmt.Sprintf("%s@%s", creds.Username, creds.Domain)
creds.Domain = ""
}
fmt.Printf("[*] Binding as %s...\n", creds.Username)
if err := client.Login(); err != nil {
log.Fatalf("[-] Bind failed: %v", err)
}
fmt.Println("[+] Bind successful.")
// Get Domain Context
baseDN, err := client.GetDefaultNamingContext()
if err != nil {
log.Fatalf("[-] Failed to get Naming Context: %v", err)
}
fmt.Printf("[+] Found BaseDN: %s\n", baseDN)
// Build search filter (matching Impacket's GetADUsers.py)
var filter string
if *allUsers {
// -all: return all users including those with no email and disabled accounts
filter = "(&(sAMAccountName=*)(objectCategory=user)"
} else {
// Default: only users with email, exclude disabled accounts
// UF_ACCOUNTDISABLE = 0x2
filter = "(&(sAMAccountName=*)(mail=*)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))"
}
if *requestUser != "" {
filter += fmt.Sprintf("(sAMAccountName:=%s))", *requestUser)
} else {
filter += ")"
}
attributes := []string{"sAMAccountName", "pwdLastSet", "mail", "lastLogon"}
results, err := client.Search(baseDN, filter, attributes)
if err != nil {
log.Fatalf("[-] Search failed: %v", err)
}
// Print table header (matching Impacket format)
colLen := []int{20, 30, 19, 19}
header := []string{"Name", "Email", "PasswordLastSet", "LastLogon"}
fmt.Printf("%-*s %-*s %-*s %-*s\n", colLen[0], header[0], colLen[1], header[1], colLen[2], header[2], colLen[3], header[3])
for i, w := range colLen {
if i > 0 {
fmt.Print(" ")
}
for j := 0; j < w; j++ {
fmt.Print("-")
}
}
fmt.Println()
for _, entry := range results.Entries {
name := entry.GetAttributeValue("sAMAccountName")
// Skip machine accounts (end with $)
if len(name) > 0 && name[len(name)-1] == '$' {
continue
}
mail := entry.GetAttributeValue("mail")
pwdLastSet := formatFileTime(entry.GetAttributeValue("pwdLastSet"))
lastLogon := formatFileTime(entry.GetAttributeValue("lastLogon"))
fmt.Printf("%-*s %-*s %-*s %-*s\n", colLen[0], name, colLen[1], mail, colLen[2], pwdLastSet, colLen[3], lastLogon)
}
}
// formatFileTime converts a Windows FILETIME string (100-nanosecond intervals
// since 1601-01-01) to a human-readable timestamp, matching Impacket's getUnixTime().
func formatFileTime(s string) string {
if s == "" || s == "0" {
return "<never>"
}
ft, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return "N/A"
}
// Convert Windows FILETIME to Unix timestamp:
// Subtract the epoch difference (1601-01-01 to 1970-01-01) in 100ns intervals,
// then convert to seconds.
const epochDiff = 116444736000000000
unixNano := (ft - epochDiff) * 100
t := time.Unix(0, unixNano)
return t.Format("2006-01-02 15:04:05")
}