From e5f9e46e19f241557b683e6950cefd46bb347865 Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Sat, 16 Mar 2013 18:54:00 +0100 Subject: [PATCH] dom0-updates code --- doc/tools/qubes-dom0-update.rst | 31 +++++ dom0-updates/Makefile | 4 + dom0-updates/copy-file.c | 44 +++++++ dom0-updates/crc32.c | 146 ++++++++++++++++++++ dom0-updates/crc32.h | 7 + dom0-updates/filecopy.h | 32 +++++ dom0-updates/ioall.c | 112 ++++++++++++++++ dom0-updates/ioall.h | 5 + dom0-updates/qfile-dom0-unpacker.c | 74 +++++++++++ dom0-updates/qubes-cached.repo | 6 + dom0-updates/qubes-dom0-update | 117 ++++++++++++++++ dom0-updates/qubes-dom0-updates.cron | 42 ++++++ dom0-updates/qubes-receive-updates | 119 +++++++++++++++++ dom0-updates/qubes.ReceiveUpdates | 1 + dom0-updates/qubes.ReceiveUpdates.policy | 6 + dom0-updates/unpack.c | 161 +++++++++++++++++++++++ rpm_spec/core-dom0-linux.spec | 44 ++++++- 17 files changed, 948 insertions(+), 3 deletions(-) create mode 100644 doc/tools/qubes-dom0-update.rst create mode 100644 dom0-updates/Makefile create mode 100644 dom0-updates/copy-file.c create mode 100644 dom0-updates/crc32.c create mode 100644 dom0-updates/crc32.h create mode 100644 dom0-updates/filecopy.h create mode 100644 dom0-updates/ioall.c create mode 100644 dom0-updates/ioall.h create mode 100644 dom0-updates/qfile-dom0-unpacker.c create mode 100644 dom0-updates/qubes-cached.repo create mode 100755 dom0-updates/qubes-dom0-update create mode 100755 dom0-updates/qubes-dom0-updates.cron create mode 100755 dom0-updates/qubes-receive-updates create mode 100644 dom0-updates/qubes.ReceiveUpdates create mode 100644 dom0-updates/qubes.ReceiveUpdates.policy create mode 100644 dom0-updates/unpack.c diff --git a/doc/tools/qubes-dom0-update.rst b/doc/tools/qubes-dom0-update.rst new file mode 100644 index 0000000..dbb4720 --- /dev/null +++ b/doc/tools/qubes-dom0-update.rst @@ -0,0 +1,31 @@ +================= +qubes-dom0-update +================= + +NAME +==== +qubes-dom0-update - update software in dom0 + +:Date: 2012-04-13 + +SYNOPSIS +======== +| qubes-dom0-update [--clean] [--check-only] [--gui] [] + +OPTIONS +======= +--clean + Clean yum cache before doing anything +--check-only + Only check for updates (no install) +--gui + Use gpk-update-viewer for update selection + + + Download (and install if run by root) new packages in dom0 instead of updating + +AUTHORS +======= +| Joanna Rutkowska +| Rafal Wojtczuk +| Marek Marczykowski diff --git a/dom0-updates/Makefile b/dom0-updates/Makefile new file mode 100644 index 0000000..8980fa8 --- /dev/null +++ b/dom0-updates/Makefile @@ -0,0 +1,4 @@ +CC=gcc +CFLAGS=-g -I. -Wall -fPIC -pie +qfile-dom0-unpacker: qfile-dom0-unpacker.o ioall.o copy-file.o unpack.o crc32.o + $(CC) -pie -g -o $@ $^ diff --git a/dom0-updates/copy-file.c b/dom0-updates/copy-file.c new file mode 100644 index 0000000..d58a14c --- /dev/null +++ b/dom0-updates/copy-file.c @@ -0,0 +1,44 @@ +#include +#include +#include "filecopy.h" +#include "crc32.h" + +extern void notify_progress(int, int); + +int copy_file(int outfd, int infd, long long size, unsigned long *crc32) +{ + char buf[4096]; + long long written = 0; + int ret; + int count; + while (written < size) { + if (size - written > sizeof(buf)) + count = sizeof buf; + else + count = size - written; + ret = read(infd, buf, count); + if (!ret) + return COPY_FILE_READ_EOF; + if (ret < 0) + return COPY_FILE_READ_ERROR; + /* acumulate crc32 if requested */ + if (crc32) + *crc32 = Crc32_ComputeBuf(*crc32, buf, ret); + if (!write_all(outfd, buf, ret)) + return COPY_FILE_WRITE_ERROR; + notify_progress(ret, 0); + written += ret; + } + return COPY_FILE_OK; +} + +char * copy_file_status_to_str(int status) +{ + switch (status) { + case COPY_FILE_OK: return "OK"; + case COPY_FILE_READ_EOF: return "Unexpected end of data while reading"; + case COPY_FILE_READ_ERROR: return "Error reading"; + case COPY_FILE_WRITE_ERROR: return "Error writing"; + default: return "????????"; + } +} diff --git a/dom0-updates/crc32.c b/dom0-updates/crc32.c new file mode 100644 index 0000000..88b0627 --- /dev/null +++ b/dom0-updates/crc32.c @@ -0,0 +1,146 @@ +/*----------------------------------------------------------------------------*\ + * CRC-32 version 2.0.0 by Craig Bruce, 2006-04-29. + * + * This program generates the CRC-32 values for the files named in the + * command-line arguments. These are the same CRC-32 values used by GZIP, + * PKZIP, and ZMODEM. The Crc32_ComputeBuf() can also be detached and + * used independently. + * + * THIS PROGRAM IS PUBLIC-DOMAIN SOFTWARE. + * + * Based on the byte-oriented implementation "File Verification Using CRC" + * by Mark R. Nelson in Dr. Dobb's Journal, May 1992, pp. 64-67. + * + * v1.0.0: original release. + * v1.0.1: fixed printf formats. + * v1.0.2: fixed something else. + * v1.0.3: replaced CRC constant table by generator function. + * v1.0.4: reformatted code, made ANSI C. 1994-12-05. + * v2.0.0: rewrote to use memory buffer & static table, 2006-04-29. +\*----------------------------------------------------------------------------*/ + +#include +#include + +/*----------------------------------------------------------------------------*\ + * Local functions +\*----------------------------------------------------------------------------*/ + +unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, + size_t bufLen ); + +/*----------------------------------------------------------------------------*\ + * NAME: + * Crc32_ComputeFile() - compute CRC-32 value for a file + * DESCRIPTION: + * Computes the CRC-32 value for an opened file. + * ARGUMENTS: + * file - file pointer + * outCrc32 - (out) result CRC-32 value + * RETURNS: + * err - 0 on success or -1 on error + * ERRORS: + * - file errors +\*----------------------------------------------------------------------------*/ + +int Crc32_ComputeFile( FILE *file, unsigned long *outCrc32 ) +{ +# define CRC_BUFFER_SIZE 8192 + unsigned char buf[CRC_BUFFER_SIZE]; + size_t bufLen; + + /** accumulate crc32 from file **/ + *outCrc32 = 0; + while (1) { + bufLen = fread( buf, 1, CRC_BUFFER_SIZE, file ); + if (bufLen == 0) { + if (ferror(file)) { + fprintf( stderr, "error reading file\n" ); + goto ERR_EXIT; + } + break; + } + *outCrc32 = Crc32_ComputeBuf( *outCrc32, buf, bufLen ); + } + return( 0 ); + + /** error exit **/ +ERR_EXIT: + return( -1 ); +} + +/*----------------------------------------------------------------------------*\ + * NAME: + * Crc32_ComputeBuf() - computes the CRC-32 value of a memory buffer + * DESCRIPTION: + * Computes or accumulates the CRC-32 value for a memory buffer. + * The 'inCrc32' gives a previously accumulated CRC-32 value to allow + * a CRC to be generated for multiple sequential buffer-fuls of data. + * The 'inCrc32' for the first buffer must be zero. + * ARGUMENTS: + * inCrc32 - accumulated CRC-32 value, must be 0 on first call + * buf - buffer to compute CRC-32 value for + * bufLen - number of bytes in buffer + * RETURNS: + * crc32 - computed CRC-32 value + * ERRORS: + * (no errors are possible) +\*----------------------------------------------------------------------------*/ + +unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, + size_t bufLen ) +{ + static const unsigned long crcTable[256] = { + 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535, + 0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD, + 0xE7B82D07,0x90BF1D91,0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D, + 0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC, + 0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,0x3B6E20C8,0x4C69105E,0xD56041E4, + 0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C, + 0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,0x26D930AC, + 0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, + 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB, + 0xB6662D3D,0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F, + 0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB, + 0x086D3D2D,0x91646C97,0xE6635C01,0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E, + 0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA, + 0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,0x4DB26158,0x3AB551CE, + 0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A, + 0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, + 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409, + 0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81, + 0xB7BD5C3B,0xC0BA6CAD,0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739, + 0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8, + 0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,0xF00F9344,0x8708A3D2,0x1E01F268, + 0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0, + 0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,0xD6D6A3E8, + 0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, + 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF, + 0x4669BE79,0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703, + 0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7, + 0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A, + 0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE, + 0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,0x86D3D2D4,0xF1D4E242, + 0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6, + 0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, + 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D, + 0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5, + 0x47B2CF7F,0x30B5FFE9,0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605, + 0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94, + 0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D }; + unsigned long crc32; + unsigned char *byteBuf; + size_t i; + + /** accumulate crc32 for buffer **/ + crc32 = inCrc32 ^ 0xFFFFFFFF; + byteBuf = (unsigned char*) buf; + for (i=0; i < bufLen; i++) { + crc32 = (crc32 >> 8) ^ crcTable[ (crc32 ^ byteBuf[i]) & 0xFF ]; + } + return( crc32 ^ 0xFFFFFFFF ); +} + +/*----------------------------------------------------------------------------*\ + * END OF MODULE: crc32.c +\*----------------------------------------------------------------------------*/ diff --git a/dom0-updates/crc32.h b/dom0-updates/crc32.h new file mode 100644 index 0000000..0aa1846 --- /dev/null +++ b/dom0-updates/crc32.h @@ -0,0 +1,7 @@ +#ifndef _CRC32_H +#define _CRC32_H + +extern unsigned long Crc32_ComputeBuf( unsigned long inCrc32, const void *buf, + size_t bufLen ); + +#endif /* _CRC32_H */ diff --git a/dom0-updates/filecopy.h b/dom0-updates/filecopy.h new file mode 100644 index 0000000..c51d68e --- /dev/null +++ b/dom0-updates/filecopy.h @@ -0,0 +1,32 @@ +#define FILECOPY_SPOOL "/home/user/.filecopyspool" +#define FILECOPY_VMNAME_SIZE 32 +#define PROGRESS_NOTIFY_DELTA (15*1000*1000) +#define MAX_PATH_LENGTH 16384 + +#define LEGAL_EOF 31415926 + +struct file_header { + unsigned int namelen; + unsigned int mode; + unsigned long long filelen; + unsigned int atime; + unsigned int atime_nsec; + unsigned int mtime; + unsigned int mtime_nsec; +}; + +struct result_header { + unsigned int error_code; + unsigned long crc32; +}; + +enum { + COPY_FILE_OK, + COPY_FILE_READ_EOF, + COPY_FILE_READ_ERROR, + COPY_FILE_WRITE_ERROR +}; + +int copy_file(int outfd, int infd, long long size, unsigned long *crc32); +char *copy_file_status_to_str(int status); +void set_size_limit(long long new_bytes_limit, long long new_files_limit); diff --git a/dom0-updates/ioall.c b/dom0-updates/ioall.c new file mode 100644 index 0000000..2a81df4 --- /dev/null +++ b/dom0-updates/ioall.c @@ -0,0 +1,112 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include + +void perror_wrapper(char * msg) +{ + int prev=errno; + perror(msg); + errno=prev; +} + +void set_nonblock(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl | O_NONBLOCK); +} + +void set_block(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl & ~O_NONBLOCK); +} + +int write_all(int fd, void *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = write(fd, (char *) buf + written, size - written); + if (ret == -1 && errno == EINTR) + continue; + if (ret <= 0) { + return 0; + } + written += ret; + } +// fprintf(stderr, "sent %d bytes\n", size); + return 1; +} + +int read_all(int fd, void *buf, int size) +{ + int got_read = 0; + int ret; + while (got_read < size) { + ret = read(fd, (char *) buf + got_read, size - got_read); + if (ret == -1 && errno == EINTR) + continue; + if (ret == 0) { + errno = 0; + fprintf(stderr, "EOF\n"); + return 0; + } + if (ret < 0) { + if (errno != EAGAIN) + perror_wrapper("read"); + return 0; + } + if (got_read == 0) { + // force blocking operation on further reads + set_block(fd); + } + got_read += ret; + } +// fprintf(stderr, "read %d bytes\n", size); + return 1; +} + +int copy_fd_all(int fdout, int fdin) +{ + int ret; + char buf[4096]; + for (;;) { + ret = read(fdin, buf, sizeof(buf)); + if (ret == -1 && errno == EINTR) + continue; + if (!ret) + break; + if (ret < 0) { + perror_wrapper("read"); + return 0; + } + if (!write_all(fdout, buf, ret)) { + perror_wrapper("write"); + return 0; + } + } + return 1; +} diff --git a/dom0-updates/ioall.h b/dom0-updates/ioall.h new file mode 100644 index 0000000..c9913e8 --- /dev/null +++ b/dom0-updates/ioall.h @@ -0,0 +1,5 @@ +int write_all(int fd, void *buf, int size); +int read_all(int fd, void *buf, int size); +int copy_fd_all(int fdout, int fdin); +void set_nonblock(int fd); +void set_block(int fd); diff --git a/dom0-updates/qfile-dom0-unpacker.c b/dom0-updates/qfile-dom0-unpacker.c new file mode 100644 index 0000000..15356e8 --- /dev/null +++ b/dom0-updates/qfile-dom0-unpacker.c @@ -0,0 +1,74 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "filecopy.h" + +#define DEFAULT_MAX_UPDATES_BYTES (2L<<30) +#define DEFAULT_MAX_UPDATES_FILES 2048 + +int prepare_creds_return_uid(char *username) +{ + struct passwd *pwd; + // First try name + pwd = getpwnam(username); + if (!pwd) { + // Then try UID + pwd = getpwuid(atoi(username)); + if (!pwd) { + perror("getpwuid"); + exit(1); + } + } + setenv("HOME", pwd->pw_dir, 1); + setenv("USER", pwd->pw_name, 1); + setgid(pwd->pw_gid); + initgroups(pwd->pw_name, pwd->pw_gid); + setfsuid(pwd->pw_uid); + return pwd->pw_uid; +} + +extern int do_unpack(void); + +int main(int argc, char ** argv) +{ + char *incoming_dir; + int uid; + char *var; + long long files_limit = DEFAULT_MAX_UPDATES_FILES; + long long bytes_limit = DEFAULT_MAX_UPDATES_BYTES; + + if (argc < 3) { + fprintf(stderr, "Invalid parameters, usage: %s user dir\n", argv[0]); + exit(1); + } + + if ((var=getenv("UPDATES_MAX_BYTES"))) + bytes_limit = atoll(var); + if ((var=getenv("UPDATES_MAX_FILES"))) + files_limit = atoll(var); + + uid = prepare_creds_return_uid(argv[1]); + + incoming_dir = argv[2]; + mkdir(incoming_dir, 0700); + if (chdir(incoming_dir)) { + fprintf(stderr, "Error chdir to %s", incoming_dir); + exit(1); + } + if (chroot(incoming_dir)) {//impossible + fprintf(stderr, "Error chroot to %s", incoming_dir); + exit(1); + } + setuid(uid); + set_size_limit(bytes_limit, files_limit); + return do_unpack(); +} diff --git a/dom0-updates/qubes-cached.repo b/dom0-updates/qubes-cached.repo new file mode 100644 index 0000000..963a7ba --- /dev/null +++ b/dom0-updates/qubes-cached.repo @@ -0,0 +1,6 @@ +[qubes-dom0-cached] +name = Qubes OS Repository for Dom0 +baseurl = file:///var/lib/qubes/updates +gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-qubes-1-primary +gpgcheck = 1 +metadata_expire = 0 diff --git a/dom0-updates/qubes-dom0-update b/dom0-updates/qubes-dom0-update new file mode 100755 index 0000000..2cf2285 --- /dev/null +++ b/dom0-updates/qubes-dom0-update @@ -0,0 +1,117 @@ +#!/bin/bash + +UPDATEVM=`qubes-prefs --get updatevm` +UPDATES_STAT_FILE=/var/lib/qubes/updates/dom0-updates-available + +if [ -z "$UPDATEVM" ]; then + echo "UpdateVM not set, exiting" + exit 1 +fi + +if [ "$1" = "--help" ]; then + echo "This tool is used to download packages for dom0. Without package list" + echo "it checks for updates for installed packages" + echo "" + echo "Usage: $0 [--clean] [--check-only] [--gui] []" + echo " --clean clean yum cache before doing anything" + echo " --check-only only check for updates (no install)" + echo " --gui use gpk-update-viewer for update selection" + echo " download (and install if run by root) new packages" + echo " in dom0 instead of updating" + exit +fi + +PKGS= +YUM_OPTS= +GUI= +CHECK_ONLY= +ALL_OPTS=$* +QVMRUN_OPTS= +# Filter out some yum options and collect packages list +while [ $# -gt 0 ]; do + case "$1" in + --enablerepo=*|\ + --disablerepo=*|\ + --clean) + ;; + --gui) + GUI=1 + ;; + --check-only) + CHECK_ONLY=1 + ;; + -*) + YUM_OPTS="$YUM_OPTS $1" + ;; + *) + PKGS="$PKGS $1" + ;; + esac + shift +done + +ID=$(id -ur) +if [ $ID != 0 -a -z "$GUI" -a -z "$CHECK_ONLY" ] ; then + echo "This script should be run as root (when used in console mode), use sudo." >&2 + exit 1 +fi + +if [ "$GUI" == "1" -a -n "$PKGS" ]; then + echo "ERROR: GUI mode can be used only for updates" >&2 + exit 1 +fi + +if [ "$GUI" != "1" ]; then + QVMRUN_OPTS=--nogui +fi + +# Do not start VM automaticaly when running from cron (only checking for updates) +if [ "$CHECK_ONLY" == "1" ] && ! xl domid $UPDATEVM > /dev/null 2>&1; then + echo "ERROR: UpdateVM not running, not starting it in non-interactive mode" >&2 + exit 1 +fi + +# We should ensure the clocks in Dom0 and UpdateVM are in sync +# becuase otherwise yum might complain about future timestamps +qvm-sync-clock + +echo "Checking for dom0 updates" >&2 + +# Start VM if not running already +qvm-run $QVMRUN_OPTS -a $UPDATEVM true || exit 1 + +/usr/lib/qubes/qrexec-client -d "$UPDATEVM" -l 'tar c /var/lib/rpm /etc/yum.repos.d /etc/yum.conf 2>/dev/null' 'user:tar x -C /var/lib/qubes/dom0-updates' 2> /dev/null + +qvm-run $QVMRUN_OPTS --pass-io $UPDATEVM "/usr/lib/qubes/qubes-download-dom0-updates.sh --doit --nogui $ALL_OPTS" +RETCODE=$? +if [ "$CHECK_ONLY" == "1" ]; then + exit $RETCODE +elif [ "$RETCODE" -ne 0 ]; then + exit $RETCODE +fi +# Wait for download completed +while pidof -x qubes-receive-updates >/dev/null; do sleep 0.5; done + +if [ -r /var/lib/qubes/updates/errors ]; then + echo "*** ERROR while receiving updates:" >&2 + cat /var/lib/qubes/updates/errors >&2 + echo "--> if you want to use packages that were downloaded correctly, use yum directly now" >&2 + exit 1 +fi + +if [ "x$PKGS" != "x" ]; then + yum $YUM_OPTS install $PKGS +elif [ -f /var/lib/qubes/updates/repodata/repomd.xml ]; then + # Above file exists only when at least one package was downloaded + if [ "$GUI" == "1" ]; then + gpk-update-viewer + else + yum check-update + if [ $? -eq 100 ]; then + yum $YUM_OPTS update + fi + fi + yum -q check-update && rm -f $UPDATES_STAT_FILE +else + echo "No updates avaliable" >&2 +fi diff --git a/dom0-updates/qubes-dom0-updates.cron b/dom0-updates/qubes-dom0-updates.cron new file mode 100755 index 0000000..b30a0b4 --- /dev/null +++ b/dom0-updates/qubes-dom0-updates.cron @@ -0,0 +1,42 @@ +#!/bin/bash + +# Get normal user name +LOCAL_USER=`users | sed -e 's/root *//' | cut -d' ' -f 1` +PIDFILE=/var/run/qubes/dom0-update-notification.pid +NOTIFY_ICON=/usr/share/qubes/icons/dom0-update-avail.svg +UPDATES_STAT_FILE=/var/lib/qubes/updates/dom0-updates-available + +# Do not allow multiple instances +[ -r $PIDFILE ] && kill -0 `cat $PIDFILE` && exit 0 +# Teoretically the race can happen here, but this tool will be run once a few +# hours, so no real problem +echo $$ > $PIDFILE +trap "rm $PIDFILE" EXIT + +# If no updates available - exit here +qubes-dom0-update --check-only >/dev/null && exit +RETCODE=$? + +if [ "$RETCODE" -ne 100 ]; then + echo "ERROR: Error checking for updates" >&2 + exit $RETCODE +fi + +if [ -z "$LOCAL_USER" ]; then + echo "ERROR: no user logged in, cannot nofity about updates" >&2 + exit 1 +fi + +# Touch stat file for qubes-manager +touch $UPDATES_STAT_FILE + +# Notify about updates using system tray +zenity --notification --window-icon=$NOTIFY_ICON --text="Qubes dom0 updates available." + +zenity --question --title="Qubes Dom0 updates" \ + --text="There are updates for dom0 available, do you want to download them now?" || exit 0 + +su -c "DISPLAY=:0 qubes-dom0-update --gui" $LOCAL_USER + +# Check if user installed updates +yum -q check-updates && rm $UPDATES_STAT_FILE diff --git a/dom0-updates/qubes-receive-updates b/dom0-updates/qubes-receive-updates new file mode 100755 index 0000000..4d147d0 --- /dev/null +++ b/dom0-updates/qubes-receive-updates @@ -0,0 +1,119 @@ +#!/usr/bin/python2 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# +import os +import os.path +import re +import sys +import subprocess +import shutil +import glob +import grp +from qubes.qubes import QubesVmCollection + +updates_dir = "/var/lib/qubes/updates" +updates_rpm_dir = updates_dir + "/rpm" +updates_repodata_dir = updates_dir + "/repodata" +updates_error_file = updates_dir + "/errors" +updates_error_file_handle = None + +comps_file = None +if os.path.exists('/usr/share/qubes/Qubes-comps.xml'): + comps_file = '/usr/share/qubes/Qubes-comps.xml' + +package_regex = re.compile(r"^[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._+-]{1,128}.rpm$") +gpg_ok_regex = re.compile(r"pgp md5 OK$") + +def dom0updates_fatal(pkg, msg): + global updates_error_file_handle + print >> sys.stderr, msg + if updates_error_file_handle is None: + updates_error_file_handle = open(updates_error_file, "a") + updates_error_file_handle.write(msg + "\n") + os.remove(pkg) + +def handle_dom0updates(updatevm): + global updates_error_file_handle + + source=os.getenv("QREXEC_REMOTE_DOMAIN") + if source != updatevm.name: + print >> sys.stderr, 'Domain ' + str(source) + ' not allowed to send dom0 updates' + exit(1) + # Clean old packages + if os.path.exists(updates_rpm_dir): + shutil.rmtree(updates_rpm_dir) + if os.path.exists(updates_repodata_dir): + shutil.rmtree(updates_repodata_dir) + if os.path.exists(updates_error_file): + os.remove(updates_error_file) + qubes_gid = grp.getgrnam('qubes').gr_gid + os.mkdir(updates_rpm_dir) + os.chown(updates_rpm_dir, -1, qubes_gid) + os.chmod(updates_rpm_dir, 0775) + subprocess.check_call(["/usr/libexec/qubes/qfile-dom0-unpacker", str(os.getuid()), updates_rpm_dir]) + # Verify received files + for untrusted_f in os.listdir(updates_rpm_dir): + if not package_regex.match(untrusted_f): + dom0updates_fatal(updates_rpm_dir + '/' + untrusted_f, 'Domain ' + source + ' sent unexpected file: ' + untrusted_f) + else: + f = untrusted_f + full_path = updates_rpm_dir + "/" + f + if os.path.islink(full_path) or not os.path.isfile(full_path): + dom0updates_fatal(full_path, 'Domain ' + source + ' sent not regular file') + p = subprocess.Popen (["/bin/rpm", "-K", full_path], + stdout=subprocess.PIPE) + output = p.communicate()[0] + if p.returncode != 0: + dom0updates_fatal(full_path, 'Error while verifing %s signature: %s' % (f, output)) + if not gpg_ok_regex.search(output.strip()): + dom0updates_fatal(full_path, 'Domain ' + source + ' sent not signed rpm: ' + f) + if updates_error_file_handle is not None: + updates_error_file_handle.close() + # After updates received - create repo metadata + createrepo_cmd = ["/usr/bin/createrepo"] + if comps_file: + createrepo_cmd += ["-g", comps_file] + createrepo_cmd += ["-q", updates_dir] + subprocess.check_call(createrepo_cmd) + os.chown(updates_repodata_dir, -1, qubes_gid) + os.chmod(updates_repodata_dir, 0775) + # Clean old cache + subprocess.call(["sudo", "/usr/bin/yum", "-q", "clean", "all"], stdout=sys.stderr) + # This will fail because of "smart" detection of no-network, but it will invalidate the cache + try: + null = open('/dev/null','w') + subprocess.call(["/usr/bin/pkcon", "refresh"], stdout=null) + null.close() + except: + pass + exit(0) + +def main(): + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_reading() + qvm_collection.load() + qvm_collection.unlock_db() + + updatevm = qvm_collection.get_updatevm_vm() + handle_dom0updates(updatevm) + +main() diff --git a/dom0-updates/qubes.ReceiveUpdates b/dom0-updates/qubes.ReceiveUpdates new file mode 100644 index 0000000..174ce84 --- /dev/null +++ b/dom0-updates/qubes.ReceiveUpdates @@ -0,0 +1 @@ +/usr/libexec/qubes/qubes-receive-updates diff --git a/dom0-updates/qubes.ReceiveUpdates.policy b/dom0-updates/qubes.ReceiveUpdates.policy new file mode 100644 index 0000000..0f00b0b --- /dev/null +++ b/dom0-updates/qubes.ReceiveUpdates.policy @@ -0,0 +1,6 @@ +## Note that policy parsing stops at the first match, +## so adding anything below "$anyvm $anyvm action" line will have no effect + +## Please use a single # to start your custom comments + +$anyvm dom0 allow diff --git a/dom0-updates/unpack.c b/dom0-updates/unpack.c new file mode 100644 index 0000000..9c018fa --- /dev/null +++ b/dom0-updates/unpack.c @@ -0,0 +1,161 @@ +#define _GNU_SOURCE /* For O_NOFOLLOW. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "filecopy.h" +#include "crc32.h" + +char untrusted_namebuf[MAX_PATH_LENGTH]; +long long bytes_limit = 0; +long long files_limit = 0; +long long total_bytes = 0; +long long total_files = 0; + +void notify_progress(int p1, int p2) +{ +} + +void set_size_limit(long long new_bytes_limit, long long new_files_limit) +{ + bytes_limit = new_bytes_limit; + files_limit = new_files_limit; +} + +unsigned long crc32_sum = 0; +int read_all_with_crc(int fd, void *buf, int size) { + int ret; + ret = read_all(fd, buf, size); + if (ret) + crc32_sum = Crc32_ComputeBuf(crc32_sum, buf, size); + return ret; +} + +void send_status_and_crc(int code) { + struct result_header hdr; + int saved_errno; + + saved_errno = errno; + hdr.error_code = code; + hdr.crc32 = crc32_sum; + if (!write_all(1, &hdr, sizeof(hdr))) + perror("write status"); + errno = saved_errno; +} + +void do_exit(int code) +{ + close(0); + send_status_and_crc(code); + exit(code); +} + +void fix_times_and_perms(struct file_header *untrusted_hdr, + char *untrusted_name) +{ + struct timeval times[2] = + { {untrusted_hdr->atime, untrusted_hdr->atime_nsec / 1000}, + {untrusted_hdr->mtime, + untrusted_hdr->mtime_nsec / 1000} + }; + if (chmod(untrusted_name, untrusted_hdr->mode & 07777)) /* safe because of chroot */ + do_exit(errno); + if (utimes(untrusted_name, times)) /* as above */ + do_exit(errno); +} + + + +void process_one_file_reg(struct file_header *untrusted_hdr, + char *untrusted_name) +{ + int ret; + int fdout = open(untrusted_name, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0700); /* safe because of chroot */ + if (fdout < 0) + do_exit(errno); + total_bytes += untrusted_hdr->filelen; + if (bytes_limit && total_bytes > bytes_limit) + do_exit(EDQUOT); + ret = copy_file(fdout, 0, untrusted_hdr->filelen, &crc32_sum); + if (ret != COPY_FILE_OK) { + if (ret == COPY_FILE_READ_EOF + || ret == COPY_FILE_READ_ERROR) + do_exit(LEGAL_EOF); // hopefully remote will produce error message + else + do_exit(errno); + } + close(fdout); + fix_times_and_perms(untrusted_hdr, untrusted_name); +} + + +void process_one_file_dir(struct file_header *untrusted_hdr, + char *untrusted_name) +{ +// fix perms only when the directory is sent for the second time +// it allows to transfer r.x directory contents, as we create it rwx initially + if (!mkdir(untrusted_name, 0700)) /* safe because of chroot */ + return; + if (errno != EEXIST) + do_exit(errno); + fix_times_and_perms(untrusted_hdr, untrusted_name); +} + +void process_one_file_link(struct file_header *untrusted_hdr, + char *untrusted_name) +{ + char untrusted_content[MAX_PATH_LENGTH]; + unsigned int filelen; + if (untrusted_hdr->filelen > MAX_PATH_LENGTH - 1) + do_exit(ENAMETOOLONG); + filelen = untrusted_hdr->filelen; /* sanitized above */ + if (!read_all_with_crc(0, untrusted_content, filelen)) + do_exit(LEGAL_EOF); // hopefully remote has produced error message + untrusted_content[filelen] = 0; + if (symlink(untrusted_content, untrusted_name)) /* safe because of chroot */ + do_exit(errno); + +} + +void process_one_file(struct file_header *untrusted_hdr) +{ + unsigned int namelen; + if (untrusted_hdr->namelen > MAX_PATH_LENGTH - 1) + do_exit(ENAMETOOLONG); + namelen = untrusted_hdr->namelen; /* sanitized above */ + if (!read_all_with_crc(0, untrusted_namebuf, namelen)) + do_exit(LEGAL_EOF); // hopefully remote has produced error message + untrusted_namebuf[namelen] = 0; + if (S_ISREG(untrusted_hdr->mode)) + process_one_file_reg(untrusted_hdr, untrusted_namebuf); + else if (S_ISLNK(untrusted_hdr->mode)) + process_one_file_link(untrusted_hdr, untrusted_namebuf); + else if (S_ISDIR(untrusted_hdr->mode)) + process_one_file_dir(untrusted_hdr, untrusted_namebuf); + else + do_exit(EINVAL); +} + +int do_unpack() +{ + struct file_header untrusted_hdr; + /* initialize checksum */ + crc32_sum = 0; + while (read_all_with_crc(0, &untrusted_hdr, sizeof untrusted_hdr)) { + /* check for end of transfer marker */ + if (untrusted_hdr.namelen == 0) { + errno = 0; + break; + } + process_one_file(&untrusted_hdr); + total_files++; + if (files_limit && total_files > files_limit) + do_exit(EDQUOT); + } + send_status_and_crc(errno); + return errno; +} diff --git a/rpm_spec/core-dom0-linux.spec b/rpm_spec/core-dom0-linux.spec index c277fec..524dae3 100644 --- a/rpm_spec/core-dom0-linux.spec +++ b/rpm_spec/core-dom0-linux.spec @@ -62,9 +62,13 @@ ln -sf . %{name}-%{version} %build python -m compileall appmenus-scripts python -O -m compileall appmenus-scripts +(cd dom0-updates; make) (cd doc; make manpages) %install + +### Appmenus + mkdir -p $RPM_BUILD_ROOT%{python_sitearch}/qubes/modules cp appmenus-scripts/qubes-core-appmenus.py $RPM_BUILD_ROOT%{python_sitearch}/qubes/modules/10appmenus.py cp appmenus-scripts/qubes-core-appmenus.pyc $RPM_BUILD_ROOT%{python_sitearch}/qubes/modules/10appmenus.pyc @@ -80,14 +84,27 @@ mkdir -p $RPM_BUILD_ROOT/etc/qubes-rpc/policy cp appmenus-scripts/qubes.SyncAppMenus $RPM_BUILD_ROOT/etc/qubes-rpc/ cp appmenus-scripts/qubes.SyncAppMenus.policy $RPM_BUILD_ROOT/etc/qubes-rpc/policy/ +mkdir -p $RPM_BUILD_ROOT/usr/share/qubes-appmenus/ +cp appmenus-files/* $RPM_BUILD_ROOT/usr/share/qubes-appmenus/ + +### Dom0 updates +install -D dom0-updates/qubes-dom0-updates.cron $RPM_BUILD_ROOT/etc/cron.daily/qubes-dom0-updates.cron +install -D dom0-updates/qubes-dom0-update $RPM_BUILD_ROOT/usr/bin/qubes-dom0-update +install -D dom0-updates/qubes-receive-updates $RPM_BUILD_ROOT/usr/libexec/qubes/qubes-receive-updates +install -m 0644 -D dom0-updates/qubes-cached.repo $RPM_BUILD_ROOT/etc/yum.real.repos.d/qubes-cached.repo +install -D dom0-updates/qfile-dom0-unpacker $RPM_BUILD_ROOT/usr/libexec/qubes/qfile-dom0-unpacker +install -m 0644 -D dom0-updates/qubes.ReceiveUpdates $RPM_BUILD_ROOT/etc/qubes-rpc/qubes.ReceiveUpdates +install -m 0664 -D dom0-updates/qubes.ReceiveUpdates.policy $RPM_BUILD_ROOT/etc/qubes-rpc/policy/qubes.ReceiveUpdates + +install -d $RPM_BUILD_ROOT/var/lib/qubes/updates + +### Icons mkdir -p $RPM_BUILD_ROOT/usr/share/qubes/icons for icon in icons/*.png; do convert -resize 48 $icon $RPM_BUILD_ROOT/usr/share/qubes/$icon done -mkdir -p $RPM_BUILD_ROOT/usr/share/qubes-appmenus/ -cp appmenus-files/* $RPM_BUILD_ROOT/usr/share/qubes-appmenus/ - +### Documentation (cd doc; make DESTDIR=$RPM_BUILD_ROOT install) %post @@ -98,6 +115,12 @@ done xdg-desktop-menu install /usr/share/qubes-appmenus/qubes-dispvm.directory /usr/share/qubes-appmenus/qubes-dispvm-firefox.desktop +sed '/^reposdir\s*=/d' -i /etc/yum.conf +echo reposdir=/etc/yum.real.repos.d >> /etc/yum.conf + +sed '/^installonlypkgs\s*=/d' -i /etc/yum.conf +echo 'installonlypkgs = kernel, kernel-qubes-vm' >> /etc/yum.conf + %preun if [ "$1" = 0 ] ; then # no more packages left @@ -109,6 +132,9 @@ if [ "$1" = 0 ] ; then xdg-desktop-menu uninstall /usr/share/qubes-appmenus/qubes-dispvm.directory /usr/share/qubes-appmenus/qubes-dispvm-firefox.desktop fi +%triggerin -- PackageKit +# dom0 have no network, but still can receive updates (qubes-dom0-update) +sed -i 's/^UseNetworkHeuristic=.*/UseNetworkHeuristic=false/' /etc/PackageKit/PackageKit.conf %files /etc/qubes-rpc/policy/qubes.SyncAppMenus.policy @@ -130,6 +156,18 @@ fi /usr/share/qubes-appmenus/qubes-vm.directory.template /usr/share/qubes/icons/*.png /usr/bin/qvm-sync-appmenus +# Dom0 updates +/etc/cron.daily/qubes-dom0-updates.cron +/etc/yum.real.repos.d/qubes-cached.repo +/usr/bin/qubes-dom0-update +%attr(4750,root,qubes) /usr/libexec/qubes/qfile-dom0-unpacker +/usr/libexec/qubes/qubes-receive-updates +/etc/qubes-rpc/qubes.ReceiveUpdates +%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.ReceiveUpdates +%attr(0770,root,qubes) %dir /var/lib/qubes/updates +# Man %{_mandir}/man1/qvm-*.1* +%{_mandir}/man1/qubes-*.1* + %changelog