source: lib/misc.c @ b447d2b

Last change on this file since b447d2b was 632627e, checked in by dequis <dx@…>, at 2014-07-24T03:51:07Z

srv_lookup: Portability fixes, handle compressed responses

srv_lookup works on cygwin and openbsd now.

Provide ns_initparse, friends, and types where they aren't provided by
platform.

Use dn_expandname instead of custom parser so compressed DNS responses
are handled correctly.

  • Property mode set to 100644
File size: 17.2 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., 59 Temple Place,
30  Suite 330, Boston, MA  02111-1307  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        text2[j] = '\0';
61
62        strcpy(text, text2);
63        g_free(text2);
64}
65
66time_t get_time(int year, int month, int day, int hour, int min, int sec)
67{
68        struct tm tm;
69
70        memset(&tm, 0, sizeof(struct tm));
71        tm.tm_year = year - 1900;
72        tm.tm_mon = month - 1;
73        tm.tm_mday = day;
74        tm.tm_hour = hour;
75        tm.tm_min = min;
76        tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60;
77       
78        return mktime(&tm);
79}
80
81time_t mktime_utc( struct tm *tp )
82{
83        struct tm utc;
84        time_t res, tres;
85       
86        tp->tm_isdst = -1;
87        res = mktime( tp );
88        /* Problem is, mktime() just gave us the GMT timestamp for the
89           given local time... While the given time WAS NOT local. So
90           we should fix this now.
91           
92           Now I could choose between messing with environment variables
93           (kludgy) or using timegm() (not portable)... Or doing the
94           following, which I actually prefer...
95           
96           tzset() may also work but in other places I actually want to
97           use local time.
98           
99           FFFFFFFFFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUU!! */
100        gmtime_r( &res, &utc );
101        utc.tm_isdst = -1;
102        if( utc.tm_hour == tp->tm_hour && utc.tm_min == tp->tm_min )
103                /* Sweet! We're in UTC right now... */
104                return res;
105       
106        tres = mktime( &utc );
107        res += res - tres;
108       
109        /* Yes, this is a hack. And it will go wrong around DST changes.
110           BUT this is more likely to be threadsafe than messing with
111           environment variables, and possibly more portable... */
112       
113        return res;
114}
115
116typedef struct htmlentity
117{
118        char code[7];
119        char is[3];
120} htmlentity_t;
121
122static const htmlentity_t ent[] =
123{
124        { "lt",     "<" },
125        { "gt",     ">" },
126        { "amp",    "&" },
127        { "apos",   "'" },
128        { "quot",   "\"" },
129        { "aacute", "á" },
130        { "eacute", "é" },
131        { "iacute", "é" },
132        { "oacute", "ó" },
133        { "uacute", "ú" },
134        { "agrave", "à" },
135        { "egrave", "è" },
136        { "igrave", "ì" },
137        { "ograve", "ò" },
138        { "ugrave", "ù" },
139        { "acirc",  "â" },
140        { "ecirc",  "ê" },
141        { "icirc",  "î" },
142        { "ocirc",  "ô" },
143        { "ucirc",  "û" },
144        { "auml",   "ä" },
145        { "euml",   "ë" },
146        { "iuml",   "ï" },
147        { "ouml",   "ö" },
148        { "uuml",   "ü" },
149        { "nbsp",   " " },
150        { "",        ""  }
151};
152
153void strip_html( char *in )
154{
155        char *start = in;
156        char out[strlen(in)+1];
157        char *s = out, *cs;
158        int i, matched;
159        int taglen;
160       
161        memset( out, 0, sizeof( out ) );
162       
163        while( *in )
164        {
165                if( *in == '<' && ( isalpha( *(in+1) ) || *(in+1) == '/' ) )
166                {
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                        taglen = in - cs - 1;   /* not <0 because the above loop runs at least once */
177                        if( *in )
178                        {
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                                in ++;
190                        }
191                        else
192                        {
193                                in = cs;
194                                *(s++) = *(in++);
195                        }
196                }
197                else if( *in == '&' )
198                {
199                        cs = ++in;
200                        while( *in && isalpha( *in ) )
201                                in ++;
202                       
203                        if( *in == ';' ) in ++;
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                                {
209                                        int j;
210                                       
211                                        for( j = 0; ent[i].is[j]; j ++ )
212                                                *(s++) = ent[i].is[j];
213                                       
214                                        matched = 1;
215                                        break;
216                                }
217
218                        /* None of the entities were matched, so return the string */
219                        if( !matched )
220                        {
221                                in = cs - 1;
222                                *(s++) = *(in++);
223                        }
224                }
225                else
226                {
227                        *(s++) = *(in++);
228                }
229        }
230       
231        strcpy( start, out );
232}
233
234char *escape_html( const char *html )
235{
236        const char *c = html;
237        GString *ret;
238        char *str;
239       
240        if( html == NULL )
241                return( NULL );
242       
243        ret = g_string_new( "" );
244       
245        while( *c )
246        {
247                switch( *c )
248                {
249                        case '&':
250                                ret = g_string_append( ret, "&amp;" );
251                                break;
252                        case '<':
253                                ret = g_string_append( ret, "&lt;" );
254                                break;
255                        case '>':
256                                ret = g_string_append( ret, "&gt;" );
257                                break;
258                        case '"':
259                                ret = g_string_append( ret, "&quot;" );
260                                break;
261                        default:
262                                ret = g_string_append_c( ret, *c );
263                }
264                c ++;
265        }
266       
267        str = ret->str;
268        g_string_free( ret, FALSE );
269        return( str );
270}
271
272/* Decode%20a%20file%20name                                             */
273void http_decode( char *s )
274{
275        char *t;
276        int i, j, k;
277       
278        t = g_new( char, strlen( s ) + 1 );
279       
280        for( i = j = 0; s[i]; i ++, j ++ )
281        {
282                if( s[i] == '%' )
283                {
284                        if( sscanf( s + i + 1, "%2x", &k ) )
285                        {
286                                t[j] = k;
287                                i += 2;
288                        }
289                        else
290                        {
291                                *t = 0;
292                                break;
293                        }
294                }
295                else
296                {
297                        t[j] = s[i];
298                }
299        }
300        t[j] = 0;
301       
302        strcpy( s, t );
303        g_free( t );
304}
305
306/* Warning: This one explodes the string. Worst-cases can make the string 3x its original size! */
307/* This fuction is safe, but make sure you call it safely as well! */
308void http_encode( char *s )
309{
310        char t[strlen(s)+1];
311        int i, j;
312       
313        strcpy( t, s );
314        for( i = j = 0; t[i]; i ++, j ++ )
315        {
316                /* Warning: isalnum() is locale-aware, so don't use it here! */
317                if( ( t[i] >= 'A' && t[i] <= 'Z' ) ||
318                    ( t[i] >= 'a' && t[i] <= 'z' ) ||
319                    ( t[i] >= '0' && t[i] <= '9' ) ||
320                    strchr( "._-~", t[i] ) )
321                {
322                        s[j] = t[i];
323                }
324                else
325                {
326                        sprintf( s + j, "%%%02X", ((unsigned char*)t)[i] );
327                        j += 2;
328                }
329        }
330        s[j] = 0;
331}
332
333/* Strip newlines from a string. Modifies the string passed to it. */ 
334char *strip_newlines( char *source )
335{
336        int i; 
337
338        for( i = 0; source[i] != '\0'; i ++ )
339                if( source[i] == '\n' || source[i] == '\r' )
340                        source[i] = ' ';
341       
342        return source;
343}
344
345/* Wrap an IPv4 address into IPv6 space. Not thread-safe... */
346char *ipv6_wrap( char *src )
347{
348        static char dst[64];
349        int i;
350       
351        for( i = 0; src[i]; i ++ )
352                if( ( src[i] < '0' || src[i] > '9' ) && src[i] != '.' )
353                        break;
354       
355        /* Hmm, it's not even an IP... */
356        if( src[i] )
357                return src;
358       
359        g_snprintf( dst, sizeof( dst ), "::ffff:%s", src );
360       
361        return dst;
362}
363
364/* Unwrap an IPv4 address into IPv6 space. Thread-safe, because it's very simple. :-) */
365char *ipv6_unwrap( char *src )
366{
367        int i;
368       
369        if( g_strncasecmp( src, "::ffff:", 7 ) != 0 )
370                return src;
371       
372        for( i = 7; src[i]; i ++ )
373                if( ( src[i] < '0' || src[i] > '9' ) && src[i] != '.' )
374                        break;
375       
376        /* Hmm, it's not even an IP... */
377        if( src[i] )
378                return src;
379       
380        return ( src + 7 );
381}
382
383/* Convert from one charset to another.
384   
385   from_cs, to_cs: Source and destination charsets
386   src, dst: Source and destination strings
387   size: Size if src. 0 == use strlen(). strlen() is not reliable for UNICODE/UTF16 strings though.
388   maxbuf: Maximum number of bytes to write to dst
389   
390   Returns the number of bytes written to maxbuf or -1 on an error.
391*/
392signed int do_iconv( char *from_cs, char *to_cs, char *src, char *dst, size_t size, size_t maxbuf )
393{
394        GIConv cd;
395        size_t res;
396        size_t inbytesleft, outbytesleft;
397        char *inbuf = src;
398        char *outbuf = dst;
399       
400        cd = g_iconv_open( to_cs, from_cs );
401        if( cd == (GIConv) -1 )
402                return -1;
403       
404        inbytesleft = size ? size : strlen( src );
405        outbytesleft = maxbuf - 1;
406        res = g_iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft );
407        *outbuf = '\0';
408        g_iconv_close( cd );
409       
410        if( res != 0 )
411                return -1;
412        else
413                return outbuf - dst;
414}
415
416/* A pretty reliable random number generator. Tries to use the /dev/random
417   devices first, and falls back to the random number generator from libc
418   when it fails. Opens randomizer devices with O_NONBLOCK to make sure a
419   lack of entropy won't halt BitlBee. */
420void random_bytes( unsigned char *buf, int count )
421{
422#ifndef _WIN32
423        static int use_dev = -1;
424       
425        /* Actually this probing code isn't really necessary, is it? */
426        if( use_dev == -1 )
427        {
428                if( access( "/dev/random", R_OK ) == 0 || access( "/dev/urandom", R_OK ) == 0 )
429                        use_dev = 1;
430                else
431                {
432                        use_dev = 0;
433                        srand( ( getpid() << 16 ) ^ time( NULL ) );
434                }
435        }
436       
437        if( use_dev )
438        {
439                int fd;
440               
441                /* At least on Linux, /dev/random can block if there's not
442                   enough entropy. We really don't want that, so if it can't
443                   give anything, use /dev/urandom instead. */
444                if( ( fd = open( "/dev/random", O_RDONLY | O_NONBLOCK ) ) >= 0 )
445                        if( read( fd, buf, count ) == count )
446                        {
447                                close( fd );
448                                return;
449                        }
450                close( fd );
451               
452                /* urandom isn't supposed to block at all, but just to be
453                   sure. If it blocks, we'll disable use_dev and use the libc
454                   randomizer instead. */
455                if( ( fd = open( "/dev/urandom", O_RDONLY | O_NONBLOCK ) ) >= 0 )
456                        if( read( fd, buf, count ) == count )
457                        {
458                                close( fd );
459                                return;
460                        }
461                close( fd );
462               
463                /* If /dev/random blocks once, we'll still try to use it
464                   again next time. If /dev/urandom also fails for some
465                   reason, stick with libc during this session. */
466               
467                use_dev = 0;
468                srand( ( getpid() << 16 ) ^ time( NULL ) );
469        }
470       
471        if( !use_dev )
472#endif
473        {
474                int i;
475               
476                /* Possibly the LSB of rand() isn't very random on some
477                   platforms. Seems okay on at least Linux and OSX though. */
478                for( i = 0; i < count; i ++ )
479                        buf[i] = rand() & 0xff;
480        }
481}
482
483int is_bool( char *value )
484{
485        if( *value == 0 )
486                return 0;
487       
488        if( ( g_strcasecmp( value, "true" ) == 0 ) || ( g_strcasecmp( value, "yes" ) == 0 ) || ( g_strcasecmp( value, "on" ) == 0 ) )
489                return 1;
490        if( ( g_strcasecmp( value, "false" ) == 0 ) || ( g_strcasecmp( value, "no" ) == 0 ) || ( g_strcasecmp( value, "off" ) == 0 ) )
491                return 1;
492       
493        while( *value )
494                if( !isdigit( *value ) )
495                        return 0;
496                else
497                        value ++;
498       
499        return 1;
500}
501
502int bool2int( char *value )
503{
504        int i;
505       
506        if( ( g_strcasecmp( value, "true" ) == 0 ) || ( g_strcasecmp( value, "yes" ) == 0 ) || ( g_strcasecmp( value, "on" ) == 0 ) )
507                return 1;
508        if( ( g_strcasecmp( value, "false" ) == 0 ) || ( g_strcasecmp( value, "no" ) == 0 ) || ( g_strcasecmp( value, "off" ) == 0 ) )
509                return 0;
510       
511        if( sscanf( value, "%d", &i ) == 1 )
512                return i;
513       
514        return 0;
515}
516
517struct ns_srv_reply **srv_lookup( char *service, char *protocol, char *domain )
518{       
519        struct ns_srv_reply **replies = NULL;
520#ifdef HAVE_RESOLV_A
521        struct ns_srv_reply *reply = NULL;
522        char name[1024];
523        unsigned char querybuf[1024];
524        const unsigned char *buf;
525        ns_msg nsh;
526        ns_rr rr;
527        int n, len, size;
528       
529        g_snprintf( name, sizeof( name ), "_%s._%s.%s", service, protocol, domain );
530       
531        if( ( size = res_query( name, ns_c_in, ns_t_srv, querybuf, sizeof( querybuf ) ) ) <= 0 )
532                return NULL;
533       
534        if( ns_initparse( querybuf, size, &nsh ) != 0 )
535                return NULL;
536       
537        n = 0;
538        while( ns_parserr( &nsh, ns_s_an, n, &rr ) == 0 )
539        {
540                char name[NS_MAXDNAME];
541
542                if( ns_rr_rdlen( rr ) < 7)
543                    break;
544
545                buf = ns_rr_rdata( rr );
546               
547                if( dn_expand(querybuf, querybuf + size, &buf[6], name, NS_MAXDNAME) == -1 )
548                        break;
549
550                len = strlen(name) + 1;
551               
552                reply = g_malloc( sizeof( struct ns_srv_reply ) + len );
553                memcpy( reply->name, name, len );
554               
555                reply->prio = ( buf[0] << 8 ) | buf[1];
556                reply->weight = ( buf[2] << 8 ) | buf[3];
557                reply->port = ( buf[4] << 8 ) | buf[5];
558               
559                n ++;
560                replies = g_renew( struct ns_srv_reply *, replies, n + 1 );
561                replies[n-1] = reply;
562        }
563        if( replies )
564                replies[n] = NULL;
565#endif
566       
567        return replies;
568}
569
570void srv_free( struct ns_srv_reply **srv )
571{
572        int i;
573       
574        if( srv == NULL )
575                return;
576       
577        for( i = 0; srv[i]; i ++ )
578                g_free( srv[i] );
579        g_free( srv );
580}
581
582/* Word wrapping. Yes, I know this isn't UTF-8 clean. I'm willing to take the risk. */
583char *word_wrap( const char *msg, int line_len )
584{
585        GString *ret = g_string_sized_new( strlen( msg ) + 16 );
586       
587        while( strlen( msg ) > line_len )
588        {
589                int i;
590               
591                /* First try to find out if there's a newline already. Don't
592                   want to add more splits than necessary. */
593                for( i = line_len; i > 0 && msg[i] != '\n'; i -- );
594                if( msg[i] == '\n' )
595                {
596                        g_string_append_len( ret, msg, i + 1 );
597                        msg += i + 1;
598                        continue;
599                }
600               
601                for( i = line_len; i > 0; i -- )
602                {
603                        if( msg[i] == '-' )
604                        {
605                                g_string_append_len( ret, msg, i + 1 );
606                                g_string_append_c( ret, '\n' );
607                                msg += i + 1;
608                                break;
609                        }
610                        else if( msg[i] == ' ' )
611                        {
612                                g_string_append_len( ret, msg, i );
613                                g_string_append_c( ret, '\n' );
614                                msg += i + 1;
615                                break;
616                        }
617                }
618                if( i == 0 )
619                {
620                        g_string_append_len( ret, msg, line_len );
621                        g_string_append_c( ret, '\n' );
622                        msg += line_len;
623                }
624        }
625        g_string_append( ret, msg );
626       
627        return g_string_free( ret, FALSE );
628}
629
630gboolean ssl_sockerr_again( void *ssl )
631{
632        if( ssl )
633                return ssl_errno == SSL_AGAIN;
634        else
635                return sockerr_again();
636}
637
638/* Returns values: -1 == Failure (base64-decoded to something unexpected)
639                    0 == Okay
640                    1 == Password doesn't match the hash. */
641int md5_verify_password( char *password, char *hash )
642{
643        md5_byte_t *pass_dec = NULL;
644        md5_byte_t pass_md5[16];
645        md5_state_t md5_state;
646        int ret = -1, i;
647       
648        if( base64_decode( hash, &pass_dec ) == 21 )
649        {
650                md5_init( &md5_state );
651                md5_append( &md5_state, (md5_byte_t*) password, strlen( password ) );
652                md5_append( &md5_state, (md5_byte_t*) pass_dec + 16, 5 ); /* Hmmm, salt! */
653                md5_finish( &md5_state, pass_md5 );
654               
655                for( i = 0; i < 16; i ++ )
656                {
657                        if( pass_dec[i] != pass_md5[i] )
658                        {
659                                ret = 1;
660                                break;
661                        }
662                }
663               
664                /* If we reached the end of the loop, it was a match! */
665                if( i == 16 )
666                        ret = 0;
667        }
668       
669        g_free( pass_dec );
670
671        return ret;
672}
673
674/* Split commands (root-style, *not* IRC-style). Handles "quoting of"
675   white\ space in 'various ways'. Returns a NULL-terminated static
676   char** so watch out with nested use! Definitely not thread-safe. */
677char **split_command_parts( char *command, int limit )
678{
679        static char *cmd[IRC_MAX_ARGS+1];
680        char *s, q = 0;
681        int k;
682       
683        memset( cmd, 0, sizeof( cmd ) );
684        cmd[0] = command;
685        k = 1;
686        for( s = command; *s && k < IRC_MAX_ARGS; s ++ )
687        {
688                if( *s == ' ' && !q )
689                {
690                        *s = 0;
691                        while( *++s == ' ' );
692                        if( k != limit && (*s == '"' || *s == '\'') )
693                        {
694                                q = *s;
695                                s ++;
696                        }
697                        if( *s )
698                        {
699                                cmd[k++] = s;
700                                if (limit && k > limit) {
701                                        break;
702                                }
703                                s --;
704                        }
705                        else
706                        {
707                                break;
708                        }
709                }
710                else if( *s == '\\' && ( ( !q && s[1] ) || ( q && q == s[1] ) ) )
711                {
712                        char *cpy;
713                       
714                        for( cpy = s; *cpy; cpy ++ )
715                                cpy[0] = cpy[1];
716                }
717                else if( *s == q )
718                {
719                        q = *s = 0;
720                }
721        }
722       
723        /* Full zero-padding for easier argc checking. */
724        while( k <= IRC_MAX_ARGS )
725                cmd[k++] = NULL;
726       
727        return cmd;
728}
729
730char *get_rfc822_header( const char *text, const char *header, int len )
731{
732        int hlen = strlen( header ), i;
733        const char *ret;
734       
735        if( text == NULL )
736                return NULL;
737       
738        if( len == 0 )
739                len = strlen( text );
740       
741        i = 0;
742        while( ( i + hlen ) < len )
743        {
744                /* Maybe this is a bit over-commented, but I just hate this part... */
745                if( g_strncasecmp( text + i, header, hlen ) == 0 )
746                {
747                        /* Skip to the (probable) end of the header */
748                        i += hlen;
749                       
750                        /* Find the first non-[: \t] character */
751                        while( i < len && ( text[i] == ':' || text[i] == ' ' || text[i] == '\t' ) ) i ++;
752                       
753                        /* Make sure we're still inside the string */
754                        if( i >= len ) return( NULL );
755                       
756                        /* Save the position */
757                        ret = text + i;
758                       
759                        /* Search for the end of this line */
760                        while( i < len && text[i] != '\r' && text[i] != '\n' ) i ++;
761                       
762                        /* Make sure we're still inside the string */
763                        if( i >= len ) return( NULL );
764                       
765                        /* Copy the found data */
766                        return( g_strndup( ret, text + i - ret ) );
767                }
768               
769                /* This wasn't the header we were looking for, skip to the next line. */
770                while( i < len && ( text[i] != '\r' && text[i] != '\n' ) ) i ++;
771                while( i < len && ( text[i] == '\r' || text[i] == '\n' ) ) i ++;
772               
773                /* End of headers? */
774                if( ( i >= 4 && strncmp( text + i - 4, "\r\n\r\n", 4 ) == 0 ) ||
775                    ( i >= 2 && ( strncmp( text + i - 2, "\n\n", 2 ) == 0 ||   
776                                  strncmp( text + i - 2, "\r\r", 2 ) == 0 ) ) )
777                {
778                        break;
779                }
780        }
781       
782        return NULL;
783}
Note: See TracBrowser for help on using the repository browser.