ostree/src/libotutil/ot-gpg-utils.c

447 lines
11 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include "ot-gpg-utils.h"
#include <stdlib.h>
#include "libglnx.h"
void
ot_gpgme_error_to_gio_error (gpgme_error_t gpg_error,
GError **error)
{
GIOErrorEnum errcode;
char errbuf[1024];
/* XXX This list is incomplete. Add cases as needed. */
switch (gpgme_err_code (gpg_error))
{
/* special case - shouldn't be here */
case GPG_ERR_NO_ERROR:
g_return_if_reached ();
/* special case - abort on out-of-memory */
case GPG_ERR_ENOMEM:
(void) gpg_strerror_r (gpg_error, errbuf, sizeof (errbuf));
errbuf[sizeof(errbuf)-1] = '\0';
g_error ("%s: %s",
gpgme_strsource (gpg_error),
errbuf);
case GPG_ERR_INV_VALUE:
errcode = G_IO_ERROR_INVALID_ARGUMENT;
break;
default:
errcode = G_IO_ERROR_FAILED;
break;
}
(void) gpg_strerror_r (gpg_error, errbuf, sizeof (errbuf));
errbuf[sizeof(errbuf)-1] = '\0';
g_set_error (error, G_IO_ERROR, errcode, "%s: %s",
gpgme_strsource (gpg_error),
errbuf);
}
gboolean
ot_gpgme_ctx_tmp_home_dir (gpgme_ctx_t gpgme_ctx,
const char *tmp_dir,
char **out_tmp_home_dir,
GOutputStream **out_pubring_stream,
GCancellable *cancellable,
GError **error)
{
g_autofree char *tmp_home_dir = NULL;
gpgme_error_t gpg_error;
gboolean ret = FALSE;
g_return_val_if_fail (gpgme_ctx != NULL, FALSE);
/* GPGME has no API for using multiple keyrings (aka, gpg --keyring),
* so we create a temporary directory and tell GPGME to use it as the
* home directory. Then (optionally) create a pubring.gpg file there
* and hand the caller an open output stream to concatenate necessary
* keyring files. */
if (tmp_dir == NULL)
tmp_dir = g_get_tmp_dir ();
tmp_home_dir = g_build_filename (tmp_dir, "ostree-gpg-XXXXXX", NULL);
if (mkdtemp (tmp_home_dir) == NULL)
{
glnx_set_error_from_errno (error);
goto out;
}
/* Not documented, but gpgme_ctx_set_engine_info() accepts NULL for
* the executable file name, which leaves the old setting unchanged. */
gpg_error = gpgme_ctx_set_engine_info (gpgme_ctx,
GPGME_PROTOCOL_OpenPGP,
NULL, tmp_home_dir);
if (gpg_error != GPG_ERR_NO_ERROR)
{
ot_gpgme_error_to_gio_error (gpg_error, error);
goto out;
}
if (out_pubring_stream != NULL)
{
GFileOutputStream *pubring_stream;
g_autoptr(GFile) pubring_file = NULL;
g_autofree char *pubring_path = NULL;
pubring_path = g_build_filename (tmp_home_dir, "pubring.gpg", NULL);
pubring_file = g_file_new_for_path (pubring_path);
pubring_stream = g_file_create (pubring_file,
G_FILE_CREATE_NONE,
cancellable, error);
if (pubring_stream == NULL)
goto out;
/* Sneaky cast from GFileOutputStream to GOutputStream. */
*out_pubring_stream = g_steal_pointer (&pubring_stream);
}
if (out_tmp_home_dir != NULL)
*out_tmp_home_dir = g_steal_pointer (&tmp_home_dir);
ret = TRUE;
out:
if (!ret)
{
/* Clean up our mess on error. */
(void) glnx_shutil_rm_rf_at (AT_FDCWD, tmp_home_dir, NULL, NULL);
}
return ret;
}
/**** The functions below are based on seahorse-gpgme-data.c ****/
static void
set_errno_from_gio_error (GError *error)
{
/* This is the reverse of g_io_error_from_errno() */
g_return_if_fail (error != NULL);
switch (error->code)
{
case G_IO_ERROR_FAILED:
errno = EIO;
break;
case G_IO_ERROR_NOT_FOUND:
errno = ENOENT;
break;
case G_IO_ERROR_EXISTS:
errno = EEXIST;
break;
case G_IO_ERROR_IS_DIRECTORY:
errno = EISDIR;
break;
case G_IO_ERROR_NOT_DIRECTORY:
errno = ENOTDIR;
break;
case G_IO_ERROR_NOT_EMPTY:
errno = ENOTEMPTY;
break;
case G_IO_ERROR_NOT_REGULAR_FILE:
case G_IO_ERROR_NOT_SYMBOLIC_LINK:
case G_IO_ERROR_NOT_MOUNTABLE_FILE:
errno = EBADF;
break;
case G_IO_ERROR_FILENAME_TOO_LONG:
errno = ENAMETOOLONG;
break;
case G_IO_ERROR_INVALID_FILENAME:
errno = EINVAL;
break;
case G_IO_ERROR_TOO_MANY_LINKS:
errno = EMLINK;
break;
case G_IO_ERROR_NO_SPACE:
errno = ENOSPC;
break;
case G_IO_ERROR_INVALID_ARGUMENT:
errno = EINVAL;
break;
case G_IO_ERROR_PERMISSION_DENIED:
errno = EPERM;
break;
case G_IO_ERROR_NOT_SUPPORTED:
errno = ENOTSUP;
break;
case G_IO_ERROR_NOT_MOUNTED:
errno = ENOENT;
break;
case G_IO_ERROR_ALREADY_MOUNTED:
errno = EALREADY;
break;
case G_IO_ERROR_CLOSED:
errno = EBADF;
break;
case G_IO_ERROR_CANCELLED:
errno = EINTR;
break;
case G_IO_ERROR_PENDING:
errno = EALREADY;
break;
case G_IO_ERROR_READ_ONLY:
errno = EACCES;
break;
case G_IO_ERROR_CANT_CREATE_BACKUP:
errno = EIO;
break;
case G_IO_ERROR_WRONG_ETAG:
errno = EACCES;
break;
case G_IO_ERROR_TIMED_OUT:
errno = EIO;
break;
case G_IO_ERROR_WOULD_RECURSE:
errno = ELOOP;
break;
case G_IO_ERROR_BUSY:
errno = EBUSY;
break;
case G_IO_ERROR_WOULD_BLOCK:
errno = EWOULDBLOCK;
break;
case G_IO_ERROR_HOST_NOT_FOUND:
errno = EHOSTDOWN;
break;
case G_IO_ERROR_WOULD_MERGE:
errno = EIO;
break;
case G_IO_ERROR_FAILED_HANDLED:
errno = 0;
break;
default:
errno = EIO;
break;
}
}
static ssize_t
data_read_cb (void *handle, void *buffer, size_t size)
{
GInputStream *input_stream = handle;
gsize bytes_read;
GError *local_error = NULL;
g_return_val_if_fail (G_IS_INPUT_STREAM (input_stream), -1);
g_input_stream_read_all (input_stream, buffer, size,
&bytes_read, NULL, &local_error);
if (local_error != NULL)
{
set_errno_from_gio_error (local_error);
g_clear_error (&local_error);
bytes_read = -1;
}
return bytes_read;
}
static ssize_t
data_write_cb (void *handle, const void *buffer, size_t size)
{
GOutputStream *output_stream = handle;
gsize bytes_written;
GError *local_error = NULL;
g_return_val_if_fail (G_IS_OUTPUT_STREAM (output_stream), -1);
if (g_output_stream_write_all (output_stream, buffer, size,
&bytes_written, NULL, &local_error))
{
g_output_stream_flush (output_stream, NULL, &local_error);
}
if (local_error != NULL)
{
set_errno_from_gio_error (local_error);
g_clear_error (&local_error);
bytes_written = -1;
}
return bytes_written;
}
static off_t
data_seek_cb (void *handle, off_t offset, int whence)
{
GObject *stream = handle;
GSeekable *seekable;
GSeekType seek_type = 0;
off_t position = -1;
GError *local_error = NULL;
g_return_val_if_fail (G_IS_INPUT_STREAM (stream) ||
G_IS_OUTPUT_STREAM (stream), -1);
if (!G_IS_SEEKABLE (stream)) {
errno = EOPNOTSUPP;
goto out;
}
switch (whence)
{
case SEEK_SET:
seek_type = G_SEEK_SET;
break;
case SEEK_CUR:
seek_type = G_SEEK_CUR;
break;
case SEEK_END:
seek_type = G_SEEK_END;
break;
default:
g_assert_not_reached ();
}
seekable = G_SEEKABLE (stream);
if (!g_seekable_seek (seekable, offset, seek_type, NULL, &local_error))
{
set_errno_from_gio_error (local_error);
g_clear_error (&local_error);
goto out;
}
position = g_seekable_tell (seekable);
out:
return position;
}
static void
data_release_cb (void *handle)
{
GObject *stream = handle;
g_return_if_fail (G_IS_INPUT_STREAM (stream) ||
G_IS_OUTPUT_STREAM (stream));
g_object_unref (stream);
}
static struct gpgme_data_cbs data_input_cbs = {
data_read_cb,
NULL,
data_seek_cb,
data_release_cb
};
static struct gpgme_data_cbs data_output_cbs = {
NULL,
data_write_cb,
data_seek_cb,
data_release_cb
};
gpgme_data_t
ot_gpgme_data_input (GInputStream *input_stream)
{
gpgme_data_t data = NULL;
gpgme_error_t gpg_error;
g_return_val_if_fail (G_IS_INPUT_STREAM (input_stream), NULL);
gpg_error = gpgme_data_new_from_cbs (&data, &data_input_cbs, input_stream);
/* The only possible error is ENOMEM, which we abort on. */
if (gpg_error != GPG_ERR_NO_ERROR)
{
g_assert (gpgme_err_code (gpg_error) == GPG_ERR_ENOMEM);
ot_gpgme_error_to_gio_error (gpg_error, NULL);
g_assert_not_reached ();
}
g_object_ref (input_stream);
return data;
}
gpgme_data_t
ot_gpgme_data_output (GOutputStream *output_stream)
{
gpgme_data_t data = NULL;
gpgme_error_t gpg_error;
g_return_val_if_fail (G_IS_OUTPUT_STREAM (output_stream), NULL);
gpg_error = gpgme_data_new_from_cbs (&data, &data_output_cbs, output_stream);
/* The only possible error is ENOMEM, which we abort on. */
if (gpg_error != GPG_ERR_NO_ERROR)
{
g_assert (gpgme_err_code (gpg_error) == GPG_ERR_ENOMEM);
ot_gpgme_error_to_gio_error (gpg_error, NULL);
g_assert_not_reached ();
}
g_object_ref (output_stream);
return data;
}
gpgme_ctx_t
ot_gpgme_new_ctx (const char *homedir,
GError **error)
{
gpgme_error_t err;
ot_auto_gpgme_ctx gpgme_ctx_t context = NULL;
if ((err = gpgme_new (&context)) != GPG_ERR_NO_ERROR)
{
ot_gpgme_error_to_gio_error (err, error);
g_prefix_error (error, "Unable to create gpg context: ");
return NULL;
}
if (homedir != NULL)
{
gpgme_engine_info_t info;
info = gpgme_ctx_get_engine_info (context);
if ((err = gpgme_ctx_set_engine_info (context, info->protocol, NULL, homedir))
!= GPG_ERR_NO_ERROR)
{
ot_gpgme_error_to_gio_error (err, error);
g_prefix_error (error, "Unable to set gpg homedir to '%s': ",
homedir);
return NULL;
}
}
return g_steal_pointer (&context);
}