view libpurple/protocols/novell/nmconn.c @ 31172:e89df17f5ae7

certificate: Better validation of chains which have an intermediate signed w/ MD5. We already distribute the CAcert class 3 root as a trusted root. Newer versions of GnuTLS (combined with the changes to deal with MSN's cert breakage) require us to check if the last cert (not just its issuer) is in our trusted store.
author Paul Aurich <paul@darkrain42.org>
date Sun, 30 Jan 2011 17:51:02 +0000
parents 44b4e8bd759b
children
line wrap: on
line source

/*
 * nmconn.c
 *
 * Copyright (c) 2004 Novell, Inc. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU 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
 *
 */

#include <glib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include "nmconn.h"

#ifdef _WIN32
#include <windows.h>
#endif

#define NO_ESCAPE(ch) ((ch == 0x20) || (ch >= 0x30 && ch <= 0x39) || \
					(ch >= 0x41 && ch <= 0x5a) || (ch >= 0x61 && ch <= 0x7a))

/* Read data from conn until the end of a line */
static NMERR_T
read_line(NMConn * conn, char *buff, int len)
{
	NMERR_T rc = NM_OK;
	int total_bytes = 0;

	while ((rc == NM_OK) && (total_bytes < (len - 1))) {
		rc = nm_read_all(conn, &buff[total_bytes], 1);
		if (rc == NM_OK) {
			total_bytes += 1;
			if (buff[total_bytes - 1] == '\n') {
				break;
			}
		}
	}
	buff[total_bytes] = '\0';

	return rc;
}

static char *
url_escape_string(char *src)
{
	guint32 escape = 0;
	char *p;
	char *q;
	char *encoded = NULL;
	int ch;

	static const char hex_table[16] = "0123456789abcdef";

	if (src == NULL) {
		return NULL;
	}

	/* Find number of chars to escape */
	for (p = src; *p != '\0'; p++) {
		ch = (guchar) *p;
		if (!NO_ESCAPE(ch)) {
			escape++;
		}
	}

	encoded = g_malloc((p - src) + (escape * 2) + 1);

	/* Escape the string */
	for (p = src, q = encoded; *p != '\0'; p++) {
		ch = (guchar) * p;
		if (NO_ESCAPE(ch)) {
			if (ch != 0x20) {
				*q = ch;
				q++;
			} else {
				*q = '+';
				q++;
			}
		} else {
			*q = '%';
			q++;

			*q = hex_table[ch >> 4];
			q++;

			*q = hex_table[ch & 15];
			q++;
		}
	}
	*q = '\0';

	return encoded;
}

static char *
encode_method(guint8 method)
{
	char *str;

	switch (method) {
		case NMFIELD_METHOD_EQUAL:
			str = "G";
			break;
		case NMFIELD_METHOD_UPDATE:
			str = "F";
			break;
		case NMFIELD_METHOD_GTE:
			str = "E";
			break;
		case NMFIELD_METHOD_LTE:
			str = "D";
			break;
		case NMFIELD_METHOD_NE:
			str = "C";
			break;
		case NMFIELD_METHOD_EXIST:
			str = "B";
			break;
		case NMFIELD_METHOD_NOTEXIST:
			str = "A";
			break;
		case NMFIELD_METHOD_SEARCH:
			str = "9";
			break;
		case NMFIELD_METHOD_MATCHBEGIN:
			str = "8";
			break;
		case NMFIELD_METHOD_MATCHEND:
			str = "7";
			break;
		case NMFIELD_METHOD_NOT_ARRAY:
			str = "6";
			break;
		case NMFIELD_METHOD_OR_ARRAY:
			str = "5";
			break;
		case NMFIELD_METHOD_AND_ARRAY:
			str = "4";
			break;
		case NMFIELD_METHOD_DELETE_ALL:
			str = "3";
			break;
		case NMFIELD_METHOD_DELETE:
			str = "2";
			break;
		case NMFIELD_METHOD_ADD:
			str = "1";
			break;
		default:					/* NMFIELD_METHOD_VALID */
			str = "0";
			break;
	}

	return str;
}

NMConn *
nm_create_conn(const char *addr, int port)
{
	NMConn *conn = 	g_new0(NMConn, 1);
	conn->addr = g_strdup(addr);
	conn->port = port;
	return conn;
}

void nm_release_conn(NMConn *conn)
{
	if (conn) {
		GSList *node;
		for (node = conn->requests; node; node = node->next) {
			if (node->data)
				nm_release_request(node->data);
		}
		g_slist_free(conn->requests);
		conn->requests = NULL;
		if (conn->ssl_conn) {
			g_free(conn->ssl_conn);
			conn->ssl_conn = NULL;
		}
		g_free(conn->addr);
		conn->addr = NULL;
		g_free(conn);
	}
}

