libpurple/protocols/novell/nmconn.c

Wed, 25 May 2022 23:52:45 -0500

author
Elliott Sales de Andrade <quantum.analyst@gmail.com>
date
Wed, 25 May 2022 23:52:45 -0500
changeset 41408
5323c0b51ddc
parent 40669
48dfed6f4f1f
permissions
-rw-r--r--

Remove prpl-gtalk from XMPP console

It no longer exists, and complicates the code a bit.

/*
 * 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>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <string.h>
#include <ctype.h>

#include <purple.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))

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)
{
	g_return_if_fail(conn != NULL);

	g_slist_free_full(conn->requests, (GDestroyNotify)nm_release_request);
	conn->requests = NULL;

	if (conn->input) {
		purple_gio_graceful_close(conn->stream, G_INPUT_STREAM(conn->input),
		                          conn->output);
	}
	g_clear_object(&conn->input);
	g_clear_object(&conn->output);
	g_clear_object(&conn->stream);

	g_clear_pointer(&conn->addr, g_free);
	g_free(conn);
}

NMERR_T
nm_write_fields(NMUser *user, NMField *fields)
{
	NMConn *conn;
	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;

	g_return_val_if_fail(user != NULL, NMERR_BAD_PARM);
	g_return_val_if_fail(user->conn != NULL, NMERR_BAD_PARM);
	g_return_val_if_fail(fields != NULL, NMERR_BAD_PARM);

	conn = user->conn;

	/* 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 = g_output_stream_write(conn->output, buffer, bytes_to_send,
		                            user->cancellable, NULL);
		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 = g_output_stream_write(conn->output, buffer, bytes_to_send,
			                            user->cancellable, NULL);
			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 = g_output_stream_write(conn->output, buffer,
						                            sizeof(buffer),
						                            user->cancellable, NULL);
					} else {
						ret = g_output_stream_write(conn->output, buffer,
						                            bytes_to_send,
						                            user->cancellable, NULL);
					}

					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 = g_output_stream_write(conn->output, buffer,
					                            bytes_to_send,
					                            user->cancellable, NULL);
					if (ret < 0) {
						rc = NMERR_TCP_WRITE;
					}

					break;

				default:

					bytes_to_send = g_snprintf(buffer, sizeof(buffer),
											   "&val=%u", field->value);
					ret = g_output_stream_write(conn->output, buffer,
					                            bytes_to_send,
					                            user->cancellable, NULL);
					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 = g_output_stream_write(conn->output, buffer, bytes_to_send,
			                            user->cancellable, NULL);
			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(user, (NMField *)field->ptr_value);
			}
		}
	}

	return rc;
}

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

	g_return_val_if_fail(user != NULL, NMERR_BAD_PARM);
	g_return_val_if_fail(user->conn != NULL, NMERR_BAD_PARM);
	g_return_val_if_fail(cmd != NULL, NMERR_BAD_PARM);

	conn = user->conn;

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

	/* Write headers */
	if (rc == NM_OK) {
		if (purple_strequal("login", cmd)) {
			bytes_to_send = g_snprintf(buffer, sizeof(buffer),
									   "Host: %s:%d\r\n\r\n", conn->addr, conn->port);
			ret = g_output_stream_write(conn->output, buffer, bytes_to_send,
			                            user->cancellable, NULL);
			if (ret < 0) {
				rc = NMERR_TCP_WRITE;
			}
		} else {
			bytes_to_send = g_snprintf(buffer, sizeof(buffer), "\r\n");
			ret = g_output_stream_write(conn->output, buffer, bytes_to_send,
			                            user->cancellable, NULL);
			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(user, request_fields);
	}

	/* Write the CRLF to terminate the data */
	if (rc == NM_OK) {
		ret = g_output_stream_write(conn->output, "\r\n", strlen("\r\n"),
		                            user->cancellable, NULL);
		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, 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(NMUser *user)
{
	NMConn *conn;
	NMERR_T rc = NM_OK;
	gchar *buffer;
	char *ptr = NULL;
	int i;
	char rtn_buf[8];
	int rtn_code = 0;
	GError *error = NULL;

	g_return_val_if_fail(user != NULL, NMERR_BAD_PARM);
	g_return_val_if_fail(user->conn != NULL, NMERR_BAD_PARM);

	conn = user->conn;

	buffer = g_data_input_stream_read_line(conn->input, NULL, user->cancellable,
	                                       &error);
	if (error == NULL) {
		/* 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 ((error == NULL) && !purple_strequal(buffer, "\r")) {
		g_free(buffer);
		buffer = g_data_input_stream_read_line(conn->input, NULL,
		                                       user->cancellable, &error);
	}
	g_free(buffer);

	if (error != NULL) {
		if (error->code != G_IO_ERROR_WOULD_BLOCK &&
		    error->code != G_IO_ERROR_CANCELLED) {
			rc = NMERR_TCP_READ;
		}
		g_error_free(error);
	}

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

	return rc;
}

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

	g_return_val_if_fail(user != NULL, NMERR_BAD_PARM);
	g_return_val_if_fail(user->conn != NULL, NMERR_BAD_PARM);
	g_return_val_if_fail(fields != NULL, NMERR_BAD_PARM);

	conn = user->conn;

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

		/* Read the field type, method, and tag */
		type = g_data_input_stream_read_byte(conn->input, user->cancellable,
		                                     &error);
		if (error != NULL || type == 0) {
			break;
		}

		method = g_data_input_stream_read_byte(conn->input, user->cancellable,
		                                       &error);
		if (error != NULL) {
			break;
		}

		val = g_data_input_stream_read_uint32(conn->input, user->cancellable,
		                                      &error);
		if (error != NULL) {
			break;
		}

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

		g_input_stream_read_all(G_INPUT_STREAM(conn->input), tag, val, NULL,
		                        user->cancellable, &error);
		if (error != NULL) {
			break;
		}

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

			/* Read the subarray (first read the number of items in the array) */
			val = g_data_input_stream_read_uint32(conn->input,
			                                      user->cancellable, &error);
			if (error != NULL) {
				break;
			}

			if (val > 0) {
				rc = nm_read_fields(user, 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) */
			val = g_data_input_stream_read_uint32(conn->input,
			                                      user->cancellable, &error);
			if (error != NULL) {
				break;
			}

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

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

				g_input_stream_read_all(G_INPUT_STREAM(conn->input), str, val,
				                        NULL, user->cancellable, &error);
				if (error != NULL) {
					break;
				}

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

		} else {

			/* Read the numerical value */
			val = g_data_input_stream_read_uint32(conn->input,
			                                      user->cancellable, &error);
			if (error != NULL) {
				break;
			}

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

	} while (count != 0);

	g_free(str);

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

	if (error != NULL) {
		if (error->code != G_IO_ERROR_WOULD_BLOCK && error->code != G_IO_ERROR_CANCELLED) {
			rc = NMERR_TCP_READ;
		}
		g_error_free(error);
	}

	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;
}

mercurial