summaryrefslogtreecommitdiffstats
path: root/signedfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'signedfile.c')
-rw-r--r--signedfile.c502
1 files changed, 502 insertions, 0 deletions
diff --git a/signedfile.c b/signedfile.c
new file mode 100644
index 0000000..2dfa348
--- /dev/null
+++ b/signedfile.c
@@ -0,0 +1,502 @@
+/* This file is part of "reprepro"
+ * Copyright (C) 2003,2004,2005,2006,2007,2009,2010,2012 Bernhard R. Link
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+#include <config.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <time.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "signature_p.h"
+#include "mprintf.h"
+#include "strlist.h"
+#include "dirs.h"
+#include "names.h"
+#include "chunks.h"
+#include "release.h"
+#include "filecntl.h"
+#include "hooks.h"
+
+#ifdef HAVE_LIBGPGME
+static retvalue check_signature_created(bool clearsign, bool willcleanup, /*@null@*/const struct strlist *options, const char *filename, const char *signaturename) {
+ gpgme_sign_result_t signresult;
+ char *uidoptions;
+ int i;
+
+ signresult = gpgme_op_sign_result(context);
+ if (signresult != NULL && signresult->signatures != NULL)
+ return RET_OK;
+ /* in an ideal world, this point is never reached.
+ * Sadly it is and people are obviously confused by it,
+ * so do some work to give helpful messages. */
+ if (options != NULL) {
+ assert (options->count > 0);
+ uidoptions = mprintf(" -u '%s'", options->values[0]);
+ for (i = 1 ;
+ uidoptions != NULL && i < options->count ;
+ i++) {
+ char *u = mprintf("%s -u '%s'", uidoptions,
+ options->values[i]);
+ free(uidoptions);
+ uidoptions = u;
+ }
+ if (FAILEDTOALLOC(uidoptions))
+ return RET_ERROR_OOM;
+ } else
+ uidoptions = NULL;
+
+ if (signresult == NULL)
+ fputs(
+"Error: gpgme returned NULL unexpectedly for gpgme_op_sign_result\n", stderr);
+ else
+ fputs("Error: gpgme created no signature!\n", stderr);
+ fputs(
+"This most likely means gpg is confused or produces some error libgpgme is\n"
+"not able to understand. Try running\n", stderr);
+ if (willcleanup)
+ fprintf(stderr,
+"gpg %s --output 'some-other-file' %s 'some-file'\n",
+ (uidoptions==NULL)?"":uidoptions,
+ clearsign?"--clearsign":"--detach-sign");
+ else
+ fprintf(stderr,
+"gpg %s --output '%s' %s '%s'\n",
+ (uidoptions==NULL)?"":uidoptions,
+ signaturename,
+ clearsign?"--clearsign":"--detach-sign",
+ filename);
+ fputs(
+"for hints what this error might have been. (Sometimes just running\n"
+"it once manually seems also to help...)\n", stderr);
+ return RET_ERROR_GPGME;
+}
+
+static retvalue signature_to_file(gpgme_data_t dh_gpg, const char *signaturename) {
+ char *signature_data;
+ const char *p;
+ size_t signature_len;
+ ssize_t written;
+ int fd, e, ret;
+
+ signature_data = gpgme_data_release_and_get_mem(dh_gpg, &signature_len);
+ if (FAILEDTOALLOC(signature_data))
+ return RET_ERROR_OOM;
+ fd = open(signaturename, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_NOFOLLOW, 0666);
+ if (fd < 0) {
+ free(signature_data);
+ return RET_ERRNO(errno);
+ }
+ p = signature_data;
+ while (signature_len > 0) {
+ written = write(fd, p, signature_len);
+ if (written < 0) {
+ e = errno;
+ fprintf(stderr, "Error %d writing to %s: %s\n",
+ e, signaturename,
+ strerror(e));
+ free(signature_data);
+ (void)close(fd);
+ return RET_ERRNO(e);
+ }
+ signature_len -= written;
+ p += written;
+ }
+#ifdef HAVE_GPGPME_FREE
+ gpgme_free(signature_data);
+#else
+ free(signature_data);
+#endif
+ ret = close(fd);
+ if (ret < 0) {
+ e = errno;
+ fprintf(stderr, "Error %d writing to %s: %s\n",
+ e, signaturename,
+ strerror(e));
+ return RET_ERRNO(e);
+ }
+ if (verbose > 1) {
+ printf("Successfully created '%s'\n", signaturename);
+ }
+ return RET_OK;
+}
+
+static retvalue create_signature(bool clearsign, gpgme_data_t dh, /*@null@*/const struct strlist *options, const char *filename, const char *signaturename, bool willcleanup) {
+ gpg_error_t err;
+ gpgme_data_t dh_gpg;
+ retvalue r;
+
+ err = gpgme_data_new(&dh_gpg);
+ if (err != 0)
+ return gpgerror(err);
+ err = gpgme_op_sign(context, dh, dh_gpg,
+ clearsign?GPGME_SIG_MODE_CLEAR:GPGME_SIG_MODE_DETACH);
+ if (err != 0)
+ return gpgerror(err);
+ r = check_signature_created(clearsign, willcleanup,
+ options, filename, signaturename);
+ if (RET_WAS_ERROR(r)) {
+ gpgme_data_release(dh_gpg);
+ return r;
+ }
+ /* releases dh_gpg: */
+ return signature_to_file(dh_gpg, signaturename);
+}
+
+static retvalue signature_sign(const struct strlist *options, const char *filename, void *data, size_t datalen, const char *signaturename, const char *clearsignfilename, bool willcleanup) {
+ retvalue r;
+ int i;
+ gpg_error_t err;
+ gpgme_data_t dh;
+
+ assert (options != NULL && options->count > 0);
+ assert (options->values[0][0] != '!');
+
+ r = signature_init(false);
+ if (RET_WAS_ERROR(r))
+ return r;
+
+ gpgme_signers_clear(context);
+ if (options->count == 1 &&
+ (strcasecmp(options->values[0], "yes") == 0 ||
+ strcasecmp(options->values[0], "default") == 0)) {
+ /* use default options */
+ options = NULL;
+ } else for (i = 0 ; i < options->count ; i++) {
+ const char *option = options->values[i];
+ gpgme_key_t key;
+
+ err = gpgme_op_keylist_start(context, option, 1);
+ if (err != 0)
+ return gpgerror(err);
+ err = gpgme_op_keylist_next(context, &key);
+ if (gpg_err_code(err) == GPG_ERR_EOF) {
+ fprintf(stderr,
+"Could not find any key matching '%s'!\n", option);
+ return RET_ERROR;
+ }
+ err = gpgme_signers_add(context, key);
+ gpgme_key_unref(key);
+ if (err != 0) {
+ gpgme_op_keylist_end(context);
+ return gpgerror(err);
+ }
+ gpgme_op_keylist_end(context);
+ }
+
+ err = gpgme_data_new_from_mem(&dh, data, datalen, 0);
+ if (err != 0) {
+ return gpgerror(err);
+ }
+
+ r = create_signature(false, dh, options,
+ filename, signaturename, willcleanup);
+ if (RET_WAS_ERROR(r)) {
+ gpgme_data_release(dh);
+ return r;
+ }
+ i = gpgme_data_seek(dh, 0, SEEK_SET);
+ if (i < 0) {
+ int e = errno;
+ fprintf(stderr,
+"Error %d rewinding gpgme's data buffer to start: %s\n",
+ e, strerror(e));
+ gpgme_data_release(dh);
+ return RET_ERRNO(e);
+ }
+ r = create_signature(true, dh, options,
+ filename, clearsignfilename, willcleanup);
+ gpgme_data_release(dh);
+ if (RET_WAS_ERROR(r))
+ return r;
+ return RET_OK;
+}
+#endif /* HAVE_LIBGPGME */
+
+static retvalue signature_with_extern(const struct strlist *options, const char *filename, const char *clearsignfilename, char **detachedfilename_p) {
+ const char *clearsign;
+ const char *detached;
+ struct stat s;
+ int status;
+ pid_t child, found;
+ const char *command;
+
+ assert (options->count == 2);
+ command = options->values[1];
+ clearsign = (clearsignfilename == NULL)?"":clearsignfilename;
+ detached = (*detachedfilename_p == NULL)?"":*detachedfilename_p;
+
+ if (interrupted())
+ return RET_ERROR_INTERRUPTED;
+
+ if (lstat(filename, &s) != 0 || !S_ISREG(s.st_mode)) {
+ fprintf(stderr, "Internal error: lost unsigned file '%s'?!\n",
+ filename);
+ return RET_ERROR;
+ }
+
+ child = fork();
+ if (child == 0) {
+ /* Try to close all open fd but 0,1,2 */
+ closefrom(3);
+ sethookenvironment(NULL, NULL, NULL, NULL);
+ (void)execl(command, command, filename,
+ clearsign, detached, ENDOFARGUMENTS);
+ fprintf(stderr, "Error executing '%s' '%s' '%s' '%s': %s\n",
+ command, filename, clearsign, detached,
+ strerror(errno));
+ _exit(255);
+ }
+ if (child < 0) {
+ int e = errno;
+ fprintf(stderr, "Error forking: %d=%s!\n", e, strerror(e));
+ return RET_ERRNO(e);
+ }
+ errno = 0;
+ while ((found = waitpid(child, &status, 0)) < 0) {
+ int e = errno;
+ if (e != EINTR) {
+ fprintf(stderr,
+"Error %d waiting for signing-command child %ld: %s!\n",
+ e, (long)child, strerror(e));
+ return RET_ERRNO(e);
+ }
+ }
+ if (found != child) {
+ fprintf(stderr,
+"Confusing return value %ld from waitpid(%ld, ..., 0)", (long)found, (long)child);
+ return RET_ERROR;
+ }
+ if (!WIFEXITED(status)) {
+ fprintf(stderr,
+"Error: Signing-hook '%s' called with arguments '%s' '%s' '%s' terminated abnormally!\n",
+ command, filename, clearsign, detached);
+ return RET_ERROR;
+ }
+ if (WEXITSTATUS(status) != 0) {
+ fprintf(stderr,
+"Error: Signing-hook '%s' called with arguments '%s' '%s' '%s' returned with exit code %d!\n",
+ command, filename, clearsign, detached,
+ (int)(WEXITSTATUS(status)));
+ return RET_ERROR;
+ }
+ if (clearsignfilename != NULL) {
+ if (lstat(clearsign, &s) != 0 || !S_ISREG(s.st_mode)) {
+ fprintf(stderr,
+"Error: Script '%s' did not generate '%s'!\n",
+ command, clearsign);
+ return RET_ERROR;
+ } else if (s.st_size == 0) {
+ fprintf(stderr,
+"Error: Script '%s' created an empty '%s' file!\n",
+ command, clearsign);
+ return RET_ERROR;
+ }
+ }
+ if (*detachedfilename_p != NULL) {
+ if (lstat(detached, &s) != 0 || !S_ISREG(s.st_mode)) {
+ /* no detached signature, no an error if there
+ * was a clearsigned file:*/
+ if (clearsignfilename == NULL) {
+ fprintf(stderr,
+"Error: Script '%s' did not generate '%s'!\n",
+ command, detached);
+ return RET_ERROR;
+ } else {
+ if (verbose > 1)
+ fprintf(stderr,
+"Ignoring legacy detached signature '%s' not generated by '%s'\n",
+ detached, command);
+ detached = NULL;
+ free(*detachedfilename_p);
+ *detachedfilename_p = NULL;
+ }
+ } else if (s.st_size == 0) {
+ fprintf(stderr,
+"Error: Script '%s' created an empty '%s' file!\n",
+ command, detached);
+ return RET_ERROR;
+ }
+ }
+ return RET_OK;
+}
+
+struct signedfile {
+ retvalue result;
+#define DATABUFFERUNITS (128ul * 1024ul)
+ size_t bufferlen, buffersize;
+ char *buffer;
+};
+
+retvalue signature_startsignedfile(struct signedfile **out) {
+ struct signedfile *n;
+
+ n = zNEW(struct signedfile);
+ if (FAILEDTOALLOC(n))
+ return RET_ERROR_OOM;
+ n->bufferlen = 0;
+ n->buffersize = DATABUFFERUNITS;
+ n->buffer = malloc(n->buffersize);
+ if (FAILEDTOALLOC(n->buffer)) {
+ free(n);
+ return RET_ERROR_OOM;
+ }
+ *out = n;
+ return RET_OK;
+}
+
+void signedfile_free(struct signedfile *f) {
+ if (f == NULL)
+ return;
+ free(f->buffer);
+ free(f);
+ return;
+}
+
+
+/* store data into buffer */
+void signedfile_write(struct signedfile *f, const void *data, size_t len) {
+
+ /* no need to try anything if there already was an error */
+ if (RET_WAS_ERROR(f->result))
+ return;
+
+ if (len > f->buffersize - f->bufferlen) {
+ size_t blocks = (len + f->bufferlen)/DATABUFFERUNITS;
+ size_t newsize = (blocks + 1) * DATABUFFERUNITS;
+ char *newbuffer;
+
+ /* realloc is wasteful, but should not happen too often */
+ newbuffer = realloc(f->buffer, newsize);
+ if (FAILEDTOALLOC(newbuffer)) {
+ free(f->buffer);
+ f->buffer = NULL;
+ f->result = RET_ERROR_OOM;
+ return;
+ }
+ f->buffer = newbuffer;
+ f->buffersize = newsize;
+ assert (f->bufferlen < f->buffersize);
+ }
+ assert (len <= f->buffersize - f->bufferlen);
+ memcpy(f->buffer + f->bufferlen, data, len);
+ f->bufferlen += len;
+ assert (f->bufferlen <= f->buffersize);
+}
+
+retvalue signedfile_create(struct signedfile *f, const char *newplainfilename, char **newsignedfilename_p, char **newdetachedsignature_p, const struct strlist *options, bool willcleanup) {
+ size_t len, ofs;
+ int fd, ret;
+
+ if (RET_WAS_ERROR(f->result))
+ return f->result;
+
+ /* write content to file */
+
+ assert (newplainfilename != NULL);
+
+ (void)dirs_make_parent(newplainfilename);
+ (void)unlink(newplainfilename);
+
+ fd = open(newplainfilename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
+ if (fd < 0) {
+ int e = errno;
+ fprintf(stderr, "Error creating file '%s': %s\n",
+ newplainfilename,
+ strerror(e));
+ return RET_ERRNO(e);
+ }
+ ofs = 0;
+ len = f->bufferlen;
+ while (len > 0) {
+ ssize_t written;
+
+ written = write(fd, f->buffer + ofs, len);
+ if (written < 0) {
+ int e = errno;
+ fprintf(stderr, "Error %d writing to file '%s': %s\n",
+ e, newplainfilename,
+ strerror(e));
+ (void)close(fd);
+ return RET_ERRNO(e);
+ }
+ assert ((size_t)written <= len);
+ ofs += written;
+ len -= written;
+ }
+ ret = close(fd);
+ if (ret < 0) {
+ int e = errno;
+ fprintf(stderr, "Error %d writing to file '%s': %s\n",
+ e, newplainfilename,
+ strerror(e));
+ return RET_ERRNO(e);
+ }
+ /* now do the actual signing */
+ if (options != NULL && options->count > 0) {
+ retvalue r;
+ const char *newsigned = *newsignedfilename_p;
+ const char *newdetached = *newdetachedsignature_p;
+
+ /* make sure the new files do not already exist: */
+ if (unlink(newdetached) != 0 && errno != ENOENT) {
+ fprintf(stderr,
+"Could not remove '%s' to prepare replacement: %s\n",
+ newdetached, strerror(errno));
+ return RET_ERROR;
+ }
+ if (unlink(newsigned) != 0 && errno != ENOENT) {
+ fprintf(stderr,
+"Could not remove '%s' to prepare replacement: %s\n",
+ newsigned, strerror(errno));
+ return RET_ERROR;
+ }
+ /* if an hook is given, use that instead */
+ if (options->values[0][0] == '!')
+ r = signature_with_extern(options, newplainfilename,
+ newsigned, newdetachedsignature_p);
+ else
+#ifdef HAVE_LIBGPGME
+ r = signature_sign(options,
+ newplainfilename,
+ f->buffer, f->bufferlen,
+ newdetached, newsigned,
+ willcleanup);
+#else /* HAVE_LIBGPGME */
+ fputs(
+"ERROR: Cannot creature signatures as this reprepro binary is not compiled\n"
+"with support for libgpgme. (Only external signing using 'Signwith: !hook'\n"
+"is supported.\n", stderr);
+ return RET_ERROR_GPGME;
+#endif
+ if (RET_WAS_ERROR(r))
+ return r;
+ } else {
+ /* no signatures requested */
+ free(*newsignedfilename_p);
+ *newsignedfilename_p = NULL;
+ free(*newdetachedsignature_p);
+ *newdetachedsignature_p = NULL;
+ }
+ return RET_OK;
+}