main.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. package main
  2. import (
  3. "bufio"
  4. "bytes"
  5. "crypto/sha1"
  6. "encoding/csv"
  7. "encoding/json"
  8. "flag"
  9. "fmt"
  10. "io"
  11. "log"
  12. "math/rand"
  13. "net"
  14. "net/http"
  15. "os"
  16. "strings"
  17. "time"
  18. "github.com/BurntSushi/toml"
  19. "github.com/google/gopacket"
  20. "github.com/google/gopacket/layers"
  21. "github.com/google/gopacket/pcap"
  22. "github.com/nats-io/nats"
  23. "github.com/nats-io/nats/encoders/protobuf"
  24. "git.scraperwall.com/scw/ajp13"
  25. "git.scraperwall.com/scw/data"
  26. "git.scraperwall.com/scw/ip"
  27. )
  28. var (
  29. doLiveCapture = flag.Bool("live", false, "Capture data in real time from a given interface")
  30. iface = flag.String("interface", "eth0", "Interface to get packets from")
  31. snapshotLen = flag.Int("snapshot-len", 8192, "Snapshot Length in Bytes")
  32. filter = flag.String("filter", "tcp", "PCAP filter expression")
  33. promiscuous = flag.Bool("promiscuous", false, "Switch interface into promiscuous mode?")
  34. natsURL = flag.String("nats-url", "nats://127.0.0.1:4222", "The URL of the NATS server")
  35. natsUser = flag.String("nats-user", "", "The user for NATS authentication")
  36. natsPassword = flag.String("nats-password", "", "The password for NATS authentication")
  37. natsQueue = flag.String("nats-queue", "requests", "The NATS queue name")
  38. natsCA = flag.String("nats-ca", "", "CA chain for NATS TLS")
  39. sleepFor = flag.Duration("sleep", 0, "Sleep this long between sending data (only when replaying a file)")
  40. requestsFile = flag.String("requests", "", "CSV file containing requests (IP and URL)")
  41. protocol = flag.String("protocol", "http", "which protocol to parse: http or ajp13")
  42. useXForwardedAsSource = flag.Bool("use-x-forwarded", false, "Use the IP address in X-Forwarded-For as source")
  43. trace = flag.Bool("trace", false, "Trace the packet capturing")
  44. apacheLog = flag.String("apache-log", "", "Parse an Apache Log file")
  45. apacheReplay = flag.String("apache-replay", "", "Apache log file to replay into the system")
  46. nginxLog = flag.String("nginx-log", "", "Nginx log file to tail")
  47. nginxFormat = flag.String("nginx-format", "", "The nginx log file format")
  48. nginxReplay = flag.String("nginx-replay", "", "Replay this nginx logfile")
  49. hostName = flag.String("hostname", "", "Override the captured hostname with this one")
  50. accessWatchKey = flag.String("access-watch-key", "", "access.watch API key")
  51. configFile = flag.String("config", "", "The location of the TOML config file")
  52. beQuiet = flag.Bool("quiet", true, "Be quiet")
  53. doVersion = flag.Bool("version", false, "Show version information")
  54. natsEC *nats.EncodedConn
  55. natsJSONEC *nats.EncodedConn
  56. natsErrorChan chan error
  57. natsIsAvailable bool
  58. count uint64
  59. timeout = -1 * time.Second
  60. ipPriv *ip.IP
  61. config Config
  62. // Version contains the program Version, e.g. 1.0.1
  63. Version string
  64. // BuildDate contains the date and time at which the program was compiled
  65. BuildDate string
  66. )
  67. // Config contains the program configuration
  68. type Config struct {
  69. Live bool
  70. Interface string
  71. SnapshotLen int
  72. Filter string
  73. Promiscuous bool
  74. NatsURL string
  75. NatsQueue string
  76. NatsUser string
  77. NatsPassword string
  78. NatsCA string
  79. SleepFor duration
  80. RequestsFile string
  81. UseXForwardedAsSource bool
  82. Quiet bool
  83. Protocol string
  84. Trace bool
  85. ApacheLog string
  86. ApacheReplay string
  87. NginxLog string
  88. NginxLogFormat string
  89. NginxReplay string
  90. HostName string
  91. AccessWatchKey string
  92. }
  93. type duration struct {
  94. time.Duration
  95. }
  96. func (d *duration) UnmarshalText(text []byte) error {
  97. var err error
  98. d.Duration, err = time.ParseDuration(string(text))
  99. return err
  100. }
  101. func (c Config) print() {
  102. fmt.Printf("Live: %t\n", c.Live)
  103. fmt.Printf("Interface: %s\n", c.Interface)
  104. fmt.Printf("SnapshotLen: %d\n", c.SnapshotLen)
  105. fmt.Printf("Filter: %s\n", c.Filter)
  106. fmt.Printf("Promiscuous: %t\n", c.Promiscuous)
  107. fmt.Printf("NatsURL: %s\n", c.NatsURL)
  108. fmt.Printf("NatsQueue: %s\n", c.NatsQueue)
  109. fmt.Printf("NatsUser: %s\n", c.NatsUser)
  110. fmt.Printf("NatsPassword: %s\n", c.NatsPassword)
  111. fmt.Printf("NatsCA: %s\n", c.NatsCA)
  112. fmt.Printf("SleepFor: %s\n", c.SleepFor.String())
  113. fmt.Printf("RequestsFile: %s\n", c.RequestsFile)
  114. fmt.Printf("Apache Log: %s\n", c.ApacheLog)
  115. fmt.Printf("Apache Replay: %s\n", c.ApacheReplay)
  116. fmt.Printf("Nginx Log: %s\n", c.NginxLog)
  117. fmt.Printf("Nginx Log Format: %s\n", c.NginxLogFormat)
  118. fmt.Printf("NginxReplay: %s\n", c.NginxReplay)
  119. fmt.Printf("HostName: %s\n", c.HostName)
  120. fmt.Printf("AccessWatchKey: %s\n", c.AccessWatchKey)
  121. fmt.Printf("UseXForwardedAsSource: %t\n", c.UseXForwardedAsSource)
  122. fmt.Printf("Protocol: %s\n", c.Protocol)
  123. fmt.Printf("Quiet: %t\n", c.Quiet)
  124. fmt.Printf("Trace: %t\n", c.Trace)
  125. }
  126. func init() {
  127. flag.Parse()
  128. nats.RegisterEncoder(protobuf.PROTOBUF_ENCODER, &protobuf.ProtobufEncoder{})
  129. }
  130. func main() {
  131. if *doVersion {
  132. version()
  133. os.Exit(0)
  134. }
  135. loadConfig()
  136. // Output how many requests per second were sent
  137. if !config.Quiet {
  138. go func(c *uint64) {
  139. for {
  140. fmt.Printf("%d requests per second\n", *c)
  141. *c = 0
  142. time.Sleep(time.Second)
  143. }
  144. }(&count)
  145. }
  146. // NATS
  147. //
  148. if config.NatsURL == "" && config.AccessWatchKey == "" {
  149. log.Fatal("No NATS URL specified (-nats-url)!")
  150. }
  151. natsIsAvailable = false
  152. natsErrorChan = make(chan error, 1)
  153. err := connectToNATS()
  154. if err != nil && config.AccessWatchKey == "" {
  155. log.Fatal(err)
  156. }
  157. go natsWatchdog(natsErrorChan)
  158. // What should I do?
  159. if config.RequestsFile != "" {
  160. replayFile()
  161. } else if config.ApacheReplay != "" {
  162. apacheLogReplay(config.ApacheReplay)
  163. } else if config.NginxReplay != "" {
  164. nginxLogReplay(config.NginxReplay, config.NginxLogFormat)
  165. } else if config.ApacheLog != "" {
  166. apacheLogCapture(config.ApacheLog)
  167. } else if config.Live {
  168. fmt.Printf("live capture (%s, %s) to %s\n", config.Interface, config.Filter, config.NatsURL)
  169. liveCapture()
  170. } else if config.NginxLog != "" && config.NginxLogFormat != "" {
  171. nginxLogCapture(config.NginxLog, config.NginxLogFormat)
  172. }
  173. }
  174. func natsWatchdog(closedChan chan error) {
  175. var lastError error
  176. for err := range closedChan {
  177. if lastError != err {
  178. lastError = err
  179. log.Println(err)
  180. }
  181. if err != nats.ErrConnectionClosed {
  182. continue
  183. }
  184. RECONNECT:
  185. for {
  186. log.Printf("Reconnecting to NATS at %s\n", *natsURL)
  187. err := connectToNATS()
  188. if err == nil {
  189. break RECONNECT
  190. }
  191. time.Sleep(1 * time.Second)
  192. }
  193. }
  194. }
  195. func connectToNATS() error {
  196. var natsConn *nats.Conn
  197. var err error
  198. if config.NatsUser != "" && config.NatsPassword != "" && config.NatsCA != "" {
  199. natsConn, err = nats.Connect(config.NatsURL, nats.UserInfo(config.NatsUser, config.NatsPassword), nats.RootCAs(config.NatsCA))
  200. } else {
  201. if config.NatsPassword != "" && config.NatsUser != "" {
  202. natsConn, err = nats.Connect(config.NatsURL, nats.UserInfo(config.NatsUser, config.NatsPassword))
  203. } else {
  204. natsConn, err = nats.Connect(config.NatsURL)
  205. }
  206. }
  207. if err != nil {
  208. return err
  209. }
  210. natsEC, err = nats.NewEncodedConn(natsConn, protobuf.PROTOBUF_ENCODER)
  211. if err != nil {
  212. return fmt.Errorf("Encoded Connection: %v", err)
  213. }
  214. natsJSONEC, err = nats.NewEncodedConn(natsConn, nats.JSON_ENCODER)
  215. if err != nil {
  216. return fmt.Errorf("Encoded Connection: %v", err)
  217. }
  218. natsIsAvailable = true
  219. return nil
  220. }
  221. func liveCapture() {
  222. ipPriv = ip.NewIP()
  223. // PCAP setup
  224. //
  225. handle, err := pcap.OpenLive(config.Interface, int32(config.SnapshotLen), config.Promiscuous, timeout)
  226. if err != nil {
  227. log.Fatal(err)
  228. }
  229. defer handle.Close()
  230. err = handle.SetBPFFilter(config.Filter)
  231. if err != nil {
  232. log.Fatal(err)
  233. }
  234. packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
  235. for packet := range packetSource.Packets() {
  236. go processPacket(packet)
  237. }
  238. }
  239. func writeLogToWatch(r *data.Request) {
  240. h := map[string]string{}
  241. if r.AcceptEncoding != "" {
  242. h["Accept-Encoding"] = r.AcceptEncoding
  243. }
  244. if r.Accept != "" {
  245. h["Accept"] = r.Accept
  246. }
  247. if r.AcceptLanguage != "" {
  248. h["Accept-Language"] = r.AcceptLanguage
  249. }
  250. if r.Cookie != "" {
  251. h["Cookie"] = r.Cookie
  252. }
  253. if r.Host != "" {
  254. h["Host"] = r.Host
  255. }
  256. if r.Referer != "" {
  257. h["Referer"] = r.Referer
  258. }
  259. if r.UserAgent != "" {
  260. h["User-Agent"] = r.UserAgent
  261. }
  262. if r.Via != "" {
  263. h["Via"] = r.Via
  264. }
  265. if r.XForwardedFor != "" {
  266. h["X-Forwarded-For"] = r.XForwardedFor
  267. }
  268. if r.XRequestedWith != "" {
  269. h["X-Requested-With"] = r.XRequestedWith
  270. }
  271. data := map[string]interface{}{
  272. "request": map[string]interface{}{
  273. "time": time.Unix(0, r.CreatedAt),
  274. "address": r.Source,
  275. "protocol": r.Protocol,
  276. "scheme": "https",
  277. "method": r.Method,
  278. "url": r.Url,
  279. "headers": h,
  280. },
  281. "response": map[string]interface{}{"status": "200"},
  282. }
  283. jdata, err := json.Marshal(data)
  284. client := &http.Client{}
  285. fmt.Println(string(jdata))
  286. buf := bytes.NewBuffer(jdata)
  287. req, err := http.NewRequest("POST", "https://log.access.watch/1.1/log", buf)
  288. req.Header.Add("Api-Key", config.AccessWatchKey)
  289. req.Header.Add("Accept", "application/json")
  290. req.Header.Add("Content-Type", "application/json")
  291. resp, err := client.Do(req)
  292. if err != nil {
  293. log.Println(err)
  294. }
  295. resp.Body.Close()
  296. }
  297. func publishRequest(queue string, request *data.Request) {
  298. if config.AccessWatchKey != "" {
  299. writeLogToWatch(request)
  300. return
  301. }
  302. if !natsIsAvailable {
  303. return
  304. }
  305. if err := natsEC.Publish(config.NatsQueue, request); err != nil {
  306. natsErrorChan <- err
  307. if err == nats.ErrConnectionClosed {
  308. natsIsAvailable = false
  309. }
  310. }
  311. }
  312. // processPacket receives a raw packet from pcap, builds a Request item from it and sends it to the queue
  313. func processPacket(packet gopacket.Packet) {
  314. hasIPv4 := false
  315. var ipSrc, ipDst string
  316. // IPv4
  317. if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil {
  318. ip := ipLayer.(*layers.IPv4)
  319. ipSrc = ip.SrcIP.String()
  320. ipDst = ip.DstIP.String()
  321. hasIPv4 = true
  322. }
  323. // IPv6
  324. if !hasIPv4 {
  325. if ipLayer := packet.Layer(layers.LayerTypeIPv6); ipLayer != nil {
  326. ip := ipLayer.(*layers.IPv6)
  327. ipSrc = ip.SrcIP.String()
  328. ipDst = ip.DstIP.String()
  329. }
  330. }
  331. // TCP
  332. tcpLayer := packet.Layer(layers.LayerTypeTCP)
  333. if tcpLayer == nil {
  334. return
  335. }
  336. tcp, _ := tcpLayer.(*layers.TCP)
  337. portSrc := tcp.SrcPort
  338. portDst := tcp.DstPort
  339. sequence := tcp.Seq
  340. applicationLayer := packet.ApplicationLayer()
  341. if applicationLayer == nil {
  342. return
  343. }
  344. count++
  345. if len(applicationLayer.Payload()) < 50 {
  346. log.Println("application layer too small!")
  347. return
  348. }
  349. request := data.Request{
  350. IpSrc: ipSrc,
  351. IpDst: ipDst,
  352. PortSrc: uint32(portSrc),
  353. PortDst: uint32(portDst),
  354. TcpSeq: uint32(sequence),
  355. CreatedAt: packet.Metadata().CaptureInfo.Timestamp.UnixNano(),
  356. }
  357. switch config.Protocol {
  358. case "http":
  359. err := processHTTP(&request, applicationLayer.Payload())
  360. if err != nil {
  361. log.Println(err)
  362. return
  363. }
  364. case "ajp13":
  365. err := processAJP13(&request, applicationLayer.Payload())
  366. if err != nil {
  367. log.Println(err)
  368. return
  369. }
  370. }
  371. if config.UseXForwardedAsSource && request.XForwardedFor != "" {
  372. if strings.Contains(request.XForwardedFor, ",") {
  373. ips := strings.Split(request.XForwardedFor, ",")
  374. for i := len(ips) - 1; i >= 0; i-- {
  375. ipRaw := strings.TrimSpace(ips[i])
  376. ipAddr := net.ParseIP(ipRaw)
  377. if ipAddr != nil && !ipPriv.IsPrivate(ipAddr) {
  378. request.Source = ipRaw
  379. break
  380. }
  381. }
  382. } else {
  383. ipAddr := net.ParseIP(strings.TrimSpace(request.XForwardedFor))
  384. if !ipPriv.IsPrivate(ipAddr) {
  385. request.Source = request.XForwardedFor
  386. }
  387. }
  388. }
  389. if request.Source == request.IpSrc && request.XRealIP != "" {
  390. request.Source = request.XRealIP
  391. }
  392. if config.Trace {
  393. log.Printf("[%s] %s\n", request.Source, request.Url)
  394. }
  395. publishRequest(config.NatsQueue, &request)
  396. }
  397. func processAJP13(request *data.Request, appData []byte) error {
  398. a, err := ajp13.Parse(appData)
  399. if err != nil {
  400. return fmt.Errorf("Failed to parse AJP13 request: %s", err)
  401. }
  402. request.Url = a.URI
  403. request.Method = a.Method()
  404. request.Host = a.Server
  405. request.Protocol = a.Version
  406. request.Origin = a.RemoteAddr.String()
  407. request.Source = a.RemoteAddr.String()
  408. if v, ok := a.Header("Referer"); ok {
  409. request.Referer = v
  410. }
  411. if v, ok := a.Header("Connection"); ok {
  412. request.Connection = v
  413. }
  414. if v, ok := a.Header("X-Forwarded-For"); ok {
  415. request.XForwardedFor = v
  416. }
  417. if v, ok := a.Header("X-Real-IP"); ok {
  418. request.XRealIP = v
  419. }
  420. if v, ok := a.Header("X-Requested-With"); ok {
  421. request.XRequestedWith = v
  422. }
  423. if v, ok := a.Header("Accept-Encoding"); ok {
  424. request.AcceptEncoding = v
  425. }
  426. if v, ok := a.Header("Accept-Language"); ok {
  427. request.AcceptLanguage = v
  428. }
  429. if v, ok := a.Header("User-Agent"); ok {
  430. request.UserAgent = v
  431. }
  432. if v, ok := a.Header("Accept"); ok {
  433. request.Accept = v
  434. }
  435. if v, ok := a.Header("Cookie"); ok {
  436. request.Cookie = v
  437. }
  438. if v, ok := a.Header("X-Forwarded-Host"); ok {
  439. if v != request.Host {
  440. request.Host = v
  441. }
  442. }
  443. return nil
  444. }
  445. func processHTTP(request *data.Request, appData []byte) error {
  446. reader := bufio.NewReader(strings.NewReader(string(appData)))
  447. req, err := http.ReadRequest(reader)
  448. if err != nil {
  449. return fmt.Errorf("Failed to parse HTTP header: %s", err)
  450. }
  451. request.Url = req.URL.String()
  452. request.Method = req.Method
  453. request.Referer = req.Referer()
  454. request.Host = req.Host
  455. request.Protocol = req.Proto
  456. request.Origin = request.Host
  457. if _, ok := req.Header["Connection"]; ok {
  458. request.Connection = req.Header["Connection"][0]
  459. }
  460. if _, ok := req.Header["X-Forwarded-For"]; ok {
  461. request.XForwardedFor = req.Header["X-Forwarded-For"][0]
  462. }
  463. // CloudFlare: override X-Forwarded for since it is tainted by cloudflare
  464. if _, ok := req.Header["True-Client-Ip"]; ok {
  465. request.XForwardedFor = req.Header["True-Client-Ip"][0]
  466. }
  467. if _, ok := req.Header["X-Real-Ip"]; ok {
  468. request.XRealIP = req.Header["X-Real-Ip"][0]
  469. }
  470. if _, ok := req.Header["X-Requested-With"]; ok {
  471. request.XRequestedWith = req.Header["X-Requested-With"][0]
  472. }
  473. if _, ok := req.Header["Accept-Encoding"]; ok {
  474. request.AcceptEncoding = req.Header["Accept-Encoding"][0]
  475. }
  476. if _, ok := req.Header["Accept-Language"]; ok {
  477. request.AcceptLanguage = req.Header["Accept-Language"][0]
  478. }
  479. if _, ok := req.Header["User-Agent"]; ok {
  480. request.UserAgent = req.Header["User-Agent"][0]
  481. }
  482. if _, ok := req.Header["Accept"]; ok {
  483. request.Accept = req.Header["Accept"][0]
  484. }
  485. if _, ok := req.Header["Cookie"]; ok {
  486. request.Cookie = req.Header["Cookie"][0]
  487. }
  488. request.Source = request.IpSrc
  489. return nil
  490. }
  491. // replayFile takes a file containing a list of requests (SourceIP Url) and queues the requests
  492. // e.g.
  493. // 157.55.39.229 /gross-gerau/12012260-beate-anstatt
  494. // 103.232.100.98 /weinsheim-eifel/13729444-plus-warenhandelsges-mbh
  495. func replayFile() {
  496. var req data.Request
  497. var startTs time.Time
  498. var endTs time.Time
  499. rand.Seed(time.Now().UnixNano())
  500. for {
  501. fh, err := os.Open(config.RequestsFile)
  502. if err != nil {
  503. log.Fatalf("Failed to open request file '%s': %s", config.RequestsFile, err)
  504. }
  505. c := csv.NewReader(fh)
  506. c.Comma = ' '
  507. for {
  508. if config.SleepFor.Duration > time.Nanosecond {
  509. startTs = time.Now()
  510. }
  511. r, err := c.Read()
  512. if err == io.EOF {
  513. break
  514. }
  515. if err != nil {
  516. log.Println(err)
  517. continue
  518. }
  519. req.IpSrc = r[0]
  520. req.Source = r[0]
  521. req.Url = r[1]
  522. req.UserAgent = "Munch/1.0"
  523. req.Host = "demo.scraperwall.com"
  524. req.CreatedAt = time.Now().UnixNano()
  525. publishRequest(config.NatsQueue, &req)
  526. if strings.Index(r[1], ".") < 0 {
  527. hash := sha1.New()
  528. io.WriteString(hash, r[0])
  529. fp := data.Fingerprint{
  530. ClientID: "scw",
  531. Fingerprint: fmt.Sprintf("%x", hash.Sum(nil)),
  532. Remote: r[0],
  533. Url: r[1],
  534. Source: r[0],
  535. CreatedAt: time.Now(),
  536. }
  537. if strings.HasPrefix(r[0], "50.31.") {
  538. fp.Fingerprint = "a1f2c2ee560ce6580d66d451a9c8dfbf"
  539. natsJSONEC.Publish("fingerprints_scw", fp)
  540. } else if rand.Intn(10) < 5 {
  541. natsJSONEC.Publish("fingerprints_scw", fp)
  542. }
  543. }
  544. count++
  545. if config.SleepFor.Duration >= time.Nanosecond {
  546. endTs = time.Now()
  547. if endTs.Before(startTs.Add(config.SleepFor.Duration)) {
  548. time.Sleep(config.SleepFor.Duration - endTs.Sub(startTs))
  549. }
  550. }
  551. }
  552. }
  553. }
  554. func loadConfig() {
  555. // initialize with values from the command line / environment
  556. config.Live = *doLiveCapture
  557. config.Interface = *iface
  558. config.SnapshotLen = *snapshotLen
  559. config.Filter = *filter
  560. config.Promiscuous = *promiscuous
  561. config.NatsURL = *natsURL
  562. config.NatsQueue = *natsQueue
  563. config.NatsUser = *natsUser
  564. config.NatsPassword = *natsPassword
  565. config.NatsCA = *natsCA
  566. config.SleepFor.Duration = *sleepFor
  567. config.RequestsFile = *requestsFile
  568. config.UseXForwardedAsSource = *useXForwardedAsSource
  569. config.Protocol = *protocol
  570. config.ApacheLog = *apacheLog
  571. config.ApacheReplay = *apacheReplay
  572. config.NginxLog = *nginxLog
  573. config.NginxLogFormat = *nginxFormat
  574. config.NginxReplay = *nginxReplay
  575. config.HostName = *hostName
  576. config.Quiet = *beQuiet
  577. config.Trace = *trace
  578. config.AccessWatchKey = *accessWatchKey
  579. if *configFile == "" {
  580. return
  581. }
  582. _, err := os.Stat(*configFile)
  583. if err != nil {
  584. log.Printf("%s: %s\n", *configFile, err)
  585. return
  586. }
  587. if _, err = toml.DecodeFile(*configFile, &config); err != nil {
  588. log.Printf("%s: %s\n", *configFile, err)
  589. }
  590. if !config.Quiet {
  591. config.print()
  592. }
  593. }
  594. // version outputs build information...
  595. func version() {
  596. fmt.Printf("munchclient %s, built on %s\n", Version, BuildDate)
  597. }