view libpurple/protocols/gg/lib/common.c @ 23731:59f8890ae8cf

Fix up the MSN Inbox URL. There was a typo that caused a use-after-free and also needed to escape the auth key because now it includes slashes and equals-signs, etc.
author Elliott Sales de Andrade <qulogic@pidgin.im>
date Thu, 07 Aug 2008 05:06:49 +0000
parents 44b4e8bd759b
children 259bbfb423d4
line wrap: on
line source

/* $Id: common.c 16856 2006-08-19 01:13:25Z evands $ */

/*
 *  (C) Copyright 2001-2002 Wojtek Kaniewski <wojtekka@irc.pl>
 *                          Robert J. Woźny <speedy@ziew.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License Version
 *  2.1 as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
 *  USA.
 */

#ifndef _WIN32
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef sun
#  include <sys/filio.h>
#endif
#endif

#include <errno.h>
#include <fcntl.h>
#ifndef _WIN32
#include <netdb.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "libgadu.h"

FILE *gg_debug_file = NULL;

#ifndef GG_DEBUG_DISABLE

/*
 * gg_debug() // funkcja wewnętrzna
 *
 * wyświetla komunikat o danym poziomie, o ile użytkownik sobie tego życzy.
 *
 *  - level - poziom wiadomości
 *  - format... - treść wiadomości (kompatybilna z printf())
 */
void gg_debug(int level, const char *format, ...)
{
	va_list ap;
	int old_errno = errno;
	
	if (gg_debug_handler) {
		va_start(ap, format);
		(*gg_debug_handler)(level, format, ap);
		va_end(ap);

		goto cleanup;
	}
	
	if ((gg_debug_level & level)) {
		va_start(ap, format);
		vfprintf((gg_debug_file) ? gg_debug_file : stderr, format, ap);
		va_end(ap);
	}

cleanup:
	errno = old_errno;
}

#endif

/*
 * gg_vsaprintf() // funkcja pomocnicza
 *
 * robi dokładnie to samo, co vsprintf(), tyle że alokuje sobie wcześniej
 * miejsce na dane. powinno działać na tych maszynach, które mają funkcję
 * vsnprintf() zgodną z C99, jak i na wcześniejszych.
 *
 *  - format - opis wyświetlanego tekstu jak dla printf()
 *  - ap - lista argumentów dla printf()
 *
 * zaalokowany bufor, który należy później zwolnić, lub NULL
 * jeśli nie udało się wykonać zadania.
 */
char *gg_vsaprintf(const char *format, va_list ap)
{
	int size = 0;
	const char *start;
	char *buf = NULL;
	
#ifdef __GG_LIBGADU_HAVE_VA_COPY
	va_list aq;

	va_copy(aq, ap);
#else
#  ifdef __GG_LIBGADU_HAVE___VA_COPY
	va_list aq;

	__va_copy(aq, ap);
#  endif
#endif

	start = format; 

#ifndef __GG_LIBGADU_HAVE_C99_VSNPRINTF
	{
		int res;
		char *tmp;
		
		size = 128;
		do {
			size *= 2;
			if (!(tmp = realloc(buf, size))) {
				free(buf);
				return NULL;
			}
			buf = tmp;
			res = vsnprintf(buf, size, format, ap);
		} while (res == size - 1 || res == -1);
	}
#else
	{
		char tmp[2];
		
		/* libce Solarisa przy buforze NULL zawsze zwracają -1, więc
		 * musimy podać coś istniejącego jako cel printf()owania. */
		size = vsnprintf(tmp, sizeof(tmp), format, ap);
		if (!(buf = malloc(size + 1)))
			return NULL;
	}
#endif

	format = start;
	
#ifdef __GG_LIBGADU_HAVE_VA_COPY
	vsnprintf(buf, size + 1, format, aq);
	va_end(aq);
#else
#  ifdef __GG_LIBGADU_HAVE___VA_COPY
	vsnprintf(buf, size + 1, format, aq);
	va_end(aq);
#  else
	vsnprintf(buf, size + 1, format, ap);
#  endif
#endif
	
	return buf;
}

/*
 * gg_saprintf() // funkcja pomocnicza
 *
 * robi dokładnie to samo, co sprintf(), tyle że alokuje sobie wcześniej
 * miejsce na dane. powinno działać na tych maszynach, które mają funkcję
 * vsnprintf() zgodną z C99, jak i na wcześniejszych.
 *
 *  - format... - treść taka sama jak w funkcji printf()
 *
 * zaalokowany bufor, który należy później zwolnić, lub NULL
 * jeśli nie udało się wykonać zadania.
 */
