api_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. package guerrilla
  2. import (
  3. "bufio"
  4. "fmt"
  5. "github.com/flashmob/go-guerrilla/backends"
  6. "github.com/flashmob/go-guerrilla/log"
  7. "github.com/flashmob/go-guerrilla/mail"
  8. "io/ioutil"
  9. "net"
  10. "os"
  11. "strings"
  12. "testing"
  13. "time"
  14. )
  15. // Test Starting smtp without setting up logger / backend
  16. func TestSMTP(t *testing.T) {
  17. done := make(chan bool)
  18. go func() {
  19. select {
  20. case <-time.After(time.Second * 40):
  21. t.Error("timeout")
  22. return
  23. case <-done:
  24. return
  25. }
  26. }()
  27. d := Daemon{}
  28. err := d.Start()
  29. if err != nil {
  30. t.Error(err)
  31. }
  32. // it should set to stderr automatically
  33. if d.Config.LogFile != log.OutputStderr.String() {
  34. t.Error("smtp.config.LogFile is not", log.OutputStderr.String())
  35. }
  36. if len(d.Config.AllowedHosts) == 0 {
  37. t.Error("smtp.config.AllowedHosts len should be 1, not 0", d.Config.AllowedHosts)
  38. }
  39. if d.Config.LogLevel != "debug" {
  40. t.Error("smtp.config.LogLevel expected'debug', it is", d.Config.LogLevel)
  41. }
  42. if len(d.Config.Servers) != 1 {
  43. t.Error("len(smtp.config.Servers) should be 1, got", len(d.Config.Servers))
  44. }
  45. time.Sleep(time.Second * 2)
  46. d.Shutdown()
  47. done <- true
  48. }
  49. // Suppressing log output
  50. func TestSMTPNoLog(t *testing.T) {
  51. // configure a default server with no log output
  52. cfg := &AppConfig{LogFile: log.OutputOff.String()}
  53. d := Daemon{Config: cfg}
  54. err := d.Start()
  55. if err != nil {
  56. t.Error(err)
  57. }
  58. time.Sleep(time.Second * 2)
  59. d.Shutdown()
  60. }
  61. // our custom server
  62. func TestSMTPCustomServer(t *testing.T) {
  63. cfg := &AppConfig{LogFile: log.OutputOff.String()}
  64. sc := ServerConfig{
  65. ListenInterface: "127.0.0.1:2526",
  66. IsEnabled: true,
  67. }
  68. cfg.Servers = append(cfg.Servers, sc)
  69. d := Daemon{Config: cfg}
  70. err := d.Start()
  71. if err != nil {
  72. t.Error("start error", err)
  73. } else {
  74. time.Sleep(time.Second * 2)
  75. d.Shutdown()
  76. }
  77. }
  78. // with a backend config
  79. func TestSMTPCustomBackend(t *testing.T) {
  80. cfg := &AppConfig{LogFile: log.OutputOff.String()}
  81. sc := ServerConfig{
  82. ListenInterface: "127.0.0.1:2526",
  83. IsEnabled: true,
  84. }
  85. cfg.Servers = append(cfg.Servers, sc)
  86. bcfg := backends.BackendConfig{
  87. "save_workers_size": 3,
  88. "save_process": "HeadersParser|Header|Hasher|Debugger",
  89. "log_received_mails": true,
  90. "primary_mail_host": "example.com",
  91. }
  92. cfg.BackendConfig = bcfg
  93. d := Daemon{Config: cfg}
  94. err := d.Start()
  95. if err != nil {
  96. t.Error("start error", err)
  97. } else {
  98. time.Sleep(time.Second * 2)
  99. d.Shutdown()
  100. }
  101. }
  102. // with a config from a json file
  103. func TestSMTPLoadFile(t *testing.T) {
  104. json := `{
  105. "log_file" : "./tests/testlog",
  106. "log_level" : "debug",
  107. "pid_file" : "tests/go-guerrilla.pid",
  108. "allowed_hosts": ["spam4.me","grr.la"],
  109. "backend_config" :
  110. {
  111. "log_received_mails" : true,
  112. "save_process": "HeadersParser|Header|Hasher|Debugger",
  113. "save_workers_size": 3
  114. },
  115. "servers" : [
  116. {
  117. "is_enabled" : true,
  118. "host_name":"mail.guerrillamail.com",
  119. "max_size": 100017,
  120. "timeout":160,
  121. "listen_interface":"127.0.0.1:2526",
  122. "max_clients": 2,
  123. "tls" : {
  124. "private_key_file":"config_test.go",
  125. "public_key_file":"config_test.go",
  126. "start_tls_on":false,
  127. "tls_always_on":false
  128. }
  129. }
  130. ]
  131. }
  132. `
  133. json2 := `{
  134. "log_file" : "./tests/testlog2",
  135. "log_level" : "debug",
  136. "pid_file" : "tests/go-guerrilla2.pid",
  137. "allowed_hosts": ["spam4.me","grr.la"],
  138. "backend_config" :
  139. {
  140. "log_received_mails" : true,
  141. "save_process": "HeadersParser|Header|Hasher|Debugger",
  142. "save_workers_size": 3
  143. },
  144. "servers" : [
  145. {
  146. "is_enabled" : true,
  147. "host_name":"mail.guerrillamail.com",
  148. "max_size": 100017,
  149. "timeout":160,
  150. "listen_interface":"127.0.0.1:2526",
  151. "max_clients": 2,
  152. "tls" : {
  153. "private_key_file":"config_test.go",
  154. "public_key_file":"config_test.go",
  155. "start_tls_on":false,
  156. "tls_always_on":false
  157. }
  158. }
  159. ]
  160. }
  161. `
  162. err := ioutil.WriteFile("goguerrilla.conf.api", []byte(json), 0644)
  163. if err != nil {
  164. t.Error("could not write guerrilla.conf.api", err)
  165. return
  166. }
  167. d := Daemon{}
  168. _, err = d.LoadConfig("goguerrilla.conf.api")
  169. if err != nil {
  170. t.Error("ReadConfig error", err)
  171. return
  172. }
  173. err = d.Start()
  174. if err != nil {
  175. t.Error("start error", err)
  176. return
  177. } else {
  178. time.Sleep(time.Second * 2)
  179. if d.Config.LogFile != "./tests/testlog" {
  180. t.Error("d.Config.LogFile != \"./tests/testlog\"")
  181. }
  182. if d.Config.PidFile != "tests/go-guerrilla.pid" {
  183. t.Error("d.Config.LogFile != tests/go-guerrilla.pid")
  184. }
  185. err := ioutil.WriteFile("goguerrilla.conf.api", []byte(json2), 0644)
  186. if err != nil {
  187. t.Error("could not write guerrilla.conf.api", err)
  188. return
  189. }
  190. d.ReloadConfigFile("goguerrilla.conf.api")
  191. if d.Config.LogFile != "./tests/testlog2" {
  192. t.Error("d.Config.LogFile != \"./tests/testlog\"")
  193. }
  194. if d.Config.PidFile != "tests/go-guerrilla2.pid" {
  195. t.Error("d.Config.LogFile != \"go-guerrilla.pid\"")
  196. }
  197. d.Shutdown()
  198. }
  199. }
  200. func TestReopenLog(t *testing.T) {
  201. os.Truncate("test/testlog", 0)
  202. cfg := &AppConfig{LogFile: "tests/testlog"}
  203. sc := ServerConfig{
  204. ListenInterface: "127.0.0.1:2526",
  205. IsEnabled: true,
  206. }
  207. cfg.Servers = append(cfg.Servers, sc)
  208. d := Daemon{Config: cfg}
  209. err := d.Start()
  210. if err != nil {
  211. t.Error("start error", err)
  212. } else {
  213. d.ReopenLogs()
  214. time.Sleep(time.Second * 2)
  215. d.Shutdown()
  216. }
  217. b, err := ioutil.ReadFile("tests/testlog")
  218. if err != nil {
  219. t.Error("could not read logfile")
  220. return
  221. }
  222. if strings.Index(string(b), "re-opened log file") < 0 {
  223. t.Error("Server log did not re-opened, expecting \"re-opened log file\"")
  224. }
  225. if strings.Index(string(b), "re-opened main log file") < 0 {
  226. t.Error("Main log did not re-opened, expecting \"re-opened main log file\"")
  227. }
  228. }
  229. func TestSetConfig(t *testing.T) {
  230. os.Truncate("test/testlog", 0)
  231. cfg := AppConfig{LogFile: "tests/testlog"}
  232. sc := ServerConfig{
  233. ListenInterface: "127.0.0.1:2526",
  234. IsEnabled: true,
  235. }
  236. cfg.Servers = append(cfg.Servers, sc)
  237. d := Daemon{Config: &cfg}
  238. // lets add a new server
  239. sc.ListenInterface = "127.0.0.1:2527"
  240. cfg.Servers = append(cfg.Servers, sc)
  241. err := d.SetConfig(cfg)
  242. if err != nil {
  243. t.Error("SetConfig returned an error:", err)
  244. return
  245. }
  246. err = d.Start()
  247. if err != nil {
  248. t.Error("start error", err)
  249. } else {
  250. time.Sleep(time.Second * 2)
  251. d.Shutdown()
  252. }
  253. b, err := ioutil.ReadFile("tests/testlog")
  254. if err != nil {
  255. t.Error("could not read logfile")
  256. return
  257. }
  258. //fmt.Println(string(b))
  259. // has 127.0.0.1:2527 started?
  260. if strings.Index(string(b), "127.0.0.1:2527") < 0 {
  261. t.Error("expecting 127.0.0.1:2527 to start")
  262. }
  263. }
  264. func TestSetConfigError(t *testing.T) {
  265. os.Truncate("tests/testlog", 0)
  266. cfg := AppConfig{LogFile: "tests/testlog"}
  267. sc := ServerConfig{
  268. ListenInterface: "127.0.0.1:2526",
  269. IsEnabled: true,
  270. }
  271. cfg.Servers = append(cfg.Servers, sc)
  272. d := Daemon{Config: &cfg}
  273. // lets add a new server with bad TLS
  274. sc.ListenInterface = "127.0.0.1:2527"
  275. sc.TLS.StartTLSOn = true
  276. sc.TLS.PublicKeyFile = "tests/testlog" // totally wrong :->
  277. sc.TLS.PrivateKeyFile = "tests/testlog" // totally wrong :->
  278. cfg.Servers = append(cfg.Servers, sc)
  279. err := d.SetConfig(cfg)
  280. if err == nil {
  281. t.Error("SetConfig should have returned an error compalning about bad tls settings")
  282. return
  283. }
  284. }
  285. var funkyLogger = func() backends.Decorator {
  286. backends.Svc.AddInitializer(
  287. backends.InitializeWith(
  288. func(backendConfig backends.BackendConfig) error {
  289. backends.Log().Info("Funky logger is up & down to funk!")
  290. return nil
  291. }),
  292. )
  293. backends.Svc.AddShutdowner(
  294. backends.ShutdownWith(
  295. func() error {
  296. backends.Log().Info("The funk has been stopped!")
  297. return nil
  298. }),
  299. )
  300. return func(p backends.Processor) backends.Processor {
  301. return backends.ProcessWith(
  302. func(e *mail.Envelope, task backends.SelectTask) (backends.Result, error) {
  303. if task == backends.TaskValidateRcpt {
  304. // validate the last recipient appended to e.Rcpt
  305. backends.Log().Infof(
  306. "another funky recipient [%s]",
  307. e.RcptTo[len(e.RcptTo)-1])
  308. // if valid then forward call to the next processor in the chain
  309. return p.Process(e, task)
  310. // if invalid, return a backend result
  311. //return backends.NewResult(response.Canned.FailRcptCmd), nil
  312. } else if task == backends.TaskSaveMail {
  313. backends.Log().Info("Another funky email!")
  314. }
  315. return p.Process(e, task)
  316. })
  317. }
  318. }
  319. // How about a custom processor?
  320. func TestSetAddProcessor(t *testing.T) {
  321. os.Truncate("tests/testlog", 0)
  322. cfg := &AppConfig{
  323. LogFile: "tests/testlog",
  324. AllowedHosts: []string{"grr.la"},
  325. BackendConfig: backends.BackendConfig{
  326. "save_process": "HeadersParser|Debugger|FunkyLogger",
  327. "validate_process": "FunkyLogger",
  328. },
  329. }
  330. d := Daemon{Config: cfg}
  331. d.AddProcessor("FunkyLogger", funkyLogger)
  332. d.Start()
  333. // lets have a talk with the server
  334. talkToServer("127.0.0.1:2525")
  335. d.Shutdown()
  336. b, err := ioutil.ReadFile("tests/testlog")
  337. if err != nil {
  338. t.Error("could not read logfile")
  339. return
  340. }
  341. // lets check for fingerprints
  342. if strings.Index(string(b), "another funky recipient") < 0 {
  343. t.Error("did not log: another funky recipient")
  344. }
  345. if strings.Index(string(b), "Another funky email!") < 0 {
  346. t.Error("Did not log: Another funky email!")
  347. }
  348. if strings.Index(string(b), "Funky logger is up & down to funk") < 0 {
  349. t.Error("Did not log: Funky logger is up & down to funk")
  350. }
  351. if strings.Index(string(b), "The funk has been stopped!") < 0 {
  352. t.Error("Did not log:The funk has been stopped!")
  353. }
  354. }
  355. func talkToServer(address string) {
  356. conn, err := net.Dial("tcp", address)
  357. if err != nil {
  358. return
  359. }
  360. in := bufio.NewReader(conn)
  361. str, err := in.ReadString('\n')
  362. fmt.Fprint(conn, "HELO maildiranasaurustester\r\n")
  363. str, err = in.ReadString('\n')
  364. fmt.Fprint(conn, "MAIL FROM:<[email protected]>r\r\n")
  365. str, err = in.ReadString('\n')
  366. fmt.Fprint(conn, "RCPT TO:[email protected]\r\n")
  367. str, err = in.ReadString('\n')
  368. fmt.Fprint(conn, "DATA\r\n")
  369. str, err = in.ReadString('\n')
  370. fmt.Fprint(conn, "Subject: Test subject\r\n")
  371. fmt.Fprint(conn, "\r\n")
  372. fmt.Fprint(conn, "A an email body\r\n")
  373. fmt.Fprint(conn, ".\r\n")
  374. str, err = in.ReadString('\n')
  375. _ = str
  376. }
  377. // Test hot config reload
  378. // Here we forgot to add FunkyLogger so backend will fail to init
  379. func TestReloadConfig(t *testing.T) {
  380. os.Truncate("tests/testlog", 0)
  381. d := Daemon{}
  382. d.Start()
  383. defer d.Shutdown()
  384. cfg := AppConfig{
  385. LogFile: "tests/testlog",
  386. AllowedHosts: []string{"grr.la"},
  387. BackendConfig: backends.BackendConfig{
  388. "save_process": "HeadersParser|Debugger|FunkyLogger",
  389. "validate_process": "FunkyLogger",
  390. },
  391. }
  392. // Look mom, reloading the config without shutting down!
  393. d.ReloadConfig(cfg)
  394. }
  395. func TestPubSubAPI(t *testing.T) {
  396. os.Truncate("tests/testlog", 0)
  397. d := Daemon{Config: &AppConfig{LogFile: "tests/testlog"}}
  398. d.Start()
  399. defer d.Shutdown()
  400. // new config
  401. cfg := AppConfig{
  402. PidFile: "tests/pidfilex.pid",
  403. LogFile: "tests/testlog",
  404. AllowedHosts: []string{"grr.la"},
  405. BackendConfig: backends.BackendConfig{
  406. "save_process": "HeadersParser|Debugger|FunkyLogger",
  407. "validate_process": "FunkyLogger",
  408. },
  409. }
  410. var i = 0
  411. pidEvHandler := func(c *AppConfig) {
  412. i++
  413. if i > 1 {
  414. t.Error("number > 1, it means d.Unsubscribe didn't work")
  415. }
  416. d.Logger.Info("number", i)
  417. }
  418. d.Subscribe(EventConfigPidFile, pidEvHandler)
  419. d.ReloadConfig(cfg)
  420. d.Unsubscribe(EventConfigPidFile, pidEvHandler)
  421. cfg.PidFile = "tests/pidfile2.pid"
  422. d.Publish(EventConfigPidFile, &cfg)
  423. d.ReloadConfig(cfg)
  424. b, err := ioutil.ReadFile("tests/testlog")
  425. if err != nil {
  426. t.Error("could not read logfile")
  427. return
  428. }
  429. // lets interrogate the log
  430. if strings.Index(string(b), "number1") < 0 {
  431. t.Error("it lools like d.ReloadConfig(cfg) did not fire EventConfigPidFile, pidEvHandler not called")
  432. }
  433. }
  434. func TestAPILog(t *testing.T) {
  435. os.Truncate("tests/testlog", 0)
  436. d := Daemon{}
  437. l := d.Log()
  438. l.Info("logtest1") // to stderr
  439. if l.GetLevel() != log.InfoLevel.String() {
  440. t.Error("Log level does not eq info, it is ", l.GetLevel())
  441. }
  442. d.Logger = nil
  443. d.Config = &AppConfig{LogFile: "tests/testlog"}
  444. l = d.Log()
  445. l.Info("logtest1") // to tests/testlog
  446. //
  447. l = d.Log()
  448. if l.GetLogDest() != "tests/testlog" {
  449. t.Error("log dest is not tests/testlog, it was ", l.GetLogDest())
  450. }
  451. b, err := ioutil.ReadFile("tests/testlog")
  452. if err != nil {
  453. t.Error("could not read logfile")
  454. return
  455. }
  456. // lets interrogate the log
  457. if strings.Index(string(b), "logtest1") < 0 {
  458. t.Error("hai was not found in the log, it should have been in tests/testlog")
  459. }
  460. }
  461. // Test the allowed_hosts config option with a single entry of ".", which will allow all hosts.
  462. func TestSkipAllowsHost(t *testing.T) {
  463. d := Daemon{}
  464. defer d.Shutdown()
  465. // setting the allowed hosts to a single entry with a dot will let any host through
  466. d.Config = &AppConfig{AllowedHosts: []string{"."}, LogFile: "off"}
  467. d.Start()
  468. conn, err := net.Dial("tcp", d.Config.Servers[0].ListenInterface)
  469. if err != nil {
  470. t.Error(t)
  471. return
  472. }
  473. in := bufio.NewReader(conn)
  474. fmt.Fprint(conn, "HELO test\r\n")
  475. fmt.Fprint(conn, "RCPT TO: [email protected]\r\n")
  476. in.ReadString('\n')
  477. in.ReadString('\n')
  478. str, _ := in.ReadString('\n')
  479. if strings.Index(str, "250") != 0 {
  480. t.Error("expected 250 reply, got:", str)
  481. }
  482. }