root/trunk/src/message.c

Revision 680, 17.6 kB (checked in by jajcus, 3 years ago)

- dates in the copyright header updated

Line 
1 /* $Id: message.c,v 1.46 2004/10/27 20:50:14 jajcus Exp $ */
2
3 /*
4  *  (C) Copyright 2002-2006 Jacek Konieczny [jajcus(a)jajcus,net]
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License Version 2 as
8  *  published by the Free Software Foundation.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19
20 #include "ggtrans.h"
21 #include <stdio.h>
22 #include <ctype.h>
23 #include <time.h>
24 #include "message.h"
25 #include "presence.h"
26 #include "jabber.h"
27 #include "jid.h"
28 #include "users.h"
29 #include "sessions.h"
30 #include "requests.h"
31 #include "encoding.h"
32 #include "acl.h"
33 #include "debug.h"
34
35 typedef void (*MsgHandler)(struct stream_s *s,const char *from, const char *to,
36                                 const char *args, xmlnode msg);
37 typedef struct iq_namespace_s{
38         const char *command;
39         const char *abr;
40         const char *description;
41         MsgHandler handler;
42         int experimental;
43 }MsgCommand;
44
45 void message_get_roster(struct stream_s *s,const char *from, const char *to,
46                                 const char *args, xmlnode msg);
47 void message_friends_only(struct stream_s *s,const char *from, const char *to,
48                                 const char *args, xmlnode msg);
49 void message_invisible(struct stream_s *s,const char *from, const char *to,
50                                 const char *args, xmlnode msg);
51 void message_locale(struct stream_s *s,const char *from, const char *to,
52                                 const char *args, xmlnode msg);
53 void message_ignore_unknown(struct stream_s *s,const char *from, const char *to,
54                                 const char *args, xmlnode msg);
55 void message_ignore(struct stream_s *s,const char *from, const char *to,
56                                 const char *args, xmlnode msg);
57 void message_unignore(struct stream_s *s,const char *from, const char *to,
58                                 const char *args, xmlnode msg);
59 void message_status(struct stream_s *s,const char *from, const char *to,
60                                 const char *args, xmlnode msg);
61
62 MsgCommand msg_commands[]={
63         {"get roster","gr",N_("Download user list from server"),message_get_roster,0},
64         {"friends only","fo",N_("\"Only for friends\" mode"),message_friends_only,0},
65         {"invisible","iv",N_("\"Invisible\" mode"),message_invisible,0},
66         {"locale","loc",N_("Set user locale (language)"),message_locale,0},
67         {"ignore_unknown","iu",N_("Ignore messages from unknown users"),message_ignore_unknown,0},
68         {"ignore","ig",N_("Add a user to, or view the ignore list"),message_ignore,0},
69         {"unignore","ui",N_("Remove a user from, or view the ignore list"),message_unignore,0},
70         {"status","st",N_("Status message to show to GG users. Use 'off' to use Jabber status."),message_status,0},
71         {NULL,NULL,NULL,0},
72 };
73
74 int message_send_subject(struct stream_s *stream,const char *from,
75                 const char *to,const char *subject,const char *message,time_t timestamp){
76 xmlnode msg;
77 xmlnode n;
78 struct tm *tm;
79 char buf[101];
80
81         msg=xmlnode_new_tag("message");
82         if (from!=NULL)
83                 xmlnode_put_attrib(msg,"from",from);
84         else{
85                 char *jid;
86                 jid=jid_my_registered();
87                 xmlnode_put_attrib(msg,"from",jid);
88                 g_free(jid);
89         }
90         xmlnode_put_attrib(msg,"to",to);
91         n=xmlnode_insert_tag(msg,"subject");
92         xmlnode_insert_cdata(n,subject,-1);
93         n=xmlnode_insert_tag(msg,"body");
94         xmlnode_insert_cdata(n,message,-1);
95         if (timestamp){
96                 n=xmlnode_insert_tag(msg,"x");
97                 xmlnode_put_attrib(n,"xmlns","jabber:x:delay");
98                 tm=gmtime(&timestamp);
99                 strftime(buf,100,"%Y%m%dT%H:%M:%S",tm);
100                 xmlnode_put_attrib(n,"stamp",buf);
101                 xmlnode_insert_cdata(n,"Delayed message",-1);
102         }
103         stream_write(stream,msg);
104         xmlnode_free(msg);
105         return 0;
106 }
107
108 int message_send(struct stream_s *stream,const char *from,
109                 const char *to,int chat,const char *message,time_t timestamp){
110 xmlnode msg;
111 xmlnode n;
112 struct tm *tm;
113 char buf[101];
114
115         msg=xmlnode_new_tag("message");
116         if (from!=NULL)
117                 xmlnode_put_attrib(msg,"from",from);
118         else{
119                 char *jid;
120                 jid=jid_my_registered();
121                 xmlnode_put_attrib(msg,"from",jid);
122                 g_free(jid);
123         }
124         xmlnode_put_attrib(msg,"to",to);
125         if (chat) xmlnode_put_attrib(msg,"type","chat");
126         n=xmlnode_insert_tag(msg,"body");
127         xmlnode_insert_cdata(n,message,-1);
128         if (timestamp){
129                 n=xmlnode_insert_tag(msg,"x");
130                 xmlnode_put_attrib(n,"xmlns","jabber:x:delay");
131                 tm=gmtime(&timestamp);
132                 strftime(buf,100,"%Y%m%dT%H:%M:%S",tm);
133                 xmlnode_put_attrib(n,"stamp",buf);
134                 xmlnode_insert_cdata(n,"Delayed message",-1);
135         }
136         stream_write(stream,msg);
137         xmlnode_free(msg);
138         return 0;
139 }
140
141 int message_send_error(struct stream_s *stream,const char *from,
142                 const char *to,const char *body,int code,const char *str){
143 xmlnode msg;
144 xmlnode n;
145 char *s;
146
147         msg=xmlnode_new_tag("message");
148         if (from!=NULL)
149                 xmlnode_put_attrib(msg,"from",from);
150         else{
151                 char *jid;
152                 jid=jid_my_registered();
153                 xmlnode_put_attrib(msg,"from",jid);
154                 g_free(jid);
155         }
156         xmlnode_put_attrib(msg,"to",to);
157         xmlnode_put_attrib(msg,"type","error");
158         s=g_strdup_printf("%03u",code);
159         xmlnode_put_attrib(msg,"code",s);
160         g_free(s);
161         n=xmlnode_insert_tag(msg,"error");
162         xmlnode_insert_cdata(n,str,-1);
163         if (body){
164                 n=xmlnode_insert_tag(msg,"body");
165                 xmlnode_insert_cdata(n,body,-1);
166         }
167         stream_write(stream,msg);
168         xmlnode_free(msg);
169         return 0;
170 }
171
172 void message_get_roster(struct stream_s *stream,const char *from, const char *to,
173                                 const char *args, xmlnode msg){
174 Session *s;
175
176         s=session_get_by_jid(from,stream,0);
177         if(!s || !s->ggs){
178                 message_send(stream,to,from,1,_("Log in first..."),0);
179                 return;
180         }
181
182         message_send(stream,to,from,1,_("Receiving roster..."),0);
183
184         gg_userlist_request(s->ggs, GG_USERLIST_GET, NULL);
185 }
186
187 void get_roster_done(Session *s,struct gg_event *e){
188 char **results;
189 char *body=NULL;
190 char *jid;
191 xmlnode roster;
192 xmlnode msg;
193 xmlnode n;
194 int i;
195
196         if(!e->event.userlist.reply){
197                 message_send(s->s,NULL,s->user->jid,1,_("Roster empty."),0);
198                 return;
199         }
200
201         message_send(s->s,NULL,s->user->jid,0,_("Roster received."),0);
202
203         msg=xmlnode_new_tag("message");
204         jid=jid_my_registered();
205         xmlnode_put_attrib(msg,"from",jid);
206         g_free(jid);
207
208         xmlnode_put_attrib(msg,"to",s->user->jid);
209         n=xmlnode_insert_tag(msg,"body");
210         roster=xmlnode_insert_tag(msg,"x");
211         xmlnode_put_attrib(roster,"xmlns","jabber:x:roster");
212
213         body=g_strdup("");
214         results=g_strsplit(e->event.userlist.reply,"\r\n",0);
215         for(i=0;results[i];i++){
216                 char **cinfo;
217                 char *t,*jid;
218                 char *name=NULL;
219                 int j,uin;
220                 xmlnode item,tag;
221
222                 cinfo=g_strsplit(results[i],";",0);
223                 for(j=0;cinfo[j];j++);
224                 if (j<7){
225                         g_strfreev(cinfo);
226                         continue;
227                 }
228
229                 uin=atoi(cinfo[6]);
230                 item=xmlnode_insert_tag(roster,"item");
231
232                 t=g_strconcat(body,"\n",NULL);
233                 g_free(body);
234                 body=t;
235
236                 t=g_strdup_printf("%sUin: %u\n",body,uin);
237                 g_free(body);
238                 body=t;
239
240                 if (cinfo[2] && cinfo[2][0]){
241                         t=g_strdup_printf("%sNick: %s\n",body,cinfo[2]);
242                         g_free(body);
243                         body=t;
244                         if (name==NULL) name=g_strdup(cinfo[2]);
245                 }
246                 if (cinfo[0] && cinfo[0][0]){
247                         t=g_strdup_printf("%sFirst name: %s\n",body,cinfo[0]);
248                         g_free(body);
249                         body=t;
250                         if (name==NULL) name=g_strdup(cinfo[0]);
251                 }
252                 if (cinfo[1] && cinfo[1][0]){
253                         t=g_strdup_printf("%sLast name: %s\n",body,cinfo[1]);
254                         g_free(body);
255                         body=t;
256                         if (name==NULL) name=g_strdup(cinfo[1]);
257                 }
258                 if (cinfo[3] && cinfo[3][0]){
259                         t=g_strdup_printf("%sDisplay: %s\n",body,cinfo[3]);
260                         g_free(body);
261                         body=t;
262                         if (name) g_free(name);
263                         name=g_strdup(cinfo[3]);
264                 }
265                 if (cinfo[4] && cinfo[4][0]){
266                         t=g_strdup_printf("%sPhone: %s\n",body,cinfo[4]);
267                         g_free(body);
268                         body=t;
269                 }
270                 if (cinfo[5] && cinfo[5][0]){
271                         t=g_strdup_printf("%sGroup: %s\n",body,cinfo[5]);
272                         g_free(body);
273                         body=t;
274                         tag=xmlnode_insert_tag(item,"group");
275                         xmlnode_insert_cdata(n,to_utf8(cinfo[5]),-1);
276                 }
277                 if (cinfo[7] && cinfo[7][0]){
278                         t=g_strdup_printf("%sE-mail: %s\n",body,cinfo[7]);
279                         g_free(body);
280                         body=t;
281                 }
282
283                 jid=jid_build(uin);
284                 xmlnode_put_attrib(item,"jid",jid);
285                 g_free(jid);
286                 if (name==NULL) name=g_strdup_printf("%u",uin);
287                 xmlnode_put_attrib(item,"name",to_utf8(name));
288                 g_free(name);
289                 g_strfreev(cinfo);
290         }
291         g_strfreev(results);
292         xmlnode_insert_cdata(n,to_utf8(body),-1);
293
294         stream_write(s->s,msg);
295         xmlnode_free(msg);
296 }
297
298 void message_friends_only(struct stream_s *stream,const char *from, const char *to,
299                                 const char *args, xmlnode msg){
300 Session *session;
301 User *user;
302 gboolean on;
303
304         session=session_get_by_jid(from,stream,0);
305         if (session!=NULL)
306                 user=session->user;
307         else
308                 user=user_get_by_jid(from);
309         if (args && g_strcasecmp(args,"on")==0) on=TRUE;
310         else if (args && g_strcasecmp(args,"off")==0) on=FALSE;
311         else on=!user->friends_only;
312
313         if (user->friends_only==on){
314                 message_send(stream,to,from,1,_("No change."),0);
315                 return;
316         }
317         user->friends_only=on;
318
319         if (on)
320                 message_send(stream,to,from,1,_("friends only: on"),0);
321         else
322                 message_send(stream,to,from,1,_("friends only: off"),0);
323
324         if (session!=NULL) session_send_status(session);
325
326         user_save(user);
327 }
328
329 void message_invisible(struct stream_s *stream,const char *from, const char *to,
330                                 const char *args, xmlnode msg){
331 Session *session;
332 User *user;
333 Resource *r;
334 gboolean on;
335
336         session=session_get_by_jid(from,stream,0);
337         if (session!=NULL)
338                 user=session->user;
339         else
340                 user=user_get_by_jid(from);
341         if (args && g_strcasecmp(args,"on")==0) on=TRUE;
342         else if (args && g_strcasecmp(args,"off")==0) on=FALSE;
343         else on=!user->invisible;
344
345         if (user->invisible==on){
346                 message_send(stream,to,from,1,_("No change."),0);
347                 return;
348         }
349         user->invisible=on;
350
351         if (on)
352                 message_send(stream,to,from,1,_("invisible: on"),0);
353         else
354                 message_send(stream,to,from,1,_("invisible: off"),0);
355
356         if (session!=NULL) session_send_status(session);
357         r=session_get_cur_resource(session);
358         if ( r )
359                 presence_send(stream,NULL,user->jid,user->invisible?-1:r->available,r->show,session->gg_status_descr,0);
360
361         user_save(user);
362 }
363
364 void message_status(struct stream_s *stream,const char *from, const char *to,
365                                 const char *args, xmlnode msg){
366 Session *session;
367 User *user;
368 char *m;
369
370         session=session_get_by_jid(from,stream,0);
371         if (session!=NULL)
372                 user=session->user;
373         else
374                 user=user_get_by_jid(from);
375
376         g_free(user->status);
377         if (args) {
378                 if (!g_strcasecmp(args,"off")) user->status=NULL;
379                 else user->status=g_strndup(from_utf8(args),GG_STATUS_DESCR_MAXSIZE);
380         }
381         else user->status=NULL;
382
383         m=g_strdup_printf(_("status: %s%s%s"),
384                         (user->status?"`":""),
385                         (user->status?to_utf8(user->status):_("not set")),
386                         (user->status?"'":""));
387         message_send(stream,to,from,1,m,0);
388         g_free(m);
389
390         if (session!=NULL) session_send_status(session);
391         user_save(user);
392 }
393
394 void message_locale(struct stream_s *stream,const char *from, const char *to,
395                                 const char *args, xmlnode msg){
396 Session *session;
397 User *user;
398 char *m;
399
400         session=session_get_by_jid(from,stream,0);
401         if (session!=NULL)
402                 user=session->user;
403         else
404                 user=user_get_by_jid(from);
405
406         if (args && args[0]){
407                 if (user->locale) g_free(user->locale);
408                 user->locale=g_strdup(args);
409         }
410
411         m=g_strdup_printf(_("Locale set to: %s"),user->locale?user->locale:_("_default_"));
412         message_send(stream,to,from,1,m,0);
413         g_free(m);
414         user_save(user);
415 }
416
417 void message_ignore_unknown(struct stream_s *stream,const char *from, const char *to,
418                                 const char *args, xmlnode msg){
419 Session *session;
420 User *user;
421 gboolean on;
422
423         session=session_get_by_jid(from,stream,0);
424         if (session!=NULL)
425                 user=session->user;
426         else
427                 user=user_get_by_jid(from);
428         if (args && g_strcasecmp(args,"on")==0) on=TRUE;
429         else if (args && g_strcasecmp(args,"off")==0) on=FALSE;
430         else on=!user->ignore_unknown;
431
432         if (user->ignore_unknown==on){
433                 message_send(stream,to,from,1,_("No change."),0);
434                 return;
435         }
436         user->ignore_unknown=on;
437
438         if (on)
439                 message_send(stream,to,from,1,_("ignore unknown: on"),0);
440         else
441                 message_send(stream,to,from,1,_("ignore unknown: off"),0);
442
443         if (session!=NULL) session_send_status(session);
444
445         user_save(user);
446 }
447
448
449 void message_ignore(struct stream_s *stream,const char *from, const char *to,
450                                 const char *args, xmlnode msg){
451 Session *session;
452 User *user;
453 uin_t uin;
454 Contact *c;
455 gchar *m;
456
457         session=session_get_by_jid(from,stream,0);
458         if (session!=NULL)
459                 user=session->user;
460         else
461                 user=user_get_by_jid(from);
462
463         if (args && *args) uin=atoi(args);
464         else uin=0;
465
466         if (uin<=0) {
467                 GList *it;
468                 m=g_strdup(_("\nMessages from the following GG numbers will be ignored:"));
469                 for(it=user->contacts;it;it=it->next){
470                         c=(Contact *)it->data;
471                         if (!c->ignored) continue;
472                         m=g_strdup_printf(_("%s\n  %li"),m,(long)c->uin);
473                 }
474                 message_send(stream,to,from,1,m,0);
475                 g_free(m);
476                 return;
477         }
478
479         c=user_get_contact(user,uin,TRUE);
480         c->ignored=TRUE;
481         if (session) session_update_contact(session,c);
482                
483         m=g_strdup_printf(_("\nMessages from GG number %li will be ignored."),(long)uin);
484         message_send(stream,to,from,1,m,0);
485         g_free(m);
486
487         user_save(user);
488 }
489
490 void message_unignore(struct stream_s *stream,const char *from, const char *to,
491                                 const char *args, xmlnode msg){
492 Session *session;
493 User *user;
494 uin_t uin;
495 Contact *c;
496 gchar *m;
497
498         session=session_get_by_jid(from,stream,0);
499         if (session!=NULL)
500                 user=session->user;
501         else
502                 user=user_get_by_jid(from);
503
504         if (args && *args) uin=atoi(args);
505         else uin=0;
506
507         if (uin<=0) {
508                 message_ignore(stream,from,to,NULL,msg);
509                 return;
510         }
511
512         c=user_get_contact(user,uin,FALSE);
513         if (c) {
514                 c->ignored=FALSE;
515                 user_check_contact(user,c);
516                 if (session) session_update_contact(session,c);
517         }
518                
519         m=g_strdup_printf(_("\nMessages from GG number %li will NOT be ignored."),(long)uin);
520         message_send(stream,to,from,1,m,0);
521         g_free(m);
522        
523         user_save(user);
524 }
525
526 int message_to_me(struct stream_s *stream,const char *from,
527                 const char *to,const char *body,xmlnode tag){
528 int i,ignored;
529 const char *ce;
530 char *args,*p;
531 User *user;
532 Session *sess;
533 char *msg, *t;
534 GList *it;
535
536         sess=session_get_by_jid(from,stream,1);
537         if (sess)
538                 user=sess->user;
539         else
540                 user=NULL;
541         if (user==NULL){
542                 message_send(stream,to,from,1,_("I don't know you. Register first."),0);
543                 return -1;
544         }
545
546         if (body!=NULL){
547                 for(i=0;msg_commands[i].command;i++){
548                         if (strncmp(body,msg_commands[i].command,
549                                         strlen(msg_commands[i].command))==0)
550                                 ce=body+strlen(msg_commands[i].command);
551                         else if (strncmp(body,msg_commands[i].abr,
552                                         strlen(msg_commands[i].abr))==0)
553                                 ce=body+strlen(msg_commands[i].abr);
554                         else continue;
555                         if (ce[0]!='\000' && !isspace(ce[0])) continue;
556                         p=g_strdup(ce);
557                         args=g_strstrip(p);
558                         msg_commands[i].handler(stream,from,to,args,tag);
559                         g_free(p);
560                         return 0;
561                 }
562         }
563         msg=g_strdup(_("\nAvailable commands and abbreviations:"));
564         for(i=0;msg_commands[i].command;i++){
565                 t=g_strdup_printf("%s\n  %-14s %-4s - %s%s",msg,
566                                 msg_commands[i].command,
567                                 msg_commands[i].abr,
568                                 _(msg_commands[i].description),
569                                 msg_commands[i].experimental?_(" EXPERIMENTAL!"):"");
570                 g_free(msg); msg=t;
571         }
572         t=g_strdup_printf(_("%s\n\nCurrent settings:"),msg);
573         g_free(msg); msg=t;
574         t=g_strdup_printf(_("%s\n  status: %s%s%s"),msg,
575                         (user->status?"`":""),
576                         (user->status?to_utf8(user->status):_("not set")),
577                         (user->status?"'":""));
578         g_free(msg); msg=t;
579         t=g_strdup_printf(_("%s\n  friends only: %s"),msg,user->friends_only?_("on"):_("off"));
580         g_free(msg); msg=t;
581         t=g_strdup_printf(_("%s\n  invisible: %s"),msg,user->invisible?_("on"):_("off"));
582         g_free(msg); msg=t;
583         t=g_strdup_printf(_("%s\n  ignore unknown: %s"),msg,user->ignore_unknown?_("on"):_("off"));
584         g_free(msg); msg=t;
585         t=g_strdup_printf(_("%s\n  locale: %s"),msg,user->locale?user->locale:_("_default_"));
586         g_free(msg); msg=t;
587         ignored=0;
588         for(it=user->contacts;it;it=it->next){
589                 Contact * c=(Contact *)it->data;
590                 if (c->ignored) ignored++;
591         }
592         t=g_strdup_printf(_("%s\n  number of ignored users: %i"),msg,ignored);
593         g_free(msg); msg=t;
594         t=g_strdup_printf(_("%s\n\nRegistered as: %u"),msg,user->uin);
595         g_free(msg); msg=t;
596         if (sess->ggs){
597                 char *t1=session_get_info_string(sess);
598                 t=g_strdup_printf("%s\n  %s",msg,t1);
599                 g_free(t1);
600                 g_free(msg); msg=t;
601         }
602         message_send(stream,to,from,1,msg,0);
603         g_free(msg);
604
605         return 0;
606 }
607
608 int jabber_message(struct stream_s *stream,xmlnode tag){
609 char *type;
610 char *from;
611 char *to;
612 char *subject;
613 char *body;
614 int chat;
615 xmlnode subject_n;
616 xmlnode body_n;
617 Session *s;
618 User *u;
619
620         body_n=xmlnode_get_tag(tag,"body");
621         if (body_n!=NULL) body=xmlnode_get_data(body_n);
622         else body=NULL;
623
624         subject_n=xmlnode_get_tag(tag,"subject");
625         if (subject_n!=NULL) subject=xmlnode_get_data(subject_n);
626         else subject=NULL;
627
628         from=xmlnode_get_attrib(tag,"from");
629         to=xmlnode_get_attrib(tag,"to");
630         type=xmlnode_get_attrib(tag,"type");
631
632         if (!acl_is_allowed(from,tag)){
633                 if (type && !strcmp(type,"error")){
634                         debug("Ignoring forbidden message error");
635                         return -1;
636                 }
637                 if (!from) return -1;
638                 message_send_error(stream,to,from,NULL,405,_("Not allowed"));
639                 return -1;
640         }
641
642         if (from) u=user_get_by_jid(from);
643         else u=NULL;
644         user_load_locale(u);
645
646         if (!type || !strcmp(type,"normal")) chat=0;
647         else if (!strcmp(type,"chat")) chat=1;
648         else if (!strcmp(type,"error")){
649                 g_warning(N_("Error message received: %s"),xmlnode2str(tag));
650                 return 0;
651         }
652         else{
653                 g_warning(N_("Unsupported message type"));
654                 message_send_error(stream,to,from,body,500,_("Internal Server Error"));
655                 return -1;
656         }
657
658         if (!to || !jid_is_my(to)){
659                 g_warning(N_("Bad 'to' in: %s"),xmlnode2str(tag));
660                 message_send_error(stream,to,from,body,400,_("Bad Request"));
661                 return -1;
662         }
663
664         if (!jid_has_uin(to)){
665                 return message_to_me(stream,from,to,body,tag);
666         }
667
668         if (!from){
669                 g_warning(N_("Anonymous message? %s"),xmlnode2str(tag));
670                 message_send_error(stream,to,from,body,400,_("Bad Request"));
671                 return -1;
672         }
673
674         s=session_get_by_jid(from,NULL,0);
675         if (!s || !s->connected){
676                 g_warning(N_("%s not logged in. While processing %s"),from,xmlnode2str(tag));
677                 message_send_error(stream,to,from,body,407,_("Not logged in"));
678                 return -1;
679         }
680
681         if (subject)
682                 body=g_strdup_printf("Subject: %s\n%s",subject,body);
683         session_send_message(s,jid_get_uin(to),chat,from_utf8(body));
684         if (subject)
685                 g_free(body);
686
687         return 0;
688 }
689
Note: See TracBrowser for help on using the browser.