curl.c (17328B)
1 /*
2 * Copyright (c) 2019-2022 Joris Vink <joris@coders.se>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <sys/types.h>
18
19 #include <inttypes.h>
20
21 #include "kore.h"
22 #include "http.h"
23 #include "curl.h"
24
25 #if defined(__linux__)
26 #include "seccomp.h"
27
28 static struct sock_filter filter_curl[] = {
29 /* Allow sockets and libcurl to call connect. */
30 KORE_SYSCALL_ALLOW(bind),
31 KORE_SYSCALL_ALLOW(ioctl),
32 KORE_SYSCALL_ALLOW(connect),
33 KORE_SYSCALL_ALLOW(socketpair),
34 KORE_SYSCALL_ALLOW(getsockopt),
35 KORE_SYSCALL_ALLOW(getsockname),
36 KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET),
37 KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_INET6),
38 KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_UNIX),
39 KORE_SYSCALL_ALLOW_ARG(socket, 0, AF_NETLINK),
40
41 /* Threading related. */
42 #if defined(SYS_clone3)
43 KORE_SYSCALL_ALLOW(clone3),
44 #endif
45 #if defined(SYS_rseq)
46 KORE_SYSCALL_ALLOW(rseq),
47 #endif
48 KORE_SYSCALL_ALLOW(clone),
49 KORE_SYSCALL_ALLOW(set_robust_list),
50
51 /* Other */
52 KORE_SYSCALL_ALLOW(uname),
53 KORE_SYSCALL_ALLOW(ioctl),
54 KORE_SYSCALL_ALLOW(madvise),
55 KORE_SYSCALL_ALLOW(recvmsg),
56 KORE_SYSCALL_ALLOW(sendmmsg),
57 KORE_SYSCALL_ALLOW(faccessat),
58 KORE_SYSCALL_ALLOW(newfstatat),
59 KORE_SYSCALL_ALLOW(getpeername),
60
61 #if defined(SYS_getdents64)
62 KORE_SYSCALL_ALLOW(getdents64),
63 #endif
64
65 };
66 #endif
67
68 #define FD_CACHE_BUCKETS 2048
69
70 struct fd_cache {
71 struct kore_event evt;
72 int fd;
73 int scheduled;
74 LIST_ENTRY(fd_cache) list;
75 };
76
77 struct curl_run {
78 int eof;
79 struct fd_cache *fdc;
80 TAILQ_ENTRY(curl_run) list;
81 };
82
83 static void curl_process(void);
84 static void curl_event_handle(void *, int);
85 static void curl_timeout(void *, u_int64_t);
86 static int curl_timer(CURLM *, long, void *);
87 static void curl_run_handle(struct curl_run *);
88 static void curl_run_schedule(struct fd_cache *, int);
89 static int curl_socket(CURL *, curl_socket_t, int, void *, void *);
90
91 static struct fd_cache *fd_cache_get(int);
92
93 static TAILQ_HEAD(, curl_run) runlist;
94 static struct kore_pool run_pool;
95 static int running = 0;
96 static CURLM *multi = NULL;
97 static struct kore_timer *timer = NULL;
98 static struct kore_pool fd_cache_pool;
99 static char user_agent[64];
100 static int timeout_immediate = 0;
101 static LIST_HEAD(, fd_cache) cache[FD_CACHE_BUCKETS];
102
103 u_int16_t kore_curl_timeout = KORE_CURL_TIMEOUT;
104 u_int64_t kore_curl_recv_max = KORE_CURL_RECV_MAX;
105
106 void
107 kore_curl_sysinit(void)
108 {
109 CURLMcode res;
110 int i, len;
111
112 if (curl_global_init(CURL_GLOBAL_ALL))
113 fatal("failed to initialize libcurl");
114
115 if ((multi = curl_multi_init()) == NULL)
116 fatal("curl_multi_init(): failed");
117
118 /* XXX - make configurable? */
119 curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, 500);
120
121 if ((res = curl_multi_setopt(multi,
122 CURLMOPT_SOCKETFUNCTION, curl_socket)) != CURLM_OK)
123 fatal("curl_multi_setopt: %s", curl_multi_strerror(res));
124
125 if ((res = curl_multi_setopt(multi,
126 CURLMOPT_TIMERFUNCTION, curl_timer)) != CURLM_OK)
127 fatal("curl_multi_setopt: %s", curl_multi_strerror(res));
128
129 for (i = 0; i < FD_CACHE_BUCKETS; i++)
130 LIST_INIT(&cache[i]);
131
132 TAILQ_INIT(&runlist);
133
134 kore_pool_init(&fd_cache_pool, "fd_cache_pool", 100,
135 sizeof(struct fd_cache));
136 kore_pool_init(&run_pool, "run_pool", 100, sizeof(struct curl_run));
137
138 len = snprintf(user_agent, sizeof(user_agent), "kore/%s", kore_version);
139 if (len == -1 || (size_t)len >= sizeof(user_agent))
140 fatal("user-agent string too long");
141
142 #if defined(__linux__)
143 kore_seccomp_filter("curl", filter_curl, KORE_FILTER_LEN(filter_curl));
144 #endif
145 #if defined(KORE_USE_PLATFORM_PLEDGE)
146 kore_platform_add_pledge("dns");
147 #endif
148 }
149
150 int
151 kore_curl_init(struct kore_curl *client, const char *url, int flags)
152 {
153 CURL *handle;
154
155 if ((flags & KORE_CURL_ASYNC) && (flags & KORE_CURL_SYNC)) {
156 (void)kore_strlcpy(client->errbuf, "invalid flags",
157 sizeof(client->errbuf));
158 return (KORE_RESULT_ERROR);
159 }
160
161 memset(client, 0, sizeof(*client));
162
163 TAILQ_INIT(&client->http.resp_hdrs);
164
165 if ((handle = curl_easy_init()) == NULL) {
166 (void)kore_strlcpy(client->errbuf, "failed to setup curl",
167 sizeof(client->errbuf));
168 return (KORE_RESULT_ERROR);
169 }
170
171 curl_easy_setopt(handle, CURLOPT_WRITEDATA, &client->response);
172 curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, kore_curl_tobuf);
173
174 curl_easy_setopt(handle, CURLOPT_URL, url);
175 curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1);
176 curl_easy_setopt(handle, CURLOPT_PRIVATE, client);
177 curl_easy_setopt(handle, CURLOPT_USERAGENT, user_agent);
178 curl_easy_setopt(handle, CURLOPT_TIMEOUT, kore_curl_timeout);
179 curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, client->errbuf);
180
181 client->flags = flags;
182 client->handle = handle;
183 client->url = kore_strdup(url);
184 client->type = KORE_CURL_TYPE_CUSTOM;
185
186 return (KORE_RESULT_OK);
187 }
188
189 void
190 kore_curl_cleanup(struct kore_curl *client)
191 {
192 struct http_header *hdr, *next;
193
194 kore_free(client->url);
195
196 if (client->flags & KORE_CURL_FLAG_BOUND)
197 LIST_REMOVE(client, list);
198
199 if (client->handle != NULL) {
200 curl_multi_remove_handle(multi, client->handle);
201 curl_easy_cleanup(client->handle);
202 }
203
204 if (client->http.hdrlist != NULL)
205 curl_slist_free_all(client->http.hdrlist);
206
207 if (client->response != NULL)
208 kore_buf_free(client->response);
209
210 if (client->http.headers != NULL)
211 kore_buf_free(client->http.headers);
212
213 if (client->http.tosend != NULL)
214 kore_buf_free(client->http.tosend);
215
216 for (hdr = TAILQ_FIRST(&client->http.resp_hdrs);
217 hdr != NULL; hdr = next) {
218 next = TAILQ_NEXT(hdr, list);
219 TAILQ_REMOVE(&client->http.resp_hdrs, hdr, list);
220 kore_pool_put(&http_header_pool, hdr);
221 }
222 }
223
224 void
225 kore_curl_do_timeout(void)
226 {
227 while (timeout_immediate) {
228 curl_timeout(NULL, kore_time_ms());
229 if (running == 0)
230 curl_timer(multi, -1, NULL);
231 }
232 }
233
234 void
235 kore_curl_run_scheduled(void)
236 {
237 struct curl_run *run;
238
239 while ((run = TAILQ_FIRST(&runlist))) {
240 TAILQ_REMOVE(&runlist, run, list);
241 curl_run_handle(run);
242 kore_pool_put(&run_pool, run);
243 }
244
245 curl_process();
246 }
247
248 size_t
249 kore_curl_tobuf(char *ptr, size_t size, size_t nmemb, void *udata)
250 {
251 size_t len;
252 struct kore_buf **buf, *b;
253
254 if (SIZE_MAX / nmemb < size)
255 fatal("%s: %zu * %zu overflow", __func__, nmemb, size);
256
257 buf = udata;
258 len = size * nmemb;
259
260 if (*buf == NULL)
261 *buf = kore_buf_alloc(len);
262
263 b = *buf;
264
265 if (b->offset + len < b->offset)
266 fatal("%s: %zu+%zu overflows", __func__, b->offset, len);
267
268 if ((b->offset + len) > kore_curl_recv_max) {
269 kore_log(LOG_ERR,
270 "received too large transfer (%zu > %" PRIu64 ")",
271 b->offset + len, kore_curl_recv_max);
272 return (0);
273 }
274
275 kore_buf_append(b, ptr, len);
276
277 return (len);
278 }
279
280 size_t
281 kore_curl_frombuf(char *ptr, size_t size, size_t nmemb, void *udata)
282 {
283 size_t len;
284 struct kore_buf *buf;
285
286 if (SIZE_MAX / nmemb < size)
287 fatal("%s: %zu * %zu overflow", __func__, nmemb, size);
288
289 buf = udata;
290 len = size * nmemb;
291
292 if (buf->offset == buf->length)
293 return (0);
294
295 if (buf->offset + len < buf->offset)
296 fatal("%s: %zu+%zu overflows", __func__, buf->offset, len);
297
298 if ((buf->offset + len) < buf->length) {
299 memcpy(ptr, buf->data + buf->offset, len);
300 } else {
301 len = buf->length - buf->offset;
302 memcpy(ptr, buf->data + buf->offset, len);
303 }
304
305 buf->offset += len;
306
307 return (len);
308 }
309
310 void
311 kore_curl_bind_request(struct kore_curl *client, struct http_request *req)
312 {
313 if (client->cb != NULL)
314 fatal("%s: already bound to callback", __func__);
315
316 client->req = req;
317 http_request_sleep(req);
318
319 client->flags |= KORE_CURL_FLAG_BOUND;
320 LIST_INSERT_HEAD(&req->chandles, client, list);
321 }
322
323 void
324 kore_curl_bind_callback(struct kore_curl *client,
325 void (*cb)(struct kore_curl *, void *), void *arg)
326 {
327 if (client->req != NULL)
328 fatal("%s: already bound to request", __func__);
329
330 client->cb = cb;
331 client->arg = arg;
332 }
333
334 void
335 kore_curl_run(struct kore_curl *client)
336 {
337 if (client->flags & KORE_CURL_ASYNC) {
338 curl_multi_add_handle(multi, client->handle);
339 return;
340 }
341
342 client->result = curl_easy_perform(client->handle);
343
344 curl_easy_getinfo(client->handle,
345 CURLINFO_RESPONSE_CODE, &client->http.status);
346
347 curl_easy_cleanup(client->handle);
348 client->handle = NULL;
349 }
350
351 int
352 kore_curl_success(struct kore_curl *client)
353 {
354 return (client->result == CURLE_OK);
355 }
356
357 const char *
358 kore_curl_strerror(struct kore_curl *client)
359 {
360 const char *err;
361
362 if (client->errbuf[0] != '\0')
363 err = &client->errbuf[0];
364 else
365 err = curl_easy_strerror(client->result);
366
367 return (err);
368 }
369
370 void
371 kore_curl_logerror(struct kore_curl *client)
372 {
373 kore_log(LOG_NOTICE, "curl error: %s -> %s", client->url,
374 kore_curl_strerror(client));
375 }
376
377 void
378 kore_curl_response_as_bytes(struct kore_curl *client, const u_int8_t **body,
379 size_t *len)
380 {
381 if (client->response == NULL) {
382 *len = 0;
383 *body = NULL;
384 } else {
385 *len = client->response->offset;
386 *body = client->response->data;
387 }
388 }
389
390 char *
391 kore_curl_response_as_string(struct kore_curl *client)
392 {
393 kore_buf_stringify(client->response, NULL);
394
395 return ((char *)client->response->data);
396 }
397
398 void
399 kore_curl_http_setup(struct kore_curl *client, int method, const void *data,
400 size_t len)
401 {
402 const char *mname;
403 int has_body;
404
405 if (client->handle == NULL)
406 fatal("%s: called without setup", __func__);
407
408 mname = NULL;
409 has_body = 1;
410
411 client->type = KORE_CURL_TYPE_HTTP_CLIENT;
412
413 curl_easy_setopt(client->handle, CURLOPT_HEADERDATA,
414 &client->http.headers);
415 curl_easy_setopt(client->handle, CURLOPT_HEADERFUNCTION,
416 kore_curl_tobuf);
417
418 kore_curl_http_set_header(client, "expect", "");
419
420 switch (method) {
421 case HTTP_METHOD_GET:
422 case HTTP_METHOD_OPTIONS:
423 break;
424 case HTTP_METHOD_HEAD:
425 curl_easy_setopt(client->handle, CURLOPT_NOBODY, 1);
426 break;
427 case HTTP_METHOD_PUT:
428 has_body = 1;
429 curl_easy_setopt(client->handle, CURLOPT_UPLOAD, 1);
430 break;
431 case HTTP_METHOD_PATCH:
432 case HTTP_METHOD_DELETE:
433 mname = http_method_text(method);
434 /* fallthrough */
435 case HTTP_METHOD_POST:
436 has_body = 1;
437 curl_easy_setopt(client->handle, CURLOPT_POST, 1);
438 break;
439 default:
440 fatal("%s: unknown method %d", __func__, method);
441 }
442
443 if (has_body && data != NULL && len > 0) {
444 client->http.tosend = kore_buf_alloc(len);
445 kore_buf_append(client->http.tosend, data, len);
446 kore_buf_reset(client->http.tosend);
447
448 curl_easy_setopt(client->handle, CURLOPT_READDATA,
449 client->http.tosend);
450 curl_easy_setopt(client->handle, CURLOPT_READFUNCTION,
451 kore_curl_frombuf);
452 }
453
454 if (has_body) {
455 if (method == HTTP_METHOD_PUT) {
456 curl_easy_setopt(client->handle,
457 CURLOPT_INFILESIZE_LARGE, (curl_off_t)len);
458 } else {
459 curl_easy_setopt(client->handle,
460 CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)len);
461 }
462 } else {
463 if (data != NULL || len != 0) {
464 fatal("%s: %d should not have a body",
465 __func__, method);
466 }
467 }
468
469 if (mname != NULL)
470 curl_easy_setopt(client->handle, CURLOPT_CUSTOMREQUEST, mname);
471 }
472
473 void
474 kore_curl_http_set_header(struct kore_curl *client, const char *header,
475 const char *value)
476 {
477 struct kore_buf buf;
478 const char *hdr;
479
480 kore_buf_init(&buf, 512);
481
482 if (value != NULL || *value != '\0') {
483 kore_buf_appendf(&buf, "%s: %s", header, value);
484 } else {
485 kore_buf_appendf(&buf, "%s:", header);
486 }
487
488 hdr = kore_buf_stringify(&buf, NULL);
489
490 client->http.hdrlist = curl_slist_append(client->http.hdrlist, hdr);
491 kore_buf_cleanup(&buf);
492
493 curl_easy_setopt(client->handle,
494 CURLOPT_HTTPHEADER, client->http.hdrlist);
495 }
496
497 int
498 kore_curl_http_get_header(struct kore_curl *client, const char *header,
499 const char **out)
500 {
501 struct http_header *hdr;
502
503 if (!(client->flags & KORE_CURL_FLAG_HTTP_PARSED_HEADERS))
504 kore_curl_http_parse_headers(client);
505
506 TAILQ_FOREACH(hdr, &(client->http.resp_hdrs), list) {
507 if (!strcasecmp(hdr->header, header)) {
508 *out = hdr->value;
509 return (KORE_RESULT_OK);
510 }
511 }
512
513 return (KORE_RESULT_ERROR);
514 }
515
516 void
517 kore_curl_http_parse_headers(struct kore_curl *client)
518 {
519 struct http_header *hdr;
520 int i, cnt;
521 char *value, *hbuf, *headers[HTTP_REQ_HEADER_MAX];
522
523 if (client->flags & KORE_CURL_FLAG_HTTP_PARSED_HEADERS)
524 fatal("%s: headers already parsed", __func__);
525
526 client->flags |= KORE_CURL_FLAG_HTTP_PARSED_HEADERS;
527
528 if (client->http.headers == NULL)
529 return;
530
531 hbuf = kore_buf_stringify(client->http.headers, NULL);
532 cnt = kore_split_string(hbuf, "\r\n", headers, HTTP_REQ_HEADER_MAX);
533
534 for (i = 0; i < cnt; i++) {
535 if ((value = http_validate_header(headers[i])) == NULL)
536 continue;
537
538 if (*value == '\0')
539 continue;
540
541 hdr = kore_pool_get(&http_header_pool);
542 hdr->header = headers[i];
543 hdr->value = value;
544 TAILQ_INSERT_TAIL(&(client->http.resp_hdrs), hdr, list);
545 }
546 }
547
548 static int
549 curl_socket(CURL *easy, curl_socket_t fd, int action, void *arg, void *sock)
550 {
551 CURLcode res;
552 struct fd_cache *fdc;
553 struct kore_curl *client;
554
555 client = NULL;
556
557 res = curl_easy_getinfo(easy, CURLINFO_PRIVATE, (char **)&client);
558 if (res != CURLE_OK)
559 fatal("curl_easy_getinfo: %s", curl_easy_strerror(res));
560
561 if (client == NULL)
562 fatal("%s: failed to get client context", __func__);
563
564 fdc = fd_cache_get(fd);
565
566 switch (action) {
567 case CURL_POLL_NONE:
568 break;
569 case CURL_POLL_IN:
570 if (fdc->scheduled) {
571 kore_platform_disable_read(fd);
572 #if !defined(__linux__)
573 kore_platform_disable_write(fd);
574 #endif
575 }
576 fdc->scheduled = 1;
577 kore_platform_event_level_read(fd, fdc);
578 break;
579 case CURL_POLL_OUT:
580 case CURL_POLL_INOUT:
581 if (fdc->scheduled) {
582 kore_platform_disable_read(fd);
583 #if !defined(__linux__)
584 kore_platform_disable_write(fd);
585 #endif
586 }
587 fdc->scheduled = 1;
588 kore_platform_event_level_all(fd, fdc);
589 break;
590 case CURL_POLL_REMOVE:
591 if (fdc->scheduled) {
592 fdc->evt.flags = 0;
593 fdc->scheduled = 0;
594 kore_platform_disable_read(fd);
595 #if !defined(__linux__)
596 kore_platform_disable_write(fd);
597 #endif
598 }
599 break;
600 default:
601 fatal("unknown action value: %d", action);
602 }
603
604 if (action != CURL_POLL_NONE && action != CURL_POLL_REMOVE)
605 curl_run_schedule(fdc, 0);
606
607 return (CURLM_OK);
608 }
609
610 static void
611 curl_process(void)
612 {
613 CURLcode res;
614 CURLMsg *msg;
615 CURL *handle;
616 struct kore_curl *client;
617 int pending;
618
619 pending = 0;
620
621 while ((msg = curl_multi_info_read(multi, &pending)) != NULL) {
622 if (msg->msg != CURLMSG_DONE)
623 continue;
624
625 handle = msg->easy_handle;
626
627 res = curl_easy_getinfo(handle, CURLINFO_PRIVATE,
628 (char **)&client);
629 if (res != CURLE_OK)
630 fatal("curl_easy_getinfo: %s", curl_easy_strerror(res));
631
632 if (client == NULL)
633 fatal("%s: failed to get client context", __func__);
634
635 client->result = msg->data.result;
636
637 if (client->type == KORE_CURL_TYPE_HTTP_CLIENT) {
638 curl_easy_getinfo(client->handle,
639 CURLINFO_RESPONSE_CODE, &client->http.status);
640 }
641
642 curl_multi_remove_handle(multi, client->handle);
643 curl_easy_cleanup(client->handle);
644
645 client->handle = NULL;
646
647 if (client->req != NULL)
648 http_request_wakeup(client->req);
649 else if (client->cb != NULL)
650 client->cb(client, client->arg);
651 }
652 }
653
654 static void
655 curl_timeout(void *uarg, u_int64_t now)
656 {
657 CURLMcode res;
658
659 timer = NULL;
660
661 res = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running);
662 if (res != CURLM_OK)
663 fatal("curl_multi_socket_action: %s", curl_multi_strerror(res));
664
665 curl_process();
666 }
667
668 static int
669 curl_timer(CURLM *mctx, long timeout, void *arg)
670 {
671 timeout_immediate = 0;
672
673 if (timeout < 0) {
674 if (timer != NULL) {
675 kore_timer_remove(timer);
676 timer = NULL;
677 }
678 return (CURLM_OK);
679 }
680
681 if (timer != NULL) {
682 kore_timer_remove(timer);
683 timer = NULL;
684 }
685
686 if (timeout == 0) {
687 timeout_immediate = 1;
688 return (CURLM_OK);
689 }
690
691 timer = kore_timer_add(curl_timeout, timeout, mctx, KORE_TIMER_ONESHOT);
692
693 return (CURLM_OK);
694 }
695
696 static void
697 curl_run_schedule(struct fd_cache *fdc, int eof)
698 {
699 struct curl_run *run;
700
701 run = kore_pool_get(&run_pool);
702 run->fdc = fdc;
703 run->eof = eof;
704
705 TAILQ_INSERT_TAIL(&runlist, run, list);
706 }
707
708 static void
709 curl_event_handle(void *arg, int eof)
710 {
711 curl_run_schedule(arg, eof);
712 }
713
714 static void
715 curl_run_handle(struct curl_run *run)
716 {
717 CURLMcode res;
718 int flags;
719 struct fd_cache *fdc = run->fdc;
720
721 flags = 0;
722
723 if (fdc->evt.flags & KORE_EVENT_READ)
724 flags |= CURL_CSELECT_IN;
725
726 if (fdc->evt.flags & KORE_EVENT_WRITE)
727 flags |= CURL_CSELECT_OUT;
728
729 if (run->eof)
730 flags |= CURL_CSELECT_ERR;
731
732 res = curl_multi_socket_action(multi, fdc->fd, flags, &running);
733 if (res != CURLM_OK)
734 fatal("curl_multi_socket_action: %s", curl_multi_strerror(res));
735 }
736
737 static struct fd_cache *
738 fd_cache_get(int fd)
739 {
740 struct fd_cache *fdc;
741 int bucket;
742
743 bucket = fd % FD_CACHE_BUCKETS;
744
745 LIST_FOREACH(fdc, &cache[bucket], list) {
746 if (fdc->fd == fd)
747 return (fdc);
748 }
749
750 fdc = kore_pool_get(&fd_cache_pool);
751
752 fdc->fd = fd;
753 fdc->scheduled = 0;
754
755 fdc->evt.flags = 0;
756 fdc->evt.handle = curl_event_handle;
757 fdc->evt.type = KORE_TYPE_CURL_HANDLE;
758
759 LIST_INSERT_HEAD(&cache[bucket], fdc, list);
760
761 return (fdc);
762 }