/* wydawca - automatic release submission daemon
Copyright (C) 2007, 2010 Sergey Poznyakoff
Wydawca 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 3 of the License, or (at your
option) any later version.
Wydawca 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 wydawca. If not, see . */
/* GPG interface functions */
#include "wydawca.h"
#include
#define fail_if_err(expr) \
do \
{ \
int a = expr; \
if (a) \
{ \
logmsg (LOG_ERR, _("%s: GPGME error: %s"), #expr, \
gpgme_strerror (a)); \
return 1; \
} \
} \
while (0)
char *temp_homedir;
static int rmdir_r (const char *name);
/* Auxiliary function: change to directory NAME and recursively remove
everything under it. */
static int
recursive_rmdir (const char *name)
{
int rc;
DIR *dir;
struct dirent *ent;
if (chdir (name))
{
logmsg (LOG_ERR, _("cannot change to directory %s: %s"),
name, strerror (errno));
return 1;
}
dir = opendir (".");
if (!dir)
{
logmsg (LOG_ERR, _("cannot open directory %s: %s"),
name, strerror (errno));
return 1;
}
for (rc = 0; rc == 0 && (ent = readdir (dir));)
{
struct stat st;
if (strcmp (ent->d_name, ".") == 0
|| strcmp (ent->d_name, "..") == 0)
continue;
if (stat (ent->d_name, &st) && errno != ENOENT)
{
logmsg (LOG_ERR, _("cannot stat file `%s': %s"),
name, strerror (errno));
rc = 1;
}
else if (S_ISDIR (st.st_mode))
rc = rmdir_r (ent->d_name);
else if ((rc = unlink (ent->d_name)) != 0 && errno != ENOENT)
logmsg (LOG_ERR, _("cannot unlink %s: %s"),
ent->d_name, strerror (errno));
}
closedir (dir);
return rc;
}
/* Recursively remove the contents of the directory NAME and the directory
itself. Do not change CWD. */
static int
rmdir_r (const char *name)
{
int rc;
if (push_dir (NULL))
{
logmsg (LOG_ERR, _("cannot save current directory: %s"),
strerror (errno));
return 1;
}
rc = recursive_rmdir (name);
if (pop_dir ())
{
logmsg (LOG_ERR, _("cannot restore current directory: %s"),
strerror (errno));
rc = 1;
}
if (rc == 0 && rmdir (name))
{
logmsg (LOG_ERR, _("cannot remove directory %s: %s"),
name, strerror (errno));
return 1;
}
return rc;
}
/* Remove temporary GPG home directory */
static void
remove_homedir ()
{
if (debug_level > 1)
logmsg (LOG_DEBUG, _("removing GNUPG home directory: %s"), temp_homedir);
if (rmdir_r (temp_homedir))
logmsg (LOG_CRIT, _("failed to remove GPG directory %s"), temp_homedir);
}
/* Create a temporary GPG home directory */
static int
create_gpg_homedir ()
{
if (temp_homedir)
return 0;
temp_homedir = grecs_strdup ("/tmp/wydawca-XXXXXX");
if (!mkdtemp (temp_homedir))
{
logmsg (LOG_CRIT, _("cannot create GPG home directory (%s): %s"),
temp_homedir, strerror (errno));
return 1;
}
atexit (remove_homedir);
if (debug_level > 1)
logmsg (LOG_DEBUG, _("GNUPG home directory: %s"), temp_homedir);
setenv ("GNUPGHOME", temp_homedir, 1);
return 0;
}
static int
gpg_verify_signature (gpgme_ctx_t ctx, gpgme_signature_t sig,
struct file_triplet *trp)
{
if (!sig)
return 0;
for (; sig; sig = sig->next)
{
const char *uid;
gpgme_key_t key;
if (gpgme_get_key (ctx, sig->fpr, &key, 0) == GPG_ERR_NO_ERROR)
uid = key->uids->uid;
else
uid = sig->fpr;
switch (gpg_err_code (sig->status))
{
case GPG_ERR_NO_ERROR:
if (debug_level)
logmsg (LOG_NOTICE, _("Good signature from %s"), uid);
trp->uploader = uploader_find_frp (trp->uploader_list, sig->fpr);
if (!trp->uploader)
{
logmsg (LOG_ERR,
_("good signature from %s, "
"but the uploader info for %s not found"),
uid, sig->fpr);
return 1;
}
break;
case GPG_ERR_BAD_SIGNATURE:
UPDATE_STATS (STAT_BAD_SIGNATURE);
logmsg (LOG_ERR, _("BAD signature from %s"), uid);
return 0;
case GPG_ERR_NO_PUBKEY:
UPDATE_STATS (STAT_ACCESS_VIOLATIONS);
logmsg (LOG_ERR, _("No public key"));
return 0;
case GPG_ERR_NO_DATA:
UPDATE_STATS (STAT_BAD_TRIPLETS);
logmsg (LOG_ERR, _("No signature"));
return 0;
case GPG_ERR_SIG_EXPIRED:
UPDATE_STATS (STAT_BAD_SIGNATURE);
logmsg (LOG_ERR, _("Expired signature from %s"), uid);
return 0;
case GPG_ERR_KEY_EXPIRED:
UPDATE_STATS (STAT_BAD_SIGNATURE);
logmsg (LOG_ERR, _("Key expired (%s)"), uid);
return 0;
default:
logmsg (LOG_ERR, _("Unknown signature error"));
return 0;
}
}
return 1;
}
/* Verify the directive file from TRP using public key PUBKEY */
int
verify_directive_signature (struct file_triplet *trp)
{
gpgme_ctx_t ctx;
gpgme_data_t key_data, directive_data, plain;
gpgme_error_t ec;
int rc;
struct uploader_info *uptr;
create_gpg_homedir ();
fail_if_err (gpgme_new (&ctx));
for (uptr = trp->uploader_list; uptr; uptr = uptr->next)
{
gpgme_import_result_t res;
gpgme_import_status_t pstat;
fail_if_err (gpgme_data_new_from_mem (&key_data,
uptr->gpg_key,
strlen (uptr->gpg_key),
0));
fail_if_err (gpgme_op_import (ctx, key_data));
res = gpgme_op_import_result (ctx);
pstat = res->imports;
uptr->fpr = grecs_strdup (pstat->fpr);
if (debug_level > 2)
logmsg (LOG_DEBUG, _("imported key: user = %s, fingerprint = %s"),
uptr->name, uptr->fpr);
}
fail_if_err (gpgme_data_new_from_file (&directive_data,
trp->file[file_directive].name, 1));
gpgme_data_new (&plain);
ec = gpgme_op_verify (ctx, directive_data, NULL, plain);
if (ec == GPG_ERR_NO_ERROR)
{
gpgme_verify_result_t result;
result = gpgme_op_verify_result (ctx);
if (!gpg_verify_signature (ctx, result->signatures, trp))
{
UPDATE_STATS (STAT_BAD_SIGNATURE);
notify (trp->spool->notification, trp, ev_bad_directive_signature);
rc = 1;
}
else
rc = 0;
}
else
{
rc = 1;
UPDATE_STATS (STAT_BAD_SIGNATURE);
logmsg (LOG_ERR, _("%s: directive verification failed: %s"),
trp->name, gpgme_strerror (ec));
}
gpgme_data_release (directive_data);
gpgme_data_release (key_data);
gpgme_release (ctx);
return rc;
}
/* Verify the detached signature of TRP.
NOTE: It is assumed that the public key is already registered (by
a previous call to verify_directive_signature). */
int
verify_detached_signature (struct file_triplet *trp)
{
gpgme_engine_info_t info;
const char *argv[5];
const struct spool *spool;
ASGN_SPOOL (spool, trp, return 1);
fail_if_err (gpgme_get_engine_info (&info));
while (info && info->protocol != GPGME_PROTOCOL_OpenPGP)
info = info->next;
if (!info)
{
logmsg (LOG_CRIT,
_("cannot find path to gpg binary (attempting to verify "
"the detached signature for %s"), trp->name);
return 1;
}
create_gpg_homedir ();
argv[0] = info->file_name;
argv[1] = "--verify";
argv[2] = trp->file[file_signature].name;
argv[3] = trp->file[file_dist].name;
argv[4] = NULL;
switch (wydawca_exec (5, argv, NULL))
{
case exec_success:
if (debug_level)
logmsg (LOG_DEBUG, _("good detached signature for %s"), trp->name);
return 0;
case exec_fail:
UPDATE_STATS (STAT_BAD_SIGNATURE);
logmsg (LOG_ERR, _("BAD detached signature for %s"), trp->name);
notify (spool->notification, trp, ev_bad_detached_signature);
break;
case exec_error:
logmsg (LOG_CRIT, _("cannot verify detached signature for %s"),
trp->name);
break;
}
return 1;
}