--- a/libpurple/protocols/zephyr/zephyr.c Fri Nov 20 17:03:32 2020 -0600
+++ b/libpurple/protocols/zephyr/zephyr.c Sat Nov 21 22:54:04 2020 -0600
@@ -90,10 +90,10 @@
char ourhost[HOST_NAME_MAX + 1];
char ourhostcanon[HOST_NAME_MAX + 1];
zephyr_connection_type connection_type;
+ GOutputStream *tzc_stdin; + GInputStream *tzc_stdout; @@ -162,16 +162,21 @@
static Code_t zephyr_subscribe_to(zephyr_account* zephyr, char* class, char *instance, char *recipient, char* galaxy) {
/* ((tzcfodder . subscribe) ("class" "instance" "recipient")) */
- gchar *zsubstr = g_strdup_printf("((tzcfodder . subscribe) (\"%s\" \"%s\" \"%s\"))\n",class,instance,recipient);
- size_t len = strlen(zsubstr);
- result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zsubstr,len);
- purple_debug_error("zephyr", "Unable to write a message: %s\n", g_strerror(errno));
+ zsubstr = g_strdup_printf( + "((tzcfodder . subscribe) (\"%s\" \"%s\" \"%s\"))\n", class, + if (!g_output_stream_write_all(zephyr->tzc_stdin, zsubstr, + strlen(zsubstr), NULL, NULL, &error)) { + purple_debug_error("zephyr", "Unable to write a message: %s", @@ -1081,31 +1086,33 @@
static parse_tree *read_from_tzc(zephyr_account* zephyr){
- char *buf = (char *)calloc(bufsize, 1);
+ GPollableInputStream *stream = G_POLLABLE_INPUT_STREAM(zephyr->tzc_stdout); + gchar *buf = g_new(gchar, bufsize); + gboolean selected = FALSE; parse_tree *incoming_msg;
- FD_SET(zephyr->fromtzc[ZEPHYR_FD_READ], &rfds);
- while (select(zephyr->fromtzc[ZEPHYR_FD_READ] + 1, &rfds, NULL, NULL, &tv)) {
- if (read(zephyr->fromtzc[ZEPHYR_FD_READ], bufcur, 1) != 1) {
- purple_debug_error("zephyr", "couldn't read\n");
+ if (g_pollable_input_stream_read_nonblocking(stream, bufcur, 1, NULL, + if (error->code == G_IO_ERROR_WOULD_BLOCK) { + 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");
if ((bufcur - buf) > (bufsize - 1)) {
- if ((buf = realloc(buf, bufsize * 2)) == NULL) {
+ if ((buf = g_realloc(buf, bufsize * 2)) == NULL) { purple_debug_error("zephyr","Ran out of memory\n");
@@ -1119,7 +1126,7 @@
incoming_msg = parse_buffer(buf,TRUE);
@@ -1339,16 +1346,20 @@
ZRequestLocations(chk, &ald, UNACKED, ZAUTH);
- gchar *zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n",chk);
- size_t len = strlen(zlocstr);
- size_t result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,len);
- purple_debug_error("zephyr", "Unable to write a message: %s\n", g_strerror(errno));
+ } else if (use_tzc(zephyr)) { + zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n", chk); + if (!g_output_stream_write_all(zephyr->tzc_stdin, zlocstr, + strlen(zlocstr), NULL, NULL, + purple_debug_error("zephyr", "Unable to write a message: %s", @@ -1554,6 +1565,35 @@
return g_strdup(EXPOSE_REALMVIS);
+pollable_input_stream_read_with_timeout(GPollableInputStream *stream, + void *bufcur, gint64 timeout, + 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 (local_error->code == G_IO_ERROR_WOULD_BLOCK) { + /* Keep on waiting if this is a blocking error. */ + g_clear_error(&local_error); + g_propagate_error(error, local_error); + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, + "tzc did not respond in time"); static void zephyr_login(PurpleAccount * account)
@@ -1592,164 +1632,150 @@
/* XXX z_call_s should actually try to report the com_err determined error */
- /* purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "tzc not supported yet"); */
- if ((pipe(zephyr->totzc) != 0) || (pipe(zephyr->fromtzc) != 0)) {
- purple_debug_error("zephyr", "pipe creation failed. killing\n");
- purple_debug_error("zephyr", "forking failed\n");
+ gboolean found_ps = FALSE; + gchar **tzc_cmd_array = NULL; + GPollableInputStream *stream = NULL; + /* tzc_command should really be of the form + ssh username@hostname pathtotzc -e %s + -- this should not require a password, and ideally should be + fsh username@hostname pathtotzc -e %s + if (!g_shell_parse_argv(purple_account_get_string( + purple_connection_get_account(gc), + "tzc_command", "/usr/bin/tzc -e %s"), + NULL, &tzc_cmd_array, &error)) { + purple_debug_error("zephyr", "Unable to parse tzc_command: %s", + purple_connection_error(gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + "invalid tzc_command setting");
- gboolean found_ps = FALSE;
- gchar ** tzc_cmd_array = g_strsplit(purple_account_get_string(purple_connection_get_account(gc),"tzc_command","/usr/bin/tzc -e %s")," ",0);
- if (dup2(zephyr->fromtzc[1], 1) == -1) {
- if (close(zephyr->fromtzc[1]) == -1) {
+ for (i = 0; tzc_cmd_array[i] != NULL; i++) { + if (g_str_equal(tzc_cmd_array[i], "%s")) { + tzc_cmd_array[i] = g_strdup(zephyr->exposure); - if (dup2(zephyr->totzc[0], 0) == -1) {
- if (close(zephyr->totzc[0]) == -1) {
- /* tzc_command should really be of the form
- ssh username@hostname pathtotzc -e %s
- -- this should not require a password, and ideally should be kerberized ssh --
- fsh username@hostname pathtotzc -e %s
- while(tzc_cmd_array[i] != NULL){
- if (!g_ascii_strncasecmp(tzc_cmd_array[i],"%s",2)) {
- /* fprintf(stderr,"replacing %%s with %s\n",zephyr->exposure); */
- tzc_cmd_array[i] = g_strdup(zephyr->exposure);
- /* fprintf(stderr,"keeping %s\n",tzc_cmd_array[i]); */
- execvp(tzc_cmd_array[0], tzc_cmd_array);
- char *buf = (char *)calloc(bufsize, 1);
- /* wait till we have data to read from ssh */
- FD_SET(zephyr->fromtzc[ZEPHYR_FD_READ], &rfds);
- purple_debug_info("zephyr", "about to read from tzc\n");
- if (waitpid(pid, NULL, WNOHANG) == 0) { /* Only select if tzc is still running */
- purple_debug_info("zephyr", "about to read from tzc\n");
- select_status = select(zephyr->fromtzc[ZEPHYR_FD_READ] + 1, &rfds, NULL, NULL, NULL);
- purple_debug_info("zephyr", "tzc exited early\n");
- FD_SET(zephyr->fromtzc[ZEPHYR_FD_READ], &rfds);
- while (select_status > 0 &&
- select(zephyr->fromtzc[ZEPHYR_FD_READ] + 1, &rfds, NULL, NULL, &tv) > 0) {
- if (read(zephyr->fromtzc[ZEPHYR_FD_READ], bufcur, 1) != 1) {
- purple_debug_error("zephyr", "couldn't read\n");
- purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "couldn't read");
+ purple_debug_error("zephyr", "tzc exited early"); + purple_connection_error( + gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + "invalid output by tzc (or bad parsing code)"); + g_strfreev(tzc_cmd_array); + zephyr->tzc_proc = g_subprocess_newv( + (const gchar *const *)tzc_cmd_array, + G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE, + g_strfreev(tzc_cmd_array); + if (zephyr->tzc_proc == NULL) { + purple_debug_error("zephyr", "tzc exited early: %s", + purple_connection_error( + gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + "invalid output by tzc (or bad parsing code)"); + zephyr->tzc_stdin = g_subprocess_get_stdin_pipe(zephyr->tzc_proc); + zephyr->tzc_stdout = g_subprocess_get_stdout_pipe(zephyr->tzc_proc); + stream = G_POLLABLE_INPUT_STREAM(zephyr->tzc_stdout); + buf = g_new(gchar, bufsize); + purple_debug_info("zephyr", "about to read from tzc"); + if (pollable_input_stream_read_with_timeout( + stream, bufcur, 10 * G_USEC_PER_SEC, &error) < 0) { + if (error->code == G_IO_ERROR_WOULD_BLOCK || + error->code == G_IO_ERROR_TIMED_OUT) {
- if ((bufcur - buf) > (bufsize - 1)) {
- if ((buf = realloc(buf, bufsize * 2)) == NULL) {
- bufcur = buf + bufsize;
- FD_SET(zephyr->fromtzc[ZEPHYR_FD_READ], &rfds);
- /* fprintf(stderr, "read from tzc\n"); */
- /* ignore all tzcoutput till we've received the first (*/
- while (ptr < bufcur && (*ptr !='(')) {
- purple_connection_error(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, "invalid output by tzc (or bad parsing code)");
+ purple_debug_error("zephyr", "couldn't read: %s", + purple_connection_error(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+ if ((bufcur - buf) > (bufsize - 1)) { + if ((buf = g_realloc(buf, bufsize * 2)) == NULL) { + bufcur = buf + bufsize; - else if (*ptr == ')') {
- purple_debug_info("zephyr","tzc parenlevel is %d\n",parenlevel);
+ /* ignore all tzcoutput till we've received the first (*/ + while (ptr < bufcur && (*ptr != '(')) { + purple_connection_error( + gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + "invalid output by tzc (or bad parsing code)"); + } else if (*ptr == ')') { + purple_debug_info("zephyr", "tzc parenlevel is %d", parenlevel); /* Search for next beginning (, or for the ending */
- while((*ptr != '(') && (*ptr != ')') && (ptr <bufcur))
+ while ((*ptr != '(') && (*ptr != ')') && (ptr < bufcur)) {
- purple_debug_error("zephyr","tzc parsing error\n");
+ purple_debug_error("zephyr", "tzc parsing error"); (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 )
- tempstr = g_malloc0(20000);
+ tempstr = g_new0(gchar, 20000); @@ -1757,29 +1783,33 @@
- tempstr[tempstridx++]=*ptr;
- purple_debug_info("zephyr","tempstr parsed\n");
- /* tempstr should now be a tempstridx length string containing all characters
- from that 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"' */
+ purple_debug_info("zephyr", "tempstr parsed"); + /* tempstr should now be a i-length string containing all + * characters from that 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(tempstr,"zephyrid",8)) {
gchar* username = g_malloc0(100);
- purple_debug_info("zephyr","zephyrid found\n");
- while(tempstr[tempstridx] !='"' && tempstridx < 20000)
- while(tempstr[tempstridx] !='"' && tempstridx < 20000)
- username[username_idx++]=tempstr[tempstridx++];
+ purple_debug_info("zephyr", "zephyrid found"); + while (i < 20000 && tempstr[i] != '"') { + while (i < 20000 && tempstr[i] != '"') { + username[username_idx++] = tempstr[i++]; zephyr->username = g_strdup_printf("%s",username);
if ((realm = strchr(username,'@')))
@@ -1790,7 +1820,9 @@
zephyr->realm = g_strdup(realm);
- g_strlcpy(__Zephyr_realm, (const char*)zephyr->realm, REALM_SZ-1);
+ g_strlcpy(__Zephyr_realm, + (const gchar *)zephyr->realm, zephyr->realm = g_strdup("local-realm");
@@ -1798,23 +1830,26 @@
- purple_debug_info("zephyr", "something that's not zephyr id found %s\n",tempstr);
+ "something that's not zephyr id found %s", /* We don't care about anything else yet */
- purple_debug_info("zephyr","parenlevel is not 1 or 2\n");
+ purple_debug_info("zephyr", "parenlevel is not 1 or 2"); /* This shouldn't be happening */
- } /* while (ptr < bufcur) */
- purple_debug_info("zephyr", "tzc startup done\n");
+ } /* while (ptr < bufcur) */ + purple_debug_info("zephyr", "tzc startup done"); else if ( use_zeph02(zephyr)) {
@@ -1968,7 +2003,6 @@
static void zephyr_close(PurpleConnection * gc)
zephyr_account *zephyr = purple_connection_get_protocol_data(gc);
- pid_t tzc_pid = zephyr->tzc_pid;
g_list_free_full(zephyr->pending_zloc_names, g_free);
@@ -1986,33 +2020,34 @@
g_source_remove(zephyr->loctimer);
if (use_zeph02(zephyr)) {
z_call(ZCancelSubscriptions(0));
z_call(ZUnsetLocation());
- if (kill(tzc_pid,SIGTERM) == -1) {
- purple_debug_error("zephyr","An invalid signal was specified when killing tzc\n");
- purple_debug_error("zephyr","Tzc's pid didn't exist while killing tzc\n");
- purple_debug_error("zephyr","purple didn't have permission to kill tzc\n");
- purple_debug_error("zephyr","miscellaneous error while attempting to close tzc\n");
+ 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", + g_subprocess_force_exit(zephyr->tzc_proc); + zephyr->tzc_stdin = NULL; + zephyr->tzc_stdout = NULL; + g_clear_object(&zephyr->tzc_proc); -static int zephyr_send_message(zephyr_account *zephyr,char* zclass, char* instance, char* recipient, const char *im,
- const char *sig, char *opcode) ;
+static gboolean zephyr_send_message(zephyr_account *zephyr, gchar *zclass, + gchar *instance, gchar *recipient, + const gchar *im, const gchar *sig, static const char * zephyr_get_signature(void)
@@ -2130,8 +2165,10 @@
-static int zephyr_send_message(zephyr_account *zephyr,char* zclass, char* instance, char* recipient, const char *im,
- const char *sig, char *opcode)
+zephyr_send_message(zephyr_account *zephyr, gchar *zclass, gchar *instance, + gchar *recipient, const gchar *im, const gchar *sig, @@ -2149,8 +2186,6 @@
html_buf2 = purple_unescape_html(html_buf);
/* CMU cclub tzc doesn't grok opcodes for now */
char* tzc_sig = zephyr_tzc_escape_msg(sig);
@@ -2158,15 +2193,15 @@
zsendstr = g_strdup_printf("((tzcfodder . send) (class . \"%s\") (auth . t) (recipients (\"%s\" . \"%s\")) (message . (\"%s\" \"%s\")) ) \n",
zclass, instance, recipient, tzc_sig, tzc_body);
/* fprintf(stderr,"zsendstr = %s\n",zsendstr); */
- len = strlen(zsendstr);
- result = write(zephyr->totzc[ZEPHYR_FD_WRITE], zsendstr, len);
+ if (!g_output_stream_write_all(zephyr->tzc_stdin, zsendstr, + strlen(zsendstr), NULL, NULL, NULL)) {
@@ -2192,7 +2227,7 @@
purple_debug_info("zephyr","notice sent\n");
@@ -2201,7 +2236,7 @@
char *local_zephyr_normalize(zephyr_account *zephyr,const char *orig)
@@ -2266,22 +2301,23 @@
/* XXX deal with errors somehow */
} else if (use_tzc(zephyr)) {
- char* zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n",normalized_who);
zephyr->pending_zloc_names = g_list_append(zephyr->pending_zloc_names, g_strdup(normalized_who));
- result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,len);
- purple_debug_error("zephyr", "Unable to write a message: %s\n", g_strerror(errno));
+ zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n", + if (!g_output_stream_write_all(zephyr->tzc_stdin, zlocstr, + strlen(zlocstr), NULL, NULL, &error)) { + purple_debug_error("zephyr", "Unable to write a message: %s", static void zephyr_set_status(PurpleAccount *account, PurpleStatus *status) {
PurpleConnection *gc = purple_account_get_connection(account);
zephyr_account *zephyr = purple_connection_get_protocol_data(gc);
PurpleStatusPrimitive primitive = purple_status_type_get_primitive(purple_status_get_status_type(status));
@@ -2297,11 +2333,17 @@
ZSetLocation(zephyr->exposure);
- char *zexpstr = g_strdup_printf("((tzcfodder . set-location) (hostname . \"%s\") (exposure . \"%s\"))\n",zephyr->ourhost,zephyr->exposure);
- result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zexpstr,len);
- purple_debug_error("zephyr", "Unable to write message: %s\n", g_strerror(errno));
+ gchar *zexpstr = g_strdup_printf("((tzcfodder . set-location)" + " (exposure . \"%s\"))\n", + zephyr->ourhost, zephyr->exposure); + if (g_output_stream_write_all(zephyr->tzc_stdin, zexpstr, + strlen(zexpstr), NULL, NULL, + purple_debug_error("zephyr", "Unable to write message: %s", @@ -2311,11 +2353,17 @@
if (use_zeph02(zephyr)) {
ZSetLocation(EXPOSE_OPSTAFF);
- char *zexpstr = g_strdup_printf("((tzcfodder . set-location) (hostname . \"%s\") (exposure . \"%s\"))\n",zephyr->ourhost,EXPOSE_OPSTAFF);
- result = write(zephyr->totzc[ZEPHYR_FD_WRITE],zexpstr,len);
- purple_debug_error("zephyr", "Unable to write message: %s\n", g_strerror(errno));
+ gchar *zexpstr = g_strdup_printf("((tzcfodder . set-location)" + " (exposure . \"%s\"))\n", + zephyr->ourhost, EXPOSE_OPSTAFF); + if (g_output_stream_write_all(zephyr->tzc_stdin, zexpstr, + strlen(zexpstr), NULL, NULL, + purple_debug_error("zephyr", "Unable to write message: %s",