/* wydawca - automatic release submission daemon
Copyright (C) 2007, 2010-2013 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) { \
wy_log(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)) {
wy_log(LOG_ERR, _("cannot change to directory %s: %s"),
name, strerror(errno));
return 1;
}
dir = opendir(".");
if (!dir) {
wy_log(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) {
wy_log(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)
wy_log(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)) {
wy_log(LOG_ERR, _("cannot save current directory: %s"),
strerror(errno));
return 1;
}
rc = recursive_rmdir(name);
if (pop_dir()) {
wy_log(LOG_ERR, _("cannot restore current directory: %s"),
strerror(errno));
rc = 1;
}
if (rc == 0 && rmdir(name)) {
wy_log(LOG_ERR, _("cannot remove directory %s: %s"),
name, strerror(errno));
return 1;
}
return rc;
}
/* Remove temporary GPG home directory */
static void
remove_homedir()
{
wy_debug(2, (_("removing GNUPG home directory: %s"), temp_homedir));
if (rmdir_r(temp_homedir))
wy_log(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)) {
wy_log(LOG_CRIT,
_("cannot create GPG home directory (%s): %s"),
temp_homedir, strerror(errno));
return 1;
}
atexit(remove_homedir);
wy_debug(2, (_("GNUPG home directory: %s"), temp_homedir));
setenv("GNUPGHOME", temp_homedir, 1);
return 0;
}
static int
checksig(gpgme_signature_t sig, const char *uid, struct wy_triplet *trp)
{
switch (gpg_err_code(sig->status)) {
case GPG_ERR_NO_ERROR:
wy_debug(1, (_("Good signature from %s"), uid));
trp->uploader = uploader_find_frp(trp->uploader_list,
sig->fpr);
if (!trp->uploader) {
wy_log(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);
wy_log(LOG_ERR, _("BAD signature from %s"), uid);
return 0;
case GPG_ERR_NO_PUBKEY:
UPDATE_STATS(STAT_ACCESS_VIOLATIONS);
wy_log(LOG_ERR, _("No public key"));
return 0;
case GPG_ERR_NO_DATA:
UPDATE_STATS(STAT_BAD_TRIPLETS);
wy_log(LOG_ERR, _("No signature"));
return 0;
case GPG_ERR_SIG_EXPIRED:
UPDATE_STATS(STAT_BAD_SIGNATURE);
wy_log(LOG_ERR, _("Expired signature from %s"), uid);
return 0;
case GPG_ERR_KEY_EXPIRED:
UPDATE_STATS(STAT_BAD_SIGNATURE);
wy_log(LOG_ERR, _("Key expired (%s)"), uid);
return 0;
default:
wy_log(LOG_ERR, _("Unknown signature error"));
return 0;
}
return -1;
}
static int
gpg_verify_signature(gpgme_ctx_t ctx, gpgme_signature_t sig,
struct wy_triplet *trp)
{
if (!sig)
return 0;
for (; sig; sig = sig->next) {
const char *uid;
gpgme_key_t key;
int rc;
if (gpgme_get_key(ctx, sig->fpr, &key, 0) == GPG_ERR_NO_ERROR)
uid = key->uids->uid;
else
uid = sig->fpr;
rc = checksig(sig, uid, trp);
gpgme_key_unref(key);
if (rc != -1)
return rc;
}
return 1;
}
/* Verify the directive file from TRP using public key PUBKEY */
int
verify_directive_signature(struct wy_triplet *trp)
{
gpgme_ctx_t ctx;
gpgme_data_t key_data, directive_data, plain = NULL;
gpgme_error_t ec;
int rc;
struct wy_user *uptr;
create_gpg_homedir();
fail_if_err(gpgme_new(&ctx));
for (uptr = wy_triplet_get_uploaders(trp); 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);
wy_debug(3, (_("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,
wy_ev_bad_directive_signature);
rc = 1;
} else
rc = 0;
} else {
rc = 1;
UPDATE_STATS(STAT_BAD_SIGNATURE);
wy_log(LOG_ERR, _("%s: directive verification failed: %s"),
trp->name, gpgme_strerror(ec));
}
gpgme_data_release(plain);
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 wy_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) {
wy_log(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:
wy_debug(1, (_("good detached signature for %s"), trp->name));
return 0;
case exec_fail:
UPDATE_STATS(STAT_BAD_SIGNATURE);
wy_log(LOG_ERR, _("BAD detached signature for %s"), trp->name);
notify(spool->notification, trp, wy_ev_bad_detached_signature);
break;
case exec_error:
wy_log(LOG_CRIT, _("cannot verify detached signature for %s"),
trp->name);
break;
}
return 1;
}