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