ee8dc082ec952129bc6352176a79897735f3f9e5
[oweals/opkg-lede.git] / libopkg / opkg_download.c
1 /* vi: set noexpandtab sw=4 sts=4: */
2 /* opkg_download.c - the opkg package management system
3
4    Carl D. Worth
5
6    Copyright (C) 2001 University of Southern California
7    Copyright (C) 2008 OpenMoko Inc
8
9    This program is free software; you can redistribute it and/or
10    modify it under the terms of the GNU General Public License as
11    published by the Free Software Foundation; either version 2, or (at
12    your option) any later version.
13
14    This program is distributed in the hope that it will be useful, but
15    WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17    General Public License for more details.
18 */
19 #include "config.h"
20 #ifdef HAVE_CURL
21 #include <curl/curl.h>
22 #endif
23 #if defined(HAVE_GPGME)
24 #include <gpgme.h>
25 #elif defined(HAVE_OPENSSL)
26 #include <openssl/bio.h>
27 #include <openssl/err.h>
28 #include <openssl/evp.h>
29 #include <openssl/objects.h>
30 #include <openssl/x509.h>
31 #include <openssl/pem.h>
32 #include <openssl/hmac.h>
33
34 #endif
35
36 #include "includes.h"
37 #include "opkg_download.h"
38 #include "opkg_message.h"
39 #include "opkg_state.h"
40
41 #include "sprintf_alloc.h"
42 #include "xsystem.h"
43 #include "file_util.h"
44 #include "str_util.h"
45 #include "opkg_defines.h"
46
47
48 #ifdef HAVE_OPENSSL
49 static X509_STORE *setup_verify(opkg_conf_t *conf, char *CAfile, char *CApath);
50 static void init_openssl(void);
51 #endif
52
53 int opkg_download(opkg_conf_t *conf, const char *src,
54   const char *dest_file_name, curl_progress_func cb, void *data)
55 {
56     int err = 0;
57
58     char *src_basec = strdup(src);
59     char *src_base = basename(src_basec);
60     char *tmp_file_location;
61
62     opkg_message(conf,OPKG_NOTICE,"Downloading %s\n", src);
63         
64     if (str_starts_with(src, "file:")) {
65         int ret;
66         const char *file_src = src + 5;
67         opkg_message(conf,OPKG_INFO,"Copying %s to %s...", file_src, dest_file_name);
68         ret = file_copy(src + 5, dest_file_name);
69         opkg_message(conf,OPKG_INFO,"Done\n");
70         free(src_basec);
71         return ret;
72     }
73
74     sprintf_alloc(&tmp_file_location, "%s/%s", conf->tmp_dir, src_base);
75     err = unlink(tmp_file_location);
76     if (err && errno != ENOENT) {
77         opkg_message(conf,OPKG_ERROR, "%s: ERROR: failed to unlink %s: %s\n",
78                 __FUNCTION__, tmp_file_location, strerror(errno));
79         free(tmp_file_location);
80         free(src_basec);
81         return errno;
82     }
83
84     if (conf->http_proxy) {
85         opkg_message(conf,OPKG_DEBUG,"Setting environment variable: http_proxy = %s\n", conf->http_proxy);
86         setenv("http_proxy", conf->http_proxy, 1);
87     }
88     if (conf->ftp_proxy) {
89         opkg_message(conf,OPKG_DEBUG,"Setting environment variable: ftp_proxy = %s\n", conf->ftp_proxy);
90         setenv("ftp_proxy", conf->ftp_proxy, 1);
91     }
92     if (conf->no_proxy) {
93         opkg_message(conf,OPKG_DEBUG,"Setting environment variable: no_proxy = %s\n", conf->no_proxy);
94         setenv("no_proxy", conf->no_proxy, 1);
95     }
96
97 #ifdef HAVE_CURL
98     CURL *curl;
99     CURLcode res;
100     FILE * file = fopen (tmp_file_location, "w");
101
102     curl = curl_easy_init ();
103     if (curl)
104     {
105         curl_easy_setopt (curl, CURLOPT_URL, src);
106         curl_easy_setopt (curl, CURLOPT_WRITEDATA, file);
107         curl_easy_setopt (curl, CURLOPT_NOPROGRESS, (cb == NULL));
108         if (cb)
109         {
110                 curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, data);
111                 curl_easy_setopt (curl, CURLOPT_PROGRESSFUNCTION, cb);
112         }
113         curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, 1);
114         curl_easy_setopt (curl, CURLOPT_FAILONERROR, 1);
115         if (conf->http_proxy || conf->ftp_proxy)
116         {
117             char *userpwd;
118             sprintf_alloc (&userpwd, "%s:%s", conf->proxy_user,
119                     conf->proxy_passwd);
120             curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, userpwd);
121             free (userpwd);
122         }
123         res = curl_easy_perform (curl);
124         fclose (file);
125         if (res)
126         {
127             long error_code;
128             curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &error_code);
129             opkg_message(conf, OPKG_ERROR, "Failed to download %s. \nerror detail: %s\n", src, curl_easy_strerror(res));
130             free(tmp_file_location);
131             free(src_basec);
132             curl_easy_cleanup (curl);
133             return res;
134         }
135         curl_easy_cleanup (curl);
136
137     }
138     else
139     {
140         free(tmp_file_location);
141         free(src_basec);
142         return -1;
143     }
144 #else
145     {
146       int res;
147       char *wgetcmd;
148       char *wgetopts;
149       wgetopts = getenv("OPKG_WGETOPTS");
150       sprintf_alloc(&wgetcmd, "wget -q %s%s -O \"%s\" \"%s\"",
151                     (conf->http_proxy || conf->ftp_proxy) ? "-Y on " : "",
152                     (wgetopts!=NULL) ? wgetopts : "",
153                     tmp_file_location, src);
154       opkg_message(conf, OPKG_INFO, "Executing: %s\n", wgetcmd);
155       res = xsystem(wgetcmd);
156       free(wgetcmd);
157       if (res) {
158         opkg_message(conf, OPKG_ERROR, "Failed to download %s, error %d\n", src, res);
159         free(tmp_file_location);
160         free(src_basec);
161         return res;
162       }
163     }
164 #endif
165
166     err = file_move(tmp_file_location, dest_file_name);
167
168     free(tmp_file_location);
169     free(src_basec);
170
171     if (err) {
172         return err;
173     }
174
175     return 0;
176 }
177
178 static int opkg_download_cache(opkg_conf_t *conf, const char *src,
179   const char *dest_file_name, curl_progress_func cb, void *data)
180 {
181     char *cache_name = strdup(src);
182     char *cache_location, *p;
183     int err = 0;
184
185     if (!conf->cache || str_starts_with(src, "file:")) {
186         err = opkg_download(conf, src, dest_file_name, cb, data);
187         goto out1;
188     }
189
190     for (p = cache_name; *p; p++)
191         if (*p == '/')
192             *p = ',';   /* looks nicer than | or # */
193
194     sprintf_alloc(&cache_location, "%s/%s", conf->cache, cache_name);
195     if (file_exists(cache_location))
196         opkg_message(conf, OPKG_NOTICE, "Copying %s\n", cache_location);
197     else {
198         err = opkg_download(conf, src, cache_location, cb, data);
199         if (err) {
200             (void) unlink(cache_location);
201             goto out2;
202         }
203     }
204
205     err = file_copy(cache_location, dest_file_name);
206
207
208 out2:
209     free(cache_location);
210 out1:
211     free(cache_name);
212     return err;
213 }
214
215 int opkg_download_pkg(opkg_conf_t *conf, pkg_t *pkg, const char *dir)
216 {
217     int err;
218     char *url;
219     char *pkgid;
220     char *stripped_filename;
221
222     if (pkg->src == NULL) {
223         opkg_message(conf,OPKG_ERROR, "ERROR: Package %s (parent %s) is not available from any configured src.\n",
224                 pkg->name, pkg->parent->name);
225         return -1;
226     }
227     if (pkg->filename == NULL) {
228         opkg_message(conf,OPKG_ERROR, "ERROR: Package %s (parent %s) does not have a valid filename field.\n",pkg->name, pkg->parent->name);
229         return -1;
230     }
231
232     sprintf_alloc (&pkgid, "%s;%s;%s;", pkg->name, pkg->version, pkg->architecture);
233     opkg_set_current_state (conf, OPKG_STATE_DOWNLOADING_PKG, pkgid);
234     free (pkgid);
235
236     sprintf_alloc(&url, "%s/%s", pkg->src->value, pkg->filename);
237
238     /* XXX: BUG: The pkg->filename might be something like
239        "../../foo.opk". While this is correct, and exactly what we
240        want to use to construct url above, here we actually need to
241        use just the filename part, without any directory. */
242
243     stripped_filename = strrchr(pkg->filename, '/');
244     if ( ! stripped_filename )
245         stripped_filename = pkg->filename;
246
247     sprintf_alloc(&pkg->local_filename, "%s/%s", dir, stripped_filename);
248
249     err = opkg_download_cache(conf, url, pkg->local_filename, NULL, NULL);
250     free(url);
251
252     opkg_set_current_state (conf, OPKG_STATE_NONE, NULL);
253     return err;
254 }
255
256 /*
257  * Downloads file from url, installs in package database, return package name. 
258  */
259 int opkg_prepare_url_for_install(opkg_conf_t *conf, const char *url, char **namep)
260 {
261      int err = 0;
262      pkg_t *pkg;
263      pkg = pkg_new();
264      if (pkg == NULL)
265           return ENOMEM;
266
267      if (str_starts_with(url, "http://")
268          || str_starts_with(url, "ftp://")) {
269           char *tmp_file;
270           char *file_basec = strdup(url);
271           char *file_base = basename(file_basec);
272
273           sprintf_alloc(&tmp_file, "%s/%s", conf->tmp_dir, file_base);
274           err = opkg_download(conf, url, tmp_file, NULL, NULL);
275           if (err)
276                return err;
277
278           err = pkg_init_from_file(pkg, tmp_file);
279           if (err)
280                return err;
281
282           free(tmp_file);
283           free(file_basec);
284
285      } else if (strcmp(&url[strlen(url) - 4], OPKG_PKG_EXTENSION) == 0
286                 || strcmp(&url[strlen(url) - 4], IPKG_PKG_EXTENSION) == 0
287                 || strcmp(&url[strlen(url) - 4], DPKG_PKG_EXTENSION) == 0) {
288
289           err = pkg_init_from_file(pkg, url);
290           if (err)
291                return err;
292           opkg_message(conf, OPKG_DEBUG2, "Package %s provided by hand (%s).\n", pkg->name,pkg->local_filename);
293           pkg->provided_by_hand = 1;
294
295      } else {
296        pkg_deinit(pkg);
297        free(pkg);
298        return 0;
299      }
300
301      if (!pkg->architecture) {
302           opkg_message(conf, OPKG_ERROR, "Package %s has no Architecture defined.\n", pkg->name);
303           return -EINVAL;
304      }
305
306      pkg->dest = conf->default_dest;
307      pkg->state_want = SW_INSTALL;
308      pkg->state_flag |= SF_PREFER;
309      pkg = hash_insert_pkg(&conf->pkg_hash, pkg, 1,conf);  
310      if ( pkg == NULL ){
311         fprintf(stderr, "%s : This should never happen. Report this Bug in bugzilla please \n ",__FUNCTION__);
312         return 0;
313      }
314      if (namep) {
315           *namep = strdup(pkg->name);
316      }
317      return 0;
318 }
319
320 int
321 opkg_verify_file (opkg_conf_t *conf, char *text_file, char *sig_file)
322 {
323 #if defined HAVE_GPGME
324     if (conf->check_signature == 0 )
325         return 0;
326     int status = -1;
327     gpgme_ctx_t ctx;
328     gpgme_data_t sig, text, key;
329     gpgme_error_t err = -1;
330     gpgme_verify_result_t result;
331     gpgme_signature_t s;
332     char *trusted_path = NULL;
333     
334     err = gpgme_new (&ctx);
335
336     if (err)
337         return -1;
338
339     sprintf_alloc(&trusted_path, "%s/%s", conf->offline_root, "/etc/opkg/trusted.gpg");
340     err = gpgme_data_new_from_file (&key, trusted_path, 1); 
341     free (trusted_path);
342     if (err)
343     {
344       return -1;
345     }
346     err = gpgme_op_import (ctx, key);
347     if (err)
348     {
349       gpgme_data_release (key);
350       return -1;
351     }
352     gpgme_data_release (key);
353
354     err = gpgme_data_new_from_file (&sig, sig_file, 1); 
355     if (err)
356     {
357         gpgme_release (ctx);
358         return -1;
359     }
360
361     err = gpgme_data_new_from_file (&text, text_file, 1); 
362     if (err)
363     {
364         gpgme_data_release (sig);
365         gpgme_release (ctx);
366         return -1;
367     }
368
369     err = gpgme_op_verify (ctx, sig, text, NULL);
370
371     result = gpgme_op_verify_result (ctx);
372     if (!result)
373         return -1;
374
375     /* see if any of the signitures matched */
376     s = result->signatures;
377     while (s)
378     {
379         status = gpg_err_code (s->status);
380         if (status == GPG_ERR_NO_ERROR)
381             break;
382         s = s->next;
383     }
384
385
386     gpgme_data_release (sig);
387     gpgme_data_release (text);
388     gpgme_release (ctx);
389
390     return status;
391 #elif defined HAVE_OPENSSL
392     X509_STORE *store = NULL;
393     PKCS7 *p7 = NULL;
394     BIO *in = NULL, *indata = NULL;
395
396     // Sig check failed by default !
397     int status = -1;
398
399     init_openssl();
400
401     // Set-up the key store
402     if(!(store = setup_verify(conf, conf->signature_ca_file, conf->signature_ca_path))){
403         opkg_message(conf, OPKG_ERROR,
404                 "Can't open CA certificates\n");
405         goto verify_file_end;
406     }
407
408     // Open a BIO to read the sig file
409     if (!(in = BIO_new_file(sig_file, "rb"))){
410         opkg_message(conf, OPKG_ERROR,
411                 "Can't open signature file %s\n", sig_file);
412         goto verify_file_end;
413     }
414
415     // Read the PKCS7 block contained in the sig file
416     p7 = PEM_read_bio_PKCS7(in, NULL, NULL, NULL);
417     if(!p7){
418         opkg_message(conf, OPKG_ERROR,
419                 "Can't read signature file (Corrupted ?)\n");
420         goto verify_file_end;
421     }
422
423     // Open the Package file to authenticate
424     if (!(indata = BIO_new_file(text_file, "rb"))){
425         opkg_message(conf, OPKG_ERROR,
426                 "Can't open file %s\n", text_file);
427         goto verify_file_end;
428     }
429
430     // Let's verify the autenticity !
431     if (PKCS7_verify(p7, NULL, store, indata, NULL, PKCS7_BINARY) != 1){
432         // Get Off My Lawn!
433         opkg_message(conf, OPKG_ERROR,
434                 "Verification failure\n");
435     }else{
436         // Victory !
437         status = 0;
438     }
439
440 verify_file_end:
441     BIO_free(in);
442     BIO_free(indata);
443     PKCS7_free(p7);
444     X509_STORE_free(store);
445
446     return status;
447 #else
448     /* mute `unused variable' warnings. */
449     (void) sig_file;
450     (void) text_file;
451     (void) conf;
452     return 0;
453 #endif
454 }
455
456
457 #if defined HAVE_OPENSSL
458 static X509_STORE *setup_verify(opkg_conf_t *conf, char *CAfile, char *CApath){
459     X509_STORE *store = NULL;
460     X509_LOOKUP *lookup = NULL;
461
462     if(!(store = X509_STORE_new())){
463         // Something bad is happening...
464         goto end;
465     }
466
467     // adds the X509 file lookup method
468     lookup = X509_STORE_add_lookup(store,X509_LOOKUP_file());
469     if (lookup == NULL){
470         goto end;
471     }
472
473     // Autenticating against one CA file
474     if (CAfile) {
475         if(!X509_LOOKUP_load_file(lookup,CAfile,X509_FILETYPE_PEM)) {
476             // Invalid CA => Bye bye
477             opkg_message(conf, OPKG_ERROR,
478                     "Error loading file %s\n", CAfile);
479             goto end;
480         }
481     } else {
482         X509_LOOKUP_load_file(lookup,NULL,X509_FILETYPE_DEFAULT);
483     }
484
485     // Now look into CApath directory if supplied
486     lookup = X509_STORE_add_lookup(store,X509_LOOKUP_hash_dir());
487     if (lookup == NULL){
488         goto end;
489     }
490
491     if (CApath) {
492         if(!X509_LOOKUP_add_dir(lookup,CApath,X509_FILETYPE_PEM)) {
493             opkg_message(conf, OPKG_ERROR,
494                     "Error loading directory %s\n", CApath);
495             goto end;
496         }
497     } else {
498         X509_LOOKUP_add_dir(lookup,NULL,X509_FILETYPE_DEFAULT);
499     }
500
501     // All right !
502     ERR_clear_error();
503     return store;
504
505 end:
506
507     X509_STORE_free(store);
508     return NULL;
509
510 }
511
512 static void init_openssl(void){
513     static int init = 0;
514
515     if(!init){      
516         OpenSSL_add_all_algorithms();       
517         ERR_load_crypto_strings();
518         init = 1;
519     }
520 }
521 #endif