1#! /usr/bin/env python3 

2 

3""" 

4Do whatever is needed to get a security upload released 

5 

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""" 

10 

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. 

15 

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. 

20 

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 

24 

25################################################################################ 

26 

27 

28################################################################################ 

29 

30import errno 

31import fcntl 

32import os 

33import subprocess 

34import sys 

35import time 

36 

37import apt_pkg 

38 

39from daklib import daklog, utils 

40from daklib.config import Config 

41from daklib.dbconn import DBConn, get_dbchange 

42from daklib.regexes import re_taint_free 

43 

44Options = None 

45Logger = None 

46Queue = None 

47changes = [] 

48 

49 

50def usage(): 

51 print( 

52 """Usage: dak security-install [OPTIONS] changesfiles 

53Do whatever there is to do for a security release 

54 

55 -h, --help show this help and exit 

56 -n, --no-action don't commit changes 

57 -s, --sudo dont bother, used internally 

58 

59""" 

60 ) 

61 sys.exit() 

62 

63 

64def spawn(command): 

65 if not re_taint_free.match(command): 

66 utils.fubar('Invalid character in "%s".' % (command)) 

67 

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 ) 

78 

79 

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 ! ! ! ##################### 

86 

87 

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() 

105 

106 

107def do_Approve(): 

108 sudo("A", _do_Approve, True) 

109 

110 

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 

130 

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)) 

135 

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 ) 

144 

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") 

147 

148 

149######################################################################## 

150######################################################################## 

151 

152 

153def main(): 

154 global Options, Logger, Queue, changes 

155 cnf = Config() 

156 

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 ] 

164 

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] = "" 

169 

170 changes_files = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) 

171 

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() 

175 

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()) 

182 

183 username = utils.getusername() 

184 if username != "dak": 

185 print("Non-dak user: %s" % username) 

186 Options["Sudo"] = "y" 

187 

188 if Options["No-Action"]: 

189 Options["Sudo"] = "" 

190 

191 if not Options["Sudo"] and not Options["No-Action"]: 

192 Logger = daklog.Logger("security-install") 

193 

194 session = DBConn().session() 

195 

196 # If we call ourselve to approve, we do just that and exit 

197 if Options["Approve"]: 

198 do_Approve() 

199 sys.exit() 

200 

201 if len(changes) == 0: 

202 utils.fubar("Need changes files as arguments") 

203 

204 # Yes, we could do this inside do_Approve too. But this way we see who exactly 

205 # called it (ownership of the file) 

206 

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 

221 

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") 

230 

231 for acceptfilename in acceptfiles: 

232 with open(acceptfilename, "w") as accept_file: 

233 accept_file.write("OK\n") 

234 

235 do_Approve() 

236 

237 

238if __name__ == "__main__": 

239 main()