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