int
nm_tcp_write(NMConn * conn, const void *buff, int len)
{
	if (conn == NULL || buff == NULL)
		return -1;

	if (!conn->use_ssl)
		return (write(conn->fd, buff, len));
	else if (conn->ssl_conn && conn->ssl_conn->write)
		return (conn->ssl_conn->write(conn->ssl_conn->data, buff, len));
	else
		return -1;
}

int
nm_tcp_read(NMConn * conn, void *buff, int len)
{
	if (conn == NULL || buff == NULL)
		return -1;

	if (!conn->use_ssl)
		return (read(conn->fd, buff, len));
	else if (conn->ssl_conn && conn->ssl_conn->read)
		return ((conn->ssl_conn->read)(conn->ssl_conn->data, buff, len));
	else
		return -1;
}

NMERR_T
nm_read_all(NMConn * conn, char *buff, int len)
{
	NMERR_T rc = NM_OK;
	int bytes_left = len;
	int bytes_read;
	int total_bytes = 0;
	int retry = 1000;

	if (conn == NULL || buff == NULL)
		return NMERR_BAD_PARM;

	/* Keep reading until buffer is full */
	while (bytes_left) {
		bytes_read = nm_tcp_read(conn, &buff[total_bytes], bytes_left);
		if (bytes_read > 0) {
			bytes_left -= bytes_read;
			total_bytes += bytes_read;
		} else {
			if (errno == EAGAIN) {
				if (--retry == 0) {
					rc = NMERR_TCP_READ;
					break;
				}
#ifdef _WIN32
				Sleep(1);
#else
				usleep(1000);
#endif
			} else {
				rc = NMERR_TCP_READ;
				break;
			}
		}
	}
	return rc;
}

NMERR_T
nm_read_uint32(NMConn *conn, guint32 *val)
{
	NMERR_T rc = NM_OK;

	rc = nm_read_all(conn, (char *)val, sizeof(*val));
	if (rc == NM_OK) {
		*val = GUINT32_FROM_LE(*val);
	}

	return rc;
}

NMERR_T
nm_read_uint16(NMConn *conn, guint16 *val)
{
	NMERR_T rc = NM_OK;

	rc = nm_read_all(conn, (char *)val, sizeof(*val));
	if (rc == NM_OK) {
		*val = GUINT16_FROM_LE(*val);
	}

	return rc;
}

NMERR_T
nm_write_fields(NMConn * conn, NMField * fields)
{
	NMERR_T rc = NM_OK;
	NMField *field;
	char *value = NULL;
	char *method = NULL;
	char buffer[4096];
	int ret;
	int bytes_to_send;
	int val = 0;

	if (conn == NULL || fields == NULL) {
		return NMERR_BAD_PARM;
	}

	/* Format each field as valid "post" data and write it out */
	for (field = fields; (rc == NM_OK) && (field->tag); field++) {

		/* We don't currently handle binary types */
		if (field->method == NMFIELD_METHOD_IGNORE ||
			field->type == NMFIELD_TYPE_BINARY) {
			continue;
		}

		/* Write the field tag */
		bytes_to_send = g_snprintf(buffer, sizeof(buffer), "&tag=%s", field->tag);
		ret = nm_tcp_write(conn, buffer, bytes_to_send);
		if (ret < 0) {
			rc = NMERR_TCP_WRITE;
		}

		/* Write the field method */
		if (rc == NM_OK) {
			method = encode_method(field->method);
			bytes_to_send = g_snprintf(buffer, sizeof(buffer), "&cmd=%s", method);
			ret = nm_tcp_write(conn, buffer, bytes_to_send);
			if (ret < 0) {
				rc = NMERR_TCP_WRITE;
			}
		}

		/* Write the field value */
		if (rc == NM_OK) {
			switch (field->type) {
				case NMFIELD_TYPE_UTF8:
				case NMFIELD_TYPE_DN:

					value = url_escape_string((char *) field->ptr_value);
					bytes_to_send = g_snprintf(buffer, sizeof(buffer),
											   "&val=%s", value);
					if (bytes_to_send > (int)sizeof(buffer)) {
						ret = nm_tcp_write(conn, buffer, sizeof(buffer));
					} else {
						ret = nm_tcp_write(conn, buffer, bytes_to_send);
					}

					if (ret < 0) {
						rc = NMERR_TCP_WRITE;
					}

					g_free(value);

					break;

				case NMFIELD_TYPE_ARRAY:
				case NMFIELD_TYPE_MV:

					val = nm_count_fields((NMField *) field->ptr_value);
					bytes_to_send = g_snprintf(buffer, sizeof(buffer),
											   "&val=%u", val);
					ret = nm_tcp_write(conn, buffer, bytes_to_send);
					if (ret < 0) {
						rc = NMERR_TCP_WRITE;
					}

					break;

				default:

					bytes_to_send = g_snprintf(buffer, sizeof(buffer),
											   "&val=%u", field->value);
					ret = nm_tcp_write(conn, buffer, bytes_to_send);
					if (ret < 0) {
						rc = NMERR_TCP_WRITE;
					}

					break;
			}
		}

		/* Write the field type */
		if (rc == NM_OK) {
			bytes_to_send = g_snprintf(buffer, sizeof(buffer),
									   "&type=%u", field->type);
			ret = nm_tcp_write(conn, buffer, bytes_to_send);
			if (ret < 0) {
				rc = NMERR_TCP_WRITE;
			}
		}

		/* If the field is a sub array then post its fields */
		if (rc == NM_OK && val > 0) {
			if (field->type == NMFIELD_TYPE_ARRAY ||
				field->type == NMFIELD_TYPE_MV) {

				rc = nm_write_fields(conn, (NMField *) field->ptr_value);

			}
		}
	}

	return rc;
}

