]> git.decadent.org.uk Git - dak.git/commitdiff
Initial import of newamber from klecker.
authorJames Troup <james@nocrew.org>
Fri, 16 Jun 2006 04:31:52 +0000 (05:31 +0100)
committerJames Troup <james@nocrew.org>
Fri, 16 Jun 2006 04:31:52 +0000 (05:31 +0100)
dak/new_security_install.py [new file with mode: 0755]

diff --git a/dak/new_security_install.py b/dak/new_security_install.py
new file mode 100755 (executable)
index 0000000..3d8c6da
--- /dev/null
@@ -0,0 +1,549 @@
+#!/usr/bin/env python
+
+import katie, logging, utils, db_access
+import apt_pkg, os, sys, pwd, time, re, commands
+
+re_taint_free = re.compile(r"^['/;\-\+\.\s\w]+$");
+
+Cnf = None
+Options = None
+Katie = None
+Logger = None
+
+advisory = None
+changes = []
+srcverarches = {}
+
+def init():
+    global Cnf, Katie, Options, Logger
+
+    Cnf = utils.get_conf()
+    Cnf["Dinstall::Options::No-Mail"] = "y"
+    Arguments = [('h', "help", "Amber::Options::Help"),
+                 ('a', "automatic", "Amber::Options::Automatic"),
+                 ('n', "no-action", "Amber::Options::No-Action"),
+                ('s', "sudo", "Amber::Options::Sudo"),
+                (' ', "no-upload", "Amber::Options::No-Upload"),
+                (' ', "drop-advisory", "Amber::Options::Drop-Advisory"),
+                ('A', "approve", "Amber::Options::Approve"),
+                ('R', "reject", "Amber::Options::Reject"),
+                ('D', "disembargo", "Amber::Options::Disembargo") ]
+
+    for i in Arguments:
+         Cnf[i[2]] = ""
+
+    arguments = apt_pkg.ParseCommandLine(Cnf, Arguments, sys.argv)
+
+    Options = Cnf.SubTree("Amber::Options")
+
+    whoami = os.getuid()
+    whoamifull = pwd.getpwuid(whoami)
+    username = whoamifull[0]
+    if username != "katie":
+        print "Non-katie user: %s" % username
+        Options["Sudo"] = "y"
+
+    if Options["Help"]:
+        print "help yourself"
+       sys.exit(0)
+
+    if len(arguments) == 0:
+       utils.fubar("Process what?")
+
+    Katie = katie.Katie(Cnf)
+    if not Options["Sudo"] and not Options["No-Action"]:
+        Logger = Katie.Logger = logging.Logger(Cnf, "newamber")
+
+    return arguments
+
+def quit():
+    if Logger:
+        Logger.close()
+    sys.exit(0)
+
+def load_args(arguments):
+    global advisory, changes
+
+    adv_ids = {}
+    if not arguments[0].endswith(".changes"):
+       adv_ids [arguments[0]] = 1
+       arguments = arguments[1:]
+
+    null_adv_changes = []
+
+    changesfiles = {}
+    for a in arguments:
+        if "/" in a:
+            utils.fubar("can only deal with files in the current directory")
+        if not a.endswith(".changes"):
+            utils.fubar("not a .changes file: %s" % (a))
+        Katie.init_vars()
+        Katie.pkg.changes_file = a
+        Katie.update_vars()
+        if "adv id" in Katie.pkg.changes:
+            changesfiles[a] = 1
+            adv_ids[Katie.pkg.changes["adv id"]] = 1
+        else:
+            null_adv_changes.append(a)
+
+    adv_ids = adv_ids.keys()
+    if len(adv_ids) > 1:
+        utils.fubar("multiple advisories selected: %s" % (", ".join(adv_ids)))
+    if adv_ids == []:
+        advisory = None
+    else:
+        advisory = adv_ids[0]
+
+    changes = changesfiles.keys()
+    return null_adv_changes
+
+def load_adv_changes():
+    global srcverarches, changes
+
+    for c in os.listdir("."):
+        if not c.endswith(".changes"): continue
+        Katie.init_vars()
+        Katie.pkg.changes_file = c
+        Katie.update_vars()
+        if "adv id" not in Katie.pkg.changes:
+           continue
+       if Katie.pkg.changes["adv id"] != advisory:
+           continue
+
+       if c not in changes: changes.append(c)
+        srcver = "%s %s" % (Katie.pkg.changes["source"], 
+                           Katie.pkg.changes["version"])
+        srcverarches.setdefault(srcver, {})
+        for arch in Katie.pkg.changes["architecture"].keys():
+            srcverarches[srcver][arch] = 1
+
+def advisory_info():
+    if advisory != None:
+        print "Advisory: %s" % (advisory)
+    print "Changes:"
+    for c in changes:
+        print " %s" % (c)
+
+    print "Packages:"
+    svs = srcverarches.keys()
+    svs.sort()
+    for sv in svs:
+        as = srcverarches[sv].keys()
+        as.sort()
+        print " %s (%s)" % (sv, ", ".join(as))
+
+def prompt(opts, default):
+    p = ""
+    v = {}
+    for o in opts:
+        v[o[0].upper()] = o
+        if o[0] == default:
+           p += ", [%s]%s" % (o[0], o[1:])
+       else:
+           p += ", " + o
+    p = p[2:] + "? "
+    a = None
+
+    if Options["Automatic"]:
+        a = default
+
+    while a not in v:
+        a = utils.our_raw_input(p) + default
+       a = a[:1].upper()
+       
+    return v[a]
+
+def add_changes(extras):
+    for c in extras:
+       changes.append(c)
+        Katie.init_vars()
+        Katie.pkg.changes_file = c
+        Katie.update_vars()
+        srcver = "%s %s" % (Katie.pkg.changes["source"], Katie.pkg.changes["version"])
+        srcverarches.setdefault(srcver, {})
+        for arch in Katie.pkg.changes["architecture"].keys():
+            srcverarches[srcver][arch] = 1
+       Katie.pkg.changes["adv id"] = advisory
+       Katie.dump_vars(os.getcwd())
+
+def yes_no(prompt):
+    if Options["Automatic"]: return True
+    while 1:
+        answer = utils.our_raw_input(prompt + " ").lower()
+       if answer in "yn":
+           return answer == "y"
+       print "Invalid answer; please try again."
+
+def do_upload():
+    if Options["No-Upload"]:
+        print "Not uploading as requested"
+       return
+
+    print "Would upload to ftp-master" # XXX
+
+def generate_advisory(template):
+    global changes, advisory
+
+    adv_packages = []
+    updated_pkgs = {};  # updated_pkgs[distro][arch][file] = {path,md5,size}
+
+    for arg in changes:
+        arg = utils.validate_changes_file_arg(arg)
+       Katie.pkg.changes_file = arg
+       Katie.init_vars()
+       Katie.update_vars()
+
+        src = Katie.pkg.changes["source"]
+       src_ver = "%s (%s)" % (src, Katie.pkg.changes["version"])
+       if src_ver not in adv_packages:
+           adv_packages.append(src_ver)
+
+       suites = Katie.pkg.changes["distribution"].keys()
+       for suite in suites:
+           if not updated_pkgs.has_key(suite):
+                updated_pkgs[suite] = {}
+
+       files = Katie.pkg.files
+       for file in files.keys():
+           arch = files[file]["architecture"]
+           md5 = files[file]["md5sum"]
+           size = files[file]["size"]
+           poolname = Cnf["Dir::PoolRoot"] + \
+               utils.poolify(src, files[file]["component"])
+           if arch == "source" and file.endswith(".dsc"):
+               dscpoolname = poolname
+           for suite in suites:
+               if not updated_pkgs[suite].has_key(arch):
+                   updated_pkgs[suite][arch] = {}
+               updated_pkgs[suite][arch][file] = {
+                    "md5": md5, "size": size, "poolname": poolname }
+
+       dsc_files = Katie.pkg.dsc_files
+       for file in dsc_files.keys():
+           arch = "source"
+           if not dsc_files[file].has_key("files id"):
+                continue
+
+           # otherwise, it's already in the pool and needs to be
+           # listed specially
+           md5 = dsc_files[file]["md5sum"]
+           size = dsc_files[file]["size"]
+           for suite in suites:
+               if not updated_pkgs[suite].has_key(arch):
+                   updated_pkgs[suite][arch] = {}
+               updated_pkgs[suite][arch][file] = {
+                    "md5": md5, "size": size, "poolname": dscpoolname }
+
+    if os.environ.has_key("SUDO_UID"):
+        whoami = long(os.environ["SUDO_UID"])
+    else:
+        whoami = os.getuid()
+    whoamifull = pwd.getpwuid(whoami)
+    username = whoamifull[4].split(",")[0]
+
+    Subst = {
+       "__ADVISORY__": advisory,
+       "__WHOAMI__": username,
+       "__DATE__": time.strftime("%B %d, %Y", time.gmtime(time.time())),
+       "__PACKAGE__": ", ".join(adv_packages),
+        "__KATIE_ADDRESS__": Cnf["Dinstall::MyEmailAddress"]
+        }
+
+    if Cnf.has_key("Dinstall::Bcc"):
+        Subst["__BCC__"] = "Bcc: %s" % (Cnf["Dinstall::Bcc"])
+
+    adv = ""
+    archive = Cnf["Archive::%s::PrimaryMirror" % (utils.where_am_i())]
+    for suite in updated_pkgs.keys():
+        ver = Cnf["Suite::%s::Version" % suite]
+       if ver != "": ver += " "
+        suite_header = "%s %s(%s)" % (Cnf["Dinstall::MyDistribution"],
+                                       ver, suite)
+        adv += "%s\n%s\n\n" % (suite_header, "-"*len(suite_header))
+
+       arches = Cnf.ValueList("Suite::%s::Architectures" % suite)
+       if "source" in arches:
+            arches.remove("source")
+       if "all" in arches:
+            arches.remove("all")
+       arches.sort()
+
+       adv += "%s updates are available for %s.\n\n" % (
+               suite.capitalize(), utils.join_with_commas_and(arches))
+
+       for a in ["source", "all"] + arches:
+           if not updated_pkgs[suite].has_key(a):
+                continue
+
+           if a == "source":
+               adv += "Source archives:\n\n"
+           elif a == "all":
+               adv += "Architecture independent packages:\n\n"
+           else:
+               adv += "%s architecture (%s)\n\n" % (a,
+                       Cnf["Architectures::%s" % a])
+
+           for file in updated_pkgs[suite][a].keys():
+               adv += "  http://%s/%s%s\n" % (
+                               archive, updated_pkgs[suite][a][file]["poolname"], file)
+               adv += "    Size/MD5 checksum: %8s %s\n" % (
+                       updated_pkgs[suite][a][file]["size"],
+                       updated_pkgs[suite][a][file]["md5"])
+           adv += "\n"
+    adv = adv.rstrip()
+
+    Subst["__ADVISORY_TEXT__"] = adv
+
+    adv = utils.TemplateSubst(Subst, template)
+    return adv
+
+
+def spawn(command):
+    if not re_taint_free.match(command):
+        utils.fubar("Invalid character in \"%s\"." % (command))
+
+    if Options["No-Action"]:
+        print "[%s]" % (command)
+    else:
+        (result, output) = commands.getstatusoutput(command)
+        if (result != 0):
+            utils.fubar("Invocation of '%s' failed:\n%s\n" % (command, output), result)
+
+
+##################### ! ! ! N O T E ! ! !  #####################
+#
+# These functions will be reinvoked by semi-priveleged users, be careful not
+# to invoke external programs that will escalate privileges, etc.
+#
+##################### ! ! ! N O T E ! ! !  #####################
+
+def sudo(arg, fn, exit):
+    if Options["Sudo"]:
+        if advisory == None:
+           utils.fubar("Must set advisory name")
+        os.spawnl(os.P_WAIT, "/usr/bin/sudo","/usr/bin/sudo", "-u", "katie", "-H", 
+          "/org/security.debian.org/katie/newamber", "-"+arg, "--", advisory)
+    else:
+        fn()
+    if exit:
+        quit()
+
+def do_Approve(): sudo("A", _do_Approve, True)
+def _do_Approve():
+    # 1. dump advisory in drafts
+    draft = "/org/security.debian.org/advisories/drafts/%s" % (advisory)
+    print "Advisory in %s" % (draft)
+    if not Options["No-Action"]:
+        adv_file = "./advisory.%s" % (advisory)
+        if not os.path.exists(adv_file):
+            adv_file = Cnf["Dir::Templates"]+"/amber.advisory"
+        adv_fd = os.open(draft, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0664)
+        os.write(adv_fd, generate_advisory(adv_file))
+        os.close(adv_fd)
+        adv_fd = None
+
+    # 2. run kelly on changes
+    print "Accepting packages..."
+    spawn("%s/kelly -pa %s" % (Cnf["Dir::Katie"], " ".join(changes)))
+
+    # 3. run jenna / apt-ftparchve / ziyi / tiffani
+    if not Options["No-Action"]:
+        os.chdir(Cnf["Dir::Katie"])
+
+    print "Updating file lists for apt-ftparchive..."
+    spawn("./jenna")
+    print "Updating Packages and Sources files..."
+    spawn("apt-ftparchive generate %s" % (utils.which_apt_conf_file()))
+    print "Updating Release files..."
+    spawn("./ziyi")
+    print "Triggering security mirrors..."
+    spawn("sudo -u archvsync /home/archvsync/signal_security")
+
+    # 4. chdir to done - do upload
+    if not Options["No-Action"]:
+        os.chdir(Cnf["Dir::Queue::Done"])
+    do_upload()
+
+def do_Disembargo(): sudo("D", _do_Disembargo, True)
+def _do_Disembargo():
+    if os.getcwd() != Cnf["Dir::Queue::Embargoed"].rstrip("/"):
+        utils.fubar("Can only disembargo from %s" % Cnf["Dir::Queue::Embargoed"])
+
+    dest = Cnf["Dir::Queue::Unembargoed"]
+    emb_q = db_access.get_or_set_queue_id("embargoed")
+    une_q = db_access.get_or_set_queue_id("unembargoed")
+
+    queuefiles = []
+    for c in changes:
+        print "Disembargoing %s" % (c)
+
+        Katie.init_vars()
+        Katie.pkg.changes_file = c
+        Katie.update_vars()
+
+        if "source" in Katie.pkg.changes["architecture"].keys():
+           print "Adding %s %s to disembargo table" % (Katie.pkg.changes["source"], Katie.pkg.changes["version"])
+           Katie.projectB.query("INSERT INTO disembargo (package, version) VALUES ('%s', '%s')" % (Katie.pkg.changes["source"], Katie.pkg.changes["version"]))
+
+        files = {}
+       for suite in Katie.pkg.changes["distribution"].keys():
+           if suite not in Cnf.ValueList("Dinstall::QueueBuildSuites"):
+               continue
+           dest_dir = Cnf["Dir::QueueBuild"]
+           if Cnf.FindB("Dinstall::SecurityQueueBuild"):
+               dest_dir = os.path.join(dest_dir, suite)
+           for file in Katie.pkg.files.keys():
+               files[os.path.join(dest_dir, file)] = 1
+
+       files = files.keys()
+       Katie.projectB.query("BEGIN WORK")
+       for f in files:
+           Katie.projectB.query("UPDATE queue_build SET queue = %s WHERE filename = '%s' AND queue = %s" % (une_q, f, emb_q))
+       Katie.projectB.query("COMMIT WORK")
+
+       for file in Katie.pkg.files.keys():
+           utils.copy(file, os.path.join(dest, file))
+           os.unlink(file)
+
+    for c in changes:
+        utils.copy(c, os.path.join(dest, c))
+       os.unlink(c)
+       k = c[:8] + ".katie"
+        utils.copy(k, os.path.join(dest, k))
+       os.unlink(k)
+
+def do_Reject(): sudo("R", _do_Reject, True)
+def _do_Reject():
+    global changes
+    for c in changes:
+        print "Rejecting %s..." % (c)
+        Katie.init_vars()
+        Katie.pkg.changes_file = c
+        Katie.update_vars()
+        files = {}
+       for suite in Katie.pkg.changes["distribution"].keys():
+           if suite not in Cnf.ValueList("Dinstall::QueueBuildSuites"):
+               continue
+           dest_dir = Cnf["Dir::QueueBuild"]
+           if Cnf.FindB("Dinstall::SecurityQueueBuild"):
+               dest_dir = os.path.join(dest_dir, suite)
+           for file in Katie.pkg.files.keys():
+               files[os.path.join(dest_dir, file)] = 1
+
+       files = files.keys()
+
+       aborted = Katie.do_reject()
+       if not aborted:
+           os.unlink(c[:-8]+".katie")
+           for f in files:
+               Katie.projectB.query(
+                   "DELETE FROM queue_build WHERE filename = '%s'" % (f))
+               os.unlink(f)
+
+    print "Updating buildd information..."
+    spawn("/org/security.debian.org/katie/cron.buildd-security")
+
+    adv_file = "./advisory.%s" % (advisory)
+    if os.path.exists(adv_file):
+        os.unlink(adv_file)
+
+def do_DropAdvisory():
+    for c in changes:
+        Katie.init_vars()
+        Katie.pkg.changes_file = c
+        Katie.update_vars()
+       del Katie.pkg.changes["adv id"]
+       Katie.dump_vars(os.getcwd())
+    quit()
+
+def do_Edit():
+    adv_file = "./advisory.%s" % (advisory)
+    if not os.path.exists(adv_file):
+        utils.copy(Cnf["Dir::Templates"]+"/amber.advisory", adv_file)
+    editor = os.environ.get("EDITOR","vi")
+    result = os.system("%s %s" % (editor, adv_file))
+    if result != 0:
+        utils.fubar("%s invocation failed for %s." % (editor, adv_file))
+
+def do_Show():
+    adv_file = "./advisory.%s" % (advisory)
+    if not os.path.exists(adv_file):
+        adv_file = Cnf["Dir::Templates"]+"/amber.advisory"
+    print "====\n%s\n====" % (generate_advisory(adv_file))
+
+def do_Quit():
+    quit()
+
+def main():
+    global changes
+
+    args = init()
+    extras = load_args(args)
+    if advisory:
+        load_adv_changes()
+    if extras:
+        if not advisory:
+            changes = extras
+       else:
+            if srcverarches == {}:
+                if not yes_no("Create new advisory %s?" % (advisory)):
+                   print "Not doing anything, then"
+                   quit()
+            else:
+                advisory_info()
+               doextras = []
+                for c in extras:
+                   if yes_no("Add %s to %s?" % (c, advisory)):
+                        doextras.append(c)
+                extras = doextras
+            add_changes(extras)
+
+    if not advisory:
+        utils.fubar("Must specify an advisory id")
+
+    if not changes:
+        utils.fubar("No changes specified")
+
+    if Options["Approve"]:
+       advisory_info()
+        do_Approve()
+    elif Options["Reject"]:
+       advisory_info()
+        do_Reject()
+    elif Options["Disembargo"]:
+       advisory_info()
+        do_Disembargo()
+    elif Options["Drop-Advisory"]:
+        advisory_info()
+       do_DropAdvisory()
+    else:
+        while 1:
+            default = "Q"
+           opts = ["Approve", "Edit advisory"]
+           if os.path.exists("./advisory.%s" % advisory):
+               default = "A"
+           else:
+               default = "E"
+           if os.getcwd() == Cnf["Dir::Queue::Embargoed"].rstrip("/"):
+               opts.append("Disembargo")
+           opts += ["Show advisory", "Reject", "Quit"]
+       
+           advisory_info()
+            what = prompt(opts, default)
+
+           if what == "Quit":
+               do_Quit()
+           elif what == "Approve":
+               do_Approve()
+           elif what == "Edit advisory":
+               do_Edit()
+           elif what == "Show advisory":
+               do_Show()
+           elif what == "Disembargo":
+               do_Disembargo()
+           elif what == "Reject":
+               do_Reject()
+           else:
+               utils.fubar("Impossible answer '%s', wtf?" % (what))
+
+main()