/* wydawca - automatic release submission daemon Copyright (C) 2007, 2010-2013, 2017 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; }