1 /* $Id: users.c,v 1.41 2004/04/13 17:44:07 jajcus Exp $ */
2
3 /*
4 * (C) Copyright 2002-2013 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 <string.h>
21 #include <dirent.h>
22 #include <sys/stat.h>
23 #include <time.h>
24 #include <locale.h>
25 #include <stdlib.h>
26 #include "ggtrans.h"
27 #include "jabber.h"
28 #include "users.h"
29 #include "jid.h"
30 #include "presence.h"
31 #include "conf.h"
32 #include "encoding.h"
33 #include "debug.h"
34
35 GHashTable *users_jid=NULL;
36 static char *spool_dir;
37 char *default_user_locale="C";
38 static guint users_tick_source;
39
40 static int user_destroy(User *s);
41
users_gc_hash_func(gpointer key,gpointer value,gpointer udata)42 static gboolean users_gc_hash_func(gpointer key,gpointer value,gpointer udata){
43 User *u=(User *)value;
44
45 if (u->refcount==0) {
46 user_destroy(u);
47 g_free(key);
48 }
49 return TRUE;
50 }
51
users_gc()52 int users_gc(){
53 g_hash_table_foreach_remove(users_jid,users_gc_hash_func,NULL);
54 return 0;
55 }
56
users_tick(gpointer data)57 gboolean users_tick(gpointer data){
58
59 users_gc();
60 return TRUE;
61 }
62
users_init()63 int users_init(){
64 int r;
65
66 default_user_locale=config_load_string("default_locale");
67
68 spool_dir=config_load_string("spool");
69 if (!spool_dir)
70 error_exit("%s", L_("No <spool/> defined in config file"));
71
72 r=chdir(spool_dir);
73 if (r) error_exit(L_("Couldn't enter %s: %s"),spool_dir,g_strerror(errno));
74
75 users_jid=g_hash_table_new(g_str_hash,g_str_equal);
76 if (!users_jid) return -1;
77
78 users_tick_source=g_timeout_add(60000,users_tick,NULL);
79
80 return 0;
81 }
82
83
84
users_done()85 int users_done(){
86 guint s;
87
88 g_source_remove(users_tick_source);
89 s=g_hash_table_size(users_jid);
90 if (s) g_debug(L_("Before cleanup: %u users in hash table"),s);
91 users_gc();
92 s=g_hash_table_size(users_jid);
93 if (s) g_warning(L_("Still %u users in hash table"),s);
94 g_hash_table_destroy(users_jid);
95 return 0;
96 }
97
user_ref(User * u)98 int user_ref(User *u){
99
100 return ++u->refcount;
101 }
102
user_unref(User * u)103 int user_unref(User *u){
104
105 g_assert(u->refcount>0);
106 u->refcount--;
107 return u->refcount;
108 }
109
user_free(User * u)110 int user_free(User *u){
111 gpointer key,value;
112 char *njid;
113
114 g_assert(u->refcount==0);
115 g_assert(users_jid!=NULL);
116
117 njid=jid_normalized(u->jid,0);
118 g_assert(njid!=NULL);
119 if (g_hash_table_lookup_extended(users_jid,(gpointer)njid,&key,&value)){
120 g_assert(u==value);
121 g_hash_table_remove(users_jid,(gpointer)njid);
122 g_free(key);
123 }
124 else debug(L_("user_remove: user '%s' not found in hash table"),njid);
125 g_free(njid);
126 return user_destroy(u);
127 }
128
set_subscribe(xmlnode node,SubscriptionType subscription)129 static void set_subscribe(xmlnode node, SubscriptionType subscription) {
130
131 switch (subscription){
132 case SUB_NONE:
133 xmlnode_put_attrib(node,"subscribe","none");
134 break;
135 case SUB_FROM:
136 xmlnode_put_attrib(node,"subscribe","from");
137 break;
138 case SUB_TO:
139 xmlnode_put_attrib(node,"subscribe","to");
140 break;
141 case SUB_BOTH:
142 xmlnode_put_attrib(node,"subscribe","both");
143 break;
144 default:
145 break;
146 }
147 }
148
user_save(User * u)149 int user_save(User *u){
150 FILE *f;
151 char *fn;
152 char *str;
153 char *njid;
154 int r;
155 xmlnode xml,tag,ctag,userlist;
156
157 g_assert(u!=NULL);
158 str=strchr(u->jid,'/');
159 g_assert(str==NULL);
160
161 if (!u->confirmed){
162 g_message(L_("Not saving user '%s' - account not confirmed."),u->jid);
163 return -1;
164 }
165
166 g_debug(L_("Saving user '%s'"),u->jid);
167 njid=jid_normalized(u->jid,0);
168 g_assert(njid!=NULL);
169 fn=g_strdup_printf("%s.new",njid);
170 f=fopen(fn,"w");
171 if (!f){
172 g_warning(L_("Couldn't open '%s': %s"),fn,g_strerror(errno));
173 g_free(fn);
174 g_free(njid);
175 return -1;
176 }
177 xml=xmlnode_new_tag("user");
178 tag=xmlnode_insert_tag(xml,"version");
179 str=g_strdup_printf("%08x",USER_FILE_FORMAT_VERSION);
180 xmlnode_put_attrib(tag,"file_format",str);
181 g_free(str);
182 tag=xmlnode_insert_tag(xml,"jid");
183 xmlnode_insert_cdata(tag,u->jid,-1);
184 set_subscribe(tag, u->subscribe);
185 tag=xmlnode_insert_tag(xml,"uin");
186 str=g_strdup_printf("%lu",(unsigned long)u->uin);
187 xmlnode_insert_cdata(tag,str,-1);
188 g_free(str);
189 tag=xmlnode_insert_tag(xml,"password");
190 xmlnode_insert_cdata(tag,u->password,-1);
191
192 if (u->last_sys_msg>0){
193 tag=xmlnode_insert_tag(xml,"last_sys_msg");
194 str=g_strdup_printf("%i",u->last_sys_msg);
195 xmlnode_insert_cdata(tag,str,-1);
196 g_free(str);
197 }
198
199 if (u->invisible) tag=xmlnode_insert_tag(xml,"invisible");
200 if (u->friends_only) tag=xmlnode_insert_tag(xml,"friendsonly");
201 if (u->ignore_unknown) tag=xmlnode_insert_tag(xml,"ignore_unknown");
202 if (u->locale){
203 tag=xmlnode_insert_tag(xml,"locale");
204 xmlnode_insert_cdata(tag,u->locale,-1);
205 }
206 if (u->status){
207 tag=xmlnode_insert_tag(xml,"status");
208 xmlnode_insert_cdata(tag,to_utf8(u->status),-1);
209 }
210
211 if (u->contacts){
212 GList *it;
213 Contact *c;
214
215 userlist=xmlnode_insert_tag(xml,"userlist");
216 for(it=g_list_first(u->contacts);it;it=it->next){
217 c=(Contact *)it->data;
218 ctag=xmlnode_insert_tag(userlist,"contact");
219 str=g_strdup_printf("%lu",(unsigned long)c->uin);
220 xmlnode_put_attrib(ctag,"uin",str);
221 if (c->ignored) xmlnode_put_attrib(ctag,"ignored","ignored");
222 if (c->blocked) xmlnode_put_attrib(ctag,"blocked","blocked");
223 set_subscribe(ctag, c->subscribe);
224 g_free(str);
225 }
226 }
227
228 str=xmlnode2str(xml);
229 r=fputs(str,f);
230 if (r<0){
231 g_warning(L_("Couldn't save '%s': %s"),u->jid,g_strerror(errno));
232 fclose(f);
233 unlink(fn);
234 xmlnode_free(xml);
235 g_free(fn);
236 g_free(njid);
237 return -1;
238 }
239 fclose(f);
240 r=unlink(njid);
241 if (r && errno!=ENOENT){
242 g_warning(L_("Couldn't unlink '%s': %s"),njid,g_strerror(errno));
243 xmlnode_free(xml);
244 g_free(fn);
245 g_free(njid);
246 return -1;
247 }
248
249 r=rename(fn,njid);
250 if (r){
251 g_warning(L_("Couldn't rename '%s' to '%s': %s"),fn,u->jid,g_strerror(errno));
252 xmlnode_free(xml);
253 g_free(fn);
254 g_free(njid);
255 return -1;
256 }
257
258 xmlnode_free(xml);
259 g_free(fn);
260 g_free(njid);
261 return 0;
262 }
263
get_subscribe(xmlnode node,unsigned int file_format_version)264 static SubscriptionType get_subscribe(xmlnode node, unsigned int file_format_version){
265 char *tmp;
266
267 tmp=xmlnode_get_attrib(node,"subscribe");
268 if (tmp) {
269 switch (tmp[0]) {
270 case 'f':
271 return SUB_FROM;
272 break;
273 case 't':
274 /* version 2.2.0 has a bug which causes
275 * SUB_BOTH to be changed to SUB_TO
276 * this will force resynchronisation with
277 * user's roster. User will be asked for
278 * subscription authorisation if the
279 * subscription was really "to"
280 */
281 if (file_format_version<=0x02020000) return SUB_UNDEFINED;
282 else return SUB_TO;
283 break;
284 case 'b':
285 return SUB_BOTH;
286 break;
287 default:
288 return SUB_NONE;
289 break;
290 }
291 }
292 return SUB_UNDEFINED;
293 }
294
user_load(const char * jid)295 User *user_load(const char *jid){
296 char *fn,*njid;
297 xmlnode xml,tag,t;
298 char *uin,*ujid,*name,*password,*email,*locale;
299 char *status;
300 int last_sys_msg=0,invisible=0,friends_only=0,ignore_unknown=0;
301 unsigned int file_format_version=0;
302 SubscriptionType subscribe;
303 User *u;
304 GList *contacts;
305 char *p;
306 char *data;
307
308 uin=ujid=name=password=email=NULL;
309 debug(L_("Loading user '%s'"),jid);
310 fn=jid_normalized(jid,0);
311 if (fn==NULL){
312 g_warning(L_("Bad JID: %s"),jid);
313 return NULL;
314 }
315 errno=0;
316 xml=xmlnode_file(fn);
317 if (xml==NULL){
318 debug(L_("Couldn't read or parse '%s': %s"),fn,errno?g_strerror(errno):N_("XML parse error"));
319 g_free(fn);
320 return NULL;
321 }
322 g_free(fn);
323 tag=xmlnode_get_tag(xml,"version");
324 if (tag!=NULL) {
325 p=xmlnode_get_attrib(tag,"file_format");
326 if (p!=NULL) file_format_version=(unsigned int)strtol(p,NULL,16);
327 }
328 tag=xmlnode_get_tag(xml,"jid");
329 if (tag!=NULL) {
330 ujid=xmlnode_get_data(tag);
331 subscribe=get_subscribe(tag, file_format_version);
332 }
333 if (ujid==NULL){
334 g_warning(L_("Couldn't find JID in %s's file"),jid);
335 return NULL;
336 }
337 tag=xmlnode_get_tag(xml,"uin");
338 if (tag!=NULL) uin=xmlnode_get_data(tag);
339 if (uin==NULL){
340 g_warning(L_("Couldn't find UIN in %s's file"),jid);
341 return NULL;
342 }
343 tag=xmlnode_get_tag(xml,"password");
344 if (tag!=NULL) password=xmlnode_get_data(tag);
345 if (password==NULL){
346 g_warning(L_("Couldn't find password in %s's file"),jid);
347 return NULL;
348 }
349 tag=xmlnode_get_tag(xml,"email");
350 if (tag!=NULL) email=xmlnode_get_data(tag);
351 tag=xmlnode_get_tag(xml,"name");
352 if (tag!=NULL) name=xmlnode_get_data(tag);
353 tag=xmlnode_get_tag(xml,"last_sys_msg");
354 if (tag!=NULL){
355 data=xmlnode_get_data(tag);
356 if (data!=NULL)
357 last_sys_msg=atoi(data);
358 }
359 tag=xmlnode_get_tag(xml,"friendsonly");
360 if (tag!=NULL) friends_only=1;
361 tag=xmlnode_get_tag(xml,"invisible");
362 if (tag!=NULL) invisible=1;
363 tag=xmlnode_get_tag(xml,"ignore_unknown");
364 if (tag!=NULL) ignore_unknown=1;
365 tag=xmlnode_get_tag(xml,"locale");
366 if (tag!=NULL) locale=xmlnode_get_data(tag);
367 else locale=NULL;
368 tag=xmlnode_get_tag(xml,"status");
369 if (tag!=NULL) {
370 status=xmlnode_get_data(tag);
371 if (status==NULL) status="";
372 }
373 else status=NULL;
374 tag=xmlnode_get_tag(xml,"userlist");
375 contacts=NULL;
376 if (tag!=NULL){
377 Contact *c;
378
379 for(t=xmlnode_get_firstchild(tag);t;t=xmlnode_get_nextsibling(t)){
380 char *node_name;
381 node_name=xmlnode_get_name(t);
382 if (!node_name) continue;
383 if (!strcmp(node_name,"uin")){
384 char *d;
385 int uin;
386
387 d=xmlnode_get_data(t);
388 if (d==NULL) continue;
389 uin=atoi(d);
390 if (uin<=0) continue;
391
392 c=g_new0(Contact,1);
393 c->status=-1;
394 c->uin=uin;
395 contacts=g_list_append(contacts,c);
396 continue;
397 }
398 if (!strcmp(node_name,"contact")){
399 char *d;
400 int uin;
401
402 d=xmlnode_get_attrib(t,"uin");
403 if (d==NULL) continue;
404
405 uin=atoi(d);
406 if (uin<=0) continue;
407
408 c=g_new0(Contact,1);
409 c->status=-1;
410 c->uin=uin;
411
412 d=xmlnode_get_attrib(t,"ignored");
413 if (d!=NULL && d[0]!='\000') c->ignored=1;
414 else c->ignored=0;
415 d=xmlnode_get_attrib(t,"blocked");
416 if (d!=NULL && d[0]!='\000') c->blocked=1;
417 else c->blocked=0;
418 c->subscribe=get_subscribe(t, file_format_version);
419 contacts=g_list_append(contacts,c);
420 }
421 }
422 }
423 u=g_new0(User,1);
424 u->uin=atoi(uin);
425 u->jid=g_strdup(jid);
426 p=strchr(u->jid,'/');
427 if (p) *p=0;
428 u->password=g_strdup(password);
429 u->last_sys_msg=last_sys_msg;
430 u->friends_only=friends_only;
431 u->invisible=invisible;
432 u->ignore_unknown=ignore_unknown;
433 u->locale=g_strdup(locale);
434 u->status=g_strdup(from_utf8(status));
435 u->contacts=contacts;
436 xmlnode_free(xml);
437 g_assert(users_jid!=NULL);
438 njid=jid_normalized(u->jid,0);
439 g_assert(njid!=NULL);
440 g_hash_table_insert(users_jid,(gpointer)njid,(gpointer)u);
441 u->confirmed=1;
442 u->subscribe=subscribe;
443 return u;
444 }
445
user_get_by_jid(const char * jid)446 User *user_get_by_jid(const char *jid){
447 User *u;
448 char *njid;
449
450 njid=jid_normalized(jid,0);
451 if (njid==NULL) return NULL;
452 u=(User *)g_hash_table_lookup(users_jid,(gpointer)njid);
453 g_free(njid);
454 if (u==NULL) u=user_load(jid);
455 return u;
456 }
457
user_destroy(User * u)458 static int user_destroy(User *u){
459 GList *it;
460 Contact *c;
461
462 g_message(L_("Destroying user '%s'"),u->jid);
463
464 g_assert(u!=NULL);
465 for(it=u->contacts;it;it=it->next){
466 c=(Contact *)it->data;
467 g_free(c->status_desc);
468 g_free(c);
469 }
470 g_list_free(u->contacts);
471 u->contacts=NULL;
472
473 g_free(u->jid);
474 g_free(u->password);
475 g_free(u->locale);
476 g_free(u->status);
477 g_free(u);
478 return 0;
479 }
480
481
user_create(const char * jid,uin_t uin,const char * password)482 User *user_create(const char *jid,uin_t uin,const char * password){
483 User *u;
484 char *p,*njid;
485
486 g_message(L_("Creating user '%s'"),jid);
487
488 njid=jid_normalized(jid,0);
489 if (njid==NULL){
490 g_warning(L_("Bad JID: '%s'"),jid);
491 return NULL;
492 }
493
494 u=(User *)g_hash_table_lookup(users_jid,(gpointer)njid);
495 if (u){
496 g_warning(L_("User '%s' already exists"),jid);
497 g_free(njid);
498 return NULL;
499 }
500
501 if (uin<1){
502 g_warning("%s", L_("Bad UIN"));
503 g_free(njid);
504 return NULL;
505 }
506 if (!password){
507 g_warning("%s", L_("Password not given"));
508 g_free(njid);
509 return NULL;
510 }
511 if (!jid){
512 g_warning("%s", L_("JID not given"));
513 g_free(njid);
514 return NULL;
515 }
516
517 u=g_new0(User,1);
518 u->uin=uin;
519 u->jid=g_strdup(jid);
520 p=strchr(u->jid,'/');
521 if (p) *p=0;
522 u->password=g_strdup(password);
523 u->confirmed=0;
524 u->invisible=0;
525 u->friends_only=1;
526 g_hash_table_insert(users_jid,(gpointer)njid,(gpointer)u);
527 u->refcount=0;
528 u->deleted=FALSE;
529 return u;
530 }
531
user_get_contact(User * u,uin_t uin,gboolean create)532 Contact * user_get_contact(User *u,uin_t uin, gboolean create){
533 Contact *c;
534 GList *it;
535
536 g_assert(u!=NULL);
537 for(it=g_list_first(u->contacts);it;it=it->next){
538 c=(Contact *)it->data;
539 if (c->uin==uin) return c;
540 }
541
542 if (!create) return NULL;
543
544 c=g_new0(Contact,1);
545
546 c->uin=uin;
547
548 u->contacts=g_list_append(u->contacts,c);
549 return c;
550 }
551
user_check_contact(User * u,Contact * c)552 int user_check_contact(User *u,Contact *c){
553
554 if (c->ignored || c->got_online || c->blocked
555 || c->got_probe || c->subscribe!=SUB_NONE) return 0;
556 u->contacts=g_list_remove(u->contacts,c);
557 if (c->status_desc) free(c->status_desc);
558 g_free(c);
559 user_save(u);
560 return 0;
561 }
562
user_set_contact_status(User * u,int status,unsigned int uin,char * desc,int more,uint32_t ip,uint16_t port,uint32_t version)563 int user_set_contact_status(User *u,int status,unsigned int uin,char *desc,
564 int more,uint32_t ip,uint16_t port,uint32_t version){
565 GList *it;
566 Contact *c;
567
568 g_assert(u!=NULL);
569 for(it=u->contacts;it;it=it->next){
570 c=(Contact *)it->data;
571 if (c->uin==uin){
572 c->status=status;
573 c->last_update=time(NULL);
574 if (c->status_desc) g_free(c->status_desc);
575 if (desc) c->status_desc=g_strdup(desc);
576 else c->status_desc=NULL;
577 if (more){
578 c->ip=ip;
579 c->port=port;
580 c->version=version;
581 }
582 return 0;
583 }
584 }
585
586 return -1;
587 }
588
user_get_contact_status(User * u,unsigned int uin)589 int user_get_contact_status(User *u,unsigned int uin){
590 GList *it;
591 Contact *c;
592
593 g_assert(u!=NULL);
594 for(it=u->contacts;it;it=it->next){
595 c=(Contact *)it->data;
596 if (c->uin==uin) return c->status;
597 }
598
599 return -1;
600 }
601
user_load_locale(User * u)602 void user_load_locale(User *u){
603
604 if (u && u->locale){
605 setlocale(LC_MESSAGES,u->locale);
606 setlocale(LC_CTYPE,u->locale);
607 }
608 else{
609 setlocale(LC_MESSAGES,default_user_locale);
610 setlocale(LC_CTYPE,default_user_locale);
611 }
612 }
613
users_probe_all()614 int users_probe_all(){
615 DIR *dir;
616 Stream *s;
617 struct dirent *de;
618 struct stat st;
619 int r;
620
621 s=jabber_stream();
622 if (!s) return -1;
623 dir=opendir(".");
624 if (!dir){
625 g_warning(L_("Couldn't open '%s' directory: %s"),spool_dir,g_strerror(errno));
626 return -1;
627 }
628 while((de=readdir(dir))){
629 r=stat(de->d_name,&st);
630 if (r){
631 g_warning(L_("Couldn't stat '%s': %s"),de->d_name,g_strerror(errno));
632 continue;
633 }
634 if (S_ISREG(st.st_mode) && strchr(de->d_name,'@'))
635 presence_send_probe(s,NULL,de->d_name);
636 }
637 closedir(dir);
638 return 0;
639 }
640
users_count()641 int users_count(){
642 DIR *dir;
643 struct dirent *de;
644 struct stat st;
645 int r,count=0;
646
647 dir=opendir(".");
648 if (!dir){
649 g_warning(L_("Couldn't open '%s' directory: %s"),spool_dir,g_strerror(errno));
650 return -1;
651 }
652 while((de=readdir(dir))){
653 r=stat(de->d_name,&st);
654 if (r){
655 g_warning(L_("Couldn't stat '%s': %s"),de->d_name,g_strerror(errno));
656 continue;
657 }
658 if (S_ISREG(st.st_mode)) count++;
659 }
660 closedir(dir);
661 return count;
662 }
663
user_sys_msg_received(User * u,int nr)664 int user_sys_msg_received(User *u,int nr){
665
666 if (nr<=u->last_sys_msg) return 0;
667 u->last_sys_msg=nr;
668 user_save(u);
669 return 1;
670 }
671
user_delete(User * u)672 int user_delete(User *u){
673 int r;
674 char *njid;
675
676 g_assert(u!=NULL);
677
678 njid=jid_normalized(u->jid,0);
679 g_assert(njid!=NULL);
680
681 r=unlink(njid);
682 if (r && errno!=ENOENT){
683 g_warning(L_("Couldn't unlink '%s': %s"),njid,g_strerror(errno));
684 r=-1;
685 }
686 else r=0;
687
688 g_free(njid);
689
690 u->deleted=TRUE;
691
692 users_gc();
693
694 return r;
695 }
696
user_print(User * u,int indent)697 void user_print(User *u,int indent){
698 char *space,*space1;
699 GList *it;
700 Contact *c;
701
702 space=g_strnfill(indent*2,' ');
703 space1=g_strnfill((indent+1)*2,' ');
704 g_message(L_("%sUser: %p"),space,u);
705 g_message(L_("%sJID: %s"),space,u->jid);
706 g_message(L_("%sUIN: %u"),space,(unsigned)u->uin);
707 g_message(L_("%sPassword: %p"),space,u->password);
708 g_message(L_("%sLast sys message: %i"),space,u->last_sys_msg);
709 g_message(L_("%sConfirmed: %i"),space,u->confirmed);
710 g_message(L_("%sContacts:"),space);
711 for(it=g_list_first(u->contacts);it;it=it->next){
712 c=(Contact *)it->data;
713 g_message(L_("%sContact: %p"),space1,c);
714 g_message(L_("%sUin: %u"),space1,(unsigned)c->uin);
715 g_message(L_("%sStatus: %i"),space1,c->status);
716 g_message(L_("%sLast update: %s"),space1,ctime((time_t *)&c->last_update));
717 }
718 g_free(space1);
719 g_free(space);
720 }
721
722