diff --git a/management/univention-system-info/system-info-upload.py b/management/univention-system-info/system-info-upload.py new file mode 100755 index 0000000..883b3e5 --- /dev/null +++ b/management/univention-system-info/system-info-upload.py @@ -0,0 +1,137 @@ +#!/usr/bin/python2.6 +# +# Univention System Info +# Stores files uploaded via HTTP POST +# +# Copyright 2009-2014 Univention GmbH +# +# http://www.univention.de/ +# +# All rights reserved. +# +# The source code of this program is made available +# under the terms of the GNU Affero General Public License version 3 +# (GNU AGPL V3) as published by the Free Software Foundation. +# +# Binary versions of this program provided by Univention to you as +# well as other copyrighted, protected or trademarked materials like +# Logos, graphics, fonts, specific documentations and configurations, +# cryptographic keys etc. are subject to a license agreement between +# you and Univention and not subject to the GNU AGPL V3. +# +# In the case you use this program under the terms of the GNU AGPL V3, +# the program is provided in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License with the Debian GNU/Linux or Univention distribution in file +# /usr/share/common-licenses/AGPL-3; if not, see +# . + +import univention.config_registry as ucr + +import cgi +from email.MIMEText import MIMEText +import os +import socket +import smtplib +import time +import tarfile + +configRegistry = ucr.ConfigRegistry() +configRegistry.load() + + +def save_uploaded_file(form_field, upload_dir, max_size): + form = cgi.FieldStorage() + if form_field not in form: + return (False, None, None) + fileitem = form[form_field] + if not fileitem.filename.endswith('.tar.gz'): + return (False, fileitem.filename, None) + basename = fileitem.filename[:-len('.tar.gz')] + filename = os.path.join(upload_dir, fileitem.filename) + if not os.path.abspath(filename).startswith(upload_dir): + return (False, None, None) + if os.path.exists(filename): + filename += '.%s' % str(time.time() * 100) + fout = file(filename, 'wb') + size = 0 + while True: + chunk = fileitem.file.read(100000) + if not chunk: + break + size += len(chunk) + if size > max_size: + fout.close() + os.unlink(fout.name) + return (False, filename, basename) + fout.write(chunk) + fout.close() + + return (True, filename, basename) + +# make HTTP happy +print 'Content-Type: text/plain\n' + +path = configRegistry.get('umc/sysinfo/upload/path', '/var/lib/univention-system-info/archives/') + +ok, filepath, basename = save_uploaded_file('filename', path, configRegistry.get('umc/sysinfo/upload/size', '2000000')) +filename = filepath[len(path):] + +if not ok: + print 'ERROR: wrong file type or file too big' +else: + infoTable = '' + # get contents of info file + try: + tarFile = tarfile.open(os.path.join(path, filepath), 'r') + infoTable = '\n' + infoDict = {} + for line in tarFile.extractfile(tarFile.getmember('%s/info' % basename)).readlines(): + key, val = line.strip().split(':', 1) + if isinstance(key, unicode): + key = key.encode('UTF-8') + val = val.encode('UTF-8') + infoTable += '\n' % (key.strip(), val.strip()) + infoDict[key.strip()] = val.strip() + infoTable += '
%s:%s
' + except: + import traceback + infoTable += traceback.format_exc() + + msg = MIMEText(''' + + + + +

A new Univention system info archive has been uploaded.

+%s + +

+Archive: %s +

