MERGE command
[rxpd] / src / rxpd_connection_cmd.c
1 /*
2     rxpd_connection_cmd.c - regex policy daemon
3
4   Copyright (C)
5     2007,               Christian Thaeter <ct@pipapo.org>
6
7   This program is free software; you can redistribute it and/or
8   modify it under the terms of the GNU General Public License as
9   published by the Free Software Foundation; either version 2 of the
10   License, or (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program; if not, write to the Free Software
19   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "rxpd.h"
23
24 #define RXPD_FILENAME_REQUIRED                                          \
25   do {                                                                  \
26     if (!self->file)                                                    \
27       {                                                                 \
28         rxpd_buffer_printf (&self->out, "#ERROR: missing filename\n");  \
29         return;                                                         \
30       }                                                                 \
31   } while (0)
32
33 void
34 rxpd_connection_cmd_CHECK (struct rxpd_connection* self)
35 {
36   RXPD_FILENAME_REQUIRED;
37
38   char* line;
39   while ((line = rxpd_buffer_readline (&self->in)))
40     {
41       if (*line == '\0')
42         {
43           rxpd_buffer_printf (&self->out, "#OK:\n");
44         }
45       else
46         {
47           // TODO implement RXPD_YIELD_EVERY
48           pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RD, FALSE, NULL);
49           LLIST_FOREACH (&self->file->rules, n)
50             {
51               struct rxpd_rule* rule = (struct rxpd_rule*)n;
52               if (rule->string[0] != '#')
53                 {
54                   if (regexec (&rule->rx, line, 0, NULL, 0) == 0)
55                     {
56                       if (rule->atime != (time_t) -1)
57                         time (&rule->atime);
58
59                       rxpd_buffer_printf (&self->out, "%s\n", rule->string);
60                       break;
61                     }
62                 }
63             }
64           pth_rwlock_release (&self->file->lock);
65         }
66     }
67
68   if (rxpd_buffer_state (&self->in) == RXPD_ERROR)
69     rxpd_buffer_printf (&self->out, "#ERROR:\n");
70 }
71
72
73
74 static void
75 rxpd_connection_APPEND_PREPEND_helper (struct rxpd_connection* self)
76 {
77   char* line;
78
79   while ((line = rxpd_buffer_readline (&self->in)))
80     {
81       if (*line && strncmp (line, "#EXIT:", sizeof ("#EXIT:")-1))
82         {
83           struct rxpd_rule* rule;
84           rule = rxpd_rule_new (line);
85           if (!rule)
86             abort();
87
88           llist_insert_tail (&self->tmp_list, &rule->node);
89         }
90       else
91         break;  /* exit at empty line, error or whatever */
92     }
93
94   if (rxpd_buffer_state (&self->in) == RXPD_ERROR)
95     rxpd_buffer_printf (&self->out, "#ERROR:\n");
96   else
97     rxpd_buffer_printf (&self->out, "#OK:\n");
98 }
99
100 void
101 rxpd_connection_cmd_APPEND (struct rxpd_connection* self)
102 {
103   RXPD_FILENAME_REQUIRED;
104
105   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
106
107   rxpd_connection_APPEND_PREPEND_helper (self);
108   llist_insertlist_prev (&self->file->rules, &self->tmp_list);
109
110   pth_rwlock_release (&self->file->lock);
111 }
112
113 void
114 rxpd_connection_cmd_PREPEND (struct rxpd_connection* self)
115 {
116   RXPD_FILENAME_REQUIRED;
117
118   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
119
120   rxpd_connection_APPEND_PREPEND_helper (self);
121   llist_insertlist_next (&self->file->rules, &self->tmp_list);
122
123   pth_rwlock_release (&self->file->lock);
124 }
125
126 void
127 rxpd_connection_cmd_REMOVE (struct rxpd_connection* self)
128 {
129   RXPD_FILENAME_REQUIRED;
130
131   char* line;
132   while ((line = rxpd_buffer_readline (&self->in)))
133     {
134       if (*line && strncmp (line, "#EXIT:", sizeof ("#EXIT:")-1))
135         {
136           pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
137           LLIST_FOREACH (&self->file->rules, n)
138             {
139               struct rxpd_rule* rule = (struct rxpd_rule*)n;
140               if (strcmp (rule->string, line) == 0)
141                 {
142                   LList tmp = llist_prev (n);
143                   rxpd_rule_delete (rule);
144                   n = tmp;
145                   rxpd_buffer_printf (&self->out, "#OK:\n");
146                   goto done;
147                 }
148             }
149           rxpd_buffer_printf (&self->out, "#ERROR: line not found\n");
150         done:
151           pth_rwlock_release (&self->file->lock);
152         }
153       else
154         break;  /* exit at empty line, error or whatever */
155     }
156
157   if (rxpd_buffer_state (&self->in) == RXPD_ERROR)
158     rxpd_buffer_printf (&self->out, "#ERROR:\n");
159 }
160
161
162
163 static int
164 rxpd_connection_do_REPLACE (struct rxpd_connection* self)
165 {
166   struct rxpd_rule* rule;
167
168   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
169
170   LLIST_FOREACH (&self->file->rules, n)
171     {
172       rule = (struct rxpd_rule*)n;
173       if (strcmp (rule->string, self->tmp_str) == 0)
174         goto found;
175     }
176   pth_rwlock_release (&self->file->lock);
177   return 0;
178
179  found:
180   llist_insertlist_next (&rule->node, &self->tmp_list);
181   rxpd_rule_delete (rule);
182   pth_rwlock_release (&self->file->lock);
183
184   free (self->tmp_str);
185   self->tmp_str = NULL;
186   return 1;
187 }
188
189 void
190 rxpd_connection_cmd_REPLACE (struct rxpd_connection* self)
191 {
192   RXPD_FILENAME_REQUIRED;
193
194   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
195
196   char* line;
197   while ((line = rxpd_buffer_readline (&self->in)))
198     {
199       if (*line && strncmp (line, "#EXIT:", sizeof ("#EXIT:")-1))
200         {
201           if (self->tmp_str)
202             {
203               struct rxpd_rule* rule;
204               rule = rxpd_rule_new (line);
205               if (rule)
206                 llist_insert_tail (&self->tmp_list, &rule->node);
207               else
208                 rxpd_buffer_printf (&self->out, "#ERROR: illegal rule '%s'\n", line);
209             }
210           else
211             self->tmp_str = rxpd_strdup (line);
212         }
213       else
214         break;  /* exit at empty line, error or whatever */
215     }
216
217   pth_rwlock_release (&self->file->lock);
218
219   if (rxpd_buffer_state (&self->in) == RXPD_ERROR)
220     rxpd_buffer_printf (&self->out, "#ERROR:\n");
221
222   if (rxpd_connection_do_REPLACE (self))
223     rxpd_buffer_printf (&self->out, "#OK:\n");
224   else
225     rxpd_buffer_printf (&self->out, "#ERROR: no rule matching '%s'\n", self->tmp_str);
226 }
227
228 void
229 rxpd_connection_cmd_LOAD (struct rxpd_connection* self)
230 {
231   RXPD_FILENAME_REQUIRED;
232
233   if (rxpd_file_load (self->file))
234     rxpd_buffer_printf (&self->out, "#OK:\n");
235   else
236     rxpd_buffer_printf (&self->out, "#ERROR: loading file '%s'\n", (const char*)self->file->node.key);
237 }
238
239 void
240 rxpd_connection_cmd_SAVE (struct rxpd_connection* self)
241 {
242   RXPD_FILENAME_REQUIRED;
243
244   if (rxpd_file_save (self->file))
245     rxpd_buffer_printf (&self->out, "#OK:\n");
246   else
247     rxpd_buffer_printf (&self->out, "#ERROR: saving file '%s'\n", (const char*)self->file->node.key);
248 }
249
250
251 void
252 rxpd_connection_cmd_DELETE (struct rxpd_connection* self)
253 {
254   RXPD_FILENAME_REQUIRED;
255
256   rxpd_file_delete (self->file);
257   rxpd_buffer_printf (&self->out, "#OK:\n");
258 }
259
260 void
261 rxpd_connection_cmd_FETCH (struct rxpd_connection* self)
262 {
263   RXPD_FILENAME_REQUIRED;
264
265   struct rxpd_base* base = self->socket->base;
266
267   char* line;
268   line = rxpd_buffer_readline (&self->in);
269   if (*line)
270     {
271       /* parse line */
272
273       char* list = strrchr (line, '/');
274       if (!list)
275         goto esyntax;
276
277       *list = '\0';
278       ++ list;
279
280       char* port = strrchr (line, ':');
281       if (!port)
282         goto esyntax;
283
284       *port = '\0';
285       ++ port;
286
287       struct addrinfo* addrs = NULL;
288       int aierr;
289
290       /* resolve peer */
291       aierr = getaddrinfo (line, port, NULL, &addrs);
292       if (aierr)
293         {
294           rxpd_buffer_printf (&self->out, "#ERROR: resolving '%s': %s\n", line, gai_strerror (aierr));
295           return;
296         }
297
298       rxpd_log (base, LOG_INFO, "fetching list '%s' from '%s(%s)' at port '%s' to '%s'\n",
299                 list,
300                 line,
301                 inet_ntoa (((struct sockaddr_in*)addrs->ai_addr)->sin_addr),
302                 port,
303                 self->file->node.key);
304
305       /* establish connection */
306       int fd = socket (addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
307       if (fd == -1 || connect (fd, addrs->ai_addr, addrs->ai_addrlen))
308         {
309           freeaddrinfo (addrs);
310           rxpd_buffer_printf (&self->out, "#ERROR: error connecting '%s': %s\n", line, strerror (errno));
311           return;
312         }
313  
314       freeaddrinfo (addrs);
315
316       /* send DUMP command and fetch data */
317       struct rxpd_buffer in;
318       struct rxpd_buffer out;
319       rxpd_buffer_init (&in, fd);
320       rxpd_buffer_init (&out, fd);
321
322       rxpd_buffer_printf (&out, "DUMP:%s\n", list);
323
324       char* line;
325       while ((line = rxpd_buffer_readline (&in)))
326         {
327           if (*line && strncmp (line, "#ERROR:", sizeof ("#ERROR:")-1))
328             {
329               struct rxpd_rule* rule;
330               rule = rxpd_rule_new (line);
331               if (!rule)
332                 rxpd_die ("rule creation failed on '%s'\n", line);
333
334               llist_insert_tail (&self->tmp_list, &rule->node);
335             }
336           else
337             rxpd_log (base, LOG_INFO, "purge remote side error '%s'\n", line);
338         }
339       rxpd_log (base, LOG_DEBUG, "finished dump\n");
340
341       close (fd);
342
343       pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
344       rxpd_file_rules_delete (self->file);
345       llist_insertlist_next (&self->file->rules, &self->tmp_list);
346       pth_rwlock_release (&self->file->lock);
347
348       rxpd_buffer_printf (&self->out, "#OK:\n");
349     }
350   else
351     {
352     esyntax:
353       rxpd_buffer_printf (&self->out, "#ERROR: syntax error\n");
354     }
355 }
356
357 void
358 rxpd_connection_cmd_DUMP (struct rxpd_connection* self)
359 {
360   RXPD_FILENAME_REQUIRED;
361   rxpd_file_dump (self->file, &self->out);
362 }
363
364
365 static psplay_delete_t
366 walk_LIST (PSplay node, const enum psplay_order_e which, int level, void* data)
367 {
368   (void) level;
369   struct rxpd_file* file = (struct rxpd_file*) node;
370   struct rxpd_connection* conn = (struct rxpd_connection*) data;
371
372   if (which == PSPLAY_INORDER)
373     rxpd_buffer_printf (&conn->out, "%s\n", file->node.key);
374
375   return PSPLAY_CONT;
376 }
377
378
379 void
380 rxpd_connection_cmd_LIST (struct rxpd_connection* self)
381 {
382   struct rxpd_base* base = self->socket->base;
383
384   if (psplay_isempty_root (&base->files))
385     rxpd_buffer_printf (&self->out, "#OK:\n");
386   else
387     psplay_walk (&base->files, NULL, walk_LIST, 0, self);
388 }
389
390 void
391 rxpd_connection_cmd_SHUTDOWN (struct rxpd_connection* self)
392 {
393   struct rxpd_base* base = self->socket->base;
394
395   LLIST_FOREACH (&base->sockets, n)
396     {
397       struct rxpd_socket* socket = (struct rxpd_socket*)n;
398       rxpd_socket_cancel (socket);
399     }
400
401   rxpd_buffer_printf (&self->out, "#OK:\n");
402 }
403
404
405 void
406 rxpd_connection_cmd_VERSION (struct rxpd_connection* self)
407 {
408   rxpd_buffer_printf (&self->out, PACKAGE_STRING "\n#\n"
409                       "# Copyright (C)\n"
410                       "#   2007,               Christian Thaeter <ct@pipapo.org>\n#\n"
411                       "# This is free software.  You may redistribute copies of it under the terms of\n"
412                       "# the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.\n"
413                       "# There is NO WARRANTY, to the extent permitted by law.\n#\n"
414                       "# http://www.pipapo.org/pipawiki/RegexPolicyDaemon\n");
415 }
416
417 void
418 rxpd_connection_cmd_HELP (struct rxpd_connection* self)
419 {
420   //struct rxpd_base* base = self->socket->base;
421   rxpd_buffer_printf (&self->out, "# Available commands:\n#\n");
422 #define RXPD_CMD(cmd, help)     rxpd_buffer_printf (&self->out, "# %s %s.\n", #cmd, help);
423   RXPD_COMMANDS
424 #undef RXPD_CMD
425     rxpd_buffer_printf (&self->out, 
426                         "#\n"
427                         "# general syntax is: 'COMMAND:listname\\n..data..'\n"
428                         "# see http://www.pipapo.org/pipawiki/RegexPolicyDaemon\n");
429 }
430
431 void
432 rxpd_connection_cmd_EXPIRE (struct rxpd_connection* self)
433 {
434   RXPD_FILENAME_REQUIRED;
435
436   struct rxpd_base* base = self->socket->base;
437
438   char* line = rxpd_buffer_readline (&self->in);
439   if (*line)
440     {
441       // TODO strtol, error handling
442       time_t since = time (NULL) - atoi (line);
443       rxpd_log (base, LOG_INFO, "expire all entries in '%s' since %ld\n",
444                 (const char*) self->file->node.key, since);
445
446       pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
447
448       LLIST_FOREACH (&self->file->rules, n)
449         {
450           struct rxpd_rule* rule = (struct rxpd_rule*)n;
451           if (rule->atime != -1 && rule->atime < since)
452             {
453               n = llist_prev (n);
454               rxpd_log (base, LOG_DEBUG, "expiring %ld:%s\n", rule->atime, rule->string);
455               rxpd_buffer_printf (&self->out, "#OK: expiring '%s'\n", rule->string);
456               rxpd_rule_delete (rule);
457             }
458         }
459
460       pth_rwlock_release (&self->file->lock);
461     }
462   else
463     rxpd_buffer_printf (&self->out, "#ERROR: no age given\n");
464 }
465
466 void
467 rxpd_connection_cmd_UPDATE (struct rxpd_connection* self)
468 {
469   RXPD_FILENAME_REQUIRED;
470
471   struct rxpd_base* base = self->socket->base;
472
473   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
474
475   char* line;
476   while ((line = rxpd_buffer_readline (&self->in)))
477     {
478       /* for each line which is a listname */
479       struct rxpd_file* source;
480       source = (struct rxpd_file*) psplay_find (&base->files, line);
481       if (source)
482         {
483           pth_rwlock_acquire (&source->lock, PTH_RWLOCK_RD, FALSE, NULL);
484
485           /* find first line which is in source and self->file, this is our start */
486           LList source_itr = NULL;
487           LList file_itr = NULL;
488           LLIST_FOREACH (&source->rules, n)
489             {
490               struct rxpd_rule* rule_n = (struct rxpd_rule*)n;
491               LLIST_FOREACH (&self->file->rules, m)
492                 {
493                   struct rxpd_rule* rule_m = (struct rxpd_rule*)m;
494                   if (strcmp (rule_n->string, rule_m->string) == 0)
495                     {
496                       source_itr = &rule_n->node;
497                       file_itr = &rule_m->node;
498                       rxpd_log (base, LOG_DEBUG, "starting at '%s'\n", rule_n->string);
499                       goto leave_loop;
500                     }
501                 }
502             }
503         leave_loop:
504           if (!source_itr)
505             {
506               rxpd_log (base, LOG_DEBUG, "no common line found between '%s' and '%s'\n",
507                         (const char*)self->file->node.key, line);
508               pth_rwlock_release (&source->lock);
509               break;
510             }
511
512           /* Now we go for each rule in source which has a atime find the matching rule in self->file and update its atime */
513           /* This algorihm assumes that lists stay ordered (insertions/deletions are supported, but not reordering) */
514           /* advantage is that the cost is O(N+M) instead O(N*M) */
515           LLIST_FORRANGE (source_itr, &source->rules, n)
516             {
517               struct rxpd_rule* rule_n = (struct rxpd_rule*)n;
518               if (rule_n->atime != (time_t)-1)
519                 {
520                   LLIST_FORRANGE (file_itr, &self->file->rules, m)
521                     {
522                       struct rxpd_rule* rule_m = (struct rxpd_rule*)m;
523                       if (strcmp (rule_n->string, rule_m->string) == 0)
524                         {
525                           /* found the rule */
526                           if (rule_m->atime != (time_t)-1 && rule_m->atime < rule_n->atime)
527                             {
528                               rxpd_log (base, LOG_DEBUG,
529                                         "updating atime of rule '%s' from %ld to %ld\n",
530                                         rule_m->string,
531                                         rule_m->atime,
532                                         rule_n->atime
533                                         );
534                               rule_m->atime = rule_n->atime;
535                             }
536
537                           file_itr = llist_next (m);
538                           break;
539                         }
540                     }
541                 }
542             }
543
544           pth_rwlock_release (&source->lock);
545         }
546       else
547         rxpd_buffer_printf (&self->out, "#ERROR: unknown file '%s'\n", line);
548     }
549
550   pth_rwlock_release (&self->file->lock);
551
552   rxpd_buffer_printf (&self->out, "#OK:\n");
553 }
554
555
556 void
557 rxpd_connection_cmd_MERGE (struct rxpd_connection* self)
558 {
559   RXPD_FILENAME_REQUIRED;
560
561   struct rxpd_base* base = self->socket->base;
562
563   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
564
565   char* line;
566   while ((line = rxpd_buffer_readline (&self->in)))
567     {
568       /* for each line which is a listname */
569       struct rxpd_file* source;
570       source = (struct rxpd_file*) psplay_find (&base->files, line);
571       if (source)
572         {
573           pth_rwlock_acquire (&source->lock, PTH_RWLOCK_RD, FALSE, NULL);
574
575           LList source_start = llist_head (&source->rules);
576           LList file_start = llist_head (&self->file->rules);
577           LList source_itr = NULL;
578           LList file_itr = NULL;
579
580           /* find next line which is in source and self->file */
581           while (source_itr != &source->rules)
582             {
583               LLIST_FORRANGE (source_start, &source->rules, n)
584                 {
585                   LLIST_FORRANGE (file_start, &self->file->rules, m)
586                     {
587                       struct rxpd_rule* rule_n = (struct rxpd_rule*)n;
588                       struct rxpd_rule* rule_m = (struct rxpd_rule*)m;
589                       if (strcmp (rule_n->string, rule_m->string) == 0)
590                         {
591                           source_itr = &rule_n->node;
592                           file_itr = &rule_m->node;
593                           goto leave_loop;
594                         }
595                     }
596                 }
597               /* not found, place the itr's at the end of their lists */
598               source_itr = &source->rules;
599               file_itr = &self->file->rules;
600             leave_loop:
601               /* copy all rules from source_start to source_itr before file_itr */
602
603               LLIST_FORRANGE (source_start, source_itr, n)
604                 {
605                   struct rxpd_rule* rule = rxpd_rule_copy ((struct rxpd_rule*)n);
606                   if (!rule)
607                     rxpd_die ("rule copy failed\n");
608
609                   llist_insert_prev (file_itr, &rule->node);
610                 }
611
612               /*iterate for mismatch*/
613               file_start = llist_next (file_itr);
614               source_start = llist_next (source_itr);
615             }
616
617           pth_rwlock_release (&source->lock);
618         }
619       else
620         rxpd_buffer_printf (&self->out, "#ERROR: unknown file '%s'\n", line);
621     }
622
623   pth_rwlock_release (&self->file->lock);
624
625   rxpd_buffer_printf (&self->out, "#OK:\n");
626 }
627
628 /* Template
629 void
630 rxpd_connection_cmd_ (struct rxpd_connection* self)
631 {
632   RXPD_FILENAME_REQUIRED;
633   //struct rxpd_base* base = self->socket->base;
634   rxpd_buffer_printf (&self->out, "#ERROR: Unimplemented\n");
635 }
636 */