NMERR_T
nm_send_request(NMConn *conn, char *cmd, NMField *fields,
				nm_response_cb cb, gpointer data, NMRequest **request)
{
	NMERR_T rc = NM_OK;
	char buffer[512];
	int bytes_to_send;
	int ret;
	NMField *request_fields = NULL;
	char *str = NULL;

	if (conn == NULL || cmd == NULL)
		return NMERR_BAD_PARM;

	/* Write the post */
	bytes_to_send = g_snprintf(buffer, sizeof(buffer),
							   "POST /%s HTTP/1.0\r\n", cmd);
	ret = nm_tcp_write(conn, buffer, bytes_to_send);
	if (ret < 0) {
		rc = NMERR_TCP_WRITE;
	}

	/* Write headers */
	if (rc == NM_OK) {
		if (strcmp("login", cmd) == 0) {
			bytes_to_send = g_snprintf(buffer, sizeof(buffer),
									   "Host: %s:%d\r\n\r\n", conn->addr, conn->port);
			ret = nm_tcp_write(conn, buffer, bytes_to_send);
			if (ret < 0) {
				rc = NMERR_TCP_WRITE;
			}
		} else {
			bytes_to_send = g_snprintf(buffer, sizeof(buffer), "\r\n");
			ret = nm_tcp_write(conn, buffer, bytes_to_send);
			if (ret < 0) {
				rc = NMERR_TCP_WRITE;
			}
		}
	}

	/* Add the transaction id to the request fields */
	if (rc == NM_OK) {
		if (fields)
			request_fields = nm_copy_field_array(fields);

		str = g_strdup_printf("%d", ++(conn->trans_id));
		request_fields = nm_field_add_pointer(request_fields, NM_A_SZ_TRANSACTION_ID, 0,
											  NMFIELD_METHOD_VALID, 0,
											  str, NMFIELD_TYPE_UTF8);
	}

	/* Send the request to the server */
	if (rc == NM_OK) {
		rc = nm_write_fields(conn, request_fields);
	}

	/* Write the CRLF to terminate the data */
	if (rc == NM_OK) {
		ret = nm_tcp_write(conn, "\r\n", strlen("\r\n"));
		if (ret < 0) {
			rc = NMERR_TCP_WRITE;
		}
	}

	/* Create a request struct, add it to our queue, and return it */
	if (rc == NM_OK) {
		NMRequest *new_request = nm_create_request(cmd, conn->trans_id,
												   time(0), cb, NULL, data);
		nm_conn_add_request_item(conn, new_request);

		/* Set the out param if it was sent in, otherwise release the request */
		if (request)
			*request = new_request;
		else
			nm_release_request(new_request);
	}

	if (request_fields != NULL)
		nm_free_fields(&request_fields);

	return rc;
}

