libgaim/protocols/zephyr/zephyr.c

branch
gaim
changeset 20470
77693555855f
parent 12658
4aa7a873628d
parent 15369
9820bdea78a6
child 20471
1966704b3e42
equal deleted inserted replaced
13071:b98e72d4089a 20470:77693555855f
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * gaim
4 *
5 * Copyright (C) 1998-2001, Mark Spencer <markster@marko.net>
6 * Some code borrowed from GtkZephyr, by
7 * Jag/Sean Dilda <agrajag@linuxpower.org>/<smdilda@unity.ncsu.edu>
8 * http://gtkzephyr.linuxpower.org/
9 *
10 * Some code borrowed from kzephyr, by
11 * Chris Colohan <colohan+@cs.cmu.edu>
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 *
27
28 */
29 /* XXX eww */
30 #include "libgaim/internal.h"
31
32 #include "accountopt.h"
33 #include "debug.h"
34 #include "notify.h"
35 #include "prpl.h"
36 #include "server.h"
37 #include "util.h"
38 #include "cmds.h"
39 #include "privacy.h"
40 #include "version.h"
41
42 #include "zephyr.h"
43 #include "internal.h"
44
45 #include <strings.h>
46
47 #define ZEPHYR_FALLBACK_CHARSET "ISO-8859-1"
48
49 /* these are deliberately high, since most people don't send multiple "PING"s */
50 #define ZEPHYR_TYPING_SEND_TIMEOUT 15
51 #define ZEPHYR_TYPING_RECV_TIMEOUT 10
52 #define ZEPHYR_FD_READ 0
53 #define ZEPHYR_FD_WRITE 1
54
55 extern Code_t ZGetLocations(ZLocations_t *, int *);
56 extern Code_t ZSetLocation(char *);
57 extern Code_t ZUnsetLocation();
58 extern Code_t ZGetSubscriptions(ZSubscription_t *, int*);
59 extern char __Zephyr_realm[];
60 typedef struct _zframe zframe;
61 typedef struct _zephyr_triple zephyr_triple;
62 typedef struct _zephyr_account zephyr_account;
63 typedef struct _parse_tree parse_tree;
64
65 typedef enum {
66 GAIM_ZEPHYR_NONE, /* Non-kerberized ZEPH0.2 */
67 GAIM_ZEPHYR_KRB4, /* ZEPH0.2 w/ KRB4 support */
68 GAIM_ZEPHYR_TZC, /* tzc executable proxy */
69 GAIM_ZEPHYR_INTERGALACTIC_KRB4, /* Kerberized ZEPH0.3 */
70 } zephyr_connection_type;
71
72 struct _zephyr_account {
73 GaimAccount* account;
74 char *username;
75 char *realm;
76 char *encoding;
77 char* galaxy; /* not yet useful */
78 char* krbtkfile; /* not yet useful */
79 guint32 nottimer;
80 guint32 loctimer;
81 GList *pending_zloc_names;
82 GSList *subscrips;
83 int last_id;
84 unsigned short port;
85 char ourhost[HOST_NAME_MAX + 1];
86 char ourhostcanon[HOST_NAME_MAX + 1];
87 zephyr_connection_type connection_type;
88 int totzc[2];
89 int fromtzc[2];
90 char *exposure;
91 pid_t tzc_pid;
92 gchar *away;
93 };
94
95 #define MAXCHILDREN 20
96
97 struct _parse_tree {
98 gchar* contents;
99 parse_tree *children[MAXCHILDREN];
100 int num_children;
101 };
102
103 parse_tree null_parse_tree = {
104 "",
105 {NULL},
106 0,
107 };
108
109 #define use_none(zephyr) ((zephyr->connection_type == GAIM_ZEPHYR_NONE)?1:0)
110 #define use_krb4(zephyr) ((zephyr->connection_type == GAIM_ZEPHYR_KRB4)?1:0)
111 #define use_tzc(zephyr) ((zephyr->connection_type == GAIM_ZEPHYR_TZC)?1:0)
112
113 #define use_zeph02(zephyr) ( (zephyr->connection_type == GAIM_ZEPHYR_NONE)?1: ((zephyr->connection_type == GAIM_ZEPHYR_KRB4)?1:0))
114
115 /* struct I need for zephyr_to_html */
116 struct _zframe {
117 /* true for everything but @color, since inside the parens of that one is
118 * the color. */
119 gboolean has_closer;
120 /* @i, @b, etc. */
121 const char *env;
122 /* }=1, ]=2, )=4, >=8 */
123 int closer_mask;
124 /* }, ], ), > */
125 char *closer;
126 /* </i>, </font>, </b>, etc. */
127 const char *closing;
128 /* text including the opening html thingie. */
129 GString *text;
130 /* href for links */
131 gboolean is_href;
132 GString *href;
133 struct _zframe *enclosing;
134 };
135
136 struct _zephyr_triple {
137 char *class;
138 char *instance;
139 char *recipient;
140 char *name;
141 gboolean open;
142 int id;
143 };
144
145 #define z_call(func) if (func != ZERR_NONE)\
146 return;
147 #define z_call_r(func) if (func != ZERR_NONE)\
148 return TRUE;
149
150 #define z_call_s(func, err) if (func != ZERR_NONE) {\
151 gaim_connection_error(gc, err);\
152 return;\
153 }
154
155 #ifdef WIN32
156 extern const char *username;
157 #endif
158
159 static Code_t zephyr_subscribe_to(zephyr_account* zephyr, char* class, char *instance, char *recipient, char* galaxy) {
160
161 if (use_tzc(zephyr)) {
162 /* ((tzcfodder . subscribe) ("class" "instance" "recipient")) */
163 gchar *zsubstr = g_strdup_printf("((tzcfodder . subscribe) (\"%s\" \"%s\" \"%s\"))\n",class,instance,recipient);
164 write(zephyr->totzc[ZEPHYR_FD_WRITE],zsubstr,strlen(zsubstr));
165 g_free(zsubstr);
166 return ZERR_NONE;
167 }
168 else {
169 if (use_zeph02(zephyr)) {
170 ZSubscription_t sub;
171 sub.zsub_class = class;
172 sub.zsub_classinst = instance;
173 sub.zsub_recipient = recipient;
174 return ZSubscribeTo(&sub,1,0);
175 } else {
176 /* This should not happen */
177 return -1;
178 }
179 }
180 return -1;
181 }
182
183 char *local_zephyr_normalize(zephyr_account* zephyr,const char *);
184 static void zephyr_chat_set_topic(GaimConnection * gc, int id, const char *topic);
185 char* zephyr_tzc_deescape_str(const char *message);
186
187 static char *zephyr_strip_local_realm(zephyr_account* zephyr,const char* user){
188 /*
189 Takes in a username of the form username or username@realm
190 and returns:
191 username, if there is no realm, or the realm is the local realm
192 or:
193 username@realm if there is a realm and it is foreign
194 */
195 char *tmp = g_strdup(user);
196 char *at = strchr(tmp,'@');
197 if (at && !g_ascii_strcasecmp(at+1,zephyr->realm)) {
198 /* We're passed in a username of the form user@users-realm */
199 char* tmp2;
200 *at = '\0';
201 tmp2 = g_strdup(tmp);
202 g_free(tmp);
203 return tmp2;
204 }
205 else {
206 /* We're passed in a username of the form user or user@foreign-realm */
207 return tmp;
208 }
209 }
210
211 /* this is so bad, and if Zephyr weren't so fucked up to begin with I
212 * wouldn't do this. but it is so i will. */
213
214 /* just for debugging */
215 static void handle_unknown(ZNotice_t notice)
216 {
217 gaim_debug_error("zephyr","z_packet: %s\n", notice.z_packet);
218 gaim_debug_error("zephyr","z_version: %s\n", notice.z_version);
219 gaim_debug_error("zephyr","z_kind: %d\n", (int)(notice.z_kind));
220 gaim_debug_error("zephyr","z_class: %s\n", notice.z_class);
221 gaim_debug_error("zephyr","z_class_inst: %s\n", notice.z_class_inst);
222 gaim_debug_error("zephyr","z_opcode: %s\n", notice.z_opcode);
223 gaim_debug_error("zephyr","z_sender: %s\n", notice.z_sender);
224 gaim_debug_error("zephyr","z_recipient: %s\n", notice.z_recipient);
225 gaim_debug_error("zephyr","z_message: %s\n", notice.z_message);
226 gaim_debug_error("zephyr","z_message_len: %d\n", notice.z_message_len);
227 }
228
229
230 static zephyr_triple *new_triple(zephyr_account *zephyr,const char *c, const char *i, const char *r)
231 {
232 zephyr_triple *zt;
233
234 zt = g_new0(zephyr_triple, 1);
235 zt->class = g_strdup(c);
236 zt->instance = g_strdup(i);
237 zt->recipient = g_strdup(r);
238 zt->name = g_strdup_printf("%s,%s,%s", c, i?i:"", r?r:"");
239 zt->id = ++(zephyr->last_id);
240 zt->open = FALSE;
241 return zt;
242 }
243
244 static void free_triple(zephyr_triple * zt)
245 {
246 g_free(zt->class);
247 g_free(zt->instance);
248 g_free(zt->recipient);
249 g_free(zt->name);
250 g_free(zt);
251 }
252
253 /* returns true if zt1 is a subset of zt2. This function is used to
254 determine whether a zephyr sent to zt1 should be placed in the chat
255 with triple zt2
256
257 zt1 is a subset of zt2
258 iff. the classnames are identical ignoring case
259 AND. the instance names are identical (ignoring case), or zt2->instance is *.
260 AND. the recipient names are identical
261 */
262
263 static gboolean triple_subset(zephyr_triple * zt1, zephyr_triple * zt2)
264 {
265
266 if (!zt2) {
267 gaim_debug_error("zephyr","zt2 doesn't exist\n");
268 return FALSE;
269 }
270 if (!zt1) {
271 gaim_debug_error("zephyr","zt1 doesn't exist\n");
272 return FALSE;
273 }
274 if (!(zt1->class)) {
275 gaim_debug_error("zephyr","zt1c doesn't exist\n");
276 return FALSE;
277 }
278 if (!(zt1->instance)) {
279 gaim_debug_error("zephyr","zt1i doesn't exist\n");
280 return FALSE;
281 }
282 if (!(zt1->recipient)) {
283 gaim_debug_error("zephyr","zt1r doesn't exist\n");
284 return FALSE;
285 }
286 if (!(zt2->class)) {
287 gaim_debug_error("zephyr","zt2c doesn't exist\n");
288 return FALSE;
289 }
290 if (!(zt2->recipient)) {
291 gaim_debug_error("zephyr","zt2r doesn't exist\n");
292 return FALSE;
293 }
294 if (!(zt2->instance)) {
295 gaim_debug_error("zephyr","zt2i doesn't exist\n");
296 return FALSE;
297 }
298
299 if (g_ascii_strcasecmp(zt2->class, zt1->class)) {
300 return FALSE;
301 }
302 if (g_ascii_strcasecmp(zt2->instance, zt1->instance) && g_ascii_strcasecmp(zt2->instance, "*")) {
303 return FALSE;
304 }
305 if (g_ascii_strcasecmp(zt2->recipient, zt1->recipient)) {
306 return FALSE;
307 }
308 gaim_debug_info("zephyr","<%s,%s,%s> is in <%s,%s,%s>\n",zt1->class,zt1->instance,zt1->recipient,zt2->class,zt2->instance,zt2->recipient);
309 return TRUE;
310 }
311
312 static zephyr_triple *find_sub_by_triple(zephyr_account *zephyr,zephyr_triple * zt)
313 {
314 zephyr_triple *curr_t;
315 GSList *curr = zephyr->subscrips;
316
317 while (curr) {
318 curr_t = curr->data;
319 if (triple_subset(zt, curr_t))
320 return curr_t;
321 curr = curr->next;
322 }
323 return NULL;
324 }
325
326 static zephyr_triple *find_sub_by_id(zephyr_account *zephyr,int id)
327 {
328 zephyr_triple *zt;
329 GSList *curr = zephyr->subscrips;
330
331 while (curr) {
332 zt = curr->data;
333 if (zt->id == id)
334 return zt;
335 curr = curr->next;
336 }
337 return NULL;
338 }
339
340 /*
341 Converts strings to utf-8 if necessary using user specified encoding
342 */
343
344 static gchar *zephyr_recv_convert(GaimConnection *gc,gchar *string, int len)
345 {
346 gchar *utf8;
347 GError *err = NULL;
348 zephyr_account *zephyr = gc->proto_data;
349 if (g_utf8_validate(string, len, NULL)) {
350 return g_strdup(string);
351 } else {
352 utf8 = g_convert(string, len, "UTF-8", zephyr->encoding, NULL, NULL, &err);
353 if (err) {
354 gaim_debug_error("zephyr", "recv conversion error: %s\n", err->message);
355 utf8 = g_strdup(_("(There was an error converting this message. Check the 'Encoding' option in the Account Editor)"));
356 g_error_free(err);
357 }
358
359 return utf8;
360 }
361 }
362
363 /* This parses HTML formatting (put out by one of the gtkimhtml widgets
364 And converts it to zephyr formatting.
365 It currently deals properly with <b>, <br>, <i>, <font face=...>, <font color=...>,
366 It ignores <font back=...>
367 It does
368 <font size = "1 or 2" -> @small
369 3 or 4 @medium()
370 5,6, or 7 @large()
371 <a href is dealt with by outputting "description <link>" or just "description" as appropriate
372 */
373
374 static char *html_to_zephyr(const char *message)
375 {
376 zframe *frames, *new_f;
377 char *ret;
378
379 if (*message == '\0')
380 return g_strdup("");
381
382 frames = g_new(zframe, 1);
383 frames->text = g_string_new("");
384 frames->href = NULL;
385 frames->is_href = FALSE;
386 frames->enclosing = NULL;
387 frames->closing = NULL;
388 frames->env = "";
389 frames->has_closer = FALSE;
390 frames->closer_mask = 15;
391
392 gaim_debug_info("zephyr","html received %s\n",message);
393 while (*message) {
394 if (frames->closing && !g_ascii_strncasecmp(message, frames->closing, strlen(frames->closing))) {
395 zframe *popped;
396 message += strlen(frames->closing);
397 popped = frames;
398 frames = frames->enclosing;
399 if (popped->is_href) {
400 frames->href = popped->text;
401 } else {
402 g_string_append(frames->text, popped->env);
403 if (popped->has_closer) {
404 g_string_append_c(frames->text,
405 (popped->closer_mask & 1) ? '{' :
406 (popped->closer_mask & 2) ? '[' :
407 (popped->closer_mask & 4) ? '(' :
408 '<');
409 }
410 g_string_append(frames->text, popped->text->str);
411 if (popped->href)
412 {
413 int text_len = strlen(popped->text->str), href_len = strlen(popped->href->str);
414 if (!((text_len == href_len && !strncmp(popped->href->str, popped->text->str, text_len)) ||
415 (7 + text_len == href_len && !strncmp(popped->href->str, "http://", 7) &&
416 !strncmp(popped->href->str + 7, popped->text->str, text_len)) ||
417 (7 + text_len == href_len && !strncmp(popped->href->str, "mailto:", 7) &&
418 !strncmp(popped->href->str + 7, popped->text->str, text_len)))) {
419 g_string_append(frames->text, " <");
420 g_string_append(frames->text, popped->href->str);
421 if (popped->closer_mask & ~8) {
422 g_string_append_c(frames->text, '>');
423 popped->closer_mask &= ~8;
424 } else {
425 g_string_append(frames->text, "@{>}");
426 }
427 }
428 g_string_free(popped->href, TRUE);
429 }
430 if (popped->has_closer) {
431 g_string_append_c(frames->text,
432 (popped->closer_mask & 1) ? '}' :
433 (popped->closer_mask & 2) ? ']' :
434 (popped->closer_mask & 4) ? ')' :
435 '>');
436 }
437 if (!popped->has_closer)
438 frames->closer_mask = popped->closer_mask;
439 g_string_free(popped->text, TRUE);
440 }
441 g_free(popped);
442 } else if (*message == '<') {
443 if (!g_ascii_strncasecmp(message + 1, "i>", 2)) {
444 new_f = g_new(zframe, 1);
445 new_f->enclosing = frames;
446 new_f->text = g_string_new("");
447 new_f->href = NULL;
448 new_f->is_href = FALSE;
449 new_f->closing = "</i>";
450 new_f->env = "@i";
451 new_f->has_closer = TRUE;
452 new_f->closer_mask = 15;
453 frames = new_f;
454 message += 3;
455 } else if (!g_ascii_strncasecmp(message + 1, "b>", 2)) {
456 new_f = g_new(zframe, 1);
457 new_f->enclosing = frames;
458 new_f->text = g_string_new("");
459 new_f->href = NULL;
460 new_f->is_href = FALSE;
461 new_f->closing = "</b>";
462 new_f->env = "@b";
463 new_f->has_closer = TRUE;
464 new_f->closer_mask = 15;
465 frames = new_f;
466 message += 3;
467 } else if (!g_ascii_strncasecmp(message + 1, "br>", 3)) {
468 g_string_append_c(frames->text, '\n');
469 message += 4;
470 } else if (!g_ascii_strncasecmp(message + 1, "a href=\"", 8)) {
471 message += 9;
472 new_f = g_new(zframe, 1);
473 new_f->enclosing = frames;
474 new_f->text = g_string_new("");
475 new_f->href = NULL;
476 new_f->is_href = FALSE;
477 new_f->closing = "</a>";
478 new_f->env = "";
479 new_f->has_closer = FALSE;
480 new_f->closer_mask = frames->closer_mask;
481 frames = new_f;
482 new_f = g_new(zframe, 1);
483 new_f->enclosing = frames;
484 new_f->text = g_string_new("");
485 new_f->href = NULL;
486 new_f->is_href = TRUE;
487 new_f->closing = "\">";
488 new_f->has_closer = FALSE;
489 new_f->closer_mask = frames->closer_mask;
490 frames = new_f;
491 } else if (!g_ascii_strncasecmp(message + 1, "font", 4)) {
492 new_f = g_new(zframe, 1);
493 new_f->enclosing = frames;
494 new_f->text = g_string_new("");
495 new_f->href = NULL;
496 new_f->is_href = FALSE;
497 new_f->closing = "</font>";
498 new_f->has_closer = TRUE;
499 new_f->closer_mask = 15;
500 message += 5;
501 while (*message == ' ')
502 message++;
503 if (!g_ascii_strncasecmp(message, "color=\"", 7)) {
504 message += 7;
505 new_f->env = "@";
506 frames = new_f;
507 new_f = g_new(zframe, 1);
508 new_f->enclosing = frames;
509 new_f->env = "@color";
510 new_f->text = g_string_new("");
511 new_f->href = NULL;
512 new_f->is_href = FALSE;
513 new_f->closing = "\">";
514 new_f->has_closer = TRUE;
515 new_f->closer_mask = 15;
516 } else if (!g_ascii_strncasecmp(message, "face=\"", 6)) {
517 message += 6;
518 new_f->env = "@";
519 frames = new_f;
520 new_f = g_new(zframe, 1);
521 new_f->enclosing = frames;
522 new_f->env = "@font";
523 new_f->text = g_string_new("");
524 new_f->href = NULL;
525 new_f->is_href = FALSE;
526 new_f->closing = "\">";
527 new_f->has_closer = TRUE;
528 new_f->closer_mask = 15;
529 } else if (!g_ascii_strncasecmp(message, "size=\"", 6)) {
530 message += 6;
531 if ((*message == '1') || (*message == '2')) {
532 new_f->env = "@small";
533 } else if ((*message == '3')
534 || (*message == '4')) {
535 new_f->env = "@medium";
536 } else if ((*message == '5')
537 || (*message == '6')
538 || (*message == '7')) {
539 new_f->env = "@large";
540 } else {
541 new_f->env = "";
542 new_f->has_closer = FALSE;
543 new_f->closer_mask = frames->closer_mask;
544 }
545 message += 3;
546 } else {
547 /* Drop all unrecognized/misparsed font tags */
548 new_f->env = "";
549 new_f->has_closer = FALSE;
550 new_f->closer_mask = frames->closer_mask;
551 while (g_ascii_strncasecmp(message, "\">", 2) != 0) {
552 message++;
553 }
554 if (*message != '\0')
555 message += 2;
556 }
557 frames = new_f;
558 } else {
559 /* Catch all for all unrecognized/misparsed <foo> tage */
560 g_string_append_c(frames->text, *message++);
561 }
562 } else if (*message == '@') {
563 g_string_append(frames->text, "@@");
564 message++;
565 } else if (*message == '}') {
566 if (frames->closer_mask & ~1) {
567 frames->closer_mask &= ~1;
568 g_string_append_c(frames->text, *message++);
569 } else {
570 g_string_append(frames->text, "@[}]");
571 message++;
572 }
573 } else if (*message == ']') {
574 if (frames->closer_mask & ~2) {
575 frames->closer_mask &= ~2;
576 g_string_append_c(frames->text, *message++);
577 } else {
578 g_string_append(frames->text, "@{]}");
579 message++;
580 }
581 } else if (*message == ')') {
582 if (frames->closer_mask & ~4) {
583 frames->closer_mask &= ~4;
584 g_string_append_c(frames->text, *message++);
585 } else {
586 g_string_append(frames->text, "@{)}");
587 message++;
588 }
589 } else if (!g_ascii_strncasecmp(message, "&gt;", 4)) {
590 if (frames->closer_mask & ~8) {
591 frames->closer_mask &= ~8;
592 g_string_append_c(frames->text, *message++);
593 } else {
594 g_string_append(frames->text, "@{>}");
595 message += 4;
596 }
597 } else {
598 g_string_append_c(frames->text, *message++);
599 }
600 }
601 ret = frames->text->str;
602 g_string_free(frames->text, FALSE);
603 g_free(frames);
604 gaim_debug_info("zephyr","zephyr outputted %s\n",ret);
605 return ret;
606 }
607
608 /* this parses zephyr formatting and converts it to html. For example, if
609 * you pass in "@{@color(blue)@i(hello)}" you should get out
610 * "<font color=blue><i>hello</i></font>". */
611 static char *zephyr_to_html(const char *message)
612 {
613 zframe *frames, *curr;
614 char *ret;
615
616 frames = g_new(zframe, 1);
617 frames->text = g_string_new("");
618 frames->enclosing = NULL;
619 frames->closing = "";
620 frames->has_closer = FALSE;
621 frames->closer = NULL;
622
623 while (*message) {
624 if (*message == '@' && message[1] == '@') {
625 g_string_append(frames->text, "@");
626 message += 2;
627 } else if (*message == '@') {
628 int end;
629 for (end = 1; message[end] && (isalnum(message[end]) || message[end] == '_'); end++);
630 if (message[end] &&
631 (message[end] == '{' || message[end] == '[' || message[end] == '(' ||
632 !g_ascii_strncasecmp(message + end, "&lt;", 4))) {
633 zframe *new_f;
634 char *buf;
635 buf = g_new0(char, end);
636 g_snprintf(buf, end, "%s", message + 1);
637 message += end;
638 new_f = g_new(zframe, 1);
639 new_f->enclosing = frames;
640 new_f->has_closer = TRUE;
641 new_f->closer = (*message == '{' ? "}" :
642 *message == '[' ? "]" :
643 *message == '(' ? ")" :
644 "&gt;");
645 message += (*message == '&' ? 4 : 1);
646 if (!g_ascii_strcasecmp(buf, "italic") || !g_ascii_strcasecmp(buf, "i")) {
647 new_f->text = g_string_new("<i>");
648 new_f->closing = "</i>";
649 } else if (!g_ascii_strcasecmp(buf, "small")) {
650 new_f->text = g_string_new("<font size=\"1\">");
651 new_f->closing = "</font>";
652 } else if (!g_ascii_strcasecmp(buf, "medium")) {
653 new_f->text = g_string_new("<font size=\"3\">");
654 new_f->closing = "</font>";
655 } else if (!g_ascii_strcasecmp(buf, "large")) {
656 new_f->text = g_string_new("<font size=\"7\">");
657 new_f->closing = "</font>";
658 } else if (!g_ascii_strcasecmp(buf, "bold")
659 || !g_ascii_strcasecmp(buf, "b")) {
660 new_f->text = g_string_new("<b>");
661 new_f->closing = "</b>";
662 } else if (!g_ascii_strcasecmp(buf, "font")) {
663 zframe *extra_f;
664 extra_f = g_new(zframe, 1);
665 extra_f->enclosing = frames;
666 new_f->enclosing = extra_f;
667 extra_f->text = g_string_new("");
668 extra_f->has_closer = FALSE;
669 extra_f->closer = frames->closer;
670 extra_f->closing = "</font>";
671 new_f->text = g_string_new("<font face=\"");
672 new_f->closing = "\">";
673 } else if (!g_ascii_strcasecmp(buf, "color")) {
674 zframe *extra_f;
675 extra_f = g_new(zframe, 1);
676 extra_f->enclosing = frames;
677 new_f->enclosing = extra_f;
678 extra_f->text = g_string_new("");
679 extra_f->has_closer = FALSE;
680 extra_f->closer = frames->closer;
681 extra_f->closing = "</font>";
682 new_f->text = g_string_new("<font color=\"");
683 new_f->closing = "\">";
684 } else {
685 new_f->text = g_string_new("");
686 new_f->closing = "";
687 }
688 frames = new_f;
689 } else {
690 /* Not a formatting tag, add the character as normal. */
691 g_string_append_c(frames->text, *message++);
692 }
693 } else if (frames->closer && !g_ascii_strncasecmp(message, frames->closer, strlen(frames->closer))) {
694 zframe *popped;
695 gboolean last_had_closer;
696
697 message += strlen(frames->closer);
698 if (frames && frames->enclosing) {
699 do {
700 popped = frames;
701 frames = frames->enclosing;
702 g_string_append(frames->text, popped->text->str);
703 g_string_append(frames->text, popped->closing);
704 g_string_free(popped->text, TRUE);
705 last_had_closer = popped->has_closer;
706 g_free(popped);
707 } while (frames && frames->enclosing && !last_had_closer);
708 } else {
709 g_string_append_c(frames->text, *message);
710 }
711 } else if (*message == '\n') {
712 g_string_append(frames->text, "<br>");
713 message++;
714 } else {
715 g_string_append_c(frames->text, *message++);
716 }
717 }
718 /* go through all the stuff that they didn't close */
719 while (frames->enclosing) {
720 curr = frames;
721 g_string_append(frames->enclosing->text, frames->text->str);
722 g_string_append(frames->enclosing->text, frames->closing);
723 g_string_free(frames->text, TRUE);
724 frames = frames->enclosing;
725 g_free(curr);
726 }
727 ret = frames->text->str;
728 g_string_free(frames->text, FALSE);
729 g_free(frames);
730 return ret;
731 }
732
733 static gboolean pending_zloc(zephyr_account *zephyr,char *who)
734 {
735 GList *curr;
736
737 for (curr = zephyr->pending_zloc_names; curr != NULL; curr = curr->next) {
738 char* normalized_who = local_zephyr_normalize(zephyr,who);
739 if (!g_ascii_strcasecmp(normalized_who, (char *)curr->data)) {
740 g_free((char *)curr->data);
741 zephyr->pending_zloc_names = g_list_remove(zephyr->pending_zloc_names, curr->data);
742 return TRUE;
743 }
744 }
745 return FALSE;
746 }
747
748 /* Called when the server notifies us a message couldn't get sent */
749
750 static void message_failed(GaimConnection *gc, ZNotice_t notice, struct sockaddr_in from)
751 {
752 if (g_ascii_strcasecmp(notice.z_class, "message")) {
753 gchar* chat_failed = g_strdup_printf(_("Unable to send to chat %s,%s,%s"),notice.z_class,notice.z_class_inst,notice.z_recipient);
754 gaim_notify_error(gc,"",chat_failed,NULL);
755 g_free(chat_failed);
756 } else {
757 gaim_notify_error(gc, notice.z_recipient, _("User is offline"), NULL);
758 }
759 }
760
761 static void handle_message(GaimConnection *gc,ZNotice_t notice)
762 {
763 zephyr_account* zephyr = gc->proto_data;
764
765 if (!g_ascii_strcasecmp(notice.z_class, LOGIN_CLASS)) {
766 /* well, we'll be updating in 20 seconds anyway, might as well ignore this. */
767 } else if (!g_ascii_strcasecmp(notice.z_class, LOCATE_CLASS)) {
768 if (!g_ascii_strcasecmp(notice.z_opcode, LOCATE_LOCATE)) {
769 int nlocs;
770 char *user;
771 GaimBuddy *b;
772 /* XXX add real error reporting */
773 if (ZParseLocations(&notice, NULL, &nlocs, &user) != ZERR_NONE)
774 return;
775
776 if ((b = gaim_find_buddy(gc->account, user)) == NULL) {
777 char* stripped_user = zephyr_strip_local_realm(zephyr,user);
778 b = gaim_find_buddy(gc->account,stripped_user);
779 g_free(stripped_user);
780 }
781 if ((b && pending_zloc(zephyr,b->name)) || pending_zloc(zephyr,user)) {
782 ZLocations_t locs;
783 int one = 1;
784 GaimNotifyUserInfo *user_info = gaim_notify_user_info_new();
785 char *tmp;
786
787 gaim_notify_user_info_add_pair(user_info, _("User"), (b ? b->name : user));
788 if (b && b->alias)
789 gaim_notify_user_info_add_pair(user_info, _("Alias"), b->alias);
790
791 if (!nlocs) {
792 gaim_notify_user_info_add_pair(user_info, NULL, _("Hidden or not logged-in"));
793 }
794 for (; nlocs > 0; nlocs--) {
795 /* XXX add real error reporting */
796
797 ZGetLocations(&locs, &one);
798 tmp = g_strdup_printf(_("<br>At %s since %s"), locs.host, locs.time);
799 gaim_notify_user_info_add_pair(user_info, _("Location"), tmp);
800 g_free(tmp);
801 }
802 gaim_notify_userinfo(gc, (b ? b->name : user),
803 user_info, NULL, NULL);
804 gaim_notify_user_info_destroy(user_info);
805 } else {
806 if (nlocs>0)
807 gaim_prpl_got_user_status(gc->account, b ? b->name : user, "available", NULL);
808 else
809 gaim_prpl_got_user_status(gc->account, b ? b->name : user, "offline", NULL);
810 }
811
812 g_free(user);
813 }
814 } else {
815 char *buf, *buf2, *buf3;
816 char *send_inst;
817 GaimConversation *gconv1;
818 GaimConvChat *gcc;
819 char *ptr = (char *) notice.z_message + (strlen(notice.z_message) + 1);
820 int len;
821 char *sendertmp = g_strdup_printf("%s", zephyr->username);
822 int signature_length = strlen(notice.z_message);
823 int message_has_no_body = 0;
824 GaimMessageFlags flags = 0;
825 gchar *tmpescape;
826
827 /* Need to deal with 0 length messages to handle typing notification (OPCODE) ping messages */
828 /* One field zephyrs would have caused gaim to crash */
829 if ( (notice.z_message_len == 0) || (signature_length >= notice.z_message_len - 1)) {
830 message_has_no_body = 1;
831 len = 0;
832 gaim_debug_info("zephyr","message_size %d %d %d\n",len,notice.z_message_len,signature_length);
833 buf3 = g_strdup("");
834
835 } else {
836 len = notice.z_message_len - ( signature_length +1);
837 gaim_debug_info("zephyr","message_size %d %d %d\n",len,notice.z_message_len,signature_length);
838 buf = g_malloc(len + 1);
839 g_snprintf(buf, len + 1, "%s", ptr);
840 g_strchomp(buf);
841 tmpescape = g_markup_escape_text(buf, -1);
842 g_free(buf);
843 buf2 = zephyr_to_html(tmpescape);
844 buf3 = zephyr_recv_convert(gc,buf2, strlen(buf2));
845 g_free(buf2);
846 g_free(tmpescape);
847 }
848
849 if (!g_ascii_strcasecmp(notice.z_class, "MESSAGE") && !g_ascii_strcasecmp(notice.z_class_inst, "PERSONAL")
850 && !g_ascii_strcasecmp(notice.z_recipient,zephyr->username)) {
851 gchar* stripped_sender;
852 if (!g_ascii_strcasecmp(notice.z_message, "Automated reply:"))
853 flags |= GAIM_MESSAGE_AUTO_RESP;
854 stripped_sender = zephyr_strip_local_realm(zephyr,notice.z_sender);
855
856 if (!g_ascii_strcasecmp(notice.z_opcode,"PING"))
857 serv_got_typing(gc,stripped_sender,ZEPHYR_TYPING_RECV_TIMEOUT, GAIM_TYPING);
858 else {
859 /* Based on the values of
860 account->permit_deny,
861 account->permit, account>deny , and
862 the buddylist */
863
864 GSList* l;
865 gboolean in_deny;
866
867 switch (gc->account->perm_deny) {
868 case GAIM_PRIVACY_ALLOW_ALL:
869 in_deny = 0; break;
870 case GAIM_PRIVACY_DENY_ALL:
871 in_deny = 1; break;
872 case GAIM_PRIVACY_ALLOW_USERS: /* See if stripped_sender is in gc->account->permit and allow appropriately */
873 in_deny = 1;
874 for(l=gc->account->permit;l!=NULL;l=l->next) {
875 if (!gaim_utf8_strcasecmp(stripped_sender, gaim_normalize(gc->account, (char *)l->data))) {
876 in_deny=0;
877 break;
878 }
879 }
880 break;
881 case GAIM_PRIVACY_DENY_USERS: /* See if stripped_sender is in gc->account->deny and deny if so */
882 in_deny = 0;
883 for(l=gc->account->deny;l!=NULL;l=l->next) {
884 if (!gaim_utf8_strcasecmp(stripped_sender, gaim_normalize(gc->account, (char *)l->data))) {
885 in_deny=1;
886 break;
887 }
888 }
889 break;
890 case GAIM_PRIVACY_ALLOW_BUDDYLIST:
891 in_deny = 1;
892 if (gaim_find_buddy(gc->account,stripped_sender)!=NULL) {
893 in_deny = 0;
894 }
895 break;
896 default:
897 in_deny=0; break;
898 }
899
900 if (!in_deny) {
901 serv_got_im(gc, stripped_sender, buf3, flags, time(NULL));
902 }
903 }
904
905 g_free(stripped_sender);
906 } else {
907 zephyr_triple *zt1, *zt2;
908 gchar *send_inst_utf8;
909 zephyr_account *zephyr = gc->proto_data;
910 zt1 = new_triple(gc->proto_data,notice.z_class, notice.z_class_inst, notice.z_recipient);
911 zt2 = find_sub_by_triple(gc->proto_data,zt1);
912 if (!zt2) {
913 /* This is a server supplied subscription */
914 zephyr->subscrips = g_slist_append(zephyr->subscrips, new_triple(zephyr,zt1->class,zt1->instance,zt1->recipient));
915 zt2 = find_sub_by_triple(gc->proto_data,zt1);
916 }
917
918 if (!zt2->open) {
919 zt2->open = TRUE;
920 serv_got_joined_chat(gc, zt2->id, zt2->name);
921 zephyr_chat_set_topic(gc,zt2->id,notice.z_class_inst);
922 }
923 g_free(sendertmp); /* fix memory leak? */
924 /* If the person is in the default Realm, then strip the
925 Realm from the sender field */
926 sendertmp = zephyr_strip_local_realm(zephyr,notice.z_sender);
927 send_inst = g_strdup_printf("%s %s",sendertmp,notice.z_class_inst);
928 send_inst_utf8 = zephyr_recv_convert(gc,send_inst, strlen(send_inst));
929 if (!send_inst_utf8) {
930 gaim_debug_error("zephyr","send_inst %s became null\n", send_inst);
931 send_inst_utf8 = "malformed instance";
932 }
933
934 gconv1 = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT,
935 zt2->name, gc->account);
936 gcc = gaim_conversation_get_chat_data(gconv1);
937
938 if (!gaim_conv_chat_find_user(gcc, sendertmp)) {
939 gchar ipaddr[INET_ADDRSTRLEN];
940 inet_ntop(AF_INET, &notice.z_sender_addr.s_addr, ipaddr, sizeof(ipaddr));
941
942 gaim_conv_chat_add_user(gcc, sendertmp, ipaddr, GAIM_CBFLAGS_NONE, TRUE);
943 }
944 g_free(sendertmp);
945 serv_got_chat_in(gc, zt2->id, send_inst_utf8, 0, buf3, time(NULL));
946 g_free(send_inst);
947 g_free(send_inst_utf8);
948
949 free_triple(zt1);
950 }
951 g_free(buf3);
952
953 }
954 }
955
956 static int free_parse_tree(parse_tree* tree) {
957 if (!tree) {
958 return 0;
959 }
960 else {
961 int i;
962 if (tree->children) {
963 for(i=0;i<tree->num_children;i++){
964 if (tree->children[i]) {
965 free_parse_tree(tree->children[i]);
966 g_free(tree->children[i]);
967 }
968 }
969 }
970 if ((tree != &null_parse_tree) && (tree->contents != NULL))
971 g_free(tree->contents);
972
973 }
974 return 0;
975 }
976
977 static parse_tree *tree_child(parse_tree* tree,int index) {
978 if (index < tree->num_children) {
979 return tree->children[index];
980 } else {
981 return &null_parse_tree;
982 }
983 }
984
985 static parse_tree *find_node(parse_tree* ptree,gchar* key)
986 {
987 gchar* tc;
988
989 if (!ptree || ! key)
990 return &null_parse_tree;
991
992 tc = tree_child(ptree,0)->contents;
993
994 if (ptree->num_children > 0 && tc && !strcasecmp(tc, key)) {
995 return ptree;
996 } else {
997 parse_tree *result = &null_parse_tree;
998 int i;
999 for(i = 0; i < ptree->num_children; i++) {
1000 result = find_node(ptree->children[i],key);
1001 if(result != &null_parse_tree) {
1002 break;
1003 }
1004 }
1005 return result;
1006 }
1007 }
1008
1009 static parse_tree *parse_buffer(gchar* source, gboolean do_parse) {
1010
1011 parse_tree *ptree = g_new0(parse_tree,1);
1012 ptree->contents = NULL;
1013 ptree->num_children=0;
1014 if (do_parse) {
1015 unsigned int p = 0;
1016 while(p < strlen(source)) {
1017 unsigned int end;
1018 gchar *newstr;
1019
1020 /* Eat white space: */
1021 if(g_ascii_isspace(source[p]) || source[p] == '\001') {
1022 p++;
1023 continue;
1024 }
1025
1026 /* Skip comments */
1027 if(source[p] == ';') {
1028 while(source[p] != '\n' && p < strlen(source)) {
1029 p++;
1030 }
1031 continue;
1032 }
1033
1034 if(source[p] == '(') {
1035 int nesting = 0;
1036 gboolean in_quote = FALSE;
1037 gboolean escape_next = FALSE;
1038 p++;
1039 end = p;
1040 while(!(source[end] == ')' && nesting == 0 && !in_quote) && end < strlen(source)) {
1041 if(!escape_next) {
1042 if(source[end] == '\\') {
1043 escape_next = TRUE;
1044 }
1045 if(!in_quote) {
1046 if(source[end] == '(') {
1047 nesting++;
1048 }
1049 if(source[end] == ')') {
1050 nesting--;
1051 }
1052 }
1053 if(source[end] == '"') {
1054 in_quote = !in_quote;
1055 }
1056 } else {
1057 escape_next = FALSE;
1058 }
1059 end++;
1060 }
1061 do_parse = TRUE;
1062
1063 } else {
1064 gchar end_char;
1065 if(source[p] == '"') {
1066 end_char = '"';
1067 p++;
1068 } else {
1069 end_char = ' ';
1070 }
1071 do_parse = FALSE;
1072
1073 end = p;
1074 while(source[end] != end_char && end < strlen(source)) {
1075 if(source[end] == '\\')
1076 end++;
1077 end++;
1078 }
1079 }
1080 newstr = g_new0(gchar, end+1-p);
1081 strncpy(newstr,source+p,end-p);
1082 if (ptree->num_children < MAXCHILDREN) {
1083 /* In case we surpass maxchildren, ignore this */
1084 ptree->children[ptree->num_children++] = parse_buffer( newstr, do_parse);
1085 } else {
1086 gaim_debug_error("zephyr","too many children in tzc output. skipping\n");
1087 }
1088 g_free(newstr);
1089 p = end + 1;
1090 }
1091 return ptree;
1092 } else {
1093 /* XXX does this have to be strdup'd */
1094 ptree->contents = g_strdup(source);
1095 return ptree;
1096 }
1097 }
1098
1099 static parse_tree *read_from_tzc(zephyr_account* zephyr){
1100 struct timeval tv;
1101 fd_set rfds;
1102 int bufsize = 2048;
1103 char *buf = (char *)calloc(bufsize, 1);
1104 char *bufcur = buf;
1105 int selected = 0;
1106 parse_tree *incoming_msg;
1107
1108 FD_ZERO(&rfds);
1109 FD_SET(zephyr->fromtzc[ZEPHYR_FD_READ], &rfds);
1110 tv.tv_sec = 0;
1111 tv.tv_usec = 0;
1112 incoming_msg=NULL;
1113
1114 while (select(zephyr->fromtzc[ZEPHYR_FD_READ] + 1, &rfds, NULL, NULL, &tv)) {
1115 selected = 1;
1116 read(zephyr->fromtzc[ZEPHYR_FD_READ], bufcur, 1);
1117 bufcur++;
1118 if ((bufcur - buf) > (bufsize - 1)) {
1119 if ((buf = realloc(buf, bufsize * 2)) == NULL) {
1120 gaim_debug_error("zephyr","Ran out of memory");
1121 exit(-1);
1122 } else {
1123 bufcur = buf + bufsize;
1124 bufsize *= 2;
1125 }
1126 }
1127 }
1128 *bufcur = '\0';
1129
1130 if (selected) {
1131 incoming_msg = parse_buffer(buf,TRUE);
1132 }
1133 free(buf);
1134 return incoming_msg;
1135 }
1136
1137 static gint check_notify_tzc(gpointer data)
1138 {
1139 GaimConnection *gc = (GaimConnection *)data;
1140 zephyr_account* zephyr = gc->proto_data;
1141 parse_tree *newparsetree = read_from_tzc(zephyr);
1142 if (newparsetree != NULL) {
1143 gchar *spewtype;
1144 if ( (spewtype = tree_child(find_node(newparsetree,"tzcspew"),2)->contents) ) {
1145 if (!g_ascii_strncasecmp(spewtype,"message",7)) {
1146 ZNotice_t notice;
1147 parse_tree *msgnode = tree_child(find_node(newparsetree,"message"),2);
1148 parse_tree *bodynode = tree_child(msgnode,1);
1149 /* char *zsig = g_strdup(" "); */ /* gaim doesn't care about zsigs */
1150 char *msg = zephyr_tzc_deescape_str(bodynode->contents);
1151 size_t bufsize = strlen(msg) + 3;
1152 char *buf = g_new0(char,bufsize);
1153 g_snprintf(buf,1+strlen(msg)+2," %c%s",'\0',msg);
1154 bzero((char *)&notice, sizeof(notice));
1155 notice.z_kind = ACKED;
1156 notice.z_port = 0;
1157 notice.z_opcode = tree_child(find_node(newparsetree,"opcode"),2)->contents;
1158 notice.z_class = zephyr_tzc_deescape_str(tree_child(find_node(newparsetree,"class"),2)->contents);
1159 notice.z_class_inst = tree_child(find_node(newparsetree,"instance"),2)->contents;
1160 notice.z_recipient = local_zephyr_normalize(zephyr,tree_child(find_node(newparsetree,"recipient"),2)->contents);
1161 notice.z_sender = local_zephyr_normalize(zephyr,tree_child(find_node(newparsetree,"sender"),2)->contents);
1162 notice.z_default_format = "Class $class, Instance $instance:\n" "To: @bold($recipient) at $time $date\n" "From: @bold($1) <$sender>\n\n$2";
1163 notice.z_message_len = strlen(msg) + 3;
1164 notice.z_message = buf;
1165 handle_message(gc, notice);
1166 g_free(msg);
1167 /* g_free(zsig); */
1168 g_free(buf);
1169 /* free_parse_tree(msgnode);
1170 free_parse_tree(bodynode);
1171 g_free(msg);
1172 g_free(zsig);
1173 g_free(buf);
1174 */
1175 }
1176 else if (!g_ascii_strncasecmp(spewtype,"zlocation",9)) {
1177 /* check_loc or zephyr_zloc respectively */
1178 /* XXX fix */
1179 char *user;
1180 GaimBuddy *b;
1181 int nlocs = 0;
1182 parse_tree *locations;
1183 gchar *locval;
1184 user = tree_child(find_node(newparsetree,"user"),2)->contents;
1185
1186 if ((b = gaim_find_buddy(gc->account, user)) == NULL) {
1187 gchar *stripped_user = zephyr_strip_local_realm(zephyr,user);
1188 b = gaim_find_buddy(gc->account, stripped_user);
1189 g_free(stripped_user);
1190 }
1191 locations = find_node(newparsetree,"locations");
1192 locval = tree_child(tree_child(tree_child(tree_child(locations,2),0),0),2)->contents;
1193
1194 if (!locval || !g_ascii_strcasecmp(locval," ") || (strlen(locval) == 0)) {
1195 nlocs = 0;
1196 } else {
1197 nlocs = 1;
1198 }
1199
1200 if ((b && pending_zloc(zephyr,b->name)) || pending_zloc(zephyr,user) || pending_zloc(zephyr,local_zephyr_normalize(zephyr,user))){
1201 GaimNotifyUserInfo *user_info = gaim_notify_user_info_new();
1202 char *tmp;
1203
1204 gaim_notify_user_info_add_pair(user_info, _("User"), (b ? b->name : user));
1205
1206 if (b && b->alias)
1207 gaim_notify_user_info_add_pair(user_info, _("Alias"), b->alias);
1208
1209 if (!nlocs) {
1210 gaim_notify_user_info_add_pair(user_info, NULL, _("Hidden or not logged-in"));
1211 } else {
1212 tmp = g_strdup_printf(_("<br>At %s since %s"),
1213 tree_child(tree_child(tree_child(tree_child(locations,2),0),0),2)->contents,
1214 tree_child(tree_child(tree_child(tree_child(locations,2),0),2),2)->contents);
1215 gaim_notify_user_info_add_pair(user_info, _("Location"), tmp);
1216 g_free(tmp);
1217 }
1218
1219 gaim_notify_userinfo(gc, b ? b->name : user,
1220 user_info, NULL, NULL);
1221 gaim_notify_user_info_destroy(user_info);
1222 } else {
1223 if (nlocs>0)
1224 gaim_prpl_got_user_status(gc->account, b ? b->name : user, "available", NULL);
1225 else
1226 gaim_prpl_got_user_status(gc->account, b ? b->name : user, "offline", NULL);
1227 }
1228 }
1229 else if (!g_ascii_strncasecmp(spewtype,"subscribed",10)) {
1230 }
1231 else if (!g_ascii_strncasecmp(spewtype,"start",5)) {
1232 }
1233 else if (!g_ascii_strncasecmp(spewtype,"error",5)) {
1234 /* XXX handle */
1235 }
1236 } else {
1237 }
1238 } else {
1239 }
1240
1241 free_parse_tree(newparsetree);
1242 return TRUE;
1243 }
1244
1245 static gint check_notify_zeph02(gpointer data)
1246 {
1247 /* XXX add real error reporting */
1248 GaimConnection *gc = (GaimConnection*) data;
1249 while (ZPending()) {
1250 ZNotice_t notice;
1251 struct sockaddr_in from;
1252 /* XXX add real error reporting */
1253
1254 z_call_r(ZReceiveNotice(&notice, &from));
1255
1256 switch (notice.z_kind) {
1257 case UNSAFE:
1258 case UNACKED:
1259 case ACKED:
1260 handle_message(gc,notice);
1261 break;
1262 case SERVACK:
1263 if (!(g_ascii_strcasecmp(notice.z_message, ZSRVACK_NOTSENT))) {
1264 message_failed(gc,notice, from);
1265 }
1266 break;
1267 case CLIENTACK:
1268 gaim_debug_error("zephyr", "Client ack received\n");
1269 default:
1270 /* we'll just ignore things for now */
1271 handle_unknown(notice);
1272 gaim_debug_error("zephyr", "Unhandled notice.\n");
1273 break;
1274 }
1275 /* XXX add real error reporting */
1276 ZFreeNotice(&notice);
1277 }
1278
1279 return TRUE;
1280 }
1281
1282 #ifdef WIN32
1283
1284 static gint check_loc(gpointer_data)
1285 {
1286 GaimBlistNode *gnode, *cnode, *bnode;
1287 ZLocations_t locations;
1288 int numlocs;
1289 int one = 1;
1290
1291 for (gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
1292 if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
1293 continue;
1294 for (cnode = gnode->child; cnode; cnode = cnode->next) {
1295 if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
1296 continue;
1297 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1298 GaimBuddy *b = (GaimBuddy *) bnode;
1299
1300 if (!GAIM_BLIST_NODE_IS_BUDDY(bnode))
1301 continue;
1302 if (b->account->gc == zgc) {
1303 char *chk;
1304 chk = local_zephyr_normalize(b->name);
1305 ZLocateUser(chk,&numlocs, ZAUTH);
1306 if (numlocs) {
1307 int i;
1308 for(i=0;i<numlocs;i++) {
1309 ZGetLocations(&locations,&one);
1310 serv_got_update(zgc,b->name,1,0,0,0,0);
1311 }
1312 }
1313 }
1314 }
1315 }
1316 }
1317 return TRUE;
1318 }
1319
1320 #else
1321
1322 static gint check_loc(gpointer data)
1323 {
1324 GaimBlistNode *gnode, *cnode, *bnode;
1325 ZAsyncLocateData_t ald;
1326 GaimConnection *gc = (GaimConnection *)data;
1327 zephyr_account *zephyr = gc->proto_data;
1328
1329 if (use_zeph02(zephyr)) {
1330 ald.user = NULL;
1331 memset(&(ald.uid), 0, sizeof(ZUnique_Id_t));
1332 ald.version = NULL;
1333 }
1334
1335 for (gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
1336 if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
1337 continue;
1338 for (cnode = gnode->child; cnode; cnode = cnode->next) {
1339 if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
1340 continue;
1341 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1342 GaimBuddy *b = (GaimBuddy *) bnode;
1343
1344 if (!GAIM_BLIST_NODE_IS_BUDDY(bnode))
1345 continue;
1346 if (b->account->gc == gc) {
1347 const char *chk;
1348
1349 chk = local_zephyr_normalize(zephyr,b->name);
1350 gaim_debug_info("zephyr","chk: %s b->name %s\n",chk,b->name);
1351 /* XXX add real error reporting */
1352 /* doesn't matter if this fails or not; we'll just move on to the next one */
1353 if (use_zeph02(zephyr)) {
1354 #ifdef WIN32
1355 int numlocs;
1356 int one=1;
1357 ZLocateUser(chk,&numlocs,ZAUTH);
1358 if (numlocs) {
1359 int i;
1360 for(i=0;i<numlocs;i++) {
1361 ZGetLocations(&locations,&one);
1362 if (nlocs>0)
1363 gaim_prpl_got_user_status(gc->account,b->name,"available",NULL);
1364 else
1365 gaim_prpl_got_user_status(gc->account,b->name,"offline",NULL);
1366 }
1367 }
1368 #else
1369 ZRequestLocations(chk, &ald, UNACKED, ZAUTH);
1370 g_free(ald.user);
1371 g_free(ald.version);
1372 #endif /* WIN32 */
1373 } else
1374 if (use_tzc(zephyr)) {
1375 gchar *zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n",chk);
1376 write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,strlen(zlocstr));
1377 g_free(zlocstr);
1378 }
1379 }
1380 }
1381 }
1382 }
1383
1384 return TRUE;
1385 }
1386
1387 #endif /* WIN32 */
1388
1389 static char *get_exposure_level()
1390 {
1391 /* XXX add real error reporting */
1392 char *exposure = ZGetVariable("exposure");
1393
1394 if (!exposure)
1395 return EXPOSE_REALMVIS;
1396 if (!g_ascii_strcasecmp(exposure, EXPOSE_NONE))
1397 return EXPOSE_NONE;
1398 if (!g_ascii_strcasecmp(exposure, EXPOSE_OPSTAFF))
1399 return EXPOSE_OPSTAFF;
1400 if (!g_ascii_strcasecmp(exposure, EXPOSE_REALMANN))
1401 return EXPOSE_REALMANN;
1402 if (!g_ascii_strcasecmp(exposure, EXPOSE_NETVIS))
1403 return EXPOSE_NETVIS;
1404 if (!g_ascii_strcasecmp(exposure, EXPOSE_NETANN))
1405 return EXPOSE_NETANN;
1406 return EXPOSE_REALMVIS;
1407 }
1408
1409 static void strip_comments(char *str)
1410 {
1411 char *tmp = strchr(str, '#');
1412
1413 if (tmp)
1414 *tmp = '\0';
1415 g_strchug(str);
1416 g_strchomp(str);
1417 }
1418
1419 static void zephyr_inithosts(zephyr_account *zephyr)
1420 {
1421 /* XXX This code may not be Win32 clean */
1422 struct hostent *hent;
1423
1424 if (gethostname(zephyr->ourhost, sizeof(zephyr->ourhost)) == -1) {
1425 gaim_debug_error("zephyr", "unable to retrieve hostname, %%host%% and %%canon%% will be wrong in subscriptions and have been set to unknown\n");
1426 g_strlcpy(zephyr->ourhost, "unknown", sizeof(zephyr->ourhost));
1427 g_strlcpy(zephyr->ourhostcanon, "unknown", sizeof(zephyr->ourhostcanon));
1428 return;
1429 }
1430
1431 if (!(hent = gethostbyname(zephyr->ourhost))) {
1432 gaim_debug_error("zephyr", "unable to resolve hostname, %%canon%% will be wrong in subscriptions.and has been set to the value of %%host%%, %s\n",zephyr->ourhost);
1433 g_strlcpy(zephyr->ourhostcanon, zephyr->ourhost, sizeof(zephyr->ourhostcanon));
1434 return;
1435 }
1436
1437 g_strlcpy(zephyr->ourhostcanon, hent->h_name, sizeof(zephyr->ourhostcanon));
1438
1439 return;
1440 }
1441
1442 static void process_zsubs(zephyr_account *zephyr)
1443 {
1444 /* Loads zephyr chats "(subscriptions) from ~/.zephyr.subs, and
1445 registers (subscribes to) them on the server */
1446
1447 /* XXX deal with unsubscriptions */
1448 /* XXX deal with punts */
1449
1450 FILE *f;
1451 gchar *fname;
1452 gchar buff[BUFSIZ];
1453
1454 fname = g_strdup_printf("%s/.zephyr.subs", gaim_home_dir());
1455 f = g_fopen(fname, "r");
1456 if (f) {
1457 char **triple;
1458 char *recip;
1459 char *z_class;
1460 char *z_instance;
1461 char *z_galaxy = NULL;
1462
1463 while (fgets(buff, BUFSIZ, f)) {
1464 strip_comments(buff);
1465 if (buff[0]) {
1466 triple = g_strsplit(buff, ",", 3);
1467 if (triple[0] && triple[1]) {
1468 char *tmp = g_strdup_printf("%s", zephyr->username);
1469 char *atptr;
1470
1471 z_class = triple[0];
1472 z_instance = triple[1];
1473 if (triple[2] == NULL) {
1474 recip = g_malloc0(1);
1475 } else if (!g_ascii_strcasecmp(triple[2], "%me%")) {
1476 recip = g_strdup_printf("%s", zephyr->username);
1477 } else if (!g_ascii_strcasecmp(triple[2], "*")) {
1478 /* wildcard
1479 * form of class,instance,* */
1480 recip = g_malloc0(1);
1481 } else if (!g_ascii_strcasecmp(triple[2], tmp)) {
1482 /* form of class,instance,aatharuv@ATHENA.MIT.EDU */
1483 recip = g_strdup(triple[2]);
1484 } else if ((atptr = strchr(triple[2], '@')) != NULL) {
1485 /* form of class,instance,*@ANDREW.CMU.EDU
1486 * class,instance,@ANDREW.CMU.EDU
1487 * If realm is local realm, blank recipient, else
1488 * @REALM-NAME
1489 */
1490 char *realmat = g_strdup_printf("@%s",zephyr->realm);
1491
1492 if (!g_ascii_strcasecmp(atptr, realmat))
1493 recip = g_malloc0(1);
1494 else
1495 recip = g_strdup(atptr);
1496 g_free(realmat);
1497 } else {
1498 recip = g_strdup(triple[2]);
1499 }
1500 g_free(tmp);
1501
1502 if (!g_ascii_strcasecmp(triple[0],"%host%")) {
1503 z_class = g_strdup(zephyr->ourhost);
1504 } else if (!g_ascii_strcasecmp(triple[0],"%canon%")) {
1505 z_class = g_strdup(zephyr->ourhostcanon);
1506 } else {
1507 z_class = g_strdup(triple[0]);
1508 }
1509
1510 if (!g_ascii_strcasecmp(triple[1],"%host%")) {
1511 z_instance = g_strdup(zephyr->ourhost);
1512 } else if (!g_ascii_strcasecmp(triple[1],"%canon%")) {
1513 z_instance = g_strdup(zephyr->ourhostcanon);
1514 } else {
1515 z_instance = g_strdup(triple[1]);
1516 }
1517
1518 /* There should be some sort of error report listing classes that couldn't be subbed to.
1519 Not important right now though */
1520
1521 if (zephyr_subscribe_to(zephyr,z_class, z_instance, recip,z_galaxy) != ZERR_NONE) {
1522
1523 gaim_debug_error("zephyr", "Couldn't subscribe to %s, %s, %s\n", z_class,z_instance,recip);
1524 }
1525
1526 zephyr->subscrips = g_slist_append(zephyr->subscrips, new_triple(zephyr,z_class,z_instance,recip));
1527 /* g_hash_table_destroy(sub_hash_table); */
1528 g_free(z_instance);
1529 g_free(z_class);
1530 g_free(recip);
1531 }
1532 g_strfreev(triple);
1533 }
1534 }
1535 fclose(f);
1536 }
1537 }
1538
1539 static void process_anyone(GaimConnection *gc)
1540 {
1541 FILE *fd;
1542 gchar buff[BUFSIZ], *filename;
1543 GaimGroup *g;
1544 GaimBuddy *b;
1545
1546 if (!(g = gaim_find_group(_("Anyone")))) {
1547 g = gaim_group_new(_("Anyone"));
1548 gaim_blist_add_group(g, NULL);
1549 }
1550
1551 filename = g_strconcat(gaim_home_dir(), "/.anyone", NULL);
1552 if ((fd = g_fopen(filename, "r")) != NULL) {
1553 while (fgets(buff, BUFSIZ, fd)) {
1554 strip_comments(buff);
1555 if (buff[0]) {
1556 if (!(b = gaim_find_buddy(gc->account, buff))) {
1557 char *stripped_user = zephyr_strip_local_realm(gc->proto_data,buff);
1558 gaim_debug_info("zephyr","stripped_user %s\n",stripped_user);
1559 if (!(b = gaim_find_buddy(gc->account,stripped_user))){
1560 b = gaim_buddy_new(gc->account, stripped_user, NULL);
1561 gaim_blist_add_buddy(b, NULL, g, NULL);
1562 }
1563 g_free(stripped_user);
1564 }
1565 }
1566 }
1567 fclose(fd);
1568 }
1569 g_free(filename);
1570 }
1571
1572 static char* normalize_zephyr_exposure(const char* exposure) {
1573 char *exp2 = g_strstrip(g_ascii_strup(exposure,-1));
1574
1575 if (!exp2)
1576 return EXPOSE_REALMVIS;
1577 if (!g_ascii_strcasecmp(exp2, EXPOSE_NONE))
1578 return EXPOSE_NONE;
1579 if (!g_ascii_strcasecmp(exp2, EXPOSE_OPSTAFF))
1580 return EXPOSE_OPSTAFF;
1581 if (!g_ascii_strcasecmp(exp2, EXPOSE_REALMANN))
1582 return EXPOSE_REALMANN;
1583 if (!g_ascii_strcasecmp(exp2, EXPOSE_NETVIS))
1584 return EXPOSE_NETVIS;
1585 if (!g_ascii_strcasecmp(exp2, EXPOSE_NETANN))
1586 return EXPOSE_NETANN;
1587 return EXPOSE_REALMVIS;
1588 }
1589
1590 static void zephyr_login(GaimAccount * account)
1591 {
1592 GaimConnection *gc;
1593 zephyr_account *zephyr;
1594 gboolean read_anyone;
1595 gboolean read_zsubs;
1596 gchar *exposure;
1597
1598 gc = gaim_account_get_connection(account);
1599 read_anyone = gaim_account_get_bool(gc->account,"read_anyone",TRUE);
1600 read_zsubs = gaim_account_get_bool(gc->account,"read_zsubs",TRUE);
1601 exposure = (gchar *)gaim_account_get_string(gc->account, "exposure_level", EXPOSE_REALMVIS);
1602
1603 #ifdef WIN32
1604 username = gaim_account_get_username(account);
1605 #endif
1606 gc->flags |= GAIM_CONNECTION_HTML | GAIM_CONNECTION_NO_BGCOLOR | GAIM_CONNECTION_NO_URLDESC;
1607 gc->proto_data = zephyr=g_new0(zephyr_account,1);
1608
1609 zephyr->account = account;
1610
1611 /* Make sure that the exposure (visibility) is set to a sane value */
1612 zephyr->exposure=g_strdup(normalize_zephyr_exposure(exposure));
1613
1614 if (gaim_account_get_bool(gc->account,"use_tzc",0)) {
1615 zephyr->connection_type = GAIM_ZEPHYR_TZC;
1616 } else {
1617 zephyr->connection_type = GAIM_ZEPHYR_KRB4;
1618 }
1619
1620 zephyr->encoding = (char *)gaim_account_get_string(gc->account, "encoding", ZEPHYR_FALLBACK_CHARSET);
1621 gaim_connection_update_progress(gc, _("Connecting"), 0, 8);
1622
1623 /* XXX z_call_s should actually try to report the com_err determined error */
1624 if (use_tzc(zephyr)) {
1625 pid_t pid;
1626 /* gaim_connection_error(gc,"tzc not supported yet"); */
1627 if ((pipe(zephyr->totzc) != 0) || (pipe(zephyr->fromtzc) != 0)) {
1628 gaim_debug_error("zephyr", "pipe creation failed. killing\n");
1629 exit(-1);
1630 }
1631
1632 pid = fork();
1633
1634 if (pid == -1) {
1635 gaim_debug_error("zephyr", "forking failed\n");
1636 exit(-1);
1637 }
1638 if (pid == 0) {
1639 unsigned int i=0;
1640 gboolean found_ps = FALSE;
1641 gchar ** tzc_cmd_array = g_strsplit(gaim_account_get_string(gc->account,"tzc_command","/usr/bin/tzc -e %s")," ",0);
1642 if (close(1) == -1) {
1643 gaim_debug_error("zephyr", "stdout couldn't be closed. dying\n");
1644 exit(-1);
1645 }
1646 if (dup2(zephyr->fromtzc[1], 1) == -1) {
1647 gaim_debug_error("zephyr", "dup2 of stdout failed \n");
1648 exit(-1);
1649 }
1650 if (close(zephyr->fromtzc[1]) == -1) {
1651 gaim_debug_error("zephyr", "closing of piped stdout failed\n");
1652 exit(-1);
1653 }
1654 if (close(0) == -1) {
1655 gaim_debug_error("zephyr", "stdin couldn't be closed. dying\n");
1656 exit(-1);
1657 }
1658 if (dup2(zephyr->totzc[0], 0) == -1) {
1659 gaim_debug_error("zephyr", "dup2 of stdin failed \n");
1660 exit(-1);
1661 }
1662 if (close(zephyr->totzc[0]) == -1) {
1663 gaim_debug_error("zephyr", "closing of piped stdin failed\n");
1664 exit(-1);
1665 }
1666 /* tzc_command should really be of the form
1667 path/to/tzc -e %s
1668 or
1669 ssh username@hostname pathtotzc -e %s
1670 -- this should not require a password, and ideally should be kerberized ssh --
1671 or
1672 fsh username@hostname pathtotzc -e %s
1673 */
1674 while(tzc_cmd_array[i] != NULL){
1675 if (!g_ascii_strncasecmp(tzc_cmd_array[i],"%s",2)) {
1676 /* fprintf(stderr,"replacing %%s with %s\n",zephyr->exposure); */
1677 tzc_cmd_array[i] = g_strdup(zephyr->exposure);
1678 found_ps = TRUE;
1679
1680 } else {
1681 /* fprintf(stderr,"keeping %s\n",tzc_cmd_array[i]); */
1682 }
1683 i++;
1684 }
1685
1686 if (!found_ps) {
1687 gaim_connection_error(gc,"Tzc command needs %s to set the exposure\n");
1688 return;
1689 }
1690
1691 execvp(tzc_cmd_array[0], tzc_cmd_array);
1692 }
1693 else {
1694 fd_set rfds;
1695 int bufsize = 2048;
1696 char *buf = (char *)calloc(bufsize, 1);
1697 char *bufcur = buf;
1698 struct timeval tv;
1699 char *ptr;
1700 int parenlevel=0;
1701 char* tempstr;
1702 int tempstridx;
1703
1704 zephyr->tzc_pid = pid;
1705 /* wait till we have data to read from ssh */
1706 FD_ZERO(&rfds);
1707 FD_SET(zephyr->fromtzc[ZEPHYR_FD_READ], &rfds);
1708
1709 tv.tv_sec = 10;
1710 tv.tv_usec = 0;
1711
1712 gaim_debug_info("zephyr", "about to read from tzc\n");
1713
1714 select(zephyr->fromtzc[ZEPHYR_FD_READ] + 1, &rfds, NULL, NULL, NULL);
1715
1716 FD_ZERO(&rfds);
1717 FD_SET(zephyr->fromtzc[ZEPHYR_FD_READ], &rfds);
1718 while (select(zephyr->fromtzc[ZEPHYR_FD_READ] + 1, &rfds, NULL, NULL, &tv)) {
1719 read(zephyr->fromtzc[ZEPHYR_FD_READ], bufcur, 1);
1720 bufcur++;
1721 if ((bufcur - buf) > (bufsize - 1)) {
1722 if ((buf = realloc(buf, bufsize * 2)) == NULL) {
1723 exit(-1);
1724 } else {
1725 bufcur = buf + bufsize;
1726 bufsize *= 2;
1727 }
1728 }
1729 FD_ZERO(&rfds);
1730 FD_SET(zephyr->fromtzc[ZEPHYR_FD_READ], &rfds);
1731 tv.tv_sec = 10;
1732 tv.tv_usec = 0;
1733
1734 }
1735 /* fprintf(stderr, "read from tzc\n"); */
1736 *bufcur = '\0';
1737 ptr = buf;
1738
1739 /* ignore all tzcoutput till we've received the first (*/
1740 while (ptr < bufcur && (*ptr !='(')) {
1741 ptr++;
1742 }
1743 if (ptr >=bufcur) {
1744 gaim_connection_error(gc,"invalid output by tzc (or bad parsing code)");
1745 free(buf);
1746 return;
1747 }
1748
1749 while(ptr < bufcur) {
1750 if (*ptr == '(') {
1751 parenlevel++;
1752 }
1753 else if (*ptr == ')') {
1754 parenlevel--;
1755 }
1756 gaim_debug_info("zephyr","tzc parenlevel is %d\n",parenlevel);
1757 switch (parenlevel) {
1758 case 0:
1759 break;
1760 case 1:
1761 /* Search for next beginning (, or for the ending */
1762 ptr++;
1763 while((*ptr != '(') && (*ptr != ')') && (ptr <bufcur))
1764 ptr++;
1765 if (ptr >= bufcur)
1766 gaim_debug_error("zephyr","tzc parsing error\n");
1767 break;
1768 case 2:
1769 /* You are probably at
1770 (foo . bar ) or (foo . "bar") or (foo . chars) or (foo . numbers) or (foo . () )
1771 Parse all the data between the first and last f, and move past )
1772 */
1773 tempstr = g_malloc0(20000);
1774 tempstridx=0;
1775 while(parenlevel >1) {
1776 ptr++;
1777 if (*ptr == '(')
1778 parenlevel++;
1779 if (*ptr == ')')
1780 parenlevel--;
1781 if (parenlevel > 1) {
1782 tempstr[tempstridx++]=*ptr;
1783 } else {
1784 ptr++;
1785 }
1786 }
1787 gaim_debug_info("zephyr","tempstr parsed\n");
1788 /* tempstr should now be a tempstridx length string containing all characters
1789 from that after the first ( to the one before the last paren ). */
1790 /* We should have the following possible lisp strings but we don't care
1791 (tzcspew . start) (version . "something") (pid . number)*/
1792 /* We care about 'zephyrid . "username@REALM.NAME"' and 'exposure . "SOMETHING"' */
1793 tempstridx=0;
1794 if (!g_ascii_strncasecmp(tempstr,"zephyrid",8)) {
1795 gchar* username = g_malloc0(100);
1796 int username_idx=0;
1797 char *realm;
1798 gaim_debug_info("zephyr","zephyrid found\n");
1799 tempstridx+=8;
1800 while(tempstr[tempstridx] !='"' && tempstridx < 20000)
1801 tempstridx++;
1802 tempstridx++;
1803 while(tempstr[tempstridx] !='"' && tempstridx < 20000)
1804 username[username_idx++]=tempstr[tempstridx++];
1805
1806 zephyr->username = g_strdup_printf("%s",username);
1807 if ((realm = strchr(username,'@')))
1808 zephyr->realm = g_strdup_printf("%s",realm+1);
1809 else {
1810 realm = (gchar *)gaim_account_get_string(gc->account,"realm","");
1811 if (!*realm) {
1812 realm = "local-realm";
1813 }
1814 zephyr->realm = g_strdup(realm);
1815 g_strlcpy(__Zephyr_realm, (const char*)zephyr->realm, REALM_SZ-1);
1816 }
1817 /* else {
1818 zephyr->realm = g_strdup("local-realm");
1819 }*/
1820
1821 g_free(username);
1822 } else {
1823 gaim_debug_info("zephyr", "something that's not zephyr id found %s\n",tempstr);
1824 }
1825
1826 /* We don't care about anything else yet */
1827 g_free(tempstr);
1828 break;
1829 default:
1830 gaim_debug_info("zephyr","parenlevel is not 1 or 2\n");
1831 /* This shouldn't be happening */
1832 break;
1833 }
1834 if (parenlevel==0)
1835 break;
1836 } /* while (ptr < bufcur) */
1837 gaim_debug_info("zephyr", "tzc startup done\n");
1838 free(buf);
1839 }
1840 }
1841 else if ( use_zeph02(zephyr)) {
1842 gchar* realm;
1843 z_call_s(ZInitialize(), "Couldn't initialize zephyr");
1844 z_call_s(ZOpenPort(&(zephyr->port)), "Couldn't open port");
1845 z_call_s(ZSetLocation((char *)zephyr->exposure), "Couldn't set location");
1846
1847 realm = (gchar *)gaim_account_get_string(gc->account,"realm","");
1848 if (!*realm) {
1849 realm = ZGetRealm();
1850 }
1851 zephyr->realm = g_strdup(realm);
1852 g_strlcpy(__Zephyr_realm, (const char*)zephyr->realm, REALM_SZ-1);
1853 zephyr->username = g_strdup(ZGetSender());
1854
1855 /* zephyr->realm = g_strdup(ZGetRealm()); */
1856 gaim_debug_info("zephyr","realm: %s\n",zephyr->realm);
1857 }
1858 else {
1859 gaim_connection_error(gc,"Only ZEPH0.2 supported currently");
1860 return;
1861 }
1862 gaim_debug_info("zephyr","does it get here\n");
1863 gaim_debug_info("zephyr"," realm: %s username:%s\n", zephyr->realm, zephyr->username);
1864
1865 /* For now */
1866 zephyr->galaxy = NULL;
1867 zephyr->krbtkfile = NULL;
1868 zephyr_inithosts(zephyr);
1869
1870 if (zephyr_subscribe_to(zephyr,"MESSAGE","PERSONAL",zephyr->username,NULL) != ZERR_NONE) {
1871 /* XXX don't translate this yet. It could be written better */
1872 /* XXX error messages could be handled with more detail */
1873 gaim_notify_error(account->gc, NULL,
1874 "Unable to subscribe to messages", "Unable to subscribe to initial messages");
1875 return;
1876 }
1877
1878 gaim_connection_set_state(gc, GAIM_CONNECTED);
1879
1880 if (read_anyone)
1881 process_anyone(gc);
1882 if (read_zsubs)
1883 process_zsubs(zephyr);
1884
1885 if (use_zeph02(zephyr)) {
1886 zephyr->nottimer = gaim_timeout_add(100, check_notify_zeph02, gc);
1887 } else if (use_tzc(zephyr)) {
1888 zephyr->nottimer = gaim_timeout_add(100, check_notify_tzc, gc);
1889 }
1890 zephyr->loctimer = gaim_timeout_add(20000, check_loc, gc);
1891
1892 }
1893
1894 static void write_zsubs(zephyr_account *zephyr)
1895 {
1896 /* Exports subscription (chat) list back to
1897 * .zephyr.subs
1898 * XXX deal with %host%, %canon%, unsubscriptions, and negative subscriptions (punts?)
1899 */
1900
1901 GSList *s = zephyr->subscrips;
1902 zephyr_triple *zt;
1903 FILE *fd;
1904 char *fname;
1905
1906 char **triple;
1907
1908 fname = g_strdup_printf("%s/.zephyr.subs", gaim_home_dir());
1909 fd = g_fopen(fname, "w");
1910
1911 if (!fd) {
1912 g_free(fname);
1913 return;
1914 }
1915
1916 while (s) {
1917 char *zclass, *zinst, *zrecip;
1918 zt = s->data;
1919 triple = g_strsplit(zt->name, ",", 3);
1920
1921 /* deal with classes */
1922 if (!g_ascii_strcasecmp(triple[0],zephyr->ourhost)) {
1923 zclass = g_strdup("%host%");
1924 } else if (!g_ascii_strcasecmp(triple[0],zephyr->ourhostcanon)) {
1925 zclass = g_strdup("%canon%");
1926 } else {
1927 zclass = g_strdup(triple[0]);
1928 }
1929
1930 /* deal with instances */
1931
1932 if (!g_ascii_strcasecmp(triple[1],zephyr->ourhost)) {
1933 zinst = g_strdup("%host%");
1934 } else if (!g_ascii_strcasecmp(triple[1],zephyr->ourhostcanon)) {
1935 zinst = g_strdup("%canon%");;
1936 } else {
1937 zinst = g_strdup(triple[1]);
1938 }
1939
1940 /* deal with recipients */
1941 if (triple[2] == NULL) {
1942 zrecip = g_strdup("*");
1943 } else if (!g_ascii_strcasecmp(triple[2],"")){
1944 zrecip = g_strdup("*");
1945 } else if (!g_ascii_strcasecmp(triple[2], zephyr->username)) {
1946 zrecip = g_strdup("%me%");
1947 } else {
1948 zrecip = g_strdup(triple[2]);
1949 }
1950
1951 fprintf(fd, "%s,%s,%s\n",zclass,zinst,zrecip);
1952
1953 g_free(zclass);
1954 g_free(zinst);
1955 g_free(zrecip);
1956 g_free(triple);
1957 s = s->next;
1958 }
1959 g_free(fname);
1960 fclose(fd);
1961 }
1962
1963 static void write_anyone(GaimConnection *gc)
1964 {
1965 GaimBlistNode *gnode, *cnode, *bnode;
1966 GaimBuddy *b;
1967 char *fname;
1968 FILE *fd;
1969 zephyr_account* zephyr = gc->proto_data;
1970 fname = g_strdup_printf("%s/.anyone", gaim_home_dir());
1971 fd = g_fopen(fname, "w");
1972 if (!fd) {
1973 g_free(fname);
1974 return;
1975 }
1976
1977 for (gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
1978 if (!GAIM_BLIST_NODE_IS_GROUP(gnode))
1979 continue;
1980 for (cnode = gnode->child; cnode; cnode = cnode->next) {
1981 if (!GAIM_BLIST_NODE_IS_CONTACT(cnode))
1982 continue;
1983 for (bnode = cnode->child; bnode; bnode = bnode->next) {
1984 if (!GAIM_BLIST_NODE_IS_BUDDY(bnode))
1985 continue;
1986 b = (GaimBuddy *) bnode;
1987 if (b->account == gc->account) {
1988 gchar *stripped_user = zephyr_strip_local_realm(zephyr,b->name);
1989 fprintf(fd, "%s\n", stripped_user);
1990 g_free(stripped_user);
1991 }
1992 }
1993 }
1994 }
1995
1996 fclose(fd);
1997 g_free(fname);
1998 }
1999
2000 static void zephyr_close(GaimConnection * gc)
2001 {
2002 GList *l;
2003 GSList *s;
2004 zephyr_account *zephyr = gc->proto_data;
2005 pid_t tzc_pid = zephyr->tzc_pid;
2006
2007 l = zephyr->pending_zloc_names;
2008 while (l) {
2009 g_free((char *)l->data);
2010 l = l->next;
2011 }
2012 g_list_free(zephyr->pending_zloc_names);
2013
2014 if (gaim_account_get_bool(gc->account, "write_anyone", FALSE))
2015 write_anyone(gc);
2016
2017 if (gaim_account_get_bool(gc->account, "write_zsubs", FALSE))
2018 write_zsubs(gc->proto_data);
2019
2020 s = zephyr->subscrips;
2021 while (s) {
2022 free_triple((zephyr_triple *) s->data);
2023 s = s->next;
2024 }
2025 g_slist_free(zephyr->subscrips);
2026
2027 if (zephyr->nottimer)
2028 gaim_timeout_remove(zephyr->nottimer);
2029 zephyr->nottimer = 0;
2030 if (zephyr->loctimer)
2031 gaim_timeout_remove(zephyr->loctimer);
2032 zephyr->loctimer = 0;
2033 gc = NULL;
2034 if (use_zeph02(zephyr)) {
2035 z_call(ZCancelSubscriptions(0));
2036 z_call(ZUnsetLocation());
2037 z_call(ZClosePort());
2038 } else {
2039 /* assume tzc */
2040 if (kill(tzc_pid,SIGTERM) == -1) {
2041 int err=errno;
2042 if (err==EINVAL) {
2043 gaim_debug_error("zephyr","An invalid signal was specified when killing tzc\n");
2044 }
2045 else if (err==ESRCH) {
2046 gaim_debug_error("zephyr","Tzc's pid didn't exist while killing tzc\n");
2047 }
2048 else if (err==EPERM) {
2049 gaim_debug_error("zephyr","gaim didn't have permission to kill tzc\n");
2050 }
2051 else {
2052 gaim_debug_error("zephyr","miscellaneous error while attempting to close tzc\n");
2053 }
2054 }
2055 }
2056 }
2057
2058 static int zephyr_send_message(zephyr_account *zephyr,char* zclass, char* instance, char* recipient, const char *im,
2059 const char *sig, char *opcode) ;
2060
2061 static const char * zephyr_get_signature()
2062 {
2063 /* XXX add zephyr error reporting */
2064 const char * sig =ZGetVariable("zwrite-signature");
2065 if (!sig) {
2066 sig = g_get_real_name();
2067 }
2068 return sig;
2069 }
2070
2071 static int zephyr_chat_send(GaimConnection * gc, int id, const char *im, GaimMessageFlags flags)
2072 {
2073 zephyr_triple *zt;
2074 const char *sig;
2075 GaimConversation *gconv1;
2076 GaimConvChat *gcc;
2077 char *inst;
2078 char *recipient;
2079 zephyr_account *zephyr = gc->proto_data;
2080
2081 zt = find_sub_by_id(gc->proto_data,id);
2082 if (!zt)
2083 /* this should never happen. */
2084 return -EINVAL;
2085
2086 sig = zephyr_get_signature();
2087
2088 gconv1 = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, zt->name,
2089 gc->account);
2090 gcc = gaim_conversation_get_chat_data(gconv1);
2091
2092 if (!(inst = (char *)gaim_conv_chat_get_topic(gcc)))
2093 inst = g_strdup("PERSONAL");
2094
2095 if (!g_ascii_strcasecmp(zt->recipient, "*"))
2096 recipient = local_zephyr_normalize(zephyr,"");
2097 else
2098 recipient = local_zephyr_normalize(zephyr,zt->recipient);
2099
2100 zephyr_send_message(zephyr,zt->class,inst,recipient,im,sig,"");
2101 return 0;
2102 }
2103
2104
2105 static int zephyr_send_im(GaimConnection * gc, const char *who, const char *im, GaimMessageFlags flags)
2106 {
2107 const char *sig;
2108 zephyr_account *zephyr = gc->proto_data;
2109 if (flags & GAIM_MESSAGE_AUTO_RESP)
2110 sig = "Automated reply:";
2111 else {
2112 sig = zephyr_get_signature();
2113 }
2114 zephyr_send_message(zephyr,"MESSAGE","PERSONAL",local_zephyr_normalize(zephyr,who),im,sig,"");
2115
2116 return 1;
2117 }
2118
2119 /* Munge the outgoing zephyr so that any quotes or backslashes are
2120 escaped and do not confuse tzc: */
2121
2122 static char* zephyr_tzc_escape_msg(const char *message)
2123 {
2124 int pos = 0;
2125 int pos2 = 0;
2126 char *newmsg;
2127
2128 if (message && (strlen(message) > 0)) {
2129 newmsg = g_new0(char,1+strlen(message)*2);
2130 while(pos < strlen(message)) {
2131 if (message[pos]=='\\') {
2132 newmsg[pos2]='\\';
2133 newmsg[pos2+1]='\\';
2134 pos2+=2;
2135 }
2136 else if (message[pos]=='"') {
2137 newmsg[pos2]='\\';
2138 newmsg[pos2+1]='"';
2139 pos2+=2;
2140 }
2141 else {
2142 newmsg[pos2] = message[pos];
2143 pos2++;
2144 }
2145 pos++;
2146 }
2147 } else {
2148 newmsg = g_strdup("");
2149 }
2150 /* fprintf(stderr,"newmsg %s message %s\n",newmsg,message); */
2151 return newmsg;
2152 }
2153
2154 char* zephyr_tzc_deescape_str(const char *message)
2155 {
2156 int pos = 0;
2157 int pos2 = 0;
2158 char *newmsg;
2159
2160 if (message && (strlen(message) > 0)) {
2161 newmsg = g_new0(char,strlen(message)+1);
2162 while(pos < strlen(message)) {
2163 if (message[pos]=='\\') {
2164 pos++;
2165 }
2166 newmsg[pos2] = message[pos];
2167 pos++;pos2++;
2168 }
2169 newmsg[pos2]='\0';
2170 } else {
2171 newmsg = g_strdup("");
2172 }
2173
2174 return newmsg;
2175 }
2176
2177 static int zephyr_send_message(zephyr_account *zephyr,char* zclass, char* instance, char* recipient, const char *im,
2178 const char *sig, char *opcode)
2179 {
2180
2181 /* (From the tzc source)
2182 * emacs sends something of the form:
2183 * ((class . "MESSAGE")
2184 * (auth . t)
2185 * (recipients ("PERSONAL" . "bovik") ("test" . ""))
2186 * (sender . "bovik")
2187 * (message . ("Harry Bovik" "my zgram"))
2188 * )
2189 */
2190 char *html_buf;
2191 char *html_buf2;
2192 html_buf = html_to_zephyr(im);
2193 html_buf2 = gaim_unescape_html(html_buf);
2194
2195 if(use_tzc(zephyr)) {
2196 char* zsendstr;
2197 /* CMU cclub tzc doesn't grok opcodes for now */
2198 char* tzc_sig = zephyr_tzc_escape_msg(sig);
2199 char *tzc_body = zephyr_tzc_escape_msg(html_buf2);
2200 zsendstr = g_strdup_printf("((tzcfodder . send) (class . \"%s\") (auth . t) (recipients (\"%s\" . \"%s\")) (message . (\"%s\" \"%s\")) ) \n",
2201 zclass, instance, recipient, tzc_sig, tzc_body);
2202 /* fprintf(stderr,"zsendstr = %s\n",zsendstr); */
2203 write(zephyr->totzc[ZEPHYR_FD_WRITE],zsendstr,strlen(zsendstr));
2204 g_free(zsendstr);
2205 } else if (use_zeph02(zephyr)) {
2206 ZNotice_t notice;
2207 char *buf = g_strdup_printf("%s%c%s", sig, '\0', html_buf2);
2208 bzero((char *)&notice, sizeof(notice));
2209
2210 notice.z_kind = ACKED;
2211 notice.z_port = 0;
2212 notice.z_opcode = "";
2213 notice.z_class = zclass;
2214 notice.z_class_inst = instance;
2215 notice.z_recipient = recipient;
2216 notice.z_sender = 0;
2217 notice.z_default_format = "Class $class, Instance $instance:\n" "To: @bold($recipient) at $time $date\n" "From: @bold($1) <$sender>\n\n$2";
2218 notice.z_message_len = strlen(html_buf2) + strlen(sig) + 2;
2219 notice.z_message = buf;
2220 notice.z_opcode = g_strdup(opcode);
2221 gaim_debug_info("zephyr","About to send notice");
2222 if (! ZSendNotice(&notice, ZAUTH) == ZERR_NONE) {
2223 /* XXX handle errors here */
2224 return 0;
2225 }
2226 gaim_debug_info("zephyr","notice sent");
2227 g_free(buf);
2228 }
2229
2230 g_free(html_buf2);
2231 g_free(html_buf);
2232
2233 return 1;
2234 }
2235
2236 char *local_zephyr_normalize(zephyr_account *zephyr,const char *orig)
2237 {
2238 /*
2239 Basically the inverse of zephyr_strip_local_realm
2240 */
2241 char* buf;
2242
2243 if (!g_ascii_strcasecmp(orig, "")) {
2244 return g_strdup("");
2245 }
2246
2247 if (strchr(orig,'@')) {
2248 buf = g_strdup_printf("%s",orig);
2249 } else {
2250 buf = g_strdup_printf("%s@%s",orig,zephyr->realm);
2251 }
2252 return buf;
2253 }
2254
2255 static void zephyr_zloc(GaimConnection *gc, const char *who)
2256 {
2257 ZAsyncLocateData_t ald;
2258 zephyr_account *zephyr = gc->proto_data;
2259 gchar* normalized_who = local_zephyr_normalize(zephyr,who);
2260
2261 if (use_zeph02(zephyr)) {
2262 if (ZRequestLocations(normalized_who, &ald, UNACKED, ZAUTH) == ZERR_NONE) {
2263 zephyr->pending_zloc_names = g_list_append(zephyr->pending_zloc_names,
2264 g_strdup(normalized_who));
2265 } else {
2266 /* XXX deal with errors somehow */
2267 }
2268 } else if (use_tzc(zephyr)) {
2269 char* zlocstr = g_strdup_printf("((tzcfodder . zlocate) \"%s\")\n",normalized_who);
2270 zephyr->pending_zloc_names = g_list_append(zephyr->pending_zloc_names, g_strdup(normalized_who));
2271 write(zephyr->totzc[ZEPHYR_FD_WRITE],zlocstr,strlen(zlocstr));
2272 g_free(zlocstr);
2273 }
2274 }
2275
2276 static void zephyr_set_status(GaimAccount *account, GaimStatus *status) {
2277 zephyr_account *zephyr = gaim_account_get_connection(account)->proto_data;
2278 GaimStatusPrimitive primitive = gaim_status_type_get_primitive(gaim_status_get_type(status));
2279
2280 if (zephyr->away) {
2281 g_free(zephyr->away);
2282 zephyr->away=NULL;
2283 }
2284
2285 if (primitive == GAIM_STATUS_AWAY) {
2286 zephyr->away = g_strdup(gaim_status_get_attr_string(status,"message"));
2287 }
2288 else if (primitive == GAIM_STATUS_AVAILABLE) {
2289 if (use_zeph02(zephyr)) {
2290 ZSetLocation(zephyr->exposure);
2291 }
2292 else {
2293 char *zexpstr = g_strdup_printf("((tzcfodder . set-location) (hostname . \"%s\") (exposure . \"%s\"))\n",zephyr->ourhost,zephyr->exposure);
2294 write(zephyr->totzc[ZEPHYR_FD_WRITE],zexpstr,strlen(zexpstr));
2295 g_free(zexpstr);
2296 }
2297 }
2298 else if (primitive == GAIM_STATUS_INVISIBLE) {
2299 /* XXX handle errors */
2300 if (use_zeph02(zephyr)) {
2301 ZSetLocation(EXPOSE_OPSTAFF);
2302 } else {
2303 char *zexpstr = g_strdup_printf("((tzcfodder . set-location) (hostname . \"%s\") (exposure . \"%s\"))\n",zephyr->ourhost,EXPOSE_OPSTAFF);
2304 write(zephyr->totzc[ZEPHYR_FD_WRITE],zexpstr,strlen(zexpstr));
2305 g_free(zexpstr);
2306 }
2307 }
2308 }
2309
2310 static GList *zephyr_status_types(GaimAccount *account)
2311 {
2312 GaimStatusType *type;
2313 GList *types = NULL;
2314
2315 /* zephyr has several exposures
2316 NONE (where you are hidden, and zephyrs to you are in practice silently dropped -- yes this is wrong)
2317 OPSTAFF "hidden"
2318 REALM-VISIBLE visible to people in local realm
2319 REALM-ANNOUNCED REALM-VISIBLE+ plus your logins/logouts are announced to <login,username,*>
2320 NET-VISIBLE REALM-ANNOUNCED, plus visible to people in foreign realm
2321 NET-ANNOUNCED NET-VISIBLE, plus logins/logouts are announced to <login,username,*>
2322
2323 Online will set the user to the exposure they have in their options (defaulting to REALM-VISIBLE),
2324 Hidden, will set the user's exposure to OPSTAFF
2325
2326 Away won't change their exposure but will set an auto away message (for IMs only)
2327 */
2328
2329 type = gaim_status_type_new(GAIM_STATUS_AVAILABLE, NULL, NULL, TRUE);
2330 types = g_list_append(types,type);
2331
2332 type = gaim_status_type_new(GAIM_STATUS_INVISIBLE, NULL, NULL, TRUE);
2333 types = g_list_append(types,type);
2334
2335 type = gaim_status_type_new_with_attrs(
2336 GAIM_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
2337 "message", _("Message"), gaim_value_new(GAIM_TYPE_STRING),
2338 NULL);
2339 types = g_list_append(types, type);
2340
2341 type = gaim_status_type_new(GAIM_STATUS_OFFLINE, NULL, NULL, TRUE);
2342 types = g_list_append(types,type);
2343
2344 return types;
2345 }
2346
2347 static GList *zephyr_chat_info(GaimConnection * gc)
2348 {
2349 GList *m = NULL;
2350 struct proto_chat_entry *pce;
2351
2352 pce = g_new0(struct proto_chat_entry, 1);
2353
2354 pce->label = _("_Class:");
2355 pce->identifier = "class";
2356 m = g_list_append(m, pce);
2357
2358 pce = g_new0(struct proto_chat_entry, 1);
2359
2360 pce->label = _("_Instance:");
2361 pce->identifier = "instance";
2362 m = g_list_append(m, pce);
2363
2364 pce = g_new0(struct proto_chat_entry, 1);
2365
2366 pce->label = _("_Recipient:");
2367 pce->identifier = "recipient";
2368 m = g_list_append(m, pce);
2369
2370 return m;
2371 }
2372
2373 /* Called when the server notifies us a message couldn't get sent */
2374
2375 static void zephyr_subscribe_failed(GaimConnection *gc,char * z_class, char *z_instance, char * z_recipient, char* z_galaxy)
2376 {
2377 gchar* subscribe_failed = g_strdup_printf(_("Attempt to subscribe to %s,%s,%s failed"), z_class, z_instance,z_recipient);
2378 gaim_notify_error(gc,"", subscribe_failed, NULL);
2379 g_free(subscribe_failed);
2380 }
2381
2382 static char *zephyr_get_chat_name(GHashTable *data) {
2383 gchar* zclass = g_hash_table_lookup(data,"class");
2384 gchar* inst = g_hash_table_lookup(data,"instance");
2385 gchar* recipient = g_hash_table_lookup(data, "recipient");
2386 if (!zclass) /* This should never happen */
2387 zclass = "";
2388 if (!inst)
2389 inst = "*";
2390 if (!recipient)
2391 recipient = "";
2392 return g_strdup_printf("%s,%s,%s",zclass,inst,recipient);
2393 }
2394
2395
2396 static void zephyr_join_chat(GaimConnection * gc, GHashTable * data)
2397 {
2398 /* ZSubscription_t sub; */
2399 zephyr_triple *zt1, *zt2;
2400 const char *classname;
2401 const char *instname;
2402 const char *recip;
2403 zephyr_account *zephyr=gc->proto_data;
2404 classname = g_hash_table_lookup(data, "class");
2405 instname = g_hash_table_lookup(data, "instance");
2406 recip = g_hash_table_lookup(data, "recipient");
2407
2408
2409 if (!classname)
2410 return;
2411
2412 if (!g_ascii_strcasecmp(classname,"%host%"))
2413 classname = g_strdup(zephyr->ourhost);
2414 if (!g_ascii_strcasecmp(classname,"%canon%"))
2415 classname = g_strdup(zephyr->ourhostcanon);
2416
2417 if (!instname || !strlen(instname))
2418 instname = "*";
2419
2420 if (!g_ascii_strcasecmp(instname,"%host%"))
2421 instname = g_strdup(zephyr->ourhost);
2422 if (!g_ascii_strcasecmp(instname,"%canon%"))
2423 instname = g_strdup(zephyr->ourhostcanon);
2424
2425 if (!recip || (*recip == '*'))
2426 recip = "";
2427 if (!g_ascii_strcasecmp(recip, "%me%"))
2428 recip = zephyr->username;
2429
2430 zt1 = new_triple(gc->proto_data,classname, instname, recip);
2431 zt2 = find_sub_by_triple(gc->proto_data,zt1);
2432 if (zt2) {
2433 free_triple(zt1);
2434 if (!zt2->open) {
2435 if (!g_ascii_strcasecmp(instname,"*"))
2436 instname = "PERSONAL";
2437 serv_got_joined_chat(gc, zt2->id, zt2->name);
2438 zephyr_chat_set_topic(gc,zt2->id,instname);
2439 zt2->open = TRUE;
2440 }
2441 return;
2442 }
2443
2444 /* sub.zsub_class = zt1->class;
2445 sub.zsub_classinst = zt1->instance;
2446 sub.zsub_recipient = zt1->recipient; */
2447
2448 if (zephyr_subscribe_to(zephyr,zt1->class,zt1->instance,zt1->recipient,NULL) != ZERR_NONE) {
2449 /* XXX output better subscription information */
2450 zephyr_subscribe_failed(gc,zt1->class,zt1->instance,zt1->recipient,NULL);
2451 free_triple(zt1);
2452 return;
2453 }
2454
2455 zephyr->subscrips = g_slist_append(zephyr->subscrips, zt1);
2456 zt1->open = TRUE;
2457 serv_got_joined_chat(gc, zt1->id, zt1->name);
2458 if (!g_ascii_strcasecmp(instname,"*"))
2459 instname = "PERSONAL";
2460 zephyr_chat_set_topic(gc,zt1->id,instname);
2461 }
2462
2463 static void zephyr_chat_leave(GaimConnection * gc, int id)
2464 {
2465 zephyr_triple *zt;
2466 zephyr_account *zephyr = gc->proto_data;
2467 zt = find_sub_by_id(zephyr,id);
2468
2469 if (zt) {
2470 zt->open = FALSE;
2471 zt->id = ++(zephyr->last_id);
2472 }
2473 }
2474
2475 static GaimChat *zephyr_find_blist_chat(GaimAccount *account, const char *name)
2476 {
2477 GaimBlistNode *gnode, *cnode;
2478
2479 /* XXX needs to be %host%,%canon%, and %me% clean */
2480 for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
2481 for(cnode = gnode->child; cnode; cnode = cnode->next) {
2482 GaimChat *chat = (GaimChat*)cnode;
2483 char *zclass, *inst, *recip;
2484 char** triple;
2485 if(!GAIM_BLIST_NODE_IS_CHAT(cnode))
2486 continue;
2487 if(chat->account !=account)
2488 continue;
2489 if(!(zclass = g_hash_table_lookup(chat->components, "class")))
2490 continue;
2491 if(!(inst = g_hash_table_lookup(chat->components, "instance")))
2492 inst = g_strdup("");
2493 if(!(recip = g_hash_table_lookup(chat->components, "recipient")))
2494 recip = g_strdup("");
2495 /* gaim_debug_info("zephyr","in zephyr_find_blist_chat name: %s\n",name?name:""); */
2496 triple = g_strsplit(name,",",3);
2497 if (!g_ascii_strcasecmp(triple[0],zclass) && !g_ascii_strcasecmp(triple[1],inst) && !g_ascii_strcasecmp(triple[2],recip))
2498 return chat;
2499
2500 }
2501 }
2502 return NULL;
2503 }
2504 static const char *zephyr_list_icon(GaimAccount * a, GaimBuddy * b)
2505 {
2506 return "zephyr";
2507 }
2508
2509 static unsigned int zephyr_send_typing(GaimConnection *gc, const char *who, GaimTypingState state) {
2510 gchar *recipient;
2511 zephyr_account *zephyr = gc->proto_data;
2512 if (use_tzc(zephyr))
2513 return 0;
2514
2515 if (state == GAIM_NOT_TYPING)
2516 return 0;
2517
2518 /* XXX We probably should care if this fails. Or maybe we don't want to */
2519 if (!who) {
2520 gaim_debug_info("zephyr", "who is null\n");
2521 recipient = local_zephyr_normalize(zephyr,"");
2522 } else {
2523 char *comma = strrchr(who, ',');
2524 /* Don't ping broadcast (chat) recipients */
2525 /* The strrchr case finds a realm-stripped broadcast subscription
2526 e.g. comma is the last character in the string */
2527 if (comma && ( (*(comma+1) == '\0') || (*(comma+1) == '@')))
2528 return 0;
2529
2530 recipient = local_zephyr_normalize(zephyr,who);
2531 }
2532
2533 gaim_debug_info("zephyr","about to send typing notification to %s\n",recipient);
2534 zephyr_send_message(zephyr,"MESSAGE","PERSONAL",recipient,"","","PING");
2535 gaim_debug_info("zephyr","sent typing notification\n");
2536
2537 /*
2538 * TODO: Is this correct? It means we will call
2539 * serv_send_typing(gc, who, GAIM_TYPING) once every 15 seconds
2540 * until the Gaim user stops typing.
2541 */
2542 return ZEPHYR_TYPING_SEND_TIMEOUT;
2543 }
2544
2545
2546
2547 static void zephyr_chat_set_topic(GaimConnection * gc, int id, const char *topic)
2548 {
2549 zephyr_triple *zt;
2550 GaimConversation *gconv;
2551 GaimConvChat *gcc;
2552 gchar *topic_utf8;
2553 zephyr_account* zephyr = gc->proto_data;
2554 char *sender = (char *)zephyr->username;
2555
2556 zt = find_sub_by_id(gc->proto_data,id);
2557 /* find_sub_by_id can return NULL */
2558 if (!zt)
2559 return;
2560 gconv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_CHAT, zt->name,
2561 gc->account);
2562 gcc = gaim_conversation_get_chat_data(gconv);
2563
2564 topic_utf8 = zephyr_recv_convert(gc,(gchar *)topic,strlen(topic));
2565 gaim_conv_chat_set_topic(gcc,sender,topic_utf8);
2566 g_free(topic_utf8);
2567 return;
2568 }
2569
2570 /* commands */
2571
2572 static GaimCmdRet zephyr_gaim_cmd_msg(GaimConversation *conv,
2573 const char *cmd, char **args, char **error, void *data)
2574 {
2575 char *recipient;
2576 zephyr_account *zephyr = gaim_conversation_get_gc(conv)->proto_data;
2577 if (!g_ascii_strcasecmp(args[0],"*"))
2578 return GAIM_CMD_RET_FAILED; /* "*" is not a valid argument */
2579 else
2580 recipient = local_zephyr_normalize(zephyr,args[0]);
2581
2582 if (strlen(recipient) < 1)
2583 return GAIM_CMD_RET_FAILED; /* a null recipient is a chat message, not an IM */
2584
2585 if (zephyr_send_message(zephyr,"MESSAGE","PERSONAL",recipient,args[1],zephyr_get_signature(),""))
2586 return GAIM_CMD_RET_OK;
2587 else
2588 return GAIM_CMD_RET_FAILED;
2589 }
2590
2591 static GaimCmdRet zephyr_gaim_cmd_zlocate(GaimConversation *conv,
2592 const char *cmd, char **args, char **error, void *data)
2593 {
2594 zephyr_zloc(gaim_conversation_get_gc(conv),args[0]);
2595 return GAIM_CMD_RET_OK;
2596 }
2597
2598 static GaimCmdRet zephyr_gaim_cmd_instance(GaimConversation *conv,
2599 const char *cmd, char **args, char **error, void *data)
2600 {
2601 /* Currently it sets the instance with leading spaces and
2602 * all. This might not be the best thing to do, though having
2603 * one word isn't ideal either. */
2604
2605 GaimConvChat *gcc = gaim_conversation_get_chat_data(conv);
2606 int id = gcc->id;
2607 const char* instance = args[0];
2608 zephyr_chat_set_topic(gaim_conversation_get_gc(conv),id,instance);
2609 return GAIM_CMD_RET_OK;
2610 }
2611
2612 static GaimCmdRet zephyr_gaim_cmd_joinchat_cir(GaimConversation *conv,
2613 const char *cmd, char **args, char **error, void *data)
2614 {
2615 /* Join a new zephyr chat */
2616 GHashTable *triple = g_hash_table_new(NULL,NULL);
2617 g_hash_table_insert(triple,"class",args[0]);
2618 g_hash_table_insert(triple,"instance",args[1]);
2619 g_hash_table_insert(triple,"recipient",args[2]);
2620 zephyr_join_chat(gaim_conversation_get_gc(conv),triple);
2621 return GAIM_CMD_RET_OK;
2622 }
2623
2624 static GaimCmdRet zephyr_gaim_cmd_zi(GaimConversation *conv,
2625 const char *cmd, char **args, char **error, void *data)
2626 {
2627 /* args = instance, message */
2628 zephyr_account *zephyr = gaim_conversation_get_gc(conv)->proto_data;
2629 if ( zephyr_send_message(zephyr,"message",args[0],"",args[1],zephyr_get_signature(),""))
2630 return GAIM_CMD_RET_OK;
2631 else
2632 return GAIM_CMD_RET_FAILED;
2633 }
2634
2635 static GaimCmdRet zephyr_gaim_cmd_zci(GaimConversation *conv,
2636 const char *cmd, char **args, char **error, void *data)
2637 {
2638 /* args = class, instance, message */
2639 zephyr_account *zephyr = gaim_conversation_get_gc(conv)->proto_data;
2640 if ( zephyr_send_message(zephyr,args[0],args[1],"",args[2],zephyr_get_signature(),""))
2641 return GAIM_CMD_RET_OK;
2642 else
2643 return GAIM_CMD_RET_FAILED;
2644 }
2645
2646 static GaimCmdRet zephyr_gaim_cmd_zcir(GaimConversation *conv,
2647 const char *cmd, char **args, char **error, void *data)
2648 {
2649 /* args = class, instance, recipient, message */
2650 zephyr_account *zephyr = gaim_conversation_get_gc(conv)->proto_data;
2651 if ( zephyr_send_message(zephyr,args[0],args[1],args[2],args[3],zephyr_get_signature(),""))
2652 return GAIM_CMD_RET_OK;
2653 else
2654 return GAIM_CMD_RET_FAILED;
2655 }
2656
2657 static GaimCmdRet zephyr_gaim_cmd_zir(GaimConversation *conv,
2658 const char *cmd, char **args, char **error, void *data)
2659 {
2660 /* args = instance, recipient, message */
2661 zephyr_account *zephyr = gaim_conversation_get_gc(conv)->proto_data;
2662 if ( zephyr_send_message(zephyr,"message",args[0],args[1],args[2],zephyr_get_signature(),""))
2663 return GAIM_CMD_RET_OK;
2664 else
2665 return GAIM_CMD_RET_FAILED;
2666 }
2667
2668 static GaimCmdRet zephyr_gaim_cmd_zc(GaimConversation *conv,
2669 const char *cmd, char **args, char **error, void *data)
2670 {
2671 /* args = class, message */
2672 zephyr_account *zephyr = gaim_conversation_get_gc(conv)->proto_data;
2673 if ( zephyr_send_message(zephyr,args[0],"PERSONAL","",args[1],zephyr_get_signature(),""))
2674 return GAIM_CMD_RET_OK;
2675 else
2676 return GAIM_CMD_RET_FAILED;
2677 }
2678
2679 static void zephyr_register_slash_commands()
2680 {
2681
2682 gaim_cmd_register("msg","ws", GAIM_CMD_P_PRPL,
2683 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2684 "prpl-zephyr",
2685 zephyr_gaim_cmd_msg, _("msg &lt;nick&gt; &lt;message&gt;: Send a private message to a user"), NULL);
2686
2687 gaim_cmd_register("zlocate","w", GAIM_CMD_P_PRPL,
2688 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2689 "prpl-zephyr",
2690 zephyr_gaim_cmd_zlocate, _("zlocate &lt;nick&gt;: Locate user"), NULL);
2691
2692 gaim_cmd_register("zl","w", GAIM_CMD_P_PRPL,
2693 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2694 "prpl-zephyr",
2695 zephyr_gaim_cmd_zlocate, _("zl &lt;nick&gt;: Locate user"), NULL);
2696
2697 gaim_cmd_register("instance","s", GAIM_CMD_P_PRPL,
2698 GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2699 "prpl-zephyr",
2700 zephyr_gaim_cmd_instance, _("instance &lt;instance&gt;: Set the instance to be used on this class"), NULL);
2701
2702 gaim_cmd_register("inst","s", GAIM_CMD_P_PRPL,
2703 GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2704 "prpl-zephyr",
2705 zephyr_gaim_cmd_instance, _("inst &lt;instance&gt;: Set the instance to be used on this class"), NULL);
2706
2707 gaim_cmd_register("topic","s", GAIM_CMD_P_PRPL,
2708 GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2709 "prpl-zephyr",
2710 zephyr_gaim_cmd_instance, _("topic &lt;instance&gt;: Set the instance to be used on this class"), NULL);
2711
2712 gaim_cmd_register("sub", "www", GAIM_CMD_P_PRPL,
2713 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2714 "prpl-zephyr",
2715 zephyr_gaim_cmd_joinchat_cir,
2716 _("sub &lt;class&gt; &lt;instance&gt; &lt;recipient&gt;: Join a new chat"), NULL);
2717
2718 gaim_cmd_register("zi","ws", GAIM_CMD_P_PRPL,
2719 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2720 "prpl-zephyr",
2721 zephyr_gaim_cmd_zi, _("zi &lt;instance&gt;: Send a message to &lt;message,<i>instance</i>,*&gt;"), NULL);
2722
2723 gaim_cmd_register("zci","wws",GAIM_CMD_P_PRPL,
2724 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2725 "prpl-zephyr",
2726 zephyr_gaim_cmd_zci,
2727 _("zci &lt;class&gt; &lt;instance&gt;: Send a message to &lt;<i>class</i>,<i>instance</i>,*&gt;"), NULL);
2728
2729 gaim_cmd_register("zcir","wwws",GAIM_CMD_P_PRPL,
2730 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2731 "prpl-zephyr",
2732 zephyr_gaim_cmd_zcir,
2733 _("zcir &lt;class&gt; &lt;instance&gt; &lt;recipient&gt;: Send a message to &lt;<i>class</i>,<i>instance</i>,<i>recipient</i>&gt;"), NULL);
2734
2735 gaim_cmd_register("zir","wws",GAIM_CMD_P_PRPL,
2736 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2737 "prpl-zephyr",
2738 zephyr_gaim_cmd_zir,
2739 _("zir &lt;instance&gt; &lt;recipient&gt;: Send a message to &lt;MESSAGE,<i>instance</i>,<i>recipient</i>&gt;"), NULL);
2740
2741 gaim_cmd_register("zc","ws", GAIM_CMD_P_PRPL,
2742 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY,
2743 "prpl-zephyr",
2744 zephyr_gaim_cmd_zc, _("zc &lt;class&gt;: Send a message to &lt;<i>class</i>,PERSONAL,*&gt;"), NULL);
2745
2746 }
2747
2748
2749 static void
2750 zephyr_add_deny(GaimConnection *gc, const char *who)
2751 {
2752 gaim_privacy_deny_add(gc->account,who,1);
2753 }
2754
2755 static void
2756 zephyr_remove_deny(GaimConnection *gc, const char *who)
2757 {
2758 gaim_privacy_deny_remove(gc->account,who,1);
2759 }
2760
2761 static void
2762 zephyr_add_permit(GaimConnection *gc, const char *who)
2763 {
2764 gaim_privacy_permit_add(gc->account,who,1);
2765 }
2766
2767 static void
2768 zephyr_remove_permit(GaimConnection *gc, const char *who)
2769 {
2770 gaim_privacy_permit_remove(gc->account,who,1);
2771 }
2772
2773 static void
2774 zephyr_set_permit_deny(GaimConnection *gc)
2775 {
2776 /* This doesn't have to do anything, since really, we can just check account->perm_deny when deciding whether to di */
2777 return;
2778 }
2779 static int zephyr_resubscribe(GaimConnection *gc)
2780 {
2781 /* Resubscribe to the in-memory list of subscriptions and also
2782 unsubscriptions*/
2783 zephyr_account *zephyr = gc->proto_data;
2784 GSList *s = zephyr->subscrips;
2785 zephyr_triple *zt;
2786 while (s) {
2787 zt = s->data;
2788 /* XXX We really should care if this fails */
2789 zephyr_subscribe_to(zephyr,zt->class,zt->instance,zt->recipient,NULL);
2790 s = s->next;
2791 }
2792 /* XXX handle unsubscriptions */
2793 return 1;
2794 }
2795
2796
2797 static void zephyr_action_resubscribe(GaimPluginAction *action)
2798 {
2799
2800 GaimConnection *gc = (GaimConnection *) action->context;
2801 zephyr_resubscribe(gc);
2802 }
2803
2804
2805 static void zephyr_action_get_subs_from_server(GaimPluginAction *action)
2806 {
2807 GaimConnection *gc = (GaimConnection *) action->context;
2808 zephyr_account *zephyr = gc->proto_data;
2809 gchar *title;
2810 int retval, nsubs, one,i;
2811 ZSubscription_t subs;
2812 if (use_zeph02(zephyr)) {
2813 GString* subout = g_string_new("Subscription list<br>");
2814
2815 title = g_strdup_printf("Server subscriptions for %s", zephyr->username);
2816
2817 if (zephyr->port == 0) {
2818 gaim_debug_error("zephyr", "error while retrieving port");
2819 return;
2820 }
2821 if ((retval = ZRetrieveSubscriptions(zephyr->port,&nsubs)) != ZERR_NONE) {
2822 /* XXX better error handling */
2823 gaim_debug_error("zephyr", "error while retrieving subscriptions from server");
2824 return;
2825 }
2826 for(i=0;i<nsubs;i++) {
2827 one = 1;
2828 if ((retval = ZGetSubscriptions(&subs,&one)) != ZERR_NONE) {
2829 /* XXX better error handling */
2830 gaim_debug_error("zephyr", "error while retrieving individual subscription");
2831 return;
2832 }
2833 g_string_append_printf(subout, "Class %s Instance %s Recipient %s<br>",
2834 subs.zsub_class, subs.zsub_classinst,
2835 subs.zsub_recipient);
2836 }
2837 gaim_notify_formatted(gc, title, title, NULL, subout->str, NULL, NULL);
2838 } else {
2839 /* XXX fix */
2840 gaim_notify_error(gc,"","tzc doesn't support this action",NULL);
2841 }
2842 }
2843
2844
2845 static GList *zephyr_actions(GaimPlugin *plugin, gpointer context)
2846 {
2847 GList *list = NULL;
2848 GaimPluginAction *act = NULL;
2849
2850 act = gaim_plugin_action_new(_("Resubscribe"), zephyr_action_resubscribe);
2851 list = g_list_append(list, act);
2852
2853 act = gaim_plugin_action_new(_("Retrieve subscriptions from server"), zephyr_action_get_subs_from_server);
2854 list = g_list_append(list,act);
2855
2856 return list;
2857 }
2858
2859 static GaimPlugin *my_protocol = NULL;
2860
2861 static GaimPluginProtocolInfo prpl_info = {
2862 OPT_PROTO_CHAT_TOPIC | OPT_PROTO_NO_PASSWORD,
2863 NULL, /* ??? user_splits */
2864 NULL, /* ??? protocol_options */
2865 NO_BUDDY_ICONS,
2866 zephyr_list_icon,
2867 NULL, /* ??? list_emblems */
2868 NULL, /* ??? status_text */
2869 NULL, /* ??? tooltip_text */
2870 zephyr_status_types, /* status_types */
2871 NULL, /* ??? blist_node_menu - probably all useful actions are already handled*/
2872 zephyr_chat_info, /* chat_info */
2873 NULL, /* chat_info_defaults */
2874 zephyr_login, /* login */
2875 zephyr_close, /* close */
2876 zephyr_send_im, /* send_im */
2877 NULL, /* XXX set info (Location?) */
2878 zephyr_send_typing, /* send_typing */
2879 zephyr_zloc, /* get_info */
2880 zephyr_set_status, /* set_status */
2881 NULL, /* ??? set idle */
2882 NULL, /* change password */
2883 NULL, /* add_buddy */
2884 NULL, /* add_buddies */
2885 NULL, /* remove_buddy */
2886 NULL, /* remove_buddies */
2887 zephyr_add_permit, /* add_permit */
2888 zephyr_add_deny, /* add_deny */
2889 zephyr_remove_permit, /* remove_permit */
2890 zephyr_remove_deny, /* remove_deny */
2891 zephyr_set_permit_deny, /* set_permit_deny */
2892 zephyr_join_chat, /* join_chat */
2893 NULL, /* reject_chat -- No chat invites*/
2894 zephyr_get_chat_name, /* get_chat_name */
2895 NULL, /* chat_invite -- No chat invites*/
2896 zephyr_chat_leave, /* chat_leave */
2897 NULL, /* chat_whisper -- No "whispering"*/
2898 zephyr_chat_send, /* chat_send */
2899 NULL, /* keepalive -- Not necessary*/
2900 NULL, /* register_user -- Not supported*/
2901 NULL, /* XXX get_cb_info */
2902 NULL, /* get_cb_away */
2903 NULL, /* alias_buddy */
2904 NULL, /* group_buddy */
2905 NULL, /* rename_group */
2906 NULL, /* buddy_free */
2907 NULL, /* convo_closed */
2908 NULL, /* normalize */
2909 NULL, /* XXX set_buddy_icon */
2910 NULL, /* remove_group */
2911 NULL, /* XXX get_cb_real_name */
2912 zephyr_chat_set_topic, /* set_chat_topic */
2913 zephyr_find_blist_chat, /* find_blist_chat */
2914 NULL, /* roomlist_get_list */
2915 NULL, /* roomlist_cancel */
2916 NULL, /* roomlist_expand_category */
2917 NULL, /* can_receive_file */
2918 NULL, /* send_file */
2919 NULL, /* new_xfer */
2920 NULL, /* offline_message */
2921 NULL, /* whiteboard_prpl_ops */
2922 NULL, /* send_raw */
2923 NULL, /* roomlist_room_serialize */
2924 };
2925
2926 static GaimPluginInfo info = {
2927 GAIM_PLUGIN_MAGIC,
2928 GAIM_MAJOR_VERSION,
2929 GAIM_MINOR_VERSION,
2930 GAIM_PLUGIN_PROTOCOL, /**< type */
2931 NULL, /**< ui_requirement */
2932 0, /**< flags */
2933 NULL, /**< dependencies */
2934 GAIM_PRIORITY_DEFAULT, /**< priority */
2935
2936 "prpl-zephyr", /**< id */
2937 "Zephyr", /**< name */
2938 VERSION, /**< version */
2939 /** summary */
2940 N_("Zephyr Protocol Plugin"),
2941 /** description */
2942 N_("Zephyr Protocol Plugin"),
2943 NULL, /**< author */
2944 GAIM_WEBSITE, /**< homepage */
2945
2946 NULL, /**< load */
2947 NULL, /**< unload */
2948 NULL, /**< destroy */
2949
2950 NULL, /**< ui_info */
2951 &prpl_info, /**< extra_info */
2952 NULL,
2953 zephyr_actions,
2954 };
2955
2956 static void init_plugin(GaimPlugin * plugin)
2957 {
2958 GaimAccountOption *option;
2959 char *tmp = get_exposure_level();
2960
2961 option = gaim_account_option_bool_new(_("Use tzc"), "use_tzc", FALSE);
2962 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2963
2964 option = gaim_account_option_string_new(_("tzc command"), "tzc_command", "/usr/bin/tzc -e %s");
2965 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2966
2967 option = gaim_account_option_bool_new(_("Export to .anyone"), "write_anyone", FALSE);
2968 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2969
2970 option = gaim_account_option_bool_new(_("Export to .zephyr.subs"), "write_zsubs", FALSE);
2971 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2972
2973 option = gaim_account_option_bool_new(_("Import from .anyone"), "read_anyone", TRUE);
2974 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2975
2976 option = gaim_account_option_bool_new(_("Import from .zephyr.subs"), "read_zsubs", TRUE);
2977 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2978
2979 option = gaim_account_option_string_new(_("Realm"), "realm", "");
2980 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2981
2982 option = gaim_account_option_string_new(_("Exposure"), "exposure_level", tmp?tmp: EXPOSE_REALMVIS);
2983 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2984
2985 option = gaim_account_option_string_new(_("Encoding"), "encoding", ZEPHYR_FALLBACK_CHARSET);
2986 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
2987
2988 my_protocol = plugin;
2989 zephyr_register_slash_commands();
2990 }
2991
2992 GAIM_INIT_PLUGIN(zephyr, init_plugin, info);

mercurial