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