NMERR_T
nm_read_header(NMConn * conn)
{
	NMERR_T rc = NM_OK;
	char buffer[512];
	char *ptr = NULL;
	int i;
	char rtn_buf[8];
	int rtn_code = 0;

	if (conn == NULL)
		return NMERR_BAD_PARM;

	*buffer = '\0';
	rc = read_line(conn, buffer, sizeof(buffer));
	if (rc == NM_OK) {

		/* Find the return code */
		ptr = strchr(buffer, ' ');
		if (ptr != NULL) {
			ptr++;

			i = 0;
			while (isdigit(*ptr) && (i < 3)) {
				rtn_buf[i] = *ptr;
				i++;
				ptr++;
			}
			rtn_buf[i] = '\0';

			if (i > 0)
				rtn_code = atoi(rtn_buf);
		}
	}

	/* Finish reading header, in the future we might want to do more processing here */
	/* TODO: handle more general redirects in the future */
	while ((rc == NM_OK) && (strcmp(buffer, "\r\n") != 0)) {
		rc = read_line(conn, buffer, sizeof(buffer));
	}

	if (rc == NM_OK && rtn_code == 301)
		rc = NMERR_SERVER_REDIRECT;

	return rc;
}

NMERR_T
nm_read_fields(NMConn * conn, int count, NMField ** fields)
{
	NMERR_T rc = NM_OK;
	guint8 type;
	guint8 method;
	guint32 val;
	char tag[64];
	NMField *sub_fields = NULL;
	char *str = NULL;

	if (conn == NULL || fields == NULL)
		return NMERR_BAD_PARM;

	do {
		if (count > 0) {
			count--;
		}

		/* Read the field type, method, and tag */
		rc = nm_read_all(conn, (char *)&type, sizeof(type));
		if (rc != NM_OK || type == 0)
			break;

		rc = nm_read_all(conn, (char *)&method, sizeof(method));
		if (rc != NM_OK)
			break;

		rc = nm_read_uint32(conn, &val);
		if (rc != NM_OK)
			break;

		if (val > sizeof(tag)) {
			rc = NMERR_PROTOCOL;
			break;
		}

		rc = nm_read_all(conn, tag, val);
		if (rc != NM_OK)
			break;

		if (type == NMFIELD_TYPE_MV || type == NMFIELD_TYPE_ARRAY) {

			/* Read the subarray (first read the number of items in the array) */
			rc = nm_read_uint32(conn, &val);
			if (rc != NM_OK)
				break;

			if (val > 0) {
				rc = nm_read_fields(conn, val, &sub_fields);
				if (rc != NM_OK)
					break;
			}

			*fields = nm_field_add_pointer(*fields, tag, 0, method,
									   0, sub_fields, type);

			sub_fields = NULL;

		} else if (type == NMFIELD_TYPE_UTF8 || type == NMFIELD_TYPE_DN) {

			/* Read the string (first read the length) */
			rc = nm_read_uint32(conn, &val);
			if (rc != NM_OK)
				break;

			if (val >= NMFIELD_MAX_STR_LENGTH) {
				rc = NMERR_PROTOCOL;
				break;
			}

			if (val > 0) {
				str = g_new0(char, val + 1);

				rc = nm_read_all(conn, str, val);
				if (rc != NM_OK)
					break;

				*fields = nm_field_add_pointer(*fields, tag, 0, method,
											   0, str, type);
				str = NULL;
			}

		} else {

			/* Read the numerical value */
			rc = nm_read_uint32(conn, &val);
			if (rc != NM_OK)
				break;

			*fields = nm_field_add_number(*fields, tag, 0, method,
										  0, val, type);
		}

	} while ((type != 0) && (count != 0));


	if (str != NULL) {
		g_free(str);
	}

	if (sub_fields != NULL) {
		nm_free_fields(&sub_fields);
	}

	return rc;
}

void
nm_conn_add_request_item(NMConn * conn, NMRequest * request)
{
	if (conn == NULL || request == NULL)
		return;

	nm_request_add_ref(request);
	conn->requests = g_slist_append(conn->requests, request);
}

void
nm_conn_remove_request_item(NMConn * conn, NMRequest * request)
{
	if (conn == NULL || request == NULL)
		return;

	conn->requests = g_slist_remove(conn->requests, request);
	nm_release_request(request);
}

NMRequest *
nm_conn_find_request(NMConn * conn, int trans_id)
{
	NMRequest *req = NULL;
	GSList *itr = NULL;

	if (conn == NULL)
		return NULL;

	itr = conn->requests;
	while (itr) {
		req = (NMRequest *) itr->data;
		if (req != NULL && nm_request_get_trans_id(req) == trans_id) {
			return req;
		}
		itr = g_slist_next(itr);
	}
	return NULL;
}

const char *
nm_conn_get_addr(NMConn * conn)
{
	if (conn == NULL)
		return NULL;
	else
		return conn->addr;
}

int
nm_conn_get_port(NMConn * conn)
{
	if (conn == NULL)
		return -1;
	else
		return conn->port;
}