1 /*
2 * libwebsockets - small server side websockets and web server implementation
3 *
4 * Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 */
24
25 #if !defined (LWS_PLUGIN_STATIC)
26 #if !defined(LWS_DLL)
27 #define LWS_DLL
28 #endif
29 #if !defined(LWS_INTERNAL)
30 #define LWS_INTERNAL
31 #endif
32 #include <libwebsockets.h>
33 #endif
34
35 #include <stdlib.h>
36 #include <string.h>
37 #include <fcntl.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <dirent.h>
41 #ifdef WIN32
42 #include <io.h>
43 #endif
44 #include <stdio.h>
45 #include <errno.h>
46
47 struct dir_entry {
48 lws_list_ptr next; /* sorted by mtime */
49 char user[32];
50 unsigned long long size;
51 time_t mtime;
52 };
53 /* filename follows */
54
55 #define lp_to_dir_entry(p, _n) lws_list_ptr_container(p, struct dir_entry, _n)
56
57 struct pss_deaddrop;
58
59 struct vhd_deaddrop {
60 struct lws_context *context;
61 struct lws_vhost *vh;
62 const struct lws_protocols *protocol;
63
64 struct pss_deaddrop *pss_head;
65
66 const char *upload_dir;
67
68 struct lwsac *lwsac_head;
69 struct dir_entry *dire_head;
70 int filelist_version;
71
72 unsigned long long max_size;
73 };
74
75 struct pss_deaddrop {
76 struct lws_spa *spa;
77 struct vhd_deaddrop *vhd;
78 struct lws *wsi;
79 char result[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE];
80 char filename[256];
81 char user[32];
82 unsigned long long file_length;
83 lws_filefd_type fd;
84 int response_code;
85
86 struct pss_deaddrop *pss_list;
87
88 struct lwsac *lwsac_head;
89 struct dir_entry *dire;
90 int filelist_version;
91
92 uint8_t completed:1;
93 uint8_t sent_headers:1;
94 uint8_t sent_body:1;
95 uint8_t first:1;
96 };
97
98 static const char * const param_names[] = {
99 "text",
100 "send",
101 "file",
102 "upload",
103 };
104
105 enum enum_param_names {
106 EPN_TEXT,
107 EPN_SEND,
108 EPN_FILE,
109 EPN_UPLOAD,
110 };
111
112 static int
de_mtime_sort(lws_list_ptr a,lws_list_ptr b)113 de_mtime_sort(lws_list_ptr a, lws_list_ptr b)
114 {
115 struct dir_entry *p1 = lp_to_dir_entry(a, next),
116 *p2 = lp_to_dir_entry(b, next);
117
118 return (int)(p2->mtime - p1->mtime);
119 }
120
121 static void
start_sending_dir(struct pss_deaddrop * pss)122 start_sending_dir(struct pss_deaddrop *pss)
123 {
124 if (pss->vhd->lwsac_head)
125 lwsac_reference(pss->vhd->lwsac_head);
126 pss->lwsac_head = pss->vhd->lwsac_head;
127 pss->dire = pss->vhd->dire_head;
128 pss->filelist_version = pss->vhd->filelist_version;
129 pss->first = 1;
130 }
131
132 static int
scan_upload_dir(struct vhd_deaddrop * vhd)133 scan_upload_dir(struct vhd_deaddrop *vhd)
134 {
135 char filepath[256], subdir[3][128], *p;
136 struct lwsac *lwsac_head = NULL;
137 lws_list_ptr sorted_head = NULL;
138 int i, sp = 0, found = 0;
139 struct dir_entry *dire;
140 struct dirent *de;
141 size_t initial, m;
142 struct stat s;
143 DIR *dir[3];
144
145 initial = strlen(vhd->upload_dir) + 1;
146 lws_strncpy(subdir[sp], vhd->upload_dir, sizeof(subdir[sp]));
147 dir[sp] = opendir(vhd->upload_dir);
148 if (!dir[sp]) {
149 lwsl_err("%s: Unable to walk upload dir '%s'\n", __func__,
150 vhd->upload_dir);
151 return -1;
152 }
153
154 do {
155 de = readdir(dir[sp]);
156 if (!de) {
157 closedir(dir[sp]);
158 #if !defined(__COVERITY__)
159 if (!sp)
160 #endif
161 break;
162 #if !defined(__COVERITY__)
163 sp--;
164 continue;
165 #endif
166 }
167
168 p = filepath;
169
170 for (i = 0; i <= sp; i++)
171 p += lws_snprintf(p, lws_ptr_diff_size_t((filepath + sizeof(filepath)), p),
172 "%s/", subdir[i]);
173
174 lws_snprintf(p, lws_ptr_diff_size_t((filepath + sizeof(filepath)), p), "%s",
175 de->d_name);
176
177 /* ignore temp files */
178 if (de->d_name[strlen(de->d_name) - 1] == '~')
179 continue;
180 #if defined(__COVERITY__)
181 s.st_size = 0;
182 s.st_mtime = 0;
183 #else
184 /* coverity[toctou] */
185 if (stat(filepath, &s))
186 continue;
187
188 if (S_ISDIR(s.st_mode)) {
189 if (!strcmp(de->d_name, ".") ||
190 !strcmp(de->d_name, ".."))
191 continue;
192 sp++;
193 if (sp == LWS_ARRAY_SIZE(dir)) {
194 lwsl_err("%s: Skipping too-deep subdir %s\n",
195 __func__, filepath);
196 sp--;
197 continue;
198 }
199 lws_strncpy(subdir[sp], de->d_name, sizeof(subdir[sp]));
200 dir[sp] = opendir(filepath);
201 if (!dir[sp]) {
202 lwsl_err("%s: Unable to open subdir '%s'\n",
203 __func__, filepath);
204 goto bail;
205 }
206 continue;
207 }
208 #endif
209
210 m = strlen(filepath + initial) + 1;
211 dire = lwsac_use(&lwsac_head, sizeof(*dire) + m, 0);
212 if (!dire) {
213 lwsac_free(&lwsac_head);
214
215 goto bail;
216 }
217
218 dire->next = NULL;
219 dire->size = (unsigned long long)s.st_size;
220 dire->mtime = s.st_mtime;
221 dire->user[0] = '\0';
222 #if !defined(__COVERITY__)
223 if (sp)
224 lws_strncpy(dire->user, subdir[1], sizeof(dire->user));
225 #endif
226
227 found++;
228
229 memcpy(&dire[1], filepath + initial, m);
230
231 lws_list_ptr_insert(&sorted_head, &dire->next, de_mtime_sort);
232 } while (1);
233
234 /* the old lwsac continues to live while someone else is consuming it */
235 if (vhd->lwsac_head)
236 lwsac_detach(&vhd->lwsac_head);
237
238 /* we replace it with the fresh one */
239 vhd->lwsac_head = lwsac_head;
240 if (sorted_head)
241 vhd->dire_head = lp_to_dir_entry(sorted_head, next);
242 else
243 vhd->dire_head = NULL;
244
245 vhd->filelist_version++;
246
247 lwsl_info("%s: found %d\n", __func__, found);
248
249 lws_start_foreach_llp(struct pss_deaddrop **, ppss, vhd->pss_head) {
250 start_sending_dir(*ppss);
251 lws_callback_on_writable((*ppss)->wsi);
252 } lws_end_foreach_llp(ppss, pss_list);
253
254 return 0;
255
256 bail:
257 while (sp >= 0)
258 closedir(dir[sp--]);
259
260 return -1;
261 }
262
263 static int
file_upload_cb(void * data,const char * name,const char * filename,char * buf,int _len,enum lws_spa_fileupload_states state)264 file_upload_cb(void *data, const char *name, const char *filename,
265 char *buf, int _len, enum lws_spa_fileupload_states state)
266 {
267 struct pss_deaddrop *pss = (struct pss_deaddrop *)data;
268 char filename2[256];
269 size_t len = (size_t)_len;
270 int n;
271
272 (void)n;
273
274 switch (state) {
275 case LWS_UFS_OPEN:
276 lws_urldecode(filename2, filename, sizeof(filename2) - 1);
277 lws_filename_purify_inplace(filename2);
278 if (pss->user[0]) {
279 lws_filename_purify_inplace(pss->user);
280 lws_snprintf(pss->filename, sizeof(pss->filename),
281 "%s/%s", pss->vhd->upload_dir, pss->user);
282 if (mkdir(pss->filename
283 #if !defined(WIN32)
284 , 0700
285 #endif
286 ) < 0)
287 lwsl_debug("%s: mkdir failed\n", __func__);
288 lws_snprintf(pss->filename, sizeof(pss->filename),
289 "%s/%s/%s~", pss->vhd->upload_dir,
290 pss->user, filename2);
291 } else
292 lws_snprintf(pss->filename, sizeof(pss->filename),
293 "%s/%s~", pss->vhd->upload_dir, filename2);
294 lwsl_notice("%s: filename '%s'\n", __func__, pss->filename);
295
296 pss->fd = (lws_filefd_type)(long long)lws_open(pss->filename,
297 O_CREAT | O_TRUNC | O_RDWR, 0600);
298 if (pss->fd == LWS_INVALID_FILE) {
299 pss->response_code = HTTP_STATUS_INTERNAL_SERVER_ERROR;
300 lwsl_err("%s: unable to open %s (errno %d)\n", __func__,
301 pss->filename, errno);
302 return -1;
303 }
304 break;
305
306 case LWS_UFS_FINAL_CONTENT:
307 case LWS_UFS_CONTENT:
308 if (len) {
309 pss->file_length += (unsigned int)len;
310
311 /* if the file length is too big, drop it */
312 if (pss->file_length > pss->vhd->max_size) {
313 pss->response_code =
314 HTTP_STATUS_REQ_ENTITY_TOO_LARGE;
315 close((int)(lws_intptr_t)pss->fd);
316 pss->fd = LWS_INVALID_FILE;
317 unlink(pss->filename);
318
319 return -1;
320 }
321
322 if (pss->fd != LWS_INVALID_FILE) {
323 n = (int)write((int)(lws_intptr_t)pss->fd, buf, (unsigned int)len);
324 lwsl_debug("%s: write %d says %d\n", __func__,
325 (int)len, n);
326 lws_set_timeout(pss->wsi, PENDING_TIMEOUT_HTTP_CONTENT, 30);
327 }
328 }
329 if (state == LWS_UFS_CONTENT)
330 break;
331
332 if (pss->fd != LWS_INVALID_FILE)
333 close((int)(lws_intptr_t)pss->fd);
334
335 /* the temp filename without the ~ */
336 lws_strncpy(filename2, pss->filename, sizeof(filename2));
337 filename2[strlen(filename2) - 1] = '\0';
338 if (rename(pss->filename, filename2) < 0)
339 lwsl_err("%s: unable to rename\n", __func__);
340
341 pss->fd = LWS_INVALID_FILE;
342 pss->response_code = HTTP_STATUS_OK;
343 scan_upload_dir(pss->vhd);
344
345 break;
346 case LWS_UFS_CLOSE:
347 break;
348 }
349
350 return 0;
351 }
352
353 /*
354 * returns length in bytes
355 */
356
357 static int
format_result(struct pss_deaddrop * pss)358 format_result(struct pss_deaddrop *pss)
359 {
360 unsigned char *p, *start, *end;
361
362 p = (unsigned char *)pss->result + LWS_PRE;
363 start = p;
364 end = p + sizeof(pss->result) - LWS_PRE - 1;
365
366 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),
367 "<!DOCTYPE html><html lang=\"en\"><head>"
368 "<meta charset=utf-8 http-equiv=\"Content-Language\" "
369 "content=\"en\"/>"
370 "</head>");
371 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "</body></html>");
372
373 return (int)lws_ptr_diff(p, start);
374 }
375
376 static int
callback_deaddrop(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)377 callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
378 void *user, void *in, size_t len)
379 {
380 struct vhd_deaddrop *vhd = (struct vhd_deaddrop *)
381 lws_protocol_vh_priv_get(lws_get_vhost(wsi),
382 lws_get_protocol(wsi));
383 struct pss_deaddrop *pss = (struct pss_deaddrop *)user;
384 uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE],
385 *start = &buf[LWS_PRE], *p = start,
386 *end = &buf[sizeof(buf) - 1];
387 char fname[256], *wp;
388 const char *cp;
389 int n, m, was;
390
391 switch (reason) {
392
393 case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
394 lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
395 lws_get_protocol(wsi),
396 sizeof(struct vhd_deaddrop));
397
398 vhd = (struct vhd_deaddrop *)
399 lws_protocol_vh_priv_get(lws_get_vhost(wsi),
400 lws_get_protocol(wsi));
401 if (!vhd)
402 return 0;
403
404 vhd->context = lws_get_context(wsi);
405 vhd->vh = lws_get_vhost(wsi);
406 vhd->protocol = lws_get_protocol(wsi);
407 vhd->max_size = 20 * 1024 * 1024; /* default without pvo */
408
409 if (!lws_pvo_get_str(in, "max-size", &cp))
410 vhd->max_size = (unsigned long long)atoll(cp);
411 if (lws_pvo_get_str(in, "upload-dir", &vhd->upload_dir)) {
412 lwsl_warn("%s: requires 'upload-dir' pvo\n", __func__);
413 return 0;
414 }
415
416 scan_upload_dir(vhd);
417
418 lwsl_notice(" deaddrop: vh %s, upload dir %s, max size %llu\n",
419 lws_get_vhost_name(vhd->vh), vhd->upload_dir,
420 vhd->max_size);
421 break;
422
423 case LWS_CALLBACK_PROTOCOL_DESTROY:
424 if (vhd)
425 lwsac_free(&vhd->lwsac_head);
426 break;
427
428 /* WS-related */
429
430 case LWS_CALLBACK_ESTABLISHED:
431 pss->vhd = vhd;
432 pss->wsi = wsi;
433 /* add ourselves to the list of live pss held in the vhd */
434 pss->pss_list = vhd->pss_head;
435 vhd->pss_head = pss;
436
437 m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user),
438 WSI_TOKEN_HTTP_AUTHORIZATION);
439 if (m > 0)
440 lwsl_info("%s: basic auth user: %s\n",
441 __func__, pss->user);
442 else
443 pss->user[0] = '\0';
444
445 start_sending_dir(pss);
446 lws_callback_on_writable(wsi);
447 return 0;
448
449 case LWS_CALLBACK_CLOSED:
450 if (pss->lwsac_head)
451 lwsac_unreference(&pss->lwsac_head);
452 /* remove our closing pss from the list of live pss */
453 lws_start_foreach_llp(struct pss_deaddrop **,
454 ppss, vhd->pss_head) {
455 if (*ppss == pss) {
456 *ppss = pss->pss_list;
457 break;
458 }
459 } lws_end_foreach_llp(ppss, pss_list);
460 return 0;
461
462 case LWS_CALLBACK_RECEIVE:
463 /* we get this kind of thing {"del":"agreen/no-entry.svg"} */
464 if (!pss || len < 10)
465 break;
466
467 if (strncmp((const char *)in, "{\"del\":\"", 8))
468 break;
469
470 cp = strchr((const char *)in, '/');
471 if (cp) {
472 n = (int)(((void *)cp - in)) - 8;
473
474 if ((int)strlen(pss->user) != n ||
475 memcmp(pss->user, ((const char *)in) + 8, (unsigned int)n)) {
476 lwsl_notice("%s: del: auth mismatch "
477 " '%s' '%s' (%d)\n",
478 __func__, pss->user,
479 ((const char *)in) + 8, n);
480 break;
481 }
482 }
483
484 lws_strncpy(fname, ((const char *)in) + 8, sizeof(fname));
485 lws_filename_purify_inplace(fname);
486 wp = strchr((const char *)fname, '\"');
487 if (wp)
488 *wp = '\0';
489
490 lws_snprintf((char *)buf, sizeof(buf), "%s/%s", vhd->upload_dir,
491 fname);
492
493 lwsl_notice("%s: del: path %s\n", __func__, (const char *)buf);
494
495 if (unlink((const char *)buf) < 0)
496 lwsl_err("%s: unlink %s failed\n", __func__,
497 (const char *)buf);
498
499 scan_upload_dir(vhd);
500 break;
501
502 case LWS_CALLBACK_SERVER_WRITEABLE:
503 if (pss->lwsac_head && !pss->dire)
504 return 0;
505
506 was = 0;
507 if (pss->first) {
508 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),
509 "{\"max_size\":%llu, \"files\": [",
510 vhd->max_size);
511 was = 1;
512 }
513
514 m = 5;
515 while (m-- && pss->dire) {
516 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),
517 "%c{\"name\":\"%s\", "
518 "\"size\":%llu,"
519 "\"mtime\":%llu,"
520 "\"yours\":%d}",
521 pss->first ? ' ' : ',',
522 (const char *)&pss->dire[1],
523 pss->dire->size,
524 (unsigned long long)pss->dire->mtime,
525 !strcmp(pss->user, pss->dire->user) &&
526 pss->user[0]);
527 pss->first = 0;
528 pss->dire = lp_to_dir_entry(pss->dire->next, next);
529 }
530
531 if (!pss->dire) {
532 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),
533 "]}");
534 if (pss->lwsac_head) {
535 lwsac_unreference(&pss->lwsac_head);
536 pss->lwsac_head = NULL;
537 }
538 }
539
540 n = lws_write(wsi, start, lws_ptr_diff_size_t(p, start),
541 (enum lws_write_protocol)lws_write_ws_flags(LWS_WRITE_TEXT, was,
542 !pss->dire));
543 if (n < 0) {
544 lwsl_notice("%s: ws write failed\n", __func__);
545 return 1;
546 }
547 if (pss->dire) {
548 lws_callback_on_writable(wsi);
549
550 return 0;
551 }
552
553 /* ie, we finished */
554
555 if (pss->filelist_version != pss->vhd->filelist_version) {
556 lwsl_info("%s: restart send\n", __func__);
557 /* what we just sent is already out of date */
558 start_sending_dir(pss);
559 lws_callback_on_writable(wsi);
560 }
561
562 return 0;
563
564 /* POST-related */
565
566 case LWS_CALLBACK_HTTP_BODY:
567
568 /* create the POST argument parser if not already existing */
569 if (!pss->spa) {
570 pss->vhd = vhd;
571 pss->wsi = wsi;
572 pss->spa = lws_spa_create(wsi, param_names,
573 LWS_ARRAY_SIZE(param_names),
574 1024, file_upload_cb, pss);
575 if (!pss->spa)
576 return -1;
577
578 pss->filename[0] = '\0';
579 pss->file_length = 0;
580 /* catchall */
581 pss->response_code = HTTP_STATUS_SERVICE_UNAVAILABLE;
582
583 m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user),
584 WSI_TOKEN_HTTP_AUTHORIZATION);
585 if (m > 0)
586 lwsl_info("basic auth user: %s\n", pss->user);
587 else
588 pss->user[0] = '\0';
589 }
590
591 /* let it parse the POST data */
592 if (lws_spa_process(pss->spa, in, (int)len)) {
593 lwsl_notice("spa saw a problem\n");
594 /* some problem happened */
595 lws_spa_finalize(pss->spa);
596
597 pss->completed = 1;
598 lws_callback_on_writable(wsi);
599 }
600 break;
601
602 case LWS_CALLBACK_HTTP_BODY_COMPLETION:
603 /* call to inform no more payload data coming */
604 lws_spa_finalize(pss->spa);
605
606 pss->completed = 1;
607 lws_callback_on_writable(wsi);
608 break;
609
610 case LWS_CALLBACK_HTTP_WRITEABLE:
611 if (!pss->completed)
612 break;
613
614 p = (unsigned char *)pss->result + LWS_PRE;
615 start = p;
616 end = p + sizeof(pss->result) - LWS_PRE - 1;
617
618 if (!pss->sent_headers) {
619 n = format_result(pss);
620
621 if (lws_add_http_header_status(wsi,
622 (unsigned int)pss->response_code,
623 &p, end))
624 goto bail;
625
626 if (lws_add_http_header_by_token(wsi,
627 WSI_TOKEN_HTTP_CONTENT_TYPE,
628 (unsigned char *)"text/html", 9,
629 &p, end))
630 goto bail;
631 if (lws_add_http_header_content_length(wsi, (lws_filepos_t)n, &p, end))
632 goto bail;
633 if (lws_finalize_http_header(wsi, &p, end))
634 goto bail;
635
636 /* first send the headers ... */
637 n = lws_write(wsi, start, lws_ptr_diff_size_t(p, start),
638 LWS_WRITE_HTTP_HEADERS |
639 LWS_WRITE_H2_STREAM_END);
640 if (n < 0)
641 goto bail;
642
643 pss->sent_headers = 1;
644 lws_callback_on_writable(wsi);
645 break;
646 }
647
648 if (!pss->sent_body) {
649 n = format_result(pss);
650 n = lws_write(wsi, (unsigned char *)start, (unsigned int)n,
651 LWS_WRITE_HTTP_FINAL);
652
653 pss->sent_body = 1;
654 if (n < 0) {
655 lwsl_err("%s: writing body failed\n", __func__);
656 return 1;
657 }
658 goto try_to_reuse;
659 }
660 break;
661
662 case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
663 /* called when our wsi user_space is going to be destroyed */
664 if (pss->spa) {
665 lws_spa_destroy(pss->spa);
666 pss->spa = NULL;
667 }
668 break;
669
670 default:
671 break;
672 }
673
674 return 0;
675
676 bail:
677
678 return 1;
679
680 try_to_reuse:
681 if (lws_http_transaction_completed(wsi))
682 return -1;
683
684 return 0;
685 }
686
687 #define LWS_PLUGIN_PROTOCOL_DEADDROP \
688 { \
689 "lws-deaddrop", \
690 callback_deaddrop, \
691 sizeof(struct pss_deaddrop), \
692 1024, \
693 0, NULL, 0 \
694 }
695
696 #if !defined (LWS_PLUGIN_STATIC)
697
698 LWS_VISIBLE const struct lws_protocols deaddrop_protocols[] = {
699 LWS_PLUGIN_PROTOCOL_DEADDROP
700 };
701
702 LWS_VISIBLE const lws_plugin_protocol_t deaddrop = {
703 .hdr = {
704 "deaddrop",
705 "lws_protocol_plugin",
706 LWS_BUILD_HASH,
707 LWS_PLUGIN_API_MAGIC
708 },
709
710 .protocols = deaddrop_protocols,
711 .count_protocols = LWS_ARRAY_SIZE(deaddrop_protocols),
712 .extensions = NULL,
713 .count_extensions = 0,
714 };
715
716 #endif
717