1#! /usr/bin/env python3
3"""
4Do whatever is needed to get a security upload released
6@contact: Debian FTP Master <ftpmaster@debian.org>
7@copyright: 2010 Joerg Jaspert <joerg@debian.org>
8@license: GNU General Public License version 2 or later
9"""
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation; either version 2 of the License, or
14# (at your option) any later version.
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25################################################################################
28################################################################################
30import errno
31import fcntl
32import os
33import subprocess
34import sys
35import time
37import apt_pkg
39from daklib import daklog, utils
40from daklib.config import Config
41from daklib.dbconn import DBConn, get_dbchange
42from daklib.regexes import re_taint_free
44Options = None
45Logger = None
46Queue = None
47changes = []
50def usage():
51 print(
52 """Usage: dak security-install [OPTIONS] changesfiles
53Do whatever there is to do for a security release
55 -h, --help show this help and exit
56 -n, --no-action don't commit changes
57 -s, --sudo dont bother, used internally
59"""
60 )
61 sys.exit()
64def spawn(command):
65 if not re_taint_free.match(command):
66 utils.fubar('Invalid character in "%s".' % (command))
68 if Options["No-Action"]:
69 print("[%s]" % (command))
70 else:
71 try:
72 subprocess.check_output(command.split(), stderr=subprocess.STDOUT)
73 except subprocess.CalledProcessError as e:
74 utils.fubar(
75 "Invocation of '%s' failed:\n%s\n" % (command, e.output.rstrip()),
76 e.returncode,
77 )
80##################### ! ! ! N O T E ! ! ! #####################
81#
82# These functions will be reinvoked by semi-priveleged users, be careful not
83# to invoke external programs that will escalate privileges, etc.
84#
85##################### ! ! ! N O T E ! ! ! #####################
88def sudo(arg, fn, exit):
89 if Options["Sudo"]:
90 subprocess.check_call(
91 [
92 "/usr/bin/sudo",
93 "-u",
94 "dak",
95 "-H",
96 "/usr/local/bin/dak",
97 "new-security-install",
98 "-" + arg,
99 ]
100 )
101 else:
102 fn()
103 if exit:
104 quit()
107def do_Approve():
108 sudo("A", _do_Approve, True)
111def _do_Approve():
112 print("Locking unchecked")
113 with os.fdopen(
114 os.open(
115 "/srv/security-master.debian.org/lock/unchecked.lock",
116 os.O_CREAT | os.O_RDWR,
117 ),
118 "r",
119 ) as lock_fd:
120 while True:
121 try:
122 fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
123 break
124 except OSError as e:
125 if e.errno in (errno.EACCES, errno.EAGAIN):
126 print("Another process keeping the unchecked lock, waiting.")
127 time.sleep(10)
128 else:
129 raise
131 # 1. Install accepted packages
132 print("Installing accepted packages into security archive")
133 for queue_name in ("embargoed",):
134 spawn("dak process-policy {0}".format(queue_name))
136 # 2. Run all the steps that are needed to publish the changed archive
137 print("Doing loadsa stuff in the archive, will take time, please be patient")
138 os.environ["configdir"] = (
139 "/srv/security-master.debian.org/dak/config/debian-security"
140 )
141 spawn(
142 "/srv/security-master.debian.org/dak/config/debian-security/cronscript unchecked-dinstall"
143 )
145 print("Triggering metadata export for packages.d.o and other consumers")
146 spawn("/srv/security-master.debian.org/dak/config/debian-security/export.sh")
149########################################################################
150########################################################################
153def main():
154 global Options, Logger, Queue, changes
155 cnf = Config()
157 Arguments = [
158 ("h", "Help", "Security::Options::Help"),
159 ("n", "No-Action", "Security::Options::No-Action"),
160 ("c", "Changesfile", "Security::Options::Changesfile"),
161 ("s", "Sudo", "Security::Options::Sudo"),
162 ("A", "Approve", "Security::Options::Approve"),
163 ]
165 for i in ["Help", "No-Action", "Changesfile", "Sudo", "Approve"]:
166 key = "Security::Options::%s" % i
167 if key not in cnf: 167 ↛ 165line 167 didn't jump to line 165, because the condition on line 167 was never false
168 cnf[key] = ""
170 changes_files = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
172 Options = cnf.subtree("Security::Options")
173 if Options["Help"]: 173 ↛ 176line 173 didn't jump to line 176, because the condition on line 173 was never false
174 usage()
176 changesfiles = {}
177 for a in changes_files:
178 if not a.endswith(".changes"):
179 utils.fubar("not a .changes file: %s" % (a))
180 changesfiles[a] = 1
181 changes = list(changesfiles.keys())
183 username = utils.getusername()
184 if username != "dak":
185 print("Non-dak user: %s" % username)
186 Options["Sudo"] = "y"
188 if Options["No-Action"]:
189 Options["Sudo"] = ""
191 if not Options["Sudo"] and not Options["No-Action"]:
192 Logger = daklog.Logger("security-install")
194 session = DBConn().session()
196 # If we call ourselve to approve, we do just that and exit
197 if Options["Approve"]:
198 do_Approve()
199 sys.exit()
201 if len(changes) == 0:
202 utils.fubar("Need changes files as arguments")
204 # Yes, we could do this inside do_Approve too. But this way we see who exactly
205 # called it (ownership of the file)
207 acceptfiles = {}
208 for change in changes:
209 dbchange = get_dbchange(os.path.basename(change), session)
210 # strip epoch from version
211 version = dbchange.version
212 version = version[(version.find(":") + 1) :]
213 # strip possible version from source (binNMUs)
214 source = dbchange.source.split(None, 1)[0]
215 acceptfilename = "%s/COMMENTS/ACCEPT.%s_%s" % (
216 os.path.dirname(os.path.abspath(changes[0])),
217 source,
218 version,
219 )
220 acceptfiles[acceptfilename] = 1
222 print(
223 "Would create %s now and then go on to accept this package, if you allow me to."
224 % (list(acceptfiles.keys()))
225 )
226 if Options["No-Action"]:
227 sys.exit(0)
228 else:
229 input("Press Enter to continue")
231 for acceptfilename in acceptfiles:
232 with open(acceptfilename, "w") as accept_file:
233 accept_file.write("OK\n")
235 do_Approve()
238if __name__ == "__main__":
239 main()