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 os 

31import sys 

32import time 

33import apt_pkg 

34import errno 

35import fcntl 

36import subprocess 

37 

38from daklib import daklog 

39from daklib import utils 

40from daklib.dbconn import * 

41from daklib.regexes import re_taint_free 

42from daklib.config import Config 

43 

44Options = None 

45Logger = None 

46Queue = None 

47changes = [] 

48 

49 

50def usage(): 

51 print("""Usage: dak security-install [OPTIONS] changesfiles 

52Do whatever there is to do for a security release 

53 

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

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

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

57 

58""") 

59 sys.exit() 

60 

61 

62def spawn(command): 

63 if not re_taint_free.match(command): 

64 utils.fubar("Invalid character in \"%s\"." % (command)) 

65 

66 if Options["No-Action"]: 

67 print("[%s]" % (command)) 

68 else: 

69 try: 

70 subprocess.check_output(command.split(), stderr=subprocess.STDOUT) 

71 except subprocess.CalledProcessError as e: 

72 utils.fubar("Invocation of '%s' failed:\n%s\n" % (command, e.output.rstrip()), e.returncode) 

73 

74##################### ! ! ! N O T E ! ! ! ##################### 

75# 

76# These functions will be reinvoked by semi-priveleged users, be careful not 

77# to invoke external programs that will escalate privileges, etc. 

78# 

79##################### ! ! ! N O T E ! ! ! ##################### 

80 

81 

82def sudo(arg, fn, exit): 

83 if Options["Sudo"]: 

84 subprocess.check_call( 

85 ["/usr/bin/sudo", "-u", "dak", "-H", 

86 "/usr/local/bin/dak", "new-security-install", "-" + arg]) 

87 else: 

88 fn() 

89 if exit: 

90 quit() 

91 

92 

93def do_Approve(): 

94 sudo("A", _do_Approve, True) 

95 

96 

97def _do_Approve(): 

98 print("Locking unchecked") 

99 with os.fdopen(os.open('/srv/security-master.debian.org/lock/unchecked.lock', os.O_CREAT | os.O_RDWR), 'r') as lock_fd: 

100 while True: 

101 try: 

102 fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) 

103 break 

104 except OSError as e: 

105 if e.errno in (errno.EACCES, errno.EAGAIN): 

106 print("Another process keeping the unchecked lock, waiting.") 

107 time.sleep(10) 

108 else: 

109 raise 

110 

111 # 1. Install accepted packages 

112 print("Installing accepted packages into security archive") 

113 for queue_name in ("embargoed",): 

114 spawn("dak process-policy {0}".format(queue_name)) 

115 

116 # 2. Run all the steps that are needed to publish the changed archive 

117 print("Doing loadsa stuff in the archive, will take time, please be patient") 

118 os.environ['configdir'] = '/srv/security-master.debian.org/dak/config/debian-security' 

119 spawn("/srv/security-master.debian.org/dak/config/debian-security/cronscript unchecked-dinstall") 

120 

121 print("Triggering metadata export for packages.d.o and other consumers") 

122 spawn("/srv/security-master.debian.org/dak/config/debian-security/export.sh") 

123 

124######################################################################## 

125######################################################################## 

126 

127 

128def main(): 

129 global Options, Logger, Queue, changes 

130 cnf = Config() 

131 

132 Arguments = [('h', "Help", "Security::Options::Help"), 

133 ('n', "No-Action", "Security::Options::No-Action"), 

134 ('c', 'Changesfile', "Security::Options::Changesfile"), 

135 ('s', "Sudo", "Security::Options::Sudo"), 

136 ('A', "Approve", "Security::Options::Approve") 

137 ] 

138 

139 for i in ["Help", "No-Action", "Changesfile", "Sudo", "Approve"]: 

140 key = "Security::Options::%s" % i 

141 if key not in cnf: 141 ↛ 139line 141 didn't jump to line 139, because the condition on line 141 was never false

142 cnf[key] = "" 

143 

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

145 

146 Options = cnf.subtree("Security::Options") 

147 if Options['Help']: 147 ↛ 150line 147 didn't jump to line 150, because the condition on line 147 was never false

148 usage() 

149 

150 changesfiles = {} 

151 for a in changes_files: 

152 if not a.endswith(".changes"): 

153 utils.fubar("not a .changes file: %s" % (a)) 

154 changesfiles[a] = 1 

155 changes = list(changesfiles.keys()) 

156 

157 username = utils.getusername() 

158 if username != "dak": 

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

160 Options["Sudo"] = "y" 

161 

162 if Options["No-Action"]: 

163 Options["Sudo"] = "" 

164 

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

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

167 

168 session = DBConn().session() 

169 

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

171 if Options["Approve"]: 

172 do_Approve() 

173 sys.exit() 

174 

175 if len(changes) == 0: 

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

177 

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

179 # called it (ownership of the file) 

180 

181 acceptfiles = {} 

182 for change in changes: 

183 dbchange = get_dbchange(os.path.basename(change), session) 

184 # strip epoch from version 

185 version = dbchange.version 

186 version = version[(version.find(':') + 1):] 

187 # strip possible version from source (binNMUs) 

188 source = dbchange.source.split(None, 1)[0] 

189 acceptfilename = "%s/COMMENTS/ACCEPT.%s_%s" % (os.path.dirname(os.path.abspath(changes[0])), source, version) 

190 acceptfiles[acceptfilename] = 1 

191 

192 print("Would create %s now and then go on to accept this package, if you allow me to." % (list(acceptfiles.keys()))) 

193 if Options["No-Action"]: 

194 sys.exit(0) 

195 else: 

196 input("Press Enter to continue") 

197 

198 for acceptfilename in acceptfiles: 

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

200 accept_file.write("OK\n") 

201 

202 do_Approve() 

203 

204 

205if __name__ == '__main__': 

206 main()