source: lib/misc.c @ b38f655

Last change on this file since b38f655 was 73b1a8e, checked in by dequis <dx@…>, at 2015-04-10T17:10:41Z

get_rfc822_header: allow headers at the end of a string

  • Property mode set to 100644
File size: 15.8 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2012 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/*
8 * Various utility functions. Some are copied from Gaim to support the
9 * IM-modules, most are from BitlBee.
10 *
11 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
12 *                          (and possibly other members of the Gaim team)
13 * Copyright 2002-2012 Wilmer van der Gaast <wilmer@gaast.net>
14 */
15
16/*
17  This program is free software; you can redistribute it and/or modify
18  it under the terms of the GNU General Public License as published by
19  the Free Software Foundation; either version 2 of the License, or
20  (at your option) any later version.
21
22  This program is distributed in the hope that it will be useful,
23  but WITHOUT ANY WARRANTY; without even the implied warranty of
24  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25  GNU General Public License for more details.
26
27  You should have received a copy of the GNU General Public License with
28  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
29  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
30  Fifth Floor, Boston, MA  02110-1301  USA
31*/
32
33#define BITLBEE_CORE
34#include "nogaim.h"
35#include "base64.h"
36#include "md5.h"
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <ctype.h>
41#include <glib.h>
42#include <time.h>
43
44#ifdef HAVE_RESOLV_A
45#include <arpa/nameser.h>
46#include <resolv.h>
47#endif
48
49#include "md5.h"
50#include "ssl_client.h"
51
52void strip_linefeed(gchar *text)
53{
54        int i, j;
55        gchar *text2 = g_malloc(strlen(text) + 1);
56
57        for (i = 0, j = 0; text[i]; i++) {
58                if (text[i] != '\r') {
59                        text2[j++] = text[i];
60                }
61        }
62        text2[j] = '\0';
63
64        strcpy(text, text2);
65        g_free(text2);
66}
67
68time_t get_time(int year, int month, int day, int hour, int min, int sec)
69{
70        struct tm tm;
71
72        memset(&tm, 0, sizeof(struct tm));
73        tm.tm_year = year - 1900;
74        tm.tm_mon = month - 1;
75        tm.tm_mday = day;
76        tm.tm_hour = hour;
77        tm.tm_min = min;
78        tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60;
79
80        return mktime(&tm);
81}
82
83time_t mktime_utc(struct tm *tp)
84{
85        struct tm utc;
86        time_t res, tres;
87
88        tp->tm_isdst = -1;
89        res = mktime(tp);
90        /* Problem is, mktime() just gave us the GMT timestamp for the
91           given local time... While the given time WAS NOT local. So
92           we should fix this now.
93
94           Now I could choose between messing with environment variables
95           (kludgy) or using timegm() (not portable)... Or doing the
96           following, which I actually prefer...
97
98           tzset() may also work but in other places I actually want to
99           use local time.
100
101           FFFFFFFFFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUU!! */
102        gmtime_r(&res, &utc);
103        utc.tm_isdst = -1;
104        if (utc.tm_hour == tp->tm_hour && utc.tm_min == tp->tm_min) {
105                /* Sweet! We're in UTC right now... */
106                return res;
107        }
108
109        tres = mktime(&utc);
110        res += res - tres;
111
112        /* Yes, this is a hack. And it will go wrong around DST changes.
113           BUT this is more likely to be threadsafe than messing with
114           environment variables, and possibly more portable... */
115
116        return res;
117}
118
119typedef struct htmlentity {
120        char code[7];
121        char is[3];
122} htmlentity_t;
123
124static const htmlentity_t ent[] =
125{
126        { "lt",     "<" },
127        { "gt",     ">" },
128        { "amp",    "&" },
129        { "apos",   "'" },
130        { "quot",   "\"" },
131        { "aacute", "á" },
132        { "eacute", "é" },
133        { "iacute", "é" },
134        { "oacute", "ó" },
135        { "uacute", "ú" },
136        { "agrave", "à" },
137        { "egrave", "è" },
138        { "igrave", "ì" },
139        { "ograve", "ò" },
140        { "ugrave", "ù" },
141        { "acirc",  "â" },
142        { "ecirc",  "ê" },
143        { "icirc",  "î" },
144        { "ocirc",  "ô" },
145        { "ucirc",  "û" },
146        { "auml",   "ä" },
147        { "euml",   "ë" },
148        { "iuml",   "ï" },
149        { "ouml",   "ö" },
150        { "uuml",   "ü" },
151        { "nbsp",   " " },
152        { "",        ""  }
153};
154
155void strip_html(char *in)
156{
157        char *start = in;
158        char out[strlen(in) + 1];
159        char *s = out, *cs;
160        int i, matched;
161        int taglen;
162
163        memset(out, 0, sizeof(out));
164
165        while (*in) {
166                if (*in == '<' && (g_ascii_isalpha(*(in + 1)) || *(in + 1) == '/')) {
167                        /* If in points at a < and in+1 points at a letter or a slash, this is probably
168                           a HTML-tag. Try to find a closing > and continue there. If the > can't be
169                           found, assume that it wasn't a HTML-tag after all. */
170
171                        cs = in;
172
173                        while (*in && *in != '>') {
174                                in++;
175                        }
176
177                        taglen = in - cs - 1;   /* not <0 because the above loop runs at least once */
178                        if (*in) {
179                                if (g_strncasecmp(cs + 1, "b", taglen) == 0) {
180                                        *(s++) = '\x02';
181                                } else if (g_strncasecmp(cs + 1, "/b", taglen) == 0) {
182                                        *(s++) = '\x02';
183                                } else if (g_strncasecmp(cs + 1, "i", taglen) == 0) {
184                                        *(s++) = '\x1f';
185                                } else if (g_strncasecmp(cs + 1, "/i", taglen) == 0) {
186                                        *(s++) = '\x1f';
187                                } else if (g_strncasecmp(cs + 1, "br", taglen) == 0) {
188                                        *(s++) = '\n';
189                                }
190                                in++;
191                        } else {
192                                in = cs;
193                                *(s++) = *(in++);
194                        }
195                } else if (*in == '&') {
196                        cs = ++in;
197                        while (*in && g_ascii_isalpha(*in)) {
198                                in++;
199                        }
200
201                        if (*in == ';') {
202                                in++;
203                        }
204                        matched = 0;
205
206                        for (i = 0; *ent[i].code; i++) {
207                                if (g_strncasecmp(ent[i].code, cs, strlen(ent[i].code)) == 0) {
208                                        int j;
209
210                                        for (j = 0; ent[i].is[j]; j++) {
211                                                *(s++) = ent[i].is[j];
212                                        }
213
214                                        matched = 1;
215                                        break;
216                                }
217                        }
218
219                        /* None of the entities were matched, so return the string */
220                        if (!matched) {
221                                in = cs - 1;
222                                *(s++) = *(in++);
223                        }
224                } else {
225                        *(s++) = *(in++);
226                }
227        }
228
229        strcpy(start, out);
230}
231
232char *escape_html(const char *html)
233{
234        const char *c = html;
235        GString *ret;
236        char *str;
237
238        if (html == NULL) {
239                return(NULL);
240        }
241
242        ret = g_string_new("");
243
244        while (*c) {
245                switch (*c) {
246                case '&':
247                        ret = g_string_append(ret, "&amp;");
248                        break;
249                case '<':
250                        ret = g_string_append(ret, "&lt;");
251                        break;
252                case '>':
253                        ret = g_string_append(ret, "&gt;");
254                        break;
255                case '"':
256                        ret = g_string_append(ret, "&quot;");
257                        break;
258                default:
259                        ret = g_string_append_c(ret, *c);
260                }
261                c++;
262        }
263
264        str = ret->str;
265        g_string_free(ret, FALSE);
266        return(str);
267}
268
269/* Decode%20a%20file%20name                                             */
270void http_decode(char *s)
271{
272        char *t;
273        int i, j, k;
274
275        t = g_new(char, strlen(s) + 1);
276
277        for (i = j = 0; s[i]; i++, j++) {
278                if (s[i] == '%') {
279                        if (sscanf(s + i + 1, "%2x", &k)) {
280                                t[j] = k;
281                                i += 2;
282                        } else {
283                                *t = 0;
284                                break;
285                        }
286                } else {
287                        t[j] = s[i];
288                }
289        }
290        t[j] = 0;
291
292        strcpy(s, t);
293        g_free(t);
294}
295
296/* Warning: This one explodes the string. Worst-cases can make the string 3x its original size! */
297/* This fuction is safe, but make sure you call it safely as well! */
298void http_encode(char *s)
299{
300        char t[strlen(s) + 1];
301        int i, j;
302
303        strcpy(t, s);
304        for (i = j = 0; t[i]; i++, j++) {
305                /* Warning: g_ascii_isalnum() is locale-aware, so don't use it here! */
306                if ((t[i] >= 'A' && t[i] <= 'Z') ||
307                    (t[i] >= 'a' && t[i] <= 'z') ||
308                    (t[i] >= '0' && t[i] <= '9') ||
309                    strchr("._-~", t[i])) {
310                        s[j] = t[i];
311                } else {
312                        sprintf(s + j, "%%%02X", ((unsigned char *) t)[i]);
313                        j += 2;
314                }
315        }
316        s[j] = 0;
317}
318
319/* Strip newlines from a string. Modifies the string passed to it. */
320char *strip_newlines(char *source)
321{
322        int i;
323
324        for (i = 0; source[i] != '\0'; i++) {
325                if (source[i] == '\n' || source[i] == '\r') {
326                        source[i] = ' ';
327                }
328        }
329
330        return source;
331}
332
333/* Wrap an IPv4 address into IPv6 space. Not thread-safe... */
334char *ipv6_wrap(char *src)
335{
336        static char dst[64];
337        int i;
338
339        for (i = 0; src[i]; i++) {
340                if ((src[i] < '0' || src[i] > '9') && src[i] != '.') {
341                        break;
342                }
343        }
344
345        /* Hmm, it's not even an IP... */
346        if (src[i]) {
347                return src;
348        }
349
350        g_snprintf(dst, sizeof(dst), "::ffff:%s", src);
351
352        return dst;
353}
354
355/* Unwrap an IPv4 address into IPv6 space. Thread-safe, because it's very simple. :-) */
356char *ipv6_unwrap(char *src)
357{
358        int i;
359
360        if (g_strncasecmp(src, "::ffff:", 7) != 0) {
361                return src;
362        }
363
364        for (i = 7; src[i]; i++) {
365                if ((src[i] < '0' || src[i] > '9') && src[i] != '.') {
366                        break;
367                }
368        }
369
370        /* Hmm, it's not even an IP... */
371        if (src[i]) {
372                return src;
373        }
374
375        return (src + 7);
376}
377
378/* Convert from one charset to another.
379
380   from_cs, to_cs: Source and destination charsets
381   src, dst: Source and destination strings
382   size: Size if src. 0 == use strlen(). strlen() is not reliable for UNICODE/UTF16 strings though.
383   maxbuf: Maximum number of bytes to write to dst
384
385   Returns the number of bytes written to maxbuf or -1 on an error.
386*/
387signed int do_iconv(char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf)
388{
389        GIConv cd;
390        size_t res;
391        size_t inbytesleft, outbytesleft;
392        char *inbuf = src;
393        char *outbuf = dst;
394
395        cd = g_iconv_open(to_cs, from_cs);
396        if (cd == (GIConv) - 1) {
397                return -1;
398        }
399
400        inbytesleft = size ? size : strlen(src);
401        outbytesleft = maxbuf - 1;
402        res = g_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
403        *outbuf = '\0';
404        g_iconv_close(cd);
405
406        if (res != 0) {
407                return -1;
408        } else {
409                return outbuf - dst;
410        }
411}
412
413/* A wrapper for /dev/urandom.
414 * If /dev/urandom is not present or not usable, it calls abort()
415 * to prevent bitlbee from working without a decent entropy source */
416void random_bytes(unsigned char *buf, int count)
417{
418        int fd;
419
420        if (((fd = open("/dev/urandom", O_RDONLY)) == -1) ||
421            (read(fd, buf, count) == -1)) {
422                log_message(LOGLVL_ERROR, "/dev/urandom not present - aborting");
423                abort();
424        }
425
426        close(fd);
427}
428
429int is_bool(char *value)
430{
431        if (*value == 0) {
432                return 0;
433        }
434
435        if ((g_strcasecmp(value,
436                          "true") == 0) || (g_strcasecmp(value, "yes") == 0) || (g_strcasecmp(value, "on") == 0)) {
437                return 1;
438        }
439        if ((g_strcasecmp(value,
440                          "false") == 0) || (g_strcasecmp(value, "no") == 0) || (g_strcasecmp(value, "off") == 0)) {
441                return 1;
442        }
443
444        while (*value) {
445                if (!g_ascii_isdigit(*value)) {
446                        return 0;
447                } else {
448                        value++;
449                }
450        }
451
452        return 1;
453}
454
455int bool2int(char *value)
456{
457        int i;
458
459        if ((g_strcasecmp(value,
460                          "true") == 0) || (g_strcasecmp(value, "yes") == 0) || (g_strcasecmp(value, "on") == 0)) {
461                return 1;
462        }
463        if ((g_strcasecmp(value,
464                          "false") == 0) || (g_strcasecmp(value, "no") == 0) || (g_strcasecmp(value, "off") == 0)) {
465                return 0;
466        }
467
468        if (sscanf(value, "%d", &i) == 1) {
469                return i;
470        }
471
472        return 0;
473}
474
475struct ns_srv_reply **srv_lookup(char *service, char *protocol, char *domain)
476{
477        struct ns_srv_reply **replies = NULL;
478
479#ifdef HAVE_RESOLV_A
480        struct ns_srv_reply *reply = NULL;
481        char name[1024];
482        unsigned char querybuf[1024];
483        const unsigned char *buf;
484        ns_msg nsh;
485        ns_rr rr;
486        int n, len, size;
487
488        g_snprintf(name, sizeof(name), "_%s._%s.%s", service, protocol, domain);
489
490        if ((size = res_query(name, ns_c_in, ns_t_srv, querybuf, sizeof(querybuf))) <= 0) {
491                return NULL;
492        }
493
494        if (ns_initparse(querybuf, size, &nsh) != 0) {
495                return NULL;
496        }
497
498        n = 0;
499        while (ns_parserr(&nsh, ns_s_an, n, &rr) == 0) {
500                char name[NS_MAXDNAME];
501
502                if (ns_rr_rdlen(rr) < 7) {
503                        break;
504                }
505
506                buf = ns_rr_rdata(rr);
507
508                if (dn_expand(querybuf, querybuf + size, &buf[6], name, NS_MAXDNAME) == -1) {
509                        break;
510                }
511
512                len = strlen(name) + 1;
513
514                reply = g_malloc(sizeof(struct ns_srv_reply) + len);
515                memcpy(reply->name, name, len);
516
517                reply->prio = (buf[0] << 8) | buf[1];
518                reply->weight = (buf[2] << 8) | buf[3];
519                reply->port = (buf[4] << 8) | buf[5];
520
521                n++;
522                replies = g_renew(struct ns_srv_reply *, replies, n + 1);
523                replies[n - 1] = reply;
524        }
525        if (replies) {
526                replies[n] = NULL;
527        }
528#endif
529
530        return replies;
531}
532
533void srv_free(struct ns_srv_reply **srv)
534{
535        int i;
536
537        if (srv == NULL) {
538                return;
539        }
540
541        for (i = 0; srv[i]; i++) {
542                g_free(srv[i]);
543        }
544        g_free(srv);
545}
546
547/* Word wrapping. Yes, I know this isn't UTF-8 clean. I'm willing to take the risk. */
548char *word_wrap(const char *msg, int line_len)
549{
550        GString *ret = g_string_sized_new(strlen(msg) + 16);
551
552        while (strlen(msg) > line_len) {
553                int i;
554
555                /* First try to find out if there's a newline already. Don't
556                   want to add more splits than necessary. */
557                for (i = line_len; i > 0 && msg[i] != '\n'; i--) {
558                        ;
559                }
560                if (msg[i] == '\n') {
561                        g_string_append_len(ret, msg, i + 1);
562                        msg += i + 1;
563                        continue;
564                }
565
566                for (i = line_len; i > 0; i--) {
567                        if (msg[i] == '-') {
568                                g_string_append_len(ret, msg, i + 1);
569                                g_string_append_c(ret, '\n');
570                                msg += i + 1;
571                                break;
572                        } else if (msg[i] == ' ') {
573                                g_string_append_len(ret, msg, i);
574                                g_string_append_c(ret, '\n');
575                                msg += i + 1;
576                                break;
577                        }
578                }
579                if (i == 0) {
580                        g_string_append_len(ret, msg, line_len);
581                        g_string_append_c(ret, '\n');
582                        msg += line_len;
583                }
584        }
585        g_string_append(ret, msg);
586
587        return g_string_free(ret, FALSE);
588}
589
590gboolean ssl_sockerr_again(void *ssl)
591{
592        if (ssl) {
593                return ssl_errno == SSL_AGAIN;
594        } else {
595                return sockerr_again();
596        }
597}
598
599/* Returns values: -1 == Failure (base64-decoded to something unexpected)
600                    0 == Okay
601                    1 == Password doesn't match the hash. */
602int md5_verify_password(char *password, char *hash)
603{
604        md5_byte_t *pass_dec = NULL;
605        md5_byte_t pass_md5[16];
606        md5_state_t md5_state;
607        int ret = -1, i;
608
609        if (base64_decode(hash, &pass_dec) == 21) {
610                md5_init(&md5_state);
611                md5_append(&md5_state, (md5_byte_t *) password, strlen(password));
612                md5_append(&md5_state, (md5_byte_t *) pass_dec + 16, 5);  /* Hmmm, salt! */
613                md5_finish(&md5_state, pass_md5);
614
615                for (i = 0; i < 16; i++) {
616                        if (pass_dec[i] != pass_md5[i]) {
617                                ret = 1;
618                                break;
619                        }
620                }
621
622                /* If we reached the end of the loop, it was a match! */
623                if (i == 16) {
624                        ret = 0;
625                }
626        }
627
628        g_free(pass_dec);
629
630        return ret;
631}
632
633/* Split commands (root-style, *not* IRC-style). Handles "quoting of"
634   white\ space in 'various ways'. Returns a NULL-terminated static
635   char** so watch out with nested use! Definitely not thread-safe. */
636char **split_command_parts(char *command, int limit)
637{
638        static char *cmd[IRC_MAX_ARGS + 1];
639        char *s, q = 0;
640        int k;
641
642        memset(cmd, 0, sizeof(cmd));
643        cmd[0] = command;
644        k = 1;
645        for (s = command; *s && k < IRC_MAX_ARGS; s++) {
646                if (*s == ' ' && !q) {
647                        *s = 0;
648                        while (*++s == ' ') {
649                                ;
650                        }
651                        if (k != limit && (*s == '"' || *s == '\'')) {
652                                q = *s;
653                                s++;
654                        }
655                        if (*s) {
656                                cmd[k++] = s;
657                                if (limit && k > limit) {
658                                        break;
659                                }
660                                s--;
661                        } else {
662                                break;
663                        }
664                } else if (*s == '\\' && ((!q && s[1]) || (q && q == s[1]))) {
665                        char *cpy;
666
667                        for (cpy = s; *cpy; cpy++) {
668                                cpy[0] = cpy[1];
669                        }
670                } else if (*s == q) {
671                        q = *s = 0;
672                }
673        }
674
675        /* Full zero-padding for easier argc checking. */
676        while (k <= IRC_MAX_ARGS) {
677                cmd[k++] = NULL;
678        }
679
680        return cmd;
681}
682
683char *get_rfc822_header(const char *text, const char *header, int len)
684{
685        int hlen = strlen(header), i;
686        const char *ret;
687
688        if (text == NULL) {
689                return NULL;
690        }
691
692        if (len == 0) {
693                len = strlen(text);
694        }
695
696        i = 0;
697        while ((i + hlen) < len) {
698                /* Maybe this is a bit over-commented, but I just hate this part... */
699                if (g_strncasecmp(text + i, header, hlen) == 0) {
700                        /* Skip to the (probable) end of the header */
701                        i += hlen;
702
703                        /* Find the first non-[: \t] character */
704                        while (i < len && (text[i] == ':' || text[i] == ' ' || text[i] == '\t')) {
705                                i++;
706                        }
707
708                        /* Make sure we're still inside the string */
709                        if (i >= len) {
710                                return(NULL);
711                        }
712
713                        /* Save the position */
714                        ret = text + i;
715
716                        /* Search for the end of this line */
717                        while (i < len && text[i] != '\r' && text[i] != '\n') {
718                                i++;
719                        }
720
721                        /* Copy the found data */
722                        return(g_strndup(ret, text + i - ret));
723                }
724
725                /* This wasn't the header we were looking for, skip to the next line. */
726                while (i < len && (text[i] != '\r' && text[i] != '\n')) {
727                        i++;
728                }
729                while (i < len && (text[i] == '\r' || text[i] == '\n')) {
730                        i++;
731                }
732
733                /* End of headers? */
734                if ((i >= 4 && strncmp(text + i - 4, "\r\n\r\n", 4) == 0) ||
735                    (i >= 2 && (strncmp(text + i - 2, "\n\n", 2) == 0 ||
736                                strncmp(text + i - 2, "\r\r", 2) == 0))) {
737                        break;
738                }
739        }
740
741        return NULL;
742}
743
744/* Takes a string, truncates it where it's safe, returns the new length */
745int truncate_utf8(char *string, int maxlen)
746{
747        char *end;
748
749        g_utf8_validate((const gchar *) string, maxlen, (const gchar **) &end);
750        *end = '\0';
751        return end - string;
752}
Note: See TracBrowser for help on using the repository browser.