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
47struct 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
57struct pss_deaddrop;
58
59struct 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
75struct 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
98static const char * const param_names[] = {
99	"text",
100	"send",
101	"file",
102	"upload",
103};
104
105enum enum_param_names {
106	EPN_TEXT,
107	EPN_SEND,
108	EPN_FILE,
109	EPN_UPLOAD,
110};
111
112static int
113de_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
121static void
122start_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
132static int
133scan_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
256bail:
257	while (sp >= 0)
258		closedir(dir[sp--]);
259
260	return -1;
261}
262
263static int
264file_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
357static int
358format_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
376static int
377callback_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
676bail:
677
678	return 1;
679
680try_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
698LWS_VISIBLE const struct lws_protocols deaddrop_protocols[] = {
699	LWS_PLUGIN_PROTOCOL_DEADDROP
700};
701
702LWS_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