Use EROOT instead of ROOT for keys everywhere.
[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 eroot = portage.settings['EROOT']
107 vardb = portage.db[eroot]["vartree"].dbapi
108 portdb = portage.db[eroot]["porttree"].dbapi
109
110 # build glsa lists
111 completelist = get_glsa_list(portage.settings)
112
113 checklist = get_applied_glsas(portage.settings)
114 todolist = [e for e in completelist if e not in checklist]
115
116 glsalist = []
117 if "new" in params:
118         glsalist = todolist
119         params.remove("new")
120         
121 if "all" in params:
122         glsalist = completelist
123         params.remove("all")
124 if "affected" in params:
125         # replaced completelist with todolist on request of wschlich
126         for x in todolist:
127                 try:
128                         myglsa = Glsa(x, portage.settings, vardb, portdb)
129                 except (GlsaTypeException, GlsaFormatException) as e:
130                         if verbose:
131                                 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (x, e)))
132                         continue
133                 if myglsa.isVulnerable():
134                         glsalist.append(x)
135         params.remove("affected")
136
137 # remove invalid parameters
138 for p in params[:]:
139         if not (p in completelist or os.path.exists(p)):
140                 sys.stderr.write(("(removing %s from parameter list as it isn't a valid GLSA specification)\n" % p))
141                 params.remove(p)
142
143 glsalist.extend([g for g in params if g not in glsalist])
144
145 def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr):
146         fd2.write(white("[A]")+" means this GLSA was already applied,\n")
147         fd2.write(green("[U]")+" means the system is not affected and\n")
148         fd2.write(red("[N]")+" indicates that the system might be affected.\n\n")
149
150         myglsalist.sort()
151         for myid in myglsalist:
152                 try:
153                         myglsa = Glsa(myid, portage.settings, vardb, portdb)
154                 except (GlsaTypeException, GlsaFormatException) as e:
155                         if verbose:
156                                 fd2.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
157                         continue
158                 if myglsa.isApplied():
159                         status = "[A]"
160                         color = white
161                 elif myglsa.isVulnerable():
162                         status = "[N]"
163                         color = red
164                 else:
165                         status = "[U]"
166                         color = green
167
168                 if verbose:
169                         access = ("[%-8s] " % myglsa.access)
170                 else:
171                         access=""
172
173                 fd1.write(color(myglsa.nr) + " " + color(status) + " " + color(access) + myglsa.title + " (")
174                 if not verbose:
175                         for pkg in list(myglsa.packages)[:3]:
176                                 fd1.write(" " + pkg + " ")
177                         if len(myglsa.packages) > 3:
178                                 fd1.write("... ")
179                 else:
180                         for pkg in myglsa.packages:
181                                 mylist = vardb.match(pkg)
182                                 if len(mylist) > 0:
183                                         pkg = color(" ".join(mylist))
184                                 fd1.write(" " + pkg + " ")
185
186                 fd1.write(")")
187                 if list_cve:
188                         fd1.write(" "+(",".join([r[:13] for r in myglsa.references if r[:4] in ["CAN-", "CVE-"]])))
189                 fd1.write("\n")         
190         return 0
191
192 if mode == "list":
193         sys.exit(summarylist(glsalist))
194
195 # dump, fix, inject and fix are nearly the same code, only the glsa method call differs
196 if mode in ["dump", "fix", "inject", "pretend"]:
197         for myid in glsalist:
198                 try:
199                         myglsa = Glsa(myid, portage.settings, vardb, portdb)
200                 except (GlsaTypeException, GlsaFormatException) as e:
201                         if verbose:
202                                 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
203                         continue
204                 if mode == "dump":
205                         myglsa.dump()
206                 elif mode == "fix":
207                         sys.stdout.write("fixing "+myid+"\n")
208                         mergelist = myglsa.getMergeList(least_change=least_change)
209                         for pkg in mergelist:
210                                 sys.stdout.write(">>> merging "+pkg+"\n")
211                                 # using emerge for the actual merging as it contains the dependency
212                                 # code and we want to be consistent in behaviour. Also this functionality
213                                 # will be integrated in emerge later, so it shouldn't hurt much.
214                                 emergecmd = "emerge --oneshot " + portage.settings["EMERGE_OPTS"] + " =" + pkg
215                                 if verbose:
216                                         sys.stderr.write(emergecmd+"\n")
217                                 exitcode = os.system(emergecmd)
218                                 # system() returns the exitcode in the high byte of a 16bit integer
219                                 if exitcode >= 1<<8:
220                                         exitcode >>= 8
221                                 if exitcode:
222                                         sys.exit(exitcode)
223                         myglsa.inject()
224                 elif mode == "pretend":
225                         sys.stdout.write("Checking GLSA "+myid+"\n")
226                         mergelist = myglsa.getMergeList(least_change=least_change)
227                         if mergelist:
228                                 sys.stdout.write("The following updates will be performed for this GLSA:\n")
229                                 for pkg in mergelist:
230                                         oldver = None
231                                         for x in vardb.match(portage.cpv_getkey(pkg)):
232                                                 if vardb.aux_get(x, ["SLOT"]) == portdb.aux_get(pkg, ["SLOT"]):
233                                                         oldver = x
234                                         if oldver == None:
235                                                 raise ValueError("could not find old version for package %s" % pkg)
236                                         oldver = oldver[len(portage.cpv_getkey(oldver))+1:]
237                                         sys.stdout.write("     " + pkg + " (" + oldver + ")\n")
238                         else:
239                                 sys.stdout.write("Nothing to do for this GLSA\n")
240                 elif mode == "inject":
241                         sys.stdout.write("injecting " + myid + "\n")
242                         myglsa.inject()
243                 sys.stdout.write("\n")
244         sys.exit(0)
245
246 # test is a bit different as Glsa.test() produces no output
247 if mode == "test":
248         outputlist = []
249         for myid in glsalist:
250                 try:
251                         myglsa = Glsa(myid, portage.settings, vardb, portdb)
252                 except (GlsaTypeException, GlsaFormatException) as e:
253                         if verbose:
254                                 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
255                         continue
256                 if myglsa.isVulnerable():
257                         outputlist.append(str(myglsa.nr))
258         if len(outputlist) > 0:
259                 sys.stderr.write("This system is affected by the following GLSAs:\n")
260                 if verbose:
261                         summarylist(outputlist)
262                 else:
263                         sys.stdout.write("\n".join(outputlist)+"\n")
264         else:
265                 sys.stderr.write("This system is not affected by any of the listed GLSAs\n")
266         sys.exit(0)
267
268 # mail mode as requested by solar
269 if mode == "mail":
270         import portage.mail, socket
271         from io import StringIO
272         from email.mime.text import MIMEText
273         
274         # color doesn't make any sense for mail
275         nocolor()
276
277         if "PORTAGE_ELOG_MAILURI" in portage.settings:
278                 myrecipient = portage.settings["PORTAGE_ELOG_MAILURI"].split()[0]
279         else:
280                 myrecipient = "root@localhost"
281         
282         if "PORTAGE_ELOG_MAILFROM" in portage.settings:
283                 myfrom = portage.settings["PORTAGE_ELOG_MAILFROM"]
284         else:
285                 myfrom = "glsa-check"
286
287         mysubject = "[glsa-check] Summary for %s" % socket.getfqdn()
288
289         # need a file object for summarylist()
290         myfd = StringIO()
291         myfd.write("GLSA Summary report for host %s\n" % socket.getfqdn())
292         myfd.write("(Command was: %s)\n\n" % " ".join(sys.argv))
293         summarylist(glsalist, fd1=myfd, fd2=myfd)
294         summary = str(myfd.getvalue())
295         myfd.close()
296
297         myattachments = []
298         for myid in glsalist:
299                 try:
300                         myglsa = Glsa(myid, portage.settings, vardb, portdb)
301                 except (GlsaTypeException, GlsaFormatException) as e:
302                         if verbose:
303                                 sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e)))
304                         continue
305                 myfd = StringIO()
306                 myglsa.dump(outstream=myfd)
307                 myattachments.append(MIMEText(str(myfd.getvalue()), _charset="utf8"))
308                 myfd.close()
309                 
310         mymessage = portage.mail.create_message(myfrom, myrecipient, mysubject, summary, myattachments)
311         portage.mail.send_mail(portage.settings, mymessage)
312                 
313         sys.exit(0)
314         
315 # something wrong here, all valid paths are covered with sys.exit()
316 sys.stderr.write("nothing more to do\n")
317 sys.exit(2)