Update syntax of 'except' statements for compatibility with Python 3.
[portage.git] / bin / glsa-check
1 #!/usr/bin/python
2 # Copyright 2008-2009 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4 # $Id$
5
6 import sys
7
8 try:
9         import portage
10 except ImportError:
11         from os import path as osp
12         sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
13         import portage
14
15 from portage import os
16 from portage.output import *
17
18 from optparse import OptionGroup, OptionParser
19
20 __program__ = "glsa-check"
21 __author__ = "Marius Mauch <genone@gentoo.org>"
22 __version__ = "1.0"
23
24 def cb_version(*args, **kwargs):
25         """Callback for --version"""
26         sys.stderr.write("\n"+ __program__ + ", version " + __version__ + "\n")
27         sys.stderr.write("Author: " + __author__ + "\n")
28         sys.stderr.write("This program is licensed under the GPL, version 2\n\n")
29         sys.exit(0)
30
31 # option parsing
32 parser = OptionParser(usage="%prog <option> [glsa-list]",
33                 version="%prog "+ __version__)
34 parser.epilog = "glsa-list can contain an arbitrary number of GLSA ids," \
35                 " filenames containing GLSAs or the special identifiers" \
36                 " 'all', 'new' and 'affected'"
37
38 modes = OptionGroup(parser, "Modes")
39 modes.add_option("-l", "--list", action="store_const",
40                 const="list", dest="mode",
41                 help="List all unapplied GLSA")
42 modes.add_option("-d", "--dump", action="store_const",
43                 const="dump", dest="mode",
44                 help="Show all information about the given GLSA")
45 modes.add_option("", "--print", action="store_const",
46                 const="dump", dest="mode",
47                 help="Alias for --dump")
48 modes.add_option("-t", "--test", action="store_const",
49                 const="test", dest="mode",
50                 help="Test if this system is affected by the given GLSA")
51 modes.add_option("-p", "--pretend", action="store_const",
52                 const="pretend", dest="mode",
53                 help="Show the necessary commands to apply this GLSA")
54 modes.add_option("-f", "--fix", action="store_const",
55                 const="fix", dest="mode",
56                 help="Try to auto-apply this GLSA (experimental)")
57 modes.add_option("-i", "--inject", action="store_const", dest="mode",
58                 help="Inject the given GLSA into the checkfile")
59 modes.add_option("-m", "--mail", action="store_const",
60                 const="mail", dest="mode",
61                 help="Send a mail with the given GLSAs to the administrator")
62 parser.add_option_group(modes)
63
64 parser.remove_option("--version")
65 parser.add_option("-V", "--version", action="callback",
66                 callback=cb_version, help="Some information about this tool")
67 parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
68                 help="Print more information")
69 parser.add_option("-n", "--nocolor", action="callback",
70                 callback=lambda *args, **kwargs: nocolor(),
71                 help="Disable colors")
72 parser.add_option("-e", "--emergelike", action="store_false", dest="least_change",
73                 help="Do not use a least-change algorithm")
74 parser.add_option("-c", "--cve", action="store_true", dest="list_cve",
75                 help="Show CAN ids in listing mode")
76
77 options, params = parser.parse_args()
78
79 mode = options.mode
80 least_change = options.least_change
81 list_cve = options.list_cve
82 verbose = options.verbose
83
84 # Sanity checking
85 if mode is None:
86         sys.stderr.write("No mode given: what should I do?\n")
87         parser.print_help()
88         sys.exit(1)
89 elif mode != "list" and not params:
90         sys.stderr.write("\nno GLSA given, so we'll do nothing for now. \n")
91         sys.stderr.write("If you want to run on all GLSA please tell me so \n")
92         sys.stderr.write("(specify \"all\" as parameter)\n\n")
93         parser.print_help()
94         sys.exit(1)
95 elif mode in ["fix", "inject"] and os.geteuid() != 0:
96         # we need root priviledges for write access
97         sys.stderr.write("\nThis tool needs root access to "+options.mode+" this GLSA\n\n")
98         sys.exit(2)
99 elif mode == "list" and not params:
100         params.append("new")
101
102 # delay this for speed increase
103 from portage.glsa import *
104
105 vardb = portage.db[portage.settings["ROOT"]]["vartree"].dbapi
106 portdb = portage.db["/"]["porttree"].dbapi
107
108 # build glsa lists
109 completelist = get_glsa_list(portage.settings)
110
111 checklist = get_applied_glsas(portage.settings)
112 todolist = [e for e in completelist if e not in checklist]
113
114 glsalist = []
115 if "new" in params:
116         glsalist = todolist
117         params.remove("new")
118         
119 if "all" in params:
120         glsalist = completelist
121         params.remove("all")
122 if "affected" in params:
123         # replaced completelist with todolist on request of wschlich
124         for x in todolist:
125                 try:
126                         myglsa = Glsa(x, portage.settings, vardb, portdb)
127                 except (GlsaTypeException, GlsaFormatException) as e:
128                         if verbose:
129                                 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e)))
130                         continue
131                 if myglsa.isVulnerable():
132                         glsalist.append(x)
133         params.remove("affected")
134
135 # remove invalid parameters
136 for p in params[:]:
137         if not (p in completelist or os.path.exists(p)):
138                 sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p))
139                 params.remove(p)
140
141 glsalist.extend([g for g in params if g not in glsalist])
142
143 def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr):
144         fd2.write(white("[A]")+" means this GLSA was already applied,\n")
145         fd2.write(green("[U]")+" means the system is not affected and\n")
146         fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
147
148         myglsalist.sort()
149         for myid in myglsalist:
150                 try:
151                         myglsa = Glsa(myid, portage.settings, vardb, portdb)
152                 except (GlsaTypeException, GlsaFormatException) as e:
153                         if verbose:
154                                 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
155                         continue
156                 if myglsa.isApplied():
157                         status = "[A]"
158                         color = white
159                 elif myglsa.isVulnerable():
160                         status = "[N]"
161                         color = red
162                 else:
163                         status = "[U]"
164                         color = green
165
166                 if verbose:
167                         access = ("[%-8s] " % myglsa.access)
168                 else:
169                         access=""
170
171                 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
172                 if not verbose:
173                         for pkg in myglsa.packages.keys()[:3]:
174                                 fd1.write(" " + pkg + " ")
175                         if len(myglsa.packages) > 3:
176                                 fd1.write("... ")
177                 else:
178                         for pkg in myglsa.packages.keys():
179                                 mylist = vardb.match(portage.dep_getkey(str(pkg)))
180                                 if len(mylist) > 0:
181                                         pkg = color(" ".join(mylist))
182                                 fd1.write(" " + pkg + " ")
183
184                 fd1.write(")")
185                 if list_cve:
186                         fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
187                 fd1.write("\n")         
188         return 0
189
190 if mode == "list":
191         sys.exit(summarylist(glsalist))
192
193 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
194 if mode in ["dump", "fix", "inject", "pretend"]:
195         for myid in glsalist:
196                 try:
197                         myglsa = Glsa(myid, portage.settings, vardb, portdb)
198                 except (GlsaTypeException, GlsaFormatException) as e:
199                         if verbose:
200                                 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
201                         continue
202                 if mode == "dump":
203                         myglsa.dump()
204                 elif mode == "fix":
205                         sys.stdout.write("fixing "+myid+"\n")
206                         mergelist = myglsa.getMergeList(least_change=least_change)
207                         for pkg in mergelist:
208                                 sys.stdout.write(">>> merging "+pkg+"\n")
209                                 # using emerge for the actual merging as it contains the dependency
210                                 # code and we want to be consistent in behaviour. Also this functionality
211                                 # will be integrated in emerge later, so it shouldn't hurt much.
212                                 emergecmd = "emerge --oneshot " + portage.settings["EMERGE_OPTS"] + " =" + pkg
213                                 if verbose:
214                                         sys.stderr.write(emergecmd+"\n")
215                                 exitcode = os.system(emergecmd)
216                                 # system() returns the exitcode in the high byte of a 16bit integer
217                                 if exitcode >= 1<<8:
218                                         exitcode >>= 8
219                                 if exitcode:
220                                         sys.exit(exitcode)
221                         myglsa.inject()
222                 elif mode == "pretend":
223                         sys.stdout.write("Checking GLSA "+myid+"\n")
224                         mergelist = myglsa.getMergeList(least_change=least_change)
225                         if mergelist:
226                                 sys.stdout.write("The following updates will be performed for this GLSA:\n")
227                                 for pkg in mergelist:
228                                         oldver = None
229                                         for x in vardb.match(portage.dep_getkey(pkg)):
230                                                 if vardb.aux_get(x, ["SLOT"]) == portdb.aux_get(pkg, ["SLOT"]):
231                                                         oldver = x
232                                         if oldver == None:
233                                                 raise ValueError("could not find old version for package %s" % pkg)
234                                         oldver = oldver[len(portage.dep_getkey(oldver))+1:]
235                                         sys.stdout.write("     " + pkg + " (" + oldver + ")\n")
236                         else:
237                                 sys.stdout.write("Nothing to do for this GLSA\n")
238                 elif mode == "inject":
239                         sys.stdout.write("injecting " + myid + "\n")
240                         myglsa.inject()
241                 sys.stdout.write("\n")
242         sys.exit(0)
243
244 # test is a bit different as Glsa.test() produces no output
245 if mode == "test":
246         outputlist = []
247         for myid in glsalist:
248                 try:
249                         myglsa = Glsa(myid, portage.settings, vardb, portdb)
250                 except (GlsaTypeException, GlsaFormatException) as e:
251                         if verbose:
252                                 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
253                         continue
254                 if myglsa.isVulnerable():
255                         outputlist.append(str(myglsa.nr))
256         if len(outputlist) > 0:
257                 sys.stderr.write("This system is affected by the following GLSAs:\n")
258                 if verbose:
259                         summarylist(outputlist)
260                 else:
261                         sys.stdout.write("\n".join(outputlist)+"\n")
262         else:
263                 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
264         sys.exit(0)
265
266 # mail mode as requested by solar
267 if mode == "mail":
268         import portage.mail, socket
269         from StringIO import StringIO
270         from email.mime.text import MIMEText
271         
272         # color doesn't make any sense for mail
273         nocolor()
274
275         if "PORTAGE_ELOG_MAILURI" in portage.settings:
276                 myrecipient = portage.settings["PORTAGE_ELOG_MAILURI"].split()[0]
277         else:
278                 myrecipient = "root@localhost"
279         
280         if "PORTAGE_ELOG_MAILFROM" in portage.settings:
281                 myfrom = portage.settings["PORTAGE_ELOG_MAILFROM"]
282         else:
283                 myfrom = "glsa-check"
284
285         mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
286
287         # need a file object for summarylist()
288         myfd = StringIO()
289         myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
290         myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
291         summarylist(glsalist, fd1=myfd, fd2=myfd)
292         summary = str(myfd.getvalue())
293         myfd.close()
294
295         myattachments = []
296         for myid in glsalist:
297                 try:
298                         myglsa = Glsa(myid, portage.settings, vardb, portdb)
299                 except (GlsaTypeException, GlsaFormatException) as e:
300                         if verbose:
301                                 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
302                         continue
303                 myfd = StringIO()
304                 myglsa.dump(outstream=myfd)
305                 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
306                 myfd.close()
307                 
308         mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
309         portage.mail.send_mail(portage.settings, mymessage)
310                 
311         sys.exit(0)
312         
313 # something wrong here, all valid paths are covered with sys.exit()
314 sys.stderr.write("nothing more to do\n")
315 sys.exit(2)