Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/libpkg/fetch_libcurl.c
2065 views
1
/*-
2
* Copyright (c) 2023 Baptiste Daroussin <[email protected]>
3
* Copyright (c) 2023 Serenity Cyber Security, LLC <[email protected]>
4
* Author: Gleb Popov <[email protected]>
5
*
6
* Redistribution and use in source and binary forms, with or without
7
* modification, are permitted provided that the following conditions
8
* are met:
9
* 1. Redistributions of source code must retain the above copyright
10
* notice, this list of conditions and the following disclaimer
11
* in this position and unchanged.
12
* 2. Redistributions in binary form must reproduce the above copyright
13
* notice, this list of conditions and the following disclaimer in the
14
* documentation and/or other materials provided with the distribution.
15
*
16
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
17
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
20
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
*/
27
28
#include <sys/param.h>
29
#include <stdlib.h>
30
#include <curl/curl.h>
31
#include <ctype.h>
32
33
#include "pkg.h"
34
#include "private/pkg.h"
35
#include "private/event.h"
36
#include "private/fetch.h"
37
38
#define curl_response_is_ok(res) ((res) == 200 || (res) == 206)
39
40
/*
41
* The choice of 2KB/s is arbitrary; at some point this should be configurable.
42
*/
43
#define LIBPKG_SPEED_LIMIT (2 * 1024) /* bytes per second */
44
45
struct curl_repodata {
46
CURLM *cm;
47
CURLU *url;
48
};
49
50
struct curl_userdata {
51
int fd;
52
CURL *cl;
53
FILE *fh;
54
off_t offset;
55
size_t size;
56
size_t totalsize;
57
size_t content_length;
58
bool started;
59
const char *url;
60
long response;
61
};
62
63
struct http_mirror {
64
CURLU *url;
65
struct http_mirror *next;
66
};
67
68
static
69
void dump(const char *text,
70
FILE *stream, unsigned char *ptr, size_t size)
71
{
72
size_t i;
73
size_t c;
74
75
unsigned int width = 0x40;
76
77
fprintf(stream, "%s, %10.10lu bytes (0x%8.8lx)\n",
78
text, (unsigned long)size, (unsigned long)size);
79
80
for(i = 0; i<size; i += width) {
81
82
fprintf(stream, "%4.4lx: ", (unsigned long)i);
83
84
for(c = 0; (c < width) && (i + c < size); c++) {
85
/* check for 0D0A; if found, skip past and start a new line of output */
86
if((i + c + 1 < size) && ptr[i + c] == 0x0D &&
87
ptr[i + c + 1] == 0x0A) {
88
i += (c + 2 - width);
89
break;
90
}
91
fprintf(stream, "%c",
92
(ptr[i + c] >= 0x20) && (ptr[i + c]<0x80)?ptr[i + c]:'.');
93
/* check again for 0D0A, to avoid an extra \n if it's at width */
94
if((i + c + 2 < size) && ptr[i + c + 1] == 0x0D &&
95
ptr[i + c + 2] == 0x0A) {
96
i += (c + 3 - width);
97
break;
98
}
99
}
100
fputc('\n', stream); /* newline */
101
}
102
fflush(stream);
103
}
104
105
static
106
int my_trace(CURL *handle, curl_infotype type,
107
char *data, size_t size,
108
void *userp __unused)
109
{
110
const char *text;
111
(void)handle; /* prevent compiler warning */
112
113
switch(type) {
114
case CURLINFO_TEXT:
115
fprintf(stderr, "== Info: %s", data);
116
/* FALLTHROUGH */
117
default: /* in case a new one is introduced to shock us */
118
return 0;
119
120
case CURLINFO_HEADER_OUT:
121
text = "=> Send header";
122
break;
123
case CURLINFO_DATA_OUT:
124
text = "=> Send data";
125
break;
126
case CURLINFO_SSL_DATA_OUT:
127
text = "=> Send SSL data";
128
break;
129
case CURLINFO_HEADER_IN:
130
text = "<= Recv header";
131
break;
132
case CURLINFO_DATA_IN:
133
text = "<= Recv data";
134
break;
135
case CURLINFO_SSL_DATA_IN:
136
text = "<= Recv SSL data";
137
break;
138
}
139
140
dump(text, stderr, (unsigned char *)data, size);
141
return 0;
142
}
143
144
static long
145
curl_do_fetch(struct curl_userdata *data, CURL *cl, struct curl_repodata *cr)
146
{
147
char *tmp;
148
int still_running = 1;
149
CURLMsg *msg;
150
int msg_left;
151
152
curl_easy_setopt(cl, CURLOPT_FOLLOWLOCATION, 1L);
153
curl_easy_setopt(cl, CURLOPT_PRIVATE, &data);
154
if (data->offset > 0)
155
curl_easy_setopt(cl, CURLOPT_RESUME_FROM_LARGE, data->offset);
156
if (ctx.debug_flags & PKG_DBG_FETCH && ctx.debug_level >= 1)
157
curl_easy_setopt(cl, CURLOPT_VERBOSE, 1L);
158
if (ctx.debug_flags & PKG_DBG_FETCH && ctx.debug_level >= 1)
159
curl_easy_setopt(cl, CURLOPT_DEBUGFUNCTION, my_trace);
160
161
/* compat with libfetch */
162
if ((tmp = getenv("HTTP_USER_AGENT")) != NULL)
163
curl_easy_setopt(cl, CURLOPT_USERAGENT, tmp);
164
if (getenv("SSL_NO_VERIFY_PEER") != NULL)
165
curl_easy_setopt(cl, CURLOPT_SSL_VERIFYPEER, 0L);
166
if (getenv("SSL_NO_VERIFY_HOSTNAME") != NULL)
167
curl_easy_setopt(cl, CURLOPT_SSL_VERIFYHOST, 0L);
168
curl_multi_add_handle(cr->cm, cl);
169
170
while(still_running) {
171
CURLMcode mc = curl_multi_perform(cr->cm, &still_running);
172
173
if(still_running)
174
/* wait for activity, timeout or "nothing" */
175
mc = curl_multi_poll(cr->cm, NULL, 0, 1000, NULL);
176
177
if(mc)
178
break;
179
}
180
181
while ((msg = curl_multi_info_read(cr->cm, &msg_left))) {
182
if (msg->msg == CURLMSG_DONE) {
183
if (msg->data.result == CURLE_ABORTED_BY_CALLBACK)
184
return (-1); // FIXME: more clear return code?
185
if (msg->data.result == CURLE_COULDNT_CONNECT
186
|| msg->data.result == CURLE_COULDNT_RESOLVE_HOST
187
|| msg->data.result == CURLE_COULDNT_RESOLVE_PROXY)
188
pkg_emit_pkg_errno(EPKG_NONETWORK, "curl_do_fetch", NULL);
189
CURL *eh = msg->easy_handle;
190
long rc = 0;
191
curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &rc);
192
return (rc);
193
}
194
}
195
return (0);
196
}
197
198
static size_t
199
curl_write_cb(char *data, size_t size, size_t nmemb, void *userdata)
200
{
201
struct curl_userdata *d = (struct curl_userdata *)userdata;
202
size_t written;
203
204
written = fwrite(data, size, nmemb, d->fh);
205
d->size += written;
206
d->offset += written;
207
208
return (written);
209
}
210
211
static struct http_mirror *
212
http_getmirrors(struct pkg_repo *r, struct curl_repodata *cr)
213
{
214
CURL *cl;
215
struct curl_userdata data = { 0 };
216
char *buf = NULL, *walk, *line;
217
size_t cap = 0;
218
struct http_mirror *m, *mirrors = NULL;
219
CURLU *url;
220
pkg_dbg(PKG_DBG_FETCH, 2, "CURL> fetching http mirror list if any");
221
222
cl = curl_easy_init();
223
data.fh = open_memstream(& buf, &cap);
224
data.cl = cl;
225
226
curl_easy_setopt(cl, CURLOPT_WRITEFUNCTION, curl_write_cb);
227
curl_easy_setopt(cl, CURLOPT_WRITEDATA, &data);
228
curl_easy_setopt(cl, CURLOPT_MAXFILESIZE_LARGE, 1048576L);
229
curl_easy_setopt(cl, CURLOPT_URL, r->url);
230
curl_easy_setopt(cl, CURLOPT_NOPROGRESS, 1L);
231
data.url = r->url;
232
if (ctx.ip == IPV4)
233
curl_easy_setopt(cl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
234
if (ctx.ip == IPV6)
235
curl_easy_setopt(cl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
236
curl_do_fetch(&data, cl, cr);
237
fclose(data.fh);
238
walk = buf;
239
while ((line = strsep(&walk, "\n\r")) != NULL) {
240
if (strncmp(line, "URL:", 4) != 0)
241
continue;
242
line += 4;
243
while (isspace(*line))
244
line++;
245
if (*line == '\0')
246
continue;
247
url = curl_url();
248
if (curl_url_set(url, CURLUPART_URL, line, 0)) {
249
curl_url_cleanup(url);
250
pkg_emit_error("Invalid mirror url: '%s'", line);
251
continue;
252
}
253
m = xmalloc(sizeof(*m));
254
m->url = url;
255
pkg_dbg(PKG_DBG_FETCH, 2, "CURL> appending an http mirror: %s", line);
256
LL_APPEND(mirrors, m);
257
}
258
free(buf);
259
260
return (mirrors);
261
}
262
263
static size_t
264
curl_parseheader_cb(void *ptr __unused, size_t size, size_t nmemb, void *userdata)
265
{
266
struct curl_userdata *d = (struct curl_userdata *)userdata;
267
268
curl_easy_getinfo(d->cl, CURLINFO_RESPONSE_CODE, &d->response);
269
if (d->response == 404)
270
return (CURLE_WRITE_ERROR);
271
if (curl_response_is_ok(d->response) && !d->started) {
272
pkg_emit_fetch_begin(d->url);
273
pkg_emit_progress_start(NULL);
274
d->started = true;
275
}
276
277
return (size * nmemb);
278
}
279
280
static int
281
curl_progress_cb(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal __unused, curl_off_t ulnow __unused)
282
{
283
struct curl_userdata *d = (struct curl_userdata *)userdata;
284
285
if (!curl_response_is_ok(d->response))
286
return (0);
287
288
return pkg_emit_progress_tick(dlnow, dltotal);
289
}
290
291
int
292
curl_open(struct pkg_repo *repo, struct fetch_item *fi __unused)
293
{
294
struct curl_repodata *cr;
295
pkg_dbg(PKG_DBG_FETCH, 2, "curl_open");
296
297
if (repo->fetch_priv != NULL)
298
return (EPKG_OK);
299
300
curl_global_init(CURL_GLOBAL_ALL);
301
cr = xcalloc(1, sizeof(*cr));
302
cr->cm = curl_multi_init();
303
curl_multi_setopt(cr->cm, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
304
curl_multi_setopt(cr->cm, CURLMOPT_MAX_HOST_CONNECTIONS, 1);
305
/*curl_multi_setopt(cm, CURLMOPT_MAX_HOST_TOTAL_CONNECTIONS, 4);*/
306
if (repo->mirror_type == SRV && repo->srv == NULL) {
307
int urloff = 0;
308
cr->url = curl_url();
309
if (strncasecmp(repo->url, "pkg+", 4) == 0)
310
urloff = 4;
311
CURLUcode c = curl_url_set(cr->url, CURLUPART_URL, repo->url + urloff, 0);
312
if (c) {
313
pkg_emit_error("impossible to parse url: '%s'", repo->url);
314
return (EPKG_FATAL);
315
}
316
317
char *zone;
318
char *host = NULL, *scheme = NULL;
319
curl_url_get(cr->url, CURLUPART_HOST, &host, 0);
320
curl_url_get(cr->url, CURLUPART_SCHEME, &scheme, 0);
321
xasprintf(&zone, "_%s._tcp.%s", scheme, host);
322
repo->srv = dns_getsrvinfo(zone);
323
free(zone);
324
free(host);
325
free(scheme);
326
if (repo->srv == NULL) {
327
pkg_emit_error("No SRV record found for the "
328
"repo '%s'", repo->name);
329
repo->mirror_type = NOMIRROR;
330
}
331
}
332
if (repo->mirror_type == HTTP && repo->http == NULL) {
333
if (strncasecmp(repo->url, "pkg+", 4) == 0) {
334
pkg_emit_error("invalid for http mirror mechanism "
335
"scheme '%s'", repo->url);
336
return (EPKG_FATAL);
337
}
338
cr->url = curl_url();
339
CURLUcode c = curl_url_set(cr->url, CURLUPART_URL, repo->url, 0);
340
if (c) {
341
pkg_emit_error("impossible to parse url: '%s'", repo->url);
342
return (EPKG_FATAL);
343
}
344
repo->http = http_getmirrors(repo, cr);
345
if (repo->http == NULL) {
346
pkg_emit_error("No HTTP mirrors founds for the repo "
347
"'%s'", repo->name);
348
repo->mirror_type = NOMIRROR;
349
}
350
}
351
/* TODO: Later for parallel fetching */
352
repo->fetch_priv = cr;
353
354
return (EPKG_OK);
355
}
356
357
int
358
curl_fetch(struct pkg_repo *repo, int dest, struct fetch_item *fi)
359
{
360
CURL *cl;
361
CURLU *hu = NULL;
362
CURLcode res;
363
struct curl_userdata data = { 0 };
364
int64_t retry;
365
int retcode = EPKG_OK;
366
struct dns_srvinfo *srv_current = NULL;
367
struct http_mirror *http_current = NULL;
368
char *urlpath = NULL;
369
const char *relpath = NULL;
370
const char *userpasswd = get_http_auth();
371
const char *http_proxy = getenv("HTTP_PROXY");
372
const char *http_proxy_auth = getenv("HTTP_PROXY_AUTH");
373
const char *sslkey = getenv("SSL_CLIENT_KEY_FILE");
374
const char *sslcert = getenv("SSL_CLIENT_CERT_FILE");
375
const char *ssl_ca_cert_file = getenv("SSL_CA_CERT_FILE");
376
const char *ssl_ca_cert_path = getenv("SSL_CA_CERT_PATH");
377
const char *netrc_file = getenv("NETRC");
378
379
struct curl_repodata *cr = (struct curl_repodata *)repo->fetch_priv;
380
381
data.fh = fdopen(dup(dest), "w");
382
if (data.fh == NULL)
383
return (EPKG_FATAL);
384
data.totalsize = fi->size;
385
data.url = fi->url;
386
data.offset = fi->offset > 0 ? fi->offset : 0;
387
388
pkg_dbg(PKG_DBG_FETCH, 2, "curl> fetching %s\n", fi->url);
389
retry = pkg_object_int(pkg_config_get("FETCH_RETRY"));
390
if (repo->mirror_type == SRV || repo->mirror_type == HTTP) {
391
CURLU *cu = curl_url();
392
curl_url_set(cu, CURLUPART_URL, fi->url, 0);
393
curl_url_get(cu, CURLUPART_PATH, &urlpath, 0);
394
if (urlpath != NULL && repo->mirror_type == SRV)
395
curl_url_set(cr->url, CURLUPART_PATH, urlpath, 0);
396
if (urlpath != NULL && repo->mirror_type == HTTP) {
397
CURLU *ru = curl_url();
398
char *doc = NULL;
399
curl_url_set(ru, CURLUPART_URL, repo->url, 0);
400
curl_url_get(ru, CURLUPART_PATH, &doc, 0);
401
relpath = urlpath;
402
if (doc != NULL)
403
relpath += strlen(doc);
404
free(doc);
405
curl_url_cleanup(ru);
406
}
407
curl_url_cleanup(cu);
408
}
409
if (http_proxy == NULL)
410
http_proxy = getenv("http_proxy");
411
412
do_retry:
413
cl = curl_easy_init();
414
data.cl = cl;
415
if (repo->mirror_type == SRV) {
416
char *portstr;
417
if (srv_current != NULL)
418
srv_current = srv_current->next;
419
if (srv_current == NULL)
420
srv_current = repo->srv;
421
curl_url_set(cr->url, CURLUPART_HOST, srv_current->host, 0);
422
xasprintf(&portstr, "%d", srv_current->port);
423
curl_url_set(cr->url, CURLUPART_PORT, portstr, 0);
424
free(portstr);
425
curl_easy_setopt(cl, CURLOPT_CURLU, cr->url);
426
} else if (repo->mirror_type == HTTP) {
427
if (http_current != NULL)
428
http_current = http_current->next;
429
if (http_current == NULL)
430
http_current = repo->http;
431
char *doc = NULL;
432
char *p = NULL;
433
const char *path = relpath;;
434
curl_url_cleanup(hu);
435
hu = curl_url_dup(http_current->url);
436
curl_url_get(hu, CURLUPART_PATH, &doc, 0);
437
if (doc != NULL) {
438
xasprintf(&p, "%s/%s", doc, relpath);
439
path = p;
440
}
441
curl_url_set(hu, CURLUPART_PATH, path, 0);
442
free(p);
443
char *lurl;
444
curl_url_get(hu, CURLUPART_URL, &lurl, 0);
445
pkg_dbg(PKG_DBG_FETCH, 2, "CURL> new http mirror url: %s", lurl);
446
curl_easy_setopt(cl, CURLOPT_CURLU, hu);
447
} else {
448
pkg_dbg(PKG_DBG_FETCH, 2, "CURL> No mirror set url to %s\n", fi->url);
449
curl_easy_setopt(cl, CURLOPT_URL, fi->url);
450
}
451
if (ctx.debug_flags & PKG_DBG_FETCH && ctx.debug_level >= 1) {
452
const char *lurl = NULL;
453
curl_easy_getinfo(cl, CURLINFO_EFFECTIVE_URL, &lurl);
454
if (lurl) {
455
pkg_dbg(PKG_DBG_FETCH, 2, "CURL> attempting to fetch from %s\n", lurl);
456
}
457
pkg_dbg(PKG_DBG_FETCH, 2, "CURL> retries left: %"PRId64"\n", retry);
458
}
459
curl_easy_setopt(cl, CURLOPT_HTTPAUTH, (long)CURLAUTH_ANY);
460
if (userpasswd != NULL) {
461
curl_easy_setopt(cl, CURLOPT_USERPWD, userpasswd);
462
}
463
if (http_proxy != NULL)
464
curl_easy_setopt(cl, CURLOPT_PROXY, http_proxy);
465
if (http_proxy_auth != NULL) {
466
curl_easy_setopt(cl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
467
curl_easy_setopt(cl, CURLOPT_PROXYUSERPWD, http_proxy_auth);
468
}
469
if (sslkey != NULL)
470
curl_easy_setopt(cl, CURLOPT_SSLKEY, sslkey);
471
if (sslcert != NULL)
472
curl_easy_setopt(cl, CURLOPT_SSLCERT, sslcert);
473
if (ssl_ca_cert_file != NULL)
474
curl_easy_setopt(cl, CURLOPT_CAINFO, ssl_ca_cert_file);
475
if (ssl_ca_cert_path != NULL)
476
curl_easy_setopt(cl, CURLOPT_CAPATH, ssl_ca_cert_path);
477
if (netrc_file != NULL)
478
curl_easy_setopt(cl, CURLOPT_NETRC_FILE, netrc_file);
479
curl_easy_setopt(cl, CURLOPT_NETRC, 1L);
480
481
if (repo->ip == IPV4)
482
curl_easy_setopt(cl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
483
if (repo->ip == IPV6)
484
curl_easy_setopt(cl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
485
curl_easy_setopt(cl, CURLOPT_NOPROGRESS, 0L);
486
curl_easy_setopt(cl, CURLOPT_WRITEFUNCTION, curl_write_cb);
487
curl_easy_setopt(cl, CURLOPT_WRITEDATA, &data);
488
curl_easy_setopt(cl, CURLOPT_XFERINFOFUNCTION, curl_progress_cb);
489
curl_easy_setopt(cl, CURLOPT_XFERINFODATA, &data);
490
curl_easy_setopt(cl, CURLOPT_HEADERFUNCTION, curl_parseheader_cb);
491
curl_easy_setopt(cl, CURLOPT_HEADERDATA, &data);
492
curl_easy_setopt(cl, CURLOPT_TIMEVALUE_LARGE, (curl_off_t)fi->mtime);
493
curl_easy_setopt(cl, CURLOPT_FILETIME, 1L);
494
curl_easy_setopt(cl, CURLOPT_TIMECONDITION, (long)CURL_TIMECOND_IFMODSINCE);
495
if (repo->fetcher->timeout > 0) {
496
curl_easy_setopt(cl, CURLOPT_CONNECTTIMEOUT, repo->fetcher->timeout);
497
498
curl_easy_setopt(cl, CURLOPT_LOW_SPEED_LIMIT, LIBPKG_SPEED_LIMIT);
499
curl_easy_setopt(cl, CURLOPT_LOW_SPEED_TIME, repo->fetcher->timeout);
500
}
501
502
long rc = curl_do_fetch(&data, cl, cr);
503
curl_off_t t;
504
res = curl_easy_getinfo(cl, CURLINFO_FILETIME_T, &t);
505
curl_multi_remove_handle(cr->cm, cl);
506
curl_easy_cleanup(cl);
507
if (rc == 304) {
508
retcode = EPKG_UPTODATE;
509
} else if (rc == -1) {
510
retcode = EPKG_CANCEL;
511
} else if (!curl_response_is_ok(rc)) {
512
--retry;
513
if (retry <= 0) {
514
if (rc == 404) {
515
retcode = EPKG_ENOENT;
516
} else {
517
pkg_emit_error("An error occurred while fetching package: %s", curl_easy_strerror(rc));
518
retcode = EPKG_FATAL;
519
}
520
} else
521
goto do_retry;
522
}
523
524
if (res == CURLE_OK && t >= 0) {
525
fi->mtime = t;
526
} else if (rc != 304 && retcode != EPKG_FATAL &&
527
retcode != EPKG_CANCEL && retcode != EPKG_ENOENT) {
528
pkg_emit_error("Impossible to get the value from Last-Modified"
529
" HTTP header");
530
fi->mtime = 0;
531
}
532
fclose(data.fh);
533
free(urlpath);
534
curl_url_cleanup(hu);
535
536
return (retcode);
537
}
538
539
void
540
curl_cleanup(struct pkg_repo *repo)
541
{
542
struct curl_repodata *cr;
543
544
if (repo->fetch_priv == NULL)
545
return;
546
cr = repo->fetch_priv;
547
curl_multi_cleanup(cr->cm);
548
if (cr->url != NULL)
549
curl_url_cleanup(cr->url);
550
repo->fetch_priv = NULL;
551
}
552
553