char *gg_saprintf(const char *format, ...)
{
	va_list ap;
	char *res;

	va_start(ap, format);
	res = gg_vsaprintf(format, ap);
	va_end(ap);

	return res;
}

/*
 * gg_get_line() // funkcja pomocnicza
 * 
 * podaje kolejną linię z bufora tekstowego. niszczy go bezpowrotnie, dzieląc
 * na kolejne stringi. zdarza się, nie ma potrzeby pisania funkcji dublującej
 * bufor żeby tylko mieć nieruszone dane wejściowe, skoro i tak nie będą nam
 * poźniej potrzebne. obcina `\r\n'.
 * 
 *  - ptr - wskaźnik do zmiennej, która przechowuje aktualną pozycję
 *    w przemiatanym buforze
 * 
 * wskaźnik do kolejnej linii tekstu lub NULL, jeśli to już koniec bufora.
 */
char *gg_get_line(char **ptr)
{
	char *foo, *res;

	if (!ptr || !*ptr || !strcmp(*ptr, ""))
		return NULL;

	res = *ptr;

	if (!(foo = strchr(*ptr, '\n')))
		*ptr += strlen(*ptr);
	else {
		*ptr = foo + 1;
		*foo = 0;
		if (strlen(res) > 1 && res[strlen(res) - 1] == '\r')
			res[strlen(res) - 1] = 0;
	}

	return res;
}

/*
 * gg_connect() // funkcja pomocnicza
 *
 * łączy się z serwerem. pierwszy argument jest typu (void *), żeby nie
 * musieć niczego inkludować w libgadu.h i nie psuć jakiś głupich zależności
 * na dziwnych systemach.
 *
 *  - addr - adres serwera (struct in_addr *)
 *  - port - port serwera
 *  - async - asynchroniczne połączenie
 *
 * deskryptor gniazda lub -1 w przypadku błędu (kod błędu w zmiennej errno).
 */
int gg_connect(void *addr, int port, int async)
{
	int sock, one = 1, errno2;
	struct sockaddr_in sin;
	struct in_addr *a = addr;
	struct sockaddr_in myaddr;

	gg_debug(GG_DEBUG_FUNCTION, "** gg_connect(%s, %d, %d);\n", inet_ntoa(*a), port, async);
	
	if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
		gg_debug(GG_DEBUG_MISC, "// gg_connect() socket() failed (errno=%d, %s)\n", errno, strerror(errno));
		return -1;
	}

	memset(&myaddr, 0, sizeof(myaddr));
	myaddr.sin_family = AF_INET;

	myaddr.sin_addr.s_addr = gg_local_ip;

	if (bind(sock, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1) {
		gg_debug(GG_DEBUG_MISC, "// gg_connect() bind() failed (errno=%d, %s)\n", errno, strerror(errno));
		return -1;
	}

#ifdef ASSIGN_SOCKETS_TO_THREADS
	gg_win32_thread_socket(0, sock);
#endif

	if (async) {
#ifdef FIONBIO
		if (ioctl(sock, FIONBIO, &one) == -1) {
#else
		if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) {
#endif
			gg_debug(GG_DEBUG_MISC, "// gg_connect() ioctl() failed (errno=%d, %s)\n", errno, strerror(errno));
			errno2 = errno;
			close(sock);
			errno = errno2;
			return -1;
		}
	}

	sin.sin_port = htons(port);
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = a->s_addr;
	
	if (connect(sock, (struct sockaddr*) &sin, sizeof(sin)) == -1) {
		if (errno && (!async || errno != EINPROGRESS)) {
			gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() failed (errno=%d, %s)\n", errno, strerror(errno));
			errno2 = errno;
			close(sock);
			errno = errno2;
			return -1;
		}
		gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() in progress\n");
	}
	
	return sock;
}

/*
 * gg_read_line() // funkcja pomocnicza
 *
 * czyta jedną linię tekstu z gniazda.
 *
 *  - sock - deskryptor gniazda
 *  - buf - wskaźnik do bufora
 *  - length - długość bufora
 *
 * jeśli trafi na błąd odczytu lub podano nieprawidłowe parametry, zwraca NULL.
 * inaczej zwraca buf.
 */
