Carefully handle the signal, exit gracefully
[rahunas] / src / rahunasd.c
1 /**
2  * RahuNASd
3  * Author: Neutron Soutmun <neo.neutron@gmail.com>
4  * Date:   2008-08-07
5  */
6
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <sys/wait.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <stdarg.h>
13 #include <string.h>
14 #include <fcntl.h>
15 #include <errno.h>
16 #include <unistd.h>
17 #include <signal.h>
18 #include <syslog.h>
19
20 #include "rahunasd.h"
21 #include "rh-xmlrpc-server.h"
22 #include "rh-xmlrpc-cmd.h"
23 #include "rh-ipset.h"
24 #include "rh-utils.h"
25 #include "rh-task.h"
26
27 /* Abstract functions */
28 int logmsg(int priority, const char *msg, ...); 
29 int getline(int fd, char *buf, size_t size);
30
31 size_t expired_check(void *data);
32
33 /* Declaration */
34 struct rahunas_config rh_config;
35 struct rahunas_map *map = NULL;
36 struct set *rahunas_set = NULL;
37
38 struct set **set_list = NULL;
39 ip_set_id_t max_sets = 0;
40
41 const char *termstring = '\0';
42 pid_t pid, sid;
43
44 uint32_t iptoid(struct rahunas_map *map, const char *ip) {
45   uint32_t ret;
46   struct in_addr req_ip;
47
48   if (!map || !ip)
49     return (-1);
50
51   if (!(inet_aton(ip, &req_ip))) {
52     DP("Could not convert IP: %s", ip);
53     return (-1);  
54   }
55
56   DP("Request IP: %s", ip);
57   
58   ret = ntohl(req_ip.s_addr) - ntohl(map->first_ip);
59   if (ret < 0 || ret > (map->size - 1))
60     ret = (-1);
61
62   DP("Request Index: %lu", ret);
63   return ret; 
64 }
65
66 char *idtoip(struct rahunas_map *map, uint32_t id) {
67   struct in_addr sess_addr;
68
69   if (!map)
70     return termstring;
71
72   if (id < 0)
73     return termstring;
74
75   sess_addr.s_addr = htonl((ntohl(map->first_ip) + id));
76
77   return inet_ntoa(sess_addr);
78 }
79
80 void rh_free_member (struct rahunas_member *member)
81 {
82   if (member->username && member->username != termstring)
83     free(member->username);
84
85   if (member->session_id && member->session_id != termstring)
86     free(member->session_id);
87   
88   memset(member, 0, sizeof(struct rahunas_member));
89   member->username = termstring;
90   member->session_id = termstring;
91 }
92
93 int rh_openlog(const char *filename)
94 {
95   return open(filename, O_WRONLY | O_APPEND | O_CREAT);
96 }
97
98 int rh_closelog(int fd)
99 {
100   if (close(fd) == 0)
101     return 1;
102   else
103     return 0;
104 }
105
106 int logmsg(int priority, const char *msg, ...) 
107 {
108   int n, size = 256;
109   va_list ap;
110   char *time_fmt = "%b %e %T";
111   char *p = NULL;
112   char *np = NULL;
113
114   if (priority < RH_LOG_LEVEL)
115     return 0;
116
117   if ((p = rh_malloc(size)) == NULL) {
118     return (-1);
119   }
120
121   while (1) {
122     va_start(ap, msg);
123     n = vsnprintf(p, size, msg, ap);
124     va_end(ap);
125
126     if (n > -1 && n < size)
127       break;
128  
129     if (n > -1)
130       size = n+1;
131     else
132       size *= 2;
133
134     if ((np = realloc(p, size)) == NULL) {
135       free(p);
136       p = NULL;
137       break;
138     } else {
139       p = np;
140     }
141   }
142
143   if (!p)
144     return (-1);
145
146   fprintf(stderr, "%s : %s\n", timemsg(), p);
147
148   rh_free(&p);
149   rh_free(&np);
150 }
151
152 void rh_sighandler(int sig)
153 {
154   switch (sig) {
155     case SIGTERM:
156       if (pid == 0) {
157         rh_exit();
158         exit(EXIT_SUCCESS);
159       } else if (pid > 0) {
160         syslog(LOG_NOTICE, "Kill Child PID %d", pid);
161         kill(pid, SIGTERM);
162       } else {
163         syslog(LOG_ERR, "Invalid PID");
164         exit(EXIT_FAILURE);
165       }
166       break;
167   }
168 }
169
170 int getline(int fd, char *buf, size_t size)
171 {
172   char cbuf;
173   char *current;
174
175   if (!buf || fd < 0)
176     return 0;
177
178   current = buf;
179
180   while (read(fd, &cbuf, 1) > 0) {
181     *current = cbuf;
182     if (cbuf == '\n') {
183       *current = '\0';
184       break;
185     } else if ((current - buf) < (size - 1)) {
186       current++;
187     }
188   }
189
190   return (current - buf);
191 }
192
193 gboolean polling(gpointer data) {
194   struct rahunas_map *map = (struct rahunas_map *)data;
195   DP("%s", "Start polling!");
196   walk_through_set(&expired_check);
197   return TRUE;
198 }
199
200 size_t expired_check(void *data)
201 {
202   struct ip_set_list *setlist = (struct ip_set_list *) data;
203   struct set *set = set_list[setlist->index];
204   size_t offset;
205   struct ip_set_rahunas *table = NULL;
206   struct rahunas_member *members = map->members;
207   struct task_req req;
208   unsigned int i;
209   char *ip = NULL;
210   int res  = 0;
211
212   offset = sizeof(struct ip_set_list) + setlist->header_size;
213   table = (struct ip_set_rahunas *)(data + offset);
214
215   DP("Map size %d", map->size);
216  
217   for (i = 0; i < map->size; i++) {
218     if (test_bit(IPSET_RAHUNAS_ISSET, (void *)&table[i].flags)) {
219       if ((time(NULL) - table[i].timestamp) > rh_config.idle_threshold) {
220         // Idle Timeout
221         DP("Found IP: %s idle timeout", idtoip(map, i));
222         req.id = i;
223         memcpy(req.mac_address, &table[i].ethernet, ETH_ALEN);
224         req.req_opt = RH_RADIUS_TERM_IDLE_TIMEOUT;
225         send_xmlrpc_stopacct(map, i, RH_RADIUS_TERM_IDLE_TIMEOUT);
226         res = rh_task_stopsess(map, &req);
227       } else if (members[i].session_timeout != 0 && 
228                    time(NULL) > members[i].session_timeout) {
229         // Session Timeout (Expired)
230         DP("Found IP: %s session timeout", idtoip(map, i));
231         req.id = i;
232         memcpy(req.mac_address, &table[i].ethernet, ETH_ALEN);
233         req.req_opt = RH_RADIUS_TERM_SESSION_TIMEOUT;
234         send_xmlrpc_stopacct(map, i, RH_RADIUS_TERM_SESSION_TIMEOUT);
235         res = rh_task_stopsess(map, &req);
236       }
237     }
238   }
239 }
240
241 void rh_exit()
242 {
243   rh_task_stopservice(map);
244   rh_task_cleanup();
245   rh_closelog(rh_config.log_file);
246 }
247
248 static void
249 watch_child(char *argv[])
250 {
251   char *prog = NULL;
252   int failcount = 0;
253   time_t start;
254   time_t stop;
255   int status;
256
257   int nullfd;
258   int pidfd;
259   
260   if (*(argv[0]) == '(')
261     return;
262
263   pid = fork(); 
264   if (pid < 0) {
265     syslog(LOG_ALERT, "fork failed");
266     exit(EXIT_FAILURE);
267   } else if (pid > 0) {
268     /* parent */
269     pidfd = open(DEFAULT_PID, O_WRONLY | O_TRUNC | O_CREAT);
270     if (pidfd) {
271       dup2(pidfd, STDOUT_FILENO);
272       fprintf(stdout, "%d\n", pid);
273       close(pidfd);
274     }
275     exit(EXIT_SUCCESS);
276   }
277
278   /* Change the file mode mask */
279   umask(0);
280
281   if ((sid = setsid()) < 0)
282     syslog(LOG_ALERT, "setsid failed");
283
284   if ((chdir("/")) < 0) {
285     exit(EXIT_FAILURE);
286   }
287     
288   /* Close out the standard file descriptors */
289   close(STDIN_FILENO);
290   close(STDOUT_FILENO);
291   close(STDERR_FILENO);
292
293
294   while(1) {
295     pid = fork();
296     if (pid == 0) {
297       /* child */
298       prog = strdup(argv[0]);
299       argv[0] = strdup("(rahunasd)");
300       execvp(prog, argv);
301       syslog(LOG_ALERT, "execvp failed");
302     } else if (pid < 0) {
303       syslog(LOG_ERR, "Could not fork the child process");   
304       exit(EXIT_FAILURE);
305     }
306   
307     /* parent */
308     syslog(LOG_NOTICE, "RahuNASd Parent: child process %d started", pid);   
309
310     time(&start);
311
312     pid = waitpid(-1, &status, 0);
313     time(&stop);
314
315     if (WIFEXITED(status)) {
316       syslog(LOG_NOTICE,
317                "RahuNASd Parent: child process %d exited with status %d",
318                pid, WEXITSTATUS(status));
319     } else if (WIFSIGNALED(status)) {
320       syslog(LOG_NOTICE,
321                "RahuNASd Parent: child process %d exited due to signal %d",
322                pid, WTERMSIG(status));
323     } else {
324       syslog(LOG_NOTICE, "RahuNASd Parent: child process %d exited", pid);
325     }
326   
327     if (stop - start < 10)
328       failcount++;
329     else
330       failcount = 0;
331   
332     if (failcount == 5) {
333       syslog(LOG_ALERT, "Exiting due to repeated, frequent failures");
334       exit(EXIT_FAILURE);
335     }
336   
337     if (WIFEXITED(status) && (WEXITSTATUS(status) == 0)) {
338         syslog(LOG_NOTICE, "Exit Gracefully");
339         exit(EXIT_SUCCESS);
340     }
341     
342     sleep(3);
343   }
344 }
345
346 int main(int argc, char **argv) 
347 {
348   gchar* addr = "localhost";
349   int port    = 8123;
350   int fd_log;
351
352   char line[256];
353   char version[256];
354
355   GNetXmlRpcServer *server = NULL;
356   GMainLoop* main_loop     = NULL;
357
358   signal(SIGTERM, rh_sighandler);
359
360   watch_child(argv);
361
362   /* Get configuration from config file */
363   if (config_init(&rh_config) < 0) {
364     syslog(LOG_ERR, "Could not open config file %s", CONFIG_FILE);
365     exit(EXIT_FAILURE);
366   }
367
368   /* Open log file */
369   if ((fd_log = rh_openlog(rh_config.log_file)) < 0) {
370     syslog(LOG_ERR, "Could not open log file %s", rh_config.log_file);
371     exit(EXIT_FAILURE);
372   }
373
374   dup2(fd_log, STDERR_FILENO);
375
376   sprintf(version, "Starting %s - Version %s", PROGRAM, RAHUNAS_VERSION);
377   logmsg(RH_LOG_NORMAL, version);
378   syslog(LOG_INFO, version);
379
380   rh_task_init();
381
382   gnet_init();
383   main_loop = g_main_loop_new (NULL, FALSE);
384
385   /* XML RPC Server */
386   server = gnet_xmlrpc_server_new (addr, port);
387
388   if (!server) {
389     syslog(LOG_ERR, "Could not start XML-RPC server!");
390     rh_task_stopservice(map);
391     exit (EXIT_FAILURE);
392   }
393
394   gnet_xmlrpc_server_register_command (server, 
395                                        "startsession", 
396                                        do_startsession, 
397                                        map);
398
399   gnet_xmlrpc_server_register_command (server, 
400                                        "stopsession", 
401                                        do_stopsession, 
402                                        map);
403
404   gnet_xmlrpc_server_register_command (server, 
405                                        "getsessioninfo", 
406                                        do_getsessioninfo, 
407                                        map);
408
409   g_timeout_add_seconds (rh_config.polling_interval, polling, map);
410
411   rh_task_startservice(map);
412
413   g_main_loop_run(main_loop);
414
415   exit(EXIT_SUCCESS);
416 }