]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/gpgv.cc
Bill is consistent. Bill is correct. Be like Bill.
[apt.git] / apt-pkg / contrib / gpgv.cc
CommitLineData
2f5b6151
DK
1// -*- mode: cpp; mode: fold -*-
2// Include Files /*{{{*/
3#include<config.h>
4
453b82a3
DK
5#include<apt-pkg/configuration.h>
6#include<apt-pkg/error.h>
7#include<apt-pkg/strutl.h>
8#include<apt-pkg/fileutl.h>
9#include<apt-pkg/gpgv.h>
10
2d3fe9cf 11#include <errno.h>
b38bb727 12#include <stdio.h>
2d3fe9cf 13#include <string.h>
b38bb727 14#include <stdlib.h>
2d3fe9cf 15#include <fcntl.h>
2d3fe9cf 16#include <sys/wait.h>
38beb8b5 17#include <unistd.h>
453b82a3 18#include <stddef.h>
b0d40854
DK
19
20#include <algorithm>
98f884eb 21#include <fstream>
453b82a3 22#include <iostream>
8e438ede 23#include <sstream>
453b82a3
DK
24#include <string>
25#include <vector>
2f5b6151
DK
26
27#include <apti18n.h>
28 /*}}}*/
f1828b69 29static char * GenerateTemporaryFileTemplate(const char *basename) /*{{{*/
2d3fe9cf 30{
2d3fe9cf 31 std::string out;
68e01721
MV
32 std::string tmpdir = GetTempDir();
33 strprintf(out, "%s/%s.XXXXXX", tmpdir.c_str(), basename);
2d3fe9cf
DK
34 return strdup(out.c_str());
35}
36 /*}}}*/
37// ExecGPGV - returns the command needed for verify /*{{{*/
2f5b6151 38// ---------------------------------------------------------------------
12841e83 39/* Generating the commandline for calling gpg is somehow complicated as
2d3fe9cf 40 we need to add multiple keyrings and user supplied options.
12841e83 41 Also, as gpg has no options to enforce a certain reduced style of
2d3fe9cf
DK
42 clear-signed files (=the complete content of the file is signed and
43 the content isn't encoded) we do a divide and conquer approach here
12841e83
DK
44 and split up the clear-signed file in message and signature for gpg.
45 And as a cherry on the cake, we use our apt-key wrapper to do part
46 of the lifting in regards to merging keyrings. Fun for the whole family.
2d3fe9cf 47*/
8e438ede
DK
48static bool iovprintf(std::ostream &out, const char *format,
49 va_list &args, ssize_t &size) {
50 char *S = (char*)malloc(size);
51 ssize_t const n = vsnprintf(S, size, format, args);
52 if (n > -1 && n < size) {
53 out << S;
54 free(S);
55 return true;
56 } else {
57 if (n > -1)
58 size = n + 1;
59 else
60 size *= 2;
61 }
62 free(S);
63 return false;
64}
65static void APT_PRINTF(4) apt_error(std::ostream &outterm, int const statusfd, int fd[2], const char *format, ...)
66{
67 std::ostringstream outstr;
68 std::ostream &out = (statusfd == -1) ? outterm : outstr;
69 va_list args;
70 ssize_t size = 400;
71 while (true) {
72 bool ret;
73 va_start(args,format);
74 ret = iovprintf(out, format, args, size);
75 va_end(args);
76 if (ret == true)
77 break;
78 }
79 if (statusfd != -1)
80 {
81 auto const errtag = "[APTKEY:] ERROR ";
82 outstr << '\n';
83 auto const errtext = outstr.str();
84 if (FileFd::Write(fd[1], errtag, strlen(errtag)) == false ||
85 FileFd::Write(fd[1], errtext.data(), errtext.size()) == false)
86 outterm << errtext << std::flush;
87 }
88}
99ed26d3 89void ExecGPGV(std::string const &File, std::string const &FileGPG,
b0d40854 90 int const &statusfd, int fd[2], std::string const &key)
2f5b6151 91{
99ed26d3 92 #define EINTERNAL 111
8757a0fd 93 std::string const aptkey = _config->Find("Dir::Bin::apt-key", CMAKE_INSTALL_FULL_BINDIR "/apt-key");
2f5b6151
DK
94
95 bool const Debug = _config->FindB("Debug::Acquire::gpgv", false);
e0a243f3
JAK
96 struct exiter {
97 std::vector<const char *> files;
98 void operator ()(int code) APT_NORETURN {
99 std::for_each(files.begin(), files.end(), unlink);
100 exit(code);
101 }
102 } local_exit;
103
2f5b6151 104
2f5b6151 105 std::vector<const char *> Args;
12841e83 106 Args.reserve(10);
2f5b6151 107
12841e83
DK
108 Args.push_back(aptkey.c_str());
109 Args.push_back("--quiet");
33a22672 110 Args.push_back("--readonly");
b0d40854
DK
111 if (key.empty() == false)
112 {
113 if (key[0] == '/')
114 {
115 Args.push_back("--keyring");
116 Args.push_back(key.c_str());
117 }
118 else
119 {
120 Args.push_back("--keyid");
121 Args.push_back(key.c_str());
122 }
123 }
c46a36ad 124 Args.push_back("verify");
2f5b6151 125
b38bb727 126 char statusfdstr[10];
2f5b6151
DK
127 if (statusfd != -1)
128 {
129 Args.push_back("--status-fd");
2d3fe9cf 130 snprintf(statusfdstr, sizeof(statusfdstr), "%i", statusfd);
b38bb727 131 Args.push_back(statusfdstr);
2f5b6151
DK
132 }
133
2f5b6151
DK
134 Configuration::Item const *Opts;
135 Opts = _config->Tree("Acquire::gpgv::Options");
136 if (Opts != 0)
137 {
138 Opts = Opts->Child;
139 for (; Opts != 0; Opts = Opts->Next)
140 {
141 if (Opts->Value.empty() == true)
142 continue;
143 Args.push_back(Opts->Value.c_str());
144 }
145 }
146
62d8a765 147 enum { DETACHED, CLEARSIGNED } releaseSignature = (FileGPG != File) ? DETACHED : CLEARSIGNED;
2d3fe9cf
DK
148 std::vector<std::string> dataHeader;
149 char * sig = NULL;
150 char * data = NULL;
98f884eb
JAK
151 char * conf = nullptr;
152
153 // Dump the configuration so apt-key picks up the correct Dir values
154 {
155 conf = GenerateTemporaryFileTemplate("apt.conf");
156 if (conf == nullptr) {
8e438ede 157 apt_error(std::cerr, statusfd, fd, "Couldn't create tempfile names for passing config to apt-key");
98f884eb
JAK
158 local_exit(EINTERNAL);
159 }
160 int confFd = mkstemp(conf);
161 if (confFd == -1) {
8e438ede 162 apt_error(std::cerr, statusfd, fd, "Couldn't create temporary file %s for passing config to apt-key", conf);
98f884eb
JAK
163 local_exit(EINTERNAL);
164 }
165 local_exit.files.push_back(conf);
166
167 std::ofstream confStream(conf);
168 close(confFd);
169 _config->Dump(confStream);
170 confStream.close();
171 setenv("APT_CONFIG", conf, 1);
172 }
2d3fe9cf 173
62d8a765 174 if (releaseSignature == DETACHED)
2d3fe9cf
DK
175 {
176 Args.push_back(FileGPG.c_str());
2f5b6151 177 Args.push_back(File.c_str());
2d3fe9cf
DK
178 }
179 else // clear-signed file
180 {
181 sig = GenerateTemporaryFileTemplate("apt.sig");
182 data = GenerateTemporaryFileTemplate("apt.data");
183 if (sig == NULL || data == NULL)
184 {
8e438ede 185 apt_error(std::cerr, statusfd, fd, "Couldn't create tempfile names for splitting up %s", File.c_str());
e0a243f3 186 local_exit(EINTERNAL);
b408e4ad
DK
187 }
188
189 int const sigFd = mkstemp(sig);
190 int const dataFd = mkstemp(data);
e0a243f3
JAK
191 if (dataFd != -1)
192 local_exit.files.push_back(data);
193 if (sigFd != -1)
194 local_exit.files.push_back(sig);
b408e4ad
DK
195 if (sigFd == -1 || dataFd == -1)
196 {
8e438ede 197 apt_error(std::cerr, statusfd, fd, "Couldn't create tempfiles for splitting up %s", File.c_str());
e0a243f3 198 local_exit(EINTERNAL);
2d3fe9cf
DK
199 }
200
b408e4ad
DK
201 FileFd signature;
202 signature.OpenDescriptor(sigFd, FileFd::WriteOnly, true);
203 FileFd message;
204 message.OpenDescriptor(dataFd, FileFd::WriteOnly, true);
2d3fe9cf 205
b408e4ad
DK
206 if (signature.Failed() == true || message.Failed() == true ||
207 SplitClearSignedFile(File, &message, &dataHeader, &signature) == false)
2d3fe9cf 208 {
8e438ede 209 apt_error(std::cerr, statusfd, fd, "Splitting up %s into data and signature failed", File.c_str());
e0a243f3 210 local_exit(112);
2d3fe9cf 211 }
2d3fe9cf
DK
212 Args.push_back(sig);
213 Args.push_back(data);
214 }
215
2f5b6151
DK
216 Args.push_back(NULL);
217
218 if (Debug == true)
219 {
12841e83 220 std::clog << "Preparing to exec: ";
2f5b6151
DK
221 for (std::vector<const char *>::const_iterator a = Args.begin(); *a != NULL; ++a)
222 std::clog << " " << *a;
223 std::clog << std::endl;
224 }
225
226 if (statusfd != -1)
227 {
12841e83 228 int const nullfd = open("/dev/null", O_WRONLY);
2f5b6151
DK
229 close(fd[0]);
230 // Redirect output to /dev/null; we read from the status fd
b38bb727
DK
231 if (statusfd != STDOUT_FILENO)
232 dup2(nullfd, STDOUT_FILENO);
233 if (statusfd != STDERR_FILENO)
234 dup2(nullfd, STDERR_FILENO);
2f5b6151
DK
235 // Redirect the pipe to the status fd (3)
236 dup2(fd[1], statusfd);
237
238 putenv((char *)"LANG=");
239 putenv((char *)"LC_ALL=");
240 putenv((char *)"LC_MESSAGES=");
241 }
242
81ee750f
JAK
243
244 // We have created tempfiles we have to clean up
245 // and we do an additional check, so fork yet another time …
246 pid_t pid = ExecFork();
247 if(pid < 0) {
8e438ede 248 apt_error(std::cerr, statusfd, fd, "Fork failed for %s to check %s", Args[0], File.c_str());
81ee750f
JAK
249 local_exit(EINTERNAL);
250 }
251 if(pid == 0)
2d3fe9cf 252 {
81ee750f
JAK
253 if (statusfd != -1)
254 dup2(fd[1], statusfd);
12841e83 255 execvp(Args[0], (char **) &Args[0]);
8e438ede 256 apt_error(std::cerr, statusfd, fd, "Couldn't execute %s to check %s", Args[0], File.c_str());
e0a243f3 257 local_exit(EINTERNAL);
2d3fe9cf 258 }
2d3fe9cf 259
81ee750f
JAK
260 // Wait and collect the error code - taken from WaitPid as we need the exact Status
261 int Status;
262 while (waitpid(pid,&Status,0) != pid)
263 {
264 if (errno == EINTR)
265 continue;
8e438ede 266 apt_error(std::cerr, statusfd, fd, _("Waited for %s but it wasn't there"), "apt-key");
81ee750f
JAK
267 local_exit(EINTERNAL);
268 }
2d3fe9cf 269
81ee750f
JAK
270 // check if it exit'ed normally …
271 if (WIFEXITED(Status) == false)
272 {
8e438ede 273 apt_error(std::cerr, statusfd, fd, _("Sub-process %s exited unexpectedly"), "apt-key");
81ee750f
JAK
274 local_exit(EINTERNAL);
275 }
2d3fe9cf 276
81ee750f
JAK
277 // … and with a good exit code
278 if (WEXITSTATUS(Status) != 0)
279 {
8e438ede
DK
280 // we forward the statuscode, so don't generate a message on the fd in this case
281 apt_error(std::cerr, -1, fd, _("Sub-process %s returned an error code (%u)"), "apt-key", WEXITSTATUS(Status));
81ee750f 282 local_exit(WEXITSTATUS(Status));
2d3fe9cf 283 }
81ee750f
JAK
284
285 // everything fine
286 local_exit(0);
2d3fe9cf
DK
287}
288 /*}}}*/
2d3fe9cf 289// SplitClearSignedFile - split message into data/signature /*{{{*/
b408e4ad
DK
290bool SplitClearSignedFile(std::string const &InFile, FileFd * const ContentFile,
291 std::vector<std::string> * const ContentHeader, FileFd * const SignatureFile)
2d3fe9cf
DK
292{
293 FILE *in = fopen(InFile.c_str(), "r");
294 if (in == NULL)
295 return _error->Errno("fopen", "can not open %s", InFile.c_str());
296
2d3fe9cf
DK
297 bool found_message_start = false;
298 bool found_message_end = false;
299 bool skip_until_empty_line = false;
300 bool found_signature = false;
301 bool first_line = true;
302
303 char *buf = NULL;
304 size_t buf_size = 0;
9ce3cfc9 305 while (getline(&buf, &buf_size, in) != -1)
2d3fe9cf
DK
306 {
307 _strrstrip(buf);
308 if (found_message_start == false)
309 {
310 if (strcmp(buf, "-----BEGIN PGP SIGNED MESSAGE-----") == 0)
311 {
312 found_message_start = true;
313 skip_until_empty_line = true;
314 }
315 }
316 else if (skip_until_empty_line == true)
317 {
318 if (strlen(buf) == 0)
319 skip_until_empty_line = false;
320 // save "Hash" Armor Headers, others aren't allowed
321 else if (ContentHeader != NULL && strncmp(buf, "Hash: ", strlen("Hash: ")) == 0)
322 ContentHeader->push_back(buf);
323 }
324 else if (found_signature == false)
325 {
326 if (strcmp(buf, "-----BEGIN PGP SIGNATURE-----") == 0)
327 {
328 found_signature = true;
329 found_message_end = true;
b408e4ad
DK
330 if (SignatureFile != NULL)
331 {
332 SignatureFile->Write(buf, strlen(buf));
333 SignatureFile->Write("\n", 1);
334 }
2d3fe9cf 335 }
cb323489 336 else if (found_message_end == false) // we are in the message block
2d3fe9cf 337 {
cb323489
DK
338 // we don't have any fields which need dash-escaped,
339 // but implementations are free to encode all lines …
340 char const * dashfree = buf;
341 if (strncmp(dashfree, "- ", 2) == 0)
342 dashfree += 2;
2d3fe9cf 343 if(first_line == true) // first line does not need a newline
2d3fe9cf 344 first_line = false;
b408e4ad 345 else if (ContentFile != NULL)
b408e4ad 346 ContentFile->Write("\n", 1);
cb323489
DK
347 else
348 continue;
349 if (ContentFile != NULL)
350 ContentFile->Write(dashfree, strlen(dashfree));
2d3fe9cf
DK
351 }
352 }
353 else if (found_signature == true)
354 {
b408e4ad
DK
355 if (SignatureFile != NULL)
356 {
357 SignatureFile->Write(buf, strlen(buf));
358 SignatureFile->Write("\n", 1);
359 }
2d3fe9cf
DK
360 if (strcmp(buf, "-----END PGP SIGNATURE-----") == 0)
361 found_signature = false; // look for other signatures
362 }
363 // all the rest is whitespace, unsigned garbage or additional message blocks we ignore
364 }
bea263c2 365 fclose(in);
3d8232bf
DK
366 if (buf != NULL)
367 free(buf);
2d3fe9cf 368
f1828b69
DK
369 if (found_signature == true)
370 return _error->Error("Signature in file %s wasn't closed", InFile.c_str());
371
372 // if we haven't found any of them, this an unsigned file,
373 // so don't generate an error, but splitting was unsuccessful none-the-less
cb323489 374 if (first_line == true && found_message_start == false && found_message_end == false)
f1828b69
DK
375 return false;
376 // otherwise one missing indicates a syntax error
463f24f9
MV
377 else if (first_line == true || found_message_start == false || found_message_end == false)
378 return _error->Error("Splitting of file %s failed as it doesn't contain all expected parts %i %i %i", InFile.c_str(), first_line, found_message_start, found_message_end);
f1828b69 379
2d3fe9cf
DK
380 return true;
381}
f1828b69
DK
382 /*}}}*/
383bool OpenMaybeClearSignedFile(std::string const &ClearSignedFileName, FileFd &MessageFile) /*{{{*/
384{
385 char * const message = GenerateTemporaryFileTemplate("fileutl.message");
386 int const messageFd = mkstemp(message);
387 if (messageFd == -1)
388 {
389 free(message);
390 return _error->Errno("mkstemp", "Couldn't create temporary file to work with %s", ClearSignedFileName.c_str());
391 }
392 // we have the fd, thats enough for us
393 unlink(message);
394 free(message);
395
46c4043d 396 MessageFile.OpenDescriptor(messageFd, FileFd::ReadWrite | FileFd::BufferedWrite, true);
b408e4ad
DK
397 if (MessageFile.Failed() == true)
398 return _error->Error("Couldn't open temporary file to work with %s", ClearSignedFileName.c_str());
f1828b69
DK
399
400 _error->PushToStack();
9ce3cfc9 401 bool const splitDone = SplitClearSignedFile(ClearSignedFileName, &MessageFile, NULL, NULL);
f1828b69
DK
402 bool const errorDone = _error->PendingError();
403 _error->MergeWithStack();
404 if (splitDone == false)
405 {
b408e4ad 406 MessageFile.Close();
f1828b69
DK
407
408 if (errorDone == true)
409 return false;
410
411 // we deal with an unsigned file
412 MessageFile.Open(ClearSignedFileName, FileFd::ReadOnly);
413 }
414 else // clear-signed
415 {
b408e4ad
DK
416 if (MessageFile.Seek(0) == false)
417 return _error->Errno("lseek", "Unable to seek back in message for file %s", ClearSignedFileName.c_str());
f1828b69
DK
418 }
419
420 return MessageFile.Failed() == false;
421}
422 /*}}}*/