char *gg_read_line(int sock, char *buf, int length)
{
	int ret;

	if (!buf || length < 0)
		return NULL;

	for (; length > 1; buf++, length--) {
		do {
			if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR) {
				gg_debug(GG_DEBUG_MISC, "// gg_read_line() error on read (errno=%d, %s)\n", errno, strerror(errno));
				*buf = 0;
				return NULL;
			} else if (ret == 0) {
				gg_debug(GG_DEBUG_MISC, "// gg_read_line() eof reached\n");
				*buf = 0;
				return NULL;
			}
		} while (ret == -1 && errno == EINTR);

		if (*buf == '\n') {
			buf++;
			break;
		}
	}

	*buf = 0;
	return buf;
}

/*
 * gg_chomp() // funkcja pomocnicza
 *
 * ucina "\r\n" lub "\n" z końca linii.
 *
 *  - line - linia do przycięcia
 */
void gg_chomp(char *line)
{
	int len;
	
	if (!line)
		return;

	len = strlen(line);
	
	if (len > 0 && line[len - 1] == '\n')
		line[--len] = 0;
	if (len > 0 && line[len - 1] == '\r')
		line[--len] = 0;
}

/*
 * gg_urlencode() // funkcja wewnętrzna
 *
 * zamienia podany tekst na ciąg znaków do formularza http. przydaje się
 * przy różnych usługach katalogu publicznego.
 *
 *  - str - ciąg znaków do zakodowania
 *
 * zaalokowany bufor, który należy później zwolnić albo NULL
 * w przypadku błędu.
 */
char *gg_urlencode(const char *str)
{
	char *q, *buf, hex[] = "0123456789abcdef";
	const char *p;
	unsigned int size = 0;

	if (!str)
		str = "";

	for (p = str; *p; p++, size++) {
		if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == ' ') || (*p == '@') || (*p == '.') || (*p == '-'))
			size += 2;
	}

	if (!(buf = malloc(size + 1)))
		return NULL;

	for (p = str, q = buf; *p; p++, q++) {
		if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || (*p == '@') || (*p == '.') || (*p == '-'))
			*q = *p;
		else {
			if (*p == ' ')
				*q = '+';
			else {
				*q++ = '%';
				*q++ = hex[*p >> 4 & 15];
				*q = hex[*p & 15];
			}
		}
	}

	*q = 0;

	return buf;
}

/*
 * gg_http_hash() // funkcja wewnętrzna
 *
 * funkcja licząca hash dla adresu e-mail, hasła i paru innych.
 *
 *  - format... - format kolejnych parametrów ('s' jeśli dany parametr jest
 *                ciągiem znaków lub 'u' jeśli numerem GG)
 *
 * hash wykorzystywany przy rejestracji i wszelkich manipulacjach własnego
 * wpisu w katalogu publicznym.
 */
int gg_http_hash(const char *format, ...)
{
	unsigned int a, c, i, j;
	va_list ap;
	int b = -1;

	va_start(ap, format);

	for (j = 0; j < strlen(format); j++) {
		char *arg, buf[16];

		if (format[j] == 'u') {
			snprintf(buf, sizeof(buf), "%d", va_arg(ap, uin_t));
			arg = buf;
		} else {
			if (!(arg = va_arg(ap, char*)))
				arg = "";
		}	

		i = 0;
		while ((c = (unsigned char) arg[i++]) != 0) {
			a = (c ^ b) + (c << 8);
			b = (a >> 24) | (a << 8);
		}
	}

	va_end(ap);

	return (b < 0 ? -b : b);
}

/*
 * gg_gethostbyname() // funkcja pomocnicza
 *
 * odpowiednik gethostbyname() troszczący się o współbieżność, gdy mamy do
 * dyspozycji funkcję gethostbyname_r().
 *
 *  - hostname - nazwa serwera
 *
 * zwraca wskaźnik na strukturę in_addr, którą należy zwolnić.
 */
struct in_addr *gg_gethostbyname(const char *hostname)
{
	struct in_addr *addr = NULL;

#ifdef HAVE_GETHOSTBYNAME_R
	char *tmpbuf = NULL, *buf = NULL;
	struct hostent *hp = NULL, *hp2 = NULL;
	int h_errnop, ret;
	size_t buflen = 1024;
	int new_errno;
	
	new_errno = ENOMEM;
	
	if (!(addr = malloc(sizeof(struct in_addr))))
		goto cleanup;
	
	if (!(hp = calloc(1, sizeof(*hp))))
		goto cleanup;

	if (!(buf = malloc(buflen)))
		goto cleanup;

