- replaced LOG/DBG with LM_
[sip-router] / modules_k / presence / presence.c
1 /*
2  * $Id$
3  *
4  * presence module - presence server implementation
5  *
6  * Copyright (C) 2006 Voice Sistem S.R.L.
7  *
8  * This file is part of openser, a free SIP server.
9  *
10  * openser is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version
14  *
15  * openser is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License 
21  * along with this program; if not, write to the Free Software 
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  *
24  * History:
25  * --------
26  *  2006-08-15  initial version (anca)
27  */
28
29 #include <stdio.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <sys/types.h>
33 #include <sys/ipc.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <time.h>
37
38 #include "../../db/db.h"
39 #include "../../sr_module.h"
40 #include "../../dprint.h"
41 #include "../../error.h"
42 #include "../../ut.h"
43 #include "../../parser/parse_to.h"
44 #include "../../parser/parse_uri.h" 
45 #include "../../parser/parse_content.h"
46 #include "../../parser/parse_from.h"
47 #include "../../mem/mem.h"
48 #include "../../mem/shm_mem.h"
49 #include "../../usr_avp.h"
50 #include "../tm/tm_load.h"
51 #include "../sl/sl_api.h"
52 #include "../../pt.h"
53 #include "../../mi/mi.h"
54 #include "../pua/hash.h"
55 #include "publish.h"
56 #include "subscribe.h"
57 #include "event_list.h"
58 #include "bind_presence.h"
59 #include "notify.h"
60
61 MODULE_VERSION
62
63 #define S_TABLE_VERSION  2
64 #define P_TABLE_VERSION  2
65 #define ACTWATCH_TABLE_VERSION 7
66
67 char *log_buf = NULL;
68 static int clean_period=100;
69
70 /* database connection */
71 db_con_t *pa_db = NULL;
72 db_func_t pa_dbf;
73 char *presentity_table="presentity";
74 char *active_watchers_table = "active_watchers";
75 char *watchers_table= "watchers";  
76
77 int use_db=1;
78 str server_address= {0, 0};
79 evlist_t* EvList= NULL;
80
81 /* to tag prefix */
82 char* to_tag_pref = "10";
83
84 /* the avp storing the To tag for replies */
85 int reply_tag_avp_id = 25;
86
87 /* TM bind */
88 struct tm_binds tmb;
89 /* SL bind */
90 struct sl_binds slb;
91
92 /** module functions */
93
94 static int mod_init(void);
95 static int child_init(int);
96 void destroy(void);
97 int handle_publish(struct sip_msg*, char*, char*);
98 int handle_subscribe(struct sip_msg*, char*, char*);
99 int stored_pres_info(struct sip_msg* msg, char* pres_uri, char* s);
100 static int fixup_presence(void** param, int param_no);
101 struct mi_root* mi_refreshWatchers(struct mi_root* cmd, void* param);
102 int update_pw_dialogs(subs_t* subs, unsigned int hash_code, subs_t** subs_array);
103 int mi_child_init(void);
104
105 int counter =0;
106 int pid = 0;
107 char prefix='a';
108 int startup_time=0;
109 str db_url = {0, 0};
110 int expires_offset = 0;
111 int max_expires= 3600;
112 int shtable_size;
113 shtable_t subs_htable= NULL;
114 int fallback2db= 0;
115
116 int phtable_size;
117 phtable_t* pres_htable;
118
119 static cmd_export_t cmds[]=
120 {
121         {"handle_publish",              handle_publish,      0,    0,         REQUEST_ROUTE},
122         {"handle_publish",              handle_publish,      1,fixup_presence,REQUEST_ROUTE},
123         {"handle_subscribe",    handle_subscribe,        0,        0,         REQUEST_ROUTE},
124         {"bind_presence",(cmd_function)bind_presence,1,    0,            0         },
125         {0,                                             0,                                   0,    0,            0             }         
126 };
127
128 static param_export_t params[]={
129         { "db_url",                                     STR_PARAM, &db_url.s},
130         { "presentity_table",           STR_PARAM, &presentity_table},
131         { "active_watchers_table",      STR_PARAM, &active_watchers_table},
132         { "watchers_table",                     STR_PARAM, &watchers_table},
133         { "clean_period",                       INT_PARAM, &clean_period },
134         { "to_tag_pref",                        STR_PARAM, &to_tag_pref },
135         { "totag_avpid",                        INT_PARAM, &reply_tag_avp_id },
136         { "expires_offset",                     INT_PARAM, &expires_offset },
137         { "max_expires",                        INT_PARAM, &max_expires },
138         { "server_address",         STR_PARAM, &server_address.s},
139         { "hash_size",              INT_PARAM, &shtable_size},
140         { "fallback2db",            INT_PARAM, &fallback2db},
141         {0,0,0}
142 };
143
144 static mi_export_t mi_cmds[] = {
145         { "refreshWatchers", mi_refreshWatchers,    0,  0,  mi_child_init},
146         {  0,                0,                     0,  0,  0}
147 };
148
149 /** module exports */
150 struct module_exports exports= {
151         "presence",                                     /* module name */
152         DEFAULT_DLFLAGS,                        /* dlopen flags */
153         cmds,                                           /* exported functions */
154         params,                                         /* exported parameters */
155         0,                                                      /* exported statistics */
156         mi_cmds,                                        /* exported MI functions */
157         0,                                                      /* exported pseudo-variables */
158         0,                                                      /* extra processes */
159         mod_init,                                       /* module initialization function */
160         (response_function) 0,      /* response handling function */
161         (destroy_function) destroy, /* destroy function */
162         child_init                  /* per-child init function */
163 };
164
165 /**
166  * init module function
167  */
168 static int mod_init(void)
169 {
170         str _s;
171         int ver = 0;
172
173         LM_NOTICE("initializing module ...\n");
174
175         if(db_url.s== NULL)
176         {
177                 use_db= 0;
178                 LM_DBG("presence module used for library purpose only\n");
179                 EvList= init_evlist();
180                 if(!EvList)
181                 {
182                         LM_ERR("unsuccessful initialize event list\n");
183                         return -1;
184                 }
185                 return 0;
186
187         }
188
189         if(expires_offset<0)
190                 expires_offset = 0;
191         
192         if(to_tag_pref==NULL || strlen(to_tag_pref)==0)
193                 to_tag_pref="10";
194
195         if(max_expires<= 0)
196                 max_expires = 3600;
197
198         if(server_address.s== NULL)
199                 LM_DBG("server_address parameter not set in configuration file\n");
200         
201         if(server_address.s)
202                 server_address.len= strlen(server_address.s);
203         else
204                 server_address.len= 0;
205
206         /* load SL API */
207         if(load_sl_api(&slb)==-1)
208         {
209                 LM_ERR("can't load sl functions\n");
210                 return -1;
211         }
212
213         /* load all TM stuff */
214         if(load_tm_api(&tmb)==-1)
215         {
216                 LM_ERR("can't load tm functions\n");
217                 return -1;
218         }
219
220         db_url.len = db_url.s ? strlen(db_url.s) : 0;
221         LM_DBG("db_url=%s/%d/%p\n", ZSW(db_url.s), db_url.len,db_url.s);
222         
223         /* binding to mysql module  */
224         if (bind_dbmod(db_url.s, &pa_dbf))
225         {
226                 LM_ERR("Database module not found\n");
227                 return -1;
228         }
229         
230
231         if (!DB_CAPABILITY(pa_dbf, DB_CAP_ALL))
232         {
233                 LM_ERR("Database module does not implement all functions"
234                                 " needed by presence module\n");
235                 return -1;
236         }
237
238         pa_db = pa_dbf.init(db_url.s);
239         if (!pa_db)
240         {
241                 LM_ERR("connecting database\n");
242                 return -1;
243         }
244         // verify table version 
245         _s.s = presentity_table;
246         _s.len = strlen(presentity_table);
247          ver =  table_version(&pa_dbf, pa_db, &_s);
248         if(ver!=P_TABLE_VERSION)
249         {
250                 LM_ERR("Wrong version v%d for table <%s>, need v%d\n", 
251                                 ver, _s.s, P_TABLE_VERSION);
252                 return -1;
253         }
254         
255         _s.s = active_watchers_table;
256         _s.len = strlen(active_watchers_table);
257          ver =  table_version(&pa_dbf, pa_db, &_s);
258         if(ver!=ACTWATCH_TABLE_VERSION)
259         {
260                 LM_ERR("Wrong version v%d for table <%s>, need v%d\n", 
261                                 ver, _s.s, ACTWATCH_TABLE_VERSION);
262                 return -1;
263         }
264
265         _s.s = watchers_table;
266         _s.len = strlen(watchers_table);
267          ver =  table_version(&pa_dbf, pa_db, &_s);
268         if(ver!=S_TABLE_VERSION)
269         {
270                 LM_ERR("Wrong version v%d for table <%s>, need v%d\n",
271                                 ver, _s.s, S_TABLE_VERSION);
272                 return -1;
273         }
274
275         EvList= init_evlist();
276         if(!EvList)
277         {
278                 LM_ERR("initializing event list\n");
279                 return -1;
280         }       
281
282         if(clean_period<=0)
283         {
284                 LM_DBG("wrong clean_period \n");
285                 return -1;
286         }
287
288         if(shtable_size< 1)
289                 shtable_size= 512;
290         else
291                 shtable_size= 1<< shtable_size;
292
293         subs_htable= new_shtable();
294         if(subs_htable== NULL)
295         {
296                 LM_ERR(" initializing subscribe hash table\n");
297                 return -1;
298         }
299         if(restore_db_subs()< 0)
300         {
301                 LM_ERR("restoring subscribe info from database\n");
302                 return -1;
303         }
304
305         if(phtable_size< 1)
306                 phtable_size= 256;
307         else
308                 phtable_size= 1<< phtable_size;
309
310         pres_htable= new_phtable();
311         if(pres_htable== NULL)
312         {
313                 LM_ERR("initializing presentity hash table\n");
314                 return -1;
315         }
316         if(pres_htable_restore()< 0)
317         {
318                 LM_ERR("filling in presentity hash table from database\n");
319                 return -1;
320         }
321         
322         startup_time = (int) time(NULL);
323         
324         register_timer(msg_presentity_clean, 0, clean_period);
325         
326         register_timer(msg_watchers_clean, 0, clean_period);
327         
328         register_timer(timer_db_update, 0, clean_period);
329
330         if(pa_db)
331                 pa_dbf.close(pa_db);
332         pa_db = NULL;
333         
334         return 0;
335 }
336
337 /**
338  * Initialize children
339  */
340 static int child_init(int rank)
341 {
342         LM_NOTICE("init_child [%d]  pid [%d]\n", rank, getpid());
343         
344         pid = my_pid();
345         
346         if(use_db== 0)
347                 return 0;
348
349         if (pa_dbf.init==0)
350         {
351                 LM_CRIT("child_init: database not bound\n");
352                 return -1;
353         }
354         pa_db = pa_dbf.init(db_url.s);
355         if (!pa_db)
356         {
357                 LM_ERR("child %d: unsuccessful connecting to database\n", rank);
358                 return -1;
359         }
360                 
361         if (pa_dbf.use_table(pa_db, presentity_table) < 0)  
362         {
363                 LM_ERR( "child %d:unsuccessful use_table presentity_table\n", rank);
364                 return -1;
365         }
366         if (pa_dbf.use_table(pa_db, active_watchers_table) < 0)  
367         {
368                 LM_ERR( "child %d:unsuccessful use_table active_watchers_table\n",
369                                 rank);
370                 return -1;
371         }
372         if (pa_dbf.use_table(pa_db, watchers_table) < 0)  
373         {
374                 LM_ERR( "child %d:unsuccessful use_table watchers_table\n", rank);
375                 return -1;
376         }
377
378         LM_DBG("child %d: Database connection opened successfully\n", rank);
379         
380         return 0;
381 }
382
383 int mi_child_init(void)
384 {
385         if(use_db== 0)
386                 return 0;
387
388         if (pa_dbf.init==0)
389         {
390                 LM_CRIT("database not bound\n");
391                 return -1;
392         }
393         pa_db = pa_dbf.init(db_url.s);
394         if (!pa_db)
395         {
396                 LM_ERR("connecting database\n");
397                 return -1;
398         }
399         
400         if (pa_dbf.use_table(pa_db, presentity_table) < 0)  
401         {
402                 LM_ERR( "unsuccessful use_table presentity_table\n");
403                 return -1;
404         }
405         if (pa_dbf.use_table(pa_db, active_watchers_table) < 0)  
406         {
407                 LM_ERR( "unsuccessful use_table active_watchers_table\n");
408                 return -1;
409         }
410         if (pa_dbf.use_table(pa_db, watchers_table) < 0)  
411         {
412                 LM_ERR( "unsuccessful use_table watchers_table\n");
413                 return -1;
414         }
415
416         LM_DBG("Database connection opened successfully\n");
417         return 0;
418 }
419
420
421 /*
422  * destroy function
423  */
424 void destroy(void)
425 {
426         LM_NOTICE("destroy module ...\n");
427
428         if(subs_htable && pa_db)
429                 timer_db_update(0, 0);
430
431         if(subs_htable)
432                 destroy_shtable();
433         
434         if(pres_htable)
435                 destroy_phtable();
436
437         if(pa_db && pa_dbf.close)
438                 pa_dbf.close(pa_db);
439         
440         destroy_evlist();
441 }
442
443 static int fixup_presence(void** param, int param_no)
444 {
445         xl_elem_t *model;
446         if(*param)
447         {
448                 if(xl_parse_format((char*)(*param), &model, XL_DISABLE_COLORS)<0)
449                 {
450                         LM_ERR( "wrong format[%s]\n",(char*)(*param));
451                         return E_UNSPEC;
452                 }
453  
454                 *param = (void*)model;
455                 return 0;
456         }
457         LM_ERR( "null format\n");
458         return E_UNSPEC;
459 }
460
461 /* 
462  *  mi cmd: refreshWatchers
463  *                      <presentity_uri> 
464  *                      <event>
465  *                      ?? should I receive the changed doc also?? 
466  *                      (faster- does not require a query) 
467  * */
468
469 struct mi_root* mi_refreshWatchers(struct mi_root* cmd, void* param)
470 {
471         struct mi_node* node= NULL;
472         str pres_uri, event;
473         struct sip_uri uri;
474         pres_ev_t* ev;
475         str* rules_doc= NULL;
476         int result;
477
478         LM_DBG("start\n");
479         
480         node = cmd->node.kids;
481         if(node == NULL)
482                 return 0;
483
484         /* Get presentity URI */
485         pres_uri = node->value;
486         if(pres_uri.s == NULL || pres_uri.len== 0)
487         {
488                 LM_ERR( "empty uri\n");
489                 return init_mi_tree(404, "Empty presentity URI", 20);
490         }
491         
492         node = node->next;
493         if(node == NULL)
494                 return 0;
495         event= node->value;
496         if(event.s== NULL || event.len== 0)
497         {
498                 LM_ERR( "empty event parameter\n");
499                 return init_mi_tree(400, "Empty event parameter", 21);
500         }
501         LM_DBG("event '%.*s'\n",  event.len, event.s);
502         
503         if(node->next!= NULL)
504         {
505                 LM_ERR( "Too many parameters\n");
506                 return init_mi_tree(400, "Too many parameters", 19);
507         }
508
509         if(parse_uri(pres_uri.s, pres_uri.len, &uri)< 0)
510         {
511                 LM_ERR( "parsing uri\n");
512                 goto error;
513         }
514
515         ev= contains_event(&event, NULL);
516         if(ev== NULL)
517         {
518                 LM_ERR( "wrong event parameter\n");
519                 return 0;
520         }
521         
522         result= ev->get_rules_doc(&uri.user,&uri.host,&rules_doc);
523         if(result< 0 || rules_doc==NULL || rules_doc->s== NULL)
524         {
525                 LM_ERR( "getting rules doc\n");
526                 goto error;
527         }
528         
529         if(update_watchers(pres_uri, ev, rules_doc)< 0)
530         {
531                 LM_ERR( "updating watchers\n");
532                 goto error;
533         }
534
535         return init_mi_tree(200, "OK", 2);
536
537 error:
538         if(rules_doc)
539         {
540                 if(rules_doc->s)
541                         pkg_free(rules_doc->s);
542                 pkg_free(rules_doc);
543         }
544         return 0;
545 }
546
547 int update_watchers(str pres_uri, pres_ev_t* ev, str* rules_doc)
548 {
549         subs_t subs;
550         db_key_t query_cols[3], result_cols[5], update_cols[5];
551         db_val_t query_vals[3], update_vals[2];
552         int n_update_cols= 0, n_result_cols= 0, n_query_cols = 0;
553         db_res_t* result= NULL;
554         db_row_t *row ; 
555         db_val_t *row_vals ;
556         int i;
557         str w_user, w_domain, reason;
558         int status;
559         int status_col, w_user_col, w_domain_col, reason_col;
560         int u_status_col, u_reason_col;
561         subs_t* subs_array= NULL,* s;
562         unsigned int hash_code;
563                                 
564         if(ev->content_type.s== NULL)
565         {
566                 ev= contains_event(&ev->name, NULL);
567                 if(ev== NULL)
568                 {
569                         LM_ERR("wrong event parameter\n");
570                         return 0;
571                 }
572         }
573
574         subs.pres_uri= pres_uri;
575         subs.event= ev;
576         subs.auth_rules_doc= rules_doc;
577
578         /* update in watchers_table */
579         query_cols[n_query_cols]= "p_uri";
580         query_vals[n_query_cols].nul= 0;
581         query_vals[n_query_cols].type= DB_STR;
582         query_vals[n_query_cols].val.str_val= pres_uri;
583         n_query_cols++;
584
585         query_cols[n_query_cols]= "event";
586         query_vals[n_query_cols].nul= 0;
587         query_vals[n_query_cols].type= DB_STR;
588         query_vals[n_query_cols].val.str_val= ev->name;
589         n_query_cols++;
590
591         result_cols[status_col= n_result_cols++]= "subs_status";
592         result_cols[reason_col= n_result_cols++]= "reason";
593         result_cols[w_user_col= n_result_cols++]= "w_user";
594         result_cols[w_domain_col= n_result_cols++]= "w_domain";
595         
596         update_cols[u_status_col= n_update_cols]= "subs_status";
597         update_vals[u_status_col].nul= 0;
598         update_vals[u_status_col].type= DB_INT;
599         n_update_cols++;
600
601         update_cols[u_reason_col= n_update_cols]= "reason";
602         update_vals[u_reason_col].nul= 0;
603         update_vals[u_reason_col].type= DB_STR;
604         n_update_cols++;
605
606         if (pa_dbf.use_table(pa_db, watchers_table) < 0) 
607         {
608                 LM_ERR( "in use_table\n");
609                 goto error;
610         }
611
612         if(pa_dbf.query(pa_db, query_cols, 0, query_vals, result_cols,n_query_cols,
613                                 n_result_cols, 0, &result)< 0)
614         {
615                 LM_ERR( "in sql query\n");
616                 goto error;
617         }
618         if(result== NULL)
619                 return 0;
620
621         if(result->n<= 0)
622                 goto done;
623
624         hash_code= core_hash(&pres_uri, &ev->name, shtable_size);
625         lock_get(&subs_htable[hash_code].lock);
626
627         for(i = 0; i< result->n; i++)
628         {
629                 row= &result->rows[i];
630                 row_vals = ROW_VALUES(row);
631
632                 status= row_vals[status_col].val.int_val;
633         
634                 reason.s= (char*)row_vals[reason_col].val.string_val;
635                 reason.len= reason.s?strlen(reason.s):0;
636
637                 w_user.s= (char*)row_vals[w_user_col].val.string_val;
638                 w_user.len= strlen(w_user.s);
639
640                 w_domain.s= (char*)row_vals[w_domain_col].val.string_val;
641                 w_domain.len= strlen(w_domain.s);
642
643                 subs.from_user= w_user;
644                 subs.from_domain= w_domain;
645                 memset(&subs.reason, 0, sizeof(str));
646                 if(ev->get_auth_status(&subs)< 0)
647                 {
648                         LM_ERR( "getting status from rules document\n");
649                         lock_release(&subs_htable[hash_code].lock);
650                         goto error;
651                 }
652                 if(subs.status!= status || reason.len!= subs.reason.len ||
653                         (reason.s && subs.reason.s && strncmp(reason.s, subs.reason.s,
654                                                                                                   reason.len)))
655                 {
656                         /* update in watchers_table */
657                         update_vals[u_status_col].val.int_val= subs.status;
658                         update_vals[u_reason_col].val.str_val= subs.reason;
659                         if (pa_dbf.use_table(pa_db, watchers_table) < 0) 
660                         {
661                                 LM_ERR( "in use_table\n");
662                                 lock_release(&subs_htable[hash_code].lock);
663                                 goto error;
664                         }
665
666                         if(pa_dbf.update(pa_db, query_cols, 0, query_vals, update_cols,
667                                                 update_vals, n_query_cols, n_update_cols)< 0)
668                         {
669                                 LM_ERR( "in sql update\n");
670                                 lock_release(&subs_htable[hash_code].lock);
671                                 goto error;
672                         }
673                         /* save in the list all affected dialogs */
674                         /* if status switches to terminated -> delete dialog */
675                         if(update_pw_dialogs(&subs, hash_code, &subs_array)< 0)
676                         {
677                                 LM_ERR( "extracting dialogs from [watcher]=%.*s@%.*s to"
678                                         " [presentity]=%.*s\n", w_user.len, w_user.s, w_domain.len,
679                                         w_domain.s, pres_uri.len, pres_uri.s);
680                                 lock_release(&subs_htable[hash_code].lock);
681                                 goto error;
682                         }
683                  }
684                         
685         }
686         lock_release(&subs_htable[hash_code].lock);
687         pa_dbf.free_result(pa_db, result);
688         result= NULL;
689         s= subs_array;
690
691         while(s)
692         {
693                 if(notify(s, NULL, NULL, 0)< 0)
694                 {
695                         LM_ERR( "sending Notify request\n");
696                         goto error;
697                 }
698                 s= s->next;
699         }
700         
701 done:
702         pa_dbf.free_result(pa_db, result);
703         free_subs_list(subs_array, PKG_MEM_TYPE);
704         return 0;
705
706 error:
707         if(result)
708                 pa_dbf.free_result(pa_db, result);
709         free_subs_list(subs_array, PKG_MEM_TYPE);
710         return 0;
711 }
712
713 int update_pw_dialogs(subs_t* subs, unsigned int hash_code, subs_t** subs_array)
714 {
715         subs_t* s, *ps, *cs;
716         int i= 0;
717
718         ps= subs_htable[hash_code].entries;
719         
720         while(ps && ps->next)
721         {
722                 s= ps->next;
723
724                 if(s->event== subs->event && s->pres_uri.len== subs->pres_uri.len &&
725                         s->from_user.len== subs->from_user.len && 
726                         s->from_domain.len==subs->from_domain.len &&
727                         strncmp(s->pres_uri.s, subs->pres_uri.s, subs->pres_uri.len)== 0 &&
728                         strncmp(s->from_user.s, subs->from_user.s, s->from_user.len)== 0 &&
729                         strncmp(s->from_domain.s,subs->from_domain.s,s->from_domain.len)==0)
730                 {
731                         i++;
732                         s->status= subs->status;
733                         s->reason= subs->reason;
734                         s->db_flag= UPDATEDB_FLAG;
735
736                         cs= mem_copy_subs(s, PKG_MEM_TYPE);
737                         if(cs== NULL)
738                         {
739                                 LM_ERR( "copying subs_t stucture\n");
740                                 return -1;
741                         }
742                         cs->expires-= (int)time(NULL);
743                         cs->next= (*subs_array);
744                         (*subs_array)= cs;
745
746                         if(s->status== TERMINATED_STATUS)
747                         {
748                                 cs->expires= 0;
749                                 ps->next= s->next;
750                                 shm_free(s);
751                         }
752                 }
753                 ps= ps->next;
754         }
755         LM_DBG("found %d matching dialogs\n", i);
756
757         return 0;
758 }