|
|
|
1 |
#!/usr/bin/env python |
2 |
# -*- coding: utf-8 -*- |
3 |
# |
4 |
# univention-support-info - collect system information |
5 |
# |
6 |
# Copyright 2011 Univention GmbH |
7 |
# |
8 |
# http://www.univention.de/ |
9 |
# |
10 |
# All rights reserved. |
11 |
# |
12 |
# The source code of this program is made available |
13 |
# under the terms of the GNU Affero General Public License version 3 |
14 |
# (GNU AGPL V3) as published by the Free Software Foundation. |
15 |
# |
16 |
# Binary versions of this program provided by Univention to you as |
17 |
# well as other copyrighted, protected or trademarked materials like |
18 |
# Logos, graphics, fonts, specific documentations and configurations, |
19 |
# cryptographic keys etc. are subject to a license agreement between |
20 |
# you and Univention and not subject to the GNU AGPL V3. |
21 |
# |
22 |
# In the case you use this program under the terms of the GNU AGPL V3, |
23 |
# the program is provided in the hope that it will be useful, |
24 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
25 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
26 |
# GNU Affero General Public License for more details. |
27 |
# |
28 |
# You should have received a copy of the GNU Affero General Public |
29 |
# License with the Debian GNU/Linux or Univention distribution in file |
30 |
# /usr/share/common-licenses/AGPL-3; if not, see |
31 |
# <http://www.gnu.org/licenses/>. |
32 |
|
33 |
keyID = '18B913ABF3C1550B8C2DFDA994F01970E89E3BEC' |
34 |
# create with: gpg --armor --export --export-options export-minimal $keyID |
35 |
keyData = '\n'.join(('-----BEGIN PGP PUBLIC KEY BLOCK-----', |
36 |
'Version: GnuPG v1.4.9 (GNU/Linux)', |
37 |
'', |
38 |
'mQGiBEA2RhgRBADee7hGh5TPPwqs+z5tfLett9xaVxmRkUfBvqesVIj93WzrKsU3', |
39 |
'S7hjnMlh+JieFjJLy6jTJYtvqqFIxh3vw5btCwhntrTQnu00U/r3UdznqE/zGH2L', |
40 |
'A734aHaSaq6UFKE5kwX0DECgSI1hwc20d7guLJXSqOpwfYktXiB+27GRCwCgh4OR', |
41 |
'MWncPkhaJhusO8YCSnSN0GED/2ez8utubP1FloTfTof4/OLfBvPwdgJ5Q7FRqeF9', |
42 |
'wDmfd8Hetzr+Fh4zMs6dY0c5+unUQiLXjY9F01WT7SM+yFrs5EHzb+gyjIdTmTtn', |
43 |
'mtNTL2cZK8freAv9LPWCHfQ1rii+Qd71+/CKLDfwwLqQxEAkOsrpOsUD4dip6vkm', |
44 |
'ZtaRBACUYoOtB738OzjPqOpbnmNQjQYVtGCocDjfKs+bMyq+LPOyC31NVC4LvqhC', |
45 |
'nwXSUSy8jfXLzInPXgEUiHvGAEvnNzRLAIh6W/pIaK7tIITESmV/C3PBvxLEkJNm', |
46 |
'8Ll+2qGVspYnGTUFe6JzxARzcTVow4JlB+dX40bk7LNqUe6Au7QqVW5pdmVudGlv', |
47 |
'biBTdXBwb3J0IDxzdXBwb3J0QHVuaXZlbnRpb24uZGU+iF4EExECAB4GCwkIBwMC', |
48 |
'AxUCAwMWAgECHgECF4AFAkm+NOYCGQEACgkQlPAZcOieO+xhXwCfdZ7+eWGpJfhz', |
49 |
'CnrfuzgdzqsetMMAn3oUbDZCqzH083DKCNS5547V8XkCtCpVbml2ZW50aW9uIFN1', |
50 |
'cHBvcnQgPHNlcnZpY2VAdW5pdmVudGlvbi5kZT6IYAQTEQIAIAUCSNdW8wIbIwYL', |
51 |
'CQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEJTwGXDonjvsQcoAn2b9FNPECtPus0Sf', |
52 |
'3ENGafyXSIq7AJ9sbNm+QryXW1rtvtsHlNgYI9eGpLQrVW5pdmVudGlvbiBTdXBw', |
53 |
'b3J0IDxmZWVkYmFja0B1bml2ZW50aW9uLmRlPohgBBMRAgAgBQJI11aYAhsjBgsJ', |
54 |
'CAcDAgQVAggDBBYCAwECHgECF4AACgkQlPAZcOieO+wmzACfVmZKVc37T7W9FQGN', |
55 |
'K+dXsLbDplkAniAYh0l9Nd8wMh/QZrxcRdlgOBrhtDRVbml2ZW50aW9uIFN1cHBv', |
56 |
'cnQgPHBhcnRuZXItdGVjaG5pY2FsQHVuaXZlbnRpb24uZGU+iGAEExECACAFAkho', |
57 |
'p9gCGyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU8Blw6J477IMYAJ9oZoTS', |
58 |
'vHsxF7TYJSGo3pcutuV4RACfbxLxxjU7suCGs+khvINRl9pJel60KlVuaXZlbnRp', |
59 |
'b24gU2VydmljZSA8c2VydmljZUB1bml2ZW50aW9uLmRlPohgBBMRAgAgBQJJvjVV', |
60 |
'AhsjBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQlPAZcOieO+ycGwCeM8pJxUrL', |
61 |
'oiKylTxiKZ4MwQoV/L0An3y/9GafqT8SaUgSjYO+9P4szWATtCtQYXJ0bmVyLUxp', |
62 |
'c3RzIDxwYXJ0bmVyLWxpc3RzQHVuaXZlbnRpb24uZGU+iGAEExECACAFAkm+NQEC', |
63 |
'GyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU8Blw6J477C67AJ93Pv9Dcq7g', |
64 |
'HreBv3f1Q07IjDI3bACfUZgClXNISIewXER8FzV5cC08LRm0LFVuaXZlbnRpb24g', |
65 |
'RmVlZGJhY2sgPGZlZWRiYWNrQHVuaXZlbnRpb24uZGU+iGAEExECACAFAkm+NSsC', |
66 |
'GyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU8Blw6J477E8QAJ0XE91GawgL', |
67 |
'lVGfeiiT7c32P35IDACePCNLSzHA6K6JrV7CP98BeZUUNYu0LFVuaXZlbnRpb24g', |
68 |
'SGVscGRlc2sgPGhlbHBkZXNrQHVuaXZlbnRpb24uZGU+iGAEExECACAFAkm+NUMC', |
69 |
'GyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU8Blw6J477JNxAJ4l9wqf+WtV', |
70 |
'C0sAtu91aGWvgW/JQACfTJUCWf1DTqn1sDhbNiCG2jA+B9K0LFVuaXZlbnRpb24g', |
71 |
'VmVydHJpZWIgPHZlcnRyaWViQHVuaXZlbnRpb24uZGU+iGAEExECACAFAkm+NWwC', |
72 |
'GyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU8Blw6J477Jq3AJ9fN/6oKbqY', |
73 |
'exKHPdLQw31wC8HGTgCfZ5gn5zDd2JSY9tirDtMw5SSdfk20M1BhcnRuZXItVGVj', |
74 |
'aG5pY2FsIDxwYXJ0bmVyLXRlY2huaWNhbEB1bml2ZW50aW9uLmRlPohgBBMRAgAg', |
75 |
'BQJJvjUPAhsjBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQlPAZcOieO+wlNQCf', |
76 |
'eZ7Z4PeCx7SF+k1CK0bC8oouICkAn3/NYT1ASQLFP8CBqku1shs1lFCstDNQcmVt', |
77 |
'aXVtLVRlY2huaWNhbCA8cHJlbWl1bS10ZWNobmljYWxAdW5pdmVudGlvbi5kZT6I', |
78 |
'YAQTEQIAIAUCSb41GgIbIwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEJTwGXDo', |
79 |
'njvsxqcAnjRmgwWtXJqEbptTSSkaamNmGp9KAJ0R7b4RYDSKr+aFGPhhgetqLNXO', |
80 |
'MrQwVW5pdmVudGlvbiBPWCBTdXBwb3J0IDxveC1zdXBwb3J0QHVuaXZlbnRpb24u', |
81 |
'ZGU+iGAEExECACAFAk1GmXcCGyMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCU', |
82 |
'8Blw6J477JwmAJ9WeeR2SzUtHY7pPolhm4fQmtM20QCfaebVO97CHdKkcAqPjJ3K', |
83 |
'RwWVtpC5AQ0EQDZGGxAEAJotyyct6jvNscl9q2stDB+BUuXefdd7UMdSySSGqt5c', |
84 |
'7f/6IEX/eiG/2nIsqX1IsSQ+Bw0ZQTQUhgI8hICUsmjdjvWnBCyHX8xnMESITDv/', |
85 |
'fJmxgaP8fbMSJexhnizjlz8m74OgnfFew6EuRWRXA/SDeTwmsaUafTv7biKaDlU7', |
86 |
'AAMFA/9hJUqdh+tSaEfwUzPgHdFT8EIM2B0VSmVnqHSWwCjuJTLTWJi+DDe2hq7p', |
87 |
'QPpcATzgEg5qu5lsqh0AAXV998fD/RiO3B+ct1rwYbNlchACIXtgDTe43dmUaKkp', |
88 |
'fPRxeQZr8iym706LJOyppF+jXqOm2oy6Sf++/YElcCBmPPDIwIhGBBgRAgAGBQJA', |
89 |
'NkYbAAoJEJTwGXDonjvsqD0AnRVxlYyWk3DrKL0ZCxRZrtpW6pbwAJ9R/HZLaaH+', |
90 |
'043H7VXVPPTjhs6Tig==', |
91 |
'=BE0y', |
92 |
'-----END PGP PUBLIC KEY BLOCK-----', |
93 |
)) |
94 |
toprc = '\n'.join(('RCfile for "top with windows" # shameless braggin\'', |
95 |
'Id:a, Mode_altscr=0, Mode_irixps=1, Delay_time=3.000, Curwin=0', |
96 |
'Def fieldscur=AEHIOQTWKNMbcdfgjplrsuvyzX', |
97 |
' winflags=62905, sortindx=10, maxtasks=0', |
98 |
' summclr=1, msgsclr=1, headclr=3, taskclr=1', |
99 |
'Job fieldscur=ABcefgjlrstuvyzMKNHIWOPQDX', |
100 |
' winflags=62777, sortindx=0, maxtasks=0', |
101 |
' summclr=6, msgsclr=6, headclr=7, taskclr=6', |
102 |
'Mem fieldscur=ANOPQRSTUVbcdefgjlmyzWHIKX', |
103 |
' winflags=62777, sortindx=13, maxtasks=0', |
104 |
' summclr=5, msgsclr=5, headclr=4, taskclr=5', |
105 |
'Usr fieldscur=ABDECGfhijlopqrstuvyzMKNWX', |
106 |
' winflags=62777, sortindx=4, maxtasks=0', |
107 |
' summclr=3, msgsclr=3, headclr=2, taskclr=3', |
108 |
'', |
109 |
)) |
110 |
|
111 |
import cStringIO |
112 |
import glob |
113 |
import gzip |
114 |
import optparse |
115 |
import os |
116 |
import shutil |
117 |
import socket |
118 |
import stat |
119 |
import subprocess |
120 |
import sys |
121 |
import tarfile |
122 |
import tempfile |
123 |
import time |
124 |
from distutils.spawn import find_executable |
125 |
|
126 |
# ignore apt's "API not stable yet" warning |
127 |
import warnings |
128 |
warnings.filterwarnings("ignore", category=FutureWarning, append=True) |
129 |
|
130 |
import apt |
131 |
import apt_pkg |
132 |
|
133 |
try: |
134 |
from univention import config_registry |
135 |
except ImportError: |
136 |
if os.path.isfile('/usr/share/pyshared/univention/config_registry.py'): |
137 |
import imp |
138 |
config_registry = imp.load_source('config_registry', '/usr/share/pyshared/univention/config_registry.py') |
139 |
else: |
140 |
sys.path.append('/usr/share/pyshared') |
141 |
from univention import config_registry |
142 |
|
143 |
ucr = config_registry.ConfigRegistry() |
144 |
ucr.load() |
145 |
timeString = time.strftime('%Y-%m-%d_%H-%M-%SZ', time.gmtime()) |
146 |
hostname = socket.gethostname() |
147 |
|
148 |
|
149 |
|
150 |
def Popen(CommandTuple, Input=None): |
151 |
try: |
152 |
process = subprocess.Popen(CommandTuple, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, env=env) |
153 |
(stdoutdata, stderrdata) = process.communicate(input=Input) |
154 |
return (process.returncode, stdoutdata, stderrdata) |
155 |
except (OSError, IOError), error: |
156 |
return ('Could not execute %s: %s' % (repr(CommandTuple), repr(' '.join(map(str, error.args)))), '', '') |
157 |
|
158 |
def _ldapsearchCommand(): |
159 |
''' Get ldapsearch command depending on UCS version ''' |
160 |
if find_executable('univention-ldapsearch'): |
161 |
return 'univention-ldapsearch' |
162 |
return 'ldapsearch' |
163 |
|
164 |
def _sprint(string): |
165 |
''' write status string to stdout without newline ''' |
166 |
sys.stdout.write(string) |
167 |
sys.stdout.flush() |
168 |
|
169 |
def U32(i): |
170 |
''' |
171 |
Return i as an unsigned integer, assuming it fits in 32 bits. |
172 |
If it's >= 2GB when viewed as a 32-bit unsigned int, return a long. |
173 |
''' |
174 |
if i < 0: |
175 |
i += 1L << 32 |
176 |
return i |
177 |
|
178 |
def addFile(name, size, fileobj): |
179 |
# try to determine filesize |
180 |
if size == None or size == False: |
181 |
fileobj.seek(0,os.SEEK_END) |
182 |
size = fileobj.tell() |
183 |
if fileobj.tell() > 0: |
184 |
fileobj.seek(0) |
185 |
|
186 |
info = tarfile.TarInfo() |
187 |
info.size = size |
188 |
info.name = 'univention-support-info-' + hostname + '-' + timeString + '/' + name |
189 |
info.mode = stat.S_IRUSR |
190 |
info.mtime = time.time() |
191 |
archive.addfile(info, fileobj) |
192 |
|
193 |
def addFileByPath(filename): |
194 |
if filename.startswith('/proc/'): |
195 |
# This is a /proc/ file, os.stat(filename).st_size will always return 0 |
196 |
tmpf = tempfile.mkstemp(prefix='univention-support-info.')[1] |
197 |
try: |
198 |
shutil.copy(filename, tmpf) |
199 |
except (OSError, IOError), error: |
200 |
error = '\n'.join(map(str, error.args)) |
201 |
addFile('files/' + filename.strip('/').replace('/', '_') + '.ERROR', len(error), cStringIO.StringIO(error)) |
202 |
os.unlink(tmpf) |
203 |
filesize = os.stat(tmpf).st_size |
204 |
os.unlink(tmpf) |
205 |
else: |
206 |
try: |
207 |
filesize = os.stat(filename).st_size |
208 |
except (OSError, IOError), error: |
209 |
filesize = 0 |
210 |
|
211 |
try: |
212 |
fileobj = open(filename, 'rb') |
213 |
except (OSError, IOError), error: |
214 |
error = '\n'.join(map(str, error.args)) |
215 |
addFile('files/' + filename.strip('/').replace('/', '_') + '.ERROR', len(error), cStringIO.StringIO(error)) |
216 |
else: |
217 |
addFile('files/' + filename.strip('/').replace('/', '_'), filesize, fileobj) |
218 |
fileobj.close() |
219 |
|
220 |
def certificateValidity(CertificatePath): |
221 |
try: |
222 |
import M2Crypto |
223 |
except ImportError, error: |
224 |
error = '\n'.join(map(str, error.args)) |
225 |
addFile('info/ssl/' + CertificatePath.strip('/').replace('/', '_') + '.ERROR', len(error), cStringIO.StringIO(error)) |
226 |
return |
227 |
|
228 |
try: |
229 |
cert = M2Crypto.X509.load_cert(CertificatePath) |
230 |
validity = '%s\nNot Before: %s\nNot After : %s\n' % (CertificatePath, cert.get_not_before(), cert.get_not_after(), ) |
231 |
addFile('info/ssl/' + CertificatePath.strip('/').replace('/', '_'), len(validity), cStringIO.StringIO(validity)) |
232 |
except (OSError, IOError, M2Crypto.X509.X509Error), error: |
233 |
error = '\n'.join(map(str, error.args)) |
234 |
addFile('info/ssl/' + CertificatePath.strip('/').replace('/', '_') + '.ERROR', len(error), cStringIO.StringIO(error)) |
235 |
|
236 |
def certificateValidities(): |
237 |
_sprint( 'Checking certificate validity: ' ) |
238 |
CertificatePatterns = [ |
239 |
'/etc/univention/ssl/*.*/cert.pem', |
240 |
'/etc/univention/ssl/ucsCA/CAcert.pem', |
241 |
] |
242 |
for CertificatePattern in CertificatePatterns: |
243 |
for CertificatePath in glob.glob(CertificatePattern): |
244 |
certificateValidity(CertificatePath) |
245 |
print 'done.' |
246 |
|
247 |
def simpleFiles(): |
248 |
FilePatterns = [ |
249 |
'/boot/config-*', |
250 |
'/etc/apache2/*', |
251 |
'/etc/apache2/*/*', |
252 |
'/etc/apt/*', |
253 |
'/etc/apt/*/*', |
254 |
'/etc/cron*/*', |
255 |
'/etc/fstab', |
256 |
'/etc/imapd/*', |
257 |
'/etc/mtab', |
258 |
'/etc/passwd', |
259 |
'/etc/procmailrc', |
260 |
'/etc/spamassassin/*', |
261 |
'/etc/univention/connector/ad/mapping.py', |
262 |
'/etc/univention/installation_profile', |
263 |
'/etc/ox-secrets/*', |
264 |
'/opt/open-xchange/etc/*', |
265 |
'/opt/open-xchange/etc/*/*', |
266 |
'/opt/open-xchange/etc/*/*/*', |
267 |
'/proc/mounts*', |
268 |
'/proc/mdstat', |
269 |
'/proc/drbd', |
270 |
'/proc/cmdline', |
271 |
'/var/lib/univention-directory-replication/failed.ldif', |
272 |
'/etc/postfix/*', |
273 |
'/etc/imapd/*', |
274 |
'/var/univention-backup/ad-takeover/*', |
275 |
'/etc/*/local.conf*', |
276 |
'/root/.bash_history', |
277 |
'/var/log/installer/*', |
278 |
'/etc/ldap/slapd.conf', |
279 |
] |
280 |
FileExcludePatterns = [ '/etc/apache2/mods-available/*', '/etc/apache2/sites-available/*' ] |
281 |
|
282 |
Files = set() |
283 |
ExcludeFiles = list() |
284 |
for FileExcludePattern in FileExcludePatterns: |
285 |
ExcludeFiles.extend(glob.glob(FileExcludePattern)) |
286 |
for FilePattern in FilePatterns: |
287 |
for Filename in glob.glob(FilePattern): |
288 |
if not Filename in ExcludeFiles: |
289 |
Files.add(Filename) |
290 |
|
291 |
_sprint( 'Collecting files: ' ) |
292 |
for Filename in sorted(list(Files)): |
293 |
if os.path.isfile(Filename): |
294 |
addFileByPath(Filename) |
295 |
_sprint('.') |
296 |
print 'done.' |
297 |
|
298 |
def licenseObject(): |
299 |
''' Get license object from ldap (cn=license) ''' |
300 |
stdout = executeCommand( 'univention-license-object', (_ldapsearchCommand(), '-x' , '-b', 'cn=license,cn=univention,'+ucr.get('ldap/base')) ) |
301 |
addFile('info/univention-license-object', len(stdout), cStringIO.StringIO(stdout)) |
302 |
|
303 |
|
304 |
def checkMaintenance(): |
305 |
''' Check if UCS-Version is in maintenance ''' |
306 |
if ucr.get('version/version') <= '3.1' and ucr.get('server/role') != 'ucc': |
307 |
tmpf = tempfile.TemporaryFile(prefix='univention-support-info.') |
308 |
print "Please note, system is no longer maintained, security updates are no longer available for current UCS Version %s" % (ucr.get('version/version')) |
309 |
print >> tmpf, "Please note, system is no longer maintained, security updates are no longer available for current UCS Version %s" % (ucr.get('version/version')) |
310 |
addFile( 'info/no_maintenance', None, tmpf ) |
311 |
|
312 |
def checkEntryUUID(): |
313 |
''' Check if ldap base is searchable by its entryUUID ''' |
314 |
entryuuid="" |
315 |
basedn="" |
316 |
tmpf = tempfile.TemporaryFile(prefix='univention-support-info.') |
317 |
(exitcode, stdout, stderr, ) = Popen( (_ldapsearchCommand(), '-xLLL', '-sbase', 'entryUUID', ) ) |
318 |
if exitcode == 0: |
319 |
entryuuid = stdout.split()[3] |
320 |
else: |
321 |
print >> tmpf, "ERROR: ldapsearch for base failed: %s" % str(stderr) |
322 |
addFile( 'info/entryUUID.stderr', None, tmpf ) |
323 |
return |
324 |
(exitcode, stdout, stderr, ) = Popen( (_ldapsearchCommand(), '-xLLL', 'entryUUID='+entryuuid, 'dn', ) ) |
325 |
if exitcode == 0: |
326 |
basedn = stdout.split()[1] |
327 |
else: |
328 |
print >> tmpf, "ERROR: ldapsearch by entryUUID failed: %s" % str(stderr) |
329 |
addFile( 'info/entryUUID.stderr', None, tmpf ) |
330 |
return |
331 |
if ucr.get('ldap/base') == basedn: |
332 |
print >> tmpf, "OK: ldap base found by entryUUID" |
333 |
else: |
334 |
print >> tmpf, "ERROR: ldap base not found by entryUUID, check ldap index" |
335 |
addFile( 'info/entryUUID', None, tmpf ) |
336 |
|
337 |
def aptPackageList(): |
338 |
"""List installed packages and their source repository.""" |
339 |
_sprint( 'Collecting package lists: ' ) |
340 |
cache = apt.Cache() |
341 |
|
342 |
packagesAll = tempfile.TemporaryFile(prefix='univention-support-info.') |
343 |
packagesUnknownSource = tempfile.TemporaryFile(prefix='univention-support-info.') |
344 |
|
345 |
if not hasattr(apt, 'deprecation'): # python apt 0.7.7 in UCS < 3.0 |
346 |
packages = [_ for _ in cache if _.isInstalled] |
347 |
for pkg in packages: |
348 |
pkg._lookupRecord(True) |
349 |
try: |
350 |
path = apt_pkg.ParseSection(pkg._records.Record)["Filename"] |
351 |
except KeyError: |
352 |
print >> packagesUnknownSource, "%s\tUNKNOWN" % (pkg.name,) |
353 |
continue |
354 |
cand = pkg._depcache.GetCandidateVer(pkg._pkg) |
355 |
for packagefile, _ in cand.FileList: |
356 |
indexfile = cache._list.FindIndex(packagefile) |
357 |
if indexfile: |
358 |
uri = indexfile.ArchiveURI(path) |
359 |
print >> packagesAll, "%s\t%s" % (pkg.name, uri) |
360 |
else: |
361 |
packages = [_ for _ in cache if _.is_installed] |
362 |
for pkg in packages: |
363 |
version = pkg.installed.version |
364 |
package = pkg.versions[version] |
365 |
try: |
366 |
uri = package.uri |
367 |
except StopIteration: |
368 |
print >> packagesUnknownSource, "%s\tUNKNOWN" % (pkg.name,) |
369 |
continue |
370 |
print >> packagesAll, "%s\t%s" % (pkg.name, uri) |
371 |
|
372 |
addFile( 'info/packages_all', None, packagesAll ) |
373 |
addFile( 'info/packages_unknown-source', None, packagesUnknownSource ) |
374 |
print 'done.' |
375 |
|
376 |
def executeCommand(commandName, command, log_stderr = False): |
377 |
(exitcode, stdout, stderr, ) = Popen(command) |
378 |
if exitcode or log_stderr: |
379 |
if type(exitcode) is int: |
380 |
stderr += '\nExitcode was %d\n' % exitcode |
381 |
else: |
382 |
stderr += exitcode + '\n' |
383 |
addFile('info/' + commandName + '.stderr', len(stderr), cStringIO.StringIO(stderr)) |
384 |
return stdout |
385 |
|
386 |
def templateFiles(): |
387 |
_sprint( 'Searching for changed template files: ' ) |
388 |
stdout = executeCommand('check-templates', ('find', '/etc/univention/templates/files/', '(', '-name', '*.dpkg-new', '-o', '-name', '*.dpkg-dist', ')', '-print0', )) |
389 |
files = [templatefile for templatefile in stdout.split('\0') if templatefile] |
390 |
message = ('Found %d:\n' % len(files)) + '\n'.join(files) + '\n' |
391 |
addFile('info/check-templates', len(message), cStringIO.StringIO(message)) |
392 |
for templatefile in files: |
393 |
addFileByPath(templatefile) |
394 |
if templatefile.endswith('.dpkg-new'): |
395 |
addFileByPath(templatefile[:-len('.dpkg-new')]) |
396 |
elif templatefile.endswith('.dpkg-dist'): |
397 |
addFileByPath(templatefile[:-len('.dpkg-dist')]) |
398 |
_sprint('.') |
399 |
print 'done.' |
400 |
|
401 |
def collectCommandData(): |
402 |
commands = {'hostname-f': |
403 |
('hostname', '--fqdn', ), |
404 |
'ifconfig-a': |
405 |
('ifconfig', '-v', '-a', ), |
406 |
'iptables-L_filter': |
407 |
('iptables', '-L', '-n', '-v', '-t', 'filter', ), |
408 |
'iptables-L_nat': |
409 |
('iptables', '-L', '-n', '-v', '-t', 'nat', ), |
410 |
'iptables-L_mangle': |
411 |
('iptables', '-L', '-n', '-v', '-t', 'mangle', ), |
412 |
'iptables-L_raw': |
413 |
('iptables', '-L', '-n', '-v', '-t', 'raw', ), |
414 |
'iptables-save': |
415 |
('iptables-save', '-c', ), |
416 |
'route-4': |
417 |
('route', '-v', '-ee', '-n', '-A', 'inet', ), |
418 |
'route-6': |
419 |
('route', '-v', '-ee', '-n', '-A', 'inet6', ), |
420 |
'netstat': |
421 |
('netstat', '--tcp', '--udp', '--listening', '--program', '--extend', '--extend', '--verbose', '--timers', '--numeric', '--wide', ), |
422 |
'dpkg-l': |
423 |
('dpkg-query', '--show', '--showformat=${Status}\t${Package}\t${Version}\n', ), |
424 |
'dpkg--audit': |
425 |
('dpkg', '--audit', ), |
426 |
'uname': |
427 |
('uname', '-a', ), |
428 |
'ps': |
429 |
('ps', '-AHFly', ), |
430 |
'ps-full': |
431 |
('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', ), |
432 |
'ucr-dump': |
433 |
('univention-config-registry', 'dump', ), |
434 |
'df-full': |
435 |
('df', '--portability', '--print-type', ), |
436 |
'df-i-full': |
437 |
('df', '--portability', '--print-type', '--inodes', ), |
438 |
'df': |
439 |
('df', '-h', ), |
440 |
'df-i': |
441 |
('df', '-h', '-i', ), |
442 |
'join-status': |
443 |
('univention-check-join-status', ), |
444 |
'virsh-qemu': |
445 |
('virsh', '-c', 'qemu:///system', 'capabilities', ), |
446 |
'virsh-xen': |
447 |
('virsh', '-c', 'xen:///', 'capabilities', ), |
448 |
'top': |
449 |
('top', '-b', '-n2', ), |
450 |
'testparm': |
451 |
(('testparm', '-s', '-vvv', ), True), |
452 |
'listenerID': |
453 |
('cat', '/var/lib/univention-directory-listener/notifier_id', ), |
454 |
'notifierID': |
455 |
('/usr/share/univention-directory-listener/get_notifier_id.py', ), |
456 |
'mailq': |
457 |
('mailq', ), |
458 |
'univention-license-check': |
459 |
('univention-license-check', ), |
460 |
'hostaccount-id': |
461 |
('id', ucr.get('hostname') + '$', ), |
462 |
'dig_AXFR': |
463 |
('dig', '@'+ucr.get('nameserver1'), ucr.get('domainname'), '-t', 'AXFR'), |
464 |
'univention-connector-list-rejected': |
465 |
('univention-connector-list-rejected', ), |
466 |
} |
467 |
|
468 |
# Commands depending on samba version |
469 |
if sambaDomainVersion == 3: |
470 |
commands.update({'test-join': |
471 |
('net', 'rpc', 'testjoin', ), |
472 |
}) |
473 |
elif sambaDomainVersion == 4: |
474 |
commands.update({'net-ads-info': |
475 |
('net', 'ads', 'info', ), |
476 |
'net-ads-lookup': |
477 |
('net', 'ads', 'lookup', ), |
478 |
}) |
479 |
if ucr.get('samba4/role', None): |
480 |
# Run only S4 |
481 |
commands.update({'samba-tool-drs-showrepl': |
482 |
('samba-tool', 'drs', 'showrepl', ), |
483 |
'samba-tool-domain-level-show': |
484 |
('samba-tool', 'domain', 'level', 'show'), |
485 |
'samba-tool-domain-passwordsettings': |
486 |
('samba-tool', 'domain', 'passwordsettings', 'show' ), |
487 |
'testparm-samba4': |
488 |
(('testparm.samba4', '-s', '-vvv'), True), |
489 |
'samba-tool-fsmo-show': |
490 |
('samba-tool', 'fsmo', 'show'), |
491 |
'univention-s4connector-list-rejected': |
492 |
('univention-s4connector-list-rejected', ), |
493 |
'samba-tool-processes': |
494 |
('samba-tool', 'processes'), |
495 |
}) |
496 |
# >= Samba4 RC (UCS 3.1) |
497 |
if ucr.get('version/version') >= '3.1': |
498 |
commands.update({'samba-tool-domain-info': |
499 |
('samba-tool', 'domain', 'info', '127.0.0.1', ), |
500 |
}) |
501 |
else: |
502 |
# Run only on S3 member in S4 domain |
503 |
commands.update({'test-join': |
504 |
('net', 'ads', 'testjoin', ), |
505 |
}) |
506 |
|
507 |
_sprint( 'Collecting command output: ' ) |
508 |
for commandName in commands: |
509 |
command = commands[commandName] |
510 |
if type(command[0]) == tuple: |
511 |
stdout = executeCommand(commandName, command[0], command[1]) |
512 |
else: |
513 |
stdout = executeCommand(commandName, command) |
514 |
|
515 |
addFile('info/' + commandName, len(stdout), cStringIO.StringIO(stdout)) |
516 |
_sprint('.') |
517 |
print 'done.' |
518 |
|
519 |
def univentionSystemInfo(): |
520 |
_sprint( 'Collecting hardware information: ' ) |
521 |
manu = executeCommand('dmidecode', ('dmidecode', '-s', 'system-manufacturer')) |
522 |
product = executeCommand('dmidecode', ('dmidecode', '-s', 'system-product-name')) |
523 |
if not manu: |
524 |
manu = 'Unknown' |
525 |
if not product: |
526 |
product = 'Unknown' |
527 |
stdout = executeCommand('univention-system-info', ('univention-system-info','-u','-m',manu,'-t',product,'-c','Created by univention-support-info','-s','-',)) |
528 |
archive = None |
529 |
for line in stdout.split('\n'): |
530 |
if line.startswith('archive'): |
531 |
archive = line.split(':', 1)[1] |
532 |
if not archive: |
533 |
error = 'No archive returned!' |
534 |
error += '\nunivention-system-info stdout:\n%s' % stdout |
535 |
addFile('info/univention-system-info.ERROR', len(error), cStringIO.StringIO(error)) |
536 |
return |
537 |
filename =os.path.join( '/var/www/univention-management-console/system-info/', archive ) |
538 |
# If UMC is not installed /var/www/univention-management-console/system-info/ does not exist and archive stays in /tmp |
539 |
if not os.path.isfile( filename ): |
540 |
filename = os.path.join( '/tmp/', archive ) |
541 |
try: |
542 |
archive = tarfile.open(name=filename, mode='r:*') |
543 |
for member in archive: |
544 |
if member.isfile(): |
545 |
fileobj = archive.extractfile(member) |
546 |
addFile('info/univention-system-info/' + member.name, member.size, fileobj) |
547 |
fileobj.close() |
548 |
archive.close() |
549 |
except (IOError, tarfile.TarError, ), error: |
550 |
error = '\n'.join(map(str, error.args)) |
551 |
error += '\nunivention-system-info stdout:\n%s' % stdout |
552 |
addFile('info/univention-system-info.ERROR', len(error), cStringIO.StringIO(error)) |
553 |
print 'done.' |
554 |
|
555 |
def rotatedLogs(): |
556 |
DefaultMaxLineCount = 1000 |
557 |
FilePatterns = [ |
558 |
('/var/log/apache/*', DefaultMaxLineCount), |
559 |
('/var/log/apache2/*', DefaultMaxLineCount), |
560 |
('/var/log/auth.log*', DefaultMaxLineCount), |
561 |
('/var/log/dpkg.log*', DefaultMaxLineCount), |
562 |
('/var/log/mail.log*', DefaultMaxLineCount), |
563 |
('/var/log/open-xchange/*', DefaultMaxLineCount), |
564 |
('/var/log/samba/log.*', DefaultMaxLineCount), |
565 |
('/var/log/kern*', DefaultMaxLineCount), |
566 |
('/var/log/univention/*', DefaultMaxLineCount), |
567 |
('/var/log/univention/ucc-clients/*', DefaultMaxLineCount), |
568 |
('/var/log/apt/term.log*', DefaultMaxLineCount), |
569 |
('/var/log/squid/*', DefaultMaxLineCount), |
570 |
('/var/log/dansguardian/*', DefaultMaxLineCount), |
571 |
('/var/log/squidguard/*', DefaultMaxLineCount), |
572 |
('/var/log/syslog*', 2000000), |
573 |
('/var/log/univention/system-stats.log*', 5000000), |
574 |
('/var/log/freeradius/*', DefaultMaxLineCount), |
575 |
('/var/log/cups/*', DefaultMaxLineCount), |
576 |
] |
577 |
FullLogs = set(( # for these every available log-file shall be included |
578 |
'/var/log/dpkg.log', |
579 |
'/var/log/univention/updater.log', |
580 |
'/var/log/apt/term.log', |
581 |
'/var/log/univention/ad-takeover.log', |
582 |
)) |
583 |
GzipSuffix = '.gz' |
584 |
logs = {} |
585 |
for FilePattern in [fp[0] for fp in FilePatterns]: |
586 |
for filename in glob.glob(FilePattern): |
587 |
if os.stat(filename).st_size <= 0 or os.path.isdir(filename): |
588 |
# ignore 0 byte files |
589 |
continue |
590 |
if filename.endswith(GzipSuffix): |
591 |
gzipped = True |
592 |
filename = filename[:-len(GzipSuffix)] |
593 |
else: |
594 |
gzipped = False |
595 |
if not filename.endswith('.') and os.path.splitext(filename)[1].strip('0123456789') == '.': # has extension and only digits in it |
596 |
(filename, ext, ) = os.path.splitext(filename) |
597 |
number = int(ext.lstrip('.')) |
598 |
else: |
599 |
number = -1 |
600 |
if filename not in logs: |
601 |
logs[filename] = {} |
602 |
logs[filename][number] = gzipped |
603 |
|
604 |
_sprint( 'Collecting logfiles: ' ) |
605 |
for logname in sorted(logs): |
606 |
logLinecount = 0 |
607 |
nonemptyNumber = 0 |
608 |
for number in sorted(logs[logname]): |
609 |
# is logname gzipped? |
610 |
gzipped = logs[logname][number] |
611 |
path = logname |
612 |
fileLinecount = 0 |
613 |
if number != -1: |
614 |
path += '.%d' % number |
615 |
|
616 |
if gzipped: |
617 |
try: |
618 |
logfile = open(path + GzipSuffix, 'rb') |
619 |
except (OSError, IOError, ), error: |
620 |
error = '\n'.join(map(str, error.args)) |
621 |
addFile('files/' + '%s_%d.stderr' % (logname.strip('/').replace('/', '_'), number, ), len(error), cStringIO.StringIO(error)) |
622 |
continue |
623 |
|
624 |
# calc bytes and linecount of logfile |
625 |
# last 4 bytes of a gzip file contain the size of the original (uncompressed) input data modulo 2^32. |
626 |
try: |
627 |
logfile.seek(-4, os.SEEK_END) |
628 |
except IOError, e: |
629 |
print '\n\nFilename: %s' %path + GzipSuffix |
630 |
raise IOError(e) |
631 |
fileBytes = U32( gzip.read32(logfile) ) |
632 |
logfile.close() |
633 |
fileLinecount = int( subprocess.Popen( 'zcat -f %s | wc -l' % (path + GzipSuffix), stdout=subprocess.PIPE, shell=True).stdout.read().strip() ) |
634 |
else: |
635 |
# addFile may calculate the size for us |
636 |
fileBytes = None |
637 |
fileLinecount = int( subprocess.Popen( ('wc', '-l', path), stdout=subprocess.PIPE).stdout.read().strip().split()[0] ) |
638 |
logLinecount += fileLinecount |
639 |
#if gzipped: |
640 |
# _sprint( '"%s", "%s", "%s" -> ' % (path + GzipSuffix, fileBytes, fileLinecount) ) |
641 |
#else: |
642 |
# _sprint( '"%s", "%s", "%s" -> ' % (path, fileBytes, fileLinecount) ) |
643 |
|
644 |
if fileLinecount <= 0: |
645 |
# skip logname if empty |
646 |
#print 'ERROR: Empty file? "%s"' % path |
647 |
continue |
648 |
|
649 |
|
650 |
nonemptyNumber += 1 |
651 |
try: |
652 |
if gzipped: |
653 |
logfile = gzip.GzipFile(path + GzipSuffix, 'rb') |
654 |
else: |
655 |
logfile = open(path, 'rb') |
656 |
except (OSError, IOError, ), error: |
657 |
error = '\n'.join(map(str, error.args)) |
658 |
addFile('files/' + '%s_%d.stderr' % (logname.strip('/').replace('/', '_'), nonemptyNumber, ), len(error), cStringIO.StringIO(error)) |
659 |
continue |
660 |
|
661 |
# Add file to archive ... |
662 |
addFile('files/' + '%s_%d' % (logname.strip('/').replace('/', '_'), nonemptyNumber, ), fileBytes, logfile) |
663 |
#print 'Added as "%s_%d"' % (logname.strip('/').replace('/', '_'), nonemptyNumber, ) |
664 |
logfile.close() |
665 |
|
666 |
if logname not in FullLogs and logLinecount > filter(lambda x: logname.startswith(x[0].replace('*', '')), FilePatterns)[0][1]: |
667 |
break |
668 |
|
669 |
_sprint('.') |
670 |
print 'done.' |
671 |
|
672 |
def atJobs(): |
673 |
''' |
674 |
Generate a list of at-Jobs (usefull for UCS@school) |
675 |
''' |
676 |
try: |
677 |
from univention.lib import atjobs as at |
678 |
except ImportError, error: |
679 |
error = str(error.message) |
680 |
addFile('info/at-jobs' + '.ERROR', len(error), cStringIO.StringIO(error)) |
681 |
return |
682 |
|
683 |
jobs = '' |
684 |
try: |
685 |
for job in at.list( extended=True ): |
686 |
jobs += '\n'.join( (str(job), job.command) ) |
687 |
except OSError, error: |
688 |
error = str(error.message) |
689 |
addFile('info/at-jobs' + '.ERROR', len(error), cStringIO.StringIO(error)) |
690 |
addFile('info/at-jobs', len(jobs), cStringIO.StringIO(jobs)) |
691 |
|
692 |
def tryDelete(filename): |
693 |
try: |
694 |
os.remove(filename) |
695 |
except (OSError, IOError, ): |
696 |
pass |
697 |
|
698 |
def gpg(archiveFileName): |
699 |
_sprint( 'Encrypting file: ' ) |
700 |
keyringFileName = tempfile.mkstemp(prefix='univention-support-info-keyring.', suffix='.gpg')[1] |
701 |
secringFileName = tempfile.mkstemp(prefix='univention-support-info-secring.', suffix='.gpg')[1] |
702 |
trustdbFileName = tempfile.mkstemp(prefix='univention-support-info-trustdb.', suffix='.gpg')[1] |
703 |
tryDelete(trustdbFileName) # HACK: file must not exist for gpg to work |
704 |
gpgFileName = archiveFileName + '.gpg' |
705 |
gpgBase = ('gpg', |
706 |
'--batch', '--quiet', '--no-tty', |
707 |
'--with-colons', '--utf8-strings', |
708 |
'--no-auto-check-trustdb', '--no-auto-key-locate', '--no-use-agent', |
709 |
'--no-options', |
710 |
'--no-random-seed-file', |
711 |
'--trust-model','always', |
712 |
'--trustdb-name', trustdbFileName, |
713 |
'--secret-keyring', secringFileName, |
714 |
'--no-default-keyring', '--keyring', keyringFileName, |
715 |
) |
716 |
gpgImport = gpgBase + ('--import', |
717 |
) |
718 |
gpgEncrypt = gpgBase + ('--recipient', keyID, |
719 |
'--encrypt', archiveFileName, |
720 |
) |
721 |
(exitcode, stdout, stderr, ) = Popen(gpgImport, Input=keyData) |
722 |
if exitcode: |
723 |
print 'gpg-import failed with %s' % exitcode |
724 |
if stdout: |
725 |
print 'stdout: ' + repr(stdout) |
726 |
if stderr: |
727 |
print 'stderr: ' + repr(stderr) |
728 |
tryDelete(keyringFileName) |
729 |
tryDelete(keyringFileName + '~') |
730 |
tryDelete(secringFileName) |
731 |
tryDelete(trustdbFileName) |
732 |
tryDelete(gpgFileName) |
733 |
return |
734 |
(exitcode, stdout, stderr, ) = Popen(gpgEncrypt) |
735 |
if exitcode: |
736 |
print 'gpg-encrypt failed with %s' % exitcode |
737 |
if stdout: |
738 |
print 'stdout: ' + repr(stdout) |
739 |
if stderr: |
740 |
print 'stderr: ' + repr(stderr) |
741 |
tryDelete(keyringFileName) |
742 |
tryDelete(keyringFileName + '~') |
743 |
tryDelete(secringFileName) |
744 |
tryDelete(trustdbFileName) |
745 |
tryDelete(gpgFileName) |
746 |
return |
747 |
tryDelete(keyringFileName) |
748 |
tryDelete(keyringFileName + '~') |
749 |
tryDelete(secringFileName) |
750 |
tryDelete(trustdbFileName) |
751 |
os.chmod(gpgFileName, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) |
752 |
print 'done.' |
753 |
return gpgFileName |
754 |
|
755 |
def checkForRoot(): |
756 |
if os.geteuid() != 0: |
757 |
print 'Please run this program as root!' |
758 |
sys.exit(3) |
759 |
|
760 |
def prepareArchive(): |
761 |
global archiveFileName, archiveFile, archive |
762 |
archiveFileName = tempfile.mkstemp(prefix='univention-support-info-%s.' % hostname, suffix='.tar.bz2')[1] |
763 |
archiveFile = open(archiveFileName, 'wb') |
764 |
archive = tarfile.open(mode='w|bz2', fileobj=archiveFile) |
765 |
|
766 |
def closeArchive(): |
767 |
archive.close() |
768 |
archiveFile.close() |
769 |
|
770 |
def prepareEnvironment(): |
771 |
global env |
772 |
env = os.environ.copy() |
773 |
env['LC_ALL'] = 'C' |
774 |
env['COLUMNS'] = '250' |
775 |
env['HOME'] = tempfile.mkdtemp(prefix='univention-support-info_') |
776 |
try: |
777 |
shutil.copy('/etc/skel/.bashrc', env['HOME']) |
778 |
shutil.copy('/etc/skel/.profile', env['HOME']) |
779 |
f = open(os.path.join(env['HOME'], '.toprc'), 'w') |
780 |
f.write(toprc) |
781 |
f.close() |
782 |
except (OSError, IOError, ): |
783 |
pass |
784 |
|
785 |
def cleanup(): |
786 |
shutil.rmtree(env['HOME']) |
787 |
|
788 |
def main(encrypt=False): |
789 |
checkForRoot() |
790 |
global archive, env, sambaDomainVersion |
791 |
prepareArchive() |
792 |
prepareEnvironment() |
793 |
|
794 |
# Check Samba Version |
795 |
if executeCommand( 'samba4-pdc-dn', (_ldapsearchCommand(), '-xLLL', '(&(univentionService=Samba 4)(objectClass=univentionDomainController))', 'dn') ): |
796 |
sambaDomainVersion = 4 |
797 |
else: |
798 |
sambaDomainVersion = 3 |
799 |
|
800 |
# Place new calls below this line |
801 |
checkMaintenance() |
802 |
collectCommandData() |
803 |
simpleFiles() |
804 |
licenseObject() |
805 |
templateFiles() |
806 |
aptPackageList() |
807 |
atJobs() |
808 |
certificateValidities() |
809 |
univentionSystemInfo() |
810 |
rotatedLogs() |
811 |
checkEntryUUID() |
812 |
# Place new calls above this line |
813 |
|
814 |
closeArchive() |
815 |
print 'Data collection completed.' |
816 |
print |
817 |
print |
818 |
if encrypt: |
819 |
gpgArchiveFileName = gpg(archiveFileName) |
820 |
if gpgArchiveFileName: |
821 |
print 'The encrypted data can be found here:' |
822 |
print ' ' + gpgArchiveFileName |
823 |
print ' ' |
824 |
else: |
825 |
print 'WARNING: The data could not be encrypted!' |
826 |
print |
827 |
print 'The unencrypted data can be found here:' |
828 |
else: |
829 |
print 'The data can be found here:' |
830 |
print ' ' + archiveFileName |
831 |
cleanup() |
832 |
|
833 |
if __name__ == "__main__": |
834 |
parser = optparse.OptionParser() |
835 |
parser.add_option('--encrypt', action='store_true', dest='encrypt', default=False, help='encrypt data (can only be decrypted by Univention support)') |
836 |
parser.usage = '\n'.join(('%prog [options]', |
837 |
'collect system information', |
838 |
'', |
839 |
"%prog collects information about the system's configuration.", |
840 |
'The information is stored in a temporary tar archive, the path of which is printed to stdout.', |
841 |
)) |
842 |
(options, args, ) = parser.parse_args() |
843 |
if args: |
844 |
parser.print_help() |
845 |
sys.exit(0) |
846 |
|
847 |
main(options.encrypt) |