diff options
Diffstat (limited to '')
-rw-r--r-- | signedfile.c | 502 |
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; +} |