backend.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. package backends
  2. import (
  3. "fmt"
  4. "github.com/flashmob/go-guerrilla/log"
  5. "github.com/flashmob/go-guerrilla/mail"
  6. "reflect"
  7. "strconv"
  8. "strings"
  9. "sync"
  10. "sync/atomic"
  11. )
  12. var (
  13. Svc *service
  14. // Store the constructor for making an new processor decorator.
  15. processors map[string]processorConstructor
  16. b Backend
  17. )
  18. func init() {
  19. Svc = &service{}
  20. processors = make(map[string]processorConstructor)
  21. }
  22. type processorConstructor func() Decorator
  23. // Backends process received mail. Depending on the implementation, they can store mail in the database,
  24. // write to a file, check for spam, re-transmit to another server, etc.
  25. // Must return an SMTP message (i.e. "250 OK") and a boolean indicating
  26. // whether the message was processed successfully.
  27. type Backend interface {
  28. // Process processes then saves the mail envelope
  29. Process(*mail.Envelope) Result
  30. // ValidateRcpt validates the last recipient that was pushed to the mail envelope
  31. ValidateRcpt(e *mail.Envelope) RcptError
  32. // Initializes the backend, eg. creates folders, sets-up database connections
  33. Initialize(BackendConfig) error
  34. // Initializes the backend after it was Shutdown()
  35. Reinitialize() error
  36. // Shutdown frees / closes anything created during initializations
  37. Shutdown() error
  38. // Start Starts a backend that has been initialized
  39. Start() error
  40. }
  41. type BackendConfig map[string]interface{}
  42. // All config structs extend from this
  43. type BaseConfig interface{}
  44. type notifyMsg struct {
  45. err error
  46. queuedID string
  47. }
  48. // Result represents a response to an SMTP client after receiving DATA.
  49. // The String method should return an SMTP message ready to send back to the
  50. // client, for example `250 OK: Message received`.
  51. type Result interface {
  52. fmt.Stringer
  53. // Code should return the SMTP code associated with this response, ie. `250`
  54. Code() int
  55. }
  56. // Internal implementation of BackendResult for use by backend implementations.
  57. type result string
  58. func (br result) String() string {
  59. return string(br)
  60. }
  61. // Parses the SMTP code from the first 3 characters of the SMTP message.
  62. // Returns 554 if code cannot be parsed.
  63. func (br result) Code() int {
  64. trimmed := strings.TrimSpace(string(br))
  65. if len(trimmed) < 3 {
  66. return 554
  67. }
  68. code, err := strconv.Atoi(trimmed[:3])
  69. if err != nil {
  70. return 554
  71. }
  72. return code
  73. }
  74. func NewResult(message string) Result {
  75. return result(message)
  76. }
  77. type processorInitializer interface {
  78. Initialize(backendConfig BackendConfig) error
  79. }
  80. type processorShutdowner interface {
  81. Shutdown() error
  82. }
  83. type InitializeWith func(backendConfig BackendConfig) error
  84. type ShutdownWith func() error
  85. // Satisfy ProcessorInitializer interface
  86. // So we can now pass an anonymous function that implements ProcessorInitializer
  87. func (i InitializeWith) Initialize(backendConfig BackendConfig) error {
  88. // delegate to the anonymous function
  89. return i(backendConfig)
  90. }
  91. // satisfy ProcessorShutdowner interface, same concept as InitializeWith type
  92. func (s ShutdownWith) Shutdown() error {
  93. // delegate
  94. return s()
  95. }
  96. type Errors []error
  97. // implement the Error interface
  98. func (e Errors) Error() string {
  99. if len(e) == 1 {
  100. return e[0].Error()
  101. }
  102. // multiple errors
  103. msg := ""
  104. for _, err := range e {
  105. msg += "\n" + err.Error()
  106. }
  107. return msg
  108. }
  109. func convertError(name string) error {
  110. return fmt.Errorf("failed to load backend config (%s)", name)
  111. }
  112. func GetBackend() Backend {
  113. return b
  114. }
  115. type service struct {
  116. initializers []processorInitializer
  117. shutdowners []processorShutdowner
  118. sync.Mutex
  119. mainlog atomic.Value
  120. }
  121. // Get loads the log.logger in an atomic operation. Returns a stderr logger if not able to load
  122. func Log() log.Logger {
  123. if v, ok := Svc.mainlog.Load().(log.Logger); ok {
  124. return v
  125. }
  126. l, _ := log.GetLogger(log.OutputStderr.String())
  127. return l
  128. }
  129. func (s *service) SetMainlog(l log.Logger) {
  130. s.mainlog.Store(l)
  131. }
  132. // AddInitializer adds a function that implements ProcessorShutdowner to be called when initializing
  133. func (s *service) AddInitializer(i processorInitializer) {
  134. s.Lock()
  135. defer s.Unlock()
  136. s.initializers = append(s.initializers, i)
  137. }
  138. // AddShutdowner adds a function that implements ProcessorShutdowner to be called when shutting down
  139. func (s *service) AddShutdowner(sh processorShutdowner) {
  140. s.Lock()
  141. defer s.Unlock()
  142. s.shutdowners = append(s.shutdowners, sh)
  143. }
  144. // reset clears the initializers and Shutdowners
  145. func (s *service) reset() {
  146. s.shutdowners = make([]processorShutdowner, 0)
  147. s.initializers = make([]processorInitializer, 0)
  148. }
  149. // Initialize initializes all the processors one-by-one and returns any errors.
  150. // Subsequent calls to Initialize will not call the initializer again unless it failed on the previous call
  151. // so Initialize may be called again to retry after getting errors
  152. func (s *service) initialize(backend BackendConfig) Errors {
  153. s.Lock()
  154. defer s.Unlock()
  155. var errors Errors
  156. failed := make([]processorInitializer, 0)
  157. for i := range s.initializers {
  158. if err := s.initializers[i].Initialize(backend); err != nil {
  159. errors = append(errors, err)
  160. failed = append(failed, s.initializers[i])
  161. }
  162. }
  163. // keep only the failed initializers
  164. s.initializers = failed
  165. return errors
  166. }
  167. // Shutdown shuts down all the processors by calling their shutdowners (if any)
  168. // Subsequent calls to Shutdown will not call the shutdowners again unless it failed on the previous call
  169. // so Shutdown may be called again to retry after getting errors
  170. func (s *service) shutdown() Errors {
  171. s.Lock()
  172. defer s.Unlock()
  173. var errors Errors
  174. failed := make([]processorShutdowner, 0)
  175. for i := range s.shutdowners {
  176. if err := s.shutdowners[i].Shutdown(); err != nil {
  177. errors = append(errors, err)
  178. failed = append(failed, s.shutdowners[i])
  179. }
  180. }
  181. s.shutdowners = failed
  182. return errors
  183. }
  184. // AddProcessor adds a new processor, which becomes available to the backend_config.process_stack option
  185. // Use to add your own custom processor when using backends as a package, or after importing an external
  186. // processor.
  187. func (s *service) AddProcessor(name string, p processorConstructor) {
  188. // wrap in a constructor since we want to defer calling it
  189. var c processorConstructor
  190. c = func() Decorator {
  191. return p()
  192. }
  193. // add to our processors list
  194. processors[strings.ToLower(name)] = c
  195. }
  196. // extractConfig loads the backend config. It has already been unmarshalled
  197. // configData contains data from the main config file's "backend_config" value
  198. // configType is a Processor's specific config value.
  199. // The reason why using reflection is because we'll get a nice error message if the field is missing
  200. // the alternative solution would be to json.Marshal() and json.Unmarshal() however that will not give us any
  201. // error messages
  202. func (s *service) ExtractConfig(configData BackendConfig, configType BaseConfig) (interface{}, error) {
  203. // Use reflection so that we can provide a nice error message
  204. v := reflect.ValueOf(configType).Elem() // so that we can set the values
  205. //m := reflect.ValueOf(configType).Elem()
  206. t := reflect.TypeOf(configType).Elem()
  207. typeOfT := v.Type()
  208. for i := 0; i < v.NumField(); i++ {
  209. f := v.Field(i)
  210. // read the tags of the config struct
  211. field_name := t.Field(i).Tag.Get("json")
  212. omitempty := false
  213. if len(field_name) > 0 {
  214. // parse the tag to
  215. // get the field name from struct tag
  216. split := strings.Split(field_name, ",")
  217. field_name = split[0]
  218. if len(split) > 1 {
  219. if split[1] == "omitempty" {
  220. omitempty = true
  221. }
  222. }
  223. } else {
  224. // could have no tag
  225. // so use the reflected field name
  226. field_name = typeOfT.Field(i).Name
  227. }
  228. if f.Type().Name() == "int" {
  229. // in json, there is no int, only floats...
  230. if intVal, converted := configData[field_name].(float64); converted {
  231. v.Field(i).SetInt(int64(intVal))
  232. } else if intVal, converted := configData[field_name].(int); converted {
  233. v.Field(i).SetInt(int64(intVal))
  234. } else if !omitempty {
  235. return configType, convertError("property missing/invalid: '" + field_name + "' of expected type: " + f.Type().Name())
  236. }
  237. }
  238. if f.Type().Name() == "string" {
  239. if stringVal, converted := configData[field_name].(string); converted {
  240. v.Field(i).SetString(stringVal)
  241. } else if !omitempty {
  242. return configType, convertError("missing/invalid: '" + field_name + "' of type: " + f.Type().Name())
  243. }
  244. }
  245. if f.Type().Name() == "bool" {
  246. if boolVal, converted := configData[field_name].(bool); converted {
  247. v.Field(i).SetBool(boolVal)
  248. } else if !omitempty {
  249. return configType, convertError("missing/invalid: '" + field_name + "' of type: " + f.Type().Name())
  250. }
  251. }
  252. }
  253. return configType, nil
  254. }