]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/gpgv.cc
- add method to open (maybe) clearsigned files transparently
[apt.git] / apt-pkg / contrib / gpgv.cc
CommitLineData
2f5b6151
DK
1// -*- mode: cpp; mode: fold -*-
2// Include Files /*{{{*/
3#include<config.h>
4
2d3fe9cf 5#include <errno.h>
b38bb727 6#include <stdio.h>
2d3fe9cf 7#include <string.h>
b38bb727 8#include <stdlib.h>
2d3fe9cf 9#include <fcntl.h>
2f5b6151
DK
10#include <sys/stat.h>
11#include <sys/types.h>
2d3fe9cf 12#include <sys/wait.h>
2f5b6151 13
2d3fe9cf
DK
14#include<apt-pkg/configuration.h>
15#include<apt-pkg/error.h>
16#include<apt-pkg/strutl.h>
17#include<apt-pkg/fileutl.h>
18#include<apt-pkg/gpgv.h>
2f5b6151
DK
19
20#include <apti18n.h>
21 /*}}}*/
f1828b69 22static char * GenerateTemporaryFileTemplate(const char *basename) /*{{{*/
2d3fe9cf
DK
23{
24 const char *tmpdir = getenv("TMPDIR");
25#ifdef P_tmpdir
26 if (!tmpdir)
27 tmpdir = P_tmpdir;
28#endif
29 if (!tmpdir)
30 tmpdir = "/tmp";
2f5b6151 31
2d3fe9cf
DK
32 std::string out;
33 strprintf(out, "%s/%s.XXXXXX", tmpdir, basename);
34 return strdup(out.c_str());
35}
36 /*}}}*/
37// ExecGPGV - returns the command needed for verify /*{{{*/
2f5b6151
DK
38// ---------------------------------------------------------------------
39/* Generating the commandline for calling gpgv is somehow complicated as
2d3fe9cf
DK
40 we need to add multiple keyrings and user supplied options.
41 Also, as gpgv has no options to enforce a certain reduced style of
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
44*/
99ed26d3 45void ExecGPGV(std::string const &File, std::string const &FileGPG,
2d3fe9cf 46 int const &statusfd, int fd[2])
2f5b6151 47{
99ed26d3 48 #define EINTERNAL 111
2d3fe9cf 49 std::string const gpgvpath = _config->Find("Dir::Bin::gpg", "/usr/bin/gpgv");
2f5b6151 50 // FIXME: remove support for deprecated APT::GPGV setting
2d3fe9cf
DK
51 std::string const trustedFile = _config->Find("APT::GPGV::TrustedKeyring", _config->FindFile("Dir::Etc::Trusted"));
52 std::string const trustedPath = _config->FindDir("Dir::Etc::TrustedParts");
2f5b6151
DK
53
54 bool const Debug = _config->FindB("Debug::Acquire::gpgv", false);
55
56 if (Debug == true)
57 {
58 std::clog << "gpgv path: " << gpgvpath << std::endl;
59 std::clog << "Keyring file: " << trustedFile << std::endl;
60 std::clog << "Keyring path: " << trustedPath << std::endl;
61 }
62
2d3fe9cf 63 std::vector<std::string> keyrings;
2f5b6151
DK
64 if (DirectoryExists(trustedPath))
65 keyrings = GetListOfFilesInDir(trustedPath, "gpg", false, true);
66 if (RealFileExists(trustedFile) == true)
67 keyrings.push_back(trustedFile);
68
69 std::vector<const char *> Args;
70 Args.reserve(30);
71
72 if (keyrings.empty() == true)
73 {
74 // TRANSLATOR: %s is the trusted keyring parts directory
99ed26d3
DK
75 ioprintf(std::cerr, _("No keyring installed in %s."),
76 _config->FindDir("Dir::Etc::TrustedParts").c_str());
77 exit(EINTERNAL);
2f5b6151
DK
78 }
79
80 Args.push_back(gpgvpath.c_str());
81 Args.push_back("--ignore-time-conflict");
82
b38bb727 83 char statusfdstr[10];
2f5b6151
DK
84 if (statusfd != -1)
85 {
86 Args.push_back("--status-fd");
2d3fe9cf 87 snprintf(statusfdstr, sizeof(statusfdstr), "%i", statusfd);
b38bb727 88 Args.push_back(statusfdstr);
2f5b6151
DK
89 }
90
2d3fe9cf 91 for (std::vector<std::string>::const_iterator K = keyrings.begin();
2f5b6151
DK
92 K != keyrings.end(); ++K)
93 {
94 Args.push_back("--keyring");
95 Args.push_back(K->c_str());
96 }
97
98 Configuration::Item const *Opts;
99 Opts = _config->Tree("Acquire::gpgv::Options");
100 if (Opts != 0)
101 {
102 Opts = Opts->Child;
103 for (; Opts != 0; Opts = Opts->Next)
104 {
105 if (Opts->Value.empty() == true)
106 continue;
107 Args.push_back(Opts->Value.c_str());
108 }
109 }
110
2d3fe9cf
DK
111 int sigFd = -1;
112 int dataFd = -1;
113 std::vector<std::string> dataHeader;
114 char * sig = NULL;
115 char * data = NULL;
116
117 // file with detached signature
2f5b6151 118 if (FileGPG != File)
2d3fe9cf
DK
119 {
120 Args.push_back(FileGPG.c_str());
2f5b6151 121 Args.push_back(File.c_str());
2d3fe9cf
DK
122 }
123 else // clear-signed file
124 {
125 sig = GenerateTemporaryFileTemplate("apt.sig");
126 data = GenerateTemporaryFileTemplate("apt.data");
127 if (sig == NULL || data == NULL)
128 {
129 ioprintf(std::cerr, "Couldn't create tempfiles for splitting up %s", File.c_str());
130 exit(EINTERNAL);
131 }
132
133 sigFd = mkstemp(sig);
134 dataFd = mkstemp(data);
135 int const duppedSigFd = dup(sigFd);
136 int const duppedDataFd = dup(dataFd);
137
138 if (dataFd == -1 || sigFd == -1 || duppedDataFd == -1 || duppedSigFd == -1 ||
139 SplitClearSignedFile(File, duppedDataFd, &dataHeader, duppedSigFd) == false)
140 {
141 if (dataFd != -1)
142 unlink(sig);
143 if (sigFd != -1)
144 unlink(data);
145 ioprintf(std::cerr, "Splitting up %s into data and signature failed", File.c_str());
146 exit(EINTERNAL);
147 }
148 lseek(dataFd, 0, SEEK_SET);
149 lseek(sigFd, 0, SEEK_SET);
150 Args.push_back(sig);
151 Args.push_back(data);
152 }
153
2f5b6151
DK
154 Args.push_back(NULL);
155
156 if (Debug == true)
157 {
158 std::clog << "Preparing to exec: " << gpgvpath;
159 for (std::vector<const char *>::const_iterator a = Args.begin(); *a != NULL; ++a)
160 std::clog << " " << *a;
161 std::clog << std::endl;
162 }
163
164 if (statusfd != -1)
165 {
166 int const nullfd = open("/dev/null", O_RDONLY);
167 close(fd[0]);
168 // Redirect output to /dev/null; we read from the status fd
b38bb727
DK
169 if (statusfd != STDOUT_FILENO)
170 dup2(nullfd, STDOUT_FILENO);
171 if (statusfd != STDERR_FILENO)
172 dup2(nullfd, STDERR_FILENO);
2f5b6151
DK
173 // Redirect the pipe to the status fd (3)
174 dup2(fd[1], statusfd);
175
176 putenv((char *)"LANG=");
177 putenv((char *)"LC_ALL=");
178 putenv((char *)"LC_MESSAGES=");
179 }
180
2d3fe9cf
DK
181 if (FileGPG != File)
182 {
183 execvp(gpgvpath.c_str(), (char **) &Args[0]);
184 ioprintf(std::cerr, "Couldn't execute %s to check %s", Args[0], File.c_str());
185 exit(EINTERNAL);
186 }
187 else
188 {
189//#define UNLINK_EXIT(X) exit(X)
190#define UNLINK_EXIT(X) unlink(sig);unlink(data);exit(X)
191
192 // for clear-signed files we have created tempfiles we have to clean up
193 // and we do an additional check, so fork yet another time …
194 pid_t pid = ExecFork();
195 if(pid < 0) {
196 ioprintf(std::cerr, "Fork failed for %s to check %s", Args[0], File.c_str());
197 UNLINK_EXIT(EINTERNAL);
198 }
199 if(pid == 0)
200 {
201 if (statusfd != -1)
202 dup2(fd[1], statusfd);
203 execvp(gpgvpath.c_str(), (char **) &Args[0]);
204 ioprintf(std::cerr, "Couldn't execute %s to check %s", Args[0], File.c_str());
205 UNLINK_EXIT(EINTERNAL);
206 }
207
208 // Wait and collect the error code - taken from WaitPid as we need the exact Status
209 int Status;
210 while (waitpid(pid,&Status,0) != pid)
211 {
212 if (errno == EINTR)
213 continue;
214 ioprintf(std::cerr, _("Waited for %s but it wasn't there"), "gpgv");
215 UNLINK_EXIT(EINTERNAL);
216 }
bea263c2
DK
217#undef UNLINK_EXIT
218 // we don't need the files any longer as we have the filedescriptors still open
219 unlink(sig);
220 unlink(data);
221 free(sig);
222 free(data);
2d3fe9cf
DK
223
224 // check if it exit'ed normally …
225 if (WIFEXITED(Status) == false)
226 {
227 ioprintf(std::cerr, _("Sub-process %s exited unexpectedly"), "gpgv");
bea263c2 228 exit(EINTERNAL);
2d3fe9cf
DK
229 }
230
231 // … and with a good exit code
232 if (WEXITSTATUS(Status) != 0)
233 {
234 ioprintf(std::cerr, _("Sub-process %s returned an error code (%u)"), "gpgv", WEXITSTATUS(Status));
bea263c2 235 exit(WEXITSTATUS(Status));
2d3fe9cf
DK
236 }
237
238 /* looks like its fine. Our caller will check the status fd,
239 but we construct a good-known clear-signed file without garbage
240 and other non-sense. In a perfect world, we get the same file,
241 but empty lines, trailing whitespaces and stuff makes it inperfect … */
242 if (RecombineToClearSignedFile(File, dataFd, dataHeader, sigFd) == false)
243 {
244 _error->DumpErrors(std::cerr);
bea263c2 245 exit(EINTERNAL);
2d3fe9cf
DK
246 }
247
248 // everything fine, we have a clean file now!
bea263c2 249 exit(0);
2d3fe9cf
DK
250 }
251 exit(EINTERNAL); // unreachable safe-guard
252}
253 /*}}}*/
254// RecombineToClearSignedFile - combine data/signature to message /*{{{*/
255bool RecombineToClearSignedFile(std::string const &OutFile, int const ContentFile,
256 std::vector<std::string> const &ContentHeader, int const SignatureFile)
257{
258 FILE *clean_file = fopen(OutFile.c_str(), "w");
259 fputs("-----BEGIN PGP SIGNED MESSAGE-----\n", clean_file);
260 for (std::vector<std::string>::const_iterator h = ContentHeader.begin(); h != ContentHeader.end(); ++h)
261 fprintf(clean_file, "%s\n", h->c_str());
262 fputs("\n", clean_file);
263
264 FILE *data_file = fdopen(ContentFile, "r");
265 FILE *sig_file = fdopen(SignatureFile, "r");
266 if (data_file == NULL || sig_file == NULL)
bea263c2
DK
267 {
268 fclose(clean_file);
2d3fe9cf 269 return _error->Error("Couldn't open splitfiles to recombine them into %s", OutFile.c_str());
bea263c2 270 }
2d3fe9cf
DK
271 char *buf = NULL;
272 size_t buf_size = 0;
273 while (getline(&buf, &buf_size, data_file) != -1)
274 fputs(buf, clean_file);
275 fclose(data_file);
276 fputs("\n", clean_file);
277 while (getline(&buf, &buf_size, sig_file) != -1)
278 fputs(buf, clean_file);
279 fclose(sig_file);
280 fclose(clean_file);
281 return true;
2f5b6151
DK
282}
283 /*}}}*/
2d3fe9cf
DK
284// SplitClearSignedFile - split message into data/signature /*{{{*/
285bool SplitClearSignedFile(std::string const &InFile, int const ContentFile,
286 std::vector<std::string> * const ContentHeader, int const SignatureFile)
287{
288 FILE *in = fopen(InFile.c_str(), "r");
289 if (in == NULL)
290 return _error->Errno("fopen", "can not open %s", InFile.c_str());
291
292 FILE *out_content = NULL;
293 FILE *out_signature = NULL;
294 if (ContentFile != -1)
295 {
296 out_content = fdopen(ContentFile, "w");
297 if (out_content == NULL)
bea263c2
DK
298 {
299 fclose(in);
2d3fe9cf 300 return _error->Errno("fdopen", "Failed to open file to write content to from %s", InFile.c_str());
bea263c2 301 }
2d3fe9cf
DK
302 }
303 if (SignatureFile != -1)
304 {
305 out_signature = fdopen(SignatureFile, "w");
306 if (out_signature == NULL)
bea263c2
DK
307 {
308 fclose(in);
309 if (out_content != NULL)
310 fclose(out_content);
2d3fe9cf 311 return _error->Errno("fdopen", "Failed to open file to write signature to from %s", InFile.c_str());
bea263c2 312 }
2d3fe9cf
DK
313 }
314
315 bool found_message_start = false;
316 bool found_message_end = false;
317 bool skip_until_empty_line = false;
318 bool found_signature = false;
319 bool first_line = true;
320
321 char *buf = NULL;
322 size_t buf_size = 0;
323 while (getline(&buf, &buf_size, in) != -1)
324 {
325 _strrstrip(buf);
326 if (found_message_start == false)
327 {
328 if (strcmp(buf, "-----BEGIN PGP SIGNED MESSAGE-----") == 0)
329 {
330 found_message_start = true;
331 skip_until_empty_line = true;
332 }
333 }
334 else if (skip_until_empty_line == true)
335 {
336 if (strlen(buf) == 0)
337 skip_until_empty_line = false;
338 // save "Hash" Armor Headers, others aren't allowed
339 else if (ContentHeader != NULL && strncmp(buf, "Hash: ", strlen("Hash: ")) == 0)
340 ContentHeader->push_back(buf);
341 }
342 else if (found_signature == false)
343 {
344 if (strcmp(buf, "-----BEGIN PGP SIGNATURE-----") == 0)
345 {
346 found_signature = true;
347 found_message_end = true;
348 if (out_signature != NULL)
349 fprintf(out_signature, "%s\n", buf);
350 }
351 else if (found_message_end == false)
352 {
353 // we are in the message block
354 if(first_line == true) // first line does not need a newline
355 {
356 if (out_content != NULL)
357 fprintf(out_content, "%s", buf);
358 first_line = false;
359 }
360 else if (out_content != NULL)
361 fprintf(out_content, "\n%s", buf);
362 }
363 }
364 else if (found_signature == true)
365 {
366 if (out_signature != NULL)
367 fprintf(out_signature, "%s\n", buf);
368 if (strcmp(buf, "-----END PGP SIGNATURE-----") == 0)
369 found_signature = false; // look for other signatures
370 }
371 // all the rest is whitespace, unsigned garbage or additional message blocks we ignore
372 }
373 if (out_content != NULL)
374 fclose(out_content);
375 if (out_signature != NULL)
376 fclose(out_signature);
bea263c2 377 fclose(in);
2d3fe9cf 378
f1828b69
DK
379 if (found_signature == true)
380 return _error->Error("Signature in file %s wasn't closed", InFile.c_str());
381
382 // if we haven't found any of them, this an unsigned file,
383 // so don't generate an error, but splitting was unsuccessful none-the-less
384 if (found_message_start == false && found_message_end == false)
385 return false;
386 // otherwise one missing indicates a syntax error
387 else if (found_message_start == false || found_message_end == false)
388 return _error->Error("Splitting of file %s failed as it doesn't contain all expected parts", InFile.c_str());
389
2d3fe9cf
DK
390 return true;
391}
f1828b69
DK
392 /*}}}*/
393bool OpenMaybeClearSignedFile(std::string const &ClearSignedFileName, FileFd &MessageFile) /*{{{*/
394{
395 char * const message = GenerateTemporaryFileTemplate("fileutl.message");
396 int const messageFd = mkstemp(message);
397 if (messageFd == -1)
398 {
399 free(message);
400 return _error->Errno("mkstemp", "Couldn't create temporary file to work with %s", ClearSignedFileName.c_str());
401 }
402 // we have the fd, thats enough for us
403 unlink(message);
404 free(message);
405
406 int const duppedMsg = dup(messageFd);
407 if (duppedMsg == -1)
408 return _error->Errno("dup", "Couldn't duplicate FD to work with %s", ClearSignedFileName.c_str());
409
410 _error->PushToStack();
411 bool const splitDone = SplitClearSignedFile(ClearSignedFileName.c_str(), messageFd, NULL, -1);
412 bool const errorDone = _error->PendingError();
413 _error->MergeWithStack();
414 if (splitDone == false)
415 {
416 close(duppedMsg);
417
418 if (errorDone == true)
419 return false;
420
421 // we deal with an unsigned file
422 MessageFile.Open(ClearSignedFileName, FileFd::ReadOnly);
423 }
424 else // clear-signed
425 {
426 if (lseek(duppedMsg, 0, SEEK_SET) < 0)
427 return _error->Errno("lseek", "Unable to seek back in message fd for file %s", ClearSignedFileName.c_str());
428 MessageFile.OpenDescriptor(duppedMsg, FileFd::ReadOnly, true);
429 }
430
431 return MessageFile.Failed() == false;
432}
433 /*}}}*/