1
2
3
4
5
6
7
8
9
10
11
12
13
14 __doc__ = """zenmail
15
16 Listen on the SMTP port and convert email messages into events.
17
18 To test:
19 # Test pre-reqs
20 yum -y install mailx sendmail
21
22 # Mail to the local zenmail instance
23 mail -s "Hello world" bogo@localhost
24 Happy happy, joy, joy
25 .
26 Cc:
27
28 """
29
30 import logging
31 from email.Header import Header
32 import email
33 import os
34 import socket
35
36 import Globals
37 import zope.interface
38 import zope.component
39 from zope.interface import implements
40
41 from twisted.mail import smtp
42 from twisted.internet import reactor, protocol, defer
43
44 from Products.ZenCollector.daemon import CollectorDaemon
45 from Products.ZenCollector.interfaces import ICollector, ICollectorPreferences,\
46 IEventService, \
47 IScheduledTask
48 from Products.ZenCollector.tasks import NullTaskSplitter,\
49 BaseTask, TaskStates
50
51
52 from Products.ZenCollector.services.config import DeviceProxy
53
54 from Products.ZenEvents.MailProcessor import MailProcessor
55 from Products.ZenUtils.Utils import unused
56 unused(Globals, DeviceProxy)
57
58
59 COLLECTOR_NAME = 'zenmail'
60 log = logging.getLogger("zen.%s" % COLLECTOR_NAME)
61
62
64 zope.interface.implements(ICollectorPreferences)
65
84
88
90 """
91 Command-line options to be supported
92 """
93 SMTP_PORT = 25
94 try:
95 SMTP_PORT = socket.getservbyname('smtp', 'tcp')
96 except socket.error:
97 pass
98
99 parser.add_option('--useFileDescriptor',
100 dest='useFileDescriptor',
101 default=-1,
102 type="int",
103 help="File descriptor to use for listening")
104 parser.add_option('--listenPort',
105 dest='listenPort',
106 default=SMTP_PORT,
107 type="int",
108 help="Alternative listen port to use (default %default)")
109 parser.add_option('--eventseverity',
110 dest='eventseverity',
111 default="2",
112 type="int",
113 help="Severity for events created")
114 parser.add_option('--listenip',
115 dest='listenip',
116 default='0.0.0.0',
117 help='IP address to listen on. Default is 0.0.0.0')
118
119 - def postStartup(self):
121
122
123 -class ZenossEventPoster(object):
124 """
125 Implementation of interface definition for messages
126 that can be sent via SMTP.
127 """
128 implements(smtp.IMessage)
129
130 - def __init__(self, processor):
131 self.lines = []
132 self.processor = processor
133
134 - def lineReceived(self, line):
135 self.lines.append(line)
136
137 - def postEvent(self, messageStr):
138 email.message_from_string(messageStr)
139 self.processor.process(messageStr)
140
141 - def eomReceived(self):
142 log.info('Message data completed %s.', self.lines)
143 self.lines.append('')
144 messageData = '\n'.join(self.lines)
145
146 self.postEvent(messageData)
147
148 return defer.succeed("Received End Of Message marker")
149
150 - def connectionLost(self):
151 log.info('Connection lost unexpectedly')
152 del(self.lines)
153
154
156 implements(smtp.IMessageDelivery)
157
159 self.processor = processor
160
162 myHostname, self.clientIP = helo
163 date = smtp.rfc822date()
164
165 headerValue = 'by %s from %s with ESMTP ; %s' % (
166 myHostname, self.clientIP, date)
167
168 log.info('Relayed (or sent directly) from: %s', self.clientIP)
169
170 header = 'Received: %s' % Header(headerValue)
171 return header
172
176
177 - def makePoster(self):
178 return ZenossEventPoster(self.processor)
179
181 log.info("from: %s", originAddress)
182 return originAddress
183
184
187 self.processor = processor
188
190 delivery = ZenossDelivery(self.processor)
191 smtpProtocol = smtp.SMTP(delivery)
192 smtpProtocol.factory = self
193 return smtpProtocol
194
195
197 zope.interface.implements(IScheduledTask)
198
199 - def __init__(self, taskName, configId,
200 scheduleIntervalSeconds=3600, taskConfig=None):
201 BaseTask.__init__(self, taskName, configId,
202 scheduleIntervalSeconds, taskConfig)
203 self.log = log
204
205
206 self.name = taskName
207 self.configId = configId
208 self.state = TaskStates.STATE_IDLE
209 self.interval = scheduleIntervalSeconds
210 self._preferences = taskConfig
211 self._daemon = zope.component.getUtility(ICollector)
212 self._eventService = zope.component.queryUtility(IEventService)
213 self._preferences = self._daemon
214
215 self.options = self._daemon.options
216
217
218 self.sendEvent = self._eventService.sendEvent
219
220 if (self.options.useFileDescriptor < 0 and \
221 self.options.listenPort < 1024):
222 self._daemon.openPrivilegedPort('--listen',
223 '--proto=tcp', '--port=%s:%d' % (
224 self.options.listenip, self.options.listenPort))
225
226 self._daemon.changeUser()
227 self.processor = MailProcessor(self, self.options.eventseverity)
228
229 self.factory = SMTPFactory(self.processor)
230
231 log.info("listening on %s:%d" % (
232 self.options.listenip, self.options.listenPort))
233 if self.options.useFileDescriptor != -1:
234 self.useTcpFileDescriptor(int(self.options.useFileDescriptor),
235 self.factory)
236 else:
237 reactor.listenTCP(self.options.listenPort, self.factory,
238 interface=self.options.listenip)
239
241 """
242 This is a wait-around task since we really are called
243 asynchronously.
244 """
245 return defer.succeed("Waiting for SMTP messages...")
246
260
263
264
268
269
270 if __name__=='__main__':
271 myPreferences = MailPreferences()
272 myTaskSplitter = NullTaskSplitter()
273 daemon = MailDaemon(myPreferences, myTaskSplitter)
274 daemon.run()
275