* merge the remaining Ubuntu change:
[ntk/apt.git] / methods / gpgv.cc
1 #include <apt-pkg/error.h>
2 #include <apt-pkg/acquire-method.h>
3 #include <apt-pkg/strutl.h>
4 #include <apt-pkg/fileutl.h>
5 #include <apti18n.h>
6
7 #include <utime.h>
8 #include <stdio.h>
9 #include <fcntl.h>
10 #include <errno.h>
11 #include <sys/wait.h>
12 #include <iostream>
13 #include <sstream>
14
15 #include <vector>
16
17 #define GNUPGPREFIX "[GNUPG:]"
18 #define GNUPGBADSIG "[GNUPG:] BADSIG"
19 #define GNUPGNOPUBKEY "[GNUPG:] NO_PUBKEY"
20 #define GNUPGVALIDSIG "[GNUPG:] VALIDSIG"
21 #define GNUPGGOODSIG "[GNUPG:] GOODSIG"
22 #define GNUPGKEYEXPIRED "[GNUPG:] KEYEXPIRED"
23 #define GNUPGREVKEYSIG "[GNUPG:] REVKEYSIG"
24 #define GNUPGNODATA "[GNUPG:] NODATA"
25
26 class GPGVMethod : public pkgAcqMethod
27 {
28 private:
29 string VerifyGetSigners(const char *file, const char *outfile,
30 vector<string> &GoodSigners,
31 vector<string> &BadSigners,
32 vector<string> &WorthlessSigners,
33 vector<string> &NoPubKeySigners);
34
35 protected:
36 virtual bool Fetch(FetchItem *Itm);
37
38 public:
39
40 GPGVMethod() : pkgAcqMethod("1.0",SingleInstance | SendConfig) {};
41 };
42
43 string GPGVMethod::VerifyGetSigners(const char *file, const char *outfile,
44 vector<string> &GoodSigners,
45 vector<string> &BadSigners,
46 vector<string> &WorthlessSigners,
47 vector<string> &NoPubKeySigners)
48 {
49 bool const Debug = _config->FindB("Debug::Acquire::gpgv", false);
50 // setup a (empty) stringstream for formating the return value
51 std::stringstream ret;
52 ret.str("");
53
54 if (Debug == true)
55 std::clog << "inside VerifyGetSigners" << std::endl;
56
57 pid_t pid;
58 int fd[2];
59 FILE *pipein;
60 int status;
61 string const gpgvpath = _config->Find("Dir::Bin::gpg", "/usr/bin/gpgv");
62 // FIXME: remove support for deprecated APT::GPGV setting
63 string const trustedFile = _config->FindFile("Dir::Etc::Trusted",
64 _config->Find("APT::GPGV::TrustedKeyring", "/etc/apt/trusted.gpg").c_str());
65 string const trustedPath = _config->FindDir("Dir::Etc::TrustedParts", "/etc/apt/trusted.gpg.d");
66 if (Debug == true)
67 {
68 std::clog << "gpgv path: " << gpgvpath << std::endl;
69 std::clog << "Keyring file: " << trustedFile << std::endl;
70 std::clog << "Keyring path: " << trustedPath << std::endl;
71 }
72
73 vector<string> keyrings = GetListOfFilesInDir(trustedPath, "gpg", false);
74 if (FileExists(trustedFile) == true)
75 keyrings.push_back(trustedFile);
76
77 if (keyrings.empty() == true)
78 {
79 // TRANSLATOR: %s is the trusted keyring parts directory
80 ioprintf(ret, _("No keyring installed in %s."), trustedPath.c_str());
81 return ret.str();
82 }
83
84 if (pipe(fd) < 0)
85 return "Couldn't create pipe";
86
87 pid = fork();
88 if (pid < 0)
89 return string("Couldn't spawn new process") + strerror(errno);
90 else if (pid == 0)
91 {
92 std::vector<const char *> Args;
93 Args.reserve(30);
94
95 Args.push_back(gpgvpath.c_str());
96 Args.push_back("--status-fd");
97 Args.push_back("3");
98 Args.push_back("--ignore-time-conflict");
99 for (vector<string>::const_iterator K = keyrings.begin();
100 K != keyrings.end(); ++K)
101 {
102 Args.push_back("--keyring");
103 Args.push_back(K->c_str());
104 }
105
106 Configuration::Item const *Opts;
107 Opts = _config->Tree("Acquire::gpgv::Options");
108 if (Opts != 0)
109 {
110 Opts = Opts->Child;
111 for (; Opts != 0; Opts = Opts->Next)
112 {
113 if (Opts->Value.empty() == true)
114 continue;
115 Args.push_back(Opts->Value.c_str());
116 }
117 }
118 Args.push_back(file);
119 Args.push_back(outfile);
120 Args.push_back(NULL);
121
122 if (Debug == true)
123 {
124 std::clog << "Preparing to exec: " << gpgvpath;
125 for(std::vector<const char *>::const_iterator a = Args.begin();*a != NULL; ++a)
126 std::clog << " " << *a;
127 std::clog << std::endl;
128 }
129 int const nullfd = open("/dev/null", O_RDONLY);
130 close(fd[0]);
131 // Redirect output to /dev/null; we read from the status fd
132 dup2(nullfd, STDOUT_FILENO);
133 dup2(nullfd, STDERR_FILENO);
134 // Redirect the pipe to the status fd (3)
135 dup2(fd[1], 3);
136
137 putenv((char *)"LANG=");
138 putenv((char *)"LC_ALL=");
139 putenv((char *)"LC_MESSAGES=");
140 execvp(gpgvpath.c_str(), (char **) &Args[0]);
141
142 exit(111);
143 }
144 close(fd[1]);
145
146 pipein = fdopen(fd[0], "r");
147
148 // Loop over the output of gpgv, and check the signatures.
149 size_t buffersize = 64;
150 char *buffer = (char *) malloc(buffersize);
151 size_t bufferoff = 0;
152 while (1)
153 {
154 int c;
155
156 // Read a line. Sigh.
157 while ((c = getc(pipein)) != EOF && c != '\n')
158 {
159 if (bufferoff == buffersize)
160 buffer = (char *) realloc(buffer, buffersize *= 2);
161 *(buffer+bufferoff) = c;
162 bufferoff++;
163 }
164 if (bufferoff == 0 && c == EOF)
165 break;
166 *(buffer+bufferoff) = '\0';
167 bufferoff = 0;
168 if (Debug == true)
169 std::clog << "Read: " << buffer << std::endl;
170
171 // Push the data into three separate vectors, which
172 // we later concatenate. They're kept separate so
173 // if we improve the apt method communication stuff later
174 // it will be better.
175 if (strncmp(buffer, GNUPGBADSIG, sizeof(GNUPGBADSIG)-1) == 0)
176 {
177 if (Debug == true)
178 std::clog << "Got BADSIG! " << std::endl;
179 BadSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
180 }
181
182 if (strncmp(buffer, GNUPGNOPUBKEY, sizeof(GNUPGNOPUBKEY)-1) == 0)
183 {
184 if (Debug == true)
185 std::clog << "Got NO_PUBKEY " << std::endl;
186 NoPubKeySigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
187 }
188 if (strncmp(buffer, GNUPGNODATA, sizeof(GNUPGBADSIG)-1) == 0)
189 {
190 if (Debug == true)
191 std::clog << "Got NODATA! " << std::endl;
192 BadSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
193 }
194 if (strncmp(buffer, GNUPGKEYEXPIRED, sizeof(GNUPGKEYEXPIRED)-1) == 0)
195 {
196 if (Debug == true)
197 std::clog << "Got KEYEXPIRED! " << std::endl;
198 WorthlessSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
199 }
200 if (strncmp(buffer, GNUPGREVKEYSIG, sizeof(GNUPGREVKEYSIG)-1) == 0)
201 {
202 if (Debug == true)
203 std::clog << "Got REVKEYSIG! " << std::endl;
204 WorthlessSigners.push_back(string(buffer+sizeof(GNUPGPREFIX)));
205 }
206 if (strncmp(buffer, GNUPGGOODSIG, sizeof(GNUPGGOODSIG)-1) == 0)
207 {
208 char *sig = buffer + sizeof(GNUPGPREFIX);
209 char *p = sig + sizeof("GOODSIG");
210 while (*p && isxdigit(*p))
211 p++;
212 *p = 0;
213 if (Debug == true)
214 std::clog << "Got GOODSIG, key ID:" << sig << std::endl;
215 GoodSigners.push_back(string(sig));
216 }
217 }
218 fclose(pipein);
219
220 waitpid(pid, &status, 0);
221 if (Debug == true)
222 {
223 std::clog << "gpgv exited\n";
224 }
225
226 if (WEXITSTATUS(status) == 0)
227 {
228 if (GoodSigners.empty())
229 return _("Internal error: Good signature, but could not determine key fingerprint?!");
230 return "";
231 }
232 else if (WEXITSTATUS(status) == 1)
233 {
234 return _("At least one invalid signature was encountered.");
235 }
236 else if (WEXITSTATUS(status) == 111)
237 {
238 ioprintf(ret, _("Could not execute '%s' to verify signature (is gpgv installed?)"), gpgvpath.c_str());
239 return ret.str();
240 }
241 else
242 {
243 return _("Unknown error executing gpgv");
244 }
245 }
246
247 bool GPGVMethod::Fetch(FetchItem *Itm)
248 {
249 URI Get = Itm->Uri;
250 string Path = Get.Host + Get.Path; // To account for relative paths
251 string keyID;
252 vector<string> GoodSigners;
253 vector<string> BadSigners;
254 // a worthless signature is a expired or revoked one
255 vector<string> WorthlessSigners;
256 vector<string> NoPubKeySigners;
257
258 FetchResult Res;
259 Res.Filename = Itm->DestFile;
260 URIStart(Res);
261
262 // Run gpgv on file, extract contents and get the key ID of the signer
263 string msg = VerifyGetSigners(Path.c_str(), Itm->DestFile.c_str(),
264 GoodSigners, BadSigners, WorthlessSigners,
265 NoPubKeySigners);
266 if (GoodSigners.empty() || !BadSigners.empty() || !NoPubKeySigners.empty())
267 {
268 string errmsg;
269 // In this case, something bad probably happened, so we just go
270 // with what the other method gave us for an error message.
271 if (BadSigners.empty() && WorthlessSigners.empty() && NoPubKeySigners.empty())
272 errmsg = msg;
273 else
274 {
275 if (!BadSigners.empty())
276 {
277 errmsg += _("The following signatures were invalid:\n");
278 for (vector<string>::iterator I = BadSigners.begin();
279 I != BadSigners.end(); I++)
280 errmsg += (*I + "\n");
281 }
282 if (!WorthlessSigners.empty())
283 {
284 errmsg += _("The following signatures were invalid:\n");
285 for (vector<string>::iterator I = WorthlessSigners.begin();
286 I != WorthlessSigners.end(); I++)
287 errmsg += (*I + "\n");
288 }
289 if (!NoPubKeySigners.empty())
290 {
291 errmsg += _("The following signatures couldn't be verified because the public key is not available:\n");
292 for (vector<string>::iterator I = NoPubKeySigners.begin();
293 I != NoPubKeySigners.end(); I++)
294 errmsg += (*I + "\n");
295 }
296 }
297 // this is only fatal if we have no good sigs or if we have at
298 // least one bad signature. good signatures and NoPubKey signatures
299 // happen easily when a file is signed with multiple signatures
300 if(GoodSigners.empty() or !BadSigners.empty())
301 return _error->Error("%s", errmsg.c_str());
302 }
303
304 // Just pass the raw output up, because passing it as a real data
305 // structure is too difficult with the method stuff. We keep it
306 // as three separate vectors for future extensibility.
307 Res.GPGVOutput = GoodSigners;
308 Res.GPGVOutput.insert(Res.GPGVOutput.end(),BadSigners.begin(),BadSigners.end());
309 Res.GPGVOutput.insert(Res.GPGVOutput.end(),NoPubKeySigners.begin(),NoPubKeySigners.end());
310 URIDone(Res);
311
312 if (_config->FindB("Debug::Acquire::gpgv", false))
313 {
314 std::clog << "gpgv succeeded\n";
315 }
316
317 return true;
318 }
319
320
321 int main()
322 {
323 setlocale(LC_ALL, "");
324
325 GPGVMethod Mth;
326
327 return Mth.Run();
328 }