pidgin/pidgin

1896a80ff8e3
Route GLib debug logging directly to the Finch debug window

Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.

Testing Done:
Open the Debug window an see some `GLib-*` outputs.

Reviewed at https://reviews.imfreedom.org/r/1057/
/*
* Purple - Internet Messaging Library
* Copyright (C) Pidgin Developers <devel@pidgin.im>
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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, see <https://www.gnu.org/licenses/>.
*/
#include <glib/gi18n-lib.h>
#include "zephyr_tzc.h"
#define MAXCHILDREN 20
typedef gssize (*PollableInputStreamReadFunc)(GPollableInputStream *stream, void *bufcur, GError **error);
static gboolean tzc_write(zephyr_account *zephyr, const gchar *format, ...) G_GNUC_PRINTF(2, 3);
static gchar *
tzc_read(zephyr_account *zephyr, PollableInputStreamReadFunc read_func)
{
GPollableInputStream *stream = G_POLLABLE_INPUT_STREAM(zephyr->tzc_stdout);
gsize bufsize = 2048;
gchar *buf = g_new(gchar, bufsize);
gchar *bufcur = buf;
gboolean selected = FALSE;
while (TRUE) {
GError *error = NULL;
if (read_func(stream, bufcur, &error) < 0) {
if (error->code == G_IO_ERROR_WOULD_BLOCK ||
error->code == G_IO_ERROR_TIMED_OUT) {
g_error_free(error);
break;
}
purple_debug_error("zephyr", "couldn't read: %s", error->message);
purple_connection_error(purple_account_get_connection(zephyr->account), PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "couldn't read");
g_error_free(error);
g_free(buf);
return NULL;
}
selected = TRUE;
bufcur++;
if ((bufcur - buf) > (bufsize - 1)) {
if ((buf = g_realloc(buf, bufsize * 2)) == NULL) {
purple_debug_error("zephyr","Ran out of memory\n");
exit(-1);
} else {
bufcur = buf + bufsize;
bufsize *= 2;
}
}
}
*bufcur = '\0';
if (!selected) {
g_free(buf);
buf = NULL;
}
return buf;
}
static gboolean
tzc_write(zephyr_account *zephyr, const gchar *format, ...)
{
va_list args;
gchar *message;
GError *error = NULL;
gboolean success;
va_start(args, format);
message = g_strdup_vprintf(format, args);
va_end(args);
success = g_output_stream_write_all(zephyr->tzc_stdin, message, strlen(message),
NULL, NULL, &error);
if (!success) {
purple_debug_error("zephyr", "Unable to write a message: %s", error->message);
g_error_free(error);
}
g_free(message);
return success;
}
/* Munge the outgoing zephyr so that any quotes or backslashes are
escaped and do not confuse tzc: */
static char *
tzc_escape_msg(const char *message)
{
gsize msglen;
char *newmsg;
if (!message || !*message) {
return g_strdup("");
}
msglen = strlen(message);
newmsg = g_new0(char, msglen*2 + 1);
for (gsize pos = 0, pos2 = 0; pos < msglen; pos++, pos2++) {
if (message[pos] == '\\' || message[pos] == '"') {
newmsg[pos2] = '\\';
pos2++;
}
newmsg[pos2] = message[pos];
}
return newmsg;
}
static char *
tzc_deescape_str(const char *message)
{
gsize msglen;
char *newmsg;
if (!message || !*message) {
return g_strdup("");
}
msglen = strlen(message);
newmsg = g_new0(char, msglen + 1);
for (gsize pos = 0, pos2 = 0; pos < msglen; pos++, pos2++) {
if (message[pos] == '\\') {
pos++;
}
newmsg[pos2] = message[pos];
}
return newmsg;
}
static GSubprocess *
get_tzc_process(const zephyr_account *zephyr)
{
GSubprocess *tzc_process = NULL;
const gchar *tzc_cmd;
gchar **tzc_cmd_array = NULL;
GError *error = NULL;
gboolean found_ps = FALSE;
gint i;
/* tzc_command should really be of the form
path/to/tzc -e %s
or
ssh username@hostname pathtotzc -e %s
-- this should not require a password, and ideally should be
kerberized ssh --
or
fsh username@hostname pathtotzc -e %s
*/
tzc_cmd = purple_account_get_string(zephyr->account, "tzc_command", "/usr/bin/tzc -e %s");
if (!g_shell_parse_argv(tzc_cmd, NULL, &tzc_cmd_array, &error)) {
purple_debug_error("zephyr", "Unable to parse tzc_command: %s", error->message);
purple_connection_error(
purple_account_get_connection(zephyr->account),
PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
"invalid tzc_command setting");
g_error_free(error);
return NULL;
}
for (i = 0; tzc_cmd_array[i] != NULL; i++) {
if (purple_strequal(tzc_cmd_array[i], "%s")) {
g_free(tzc_cmd_array[i]);
tzc_cmd_array[i] = g_strdup(zephyr->exposure);
found_ps = TRUE;
}
}
if (!found_ps) {
purple_debug_error("zephyr", "tzc exited early");
purple_connection_error(
purple_account_get_connection(zephyr->account),
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
"invalid output by tzc (or bad parsing code)");
g_strfreev(tzc_cmd_array);
return NULL;
}
tzc_process = g_subprocess_newv(
(const gchar *const *)tzc_cmd_array,
G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE,
&error);
if (tzc_process == NULL) {
purple_debug_error("zephyr", "tzc exited early: %s", error->message);
purple_connection_error(
purple_account_get_connection(zephyr->account),
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
"invalid output by tzc (or bad parsing code)");
g_error_free(error);
}
g_strfreev(tzc_cmd_array);
return tzc_process;
}
static gssize
pollable_input_stream_read(GPollableInputStream *stream, void *bufcur, GError **error)
{
return g_pollable_input_stream_read_nonblocking(stream, bufcur, 1, NULL, error);
}
static gssize
pollable_input_stream_read_with_timeout(GPollableInputStream *stream,
void *bufcur, GError **error)
{
const gint64 timeout = 10 * G_USEC_PER_SEC;
gint64 now = g_get_monotonic_time();
while (g_get_monotonic_time() < now + timeout) {
GError *local_error = NULL;
gssize ret = g_pollable_input_stream_read_nonblocking(
stream, bufcur, 1, NULL, &local_error);
if (ret == 1) {
return ret;
}
if (local_error->code != G_IO_ERROR_WOULD_BLOCK) {
g_propagate_error(error, local_error);
return ret;
}
/* Keep on waiting if this is a blocking error. */
g_clear_error(&local_error);
}
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
"tzc did not respond in time");
return -1;
}
static gint
get_paren_level(gint paren_level, gchar ch)
{
switch (ch) {
case '(': return paren_level + 1;
case ')': return paren_level - 1;
default: return paren_level;
}
}
static void
parse_tzc_login_data(zephyr_account *zephyr, const gchar *buf, gint buflen)
{
gchar *str = g_strndup(buf, buflen);
purple_debug_info("zephyr", "tempstr parsed");
/* str should now be a string containing all characters from
* buf after the first ( to the one before the last paren ).
* We should have the following possible lisp strings but we don't care
* (tzcspew . start) (version . "something") (pid . number)
* We care about 'zephyrid . "username@REALM.NAME"' and
* 'exposure . "SOMETHING"' */
if (!g_ascii_strncasecmp(str, "zephyrid", 8)) {
gchar **strv;
gchar *username;
const char *at;
purple_debug_info("zephyr", "zephyrid found");
strv = g_strsplit(str + 8, "\"", -1);
username = strv[1] ? strv[1] : "";
zephyr->username = g_strdup(username);
at = strchr(username, '@');
if (at != NULL) {
zephyr->realm = g_strdup(at + 1);
} else {
zephyr->realm = get_zephyr_realm(zephyr->account, "local-realm");
}
g_strfreev(strv);
} else {
purple_debug_info("zephyr", "something that's not zephyr id found %s", str);
}
/* We don't care about anything else yet */
g_free(str);
}
static gchar *
tree_child_contents(GNode *tree, int index)
{
GNode *child = g_node_nth_child(tree, index);
return child ? child->data : "";
}
static GNode *
find_node(GNode *ptree, gchar *key)
{
guint num_children;
gchar* tc;
if (!ptree || ! key)
return NULL;
num_children = g_node_n_children(ptree);
tc = tree_child_contents(ptree, 0);
/* g_strcasecmp() is deprecated. What is the encoding here??? */
if (num_children > 0 && tc && !g_ascii_strcasecmp(tc, key)) {
return ptree;
} else {
GNode *result = NULL;
guint i;
for (i = 0; i < num_children; i++) {
result = find_node(g_node_nth_child(ptree, i), key);
if(result != NULL) {
break;
}
}
return result;
}
}
static GNode *
parse_buffer(const gchar *source, gboolean do_parse)
{
GNode *ptree = g_node_new(NULL);
if (do_parse) {
unsigned int p = 0;
while(p < strlen(source)) {
unsigned int end;
gchar *newstr;
/* Eat white space: */
if(g_ascii_isspace(source[p]) || source[p] == '\001') {
p++;
continue;
}
/* Skip comments */
if(source[p] == ';') {
while(source[p] != '\n' && p < strlen(source)) {
p++;
}
continue;
}
if(source[p] == '(') {
int nesting = 0;
gboolean in_quote = FALSE;
gboolean escape_next = FALSE;
p++;
end = p;
while(!(source[end] == ')' && nesting == 0 && !in_quote) && end < strlen(source)) {
if(!escape_next) {
if(source[end] == '\\') {
escape_next = TRUE;
}
if(!in_quote) {
nesting = get_paren_level(nesting, source[end]);
}
if(source[end] == '"') {
in_quote = !in_quote;
}
} else {
escape_next = FALSE;
}
end++;
}
do_parse = TRUE;
} else {
gchar end_char;
if(source[p] == '"') {
end_char = '"';
p++;
} else {
end_char = ' ';
}
do_parse = FALSE;
end = p;
while(source[end] != end_char && end < strlen(source)) {
if(source[end] == '\\')
end++;
end++;
}
}
newstr = g_new0(gchar, end+1-p);
strncpy(newstr,source+p,end-p);
if (g_node_n_children(ptree) < MAXCHILDREN) {
/* In case we surpass maxchildren, ignore this */
g_node_append(ptree, parse_buffer(newstr, do_parse));
} else {
purple_debug_error("zephyr","too many children in tzc output. skipping\n");
}
g_free(newstr);
p = end + 1;
}
} else {
/* XXX does this have to be strdup'd */
ptree->data = g_strdup(source);
}
return ptree;
}
gboolean
tzc_login(zephyr_account *zephyr)
{
gchar *buf = NULL;
const gchar *bufend = NULL;
const gchar *ptr;
const gchar *tmp;
gint parenlevel = 0;
zephyr->tzc_proc = get_tzc_process(zephyr);
if (zephyr->tzc_proc == NULL) {
return FALSE;
}
zephyr->tzc_stdin = g_subprocess_get_stdin_pipe(zephyr->tzc_proc);
zephyr->tzc_stdout = g_subprocess_get_stdout_pipe(zephyr->tzc_proc);
purple_debug_info("zephyr", "about to read from tzc");
buf = tzc_read(zephyr, pollable_input_stream_read_with_timeout);
if (buf == NULL) {
return FALSE;
}
bufend = buf + strlen(buf);
ptr = buf;
/* ignore all tzcoutput till we've received the first ( */
while (ptr < bufend && (*ptr != '(')) {
ptr++;
}
if (ptr >= bufend) {
purple_connection_error(
purple_account_get_connection(zephyr->account),
PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
"invalid output by tzc (or bad parsing code)");
g_free(buf);
return FALSE;
}
do {
parenlevel = get_paren_level(parenlevel, *ptr);
purple_debug_info("zephyr", "tzc parenlevel is %d", parenlevel);
switch (parenlevel) {
case 1:
/* Search for next beginning (, or for the ending */
do {
ptr++;
} while ((ptr < bufend) && (*ptr != '(') && (*ptr != ')'));
if (ptr >= bufend) {
purple_debug_error("zephyr", "tzc parsing error");
}
break;
case 2:
/* You are probably at
(foo . bar ) or (foo . "bar") or (foo . chars) or (foo . numbers) or (foo . () )
Parse all the data between the first and last f, and move past )
*/
tmp = ptr;
do {
ptr++;
parenlevel = get_paren_level(parenlevel, *ptr);
} while (parenlevel > 1);
parse_tzc_login_data(zephyr, tmp + 1, ptr - tmp);
ptr++;
break;
default:
purple_debug_info("zephyr", "parenlevel is not 1 or 2");
/* This shouldn't be happening */
break;
}
} while (ptr < bufend && parenlevel != 0);
purple_debug_info("zephyr", "tzc startup done");
g_free(buf);
return TRUE;
}
gint
tzc_check_notify(gpointer data)
{
PurpleConnection *gc = (PurpleConnection *)data;
zephyr_account* zephyr = purple_connection_get_protocol_data(gc);
GNode *newparsetree = NULL;
gchar *buf = tzc_read(zephyr, pollable_input_stream_read);
if (buf != NULL) {
newparsetree = parse_buffer(buf, TRUE);
g_free(buf);
}
if (newparsetree != NULL) {
gchar *spewtype;
if ( (spewtype = tree_child_contents(find_node(newparsetree, "tzcspew"), 2)) ) {
if (!g_ascii_strncasecmp(spewtype,"message",7)) {
ZNotice_t notice;
GNode *msgnode = g_node_nth_child(find_node(newparsetree, "message"), 2);
/*char *zsig = g_strdup(" ");*/ /* purple doesn't care about zsigs */
char *msg = tzc_deescape_str(tree_child_contents(msgnode, 1));
char *buf = g_strdup_printf(" %c%s", '\0', msg);
memset((char *)&notice, 0, sizeof(notice));
notice.z_kind = ACKED;
notice.z_port = 0;
notice.z_opcode = tree_child_contents(find_node(newparsetree, "opcode"), 2);
notice.z_class = tzc_deescape_str(tree_child_contents(find_node(newparsetree, "class"), 2));
notice.z_class_inst = tree_child_contents(find_node(newparsetree, "instance"), 2);
notice.z_recipient = zephyr_normalize_local_realm(zephyr, tree_child_contents(find_node(newparsetree, "recipient"), 2));
notice.z_sender = zephyr_normalize_local_realm(zephyr, tree_child_contents(find_node(newparsetree, "sender"), 2));
notice.z_default_format = "Class $class, Instance $instance:\n" "To: @bold($recipient) at $time $date\n" "From: @bold($1) <$sender>\n\n$2";
notice.z_message_len = 1 + 1 + strlen(msg) + 1;
notice.z_message = buf;
handle_message(gc, &notice);
g_free(msg);
/*g_free(zsig);*/
g_free(buf);
}
else if (!g_ascii_strncasecmp(spewtype,"zlocation",9)) {
/* check_loc or zephyr_zloc respectively */
/* XXX fix */
GNode *locations = g_node_nth_child(g_node_nth_child(find_node(newparsetree, "locations"), 2), 0);
ZLocations_t zloc = {
.host = tree_child_contents(g_node_nth_child(locations, 0), 2),
.time = tree_child_contents(g_node_nth_child(locations, 2), 2),
.tty = NULL
};
handle_locations(gc, tree_child_contents(find_node(newparsetree, "user"), 2),
(zloc.host && *zloc.host && !purple_strequal(zloc.host, " ")) ? 1 : 0,
&zloc);
}
else if (!g_ascii_strncasecmp(spewtype,"subscribed",10)) {
}
else if (!g_ascii_strncasecmp(spewtype,"start",5)) {
}
else if (!g_ascii_strncasecmp(spewtype,"error",5)) {
/* XXX handle */
}
} else {
}
} else {
}
g_node_destroy(newparsetree);
return TRUE;
}
gboolean
tzc_subscribe_to(zephyr_account *zephyr, ZSubscription_t *sub)
{
/* ((tzcfodder . subscribe) ("class" "instance" "recipient")) */
return tzc_write(zephyr, "((tzcfodder . subscribe) (\"%s\" \"%s\" \"%s\"))\n",
sub->zsub_class, sub->zsub_classinst, sub->zsub_recipient);
}
gboolean
tzc_request_locations(zephyr_account *zephyr, gchar *who)
{
return tzc_write(zephyr, "((tzcfodder . zlocate) \"%s\")\n", who);
}
gboolean
tzc_send_message(zephyr_account *zephyr, gchar *zclass, gchar *instance, gchar *recipient,
const gchar *html_buf, const gchar *sig, G_GNUC_UNUSED const gchar *opcode)
{
/* CMU cclub tzc doesn't grok opcodes for now */
char *tzc_sig = tzc_escape_msg(sig);
char *tzc_body = tzc_escape_msg(html_buf);
gboolean result;
result = tzc_write(zephyr, "((tzcfodder . send) (class . \"%s\") (auth . t) (recipients (\"%s\" . \"%s\")) (message . (\"%s\" \"%s\")) ) \n",
zclass, instance, recipient, tzc_sig, tzc_body);
g_free(tzc_sig);
g_free(tzc_body);
return result;
}
void
tzc_set_location(zephyr_account *zephyr, char *exposure)
{
tzc_write(zephyr, "((tzcfodder . set-location) (hostname . \"%s\") (exposure . \"%s\"))\n",
zephyr->ourhost, exposure);
}
void
tzc_get_subs_from_server(G_GNUC_UNUSED zephyr_account *zephyr, PurpleConnection *gc)
{
/* XXX fix */
purple_notify_error(gc, "", "tzc doesn't support this action",
NULL, purple_request_cpar_from_connection(gc));
}
void
tzc_close(zephyr_account *zephyr)
{
#ifdef G_OS_UNIX
GError *error = NULL;
g_subprocess_send_signal(zephyr->tzc_proc, SIGTERM);
if (!g_subprocess_wait(zephyr->tzc_proc, NULL, &error)) {
purple_debug_error("zephyr",
"error while attempting to close tzc: %s",
error->message);
g_error_free(error);
}
#else
g_subprocess_force_exit(zephyr->tzc_proc);
#endif
zephyr->tzc_stdin = NULL;
zephyr->tzc_stdout = NULL;
g_clear_object(&zephyr->tzc_proc);
}