Package opensmtpd implements OpenSMTPD-extras in Go https://godoc.org/pkg/maze.io/x/opensmtpd
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

filter.go 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. package opensmtpd
  2. import (
  3. "fmt"
  4. "log"
  5. "net"
  6. "os"
  7. "strings"
  8. lru "github.com/hashicorp/golang-lru"
  9. )
  10. const (
  11. typeFilterRegister uint32 = iota
  12. typeFilterEvent
  13. typeFilterquery
  14. typeFilterPipe
  15. typeFilterResponse
  16. )
  17. var filterTypeName = map[uint32]string{
  18. typeFilterRegister: "IMSG_FILTER_REGISTER",
  19. typeFilterEvent: "IMSG_FILTER_EVENT",
  20. typeFilterquery: "IMSG_FILTER_QUERY",
  21. typeFilterPipe: "IMSG_FILTER_PIPE",
  22. typeFilterResponse: "IMSG_FILTER_RESPONSE",
  23. }
  24. func filterName(t uint32) string {
  25. if s, ok := filterTypeName[t]; ok {
  26. return s
  27. }
  28. return fmt.Sprintf("UNKNOWN %d", t)
  29. }
  30. const (
  31. hookConnect = 1 << iota
  32. hookHELO
  33. hookMAIL
  34. hookRCPT
  35. hookDATA
  36. hookEOM
  37. hookReset
  38. hookDisconnect
  39. hookCommit
  40. hookRollback
  41. hookDataLine
  42. )
  43. var hookTypeName = map[uint16]string{
  44. hookConnect: "HOOK_CONNECT",
  45. hookHELO: "HOOK_HELO",
  46. hookMAIL: "HOOK_MAIL",
  47. hookRCPT: "HOOK_RCPT",
  48. hookDATA: "HOOK_DATA",
  49. hookEOM: "HOOK_EOM",
  50. hookReset: "HOOK_RESET",
  51. hookDisconnect: "HOOK_DISCONNECT",
  52. hookCommit: "HOOK_COMMIT",
  53. hookRollback: "HOOK_ROLLBACK",
  54. hookDataLine: "HOOK_DATALINE",
  55. }
  56. func hookName(h uint16) string {
  57. var s []string
  58. for i := uint(0); i < 11; i++ {
  59. if h&(1<<i) != 0 {
  60. s = append(s, hookTypeName[(1<<i)])
  61. }
  62. }
  63. return strings.Join(s, ",")
  64. }
  65. const (
  66. eventConnect = iota
  67. eventReset
  68. eventDisconnect
  69. eventTXBegin
  70. eventTXCommit
  71. eventTXRollback
  72. )
  73. var eventTypeName = map[int]string{
  74. eventConnect: "EVENT_CONNECT",
  75. eventReset: "EVENT_RESET",
  76. eventDisconnect: "EVENT_DISCONNECT",
  77. eventTXBegin: "EVENT_TX_BEGIN",
  78. eventTXCommit: "EVENT_TX_COMMIT",
  79. eventTXRollback: "EVENT_TX_ROLLBACK",
  80. }
  81. func eventName(t int) string {
  82. if s, ok := eventTypeName[t]; ok {
  83. return s
  84. }
  85. return fmt.Sprintf("UNKNOWN %d", int(t))
  86. }
  87. const (
  88. queryConnect = iota
  89. queryHELO
  90. queryMAIL
  91. queryRCPT
  92. queryDATA
  93. queryEOM
  94. queryDataLine
  95. )
  96. var queryTypeName = map[int]string{
  97. queryConnect: "QUERY_CONNECT",
  98. queryHELO: "QUERY_HELO",
  99. queryMAIL: "QUERY_MAIL",
  100. queryRCPT: "QUERY_RCPT",
  101. queryDATA: "QUERY_DATA",
  102. queryEOM: "QUERY_EOM",
  103. queryDataLine: "QUERY_DATALINE",
  104. }
  105. func queryName(t int) string {
  106. if s, ok := queryTypeName[t]; ok {
  107. return s
  108. }
  109. return fmt.Sprintf("UNKNOWN %d", int(t))
  110. }
  111. const (
  112. FilterOK = iota
  113. FilterFail
  114. FilterClose
  115. )
  116. var responseTypeName = map[int]string{
  117. FilterOK: "FILTER_OK",
  118. FilterFail: "FILTER_FAIL",
  119. FilterClose: "FILTER_CLOSE",
  120. }
  121. func responseName(c int) string {
  122. if s, ok := responseTypeName[c]; ok {
  123. return s
  124. }
  125. return fmt.Sprintf("UNKNOWN %d", c)
  126. }
  127. // Filter implements the OpenSMTPD filter API
  128. type Filter struct {
  129. // Connect callback
  130. Connect func(*Session, *ConnectQuery) error
  131. // HELO callback
  132. HELO func(*Session, string) error
  133. // MAIL FROM callback
  134. MAIL func(*Session, string, string) error
  135. // RCPT TO callback
  136. RCPT func(*Session, string, string) error
  137. // DATA callback
  138. DATA func(*Session) error
  139. // DataLine callback
  140. DataLine func(*Session, string) error
  141. // EOM (end of message) callback
  142. EOM func(*Session, uint32) error
  143. // Reset callback
  144. Reset func(*Session) error
  145. // Disconnect callback
  146. Disconnect func(*Session) error
  147. // Commit callback
  148. Commit func(*Session) error
  149. Name string
  150. Version uint32
  151. c net.Conn
  152. m *message
  153. hooks int
  154. flags int
  155. ready bool
  156. session *lru.Cache
  157. }
  158. // Register our filter with OpenSMTPD
  159. func (f *Filter) Register() error {
  160. var err error
  161. if f.m == nil {
  162. f.m = new(message)
  163. }
  164. if f.c == nil {
  165. if f.c, err = newConn(0); err != nil {
  166. return err
  167. }
  168. }
  169. if err = f.m.ReadFrom(f.c); err != nil {
  170. return err
  171. }
  172. // Fill hooks mask
  173. if f.Connect != nil {
  174. f.hooks |= hookConnect
  175. }
  176. if f.HELO != nil {
  177. f.hooks |= hookHELO
  178. }
  179. if f.MAIL != nil {
  180. f.hooks |= hookMAIL
  181. }
  182. if f.RCPT != nil {
  183. f.hooks |= hookRCPT
  184. }
  185. if f.DATA != nil {
  186. f.hooks |= hookDATA
  187. }
  188. if f.DataLine != nil {
  189. f.hooks |= hookDataLine
  190. }
  191. if f.EOM != nil {
  192. f.hooks |= hookEOM
  193. }
  194. if f.Disconnect != nil {
  195. f.hooks |= hookDisconnect
  196. }
  197. if f.Commit != nil {
  198. f.hooks |= hookCommit
  199. }
  200. if t, ok := filterTypeName[f.m.Header.Type]; ok {
  201. log.Printf("filter: imsg %s\n", t)
  202. } else {
  203. log.Printf("filter: imsg UNKNOWN %d\n", f.m.Header.Type)
  204. }
  205. switch f.m.Header.Type {
  206. case typeFilterRegister:
  207. var err error
  208. if f.Version, err = f.m.GetTypeUint32(); err != nil {
  209. return err
  210. }
  211. if f.Name, err = f.m.GetTypeString(); err != nil {
  212. return err
  213. }
  214. log.Printf("register version=%d,name=%q\n", f.Version, f.Name)
  215. f.m.reset()
  216. f.m.Header.Type = typeFilterRegister
  217. f.m.PutTypeInt(f.hooks)
  218. f.m.PutTypeInt(f.flags)
  219. if err = f.m.WriteTo(f.c); err != nil {
  220. return err
  221. }
  222. default:
  223. return fmt.Errorf("filter: unexpected imsg type=%s\n", filterTypeName[f.m.Header.Type])
  224. }
  225. f.ready = true
  226. return nil
  227. }
  228. // Serve communicates with OpenSMTPD in a loop, until either one of the
  229. // parties closes stdin.
  230. func (f *Filter) Serve() error {
  231. var err error
  232. if !f.ready {
  233. if err = f.Register(); err != nil {
  234. return err
  235. }
  236. }
  237. if f.m == nil {
  238. f.m = new(message)
  239. }
  240. if f.session == nil {
  241. if f.session, err = lru.New(1024); err != nil {
  242. return err
  243. }
  244. }
  245. if f.c == nil {
  246. if f.c, err = newConn(0); err != nil {
  247. return err
  248. }
  249. }
  250. for {
  251. if err := f.m.ReadFrom(f.c); err != nil {
  252. if err.Error() != "resource temporarily unavailable" {
  253. return err
  254. }
  255. }
  256. if err := f.handle(); err != nil {
  257. return err
  258. }
  259. }
  260. }
  261. func (f *Filter) handle() (err error) {
  262. if t, ok := filterTypeName[f.m.Header.Type]; ok {
  263. log.Printf("filter: imsg %s\n", t)
  264. } else {
  265. log.Printf("filter: imsg UNKNOWN %d\n", f.m.Header.Type)
  266. }
  267. switch f.m.Header.Type {
  268. case typeFilterEvent:
  269. if err = f.handleEvent(); err != nil {
  270. return
  271. }
  272. case typeFilterquery:
  273. if err = f.handlequery(); err != nil {
  274. return
  275. }
  276. }
  277. return
  278. }
  279. func fdCount() int {
  280. d, err := os.Open("/proc/self/fd")
  281. if err != nil {
  282. log.Printf("fdcount open: %v\n", err)
  283. return -1
  284. }
  285. defer d.Close()
  286. fds, err := d.Readdirnames(-1)
  287. if err != nil {
  288. log.Printf("fdcount: %v\n", err)
  289. return -1
  290. }
  291. return len(fds) - 1 // -1 for os.Open...
  292. }
  293. func (f *Filter) handleEvent() (err error) {
  294. var (
  295. id uint64
  296. t int
  297. )
  298. if id, err = f.m.GetTypeID(); err != nil {
  299. return
  300. }
  301. if t, err = f.m.GetTypeInt(); err != nil {
  302. return
  303. }
  304. log.Printf("imsg event: %s [id=%#x]\n", eventName(t), id)
  305. log.Printf("imsg event data: %q\n", f.m.Data[14:])
  306. log.Printf("fdcount: %d [pid=%d]\n", fdCount(), os.Getpid())
  307. switch t {
  308. case eventConnect:
  309. f.session.Add(id, NewSession(f, id))
  310. case eventDisconnect:
  311. f.session.Remove(id)
  312. }
  313. return
  314. }
  315. func (f *Filter) handlequery() (err error) {
  316. var (
  317. id, qid uint64
  318. t int
  319. )
  320. if id, err = f.m.GetTypeID(); err != nil {
  321. return
  322. }
  323. if qid, err = f.m.GetTypeID(); err != nil {
  324. return
  325. }
  326. if t, err = f.m.GetTypeInt(); err != nil {
  327. return
  328. }
  329. log.Printf("imsg query: %s [id=%#x,qid=%#x]\n", queryName(t), id, qid)
  330. //log.Printf("imsg query data (%d remaining): %q\n", len(f.m.Data[f.m.rpos:]), f.m.Data[f.m.rpos:])
  331. //log.Printf("fdcount: %d [pid=%d]\n", fdCount(), os.Getpid())
  332. var s *Session
  333. if cached, ok := f.session.Get(id); ok {
  334. s = cached.(*Session)
  335. } else {
  336. s = NewSession(f, id)
  337. f.session.Add(id, s)
  338. }
  339. s.qtype = t
  340. s.qid = qid
  341. switch t {
  342. case queryConnect:
  343. var query ConnectQuery
  344. if query.Local, err = f.m.GetTypeSockaddr(); err != nil {
  345. return
  346. }
  347. if query.Remote, err = f.m.GetTypeSockaddr(); err != nil {
  348. return
  349. }
  350. if query.Hostname, err = f.m.GetTypeString(); err != nil {
  351. return
  352. }
  353. log.Printf("query connect: %s\n", query)
  354. if f.Connect != nil {
  355. return f.Connect(s, &query)
  356. }
  357. log.Printf("filter: WARNING: no connect callback\n")
  358. case queryHELO:
  359. var line string
  360. if line, err = f.m.GetTypeString(); err != nil {
  361. return
  362. }
  363. log.Printf("query HELO: %q\n", line)
  364. if f.HELO != nil {
  365. return f.HELO(s, line)
  366. }
  367. log.Printf("filter: WARNING: no HELO callback\n")
  368. return f.respond(s, FilterOK, 0, "")
  369. case queryMAIL:
  370. var user, domain string
  371. if user, domain, err = f.m.GetTypeMailaddr(); err != nil {
  372. return
  373. }
  374. log.Printf("query MAIL: %s\n", user+"@"+domain)
  375. if f.MAIL != nil {
  376. return f.MAIL(s, user, domain)
  377. }
  378. log.Printf("filter: WARNING: no MAIL callback\n")
  379. return f.respond(s, FilterOK, 0, "")
  380. case queryRCPT:
  381. var user, domain string
  382. if user, domain, err = f.m.GetTypeMailaddr(); err != nil {
  383. return
  384. }
  385. log.Printf("query RCPT: %s\n", user+"@"+domain)
  386. if f.RCPT != nil {
  387. return f.RCPT(s, user, domain)
  388. }
  389. log.Printf("filter: WARNING: no RCPT callback\n")
  390. return f.respond(s, FilterOK, 0, "")
  391. case queryDATA:
  392. if f.DATA != nil {
  393. return f.DATA(s)
  394. }
  395. log.Printf("filter: WARNING: no DATA callback\n")
  396. return f.respond(s, FilterOK, 0, "")
  397. case queryEOM:
  398. var dataLen uint32
  399. if dataLen, err = f.m.GetTypeUint32(); err != nil {
  400. return
  401. }
  402. if f.EOM != nil {
  403. return f.EOM(s, dataLen)
  404. }
  405. log.Printf("filter: WARNING: no EOM callback\n")
  406. return f.respond(s, FilterOK, 0, "")
  407. }
  408. return
  409. }
  410. func (f *Filter) respond(s *Session, status, code int, line string) error {
  411. log.Printf("filter: %s %s [code=%d,line=%q]\n", filterName(typeFilterResponse), responseName(status), code, line)
  412. if s.qtype == queryEOM {
  413. // Not implemented
  414. return nil
  415. }
  416. m := new(message)
  417. m.Header.Type = typeFilterResponse
  418. m.PutTypeID(s.qid)
  419. m.PutTypeInt(s.qtype)
  420. if s.qtype == queryEOM {
  421. // Not imlemented
  422. return nil
  423. }
  424. m.PutTypeInt(status)
  425. m.PutTypeInt(code)
  426. if line != "" {
  427. m.PutTypeString(line)
  428. }
  429. if err := m.WriteTo(f.c); err != nil {
  430. log.Printf("filter: respond failed: %v\n", err)
  431. return err
  432. }
  433. return nil
  434. }
  435. // ConnectQuery are the QUERY_CONNECT arguments
  436. type ConnectQuery struct {
  437. Local, Remote net.Addr
  438. Hostname string
  439. }
  440. func (q ConnectQuery) String() string {
  441. return fmt.Sprintf("%s -> %s [hostname=%s]", q.Remote, q.Local, q.Hostname)
  442. }