source: lib/misc.c @ b4b8f1e

Last change on this file since b4b8f1e was 0e788f5, checked in by Wilmer van der Gaast <wilmer@…>, at 2013-02-21T19:15:59Z

I'm still bored on a long flight. Wrote a script to automatically update
my copyright mentions since some were getting pretty stale. Left files not
touched since before 2012 alone so that this change doesn't touch almost
EVERY source file.

  • 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 i, 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                size = ns_rr_rdlen( rr );
541                buf = ns_rr_rdata( rr );
542               
543                len = 0;
544                for( i = 6; i < size && buf[i]; i += buf[i] + 1 )
545                        len += buf[i] + 1;
546               
547                if( i > size )
548                        break;
549               
550                reply = g_malloc( sizeof( struct ns_srv_reply ) + len );
551                memcpy( reply->name, buf + 7, len );
552               
553                for( i = buf[6]; i < len && buf[7+i]; i += buf[7+i] + 1 )
554                        reply->name[i] = '.';
555               
556                if( i > len )
557                {
558                        g_free( reply );
559                        break;
560                }
561               
562                reply->prio = ( buf[0] << 8 ) | buf[1];
563                reply->weight = ( buf[2] << 8 ) | buf[3];
564                reply->port = ( buf[4] << 8 ) | buf[5];
565               
566                n ++;
567                replies = g_renew( struct ns_srv_reply *, replies, n + 1 );
568                replies[n-1] = reply;
569        }
570        if( replies )
571                replies[n] = NULL;
572#endif
573       
574        return replies;
575}
576
577void srv_free( struct ns_srv_reply **srv )
578{
579        int i;
580       
581        if( srv == NULL )
582                return;
583       
584        for( i = 0; srv[i]; i ++ )
585                g_free( srv[i] );
586        g_free( srv );
587}
588
589/* Word wrapping. Yes, I know this isn't UTF-8 clean. I'm willing to take the risk. */
590char *word_wrap( const char *msg, int line_len )
591{
592        GString *ret = g_string_sized_new( strlen( msg ) + 16 );
593       
594        while( strlen( msg ) > line_len )
595        {
596                int i;
597               
598                /* First try to find out if there's a newline already. Don't
599                   want to add more splits than necessary. */
600                for( i = line_len; i > 0 && msg[i] != '\n'; i -- );
601                if( msg[i] == '\n' )
602                {
603                        g_string_append_len( ret, msg, i + 1 );
604                        msg += i + 1;
605                        continue;
606                }
607               
608                for( i = line_len; i > 0; i -- )
609                {
610                        if( msg[i] == '-' )
611                        {
612                                g_string_append_len( ret, msg, i + 1 );
613                                g_string_append_c( ret, '\n' );
614                                msg += i + 1;
615                                break;
616                        }
617                        else if( msg[i] == ' ' )
618                        {
619                                g_string_append_len( ret, msg, i );
620                                g_string_append_c( ret, '\n' );
621                                msg += i + 1;
622                                break;
623                        }
624                }
625                if( i == 0 )
626                {
627                        g_string_append_len( ret, msg, line_len );
628                        g_string_append_c( ret, '\n' );
629                        msg += line_len;
630                }
631        }
632        g_string_append( ret, msg );
633       
634        return g_string_free( ret, FALSE );
635}
636
637gboolean ssl_sockerr_again( void *ssl )
638{
639        if( ssl )
640                return ssl_errno == SSL_AGAIN;
641        else
642                return sockerr_again();
643}
644
645/* Returns values: -1 == Failure (base64-decoded to something unexpected)
646                    0 == Okay
647                    1 == Password doesn't match the hash. */
648int md5_verify_password( char *password, char *hash )
649{
650        md5_byte_t *pass_dec = NULL;
651        md5_byte_t pass_md5[16];
652        md5_state_t md5_state;
653        int ret = -1, i;
654       
655        if( base64_decode( hash, &pass_dec ) == 21 )
656        {
657                md5_init( &md5_state );
658                md5_append( &md5_state, (md5_byte_t*) password, strlen( password ) );
659                md5_append( &md5_state, (md5_byte_t*) pass_dec + 16, 5 ); /* Hmmm, salt! */
660                md5_finish( &md5_state, pass_md5 );
661               
662                for( i = 0; i < 16; i ++ )
663                {
664                        if( pass_dec[i] != pass_md5[i] )
665                        {
666                                ret = 1;
667                                break;
668                        }
669                }
670               
671                /* If we reached the end of the loop, it was a match! */
672                if( i == 16 )
673                        ret = 0;
674        }
675       
676        g_free( pass_dec );
677
678        return ret;
679}
680
681/* Split commands (root-style, *not* IRC-style). Handles "quoting of"
682   white\ space in 'various ways'. Returns a NULL-terminated static
683   char** so watch out with nested use! Definitely not thread-safe. */
684char **split_command_parts( char *command )
685{
686        static char *cmd[IRC_MAX_ARGS+1];
687        char *s, q = 0;
688        int k;
689       
690        memset( cmd, 0, sizeof( cmd ) );
691        cmd[0] = command;
692        k = 1;
693        for( s = command; *s && k < IRC_MAX_ARGS; s ++ )
694                if( *s == ' ' && !q )
695                {
696                        *s = 0;
697                        while( *++s == ' ' );
698                        if( *s == '"' || *s == '\'' )
699                        {
700                                q = *s;
701                                s ++;
702                        }
703                        if( *s )
704                        {
705                                cmd[k++] = s;
706                                s --;
707                        }
708                        else
709                        {
710                                break;
711                        }
712                }
713                else if( *s == '\\' && ( ( !q && s[1] ) || ( q && q == s[1] ) ) )
714                {
715                        char *cpy;
716                       
717                        for( cpy = s; *cpy; cpy ++ )
718                                cpy[0] = cpy[1];
719                }
720                else if( *s == q )
721                {
722                        q = *s = 0;
723                }
724       
725        /* Full zero-padding for easier argc checking. */
726        while( k <= IRC_MAX_ARGS )
727                cmd[k++] = NULL;
728       
729        return cmd;
730}
731
732char *get_rfc822_header( const char *text, const char *header, int len )
733{
734        int hlen = strlen( header ), i;
735        const char *ret;
736       
737        if( text == NULL )
738                return NULL;
739       
740        if( len == 0 )
741                len = strlen( text );
742       
743        i = 0;
744        while( ( i + hlen ) < len )
745        {
746                /* Maybe this is a bit over-commented, but I just hate this part... */
747                if( g_strncasecmp( text + i, header, hlen ) == 0 )
748                {
749                        /* Skip to the (probable) end of the header */
750                        i += hlen;
751                       
752                        /* Find the first non-[: \t] character */
753                        while( i < len && ( text[i] == ':' || text[i] == ' ' || text[i] == '\t' ) ) i ++;
754                       
755                        /* Make sure we're still inside the string */
756                        if( i >= len ) return( NULL );
757                       
758                        /* Save the position */
759                        ret = text + i;
760                       
761                        /* Search for the end of this line */
762                        while( i < len && text[i] != '\r' && text[i] != '\n' ) i ++;
763                       
764                        /* Make sure we're still inside the string */
765                        if( i >= len ) return( NULL );
766                       
767                        /* Copy the found data */
768                        return( g_strndup( ret, text + i - ret ) );
769                }
770               
771                /* This wasn't the header we were looking for, skip to the next line. */
772                while( i < len && ( text[i] != '\r' && text[i] != '\n' ) ) i ++;
773                while( i < len && ( text[i] == '\r' || text[i] == '\n' ) ) i ++;
774               
775                /* End of headers? */
776                if( ( i >= 4 && strncmp( text + i - 4, "\r\n\r\n", 4 ) == 0 ) ||
777                    ( i >= 2 && ( strncmp( text + i - 2, "\n\n", 2 ) == 0 ||   
778                                  strncmp( text + i - 2, "\r\r", 2 ) == 0 ) ) )
779                {
780                        break;
781                }
782        }
783       
784        return NULL;
785}
Note: See TracBrowser for help on using the repository browser.