• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * libwebsockets - small server side websockets and web server implementation
3  *
4  * Copyright (C) 2010 - 2019 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 
26 #define LWS_DLL
27 #define LWS_INTERNAL
28 #include <libwebsockets.h>
29 
30 #include <sqlite3.h>
31 #include <string.h>
32 #include <stdlib.h>
33 
34 struct per_vhost_data__gs_mb {
35 	struct lws_vhost *vh;
36 	const struct lws_protocols *gsp;
37 	sqlite3 *pdb;
38 	char message_db[256];
39 	unsigned long last_idx;
40 };
41 
42 struct per_session_data__gs_mb {
43 	void *pss_gs; /* for use by generic-sessions */
44 	struct lws_session_info sinfo;
45 	struct lws_spa *spa;
46 	unsigned long last_idx;
47 	unsigned int our_form:1;
48 	char second_http_part;
49 };
50 
51 static const char * const param_names[] = {
52 	"send",
53 	"msg",
54 };
55 enum {
56 	MBSPA_SUBMIT,
57 	MBSPA_MSG,
58 };
59 
60 #define MAX_MSG_LEN 512
61 
62 struct message {
63 	unsigned long idx;
64 	unsigned long time;
65 	char username[32];
66 	char email[100];
67 	char ip[72];
68 	char content[MAX_MSG_LEN];
69 };
70 
71 static int
lookup_cb(void * priv,int cols,char ** col_val,char ** col_name)72 lookup_cb(void *priv, int cols, char **col_val, char **col_name)
73 {
74 	struct message *m = (struct message *)priv;
75 	int n;
76 
77 	for (n = 0; n < cols; n++) {
78 
79 		if (!strcmp(col_name[n], "idx") ||
80 		    !strcmp(col_name[n], "MAX(idx)")) {
81 			if (!col_val[n])
82 				m->idx = 0;
83 			else
84 				m->idx = atol(col_val[n]);
85 			continue;
86 		}
87 		if (!strcmp(col_name[n], "time")) {
88 			m->time = atol(col_val[n]);
89 			continue;
90 		}
91 		if (!strcmp(col_name[n], "username")) {
92 			lws_strncpy(m->username, col_val[n], sizeof(m->username));
93 			continue;
94 		}
95 		if (!strcmp(col_name[n], "email")) {
96 			lws_strncpy(m->email, col_val[n], sizeof(m->email));
97 			continue;
98 		}
99 		if (!strcmp(col_name[n], "ip")) {
100 			lws_strncpy(m->ip, col_val[n], sizeof(m->ip));
101 			continue;
102 		}
103 		if (!strcmp(col_name[n], "content")) {
104 			lws_strncpy(m->content, col_val[n], sizeof(m->content));
105 			continue;
106 		}
107 	}
108 	return 0;
109 }
110 
111 static unsigned long
get_last_idx(struct per_vhost_data__gs_mb * vhd)112 get_last_idx(struct per_vhost_data__gs_mb *vhd)
113 {
114 	struct message m;
115 
116 	if (sqlite3_exec(vhd->pdb, "SELECT MAX(idx) FROM msg;",
117 			 lookup_cb, &m, NULL) != SQLITE_OK) {
118 		lwsl_err("Unable to lookup token: %s\n",
119 			 sqlite3_errmsg(vhd->pdb));
120 		return 0;
121 	}
122 
123 	return m.idx;
124 }
125 
126 static int
post_message(struct lws * wsi,struct per_vhost_data__gs_mb * vhd,struct per_session_data__gs_mb * pss)127 post_message(struct lws *wsi, struct per_vhost_data__gs_mb *vhd,
128 	     struct per_session_data__gs_mb *pss)
129 {
130 	struct lws_session_info sinfo;
131 	char s[MAX_MSG_LEN + 512];
132 	char esc[MAX_MSG_LEN + 256];
133 
134 	vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO,
135 			   pss->pss_gs, &sinfo, 0);
136 
137 	lws_snprintf((char *)s, sizeof(s) - 1,
138 		 "insert into msg(time, username, email, ip, content)"
139 		 " values (%lu, '%s', '%s', '%s', '%s');",
140 		 (unsigned long)lws_now_secs(), sinfo.username, sinfo.email, sinfo.ip,
141 		 lws_sql_purify(esc, lws_spa_get_string(pss->spa, MBSPA_MSG),
142 			        sizeof(esc) - 1));
143 	if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) {
144 		lwsl_err("Unable to insert msg: %s\n", sqlite3_errmsg(vhd->pdb));
145 		return 1;
146 	}
147 	vhd->last_idx = get_last_idx(vhd);
148 
149 	/* let everybody connected by this protocol on this vhost know */
150 	lws_callback_on_writable_all_protocol_vhost(lws_get_vhost(wsi),
151 						    lws_get_protocol(wsi));
152 
153 	return 0;
154 }
155 
156 static int
callback_messageboard(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)157 callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
158 		      void *user, void *in, size_t len)
159 {
160 	struct per_session_data__gs_mb *pss = (struct per_session_data__gs_mb *)user;
161 	const struct lws_protocol_vhost_options *pvo;
162 	struct per_vhost_data__gs_mb *vhd = (struct per_vhost_data__gs_mb *)
163 		lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi));
164 	unsigned char *p, *start, *end, buffer[LWS_PRE + 4096];
165 	char s[512];
166 	int n;
167 
168 	switch (reason) {
169 	case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
170 
171 		vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
172 			lws_get_protocol(wsi), sizeof(struct per_vhost_data__gs_mb));
173 		if (!vhd)
174 			return 1;
175 		vhd->vh = lws_get_vhost(wsi);
176 		vhd->gsp = lws_vhost_name_to_protocol(vhd->vh,
177 						"protocol-generic-sessions");
178 		if (!vhd->gsp) {
179 			lwsl_err("messageboard: requires generic-sessions\n");
180 			return 1;
181 		}
182 
183 		pvo = (const struct lws_protocol_vhost_options *)in;
184 		while (pvo) {
185 			if (!strcmp(pvo->name, "message-db"))
186 				strncpy(vhd->message_db, pvo->value,
187 					sizeof(vhd->message_db) - 1);
188 			pvo = pvo->next;
189 		}
190 		if (!vhd->message_db[0]) {
191 			lwsl_err("messageboard: \"message-db\" pvo missing\n");
192 			return 1;
193 		}
194 
195 		if (lws_struct_sq3_open(lws_get_context(wsi),
196 					vhd->message_db, &vhd->pdb)) {
197 			lwsl_err("Unable to open message db %s: %s\n",
198 				 vhd->message_db, sqlite3_errmsg(vhd->pdb));
199 
200 			return 1;
201 		}
202 		if (sqlite3_exec(vhd->pdb, "create table if not exists msg ("
203 				 " idx integer primary key, time integer,"
204 				 " username varchar(32), email varchar(100),"
205 				 " ip varchar(80), content blob);",
206 				 NULL, NULL, NULL) != SQLITE_OK) {
207 			lwsl_err("Unable to create msg table: %s\n",
208 				 sqlite3_errmsg(vhd->pdb));
209 
210 			return 1;
211 		}
212 
213 		vhd->last_idx = get_last_idx(vhd);
214 		break;
215 
216 	case LWS_CALLBACK_PROTOCOL_DESTROY:
217 		if (vhd && vhd->pdb)
218 			sqlite3_close(vhd->pdb);
219 		goto passthru;
220 
221 	case LWS_CALLBACK_ESTABLISHED:
222 		vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO,
223 				   pss->pss_gs, &pss->sinfo, 0);
224 		if (!pss->sinfo.username[0]) {
225 			lwsl_notice("messageboard ws attempt with no session\n");
226 
227 			return -1;
228 		}
229 
230 		lws_callback_on_writable(wsi);
231 		break;
232 
233 	case LWS_CALLBACK_CLOSED:
234 		lwsl_debug("%s: LWS_CALLBACK_CLOSED\n", __func__);
235 		if (pss && pss->pss_gs) {
236 			free(pss->pss_gs);
237 			pss->pss_gs = NULL;
238 		}
239 		break;
240 
241 	case LWS_CALLBACK_SERVER_WRITEABLE:
242 		{
243 			struct message m;
244 			char j[MAX_MSG_LEN + 512], e[MAX_MSG_LEN + 512],
245 				*p = j + LWS_PRE, *start = p,
246 				*end = j + sizeof(j) - LWS_PRE;
247 
248 			if (pss->last_idx == vhd->last_idx)
249 				break;
250 
251 			/* restrict to last 10 */
252 			if (!pss->last_idx)
253 				if (vhd->last_idx >= 10)
254 					pss->last_idx = vhd->last_idx - 10;
255 
256 			sprintf(s, "select idx, time, username, email, ip, content "
257 				   "from msg where idx > %lu order by idx limit 1;",
258 				   pss->last_idx);
259 			if (sqlite3_exec(vhd->pdb, s, lookup_cb, &m, NULL) != SQLITE_OK) {
260 				lwsl_err("Unable to lookup msg: %s\n",
261 					 sqlite3_errmsg(vhd->pdb));
262 				return 0;
263 			}
264 
265 			/* format in JSON */
266 			p += lws_snprintf(p, end - p,
267 					"{\"idx\":\"%lu\",\"time\":\"%lu\",",
268 					m.idx, m.time);
269 			p += lws_snprintf(p, end - p, " \"username\":\"%s\",",
270 				lws_json_purify(e, m.username, sizeof(e), NULL));
271 			p += lws_snprintf(p, end - p, " \"email\":\"%s\",",
272 				lws_json_purify(e, m.email, sizeof(e), NULL));
273 			p += lws_snprintf(p, end - p, " \"ip\":\"%s\",",
274 				lws_json_purify(e, m.ip, sizeof(e), NULL));
275 			p += lws_snprintf(p, end - p, " \"content\":\"%s\"}",
276 				lws_json_purify(e, m.content, sizeof(e), NULL));
277 
278 			if (lws_write(wsi, (unsigned char *)start, p - start,
279 				      LWS_WRITE_TEXT) < 0)
280 				return -1;
281 
282 			pss->last_idx = m.idx;
283 			if (pss->last_idx == vhd->last_idx)
284 				break;
285 
286 			lws_callback_on_writable(wsi); /* more to do */
287 		}
288 		break;
289 
290 	case LWS_CALLBACK_HTTP:
291 		pss->our_form = 0;
292 
293 		/* ie, it's our messageboard new message form */
294 		if (!strcmp((const char *)in, "/msg") ||
295 		    !strcmp((const char *)in, "msg")) {
296 			pss->our_form = 1;
297 			break;
298 		}
299 
300 		goto passthru;
301 
302 	case LWS_CALLBACK_HTTP_BODY:
303 		if (!pss->our_form)
304 			goto passthru;
305 
306 		if (len < 2)
307 			break;
308 		if (!pss->spa) {
309 			pss->spa = lws_spa_create(wsi, param_names,
310 					LWS_ARRAY_SIZE(param_names),
311 						MAX_MSG_LEN + 1024, NULL, NULL);
312 			if (!pss->spa)
313 				return -1;
314 		}
315 
316 		if (lws_spa_process(pss->spa, in, len)) {
317 			lwsl_notice("spa process blew\n");
318 			return -1;
319 		}
320 		break;
321 
322 	case LWS_CALLBACK_HTTP_WRITEABLE:
323 		if (!pss->second_http_part)
324 			goto passthru;
325 
326 		s[0] = '0';
327 		n = lws_write(wsi, (unsigned char *)s, 1, LWS_WRITE_HTTP|
328 				LWS_WRITE_H2_STREAM_END);
329 		if (n != 1)
330 			return -1;
331 
332 		goto try_to_reuse;
333 
334 	case LWS_CALLBACK_HTTP_BODY_COMPLETION:
335 		if (!pss->our_form)
336 			goto passthru;
337 
338 		if (post_message(wsi, vhd, pss))
339 			return -1;
340 
341 		p = buffer + LWS_PRE;
342 		start = p;
343 		end = p + sizeof(buffer) - LWS_PRE;
344 
345 		if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
346 			return -1;
347 		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
348 				(unsigned char *)"text/plain", 10, &p, end))
349 			return -1;
350 		if (lws_add_http_header_content_length(wsi, 1, &p, end))
351 			return -1;
352 		if (lws_finalize_http_header(wsi, &p, end))
353 			return -1;
354 
355 		n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
356 		if (n != (p - start)) {
357 			lwsl_err("_write returned %d from %ld\n", n, (long)(p - start));
358 			return -1;
359 		}
360 		pss->second_http_part = 1;
361 		lws_callback_on_writable(wsi);
362 		break;
363 
364 	case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
365 		if (!pss || !vhd || pss->pss_gs)
366 			break;
367 
368 		pss->pss_gs = malloc(vhd->gsp->per_session_data_size);
369 		if (!pss->pss_gs)
370 			return -1;
371 
372 		memset(pss->pss_gs, 0, vhd->gsp->per_session_data_size);
373 		break;
374 
375 	case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
376 		if (vhd->gsp->callback(wsi, reason, pss ? pss->pss_gs : NULL, in, len))
377 			return -1;
378 
379 		if (pss && pss->spa) {
380 			lws_spa_destroy(pss->spa);
381 			pss->spa = NULL;
382 		}
383 		if (pss && pss->pss_gs) {
384 			free(pss->pss_gs);
385 			pss->pss_gs = NULL;
386 		}
387 		break;
388 
389 	default:
390 passthru:
391 		if (!pss || !vhd)
392 			break;
393 
394 		return vhd->gsp->callback(wsi, reason, pss->pss_gs, in, len);
395 	}
396 
397 	return 0;
398 
399 
400 try_to_reuse:
401 	if (lws_http_transaction_completed(wsi))
402 		return -1;
403 
404 	return 0;
405 }
406 
407 static const struct lws_protocols protocols[] = {
408 	{
409 		"protocol-lws-messageboard",
410 		callback_messageboard,
411 		sizeof(struct per_session_data__gs_mb),
412 		4096,
413 	},
414 };
415 
416 LWS_VISIBLE int
init_protocol_lws_messageboard(struct lws_context * context,struct lws_plugin_capability * c)417 init_protocol_lws_messageboard(struct lws_context *context,
418 			       struct lws_plugin_capability *c)
419 {
420 	if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
421 		lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
422 			 c->api_magic);
423 		return 1;
424 	}
425 
426 	c->protocols = protocols;
427 	c->count_protocols = LWS_ARRAY_SIZE(protocols);
428 	c->extensions = NULL;
429 	c->count_extensions = 0;
430 
431 	return 0;
432 }
433 
434 LWS_VISIBLE int
destroy_protocol_lws_messageboard(struct lws_context * context)435 destroy_protocol_lws_messageboard(struct lws_context *context)
436 {
437 	return 0;
438 }
439