	tmpbuf = buf;
	
	while ((ret = gethostbyname_r(hostname, hp, buf, buflen, &hp2, &h_errnop)) == ERANGE) {
		buflen *= 2;
		
		if (!(tmpbuf = realloc(buf, buflen)))
			break;
		
		buf = tmpbuf;
	}
	
	if (ret)
		new_errno = h_errnop;

	if (ret || !hp2 || !tmpbuf)
		goto cleanup;
	
	memcpy(addr, hp->h_addr, sizeof(struct in_addr));
	
	free(buf);
	free(hp);
	
	return addr;
	
cleanup:
	errno = new_errno;
	
	if (addr)
		free(addr);
	if (hp)
		free(hp);
	if (buf)
		free(buf);
	
	return NULL;
#else
	struct hostent *hp;

	if (!(addr = malloc(sizeof(struct in_addr)))) {
		goto cleanup;
	}

	if (!(hp = gethostbyname(hostname)))
		goto cleanup;

	memcpy(addr, hp->h_addr, sizeof(struct in_addr));

	return addr;
	
cleanup:
	if (addr)
		free(addr);

	return NULL;
#endif
}

#ifdef ASSIGN_SOCKETS_TO_THREADS

typedef struct gg_win32_thread {
	int id;
	int socket;
	struct gg_win32_thread *next;
} gg_win32_thread;

struct gg_win32_thread *gg_win32_threads = 0;

/*
 * gg_win32_thread_socket() // funkcja pomocnicza, tylko dla win32
 *
 * zwraca deskryptor gniazda, które było ostatnio tworzone dla wątku
 * o podanym identyfikatorze.
 *
 * jeśli na win32 przy połączeniach synchronicznych zapamiętamy w jakim
 * wątku uruchomiliśmy funkcję, która się z czymkolwiek łączy, to z osobnego
 * wątku możemy anulować połączenie poprzez gg_win32_thread_socket(watek, -1);
 * 
 * - thread_id - id wątku. jeśli jest równe 0, brany jest aktualny wątek,
 *               jeśli równe -1, usuwa wpis o podanym sockecie.
 * - socket - deskryptor gniazda. jeśli równe 0, zwraca deskryptor gniazda
 *            dla podanego wątku, jeśli równe -1, usuwa wpis, jeśli coś
 *            innego, ustawia dla podanego wątku dany numer deskryptora.
 *
 * jeśli socket jest równe 0, zwraca deskryptor gniazda dla podanego wątku.
 */
int gg_win32_thread_socket(int thread_id, int socket)
{
	char close = (thread_id == -1) || socket == -1;
	gg_win32_thread *wsk = gg_win32_threads;
	gg_win32_thread **p_wsk = &gg_win32_threads;

	if (!thread_id)
		thread_id = GetCurrentThreadId();
	
	while (wsk) {
		if ((thread_id == -1 && wsk->socket == socket) || wsk->id == thread_id) {
			if (close) {
				/* socket zostaje usuniety */
				closesocket(wsk->socket);
				*p_wsk = wsk->next;
				free(wsk);
				return 1;
			} else if (!socket) {
				/* socket zostaje zwrocony */
				return wsk->socket;
			} else {
				/* socket zostaje ustawiony */
				wsk->socket = socket;
				return socket;
			}
		}
		p_wsk = &(wsk->next);
		wsk = wsk->next;
	}

	if (close && socket != -1)
		closesocket(socket);
	if (close || !socket)
		return 0;
	
	/* Dodaje nowy element */
	wsk = malloc(sizeof(gg_win32_thread));
	wsk->id = thread_id;
	wsk->socket = socket;
	wsk->next = 0;
	*p_wsk = wsk;

	return socket;
}

#endif /* ASSIGN_SOCKETS_TO_THREADS */

static char gg_base64_charset[] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/*
 * gg_base64_encode()
 *
 * zapisuje ciąg znaków w base64.
 *
 *  - buf - ciąg znaków.
 *
 * zaalokowany bufor.
 */
