# Copyright 2012-2013 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 import errno import re import subprocess from portage import os from portage import _unicode_encode, _encodings from portage.const import MANIFEST2_IDENTIFIERS from portage.util import (atomic_ofstream, grablines, shlex_split, varexpand, writemsg) from portage.util._async.PipeLogger import PipeLogger from portage.util._async.PopenProcess import PopenProcess from _emerge.CompositeTask import CompositeTask from _emerge.PipeReader import PipeReader from .ManifestProcess import ManifestProcess class ManifestTask(CompositeTask): __slots__ = ("cp", "distdir", "fetchlist_dict", "gpg_cmd", "gpg_vars", "repo_config", "force_sign_key", "_manifest_path") _PGP_HEADER = b"BEGIN PGP SIGNED MESSAGE" _manifest_line_re = re.compile(r'^(%s) ' % "|".join(MANIFEST2_IDENTIFIERS)) _gpg_key_id_re = re.compile(r'^[0-9A-F]*$') _gpg_key_id_lengths = (8, 16, 24, 32, 40) def _start(self): self._manifest_path = os.path.join(self.repo_config.location, self.cp, "Manifest") manifest_proc = ManifestProcess(cp=self.cp, distdir=self.distdir, fetchlist_dict=self.fetchlist_dict, repo_config=self.repo_config, scheduler=self.scheduler) self._start_task(manifest_proc, self._manifest_proc_exit) def _manifest_proc_exit(self, manifest_proc): self._assert_current(manifest_proc) if manifest_proc.returncode not in (os.EX_OK, manifest_proc.MODIFIED): self.returncode = manifest_proc.returncode self._current_task = None self.wait() return modified = manifest_proc.returncode == manifest_proc.MODIFIED sign = self.gpg_cmd is not None if not modified and sign: sign = self._need_signature() if not sign and self.force_sign_key is not None \ and os.path.exists(self._manifest_path): self._check_sig_key() return if not sign or not os.path.exists(self._manifest_path): self.returncode = os.EX_OK self._current_task = None self.wait() return self._start_gpg_proc() def _check_sig_key(self): null_fd = os.open('/dev/null', os.O_RDONLY) popen_proc = PopenProcess(proc=subprocess.Popen( ["gpg", "--verify", self._manifest_path], stdin=null_fd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT), pipe_reader=PipeReader()) os.close(null_fd) popen_proc.pipe_reader.input_files = { "producer" : popen_proc.proc.stdout} self._start_task(popen_proc, self._check_sig_key_exit) @staticmethod def _parse_gpg_key(output): """ Returns the first token which appears to represent a gpg key id, or None if there is no such token. """ regex = ManifestTask._gpg_key_id_re lengths = ManifestTask._gpg_key_id_lengths for token in output.split(): m = regex.match(token) if m is not None and len(m.group(0)) in lengths: return m.group(0) return None @staticmethod def _normalize_gpg_key(key_str): """ Strips leading "0x" and trailing "!", and converts to uppercase (intended to be the same format as that in gpg --verify output). """ key_str = key_str.upper() if key_str.startswith("0X"): key_str = key_str[2:] key_str = key_str.rstrip("!") return key_str def _check_sig_key_exit(self, proc): self._assert_current(proc) parsed_key = self._parse_gpg_key( proc.pipe_reader.getvalue().decode('utf_8', 'replace')) if parsed_key is not None and \ self._normalize_gpg_key(parsed_key) == \ self._normalize_gpg_key(self.force_sign_key): self.returncode = os.EX_OK self._current_task = None self.wait() return if self._was_cancelled(): self.wait() return self._strip_sig(self._manifest_path) self._start_gpg_proc() @staticmethod def _strip_sig(manifest_path): """ Strip an existing signature from a Manifest file. """ line_re = ManifestTask._manifest_line_re lines = grablines(manifest_path) f = None try: f = atomic_ofstream(manifest_path) for line in lines: if line_re.match(line) is not None: f.write(line) f.close() f = None finally: if f is not None: f.abort() def _start_gpg_proc(self): gpg_vars = self.gpg_vars if gpg_vars is None: gpg_vars = {} else: gpg_vars = gpg_vars.copy() gpg_vars["FILE"] = self._manifest_path gpg_cmd = varexpand(self.gpg_cmd, mydict=gpg_vars) gpg_cmd = shlex_split(gpg_cmd) gpg_proc = PopenProcess(proc=subprocess.Popen(gpg_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) # PipeLogger echos output and efficiently monitors for process # exit by listening for the stdout EOF event. gpg_proc.pipe_reader = PipeLogger(background=self.background, input_fd=gpg_proc.proc.stdout, scheduler=self.scheduler) self._start_task(gpg_proc, self._gpg_proc_exit) def _gpg_proc_exit(self, gpg_proc): if self._default_exit(gpg_proc) != os.EX_OK: self.wait() return rename_args = (self._manifest_path + ".asc", self._manifest_path) try: os.rename(*rename_args) except OSError as e: writemsg("!!! rename('%s', '%s'): %s\n" % rename_args + (e,), noiselevel=-1) try: os.unlink(self._manifest_path + ".asc") except OSError: pass self.returncode = 1 else: self.returncode = os.EX_OK self._current_task = None self.wait() def _need_signature(self): try: with open(_unicode_encode(self._manifest_path, encoding=_encodings['fs'], errors='strict'), 'rb') as f: return self._PGP_HEADER not in f.readline() except IOError as e: if e.errno in (errno.ENOENT, errno.ESTALE): return False raise