- initial import of revision 374 from cnc
[apt.git] / methods / gpg.cc
1
2 #include <apt-pkg/error.h>
3 #include <apt-pkg/acquire-method.h>
4 #include <apt-pkg/strutl.h>
5 #include <apt-pkg/fileutl.h>
6
7 #include <sys/stat.h>
8 #include <unistd.h>
9 #include <utime.h>
10 #include <stdio.h>
11 #include <sys/wait.h>
12
13 #include <apti18n.h>
14
15 class GPGMethod : public pkgAcqMethod
16 {
17    virtual bool Fetch(FetchItem *Itm);
18    
19  public:
20    
21    GPGMethod() : pkgAcqMethod("1.0",SingleInstance | SendConfig) {};
22 };
23
24
25 #define STRCMP(buf, conststr) strncmp(buf, conststr, sizeof(conststr)-1)
26 /*
27  * extractSignedFile - Extract parts of a gpg signed file in the format
28  *         described below.
29  *    file: The file to extract
30  *    targetPrefix: Prefix of the extracted files, containing the directory
31  *       name and prefix of the filename. Directory must not be writable
32  *       by anyone else and must be empty.
33  *    targetFile: Path of the file where the signed data will be stored.
34  *    oldStyle: Set to true if the filename is not in the format specified
35  *       below and is probably an armoured signed file.
36  *    sigCount: Number of signatures found in the file.
37  * 
38  * Returns false if there was an error processing the file.
39  * 
40  * Extracted signatures will be named
41  * targetPrefix+".sig"{1,2,...,sigCount}
42  * 
43  * File format for the signed file is:
44  *<BOF>
45  * <ascii data that was signed by gpg>
46  * -----BEGIN PGP SIGNATURE-----\n
47  * <gpg signature data>
48  * -----END PGP SIGNATURE-----\n
49  * <Repeat the above n times, depending
50  * on how many people have signed the file.>
51  * <EOF>
52  * 
53  * Ie: the original file cat'enated with the signatures generated
54  * through gpg -s --armor --detach <yourfile>
55  */
56 bool extractSignedFile(string file, string targetPrefix, string targetFile,
57                        bool &oldStyle, int &sigCount)
58 {
59    FILE *fin;
60    FILE *fout;
61    char buffer[256];
62    string tmps;
63
64    fin = fopen(file.c_str(), "r");
65    if (fin == NULL)
66       return _error->Errno("open", "could not open gpg signed file %s",
67                            file.c_str());
68    
69    oldStyle = false;
70
71    bool Failed = false;
72
73    // store the signed file in a separate file
74    tmps = targetFile;
75
76    fout = fopen(tmps.c_str(), "w+");
77    if (fout == NULL)
78    {
79       fclose(fin);
80       return _error->Errno("fopen", "could not create file %s",
81                            tmps.c_str());
82    }
83    while (1)
84    {
85       if (fgets(buffer, sizeof(buffer)-1, fin) == NULL)
86       {
87          Failed = true;
88          _error->Error("no signatures in file %s", file.c_str());
89          break; 
90       }
91
92       if (STRCMP(buffer, "-----BEGIN") == 0)
93          break;
94
95       if (fputs(buffer, fout) < 0)
96       {
97          Failed = true;
98          _error->Errno("fputs", "error writing to %s", tmps.c_str());
99          break;
100       }
101    }
102    fclose(fout);
103
104    if (Failed) 
105    {
106       fclose(fin);
107       return false;
108    }
109    
110    sigCount = 0;
111    // now store each of the signatures in a file, separately
112    while (1) 
113    {
114       char buf[32];
115
116       if (STRCMP(buffer, "-----BEGIN PGP SIGNATURE-----") != 0)
117       {
118          Failed = true;
119          _error->Error("unexpected data in gpg signed file %s",
120                        tmps.c_str());
121          break;
122       }
123       
124       sigCount++;
125       snprintf(buf, sizeof(buf), "%i", sigCount);
126
127       tmps = targetPrefix+"sig"+string(buf);
128       
129       fout = fopen(tmps.c_str(), "w+");
130       if (fout == NULL) 
131       {
132          fclose(fin);
133          return _error->Errno("fopen", "could not create signature file %s",
134                               tmps.c_str());
135       }
136       while (1)
137       {
138          if (fputs(buffer, fout) < 0)
139          {
140             Failed = true;
141             _error->Errno("fputs", "error writing to %s", tmps.c_str());
142             break;
143          }
144
145          if (STRCMP(buffer, "-----END PGP SIGNATURE-----") == 0)
146             break;
147
148          if (fgets(buffer, sizeof(buffer)-1, fin) == NULL)
149          {
150             Failed = true;
151             _error->Errno("fgets", "error reading from %s", file.c_str());
152             break;       
153          }
154       }
155       fclose(fout);
156
157       if (Failed) 
158       {
159          fclose(fin);
160          return false;
161       }
162       
163       if (fgets(buffer, sizeof(buffer)-1, fin) == NULL)
164          break;
165       
166       if (buffer[0] == '\n')
167          break;      
168    }
169
170    fclose(fin);
171
172    return true;
173 }
174 #undef STRCMP
175
176
177
178 char *getFileSigner(const char *file, const char *sigfile,
179                     const char *outfile, string &signerKeyID)
180 {
181    pid_t pid;
182    int fd[2];
183    char buffer[1024];
184    FILE *f;
185    char keyid[64];
186    int status;
187    bool goodsig = false;
188    bool badsig = false;
189
190    if (pipe(fd) < 0)
191       return "could not create pipe";  
192
193    pid = fork();
194    if (pid < 0)
195       return "could not spawn new process";
196    else if (pid == 0) 
197    {
198       string path = _config->Find("Dir::Bin::gpg", "/usr/bin/gpg");
199       string pubring = "";
200       const char *argv[16];
201       int argc = 0;
202       
203       close(fd[0]);
204       close(STDERR_FILENO);
205       close(STDOUT_FILENO);
206       dup2(fd[1], STDOUT_FILENO);
207       dup2(fd[1], STDERR_FILENO);
208       
209       unsetenv("LANG");
210       unsetenv("LC_ALL");
211       unsetenv("LC_MESSAGES");
212
213       argv[argc++] = "gpg";
214       argv[argc++] = "--batch";
215       argv[argc++] = "--no-secmem-warning";
216       pubring = _config->Find("APT::GPG::Pubring");
217       if (pubring.empty() == false)
218       {
219          argv[argc++] = "--keyring"; argv[argc++] = pubring.c_str();
220       }
221       argv[argc++] = "--status-fd"; argv[argc++] = "2";
222       
223       if (outfile != NULL)
224       {
225          argv[argc++] = "-o"; argv[argc++] = outfile;
226       }
227       else
228       {
229          argv[argc++] = "--verify"; argv[argc++] = sigfile;
230       }
231       argv[argc++] = file;
232       argv[argc] = NULL;
233       
234       execvp(path.c_str(), (char**)argv);
235       
236       exit(111);
237    }
238    close(fd[1]);
239    keyid[0] = 0;
240    goodsig = false;
241    
242    f = fdopen(fd[0], "r");
243    
244    while (1) {
245       char *ptr, *ptr1;
246       
247       if (!fgets(buffer, 1024, f))
248          break;
249       
250       if (goodsig && keyid[0])
251          continue;     
252       
253 #define SIGPACK "[GNUPG:] VALIDSIG"
254       if ((ptr1 = strstr(buffer, SIGPACK)) != NULL) 
255       {
256          char *sig;
257          ptr = sig = ptr1 + sizeof(SIGPACK);
258          while (isxdigit(*ptr) && (ptr-sig) < sizeof(keyid)) ptr++;
259          *ptr = 0;
260          strcpy(keyid, sig);
261       }
262 #undef SIGPACK
263       
264 #define GOODSIG "[GNUPG:] GOODSIG"
265       if ((ptr1 = strstr(buffer, GOODSIG)) != NULL)
266          goodsig = true;
267 #undef GOODSIG
268
269 #define BADSIG "[GNUPG:] BADSIG"
270       if ((ptr1 = strstr(buffer, BADSIG)) != NULL)
271          badsig = true;
272 #undef BADSIG
273    }
274    fclose(f);
275    
276    waitpid(pid, &status, 0);
277
278    if (WEXITSTATUS(status) == 0) 
279    {
280       signerKeyID = string(keyid);
281       return NULL;
282    }
283    else if (WEXITSTATUS(status) == 111) 
284    {
285       return "Could not execute gpg to verify signature";
286    }
287    else 
288    {
289       if (badsig)
290          return "File has bad signature, it might have been corrupted or tampered.";
291
292       if (!keyid[0] || !goodsig)
293          return "File was not signed with a known key. Check if the proper gpg key was imported to your keyring.";
294       
295       return "File could not be authenticated";
296    }
297 }
298
299
300 bool makeTmpDir(string dir, string &path)
301 {
302    char *buf;
303
304    path = dir+"/apt-gpg.XXXXXX";
305    buf = new char[path.length()+1];
306    if (buf == NULL)
307       return _error->Error(_("Could not allocate memory"));
308    strcpy(buf, path.c_str());
309
310    if (mkdtemp(buf) == NULL)
311       return _error->Errno("mkdtemp", _("Could not create temporary directory"));
312    path = buf;
313
314    delete [] buf;
315
316    return true;
317 }
318
319
320 void removeTmpDir(string path, int sigCount)
321 {
322    while (sigCount > 0)
323    {
324       char buf[32];
325       snprintf(buf, sizeof(buf)-1, "%i", sigCount--);
326       unlink(string(path+"/sig"+string(buf)).c_str());
327    }
328    
329    rmdir(path.c_str());
330 }
331
332
333
334
335 bool GPGMethod::Fetch(FetchItem *Itm)
336 {
337    URI Get = Itm->Uri;
338    string Path = Get.Host + Get.Path; // To account for relative paths
339    string KeyList;
340
341    FetchResult Res;
342    Res.Filename = Itm->DestFile;
343    URIStart(Res);
344    
345    string TempDir;
346    const char *SysTempDir;
347
348    SysTempDir = getenv("TMPDIR");
349    if (SysTempDir == NULL || !FileExists(SysTempDir)) {
350       SysTempDir = getenv("TMP");
351       if (SysTempDir == NULL || !FileExists(SysTempDir))
352          SysTempDir = "/tmp";
353    }
354    if (makeTmpDir(SysTempDir, TempDir) == false)
355       return false;
356    
357    int SigCount = 0;
358    bool OldStyle = true;
359
360    if (extractSignedFile(Path, TempDir+"/", Itm->DestFile, OldStyle,
361                          SigCount) == false)
362       return false;
363
364    if (OldStyle == true) 
365    {
366       // Run GPG on file, extract contents and get the key ID of the signer
367       char *msg = getFileSigner(Path.c_str(), NULL,
368                                 Itm->DestFile.c_str(), KeyList);
369       if (msg != NULL) 
370       {
371          removeTmpDir(TempDir, SigCount);
372          return _error->Error(msg);
373       }
374    }
375    else 
376    {
377       char *msg;
378       int i;
379       char buf[32];
380       string KeyID;
381       
382       // Check fingerprint for each signature
383       for (i = 1; i <= SigCount; i++) 
384       {
385          snprintf(buf, sizeof(buf)-1, "%i", i);
386          
387          string SigFile = TempDir+"/sig"+string(buf);
388
389          
390          // Run GPG on file and get the key ID of the signer
391          msg = getFileSigner(Itm->DestFile.c_str(), SigFile.c_str(), 
392                              NULL, KeyID);
393          if (msg != NULL)
394          {
395             removeTmpDir(TempDir, SigCount);           
396             return _error->Error(msg);
397          }
398          if (KeyList.empty())
399             KeyList = KeyID;
400          else
401             KeyList = KeyList+","+KeyID;
402       }
403    }
404    
405    removeTmpDir(TempDir, SigCount);
406    
407    // Transfer the modification times
408    struct stat Buf;
409    if (stat(Path.c_str(),&Buf) != 0)
410       return _error->Errno("stat","Failed to stat %s", Path.c_str());
411    
412    struct utimbuf TimeBuf;
413    TimeBuf.actime = Buf.st_atime;
414    TimeBuf.modtime = Buf.st_mtime;
415    if (utime(Itm->DestFile.c_str(),&TimeBuf) != 0)
416       return _error->Errno("utime","Failed to set modification time");
417    
418    if (stat(Itm->DestFile.c_str(),&Buf) != 0)
419       return _error->Errno("stat","Failed to stat %s", Itm->DestFile.c_str());
420    
421    // Return a Done response
422    Res.LastModified = Buf.st_mtime;
423    Res.Size = Buf.st_size;
424    Res.SignatureFP = KeyList;
425    URIDone(Res);
426
427    return true;
428 }
429
430
431 int main()
432 {
433    GPGMethod Mth;
434
435    return Mth.Run();
436 }