1 /*
2 * File: dpip.c
3 *
4 * Copyright 2005-2015 Jorge Arellano Cid <jcid@dillo.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 */
12
13 #include <errno.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <stdarg.h>
17 #include <string.h>
18 #include <ctype.h>
19 #include <unistd.h> /* for close */
20 #include <fcntl.h> /* for fcntl */
21
22 #include "dpip.h"
23 #include "d_size.h"
24
25 #define RBUF_SZ 16*1024
26 //#define RBUF_SZ 1
27
28 #define DPIP_TAG_END " '>"
29 #define DPIP_MODE_SWITCH_TAG "cmd='start_send_page' "
30 #define MSG_ERR(...) fprintf(stderr, "[dpip]: " __VA_ARGS__)
31
32 /*
33 * Local variables
34 */
35 static const char Quote = '\'';
36
37 /*
38 * Basically the syntax of a dpip tag is:
39 *
40 * "<"[*alpha] *(<name>"="Quote<escaped_value>Quote) " "Quote">"
41 *
42 * 1.- No space is allowed around the "=" sign between a name and its value.
43 * 2.- The Quote character is not allowed in <name>.
44 * 3.- Attribute values stuff Quote as QuoteQuote.
45 *
46 * e.g. (with ' as Quote):
47 *
48 * <a='b' b='c' '> OK
49 * <dpi a='b i' b='12' '> OK
50 * <a='>' '> OK
51 * <a='ain''t no doubt' '> OK
52 * <a='ain''t b=''no'' b='' doubt' '> OK
53 * <a = '>' '> Wrong
54 *
55 * Notes:
56 *
57 * Restriction #1 is for easy finding of end of tag (EOT=Space+Quote+>).
58 * Restriction #2 can be removed, but what for? ;)
59 * The functions here provide for this functionality.
60 */
61
62 typedef enum {
63 SEEK_NAME,
64 MATCH_NAME,
65 SKIP_VALUE,
66 SKIP_QUOTE,
67 FOUND
68 } DpipTagParsingState;
69
70 /* ------------------------------------------------------------------------- */
71
72 /*
73 * Printf like function for building dpip commands.
74 * It takes care of dpip escaping of its arguments.
75 * NOTE : It ONLY accepts string parameters, and
76 * only one %s per parameter.
77 */
a_Dpip_build_cmd(const char * format,...)78 char *a_Dpip_build_cmd(const char *format, ...)
79 {
80 va_list argp;
81 char *p, *q, *s;
82 Dstr *cmd;
83
84 /* Don't allow Quote characters in attribute names */
85 if (strchr(format, Quote))
86 return NULL;
87
88 cmd = dStr_sized_new(64);
89 dStr_append_c(cmd, '<');
90 va_start(argp, format);
91 for (p = q = (char*)format; *q; ) {
92 p = strstr(q, "%s");
93 if (!p) {
94 dStr_append(cmd, q);
95 break;
96 } else {
97 /* Copy format's part */
98 while (q != p)
99 dStr_append_c(cmd, *q++);
100 q += 2;
101
102 dStr_append_c(cmd, Quote);
103 /* Stuff-copy of argument */
104 s = va_arg (argp, char *);
105 for ( ; *s; ++s) {
106 dStr_append_c(cmd, *s);
107 if (*s == Quote)
108 dStr_append_c(cmd, *s);
109 }
110 dStr_append_c(cmd, Quote);
111 }
112 }
113 va_end(argp);
114 dStr_append_c(cmd, ' ');
115 dStr_append_c(cmd, Quote);
116 dStr_append_c(cmd, '>');
117
118 p = cmd->str;
119 dStr_free(cmd, FALSE);
120 return p;
121 }
122
123 /*
124 * Task: given a tag, its size and an attribute name, return the
125 * attribute value (stuffing of ' is removed here).
126 *
127 * Return value: the attribute value, or NULL if not present or malformed.
128 */
a_Dpip_get_attr_l(const char * tag,size_t tagsize,const char * attrname)129 char *a_Dpip_get_attr_l(const char *tag, size_t tagsize, const char *attrname)
130 {
131 uint_t i, n = 0, found = 0;
132 const char *p, *q, *start;
133 char *r, *s, *val = NULL;
134 DpipTagParsingState state = SEEK_NAME;
135
136 if (!tag || !tagsize || !attrname || !*attrname)
137 return NULL;
138
139 for (i = 1; i < tagsize && !found; ++i) {
140 switch (state) {
141 case SEEK_NAME:
142 if (tag[i] == attrname[0] && (tag[i-1] == ' ' || tag[i-1] == '<')) {
143 n = 1;
144 state = MATCH_NAME;
145 } else if (tag[i] == Quote && tag[i-1] == '=')
146 state = SKIP_VALUE;
147 break;
148 case MATCH_NAME:
149 if (tag[i] == attrname[n])
150 ++n;
151 else if (tag[i] == '=' && !attrname[n])
152 state = FOUND;
153 else
154 state = SEEK_NAME;
155 break;
156 case SKIP_VALUE:
157 if (tag[i] == Quote)
158 state = (tag[i+1] == Quote) ? SKIP_QUOTE : SEEK_NAME;
159 break;
160 case SKIP_QUOTE:
161 state = SKIP_VALUE;
162 break;
163 case FOUND:
164 found = 1;
165 break;
166 }
167 }
168
169 if (found) {
170 p = start = tag + i;
171 while ((q = strchr(p, Quote)) && q[1] == Quote)
172 p = q + 2;
173 if (q && q[1] == ' ') {
174 val = dStrndup(start, (uint_t)(q - start));
175 for (r = s = val; (*r = *s); ++r, ++s)
176 if (s[0] == Quote && s[0] == s[1])
177 ++s;
178 }
179 }
180 return val;
181 }
182
183 /*
184 * Task: given a tag and an attribute name, return its value.
185 * Return value: the attribute value, or NULL if not present or malformed.
186 */
a_Dpip_get_attr(const char * tag,const char * attrname)187 char *a_Dpip_get_attr(const char *tag, const char *attrname)
188 {
189 return (tag ? a_Dpip_get_attr_l(tag, strlen(tag), attrname) : NULL);
190 }
191
192 /*
193 * Check whether the given 'auth' string equals what dpid saved.
194 * Return value: 1 if equal, -1 otherwise
195 */
a_Dpip_check_auth(const char * auth_tag)196 int a_Dpip_check_auth(const char *auth_tag)
197 {
198 char SharedSecret[32];
199 FILE *In;
200 char *fname, *rcline = NULL, *tail, *cmd, *msg;
201 int i, port, ret = -1;
202
203 /* sanity checks */
204 if (!auth_tag ||
205 !(cmd = a_Dpip_get_attr(auth_tag, "cmd")) || strcmp(cmd, "auth") ||
206 !(msg = a_Dpip_get_attr(auth_tag, "msg"))) {
207 return ret;
208 }
209
210 fname = dStrconcat(dGethomedir(), "/.dillo/dpid_comm_keys", NULL);
211 if ((In = fopen(fname, "r")) == NULL) {
212 MSG_ERR("[a_Dpip_check_auth] %s\n", dStrerror(errno));
213 } else if ((rcline = dGetline(In)) == NULL) {
214 MSG_ERR("[a_Dpip_check_auth] empty file: %s\n", fname);
215 } else {
216 port = strtol(rcline, &tail, 10);
217 if (tail && port != 0) {
218 for (i = 0; *tail && isxdigit(tail[i+1]); ++i)
219 SharedSecret[i] = tail[i+1];
220 SharedSecret[i] = 0;
221 if (strcmp(msg, SharedSecret) == 0)
222 ret = 1;
223 }
224 }
225 if (In)
226 fclose(In);
227 dFree(rcline);
228 dFree(fname);
229 dFree(msg);
230 dFree(cmd);
231
232 return ret;
233 }
234
235 /* --------------------------------------------------------------------------
236 * Dpip socket API ----------------------------------------------------------
237 */
238
239 /*
240 * Create and initialize a dpip socket handler
241 */
a_Dpip_dsh_new(int fd_in,int fd_out,int flush_sz)242 Dsh *a_Dpip_dsh_new(int fd_in, int fd_out, int flush_sz)
243 {
244 Dsh *dsh = dNew(Dsh, 1);
245
246 /* init descriptors and streams */
247 dsh->fd_in = fd_in;
248 dsh->fd_out = fd_out;
249
250 /* init buffer */
251 dsh->wrbuf = dStr_sized_new(8 *1024);
252 dsh->rdbuf = dStr_sized_new(8 *1024);
253 dsh->flush_sz = flush_sz;
254 dsh->mode = DPIP_TAG;
255 if (fcntl(dsh->fd_in, F_GETFL) & O_NONBLOCK)
256 dsh->mode |= DPIP_NONBLOCK;
257 dsh->status = 0;
258
259 return dsh;
260 }
261
262 /*
263 * Return value: 1..DataSize sent, -1 eagain, or -3 on big Error
264 */
Dpip_dsh_write(Dsh * dsh,int nb,const char * Data,int DataSize)265 static int Dpip_dsh_write(Dsh *dsh, int nb, const char *Data, int DataSize)
266 {
267 int req_mode, old_flags = 0, st, ret = -3, sent = 0;
268
269 req_mode = (nb) ? DPIP_NONBLOCK : 0;
270 if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {
271 /* change mode temporarily... */
272 old_flags = fcntl(dsh->fd_out, F_GETFL);
273 fcntl(dsh->fd_out, F_SETFL,
274 (nb) ? O_NONBLOCK | old_flags : old_flags & ~O_NONBLOCK);
275 }
276
277 while (1) {
278 st = write(dsh->fd_out, Data + sent, DataSize - sent);
279 if (st < 0) {
280 if (errno == EINTR) {
281 continue;
282 } else if (errno == EAGAIN) {
283 dsh->status = DPIP_EAGAIN;
284 ret = -1;
285 break;
286 } else {
287 MSG_ERR("[Dpip_dsh_write] %s\n", dStrerror(errno));
288 dsh->status = DPIP_ERROR;
289 break;
290 }
291 } else {
292 sent += st;
293 if (nb || sent == DataSize) {
294 ret = sent;
295 break;
296 }
297 }
298 }
299
300 if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {
301 /* restore old mode */
302 fcntl(dsh->fd_out, F_SETFL, old_flags);
303 }
304
305 return ret;
306 }
307
308 /*
309 * Streamed write to socket
310 * Return: 0 on success, 1 on error.
311 */
a_Dpip_dsh_write(Dsh * dsh,int flush,const char * Data,int DataSize)312 int a_Dpip_dsh_write(Dsh *dsh, int flush, const char *Data, int DataSize)
313 {
314 int ret = 1;
315
316 /* append to buf */
317 dStr_append_l(dsh->wrbuf, Data, DataSize);
318
319 if (!flush || dsh->wrbuf->len == 0)
320 return 0;
321
322 ret = Dpip_dsh_write(dsh, 0, dsh->wrbuf->str, dsh->wrbuf->len);
323 if (ret == dsh->wrbuf->len) {
324 dStr_truncate(dsh->wrbuf, 0);
325 ret = 0;
326 }
327
328 return ret;
329 }
330
331 /*
332 * Return value: 0 on success or empty buffer,
333 * 1..DataSize sent, -1 eagain, or -3 on big Error
334 */
a_Dpip_dsh_tryflush(Dsh * dsh)335 int a_Dpip_dsh_tryflush(Dsh *dsh)
336 {
337 int st;
338
339 if (dsh->wrbuf->len == 0) {
340 st = 0;
341 } else {
342 st = Dpip_dsh_write(dsh, 1, dsh->wrbuf->str, dsh->wrbuf->len);
343 if (st > 0) {
344 /* update internal buffer */
345 dStr_erase(dsh->wrbuf, 0, st);
346 }
347 }
348 return (dsh->wrbuf->len == 0) ? 0 : st;
349 }
350
351 /*
352 * Return value: 1..DataSize sent, -1 eagain, or -3 on big Error
353 */
a_Dpip_dsh_trywrite(Dsh * dsh,const char * Data,int DataSize)354 int a_Dpip_dsh_trywrite(Dsh *dsh, const char *Data, int DataSize)
355 {
356 int st;
357
358 if ((st = Dpip_dsh_write(dsh, 1, Data, DataSize)) > 0) {
359 /* update internal buffer */
360 if (st < DataSize)
361 dStr_append_l(dsh->wrbuf, Data + st, DataSize - st);
362 }
363 return st;
364 }
365
366 /*
367 * Convenience function.
368 */
a_Dpip_dsh_write_str(Dsh * dsh,int flush,const char * str)369 int a_Dpip_dsh_write_str(Dsh *dsh, int flush, const char *str)
370 {
371 return a_Dpip_dsh_write(dsh, flush, str, (int)strlen(str));
372 }
373
374 /*
375 * Read raw data from the socket into our buffer in
376 * either BLOCKING or NONBLOCKING mode.
377 */
Dpip_dsh_read(Dsh * dsh,int blocking)378 static void Dpip_dsh_read(Dsh *dsh, int blocking)
379 {
380 char buf[RBUF_SZ];
381 int req_mode, old_flags = 0, st, nb = !blocking;
382
383 dReturn_if (dsh->status == DPIP_ERROR || dsh->status == DPIP_EOF);
384
385 req_mode = (nb) ? DPIP_NONBLOCK : 0;
386 if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {
387 /* change mode temporarily... */
388 old_flags = fcntl(dsh->fd_in, F_GETFL);
389 fcntl(dsh->fd_in, F_SETFL,
390 (nb) ? O_NONBLOCK | old_flags : old_flags & ~O_NONBLOCK);
391 }
392
393 while (1) {
394 st = read(dsh->fd_in, buf, RBUF_SZ);
395 if (st < 0) {
396 if (errno == EINTR) {
397 continue;
398 } else if (errno == EAGAIN) {
399 dsh->status = DPIP_EAGAIN;
400 break;
401 } else {
402 MSG_ERR("[Dpip_dsh_read] %s\n", dStrerror(errno));
403 dsh->status = DPIP_ERROR;
404 break;
405 }
406 } else if (st == 0) {
407 dsh->status = DPIP_EOF;
408 break;
409 } else {
410 /* append to buf */
411 dStr_append_l(dsh->rdbuf, buf, st);
412 if (blocking)
413 break;
414 }
415 }
416
417 if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {
418 /* restore old mode */
419 fcntl(dsh->fd_out, F_SETFL, old_flags);
420 }
421
422 /* assert there's no more data in the wire...
423 * (st < buf upon interrupt || st == buf and no more data) */
424 if (blocking)
425 Dpip_dsh_read(dsh, 0);
426 }
427
428 /*
429 * Return a newlly allocated string with the next dpip token in the socket.
430 * Return value: token string and length on success, NULL otherwise.
431 * (useful for handling null characters in the data stream)
432 */
a_Dpip_dsh_read_token2(Dsh * dsh,int blocking,int * DataSize)433 char *a_Dpip_dsh_read_token2(Dsh *dsh, int blocking, int *DataSize)
434 {
435 char *p, *ret = NULL;
436 *DataSize = 0;
437
438 /* Read all available data without blocking */
439 Dpip_dsh_read(dsh, 0);
440
441 /* switch mode upon request */
442 if (dsh->mode & DPIP_LAST_TAG)
443 dsh->mode = DPIP_RAW;
444
445 if (blocking) {
446 if (dsh->mode & DPIP_TAG) {
447 /* Only wait for data when the tag is incomplete */
448 if (!strstr(dsh->rdbuf->str, DPIP_TAG_END)) {
449 do {
450 Dpip_dsh_read(dsh, 1);
451 p = strstr(dsh->rdbuf->str, DPIP_TAG_END);
452 } while (!p && dsh->status == EAGAIN);
453 }
454
455 } else if (dsh->mode & DPIP_RAW) {
456 /* Wait for data when the buffer is empty and there's no ERR/EOF */
457 while (dsh->rdbuf->len == 0 &&
458 dsh->status != DPIP_ERROR && dsh->status != DPIP_EOF)
459 Dpip_dsh_read(dsh, 1);
460 }
461 }
462
463 if (dsh->mode & DPIP_TAG) {
464 /* return a full tag */
465 if ((p = strstr(dsh->rdbuf->str, DPIP_TAG_END))) {
466 ret = dStrndup(dsh->rdbuf->str, p - dsh->rdbuf->str + 3);
467 *DataSize = p - dsh->rdbuf->str + 3;
468 dStr_erase(dsh->rdbuf, 0, p - dsh->rdbuf->str + 3);
469 if (strstr(ret, DPIP_MODE_SWITCH_TAG))
470 dsh->mode |= DPIP_LAST_TAG;
471 }
472 } else {
473 /* raw mode, return what we have "as is" */
474 if (dsh->rdbuf->len > 0) {
475 ret = dStrndup(dsh->rdbuf->str, dsh->rdbuf->len);
476 *DataSize = dsh->rdbuf->len;
477 dStr_truncate(dsh->rdbuf, 0);
478 }
479 }
480
481 return ret;
482 }
483
484 /*
485 * Return a newlly allocated string with the next dpip token in the socket.
486 * Return value: token string on success, NULL otherwise
487 */
a_Dpip_dsh_read_token(Dsh * dsh,int blocking)488 char *a_Dpip_dsh_read_token(Dsh *dsh, int blocking)
489 {
490 int token_size;
491
492 return a_Dpip_dsh_read_token2(dsh, blocking, &token_size);
493 }
494
495 /*
496 * Close this socket for reading and writing.
497 * (flush pending data)
498 */
a_Dpip_dsh_close(Dsh * dsh)499 void a_Dpip_dsh_close(Dsh *dsh)
500 {
501 int st;
502
503 /* flush internal buffer */
504 a_Dpip_dsh_write(dsh, 1, "", 0);
505
506 /* close fds */
507 st = dClose(dsh->fd_in);
508 if (st < 0)
509 MSG_ERR("[a_Dpip_dsh_close] close: %s\n", dStrerror(errno));
510 if (dsh->fd_out != dsh->fd_in) {
511 st = dClose(dsh->fd_out);
512 if (st < 0)
513 MSG_ERR("[a_Dpip_dsh_close] close: %s\n", dStrerror(errno));
514 }
515 }
516
517 /*
518 * Free the SockHandler structure
519 */
a_Dpip_dsh_free(Dsh * dsh)520 void a_Dpip_dsh_free(Dsh *dsh)
521 {
522 dReturn_if (dsh == NULL);
523
524 dStr_free(dsh->wrbuf, 1);
525 dStr_free(dsh->rdbuf, 1);
526 dFree(dsh);
527 }
528
529