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