char *gg_base64_encode(const char *buf)
{
	char *out, *res;
	unsigned int i = 0, j = 0, k = 0, len = strlen(buf);
	
	res = out = malloc((len / 3 + 1) * 4 + 2);

	if (!res)
		return NULL;
	
	while (j <= len) {
		switch (i % 4) {
			case 0:
				k = (buf[j] & 252) >> 2;
				break;
			case 1:
				if (j < len)
					k = ((buf[j] & 3) << 4) | ((buf[j + 1] & 240) >> 4);
				else
					k = (buf[j] & 3) << 4;

				j++;
				break;
			case 2:
				if (j < len)
					k = ((buf[j] & 15) << 2) | ((buf[j + 1] & 192) >> 6);
				else
					k = (buf[j] & 15) << 2;

				j++;
				break;
			case 3:
				k = buf[j++] & 63;
				break;
		}
		*out++ = gg_base64_charset[k];
		i++;
	}

	if (i % 4)
		for (j = 0; j < 4 - (i % 4); j++, out++)
			*out = '=';
	
	*out = 0;
	
	return res;
}

/*
 * gg_base64_decode()
 *
 * dekoduje ciąg znaków z base64.
 *
 *  - buf - ciąg znaków.
 *
 * zaalokowany bufor.
 */
char *gg_base64_decode(const char *buf)
{
	char *res, *save, *foo, val;
	const char *end;
	unsigned int index = 0;

	if (!buf)
		return NULL;
	
	save = res = calloc(1, (strlen(buf) / 4 + 1) * 3 + 2);

	if (!save)
		return NULL;

	end = buf + strlen(buf);

	while (*buf && buf < end) {
		if (*buf == '\r' || *buf == '\n') {
			buf++;
			continue;
		}
		if (!(foo = strchr(gg_base64_charset, *buf)))
			foo = gg_base64_charset;
		val = (int)(foo - gg_base64_charset);
		buf++;
		switch (index) {
			case 0:
				*res |= val << 2;
				break;
			case 1:
				*res++ |= val >> 4;
				*res |= val << 4;
				break;
			case 2:
				*res++ |= val >> 2;
				*res |= val << 6;
				break;
			case 3:
				*res++ |= val;
				break;
		}
		index++;
		index %= 4;
	}
	*res = 0;
	
	return save;
}

/*
 * gg_proxy_auth() // funkcja wewnętrzna
 *
 * tworzy nagłówek autoryzacji dla proxy.
 * 
 * zaalokowany tekst lub NULL, jeśli proxy nie jest włączone lub nie wymaga
 * autoryzacji.
 */
char *gg_proxy_auth()
{
	char *tmp, *enc, *out;
	unsigned int tmp_size;
	
	if (!gg_proxy_enabled || !gg_proxy_username || !gg_proxy_password)
		return NULL;

	if (!(tmp = malloc((tmp_size = strlen(gg_proxy_username) + strlen(gg_proxy_password) + 2))))
		return NULL;

	snprintf(tmp, tmp_size, "%s:%s", gg_proxy_username, gg_proxy_password);

	if (!(enc = gg_base64_encode(tmp))) {
		free(tmp);
		return NULL;
	}
	
	free(tmp);

	if (!(out = malloc(strlen(enc) + 40))) {
		free(enc);
		return NULL;
	}
	
	snprintf(out, strlen(enc) + 40,  "Proxy-Authorization: Basic %s\r\n", enc);

	free(enc);

	return out;
}

static uint32_t gg_crc32_table[256];
static int gg_crc32_initialized = 0;

/*
 * gg_crc32_make_table()  // funkcja wewnętrzna
 */
static void gg_crc32_make_table()
{
	uint32_t h = 1;
	unsigned int i, j;

	memset(gg_crc32_table, 0, sizeof(gg_crc32_table));

	for (i = 128; i; i >>= 1) {
		h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0);

		for (j = 0; j < 256; j += 2 * i)
			gg_crc32_table[i + j] = gg_crc32_table[j] ^ h;
	}

	gg_crc32_initialized = 1;
}

/*
 * gg_crc32()
 *
 * wyznacza sumę kontrolną CRC32 danego bloku danych.
 *
 *  - crc - suma kontrola poprzedniego bloku danych lub 0 jeśli pierwszy
 *  - buf - bufor danych
 *  - size - ilość danych
 *
 * suma kontrolna CRC32.
 */
uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len)
{
	if (!gg_crc32_initialized)
		gg_crc32_make_table();

	if (!buf || len < 0)
		return crc;

	crc ^= 0xffffffffL;

	while (len--)
		crc = (crc >> 8) ^ gg_crc32_table[(crc ^ *buf++) & 0xff];

	return crc ^ 0xffffffffL;
}


/*
 * Local variables:
 * c-indentation-style: k&r
 * c-basic-offset: 8
 * indent-tabs-mode: notnil
 * End:
 *
 * vim: shiftwidth=8:
 */