+ + +''' % (infoTable, socket.getfqdn(), filename, filename), 'html', 'utf-8') + if 'Ticket No' in infoDict.keys() and infoDict['Ticket No']: + msg['Subject'] = '[Ticket#%s] Univention System Info Upload' % (infoDict['Ticket No'],) + else: + msg['Subject'] = 'Univention System Info Upload' + if 'Email' in infoDict.keys() and infoDict['Email']: + sender = infoDict['Email'] + else: + sender = configRegistry.get('umc/sysinfo/upload/sender', 'root') + recipient = configRegistry.get('umc/sysinfo/upload/recipient', 'feedback@univention.de') + + msg['From'] = sender + msg['To'] = recipient + + s = smtplib.SMTP() + s.connect() + s.sendmail(sender, [recipient], msg.as_string()) + s.close() + print 'OK: file saved successfully' diff --git a/management/univention-system-info/umc/de.po b/management/univention-system-info/umc/de.po index 01e946b..896db25 100644 --- a/management/univention-system-info/umc/de.po +++ b/management/univention-system-info/umc/de.po @@ -15,5 +15,5 @@ msgstr "" msgid "Overview of local systems hardware information" msgstr "Überblick der Hardwareinformationen des lokalen Systems" -msgid "Hardware information" -msgstr "Hardwareinformationen" +msgid "Support module" +msgstr "Support Modul" diff --git a/management/univention-system-info/umc/js/de.po b/management/univention-system-info/umc/js/de.po index ca54322..349083e 100644 --- a/management/univention-system-info/umc/js/de.po +++ b/management/univention-system-info/umc/js/de.po @@ -1,10 +1,10 @@ # This file is auto-generated by the dh-umc tools and should not be edited! -#: umc/js/sysinfo.js:165 +#: umc/js/sysinfo.js:102 msgid "" msgstr "" "Project-Id-Version: univention-management-console-module-sysinfo\n" "Report-Msgid-Bugs-To: packages@univention.de\n" -"POT-Creation-Date: 2014-12-18 16:16+0100\n" +"POT-Creation-Date: 2016-04-05 15:34+0200\n" "PO-Revision-Date: 2016-01-14 11:26+0100\n" "Last-Translator: Univention GmbH \n" "Language-Team: Univention GmbH \n" @@ -13,59 +13,13 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: umc/js/sysinfo.js:144 +#: umc/js/sysinfo.js:123 msgid "" -"

Additionally to the information listed above some more details about the " -"system has been collected. The whole set of collected data that will be " -"transmitted to Univention can be downloaded at the following URL:

" +"

After clicking \"Next\" the information will be gathered and uploaded to " +"Univention.

" msgstr "" -"

Zusätzlich zu den oben angezeigten Informationen wurden weitere Details " -"über das System ermittelt. Die gesamten Informationen, die an Univention " -"übertragen werden sollen, können unter der folgenden URL heruntergeladen " -"werden:

" -#: umc/js/sysinfo.js:94 -msgid "" -"

If a Univention Support Engineer has asked you to provide this " -"information, then please insert the ticket number of the related support " -"ticket into the following text field. The ticket number can be found in the " -"subject of a support mail of the ticket. This information will speed up the " -"processing of the ticket.

" -msgstr "" -"

Wenn Sie durch den Univention-Support gebeten worden sind, diese " -"Informationen zu ermitteln, dann geben Sie bitte im folgenden Feld die " -"Ticketnummer an, welche sich dem Betreff der Support-Mails entnehmen lässt. " -"Die Ticketnummer hilft bei der Zuordnung zu dem Supportfall und beschleunigt " -"die Bearbeitung.

" - -#: umc/js/sysinfo.js:103 -msgid "" -"

In the next step the information about the hardware of your system will " -"be collected and a summary will be shown. During this step, no information " -"will be sent to Univention.

" -msgstr "" -"

Im nächsten Schritt werden die Informationen des Systems ermittelt sowie " -"eine Zusammenfassung darüber angezeigt. Während dieses Vorganges werden " -"keine Informationen an Univention übertragen.

" - -#: umc/js/sysinfo.js:169 -msgid "" -"

The collected information can be transfered to Univention by uploading " -"the data or by sending it via mail. Please select the corresponding option." -msgstr "" -"

Die ermittelten Informationen können entweder hochgeladen oder per Mail " -"an Univention geschickt werden. Bitte wählen Sie die gewünschte Variante.

" - -#: umc/js/sysinfo.js:115 -msgid "" -"

The following information has been collected and will be transfered to " -"Univention with your acceptance.

" -msgstr "" -"

Die folgenden Informationen wurden ermittelt und werden mit Ihrer " -"Zustimmung an Univention übertragen:

" - -#: umc/js/sysinfo.js:265 +#: umc/js/sysinfo.js:237 msgid "" "

The information could not be transfered to Univention.

You can send " "them as email!

" @@ -73,7 +27,7 @@ msgstr "" "

Die Informationen konnten nicht an Univention übertragen werden.

Sie können die Information per E-Mail senden!

" -#: umc/js/sysinfo.js:189 +#: umc/js/sysinfo.js:136 msgid "" "

The information were transfered to Univention successfully.

Thank " "you very much for your support!

" @@ -81,11 +35,12 @@ msgstr "" "

Die Informationen wurden erfolgreich an Univention übertragen.

Vielen Dank für Ihre Unterstützung!

" -#: umc/js/sysinfo.js:61 +#: umc/js/sysinfo.js:64 +#, fuzzy msgid "" -"

This module collects information about the hardware of your system. This " -"might be helpful in connection with a support case. By transmitting the data " -"to Univention you provide the information on which platforms UCS is " +"

This module collects information about the configuration of your system. " +"This might be helpful in connection with a support case. By transmitting the " +"data to Univention you provide the information on which platforms UCS is " "currently used and therefore should be supported by newer versions. In the " "following procedure you will be informed in detail about the each step.

No information is transmitted without your acceptance

" @@ -98,47 +53,52 @@ msgstr "" "genau informiert.

Es werden keine Informationen ohne Ihre Einwilligung " "an Univention übermittelt

" -#: umc/js/sysinfo.js:148 umc/js/sysinfo.js:203 umc/js/sysinfo.js:322 +#: umc/js/sysinfo.js:106 +#, fuzzy +msgid "" +"

Which information do you want to send to the Univention support. Please " +"select the corresponding option.

" +msgstr "" +"

Die ermittelten Informationen können entweder hochgeladen oder per Mail " +"an Univention geschickt werden. Bitte wählen Sie die gewünschte Variante.

" + +#: umc/js/sysinfo.js:263 umc/js/sysinfo.js:334 msgid "Archive with system information" msgstr "Archiv mit den Systeminformationen" -#: umc/js/sysinfo.js:119 -msgid "CPU" -msgstr "CPU" - -#: umc/js/sysinfo.js:110 -msgid "Collected data" -msgstr "Ermittelte Daten" +#: umc/js/sysinfo.js:78 +msgid "Contact support with existing ticket number" +msgstr "" -#: umc/js/sysinfo.js:73 -msgid "Comment" -msgstr "Kommentar" +#: umc/js/sysinfo.js:296 +msgid "Downloading newest Version of Univention-System-Information" +msgstr "" -#: umc/js/sysinfo.js:60 -msgid "General information" -msgstr "Allgemeine Informationen" +#: umc/js/sysinfo.js:85 +msgid "E-Mail" +msgstr "" -#: umc/js/sysinfo.js:140 -msgid "Graphics card" -msgstr "Grafikkarte" +#: umc/js/sysinfo.js:68 +msgid "Error description" +msgstr "" -#: umc/js/sysinfo.js:81 -msgid "" -"If this is related to a support case the next step will be to enter the " -"ticket number. Otherwise the information about your system will be collected " -"and a summary is shown." +#: umc/js/sysinfo.js:326 +msgid "Executing USI" msgstr "" -"Wenn sich dies auf einen Supportfall bezieht, wird im nächsten Schritt die " -"Ticketnummer abgefragt. Anderenfalls werden die Informationen zu Ihrem " -"System ermittelt und eine Zusammenfassung angezeigt." -#: umc/js/sysinfo.js:152 -msgid "" -"In the following step two possibilities to transmit the information to " -"Univention will be described." +#: umc/js/sysinfo.js:243 +#, fuzzy +msgid "Gathering Hardware informations" +msgstr "Allgemeine Informationen" + +#: umc/js/sysinfo.js:257 +#, fuzzy +msgid "Gathering System informations" +msgstr "Archiv mit den Systeminformationen" + +#: umc/js/sysinfo.js:111 +msgid "General system information including logfiles, config files, etc." msgstr "" -"Im folgenden Schritt werden Ihnen zwei Möglichkeiten geboten, die " -"Informationen an Univention zu übermitteln." #: umc/js/sysinfo/lib.js:47 msgid "" @@ -148,35 +108,26 @@ msgstr "" "Informationen zu diesem Fehler werden, zusammen mit Angaben über das " "Betriebssystem, an den Hersteller übermittelt." -#: umc/js/sysinfo.js:65 -msgid "Manufacturer" -msgstr "Hersteller" - -#: umc/js/sysinfo.js:130 -msgid "Memory" -msgstr "Arbeitsspeicher" - -#: umc/js/sysinfo.js:174 -msgid "Method" -msgstr "Methode" - -#: umc/js/sysinfo.js:69 -msgid "Model" -msgstr "Modell" - -#: umc/js/sysinfo.js:135 -msgid "Network adapter" -msgstr "Netzwerkkarte" +#: umc/js/sysinfo.js:118 +msgid "Information about the system's hardware" +msgstr "" -#: umc/js/sysinfo.js:125 -msgid "Number of CPUs" -msgstr "Anzahl der CPUs" +#: umc/js/sysinfo.js:243 umc/js/sysinfo.js:257 umc/js/sysinfo.js:267 +#: umc/js/sysinfo.js:296 umc/js/sysinfo.js:321 umc/js/sysinfo.js:326 +#: umc/js/sysinfo.js:338 +msgid "Processing" +msgstr "" #: umc/js/sysinfo/lib.js:59 msgid "Remarks (e.g. steps to reproduce) (optional)" msgstr "Bemerkungen (z.B. Schritte, den Fehler nachzustellen) (optional)" -#: umc/js/sysinfo.js:177 umc/js/sysinfo.js:339 +#: umc/js/sysinfo.js:101 +#, fuzzy +msgid "Select information" +msgstr "Support Informationen" + +#: umc/js/sysinfo.js:287 msgid "Send mail" msgstr "Mail senden" @@ -192,30 +143,27 @@ msgstr "Das Versenden der Informationen zum Hersteller ist fehlgeschlagen" msgid "Show error message" msgstr "Fehlermeldung anzeigen" -#: umc/js/sysinfo.js:89 -msgid "Support information" -msgstr "Support Informationen" +#: umc/js/sysinfo.js:321 +msgid "Starting USI process" +msgstr "" #: umc/js/sysinfo/lib.js:69 msgid "Thank you for your help" msgstr "Vielen Dank für Ihre Hilfe" -#: umc/js/sysinfo.js:77 -msgid "This is related to a support case" -msgstr "Dies bezieht sich auf einen Supportfall" - -#: umc/js/sysinfo.js:98 +#: umc/js/sysinfo.js:72 msgid "Ticket" msgstr "Ticket" -#: umc/js/sysinfo.js:199 +#: umc/js/sysinfo.js:239 +#, fuzzy msgid "" "To transfer the information via mail please follow these steps:" "
  1. Download the archive with the collected information and save it on " -"your local system
  2. Click on link Send mail to open your mail " -"program
  3. Attach the downloaded archive to the mail and send it to " -"Univention
  4. End this assistant by clicking on the button Finish
" +"your local system
  • Click on li nk Send mail to open your " +"mail program
  • Attach the downloaded archive to the mail and send it " +"to Univention
  • End this assistant by clicking on the button " +"Finish
  • " msgstr "" "Für das Versenden der Informationen per Mail gehen Sie bitte wie folgt vor:" "
    1. Laden Sie die Datei mit den Informationen herunter und speichern " @@ -225,26 +173,24 @@ msgstr "" "diese.
    2. Beenden Sie diesen Assistenten durch Klicken auf die " "Schaltfläche Fertigstellen
    " -#: umc/js/sysinfo.js:164 -msgid "Transfer the information" -msgstr "Übertragung der Informationen" - -#: umc/js/sysinfo.js:194 -msgid "Transfer via mail" -msgstr "Übertragung via Mail" - -#: umc/js/sysinfo.js:184 +#: umc/js/sysinfo.js:131 msgid "Transfered successfully" msgstr "Übertragung erfolgreich" -#: umc/js/sysinfo.js:176 -msgid "Upload" -msgstr "Hochladen" +#: umc/js/sysinfo.js:63 +#, fuzzy +msgid "Univention-system-information" +msgstr "Archiv mit den Systeminformationen" -#: umc/js/sysinfo.js:264 +#: umc/js/sysinfo.js:236 msgid "Uploading failed" msgstr "Hochladen fehlgeschlagen" +#: umc/js/sysinfo.js:267 umc/js/sysinfo.js:338 +#, fuzzy +msgid "Uploading informations" +msgstr "Hochladen fehlgeschlagen" + #: umc/js/sysinfo/lib.js:74 msgid "You can also send the information via mail:" msgstr "Sie können die Informationen auch per Mail schicken:" @@ -252,3 +198,104 @@ msgstr "Sie können die Informationen auch per Mail schicken:" #: umc/js/sysinfo/lib.js:63 msgid "Your email address (optional)" msgstr "Ihre E-Mail Adresse (optional)" + +#: umc/js/sysinfo.js:90 +msgid "phone number" +msgstr "" + +#~ msgid "" +#~ "

    Additionally to the information listed above some more details about " +#~ "the system has been collected. The whole set of collected data that will " +#~ "be transmitted to Univention can be downloaded at the following URL:

    " +#~ msgstr "" +#~ "

    Zusätzlich zu den oben angezeigten Informationen wurden weitere " +#~ "Details über das System ermittelt. Die gesamten Informationen, die an " +#~ "Univention übertragen werden sollen, können unter der folgenden URL " +#~ "heruntergeladen werden:

    " + +#~ msgid "" +#~ "

    If a Univention Support Engineer has asked you to provide this " +#~ "information, then please insert the ticket number of the related support " +#~ "ticket into the following text field. The ticket number can be found in " +#~ "the subject of a support mail of the ticket. This information will speed " +#~ "up the processing of the ticket.

    " +#~ msgstr "" +#~ "

    Wenn Sie durch den Univention-Support gebeten worden sind, diese " +#~ "Informationen zu ermitteln, dann geben Sie bitte im folgenden Feld die " +#~ "Ticketnummer an, welche sich dem Betreff der Support-Mails entnehmen " +#~ "lässt. Die Ticketnummer hilft bei der Zuordnung zu dem Supportfall und " +#~ "beschleunigt die Bearbeitung.

    " + +#~ msgid "" +#~ "

    In the next step the information about the hardware of your system " +#~ "will be collected and a summary will be shown. During this step, no " +#~ "information will be sent to Univention.

    " +#~ msgstr "" +#~ "

    Im nächsten Schritt werden die Informationen des Systems ermittelt " +#~ "sowie eine Zusammenfassung darüber angezeigt. Während dieses Vorganges " +#~ "werden keine Informationen an Univention übertragen.

    " + +#~ msgid "" +#~ "

    The following information has been collected and will be transfered to " +#~ "Univention with your acceptance.

    " +#~ msgstr "" +#~ "

    Die folgenden Informationen wurden ermittelt und werden mit Ihrer " +#~ "Zustimmung an Univention übertragen:

    " + +#~ msgid "CPU" +#~ msgstr "CPU" + +#~ msgid "Collected data" +#~ msgstr "Ermittelte Daten" + +#~ msgid "Comment" +#~ msgstr "Kommentar" + +#~ msgid "Graphics card" +#~ msgstr "Grafikkarte" + +#~ msgid "" +#~ "If this is related to a support case the next step will be to enter the " +#~ "ticket number. Otherwise the information about your system will be " +#~ "collected and a summary is shown." +#~ msgstr "" +#~ "Wenn sich dies auf einen Supportfall bezieht, wird im nächsten Schritt " +#~ "die Ticketnummer abgefragt. Anderenfalls werden die Informationen zu " +#~ "Ihrem System ermittelt und eine Zusammenfassung angezeigt." + +#~ msgid "" +#~ "In the following step two possibilities to transmit the information to " +#~ "Univention will be described." +#~ msgstr "" +#~ "Im folgenden Schritt werden Ihnen zwei Möglichkeiten geboten, die " +#~ "Informationen an Univention zu übermitteln." + +#~ msgid "Manufacturer" +#~ msgstr "Hersteller" + +#~ msgid "Memory" +#~ msgstr "Arbeitsspeicher" + +#~ msgid "Method" +#~ msgstr "Methode" + +#~ msgid "Model" +#~ msgstr "Modell" + +#~ msgid "Network adapter" +#~ msgstr "Netzwerkkarte" + +#~ msgid "Number of CPUs" +#~ msgstr "Anzahl der CPUs" + +#~ msgid "This is related to a support case" +#~ msgstr "Dies bezieht sich auf einen Supportfall" + +#~ msgid "Transfer the information" +#~ msgstr "Übertragung der Informationen" + +#~ msgid "Transfer via mail" +#~ msgstr "Übertragung via Mail" + +#~ msgid "Upload" +#~ msgstr "Hochladen" diff --git a/management/univention-system-info/umc/js/sysinfo.js b/management/univention-system-info/umc/js/sysinfo.js index e8b2858..3f40d17 100644 --- a/management/univention-system-info/umc/js/sysinfo.js +++ b/management/univention-system-info/umc/js/sysinfo.js @@ -15,7 +15,7 @@ * cryptographic keys etc. are subject to a license agreement between * you and Univention and not subject to the GNU AGPL V3. * - * In the case you use this program under the terms of the GNU AGPL V3, + * In thGe case you use this program under the terms of the GNU AGPL V3, * the program is provided in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -42,9 +42,11 @@ define([ "umc/widgets/Text", "umc/widgets/TextArea", "umc/widgets/CheckBox", + "umc/widgets/RadioButton", + "umc/widgets/ProgressBar", "umc/i18n!umc/modules/sysinfo", "umc/modules/sysinfo/lib" // FIXME: needs to live here to be loaded -], function(declare, lang, array, topic, tools, Module, Wizard, StandbyMixin, ComboBox, TextBox, Text, TextArea, CheckBox, _) { +], function(declare, lang, array, topic, tools, Module, Wizard, StandbyMixin, ComboBox, TextBox, Text, TextArea, CheckBox, RadioButton, ProgressBar, _) { var SysinfoWizard = declare("umc.modules.sysinfo.Wizard", [ Wizard, StandbyMixin ], { @@ -53,132 +55,77 @@ define([ _archiveFilename: null, _archiveLink: null, _mailLink: null, + _ticketNumberUsed: false, constructor: function() { this.pages = [{ name: 'general', - headerText: _('General information'), - helpText: _('

    This module collects information about the hardware of your system. This might be helpful in connection with a support case. By transmitting the data to Univention you provide the information on which platforms UCS is currently used and therefore should be supported by newer versions. In the following procedure you will be informed in detail about the each step.

    No information is transmitted without your acceptance

    '), + headerText: _('Univention-system-information'), + helpText: _('

    This module collects information about the configuration of your system. This might be helpful in connection with a support case. By transmitting the data to Univention you provide the information on which platforms UCS is currently used and therefore should be supported by newer versions. In the following procedure you will be informed in detail about the each step.

    No information is transmitted without your acceptance

    '), widgets: [{ - type: TextBox, - name: 'manufacturer', - label: _('Manufacturer') - }, { - type: TextBox, - name: 'model', - label: _('Model') - }, { type: TextArea, name: 'comment', - label: _('Comment') + label: _('Error description') + }, { + type: TextBox, + name: 'ticketNumber', + label: _('Ticket'), + value: '', + visible: this._ticketNumberUsed }, { type: CheckBox, name: 'supportBox', - label: _('This is related to a support case') + label: _('Contact support with existing ticket number'), + onChange: lang.hitch(this, function(value) { + this._toggleTicketNumberUsed(); + }) }, { - type: Text, - name: 'secondText', - label: _('If this is related to a support case the next step will be to enter the ticket number. Otherwise the information about your system will be collected and a summary is shown.') - }], - layout: [['manufacturer', 'model'], - ['comment'], - ['supportBox'], - ['secondText']] - }, { - name: 'support', - headerText: _('Support information'), - helpText: '', - widgets: [{ - type: Text, - name: 'firstText', - content: _('

    If a Univention Support Engineer has asked you to provide this information, then please insert the ticket number of the related support ticket into the following text field. The ticket number can be found in the subject of a support mail of the ticket. This information will speed up the processing of the ticket.

    ') + type: TextBox, + name: 'emailBox', + label: _('E-Mail'), + required: true }, { type: TextBox, - name: 'ticket', - label: _('Ticket'), - value: '' + name: 'phoneBox', + label: _('phone number') }, { - type: Text, - name: 'secondText', - content: _('

    In the next step the information about the hardware of your system will be collected and a summary will be shown. During this step, no information will be sent to Univention.

    ') }], - layout: [['firstText'], - ['ticket'], - ['secondText']] + layout: [, + ['supportBox'], + ['ticketNumber'], + ['comment'], + ['emailBox'], + ['phoneBox']] }, { - name: 'collect', - headerText: _('Collected data'), - helpText: '', + name: 'chooseInfo', + headerText: _('Select information'), + helpText: _(''), widgets: [{ type: Text, name: 'firstText', - content: _('

    The following information has been collected and will be transfered to Univention with your acceptance.

    ') - }, { - type: TextBox, - name: 'cpu', - label: _('CPU'), - value: '' - }, { - type: TextBox, - name: 'num_cpu', - sizeClass: 'OneThird', - label: _('Number of CPUs'), - value: '' + content: _('

    Which information do you want to send to the Univention support. Please select the corresponding option.

    ') }, { - type: TextBox, - name: 'mem', - label: _('Memory'), - value: '' + type: RadioButton, + name: 'usi', + value: 'usi', + label: _('General system information including logfiles, config files, etc.'), + radioButtonGroup: 'infoChoice', + checked: true }, { - type: TextBox, - name: 'net_dev', - label: _('Network adapter'), - value: '' - }, { - type: TextBox, - name: 'gfx_dev', - label: _('Graphics card') + type: RadioButton, + name: 'hardware', + value: 'hardware', + label: _("Information about the system's hardware"), + radioButtonGroup: 'infoChoice' }, { type: Text, name: 'secondText', - content: _('

    Additionally to the information listed above some more details about the system has been collected. The whole set of collected data that will be transmitted to Univention can be downloaded at the following URL:

    ') - }, { - type: Text, - name: 'download', - content: _('Archive with system information') - }, { - type: Text, - name: 'thirdText', - content: _('In the following step two possibilities to transmit the information to Univention will be described.') + content: _('

    After clicking "Next" the information will be gathered and uploaded to Univention.

    ') }], layout: [['firstText'], - ['cpu', 'num_cpu'], - ['mem'], - ['net_dev'], - ['gfx_dev'], - ['secondText'], - ['download'], - ['thirdText']] - }, { - name: 'transfer', - headerText: _('Transfer the information'), - helpText: _(''), - widgets: [{ - type: Text, - name: 'firstText', - content: _('

    The collected information can be transfered to Univention by uploading the data or by sending it via mail. Please select the corresponding option.

    ') - }, { - type: ComboBox, - name: 'method', - value: 'upload', - label: _('Method'), - staticValues: [ - {id: 'upload', label: _('Upload')}, - {id: 'mail', label: _('Send mail')} - ] - }], - layout: [['firstText'], - ['method']] + ['usi'], + ['hardware'], + ['secondText']] }, { name: 'uploaded', headerText: _('Transfered successfully'), @@ -187,41 +134,32 @@ define([ type: Text, name: 'firstText', content: _('

    The information were transfered to Univention successfully.

    Thank you very much for your support!

    ') - }], - layout: [['firstText']] - }, { - name: 'mail', - headerText: _('Transfer via mail'), - helpText: '', - widgets: [{ - type: Text, - name: 'firstText', - content: _('To transfer the information via mail please follow these steps:
    1. Download the archive with the collected information and save it on your local system
    2. Click on link Send mail to open your mail program
    3. Attach the downloaded archive to the mail and send it to Univention
    4. End this assistant by clicking on the button Finish
    ') }, { type: Text, - name: 'download', - content: _('Archive with system information') - }, { + name: 'secondText', + content: this._archiveLink + },{ type: Text, - name: 'mail', - content: 'Send Mail' + name: 'thirdText', + content: '' }], layout: [['firstText'], - ['download'], - ['mail']] + ['secondText'] + ['thirdText']] }]; }, buildRendering: function() { this.inherited(arguments); - this._disableWidgets(); + this._progressBar = new ProgressBar(); }, - - _disableWidgets: function() { - var widgets = ['cpu', 'num_cpu', 'mem', 'net_dev', 'gfx_dev']; - array.forEach(widgets, lang.hitch(this, function(iwidget) { - this.getWidget('collect', iwidget).set('disabled', true); - })); + // toggle function for setting either ticket no or email as reuired + // so we always have contact informations + _toggleTicketNumberUsed: function(otherWidget, sourceWidget) { + this._ticketNumberUsed = this.getWidget('general', 'supportBox').get('value'); + this.getWidget('general', 'ticketNumber').set('visible', this._ticketNumberUsed); + this.getWidget('general', 'ticketNumber').set('required', this._ticketNumberUsed); + this.getWidget('general', 'emailBox').set('required', !this._ticketNumberUsed); }, canCancel: function(pageName) { @@ -242,36 +180,40 @@ define([ next: function() { var nextPage = this.inherited(arguments); - if (nextPage == 'general') { - this.getGeneralInfo(); - } - if (nextPage == 'support') { + if (nextPage == 'chooseInfo') { + // TODO validate if a mail address OR a ticket number are specified + var mailWidget = this.getWidget('general', 'emailBox') + var ticketNrWidget = this.getWidget('general', 'ticketNumber') + // don't go to next page if a required field is empty if (this.getWidget('general', 'supportBox').get('value') === false) { - nextPage = 'collect'; + if (!mailWidget.get('value')) { + this._triggerValidationMessage(mailWidget); + return 'general'; + } + }else{ + if (!ticketNrWidget.get('value')) { + this._triggerValidationMessage(ticketNrWidget); + return 'general'; + } } } - if (nextPage == 'collect') { - this.getSystemInfo(); - } if (nextPage == 'uploaded') { - if (this.getWidget('transfer', 'method') == 'mail') { - nextPage = 'mail'; - } else { - return this.uploadArchive().then(function() { - return nextPage; - }, lang.hitch(this, function() { - this.getPage('uploaded').set('headerText', _('Uploading failed')); - this.getWidget('uploaded', 'firstText').set('content', _('

    The information could not be transfered to Univention.

    You can send them as email!

    ')); - return nextPage; - })); + var doUSI = this.getWidget('chooseInfo', 'usi').get('checked'); + if (doUSI == true) { + this.usiProcess(); + }else{ + this.getSystemInfo(); } } - if (nextPage == 'mail') { - this.getMailInfo(); - } return nextPage; }, + gerValidationMessage: function(widget) { + // manual trigger for the "required filed" functionality + widget._hasBeenBlurred = true; + widget.validate(); + }, + hasPrevious: function(pageName) { if (pageName == 'uploaded') { return false; @@ -287,45 +229,52 @@ define([ return 'general'; } } - if (previousPage == 'uploaded') { - if (this.getWidget('transfer', 'method') == 'mail') { - return 'transfer'; - } - } return previousPage; }, - getGeneralInfo: function() { - this.standbyDuring(tools.umcpCommand('sysinfo/general')).then( - lang.hitch(this, function(data) { - var generalPage = this.getPage('general'); - generalPage._form.setFormValues(data.result); - }) - ); + setUploadFailedCaptions: function() { + this.getPage('uploaded').set('headerText', _('Uploading failed')); + this.getWidget('uploaded', 'firstText').set('content', _('

    The information could not be transfered to Univention.

    You can send them as email!

    ')); + + this.getWidget('uploaded', 'thirdText').set('content', _('To transfer the information via mail please follow these steps:
    1. Download the archive with the collected information and save it on your local system
    2. Click on li nk Send mail to open your mail program
    3. Attach the downloaded archive to the mail and send it to Univention
    4. End this assistant by clicking on the button Finish
    ')); }, getSystemInfo: function() { - var generalValues = this.getPage('general')._form.get('value'); - var supportValues = this.getPage('support')._form.get('value'); - var requestValues = { - 'manufacturer': generalValues.manufacturer, - 'model': generalValues.model, - 'comment': generalValues.comment, - 'ticket': supportValues.ticket - }; - this.standbyDuring(tools.umcpCommand('sysinfo/system', requestValues)).then( + this._progressBar.setInfo(_('Processing'), _('Gathering Hardware informations'), 10); + this.standby(true, this._progressBar); + tools.umcpCommand('sysinfo/general').then( lang.hitch(this, function(data) { - this._archiveFilename = data.result.archive; - this._archiveLink = lang.replace('{text}', { - 'url': '/univention-management-console/system-info/' + this._archiveFilename, - 'text': _('Archive with system information') - }); + var generalValues = data.result; + var ticketNumber = this.getWidget('general', 'ticketNumber').get('value'); + var comment = this.getWidget('general', 'comment').get('value'); + var requestValues = { + 'manufacturer': generalValues.manufacturer, + 'model': generalValues.model, + 'comment': comment, + 'ticket': ticketNumber + }; - var collectPage = this.getPage('collect'); - collectPage._form.setFormValues(data.result); - this.getWidget('collect', 'download').set('content', this._archiveLink); + this._progressBar.setInfo(_('Processing'), _('Gathering System informations'), 40); + tools.umcpCommand('sysinfo/system', requestValues).then( + lang.hitch(this, function(data) { + this._archiveFilename = data.result.archive; + this._archiveLink = lang.replace('{text}', { + 'url': '/univention-management-console/system-info/' + this._archiveFilename, + 'text': _('Archive with system information') + }); - this.getWidget('mail', 'download').set('content', this._archiveLink); + this.getWidget('uploaded', 'secondText').set('content', this._archiveLink); + this._progressBar.setInfo(_('Processing'), _('Uploading informations'), 70); + this.uploadArchive().then(lang.hitch(this, function(){ + // upload successful + this.standby(false); + }), lang.hitch(this, function(){ + // upload failed + this.setUploadFailedCaptions(); + this.standby(false); + })); + }) + ); }) ); }, @@ -343,6 +292,64 @@ define([ ); }, + downloadUSI: function() { + this._progressBar.setInfo(_('Processing'), _('Downloading newest Version of Univention-System-Information'), 20); + return tools.umcpCommand('sysinfo/download_usi'); + }, + + executeUSI: function() { + return tools.umcpCommand('sysinfo/general').then( + lang.hitch(this, function(data) { + var generalValues = data.result; + var ticketNumber = this.getWidget('general', 'ticketNumber').get('value'); + var comment = this.getWidget('general', 'comment').get('value'); + var email = this.getWidget('general', 'emailBox').get('value'); + var requestValues = { + 'manufacturer': generalValues.manufacturer, + 'model': generalValues.model, + 'comment': comment, + 'ticket': ticketNumber, + 'email': email + }; + return tools.umcpCommand('sysinfo/execute_usi', requestValues); + }) + ); + }, + + usiProcess: function() { + + this._progressBar.setInfo(_('Processing'), _('Starting USI process'), 10); + this.standby(true, this._progressBar); + + this.downloadUSI().then( + lang.hitch(this, function() { + this._progressBar.setInfo(_('Processing'), _('Executing USI'), 40); + this.executeUSI().then( + lang.hitch(this, function(data) { + this._archiveFilename = data.result; + var name = data.result.replace('/var/www/univention-management-console/system-info/',''); + // create Link on uploaded page so user can easily downlad the created archive + this._archiveLink = lang.replace('{text}', { + 'url': '/univention-management-console/system-info/' + name, + 'text': _('Archive with system information') + }); + // implement link ind uploaded page + this.getWidget('uploaded', 'secondText').set('content', this._archiveLink); + this._progressBar.setInfo(_('Processing'),_('Uploading informations'), 70); + this.uploadArchive().then(lang.hitch(this, function() { + // upload successful + this.standby(false); + }), lang.hitch(this, function() { + // upload failed + this.setUploadFailedCaptions() + this.standby(false); + })); + }) + ); + }) + ); + }, + uploadArchive: function() { var values = {'archive': this._archiveFilename}; return this.standbyDuring(tools.umcpCommand('sysinfo/upload', values)); diff --git a/management/univention-system-info/umc/python/sysinfo/__init__.py b/management/univention-system-info/umc/python/sysinfo/__init__.py index dd57563..b067fee 100644 --- a/management/univention-system-info/umc/python/sysinfo/__init__.py +++ b/management/univention-system-info/umc/python/sysinfo/__init__.py @@ -4,7 +4,7 @@ # Univention Management Console # module: collecting system information # -# Copyright 2011-2014 Univention GmbH +# Copyright 2011-2016 Univention GmbH # # http://www.univention.de/ # @@ -57,6 +57,20 @@ ucr = univention.config_registry.ConfigRegistry() _ = umc.Translation('univention-management-console-module-sysinfo').translate +import stat +from shutil import copyfile +import tarfile +import tempfile +import bz2 +import gzip +import pwd +import grp +import socket +import ssl + + +SYSINFO_PATH = '/var/www/univention-management-console/system-info/' + class Instance(umcm.Base): @@ -74,6 +88,16 @@ class Instance(umcm.Base): return (True, None, None, ) def get_general_info(self, request): + result = self.gather_general_info() + if not result: + message = _('Failed to execute command') + request.status = MODULE_ERR + self.finished(request.id, None, message) + return + request.status = SUCCESS + self.finished(request.id, result) + + def gather_general_info(self): DMIDECODE = '/usr/sbin/dmidecode' MANUFACTURER_CMD = (DMIDECODE, '-s', 'system-manufacturer', ) MODEL_CMD = (DMIDECODE, '-s', 'system-product-name', ) @@ -82,37 +106,39 @@ class Instance(umcm.Base): for command in (MANUFACTURER_CMD, MODEL_CMD, ): (exitcode, stdout, stderr, ) = self._call(command) if exitcode: - message = _('Failed to execute command') - request.status = MODULE_ERR - self.finished(request.id, None, message) - return + return None else: - stdout = stdout[:-1] # remove newline character + stdout = stdout[:-1] # remove newline character stdout_list.append(stdout) result = {} result['manufacturer'] = stdout_list[0] result['model'] = stdout_list[1] - - request.status = SUCCESS - self.finished(request.id, result) + return result def get_system_info(self, request): - MANUFACTURER = request.options['manufacturer'].encode( 'utf-8' ) - MODEL = request.options['model'].encode( 'utf-8' ) - COMMENT = request.options['comment'].encode( 'utf-8' ) - SYSTEM_INFO_CMD = ( '/usr/bin/univention-system-info', - '-m', MANUFACTURER, - '-t', MODEL, - '-c', COMMENT, - '-s', request.options.get('ticket', ''), - '-u', ) + MANUFACTURER = request.options['manufacturer'].encode('utf-8') + MODEL = request.options['model'].encode('utf-8') + COMMENT = request.options['comment'].encode('utf-8') + TICKET = request.options.get('ticket', '') + result = self.gather_system_info(MANUFACTURER, MODEL, COMMENT, TICKET) + if not result: + request.status = MODULE_ERR + else: + request.status = SUCCESS + self.finished(request.id, result) + + def gather_system_info(self, manufacturer, model, comment, ticket='', email=None): + SYSTEM_INFO_CMD = ('/usr/bin/univention-system-info', + '-m', manufacturer, + '-t', model, + '-c', comment, + '-s', ticket, + '-u',) (exitcode, stdout, stderr, ) = self._call(SYSTEM_INFO_CMD) if exitcode: - MODULE.error('Execution of univention-system-info failed: %s' - % stdout) + MODULE.error('Execution of univention-system-info failed: %s' % stdout) result = None - request.status = MODULE_ERR else: result = {} for line in stdout.splitlines(): @@ -130,10 +156,10 @@ class Instance(umcm.Base): result['mem'] = result['mem'].replace('.', ',') except (IndexError, ValueError): pass - result.pop('Temp', None) # remove unnecessary entry - request.status = SUCCESS - - self.finished(request.id, result) + if email: + result['email'] = email + result.pop('Temp', None) # remove unnecessary entry + return result @simple_response def get_mail_info(self): @@ -149,10 +175,12 @@ class Instance(umcm.Base): @sanitize(archive=StringSanitizer(required=True)) @simple_response def upload_archive(self, archive): + self._upload_archive(archive) + + def _upload_archive(self, archive): ucr.load() url = ucr.get('umc/sysinfo/upload/url', 'https://forge.univention.org/cgi-bin/system-info-upload.py') - SYSINFO_PATH = '/var/www/univention-management-console/system-info/' path = os.path.abspath(os.path.join(SYSINFO_PATH, archive)) if not path.startswith(SYSINFO_PATH): raise UMC_Error('Archive path invalid.') @@ -173,17 +201,150 @@ class Instance(umcm.Base): @simple_response def upload_traceback(self, traceback, remark, email): ucr.load() - ucs_version = '{0}-{1} errata{2} ({3})'.format( ucr.get( 'version/version', '' ), ucr.get( 'version/patchlevel', '' ), ucr.get( 'version/erratalevel', '0' ), ucr.get( 'version/releasename', '' ) ) + ucs_version = '{0}-{1} errata{2} ({3})'.format(ucr.get('version/version', ''), ucr.get('version/patchlevel', ''), ucr.get('version/erratalevel', '0'), ucr.get('version/releasename', '')) # anonymised id of localhost uuid_system = ucr.get('uuid/system', '') url = ucr.get('umc/sysinfo/traceback/url', 'https://forge.univention.org/cgi-bin/system-info-traceback.py') MODULE.process('Sending %s to %s' % (traceback, url)) request_data = { - 'traceback' : traceback, - 'remark' : remark, - 'email' : email, - 'ucs_version' : ucs_version, - 'uuid_system' : uuid_system, + 'traceback': traceback, + 'remark': remark, + 'email': email, + 'ucs_version': ucs_version, + 'uuid_system': uuid_system, } request = urllib2.Request(url, request_data) urllib2.urlopen(request) + + @simple_response + def download_usi_script(self): + self._download_usi_script() + + def _download_usi_script(self): + # TODO error handling + TMP_FOLDER = "/tmp" + usi_file = "%s/usi.py" % (TMP_FOLDER,) + url = "https://updates.software-univention.de/download/scripts/univention-support-info" + usi_fallback = "/usr/share/pyshared/univention/management/console/modules/sysinfo/univention-system-information" + try: + # create custom https handler for a secure connection + url_opener = urllib2.build_opener(HTTPSHandler) + # set as default url opener + urllib2.install_opener(url_opener) + _usi = urllib2.urlopen(url) + with open(usi_file, "wb") as _download_file: + _download_file.write(_usi.read()) + except (urllib2.HTTPError, urllib2.URLError, httplib.HTTPException): + # fallback to integrated usi in case the download fails + copyfile(usi_fallback, usi_file) + else: + # make the script executable + st = os.stat(usi_file) + os.chmod(usi_file, st.st_mode | stat.S_IEXEC) + + @simple_response + def execute_usi_script(self, manufacturer='', model='', comment='', ticket='', email=''): + return self._execute_usi_script(manufacturer, model, comment, ticket, email) + + def _execute_usi_script(self, manufacturer='', model='', comment='', ticket='', email=''): + TMP_FOLDER = "/tmp" + usi_file = "/tmp/usi.py" + output_file = None + # test if usi.py exists + if os.path.isfile(usi_file): + # execute usi script + _usi_exec = self._call(usi_file) + if not _usi_exec[0]: # if no error + # parse the output and get the filename + if len(_usi_exec) == 3 and _usi_exec[1]: + _usi_output = _usi_exec[1].split('\n') + usi_output = [entry for entry in _usi_output if entry] # remove empty strings from list + for entry in usi_output: + # TODO better use regex + if entry.strip().startswith(TMP_FOLDER): + output_file = entry.strip() + # get and append general system info and ticket number if existent + general_info = { + 'Manufacturer': manufacturer, + 'Model': model, + 'Comment': comment, + 'Ticket No': ticket, + 'Email': email + } + output_file = self.append_general_info_to_archive(output_file, general_info) + output_file = self.copy_to_upload_path(output_file) # copy finished archive to the right directory and change path accordingly + return output_file + + def append_general_info_to_archive(self, archive, info): + # create temp files + _tmp_file = tempfile.NamedTemporaryFile(delete=False) + _tmp_file.close() # we just need the name + _tmp_archive = tempfile.NamedTemporaryFile(delete=False) + _tmp_archive.close() # we just need the name + + # decompress archive + with open(_tmp_archive.name, 'wb') as uncompressed_file, bz2.BZ2File(archive, 'rb') as compressed_file: + for data in iter(lambda: compressed_file.read(100 * 1024), b''): + uncompressed_file.write(data) + + # open archive in read mode to get folder name + tar = tarfile.open(_tmp_archive.name, "r") + archive_folder = os.path.commonprefix(tar.getnames()) + tar.close() + + # correct archive name + gz_archive = archive_folder.rstrip('/') + '.tar.gz' + info['archive'] = gz_archive.split('/')[-1] + + # write info file + tmp_file = open(_tmp_file.name, "w") + for entry in info: + tmp_file.write("%s: %s%s" % (entry, info.get(entry), os.linesep,)) + tmp_file.close() + + # append info file to archive + tar = tarfile.open(_tmp_archive.name, "a") + tar.add(_tmp_file.name, arcname="%sinfo" % (archive_folder)) + tar.close() + + # re-compress archive as .gz, upload server takes only this format + with open(_tmp_archive.name, 'rb') as uncompressed_file, gzip.open(gz_archive, 'wb') as compressed_file: + for data in iter(lambda: uncompressed_file.read(100 * 1024), b''): + compressed_file.write(data) + compressed_file.close() + return gz_archive + + def copy_to_upload_path(self, archive): + # copy file to upload folder + archive_name = archive.split('/')[-1] + upload_file = SYSINFO_PATH + archive_name + copyfile(archive, upload_file) + # set owner and group + uid = pwd.getpwnam("www-data").pw_uid + gid = grp.getgrnam("www-data").gr_gid + os.chown(upload_file, uid, gid) + return upload_file + + +class HTTPSConnection(httplib.HTTPSConnection): + '''override httplib so we can check certificates against ca bundle''' + def connect(self): + sock = socket.create_connection((self.host, self.port), self.timeout) + if self._tunnel_host: + self.sock = sock + self._tunnel() + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, cert_reqs=ssl.CERT_REQUIRED, ca_certs="/etc/ssl/certs/ca-certificates.crt") + + def getreply(self): + response = self.getresponse() + self.file = response.fp + return response.status, response.reason, response.msg + + def getfile(self): + return self.file + + +class HTTPSHandler(urllib2.HTTPSHandler): + ''' handler to force certificate verification ''' + def https_open(self, req): + return self.do_open(HTTPSConnection, req) diff --git a/management/univention-system-info/umc/python/sysinfo/de.po b/management/univention-system-info/umc/python/sysinfo/de.po index f2b650c..bd2977c 100644 --- a/management/univention-system-info/umc/python/sysinfo/de.po +++ b/management/univention-system-info/umc/python/sysinfo/de.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: univention-management-console-module-sysinfo\n" "Report-Msgid-Bugs-To: packages@univention.de\n" -"POT-Creation-Date: 2014-12-18 16:16+0100\n" +"POT-Creation-Date: 2016-04-05 15:34+0200\n" "PO-Revision-Date: 2016-01-14 11:26+0100\n" "Last-Translator: Univention GmbH \n" "Language-Team: Univention GmbH \n" @@ -12,6 +12,6 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: umc/python/sysinfo/__init__.py:85 +#: umc/python/sysinfo/__init__.py:93 msgid "Failed to execute command" msgstr "Die Ausführung des Kommandos ist fehlgeschlagen" diff --git a/management/univention-system-info/umc/python/sysinfo/univention-support-info b/management/univention-system-info/umc/python/sysinfo/univention-support-info new file mode 100755 index 0000000..f3b99e8 --- /dev/null +++ b/management/univention-system-info/umc/python/sysinfo/univention-support-info @@ -0,0 +1,847 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# univention-support-info - collect system information +# +# Copyright 2011 Univention GmbH +# +# http://www.univention.de/ +# +# All rights reserved. +# +# The source code of this program is made available +# under the terms of the GNU Affero General Public License version 3 +# (GNU AGPL V3) as published by the Free Software Foundation. +# +# Binary versions of this program provided by Univention to you as +# well as other copyrighted, protected or trademarked materials like +# Logos, graphics, fonts, specific documentations and configurations, +# cryptographic keys etc. are subject to a license agreement between +# you and Univention and not subject to the GNU AGPL V3. +# +# In the case you use this program under the terms of the GNU AGPL V3, +# the program is provided in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License with the Debian GNU/Linux or Univention distribution in file +# /usr/share/common-licenses/AGPL-3; if not, see +# . + +keyID = '18B913ABF3C1550B8C2DFDA994F01970E89E3BEC' +# create with: gpg --armor --export --export-options export-minimal $keyID +keyData = '\n'.join(('-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v1.4.9 (GNU/Linux)', + '', + 'mQGiBEA2RhgRBADee7hGh5TPPwqs+z5tfLett9xaVxmRkUfBvqesVIj93WzrKsU3', + 'S7hjnMlh+JieFjJLy6jTJYtvqqFIxh3vw5btCwhntrTQnu00U/r3UdznqE/zGH2L', + 'A734aHaSaq6UFKE5kwX0DECgSI1hwc20d7guLJXSqOpwfYktXiB+27GRCwCgh4OR', + 'MWncPkhaJhusO8YCSnSN0GED/2ez8utubP1FloTfTof4/OLfBvPwdgJ5Q7FRqeF9', + 'wDmfd8Hetzr+Fh4zMs6dY0c5+unUQiLXjY9F01WT7SM+yFrs5EHzb+gyjIdTmTtn', + 'mtNTL2cZK8freAv9LPWCHfQ1rii+Qd71+/CKLDfwwLqQxEAkOsrpOsUD4dip6vkm', + 'ZtaRBACUYoOtB738OzjPqOpbnmNQjQYVtGCocDjfKs+bMyq+LPOyC31NVC4LvqhC', + 'nwXSUSy8jfXLzInPXgEUiHvGAEvnNzRLAIh6W/pIaK7tIITESmV/C3PBvxLEkJNm', + '8Ll+2qGVspYnGTUFe6JzxARzcTVow4JlB+dX40bk7LNqUe6Au7QqVW5pdmVudGlv', + 'biBTdXBwb3J0IDxzdXBwb3J0QHVuaXZlbnRpb24uZGU+iF4EExECAB4GCwkIBwMC', + 'AxUCAwMWAgECHgECF4AFAkm+NOYCGQEACgkQlPAZcOieO+xhXwCfdZ7+eWGpJfhz', + 'CnrfuzgdzqsetMMAn3oUbDZCqzH083DKCNS5547V8XkCtCpVbml2ZW50aW9uIFN1', + 'cHBvcnQgPHNlcnZpY2VAdW5pdmVudGlvbi5kZT6IYAQTEQIAIAUCSNdW8wIbIwYL', + 'CQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEJTwGXDonjvsQcoAn2b9FNPECtPus0Sf', + '3ENGafyXSIq7AJ9sbNm+QryXW1rtvtsHlNgYI9eGpLQrVW5pdmVudGlvbiBTdXBw', + 'b3J0IDxmZWVkYmFja0B1bml2ZW50aW9uLmRlPohgBBMRAgAgBQJI11aYAhsjBgsJ', + 'CAcDAgQVAggDBBYCAwECHgECF4AACgkQlPAZcOieO+wmzACfVmZKVc37T7W9FQGN', + 'K+dXsLbDplkAniAYh0l9Nd8wMh/QZrxcRdlgOBrhtDRVbml2ZW50aW9uIFN1cHBv', + 'cnQgPHBhcnRuZXItdGVjaG5pY2FsQHVuaXZlbnRpb24uZGU+iGAEExECACAFAkho', + 'p9gCGyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU8Blw6J477IMYAJ9oZoTS', + 'vHsxF7TYJSGo3pcutuV4RACfbxLxxjU7suCGs+khvINRl9pJel60KlVuaXZlbnRp', + 'b24gU2VydmljZSA8c2VydmljZUB1bml2ZW50aW9uLmRlPohgBBMRAgAgBQJJvjVV', + 'AhsjBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQlPAZcOieO+ycGwCeM8pJxUrL', + 'oiKylTxiKZ4MwQoV/L0An3y/9GafqT8SaUgSjYO+9P4szWATtCtQYXJ0bmVyLUxp', + 'c3RzIDxwYXJ0bmVyLWxpc3RzQHVuaXZlbnRpb24uZGU+iGAEExECACAFAkm+NQEC', + 'GyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU8Blw6J477C67AJ93Pv9Dcq7g', + 'HreBv3f1Q07IjDI3bACfUZgClXNISIewXER8FzV5cC08LRm0LFVuaXZlbnRpb24g', + 'RmVlZGJhY2sgPGZlZWRiYWNrQHVuaXZlbnRpb24uZGU+iGAEExECACAFAkm+NSsC', + 'GyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU8Blw6J477E8QAJ0XE91GawgL', + 'lVGfeiiT7c32P35IDACePCNLSzHA6K6JrV7CP98BeZUUNYu0LFVuaXZlbnRpb24g', + 'SGVscGRlc2sgPGhlbHBkZXNrQHVuaXZlbnRpb24uZGU+iGAEExECACAFAkm+NUMC', + 'GyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU8Blw6J477JNxAJ4l9wqf+WtV', + 'C0sAtu91aGWvgW/JQACfTJUCWf1DTqn1sDhbNiCG2jA+B9K0LFVuaXZlbnRpb24g', + 'VmVydHJpZWIgPHZlcnRyaWViQHVuaXZlbnRpb24uZGU+iGAEExECACAFAkm+NWwC', + 'GyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU8Blw6J477Jq3AJ9fN/6oKbqY', + 'exKHPdLQw31wC8HGTgCfZ5gn5zDd2JSY9tirDtMw5SSdfk20M1BhcnRuZXItVGVj', + 'aG5pY2FsIDxwYXJ0bmVyLXRlY2huaWNhbEB1bml2ZW50aW9uLmRlPohgBBMRAgAg', + 'BQJJvjUPAhsjBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQlPAZcOieO+wlNQCf', + 'eZ7Z4PeCx7SF+k1CK0bC8oouICkAn3/NYT1ASQLFP8CBqku1shs1lFCstDNQcmVt', + 'aXVtLVRlY2huaWNhbCA8cHJlbWl1bS10ZWNobmljYWxAdW5pdmVudGlvbi5kZT6I', + 'YAQTEQIAIAUCSb41GgIbIwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEJTwGXDo', + 'njvsxqcAnjRmgwWtXJqEbptTSSkaamNmGp9KAJ0R7b4RYDSKr+aFGPhhgetqLNXO', + 'MrQwVW5pdmVudGlvbiBPWCBTdXBwb3J0IDxveC1zdXBwb3J0QHVuaXZlbnRpb24u', + 'ZGU+iGAEExECACAFAk1GmXcCGyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU', + '8Blw6J477JwmAJ9WeeR2SzUtHY7pPolhm4fQmtM20QCfaebVO97CHdKkcAqPjJ3K', + 'RwWVtpC5AQ0EQDZGGxAEAJotyyct6jvNscl9q2stDB+BUuXefdd7UMdSySSGqt5c', + '7f/6IEX/eiG/2nIsqX1IsSQ+Bw0ZQTQUhgI8hICUsmjdjvWnBCyHX8xnMESITDv/', + 'fJmxgaP8fbMSJexhnizjlz8m74OgnfFew6EuRWRXA/SDeTwmsaUafTv7biKaDlU7', + 'AAMFA/9hJUqdh+tSaEfwUzPgHdFT8EIM2B0VSmVnqHSWwCjuJTLTWJi+DDe2hq7p', + 'QPpcATzgEg5qu5lsqh0AAXV998fD/RiO3B+ct1rwYbNlchACIXtgDTe43dmUaKkp', + 'fPRxeQZr8iym706LJOyppF+jXqOm2oy6Sf++/YElcCBmPPDIwIhGBBgRAgAGBQJA', + 'NkYbAAoJEJTwGXDonjvsqD0AnRVxlYyWk3DrKL0ZCxRZrtpW6pbwAJ9R/HZLaaH+', + '043H7VXVPPTjhs6Tig==', + '=BE0y', + '-----END PGP PUBLIC KEY BLOCK-----', + )) +toprc = '\n'.join(('RCfile for "top with windows" # shameless braggin\'', + 'Id:a, Mode_altscr=0, Mode_irixps=1, Delay_time=3.000, Curwin=0', + 'Def fieldscur=AEHIOQTWKNMbcdfgjplrsuvyzX', + ' winflags=62905, sortindx=10, maxtasks=0', + ' summclr=1, msgsclr=1, headclr=3, taskclr=1', + 'Job fieldscur=ABcefgjlrstuvyzMKNHIWOPQDX', + ' winflags=62777, sortindx=0, maxtasks=0', + ' summclr=6, msgsclr=6, headclr=7, taskclr=6', + 'Mem fieldscur=ANOPQRSTUVbcdefgjlmyzWHIKX', + ' winflags=62777, sortindx=13, maxtasks=0', + ' summclr=5, msgsclr=5, headclr=4, taskclr=5', + 'Usr fieldscur=ABDECGfhijlopqrstuvyzMKNWX', + ' winflags=62777, sortindx=4, maxtasks=0', + ' summclr=3, msgsclr=3, headclr=2, taskclr=3', + '', + )) + +import cStringIO +import glob +import gzip +import optparse +import os +import shutil +import socket +import stat +import subprocess +import sys +import tarfile +import tempfile +import time +from distutils.spawn import find_executable + +# ignore apt's "API not stable yet" warning +import warnings +warnings.filterwarnings("ignore", category=FutureWarning, append=True) + +import apt +import apt_pkg + +try: + from univention import config_registry +except ImportError: + if os.path.isfile('/usr/share/pyshared/univention/config_registry.py'): + import imp + config_registry = imp.load_source('config_registry', '/usr/share/pyshared/univention/config_registry.py') + else: + sys.path.append('/usr/share/pyshared') + from univention import config_registry + +ucr = config_registry.ConfigRegistry() +ucr.load() +timeString = time.strftime('%Y-%m-%d_%H-%M-%SZ', time.gmtime()) +hostname = socket.gethostname() + + + +def Popen(CommandTuple, Input=None): + try: + process = subprocess.Popen(CommandTuple, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, env=env) + (stdoutdata, stderrdata) = process.communicate(input=Input) + return (process.returncode, stdoutdata, stderrdata) + except (OSError, IOError), error: + return ('Could not execute %s: %s' % (repr(CommandTuple), repr(' '.join(map(str, error.args)))), '', '') + +def _ldapsearchCommand(): + ''' Get ldapsearch command depending on UCS version ''' + if find_executable('univention-ldapsearch'): + return 'univention-ldapsearch' + return 'ldapsearch' + +def _sprint(string): + ''' write status string to stdout without newline ''' + sys.stdout.write(string) + sys.stdout.flush() + +def U32(i): + ''' + Return i as an unsigned integer, assuming it fits in 32 bits. + If it's >= 2GB when viewed as a 32-bit unsigned int, return a long. + ''' + if i < 0: + i += 1L << 32 + return i + +def addFile(name, size, fileobj): + # try to determine filesize + if size == None or size == False: + fileobj.seek(0,os.SEEK_END) + size = fileobj.tell() + if fileobj.tell() > 0: + fileobj.seek(0) + + info = tarfile.TarInfo() + info.size = size + info.name = 'univention-support-info-' + hostname + '-' + timeString + '/' + name + info.mode = stat.S_IRUSR + info.mtime = time.time() + archive.addfile(info, fileobj) + +def addFileByPath(filename): + if filename.startswith('/proc/'): + # This is a /proc/ file, os.stat(filename).st_size will always return 0 + tmpf = tempfile.mkstemp(prefix='univention-support-info.')[1] + try: + shutil.copy(filename, tmpf) + except (OSError, IOError), error: + error = '\n'.join(map(str, error.args)) + addFile('files/' + filename.strip('/').replace('/', '_') + '.ERROR', len(error), cStringIO.StringIO(error)) + os.unlink(tmpf) + filesize = os.stat(tmpf).st_size + os.unlink(tmpf) + else: + try: + filesize = os.stat(filename).st_size + except (OSError, IOError), error: + filesize = 0 + + try: + fileobj = open(filename, 'rb') + except (OSError, IOError), error: + error = '\n'.join(map(str, error.args)) + addFile('files/' + filename.strip('/').replace('/', '_') + '.ERROR', len(error), cStringIO.StringIO(error)) + else: + addFile('files/' + filename.strip('/').replace('/', '_'), filesize, fileobj) + fileobj.close() + +def certificateValidity(CertificatePath): + try: + import M2Crypto + except ImportError, error: + error = '\n'.join(map(str, error.args)) + addFile('info/ssl/' + CertificatePath.strip('/').replace('/', '_') + '.ERROR', len(error), cStringIO.StringIO(error)) + return + + try: + cert = M2Crypto.X509.load_cert(CertificatePath) + validity = '%s\nNot Before: %s\nNot After : %s\n' % (CertificatePath, cert.get_not_before(), cert.get_not_after(), ) + addFile('info/ssl/' + CertificatePath.strip('/').replace('/', '_'), len(validity), cStringIO.StringIO(validity)) + except (OSError, IOError, M2Crypto.X509.X509Error), error: + error = '\n'.join(map(str, error.args)) + addFile('info/ssl/' + CertificatePath.strip('/').replace('/', '_') + '.ERROR', len(error), cStringIO.StringIO(error)) + +def certificateValidities(): + _sprint( 'Checking certificate validity: ' ) + CertificatePatterns = [ + '/etc/univention/ssl/*.*/cert.pem', + '/etc/univention/ssl/ucsCA/CAcert.pem', + ] + for CertificatePattern in CertificatePatterns: + for CertificatePath in glob.glob(CertificatePattern): + certificateValidity(CertificatePath) + print 'done.' + +def simpleFiles(): + FilePatterns = [ + '/boot/config-*', + '/etc/apache2/*', + '/etc/apache2/*/*', + '/etc/apt/*', + '/etc/apt/*/*', + '/etc/cron*/*', + '/etc/fstab', + '/etc/imapd/*', + '/etc/mtab', + '/etc/passwd', + '/etc/procmailrc', + '/etc/spamassassin/*', + '/etc/univention/connector/ad/mapping.py', + '/etc/univention/installation_profile', + '/etc/ox-secrets/*', + '/opt/open-xchange/etc/*', + '/opt/open-xchange/etc/*/*', + '/opt/open-xchange/etc/*/*/*', + '/proc/mounts*', + '/proc/mdstat', + '/proc/drbd', + '/proc/cmdline', + '/var/lib/univention-directory-replication/failed.ldif', + '/etc/postfix/*', + '/etc/imapd/*', + '/var/univention-backup/ad-takeover/*', + '/etc/*/local.conf*', + '/root/.bash_history', + '/var/log/installer/*', + '/etc/ldap/slapd.conf', + ] + FileExcludePatterns = [ '/etc/apache2/mods-available/*', '/etc/apache2/sites-available/*' ] + + Files = set() + ExcludeFiles = list() + for FileExcludePattern in FileExcludePatterns: + ExcludeFiles.extend(glob.glob(FileExcludePattern)) + for FilePattern in FilePatterns: + for Filename in glob.glob(FilePattern): + if not Filename in ExcludeFiles: + Files.add(Filename) + + _sprint( 'Collecting files: ' ) + for Filename in sorted(list(Files)): + if os.path.isfile(Filename): + addFileByPath(Filename) + _sprint('.') + print 'done.' + +def licenseObject(): + ''' Get license object from ldap (cn=license) ''' + stdout = executeCommand( 'univention-license-object', (_ldapsearchCommand(), '-x' , '-b', 'cn=license,cn=univention,'+ucr.get('ldap/base')) ) + addFile('info/univention-license-object', len(stdout), cStringIO.StringIO(stdout)) + + +def checkMaintenance(): + ''' Check if UCS-Version is in maintenance ''' + if ucr.get('version/version') <= '3.1' and ucr.get('server/role') != 'ucc': + tmpf = tempfile.TemporaryFile(prefix='univention-support-info.') + print "Please note, system is no longer maintained, security updates are no longer available for current UCS Version %s" % (ucr.get('version/version')) + print >> tmpf, "Please note, system is no longer maintained, security updates are no longer available for current UCS Version %s" % (ucr.get('version/version')) + addFile( 'info/no_maintenance', None, tmpf ) + +def checkEntryUUID(): + ''' Check if ldap base is searchable by its entryUUID ''' + entryuuid="" + basedn="" + tmpf = tempfile.TemporaryFile(prefix='univention-support-info.') + (exitcode, stdout, stderr, ) = Popen( (_ldapsearchCommand(), '-xLLL', '-sbase', 'entryUUID', ) ) + if exitcode == 0: + entryuuid = stdout.split()[3] + else: + print >> tmpf, "ERROR: ldapsearch for base failed: %s" % str(stderr) + addFile( 'info/entryUUID.stderr', None, tmpf ) + return + (exitcode, stdout, stderr, ) = Popen( (_ldapsearchCommand(), '-xLLL', 'entryUUID='+entryuuid, 'dn', ) ) + if exitcode == 0: + basedn = stdout.split()[1] + else: + print >> tmpf, "ERROR: ldapsearch by entryUUID failed: %s" % str(stderr) + addFile( 'info/entryUUID.stderr', None, tmpf ) + return + if ucr.get('ldap/base') == basedn: + print >> tmpf, "OK: ldap base found by entryUUID" + else: + print >> tmpf, "ERROR: ldap base not found by entryUUID, check ldap index" + addFile( 'info/entryUUID', None, tmpf ) + +def aptPackageList(): + """List installed packages and their source repository.""" + _sprint( 'Collecting package lists: ' ) + cache = apt.Cache() + + packagesAll = tempfile.TemporaryFile(prefix='univention-support-info.') + packagesUnknownSource = tempfile.TemporaryFile(prefix='univention-support-info.') + + if not hasattr(apt, 'deprecation'): # python apt 0.7.7 in UCS < 3.0 + packages = [_ for _ in cache if _.isInstalled] + for pkg in packages: + pkg._lookupRecord(True) + try: + path = apt_pkg.ParseSection(pkg._records.Record)["Filename"] + except KeyError: + print >> packagesUnknownSource, "%s\tUNKNOWN" % (pkg.name,) + continue + cand = pkg._depcache.GetCandidateVer(pkg._pkg) + for packagefile, _ in cand.FileList: + indexfile = cache._list.FindIndex(packagefile) + if indexfile: + uri = indexfile.ArchiveURI(path) + print >> packagesAll, "%s\t%s" % (pkg.name, uri) + else: + packages = [_ for _ in cache if _.is_installed] + for pkg in packages: + version = pkg.installed.version + package = pkg.versions[version] + try: + uri = package.uri + except StopIteration: + print >> packagesUnknownSource, "%s\tUNKNOWN" % (pkg.name,) + continue + print >> packagesAll, "%s\t%s" % (pkg.name, uri) + + addFile( 'info/packages_all', None, packagesAll ) + addFile( 'info/packages_unknown-source', None, packagesUnknownSource ) + print 'done.' + +def executeCommand(commandName, command, log_stderr = False): + (exitcode, stdout, stderr, ) = Popen(command) + if exitcode or log_stderr: + if type(exitcode) is int: + stderr += '\nExitcode was %d\n' % exitcode + else: + stderr += exitcode + '\n' + addFile('info/' + commandName + '.stderr', len(stderr), cStringIO.StringIO(stderr)) + return stdout + +def templateFiles(): + _sprint( 'Searching for changed template files: ' ) + stdout = executeCommand('check-templates', ('find', '/etc/univention/templates/files/', '(', '-name', '*.dpkg-new', '-o', '-name', '*.dpkg-dist', ')', '-print0', )) + files = [templatefile for templatefile in stdout.split('\0') if templatefile] + message = ('Found %d:\n' % len(files)) + '\n'.join(files) + '\n' + addFile('info/check-templates', len(message), cStringIO.StringIO(message)) + for templatefile in files: + addFileByPath(templatefile) + if templatefile.endswith('.dpkg-new'): + addFileByPath(templatefile[:-len('.dpkg-new')]) + elif templatefile.endswith('.dpkg-dist'): + addFileByPath(templatefile[:-len('.dpkg-dist')]) + _sprint('.') + print 'done.' + +def collectCommandData(): + commands = {'hostname-f': + ('hostname', '--fqdn', ), + 'ifconfig-a': + ('ifconfig', '-v', '-a', ), + 'iptables-L_filter': + ('iptables', '-L', '-n', '-v', '-t', 'filter', ), + 'iptables-L_nat': + ('iptables', '-L', '-n', '-v', '-t', 'nat', ), + 'iptables-L_mangle': + ('iptables', '-L', '-n', '-v', '-t', 'mangle', ), + 'iptables-L_raw': + ('iptables', '-L', '-n', '-v', '-t', 'raw', ), + 'iptables-save': + ('iptables-save', '-c', ), + 'route-4': + ('route', '-v', '-ee', '-n', '-A', 'inet', ), + 'route-6': + ('route', '-v', '-ee', '-n', '-A', 'inet6', ), + 'netstat': + ('netstat', '--tcp', '--udp', '--listening', '--program', '--extend', '--extend', '--verbose', '--timers', '--numeric', '--wide', ), + 'dpkg-l': + ('dpkg-query', '--show', '--showformat=${Status}\t${Package}\t${Version}\n', ), + 'dpkg--audit': + ('dpkg', '--audit', ), + 'uname': + ('uname', '-a', ), + 'ps': + ('ps', '-AHFly', ), + 'ps-full': + ('ps', '-ALwwo', 'stat,pid,ppid,sid,tty,nlwp,lwp,pri,ni,sched,wchan,vsz,rss,sz,pcpu,pmem,cmd,blocked,caught,ignored,pending,lstart,cls,time,flags,uid,user,ruid,ruser,suid,suser,gid,group,rgid,rgroup,sgid,sgroup', ), + 'ucr-dump': + ('univention-config-registry', 'dump', ), + 'df-full': + ('df', '--portability', '--print-type', ), + 'df-i-full': + ('df', '--portability', '--print-type', '--inodes', ), + 'df': + ('df', '-h', ), + 'df-i': + ('df', '-h', '-i', ), + 'join-status': + ('univention-check-join-status', ), + 'virsh-qemu': + ('virsh', '-c', 'qemu:///system', 'capabilities', ), + 'virsh-xen': + ('virsh', '-c', 'xen:///', 'capabilities', ), + 'top': + ('top', '-b', '-n2', ), + 'testparm': + (('testparm', '-s', '-vvv', ), True), + 'listenerID': + ('cat', '/var/lib/univention-directory-listener/notifier_id', ), + 'notifierID': + ('/usr/share/univention-directory-listener/get_notifier_id.py', ), + 'mailq': + ('mailq', ), + 'univention-license-check': + ('univention-license-check', ), + 'hostaccount-id': + ('id', ucr.get('hostname') + '$', ), + 'dig_AXFR': + ('dig', '@'+ucr.get('nameserver1'), ucr.get('domainname'), '-t', 'AXFR'), + 'univention-connector-list-rejected': + ('univention-connector-list-rejected', ), + } + + # Commands depending on samba version + if sambaDomainVersion == 3: + commands.update({'test-join': + ('net', 'rpc', 'testjoin', ), + }) + elif sambaDomainVersion == 4: + commands.update({'net-ads-info': + ('net', 'ads', 'info', ), + 'net-ads-lookup': + ('net', 'ads', 'lookup', ), + }) + if ucr.get('samba4/role', None): + # Run only S4 + commands.update({'samba-tool-drs-showrepl': + ('samba-tool', 'drs', 'showrepl', ), + 'samba-tool-domain-level-show': + ('samba-tool', 'domain', 'level', 'show'), + 'samba-tool-domain-passwordsettings': + ('samba-tool', 'domain', 'passwordsettings', 'show' ), + 'testparm-samba4': + (('testparm.samba4', '-s', '-vvv'), True), + 'samba-tool-fsmo-show': + ('samba-tool', 'fsmo', 'show'), + 'univention-s4connector-list-rejected': + ('univention-s4connector-list-rejected', ), + 'samba-tool-processes': + ('samba-tool', 'processes'), + }) + # >= Samba4 RC (UCS 3.1) + if ucr.get('version/version') >= '3.1': + commands.update({'samba-tool-domain-info': + ('samba-tool', 'domain', 'info', '127.0.0.1', ), + }) + else: + # Run only on S3 member in S4 domain + commands.update({'test-join': + ('net', 'ads', 'testjoin', ), + }) + + _sprint( 'Collecting command output: ' ) + for commandName in commands: + command = commands[commandName] + if type(command[0]) == tuple: + stdout = executeCommand(commandName, command[0], command[1]) + else: + stdout = executeCommand(commandName, command) + + addFile('info/' + commandName, len(stdout), cStringIO.StringIO(stdout)) + _sprint('.') + print 'done.' + +def univentionSystemInfo(): + _sprint( 'Collecting hardware information: ' ) + manu = executeCommand('dmidecode', ('dmidecode', '-s', 'system-manufacturer')) + product = executeCommand('dmidecode', ('dmidecode', '-s', 'system-product-name')) + if not manu: + manu = 'Unknown' + if not product: + product = 'Unknown' + stdout = executeCommand('univention-system-info', ('univention-system-info','-u','-m',manu,'-t',product,'-c','Created by univention-support-info','-s','-',)) + archive = None + for line in stdout.split('\n'): + if line.startswith('archive'): + archive = line.split(':', 1)[1] + if not archive: + error = 'No archive returned!' + error += '\nunivention-system-info stdout:\n%s' % stdout + addFile('info/univention-system-info.ERROR', len(error), cStringIO.StringIO(error)) + return + filename =os.path.join( '/var/www/univention-management-console/system-info/', archive ) + # If UMC is not installed /var/www/univention-management-console/system-info/ does not exist and archive stays in /tmp + if not os.path.isfile( filename ): + filename = os.path.join( '/tmp/', archive ) + try: + archive = tarfile.open(name=filename, mode='r:*') + for member in archive: + if member.isfile(): + fileobj = archive.extractfile(member) + addFile('info/univention-system-info/' + member.name, member.size, fileobj) + fileobj.close() + archive.close() + except (IOError, tarfile.TarError, ), error: + error = '\n'.join(map(str, error.args)) + error += '\nunivention-system-info stdout:\n%s' % stdout + addFile('info/univention-system-info.ERROR', len(error), cStringIO.StringIO(error)) + print 'done.' + +def rotatedLogs(): + DefaultMaxLineCount = 1000 + FilePatterns = [ + ('/var/log/apache/*', DefaultMaxLineCount), + ('/var/log/apache2/*', DefaultMaxLineCount), + ('/var/log/auth.log*', DefaultMaxLineCount), + ('/var/log/dpkg.log*', DefaultMaxLineCount), + ('/var/log/mail.log*', DefaultMaxLineCount), + ('/var/log/open-xchange/*', DefaultMaxLineCount), + ('/var/log/samba/log.*', DefaultMaxLineCount), + ('/var/log/kern*', DefaultMaxLineCount), + ('/var/log/univention/*', DefaultMaxLineCount), + ('/var/log/univention/ucc-clients/*', DefaultMaxLineCount), + ('/var/log/apt/term.log*', DefaultMaxLineCount), + ('/var/log/squid/*', DefaultMaxLineCount), + ('/var/log/dansguardian/*', DefaultMaxLineCount), + ('/var/log/squidguard/*', DefaultMaxLineCount), + ('/var/log/syslog*', 2000000), + ('/var/log/univention/system-stats.log*', 5000000), + ('/var/log/freeradius/*', DefaultMaxLineCount), + ('/var/log/cups/*', DefaultMaxLineCount), + ] + FullLogs = set(( # for these every available log-file shall be included + '/var/log/dpkg.log', + '/var/log/univention/updater.log', + '/var/log/apt/term.log', + '/var/log/univention/ad-takeover.log', + )) + GzipSuffix = '.gz' + logs = {} + for FilePattern in [fp[0] for fp in FilePatterns]: + for filename in glob.glob(FilePattern): + if os.stat(filename).st_size <= 0 or os.path.isdir(filename): + # ignore 0 byte files + continue + if filename.endswith(GzipSuffix): + gzipped = True + filename = filename[:-len(GzipSuffix)] + else: + gzipped = False + if not filename.endswith('.') and os.path.splitext(filename)[1].strip('0123456789') == '.': # has extension and only digits in it + (filename, ext, ) = os.path.splitext(filename) + number = int(ext.lstrip('.')) + else: + number = -1 + if filename not in logs: + logs[filename] = {} + logs[filename][number] = gzipped + + _sprint( 'Collecting logfiles: ' ) + for logname in sorted(logs): + logLinecount = 0 + nonemptyNumber = 0 + for number in sorted(logs[logname]): + # is logname gzipped? + gzipped = logs[logname][number] + path = logname + fileLinecount = 0 + if number != -1: + path += '.%d' % number + + if gzipped: + try: + logfile = open(path + GzipSuffix, 'rb') + except (OSError, IOError, ), error: + error = '\n'.join(map(str, error.args)) + addFile('files/' + '%s_%d.stderr' % (logname.strip('/').replace('/', '_'), number, ), len(error), cStringIO.StringIO(error)) + continue + + # calc bytes and linecount of logfile + # last 4 bytes of a gzip file contain the size of the original (uncompressed) input data modulo 2^32. + try: + logfile.seek(-4, os.SEEK_END) + except IOError, e: + print '\n\nFilename: %s' %path + GzipSuffix + raise IOError(e) + fileBytes = U32( gzip.read32(logfile) ) + logfile.close() + fileLinecount = int( subprocess.Popen( 'zcat -f %s | wc -l' % (path + GzipSuffix), stdout=subprocess.PIPE, shell=True).stdout.read().strip() ) + else: + # addFile may calculate the size for us + fileBytes = None + fileLinecount = int( subprocess.Popen( ('wc', '-l', path), stdout=subprocess.PIPE).stdout.read().strip().split()[0] ) + logLinecount += fileLinecount + #if gzipped: + # _sprint( '"%s", "%s", "%s" -> ' % (path + GzipSuffix, fileBytes, fileLinecount) ) + #else: + # _sprint( '"%s", "%s", "%s" -> ' % (path, fileBytes, fileLinecount) ) + + if fileLinecount <= 0: + # skip logname if empty + #print 'ERROR: Empty file? "%s"' % path + continue + + + nonemptyNumber += 1 + try: + if gzipped: + logfile = gzip.GzipFile(path + GzipSuffix, 'rb') + else: + logfile = open(path, 'rb') + except (OSError, IOError, ), error: + error = '\n'.join(map(str, error.args)) + addFile('files/' + '%s_%d.stderr' % (logname.strip('/').replace('/', '_'), nonemptyNumber, ), len(error), cStringIO.StringIO(error)) + continue + + # Add file to archive ... + addFile('files/' + '%s_%d' % (logname.strip('/').replace('/', '_'), nonemptyNumber, ), fileBytes, logfile) + #print 'Added as "%s_%d"' % (logname.strip('/').replace('/', '_'), nonemptyNumber, ) + logfile.close() + + if logname not in FullLogs and logLinecount > filter(lambda x: logname.startswith(x[0].replace('*', '')), FilePatterns)[0][1]: + break + + _sprint('.') + print 'done.' + +def atJobs(): + ''' + Generate a list of at-Jobs (usefull for UCS@school) + ''' + try: + from univention.lib import atjobs as at + except ImportError, error: + error = str(error.message) + addFile('info/at-jobs' + '.ERROR', len(error), cStringIO.StringIO(error)) + return + + jobs = '' + try: + for job in at.list( extended=True ): + jobs += '\n'.join( (str(job), job.command) ) + except OSError, error: + error = str(error.message) + addFile('info/at-jobs' + '.ERROR', len(error), cStringIO.StringIO(error)) + addFile('info/at-jobs', len(jobs), cStringIO.StringIO(jobs)) + +def tryDelete(filename): + try: + os.remove(filename) + except (OSError, IOError, ): + pass + +def gpg(archiveFileName): + _sprint( 'Encrypting file: ' ) + keyringFileName = tempfile.mkstemp(prefix='univention-support-info-keyring.', suffix='.gpg')[1] + secringFileName = tempfile.mkstemp(prefix='univention-support-info-secring.', suffix='.gpg')[1] + trustdbFileName = tempfile.mkstemp(prefix='univention-support-info-trustdb.', suffix='.gpg')[1] + tryDelete(trustdbFileName) # HACK: file must not exist for gpg to work + gpgFileName = archiveFileName + '.gpg' + gpgBase = ('gpg', + '--batch', '--quiet', '--no-tty', + '--with-colons', '--utf8-strings', + '--no-auto-check-trustdb', '--no-auto-key-locate', '--no-use-agent', + '--no-options', + '--no-random-seed-file', + '--trust-model','always', + '--trustdb-name', trustdbFileName, + '--secret-keyring', secringFileName, + '--no-default-keyring', '--keyring', keyringFileName, + ) + gpgImport = gpgBase + ('--import', + ) + gpgEncrypt = gpgBase + ('--recipient', keyID, + '--encrypt', archiveFileName, + ) + (exitcode, stdout, stderr, ) = Popen(gpgImport, Input=keyData) + if exitcode: + print 'gpg-import failed with %s' % exitcode + if stdout: + print 'stdout: ' + repr(stdout) + if stderr: + print 'stderr: ' + repr(stderr) + tryDelete(keyringFileName) + tryDelete(keyringFileName + '~') + tryDelete(secringFileName) + tryDelete(trustdbFileName) + tryDelete(gpgFileName) + return + (exitcode, stdout, stderr, ) = Popen(gpgEncrypt) + if exitcode: + print 'gpg-encrypt failed with %s' % exitcode + if stdout: + print 'stdout: ' + repr(stdout) + if stderr: + print 'stderr: ' + repr(stderr) + tryDelete(keyringFileName) + tryDelete(keyringFileName + '~') + tryDelete(secringFileName) + tryDelete(trustdbFileName) + tryDelete(gpgFileName) + return + tryDelete(keyringFileName) + tryDelete(keyringFileName + '~') + tryDelete(secringFileName) + tryDelete(trustdbFileName) + os.chmod(gpgFileName, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) + print 'done.' + return gpgFileName + +def checkForRoot(): + if os.geteuid() != 0: + print 'Please run this program as root!' + sys.exit(3) + +def prepareArchive(): + global archiveFileName, archiveFile, archive + archiveFileName = tempfile.mkstemp(prefix='univention-support-info-%s.' % hostname, suffix='.tar.bz2')[1] + archiveFile = open(archiveFileName, 'wb') + archive = tarfile.open(mode='w|bz2', fileobj=archiveFile) + +def closeArchive(): + archive.close() + archiveFile.close() + +def prepareEnvironment(): + global env + env = os.environ.copy() + env['LC_ALL'] = 'C' + env['COLUMNS'] = '250' + env['HOME'] = tempfile.mkdtemp(prefix='univention-support-info_') + try: + shutil.copy('/etc/skel/.bashrc', env['HOME']) + shutil.copy('/etc/skel/.profile', env['HOME']) + f = open(os.path.join(env['HOME'], '.toprc'), 'w') + f.write(toprc) + f.close() + except (OSError, IOError, ): + pass + +def cleanup(): + shutil.rmtree(env['HOME']) + +def main(encrypt=False): + checkForRoot() + global archive, env, sambaDomainVersion + prepareArchive() + prepareEnvironment() + + # Check Samba Version + if executeCommand( 'samba4-pdc-dn', (_ldapsearchCommand(), '-xLLL', '(&(univentionService=Samba 4)(objectClass=univentionDomainController))', 'dn') ): + sambaDomainVersion = 4 + else: + sambaDomainVersion = 3 + + # Place new calls below this line + checkMaintenance() + collectCommandData() + simpleFiles() + licenseObject() + templateFiles() + aptPackageList() + atJobs() + certificateValidities() + univentionSystemInfo() + rotatedLogs() + checkEntryUUID() + # Place new calls above this line + + closeArchive() + print 'Data collection completed.' + print + print + if encrypt: + gpgArchiveFileName = gpg(archiveFileName) + if gpgArchiveFileName: + print 'The encrypted data can be found here:' + print ' ' + gpgArchiveFileName + print ' ' + else: + print 'WARNING: The data could not be encrypted!' + print + print 'The unencrypted data can be found here:' + else: + print 'The data can be found here:' + print ' ' + archiveFileName + cleanup() + +if __name__ == "__main__": + parser = optparse.OptionParser() + parser.add_option('--encrypt', action='store_true', dest='encrypt', default=False, help='encrypt data (can only be decrypted by Univention support)') + parser.usage = '\n'.join(('%prog [options]', + 'collect system information', + '', + "%prog collects information about the system's configuration.", + 'The information is stored in a temporary tar archive, the path of which is printed to stdout.', + )) + (options, args, ) = parser.parse_args() + if args: + parser.print_help() + sys.exit(0) + + main(options.encrypt) diff --git a/management/univention-system-info/umc/python/sysinfo/usi.py b/management/univention-system-info/umc/python/sysinfo/usi.py new file mode 100755 index 0000000..f6bb647 --- /dev/null +++ b/management/univention-system-info/umc/python/sysinfo/usi.py @@ -0,0 +1,58 @@ +#! /usr/bin/python2.7 + +import univention.management.console.modules.sysinfo as sysinfo +from univention.management.console.modules import UMC_Error +import argparse + +# TODO ask for Ticket number and / or email + + +def check_args(args): + if not args.ticket and not args.email: + print ("Either a ticket number or an email adress are required, please provide at least one of those") + ticket = raw_input("Ticket No: ") + if ticket and ticket.isdigit(): + args.ticket = ticket + email = raw_input("Email adress: ") + if email: + # TODO validation + args.email = email + return args + + +def usi_process(userinput): + print args + usi = sysinfo.Instance() + print ("Downloading Univention-System-Information Script") + usi._download_usi_script() + print ("Gathering system manufacturer information") + general_info = usi.gather_general_info() + print ("Executing Univention-System-Information Script") + usi_file = usi._execute_usi_script(general_info.get('manufacturer'), general_info.get('model'), userinput.get('comment'), userinput.get('ticket'), userinput.get('email')) + print usi_file + print ("Uploading information") + try: + usi._upload_archive(usi_file) + except(UMC_Error): + print ("An error occured while uploading, you can try sending the informations by mail") + else: + print ("Upload successful") + print ("An archive containing the information can be found at '%s'" % (usi_file,)) + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description='Univention-System-Information') + parser.add_argument('--email', dest='email', action='store', default=None, help='an email adress to contact you') + parser.add_argument('--ticket', dest='ticket' ,action='store', default=None, help='existing Univention-support ticket number') + parser.add_argument('--comment', dest='comment' ,action='store', default=None, help='an optional comment') + + args = parser.parse_args() + + args = check_args(args) + if not args.ticket and not args.email: + #TODO exit + print ("Error, no email or ticket number provided, will exit now") + exit(1) + userinput = vars(args) + usi_process(userinput) diff --git a/management/univention-system-info/umc/sysinfo.xml b/management/univention-system-info/umc/sysinfo.xml index 0a0333d..3d5edd2 100644 --- a/management/univention-system-info/umc/sysinfo.xml +++ b/management/univention-system-info/umc/sysinfo.xml @@ -12,5 +12,7 @@ + +