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
|
// Command mitmuxd is the mitmux headless proxy daemon. It owns the
// listening socket and the history database; a TUI or other client
// attaches separately, over a Unix control socket, without interrupting
// capture.
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"mitmux/internal/ca"
"mitmux/internal/ipc"
"mitmux/internal/proxy"
"mitmux/internal/store"
"mitmux/internal/version"
)
func main() {
listen := flag.String("listen", "127.0.0.1:8080", "proxy listen address(es) - comma-separated for more than one, e.g. \"127.0.0.1:8080,127.0.0.1:8081\"")
caDir := flag.String("ca-dir", "", "directory for CA cert/key and history db (default: XDG config dir)")
dbPath := flag.String("db", "", "path to history database (default: <ca-dir>/history.db)")
socketPath := flag.String("socket", "", "control socket path (default: $XDG_RUNTIME_DIR/mitmux.sock, else <ca-dir>/mitmux.sock)")
installCA := flag.Bool("install-ca", false, "generate the CA if needed, print OS-specific trust-store install steps, and exit (doesn't start the proxy)")
upstreamProxy := flag.String("upstream-proxy", "", "chain all outbound connections through this proxy instead of dialing origins directly - an HTTP CONNECT proxy (host:port, optional http:// prefix) or a SOCKS5 proxy (socks5://[user:pass@]host:port)")
showVersion := flag.Bool("version", false, "print version and exit")
flag.Parse()
if *showVersion {
fmt.Println(version.String("mitmuxd"))
return
}
var listenAddrs []string
for _, a := range strings.Split(*listen, ",") {
if a = strings.TrimSpace(a); a != "" {
listenAddrs = append(listenAddrs, a)
}
}
if len(listenAddrs) == 0 {
log.Fatalf("-listen: no addresses given")
}
upstream := strings.TrimPrefix(strings.TrimSpace(*upstreamProxy), "http://")
dir := *caDir
if dir == "" {
d, err := ca.Dir()
if err != nil {
log.Fatalf("resolve CA dir: %v", err)
}
dir = d
}
root, err := ca.EnsureCA(dir)
if err != nil {
log.Fatalf("load CA: %v", err)
}
if *installCA {
fmt.Printf("CA certificate: %s\n\n", ca.CertPath(dir))
fmt.Print(ca.InstallInstructions(runtime.GOOS, ca.CertPath(dir)))
return
}
log.Printf("CA ready: %s (install %s/ca.pem in your client's trust store to avoid TLS warnings, or run 'mitmuxd -install-ca' for OS-specific steps)", root.Cert.Subject.CommonName, dir)
dbFile := *dbPath
if dbFile == "" {
dbFile = filepath.Join(dir, "history.db")
}
db, err := store.Open(dbFile)
if err != nil {
log.Fatalf("open history db: %v", err)
}
defer db.Close()
log.Printf("history db: %s", dbFile)
sockFile := *socketPath
if sockFile == "" {
if rt := os.Getenv("XDG_RUNTIME_DIR"); rt != "" {
sockFile = filepath.Join(rt, "mitmux.sock")
} else {
sockFile = filepath.Join(dir, "mitmux.sock")
}
}
os.Remove(sockFile) // stale socket from an unclean shutdown
sockLn, err := net.Listen("unix", sockFile)
if err != nil {
log.Fatalf("listen on control socket %s: %v", sockFile, err)
}
defer os.Remove(sockFile)
log.Printf("control socket: %s", sockFile)
srv := proxy.New(listenAddrs, root, db, upstream)
if upstream != "" {
log.Printf("chaining outbound connections through upstream proxy %s", upstream)
}
hub := ipc.NewHub()
srv.OnEntry = ipc.LogAndBroadcast(hub)
ipcSrv := ipc.NewServer(db, hub, srv, strings.Join(listenAddrs, ", "))
go func() {
if err := ipcSrv.Serve(sockLn); err != nil {
log.Printf("control socket: %v", err)
}
}()
errCh := make(chan error, 1)
go func() {
errCh <- srv.ListenAndServe()
}()
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
select {
case err := <-errCh:
if err != nil {
log.Fatalf("proxy: %v", err)
}
case sig := <-sigCh:
log.Printf("received %s, shutting down", sig)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("shutdown: %v", err)
}
sockLn.Close()
}
}
|