224 lines
6.3 KiB
Go
224 lines
6.3 KiB
Go
// Copyright (C) 2026 Mark Robillard Jr (MARKMENTAL)
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License v3. See LICENSE.
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
// Command compile is the build driver for phisherslapper. It cross-compiles
|
|
// ./cmd/phisherslapper for the requested platform/arch matrix:
|
|
//
|
|
// go run ./compile.go --platform=linux --arch=amd64 --static=true
|
|
//
|
|
// Static builds (the default) are CGO-free so every target compiles
|
|
// without a cross C toolchain. Dynamic builds (--static=false) enable
|
|
// CGO and link libc dynamically; they require a C compiler for the
|
|
// target (gcc for host builds, or CC set to a cross compiler).
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
// platformAliases maps friendly platform names to GOOS values.
|
|
// "bsd" fans out to freebsd, openbsd, and netbsd.
|
|
var platformAliases = map[string][]string{
|
|
"linux": {"linux"},
|
|
"darwin": {"darwin"},
|
|
"mac": {"darwin"},
|
|
"macos": {"darwin"},
|
|
"osx": {"darwin"},
|
|
"windows": {"windows"},
|
|
"win": {"windows"},
|
|
"bsd": {"freebsd", "openbsd", "netbsd"},
|
|
"freebsd": {"freebsd"},
|
|
"openbsd": {"openbsd"},
|
|
"netbsd": {"netbsd"},
|
|
}
|
|
|
|
// archAlias resolves a friendly arch name to GOARCH plus optional GOARM.
|
|
type archAlias struct {
|
|
goarch string
|
|
goarm string
|
|
}
|
|
|
|
var archAliases = map[string]archAlias{
|
|
"amd64": {goarch: "amd64"},
|
|
"x64": {goarch: "amd64"},
|
|
"x86-64": {goarch: "amd64"},
|
|
"386": {goarch: "386"},
|
|
"x86": {goarch: "386"},
|
|
"i386": {goarch: "386"},
|
|
"32bit": {goarch: "386"},
|
|
"arm64": {goarch: "arm64"},
|
|
"aarch64": {goarch: "arm64"},
|
|
"arm": {goarch: "arm", goarm: "7"},
|
|
"armv7": {goarch: "arm", goarm: "7"},
|
|
"armv6": {goarch: "arm", goarm: "6"},
|
|
"armv5": {goarch: "arm", goarm: "5"},
|
|
}
|
|
|
|
// target is one GOOS/GOARCH(/GOARM) build.
|
|
type target struct {
|
|
goos string
|
|
goarch string
|
|
goarm string
|
|
}
|
|
|
|
func resolvePlatforms(raw string) ([]string, error) {
|
|
gooses, ok := platformAliases[strings.ToLower(strings.TrimSpace(raw))]
|
|
if !ok {
|
|
names := make([]string, 0, len(platformAliases))
|
|
for name := range platformAliases {
|
|
names = append(names, name)
|
|
}
|
|
return nil, fmt.Errorf("unknown platform %q (valid: %s)", raw, strings.Join(names, ", "))
|
|
}
|
|
return gooses, nil
|
|
}
|
|
|
|
func resolveArch(raw string) (archAlias, error) {
|
|
name := strings.ToLower(strings.TrimSpace(raw))
|
|
if a, ok := archAliases[name]; ok {
|
|
return a, nil
|
|
}
|
|
return archAlias{}, fmt.Errorf("unknown arch %q (valid examples: amd64, x64, arm64, aarch64, 386, x86, arm, armv7)", raw)
|
|
}
|
|
|
|
// supported pairs "goos/goarch" from the local toolchain.
|
|
func supportedPairs() (map[string]bool, error) {
|
|
out, err := exec.Command("go", "tool", "dist", "list").Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot list toolchain targets: %w", err)
|
|
}
|
|
pairs := make(map[string]bool)
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
if line = strings.TrimSpace(line); line != "" {
|
|
pairs[line] = true
|
|
}
|
|
}
|
|
return pairs, nil
|
|
}
|
|
|
|
func outputName(t target) string {
|
|
name := fmt.Sprintf("phisherslapper-%s-%s", t.goos, t.goarch)
|
|
if t.goarm != "" {
|
|
name += "v" + t.goarm
|
|
}
|
|
if t.goos == "windows" {
|
|
name += ".exe"
|
|
}
|
|
return name
|
|
}
|
|
|
|
// isHost reports whether t matches the machine running compile.go.
|
|
func (t target) isHost() bool {
|
|
if t.goos != runtime.GOOS || t.goarch != runtime.GOARCH {
|
|
return false
|
|
}
|
|
if t.goarch == "arm" {
|
|
return t.goarm == goarmHost()
|
|
}
|
|
return true
|
|
}
|
|
|
|
// goarmHost reads the host GOARM (empty unless arm, where it defaults to 7).
|
|
func goarmHost() string {
|
|
if arm := os.Getenv("GOARM"); arm != "" {
|
|
return arm
|
|
}
|
|
return "7"
|
|
}
|
|
|
|
func build(t target, cgo bool) error {
|
|
// Both modes strip symbol tables and DWARF via the compiler; the
|
|
// linkage (static vs dynamic) comes from CGO_ENABLED alone.
|
|
args := []string{"build", `-ldflags=-s -w`}
|
|
cgoEnabled := "0"
|
|
if cgo {
|
|
cgoEnabled = "1"
|
|
}
|
|
args = append(args, "-o", outputName(t), "./cmd/phisherslapper")
|
|
|
|
cmd := exec.Command("go", args...)
|
|
cmd.Env = append(os.Environ(),
|
|
"GOOS="+t.goos,
|
|
"GOARCH="+t.goarch,
|
|
"CGO_ENABLED="+cgoEnabled,
|
|
)
|
|
if t.goarm != "" {
|
|
cmd.Env = append(cmd.Env, "GOARM="+t.goarm)
|
|
}
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
fmt.Printf("[*] GOOS=%s GOARCH=%s CGO_ENABLED=%s", t.goos, t.goarch, cgoEnabled)
|
|
if t.goarm != "" {
|
|
fmt.Printf(" GOARM=%s", t.goarm)
|
|
}
|
|
fmt.Printf(" -> %s\n", outputName(t))
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("build %s/%s failed: %w", t.goos, t.goarch, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func run() int {
|
|
platformFlag := flag.String("platform", "linux", "target OS: linux, mac/darwin, windows/win, bsd (freebsd+openbsd+netbsd), or a single BSD")
|
|
archFlag := flag.String("arch", "amd64", "target arch: amd64/x64, arm64/aarch64, 386/x86, arm/armv7, ...")
|
|
staticFlag := flag.Bool("static", true, "true for static (CGO_ENABLED=0), false for dynamic (CGO_ENABLED=1, host builds); both stripped via -ldflags")
|
|
flag.Parse()
|
|
|
|
gooses, err := resolvePlatforms(*platformFlag)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "[!] Error: %v.\n", err)
|
|
return 1
|
|
}
|
|
arch, err := resolveArch(*archFlag)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "[!] Error: %v.\n", err)
|
|
return 1
|
|
}
|
|
pairs, err := supportedPairs()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "[!] Error: %v.\n", err)
|
|
return 1
|
|
}
|
|
|
|
var targets []target
|
|
for _, goos := range gooses {
|
|
if !pairs[goos+"/"+arch.goarch] {
|
|
fmt.Fprintf(os.Stderr, "[!] Error: target %s/%s is not supported by this toolchain.\n", goos, arch.goarch)
|
|
return 1
|
|
}
|
|
targets = append(targets, target{goos: goos, goarch: arch.goarch, goarm: arch.goarm})
|
|
}
|
|
|
|
// Dynamic builds need a C compiler for the target: gcc covers host
|
|
// builds, while cross builds require CC set to a cross compiler.
|
|
if !*staticFlag && os.Getenv("CC") == "" {
|
|
for _, t := range targets {
|
|
if !t.isHost() {
|
|
fmt.Fprintf(os.Stderr, "[!] Error: --static=false needs CGO, which cannot cross-compile to %s/%s without a cross C compiler (set CC or use --static=true).\n", t.goos, t.goarch)
|
|
return 1
|
|
}
|
|
}
|
|
}
|
|
|
|
built := 0
|
|
for _, t := range targets {
|
|
if err := build(t, !*staticFlag); err != nil {
|
|
fmt.Fprintf(os.Stderr, "[!] Error: %v.\n", err)
|
|
return 1
|
|
}
|
|
built++
|
|
}
|
|
fmt.Printf("[+] Built %d of %d target(s).\n", built, len(targets))
|
|
return 0
|
|
}
|
|
|
|
func main() {
|
|
os.Exit(run())
|
|
}
|