ab74de4331a1cd9a89f80e7d7272bccf72cf771f
[enigma2.git] / lib / python / Plugins / SystemPlugins / CrashlogAutoSubmit / plugin.py
1 from Plugins.Plugin import PluginDescriptor
2 from Components.config import config, getConfigListEntry, ConfigSubsection, ConfigText, ConfigSelection, ConfigYesNo,ConfigText
3 from Components.ConfigList import ConfigListScreen
4 from Components.ActionMap import ActionMap
5 from Components.Sources.StaticText import StaticText
6 from Components.Pixmap import Pixmap
7 from Screens.Screen import Screen
8 from Screens.VirtualKeyBoard import VirtualKeyBoard
9 from Screens.ChoiceBox import ChoiceBox
10 from Screens.MessageBox import MessageBox
11 from enigma import ePoint, eTPM
12 from Tools import Notifications
13
14 import os
15 from twisted.mail import smtp, relaymanager
16 import MimeWriter, mimetools, StringIO
17 from __init__ import bin2long, long2bin, rsa_pub1024, decrypt_block, validate_cert, read_random
18
19 config.plugins.crashlogautosubmit = ConfigSubsection()
20 config.plugins.crashlogautosubmit.sendmail = ConfigSelection(default = "send", choices = [
21         ("send", _("Always ask before sending")), ("send_always", _("Don't ask, just send")), ("send_never", _("Disable crashlog reporting"))])
22 config.plugins.crashlogautosubmit.sendlog = ConfigSelection(default = "rename", choices = [
23         ("delete", _("Delete crashlogs")), ("rename", _("Rename crashlogs"))])
24 config.plugins.crashlogautosubmit.attachemail = ConfigYesNo(default = False)
25 config.plugins.crashlogautosubmit.email = ConfigText(default = "myemail@home.com", fixed_size = False)
26 config.plugins.crashlogautosubmit.name = ConfigText(default = "Dreambox User", fixed_size = False)
27 config.plugins.crashlogautosubmit.sendAnonCrashlog = ConfigYesNo(default = True)
28 config.plugins.crashlogautosubmit.addNetwork = ConfigYesNo(default = False)
29 config.plugins.crashlogautosubmit.addWlan = ConfigYesNo(default = False)
30
31 class CrashlogAutoSubmitConfiguration(Screen, ConfigListScreen):
32
33         oldMailEntryValue = config.plugins.crashlogautosubmit.sendmail.value
34
35         skin = """
36                 <screen name="CrashlogAutoSubmitConfiguration" position="center,center" size="560,440" title="CrashlogAutoSubmit settings" >
37                         <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" alphatest="on" />
38                         <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" alphatest="on" />
39                         <widget source="key_red" render="Label" position="0,0" zPosition="1" size="140,40" font="Regular;20" halign="center" valign="center" backgroundColor="#9f1313" transparent="1" />
40                         <widget source="key_green" render="Label" position="140,0" zPosition="1" size="140,40" font="Regular;20" halign="center" valign="center" backgroundColor="#1f771f" transparent="1" />
41                         <widget name="config" zPosition="2" position="5,50" size="550,300" scrollbarMode="showOnDemand" transparent="1" />
42                         <ePixmap pixmap="skin_default/div-h.png" position="0,390" zPosition="10" size="560,2" transparent="1" alphatest="on" />
43                         <widget source="status" render="Label" position="10,400" size="540,40" zPosition="10" font="Regular;20" halign="center" valign="center" backgroundColor="#25062748" transparent="1"/>
44                         <widget name="VKeyIcon" pixmap="skin_default/buttons/key_text.png" position="10,420" zPosition="10" size="35,25" transparent="1" alphatest="on" />
45                         <widget name="HelpWindow" pixmap="skin_default/vkey_icon.png" position="160,325" zPosition="1" size="1,1" transparent="1" alphatest="on" />
46                 </screen>"""
47
48         def __init__(self, session):
49                 Screen.__init__(self, session)
50                 self.session = session
51                 self.MailEntry = None
52                 self.LogEntry = None
53                 self.addEmailEntry = None
54                 self.EmailEntry = None
55                 self.NameEntry = None
56                 self.AnonCrashlogEntry = None
57                 self.NetworkEntry = None
58                 self.WlanEntry = None
59                 self.msgCrashlogMailer = False
60
61                 self["shortcuts"] = ActionMap(["ShortcutActions", "SetupActions" ],
62                 {
63                         "ok": self.keySave,
64                         "cancel": self.keyCancel,
65                         "red": self.keyCancel,
66                         "green": self.keySave,
67                 }, -2)
68
69                 self["VirtualKB"] = ActionMap(["VirtualKeyboardActions" ],
70                 {
71                         "showVirtualKeyboard": self.KeyText,
72                 }, -1)
73
74                 self.list = []
75                 ConfigListScreen.__init__(self, self.list,session = self.session)
76                 self.createSetup()
77
78                 self["key_red"] = StaticText(_("Close"))
79                 self["key_green"] = StaticText(_("Save"))
80                 self["status"] = StaticText()
81                 self["VKeyIcon"] = Pixmap()
82                 self["HelpWindow"] = Pixmap()
83
84                 self["VKeyIcon"].hide()
85                 self["VirtualKB"].setEnabled(False)
86                 self.onShown.append(self.setWindowTitle)
87                 self.onClose.append(self.msgCrashlogNotifier)
88
89
90         def setWindowTitle(self):
91                 self.setTitle(_("CrashlogAutoSubmit settings..."))
92
93         def keyLeft(self):
94                 ConfigListScreen.keyLeft(self)
95                 self.newConfig()
96
97         def keyRight(self):
98                 ConfigListScreen.keyRight(self)
99                 self.newConfig()
100
101         def KeyText(self):
102                         if self["config"].getCurrent() == self.EmailEntry:
103                                 self.session.openWithCallback(self.EmailCallback, VirtualKeyBoard, title = (_("Please enter your email address here:")), text = config.plugins.crashlogautosubmit.email.value)
104                         if self["config"].getCurrent() == self.NameEntry:
105                                 self.session.openWithCallback(self.NameCallback, VirtualKeyBoard, title = (_("Please enter your name here (optional):")), text = config.plugins.crashlogautosubmit.name.value)
106
107         def EmailCallback(self, callback = None):
108                 if callback is not None and len(callback):
109                         config.plugins.crashlogautosubmit.email.setValue(callback)
110                         self["config"].invalidate(self.EmailEntry)
111
112         def NameCallback(self, callback = None):
113                 if callback is not None and len(callback):
114                         config.plugins.crashlogautosubmit.name.setValue(callback)
115                         self["config"].invalidate(self.NameEntry)
116
117         def createSetup(self):
118                 self.list = []
119                 self.MailEntry = getConfigListEntry(_("How to handle found crashlogs?"), config.plugins.crashlogautosubmit.sendmail)
120                 self.LogEntry = getConfigListEntry(_("What to do with submitted crashlogs?"), config.plugins.crashlogautosubmit.sendlog)
121                 self.addEmailEntry = getConfigListEntry(_("Include your email and name (optional) in the mail?"), config.plugins.crashlogautosubmit.attachemail)
122                 self.EmailEntry = getConfigListEntry(_("Your email address:"), config.plugins.crashlogautosubmit.email)
123                 self.NameEntry = getConfigListEntry(_("Your name (optional):"), config.plugins.crashlogautosubmit.name)
124                 self.AnonCrashlogEntry = getConfigListEntry(_("Anonymize crashlog?"), config.plugins.crashlogautosubmit.sendAnonCrashlog)
125                 self.NetworkEntry = getConfigListEntry(_("Add network configuration?"), config.plugins.crashlogautosubmit.addNetwork)
126                 self.WlanEntry = getConfigListEntry(_("Add WLAN configuration?"), config.plugins.crashlogautosubmit.addWlan)
127
128                 self.list.append( self.MailEntry )
129                 if config.plugins.crashlogautosubmit.sendmail.value is not "send_never":
130                         self.list.append( self.LogEntry )
131                         self.list.append( self.addEmailEntry )
132                         if config.plugins.crashlogautosubmit.attachemail.value is True:
133                                 self.list.append( self.EmailEntry )
134                                 self.list.append( self.NameEntry )
135                         self.list.append( self.AnonCrashlogEntry )
136                         self.list.append( self.NetworkEntry )
137                         self.list.append( self.WlanEntry )
138
139                 self["config"].list = self.list
140                 self["config"].l.setList(self.list)
141                 if not self.selectionChanged in self["config"].onSelectionChanged:
142                         self["config"].onSelectionChanged.append(self.selectionChanged)
143
144                 if not self.sendmailChanged in config.plugins.crashlogautosubmit.sendmail.notifiers:
145                         config.plugins.crashlogautosubmit.sendmail.notifiers.append(self.sendmailChanged)
146
147         def sendmailChanged(self, configElement):
148                 if configElement.value != CrashlogAutoSubmitConfiguration.oldMailEntryValue:
149                         self.msgCrashlogMailer = True
150                 else:
151                         self.msgCrashlogMailer = False
152
153         def newConfig(self):
154                 if self["config"].getCurrent() == self.MailEntry:
155                         self.createSetup()
156                 if self["config"].getCurrent() == self.addEmailEntry:
157                         self.createSetup()
158
159         def selectionChanged(self):
160                 current = self["config"].getCurrent()
161                 if current == self.MailEntry:
162                         self["status"].setText(_("Decide what should be done when crashlogs are found."))
163                         self.disableVKeyIcon()
164                 elif current == self.LogEntry:
165                         self["status"].setText(_("Decide what should happen to the crashlogs after submission."))
166                         self.disableVKeyIcon()
167                 elif current == self.addEmailEntry:
168                         self["status"].setText(_("Do you want to submit your email address and name so that we can contact you if needed?"))
169                         self.disableVKeyIcon()
170                 elif current == self.EmailEntry:
171                         self["status"].setText(_("Enter your email address so that we can contact you if needed."))
172                         self.enableVKeyIcon()
173                         self.showKeypad()
174                 elif current == self.NameEntry:
175                         self["status"].setText(_("Optionally enter your name if you want to."))
176                         self.enableVKeyIcon()
177                         self.showKeypad()
178                 elif current == self.AnonCrashlogEntry:
179                         self["status"].setText(_("Adds enigma2 settings and dreambox model informations like SN, rev... if enabled."))
180                         self.disableVKeyIcon()
181                 elif current == self.NetworkEntry:
182                         self["status"].setText(_("Adds network configuration if enabled."))
183                         self.disableVKeyIcon()
184                 elif current == self.WlanEntry:
185                         self["status"].setText(_("Adds wlan configuration if enabled."))
186                         self.disableVKeyIcon()
187
188         def enableVKeyIcon(self):
189                 self["VKeyIcon"].show()
190                 self["VirtualKB"].setEnabled(True)
191
192         def showKeypad(self):
193                 current = self["config"].getCurrent()
194                 helpwindowpos = self["HelpWindow"].getPosition()
195                 if hasattr(current[1], 'help_window'):
196                         if current[1].help_window.instance is not None:
197                                 current[1].help_window.instance.show()
198                                 current[1].help_window.instance.move(ePoint(helpwindowpos[0],helpwindowpos[1]))
199
200         def disableVKeyIcon(self):
201                 self["VKeyIcon"].hide()
202                 self["VirtualKB"].setEnabled(False)
203
204         def hideKeypad(self):
205                 current = self["config"].getCurrent()
206                 if hasattr(current[1], 'help_window'):
207                         if current[1].help_window.instance is not None:
208                                 current[1].help_window.instance.hide()
209
210         def cancelConfirm(self, result):
211                 if not result:
212                         self.showKeypad()
213                         return
214                 for x in self["config"].list:
215                         x[1].cancel()
216                 self.close()
217
218         def keyCancel(self):
219                 print "cancel"
220                 if self["config"].isChanged():
221                         self.hideKeypad()
222                         self.session.openWithCallback(self.cancelConfirm, MessageBox, _("Really close without saving settings?"))
223                 else:
224                         self.close()
225
226         def keySave(self):
227                 print "saving"
228                 CrashlogAutoSubmitConfiguration.oldMailEntryValue = config.plugins.crashlogautosubmit.sendmail.value
229                 ConfigListScreen.keySave(self)
230
231         def msgCrashlogNotifier(self):
232                 if self.msgCrashlogMailer is True:
233                         try:
234                                 callCrashMailer(True, self.session)
235                         except AttributeError:
236                                 print "error, not restarting crashlogmailer"
237
238
239 def mxServerFound(mxServer,session):
240         print "[CrashlogAutoSubmit] - mxServerFound -->", mxServer
241         crashLogFilelist = []
242         message = StringIO.StringIO()
243         writer = MimeWriter.MimeWriter(message)
244         mailFrom = "enigma2@crashlog.dream-multimedia-tv.de"
245         mailTo = "enigma2@crashlog.dream-multimedia-tv.de"
246         subject = "Automatically generated crashlogmail"
247         # Define the main body headers.
248         writer.addheader('To', "dream-multimedia-crashlogs <enigma2@crashlog.dream-multimedia-tv.de>")
249         writer.addheader('From', "CrashlogAutoSubmitter <enigma2@crashlog.dream-multimedia-tv.de>")
250         writer.addheader('Subject', str(subject))
251         writer.addheader('Date', smtp.rfc822date())
252         if config.plugins.crashlogautosubmit.attachemail.value is True:
253                 if  str(config.plugins.crashlogautosubmit.email.value) != "myemail@home.com":
254                         writer.addheader('Reply-To', str(str(config.plugins.crashlogautosubmit.email.value)))
255         writer.addheader('MIME-Version', '1.0')
256         writer.startmultipartbody('mixed')
257         # start with a text/plain part
258         part = writer.nextpart()
259         body = part.startbody('text/plain')
260         part.flushheaders()
261         # Define the message body
262         body_text1 = "\nHello\n\nHere are some crashlogs i found for you.\n"
263         if  str(config.plugins.crashlogautosubmit.email.value) == "myemail@home.com":
264                 user_email = ""
265         else:
266                 user_email = "\nUser supplied email address: " + str(config.plugins.crashlogautosubmit.email.value)
267         if str(config.plugins.crashlogautosubmit.name.value) ==  "Dreambox User":
268                 user_name = ""
269         else:
270                 user_name = "\n\nOptional supplied name: " + str(config.plugins.crashlogautosubmit.name.value)
271         body_text2 = "\n\nThis is an automatically generated email from the CrashlogAutoSubmit plugin.\n\n\nHave a nice day.\n"
272         body_text = body_text1 + user_email + user_name + body_text2
273         body.write(body_text)
274
275         list = (
276                 (_("Yes"), "send"),
277                 (_("Yes, and don't ask again"), "send_always"),
278                 (_("No, not now"), "send_not"),
279                 (_("No, send them never"), "send_never")
280         )
281
282         def handleError(error):
283                 print "[CrashlogAutoSubmit] - Message send Error -->", error.getErrorMessage()
284
285         def handleSuccess(result):
286                 print "[CrashlogAutoSubmit] - Message sent successfully -->",result
287                 if len(crashLogFilelist):
288                         for crashlog in crashLogFilelist:
289                                 if config.plugins.crashlogautosubmit.sendlog.value == "delete":
290                                         os.remove(crashlog)
291                                 elif config.plugins.crashlogautosubmit.sendlog.value == "rename":
292                                         currfilename = str(os.path.basename(crashlog))
293                                         newfilename = "/media/hdd/" + currfilename + ".sent"
294                                         os.rename(crashlog,newfilename)
295
296         def send_mail():
297                 print "[CrashlogAutoSubmit] - send_mail"
298                 if len(crashLogFilelist):
299                         for crashlog in crashLogFilelist:
300                                 filename = str(os.path.basename(crashlog))
301                                 subpart = writer.nextpart()
302                                 subpart.addheader("Content-Transfer-Encoding", 'base64')
303                                 subpart.addheader("Content-Disposition",'attachment; filename="%s"' % filename)
304                                 subpart.addheader('Content-Description', 'Enigma2 crashlog')
305                                 body = subpart.startbody("%s; name=%s" % ('application/octet-stream', filename))
306                                 mimetools.encode(open(crashlog, 'rb'), body, 'base64')
307                 writer.lastpart()
308                 sending = smtp.sendmail(str(mxServer), mailFrom, mailTo, message.getvalue())
309                 sending.addCallback(handleSuccess).addErrback(handleError)
310
311         def handleAnswer(answer):
312                 answer = answer and answer[1]
313                 print "[CrashlogAutoSubmit] - handleAnswer --> ",answer
314                 if answer == "send":
315                         send_mail()
316                 elif answer == "send_always":
317                         config.plugins.crashlogautosubmit.sendmail.value = "send_always"
318                         config.plugins.crashlogautosubmit.sendmail.save()
319                         config.plugins.crashlogautosubmit.save()
320                         config.plugins.save()
321                         config.save()
322                         send_mail()
323                 elif answer in ( None, "send_never"):
324                         config.plugins.crashlogautosubmit.sendmail.value = "send_never"
325                         config.plugins.crashlogautosubmit.sendmail.save()
326                         config.plugins.crashlogautosubmit.save()
327                         config.plugins.save()
328                         config.save()
329                 elif answer == "send_not":
330                         print "[CrashlogAutoSubmit] - not sending crashlogs for this time."
331
332         for crashlog in os.listdir('/media/hdd'):
333                 if crashlog.startswith("enigma2_crash_") and crashlog.endswith(".log"):
334                         print "[CrashlogAutoSubmit] - found crashlog: ",os.path.basename(crashlog)
335                         crashLogFilelist.append('/media/hdd/' + crashlog)
336
337         if len(crashLogFilelist):
338                 if config.plugins.crashlogautosubmit.sendmail.value == "send":
339                         Notifications.AddNotificationWithCallback(handleAnswer, ChoiceBox, title=_("Crashlogs found!\nSend them to Dream Multimedia?"), list = list)
340                 elif config.plugins.crashlogautosubmit.sendmail.value == "send_always":
341                         send_mail()
342         else:
343                 print "[CrashlogAutoSubmit] - no crashlogs found."
344
345
346 def getMailExchange(host):
347         print "[CrashlogAutoSubmit] - getMailExchange"
348         return relaymanager.MXCalculator().getMX(host).addCallback(_gotMXRecord)
349
350 def _gotMXRecord(mxRecord):
351         return str(mxRecord.name)
352
353
354 def startMailer(session):
355         if config.plugins.crashlogautosubmit.sendmail.value == "send_never":
356                 print "[CrashlogAutoSubmit] - not starting CrashlogAutoSubmit"
357                 return False
358
359         def gotMXServer(mxServer):
360                 print "[CrashlogAutoSubmit] gotMXServer: ",mxServer
361                 mxServerFound(mxServer,session)
362
363         def handleMXError(error):
364                 print "[CrashlogAutoSubmit] - MX resolve ERROR:", error.getErrorMessage()
365
366         if not config.misc.firstrun.value:
367                 getMailExchange('crashlog.dream-multimedia-tv.de').addCallback(gotMXServer).addErrback(handleMXError)
368
369
370 def callCrashMailer(result,session):
371         if result is True:
372                 print "[CrashlogAutoSubmit] - config changed"
373                 startMailer(session)
374         else:
375                 print "[CrashlogAutoSubmit] - config not changed"
376
377
378 def autostart(reason, **kwargs):
379         print "[CrashlogAutoSubmit] - autostart"
380         try:
381                 device = open("/proc/stb/info/model", "r").readline().strip()
382         except:
383                 device = ""     
384         if device != "dm7025":
385                 rootkey = ['\x9f', '|', '\xe4', 'G', '\xc9', '\xb4', '\xf4', '#', '&', '\xce', '\xb3', '\xfe', '\xda', '\xc9', 'U', '`', '\xd8', '\x8c', 's', 'o', '\x90', '\x9b', '\\', 'b', '\xc0', '\x89', '\xd1', '\x8c', '\x9e', 'J', 'T', '\xc5', 'X', '\xa1', '\xb8', '\x13', '5', 'E', '\x02', '\xc9', '\xb2', '\xe6', 't', '\x89', '\xde', '\xcd', '\x9d', '\x11', '\xdd', '\xc7', '\xf4', '\xe4', '\xe4', '\xbc', '\xdb', '\x9c', '\xea', '}', '\xad', '\xda', 't', 'r', '\x9b', '\xdc', '\xbc', '\x18', '3', '\xe7', '\xaf', '|', '\xae', '\x0c', '\xe3', '\xb5', '\x84', '\x8d', '\r', '\x8d', '\x9d', '2', '\xd0', '\xce', '\xd5', 'q', '\t', '\x84', 'c', '\xa8', ')', '\x99', '\xdc', '<', '"', 'x', '\xe8', '\x87', '\x8f', '\x02', ';', 'S', 'm', '\xd5', '\xf0', '\xa3', '_', '\xb7', 'T', '\t', '\xde', '\xa7', '\xf1', '\xc9', '\xae', '\x8a', '\xd7', '\xd2', '\xcf', '\xb2', '.', '\x13', '\xfb', '\xac', 'j', '\xdf', '\xb1', '\x1d', ':', '?']
386                 etpm = eTPM()
387                 l2cert = etpm.getCert(eTPM.TPMD_DT_LEVEL2_CERT)
388                 if l2cert is None:
389                         return
390                 l2key = validate_cert(l2cert, rootkey)
391                 if l2key is None:
392                         return
393                 l3cert = etpm.getCert(eTPM.TPMD_DT_LEVEL3_CERT)
394                 if l3cert is None:
395                         return
396                 l3key = validate_cert(l3cert, l2key)
397                 if l3key is None:
398                         return
399                 rnd = read_random()
400                 if rnd is None:
401                         return
402                 val = etpm.challenge(rnd)
403                 result = decrypt_block(val, l3key)
404         if device == "dm7025" or result[80:88] == rnd:
405                 if "session" in kwargs:
406                         try:
407                                 startMailer(kwargs["session"])
408                         except ImportError, e:
409                                 print "[CrashlogAutoSubmit] Twisted-mail not available, not starting CrashlogAutoSubmitter", e
410
411
412 def openconfig(session, **kwargs):
413         session.open(CrashlogAutoSubmitConfiguration)
414
415
416 def selSetup(menuid, **kwargs):
417         if menuid != "system":
418                 return [ ]
419
420         return [(_("Crashlog settings"), openconfig, "crashlog_config", 70)]
421
422
423 def Plugins(**kwargs):
424         return [PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], needsRestart = False, fnc = autostart),
425                 PluginDescriptor(name=_("CrashlogAutoSubmit"), description=_("CrashlogAutoSubmit settings"),where=PluginDescriptor.WHERE_MENU, needsRestart = False, fnc=selSetup)]
426