backend.go 7.1 KB

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