/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #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; }