make LIST walking race/lock free
[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
34 static char*
35 rxpd_connection_do_CHECK (struct rxpd_connection* self, char* line, struct rxpd_file* file)
36 {
37   char* ret = NULL;
38
39   // TODO implement RXPD_YIELD_EVERY
40   pth_rwlock_acquire (&file->lock, PTH_RWLOCK_RD, FALSE, NULL);
41   LLIST_FOREACH (&file->rules, n)
42     {
43       struct rxpd_rule* rule = (struct rxpd_rule*)n;
44       if (rule->string[0] != '#')
45         {
46           if (regexec (&rule->rx, line, 0, NULL, 0) == 0)
47             {
48               if (rule->atime != (time_t) -1)
49                 time (&rule->atime);
50
51               ret = rule->string;
52
53               if (*ret == '>')
54                 {
55                   struct rxpd_base* base = self->socket->base;
56
57                   size_t sublen =
58                     (size_t)(strchr (&rule->string[1], ':') - &rule->string[1]);
59
60                   char* subname =
61                     alloca (strlen (file->node.key) + sublen + 1);
62                   *subname = '\0';
63
64                   strcat (subname, file->node.key);
65                   strncat (subname, &rule->string[1], sublen);
66
67                   struct rxpd_file* subfile = (struct rxpd_file*) psplay_find (&base->files, subname);
68                   if (!subfile)
69                     {
70                       subfile = rxpd_file_new (base, subname);
71                       if (!subfile)
72                         {
73                           rxpd_buffer_printf (&self->out, "#ERROR: illegal filename\n");
74                           break;
75                         }
76                       if (rxpd_file_load (subfile))
77                         rxpd_log (base, LOG_INFO, "autoloaded '%s'\n", subname);
78                       else
79                         rxpd_log (base, LOG_WARNING, "could not autoload '%s'\n", subname);
80                     }
81
82                   rxpd_log (base, LOG_DEBUG, "going to sublist '%s'\n", subname);
83                   ret = rxpd_connection_do_CHECK (self, line, subfile);
84                 }
85
86               if (ret)
87                 break;
88             }
89         }
90     }
91   pth_rwlock_release (&file->lock);
92   return ret;
93 }
94
95
96 void
97 rxpd_connection_cmd_CHECK (struct rxpd_connection* self)
98 {
99   RXPD_FILENAME_REQUIRED;
100
101   char* line;
102   while ((line = rxpd_buffer_readline (&self->in)))
103     {
104       if (*line == '\0')
105         {
106           rxpd_buffer_printf (&self->out, "#OK:\n");
107         }
108       else
109         {
110           char* match = rxpd_connection_do_CHECK (self, line, self->file);
111
112           if (match)
113             rxpd_buffer_printf (&self->out, "%s\n", match);
114         }
115     }
116
117   if (rxpd_buffer_state (&self->in) == RXPD_ERROR)
118     rxpd_buffer_printf (&self->out, "#ERROR:\n");
119 }
120
121
122 static void
123 rxpd_connection_APPEND_PREPEND_helper (struct rxpd_connection* self)
124 {
125   char* line;
126
127   while ((line = rxpd_buffer_readline (&self->in)))
128     {
129       if (*line && !RXPD_PREFIXCMP (line, "!EXIT"))
130         {
131           struct rxpd_rule* rule;
132
133           rule = rxpd_rule_new (line, self->socket->base);
134           llist_insert_tail (&self->tmp_list, &rule->node);
135         }
136       else
137         break;  /* exit at empty line, error or whatever */
138     }
139
140   if (rxpd_buffer_state (&self->in) == RXPD_ERROR)
141     rxpd_buffer_printf (&self->out, "#ERROR:\n");
142   else
143     rxpd_buffer_printf (&self->out, "#OK:\n");
144 }
145
146 void
147 rxpd_connection_cmd_APPEND (struct rxpd_connection* self)
148 {
149   RXPD_FILENAME_REQUIRED;
150
151   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
152
153   rxpd_connection_APPEND_PREPEND_helper (self);
154   llist_insertlist_prev (&self->file->rules, &self->tmp_list);
155
156   pth_rwlock_release (&self->file->lock);
157 }
158
159 void
160 rxpd_connection_cmd_PREPEND (struct rxpd_connection* self)
161 {
162   RXPD_FILENAME_REQUIRED;
163
164   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
165
166   rxpd_connection_APPEND_PREPEND_helper (self);
167   llist_insertlist_next (&self->file->rules, &self->tmp_list);
168
169   pth_rwlock_release (&self->file->lock);
170 }
171
172 void
173 rxpd_connection_cmd_REMOVE (struct rxpd_connection* self)
174 {
175   RXPD_FILENAME_REQUIRED;
176
177   char* line;
178   while ((line = rxpd_buffer_readline (&self->in)))
179     {
180       if (*line && !RXPD_PREFIXCMP (line, "!EXIT"))
181         {
182           pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
183           LLIST_FOREACH (&self->file->rules, n)
184             {
185               struct rxpd_rule* rule = (struct rxpd_rule*)n;
186               if (strcmp (rule->string, line) == 0)
187                 {
188                   LList tmp = llist_prev (n);
189                   rxpd_rule_delete (rule);
190                   n = tmp;
191                   rxpd_buffer_printf (&self->out, "#OK:\n");
192                   goto done;
193                 }
194             }
195           rxpd_buffer_printf (&self->out, "#ERROR: line not found\n");
196         done:
197           pth_rwlock_release (&self->file->lock);
198         }
199       else
200         break;  /* exit at empty line, error or whatever */
201     }
202
203   if (rxpd_buffer_state (&self->in) == RXPD_ERROR)
204     rxpd_buffer_printf (&self->out, "#ERROR:\n");
205 }
206
207
208
209 static int
210 rxpd_connection_do_REPLACE (struct rxpd_connection* self)
211 {
212   struct rxpd_rule* rule;
213
214   LLIST_FOREACH (&self->file->rules, n)
215     {
216       rule = (struct rxpd_rule*)n;
217       if (strcmp (rule->string, self->tmp_str) == 0)
218         goto found;
219     }
220   return 0;
221
222  found:
223   llist_insertlist_next (&rule->node, &self->tmp_list);
224   rxpd_rule_delete (rule);
225
226   free (self->tmp_str);
227   self->tmp_str = NULL;
228   return 1;
229 }
230
231 void
232 rxpd_connection_cmd_REPLACE (struct rxpd_connection* self)
233 {
234   RXPD_FILENAME_REQUIRED;
235
236   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
237
238   char* line;
239   while ((line = rxpd_buffer_readline (&self->in)))
240     {
241       if (*line && !RXPD_PREFIXCMP (line, "!EXIT"))
242         {
243           if (self->tmp_str)
244             {
245               struct rxpd_rule* rule;
246               rule = rxpd_rule_new (line, self->socket->base);
247               if (rule)
248                 llist_insert_tail (&self->tmp_list, &rule->node);
249               else
250                 rxpd_buffer_printf (&self->out, "#ERROR: illegal rule '%s'\n", line);
251             }
252           else
253             self->tmp_str = rxpd_strdup (line);
254         }
255       else
256         break;  /* exit at empty line, error or whatever */
257     }
258
259   if (rxpd_buffer_state (&self->in) == RXPD_ERROR)
260     rxpd_buffer_printf (&self->out, "#ERROR:\n");
261
262   if (rxpd_connection_do_REPLACE (self))
263     rxpd_buffer_printf (&self->out, "#OK:\n");
264   else
265     rxpd_buffer_printf (&self->out, "#ERROR: no rule matching '%s'\n", self->tmp_str);
266
267   pth_rwlock_release (&self->file->lock);
268 }
269
270 void
271 rxpd_connection_cmd_LOAD (struct rxpd_connection* self)
272 {
273   RXPD_FILENAME_REQUIRED;
274
275   if (rxpd_file_load (self->file))
276     rxpd_buffer_printf (&self->out, "#OK:\n");
277   else
278     rxpd_buffer_printf (&self->out, "#ERROR: loading file '%s'\n", (const char*)self->file->node.key);
279 }
280
281 void
282 rxpd_connection_cmd_SAVE (struct rxpd_connection* self)
283 {
284   RXPD_FILENAME_REQUIRED;
285
286   if (rxpd_file_save (self->file, 1))
287     rxpd_buffer_printf (&self->out, "#OK:\n");
288   else
289     rxpd_buffer_printf (&self->out, "#ERROR: saving file '%s'\n", (const char*)self->file->node.key);
290 }
291
292
293 void
294 rxpd_connection_cmd_CLEAR (struct rxpd_connection* self)
295 {
296   RXPD_FILENAME_REQUIRED;
297
298   rxpd_file_rules_delete (self->file);
299   rxpd_buffer_printf (&self->out, "#OK:\n");
300 }
301
302 void
303 rxpd_connection_cmd_DELETE (struct rxpd_connection* self)
304 {
305   RXPD_FILENAME_REQUIRED;
306
307   rxpd_file_delete (self->file);
308   rxpd_buffer_printf (&self->out, "#OK:\n");
309 }
310
311 void
312 rxpd_connection_cmd_FETCH (struct rxpd_connection* self)
313 {
314   RXPD_FILENAME_REQUIRED;
315
316   struct rxpd_base* base = self->socket->base;
317
318   char* line;
319   line = rxpd_buffer_readline (&self->in);
320   if (*line)
321     {
322       /* parse line */
323       char* list = strrchr (line, ':');
324       if (!list)
325         goto esyntax;
326
327       *list = '\0';
328       ++ list;
329
330       char* port = strrchr (line, ':');
331       if (!port)
332         goto esyntax;
333
334       *port = '\0';
335       ++ port;
336
337       struct addrinfo* addrs = NULL;
338       int aierr;
339
340       /* resolve peer */
341       aierr = getaddrinfo (line, port, NULL, &addrs);
342       if (aierr)
343         {
344           rxpd_buffer_printf (&self->out, "#ERROR: resolving '%s': %s\n", line, gai_strerror (aierr));
345           return;
346         }
347
348       rxpd_log (base, LOG_INFO, "fetching list '%s' from '%s(%s)' at port '%s' to '%s'\n",
349                 list,
350                 line,
351                 inet_ntoa (((struct sockaddr_in*)addrs->ai_addr)->sin_addr),
352                 port,
353                 self->file->node.key);
354
355       /* establish connection */
356       int fd = socket (addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
357       if (fd == -1 || connect (fd, addrs->ai_addr, addrs->ai_addrlen))
358         {
359           freeaddrinfo (addrs);
360           rxpd_buffer_printf (&self->out, "#ERROR: error connecting '%s': %s\n", line, strerror (errno));
361           return;
362         }
363  
364       freeaddrinfo (addrs);
365
366       /* send DUMP command and fetch data */
367       struct rxpd_buffer in;
368       struct rxpd_buffer out;
369       rxpd_buffer_init (&in, fd);
370       rxpd_buffer_init (&out, fd);
371
372       rxpd_buffer_printf (&out, "DUMP:%s\n", list);
373
374       char* line;
375       while ((line = rxpd_buffer_readline (&in)))
376         {
377           if (*line && !RXPD_PREFIXCMP (line, "!EXIT"))
378             {
379               struct rxpd_rule* rule;
380               rule = rxpd_rule_new (line, base);
381               if (!rule)
382                 rxpd_die ("rule creation failed on '%s'\n", line);
383
384               llist_insert_tail (&self->tmp_list, &rule->node);
385             }
386           else
387             rxpd_log (base, LOG_INFO, "purge remote side error '%s'\n", line);
388         }
389       rxpd_log (base, LOG_DEBUG, "finished dump\n");
390
391       close (fd);
392
393       pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
394       rxpd_file_rules_delete (self->file);
395       llist_insertlist_next (&self->file->rules, &self->tmp_list);
396       pth_rwlock_release (&self->file->lock);
397
398       rxpd_buffer_printf (&self->out, "#OK:\n");
399     }
400   else
401     {
402     esyntax:
403       rxpd_buffer_printf (&self->out, "#ERROR: syntax error\n");
404     }
405 }
406
407 void
408 rxpd_connection_cmd_DUMP (struct rxpd_connection* self)
409 {
410   RXPD_FILENAME_REQUIRED;
411   rxpd_file_dump (self->file, &self->out);
412 }
413
414
415 /*
416   build a temporary list to make psplay walking lockfree under pth
417 */
418 struct list_record
419 {
420   llist node;
421   char* name;
422 };
423
424 static psplay_delete_t
425 walk_LIST (PSplay node, const enum psplay_order_e which, int level, void* data)
426 {
427   (void) level;
428   struct rxpd_file* file = (struct rxpd_file*) node;
429   struct rxpd_connection* conn = (struct rxpd_connection*) data;
430
431   if (which == PSPLAY_INORDER)
432     {
433       struct list_record* rec = rxpd_malloc (sizeof (struct list_record));
434       rec->name = rxpd_strdup (file->node.key);
435       llist_init (&rec->node);
436       llist_insert_tail (&conn->tmp_list, &rec->node);
437     }
438   return PSPLAY_CONT;
439 }
440
441
442 void
443 rxpd_connection_cmd_LIST (struct rxpd_connection* self)
444 {
445   struct rxpd_base* base = self->socket->base;
446
447   if (psplay_isempty_root (&base->files))
448     rxpd_buffer_printf (&self->out, "#OK:\n");
449   else
450     {
451       psplay_walk (&base->files, NULL, walk_LIST, 0, self);
452
453       LLIST_WHILE_HEAD (&self->tmp_list, n)
454         {
455           struct list_record* rec = (struct list_record*)n;
456           rxpd_buffer_printf (&self->out, "%s\n", rec->name);
457           llist_unlink (n);
458           free (rec->name);
459           free (n);
460         }
461     }
462 }
463
464 void
465 rxpd_connection_cmd_SHUTDOWN (struct rxpd_connection* self)
466 {
467   /* not pth_raise, we don't want to schedule here! */
468   raise (SIGINT);
469   rxpd_buffer_printf (&self->out, "#OK:\n");
470 }
471
472
473 void
474 rxpd_connection_cmd_VERSION (struct rxpd_connection* self)
475 {
476   rxpd_buffer_printf (&self->out, PACKAGE_STRING "\n#\n"
477                       "# Copyright (C)\n"
478                       "#   2007,               Christian Thaeter <ct@pipapo.org>\n#\n"
479                       "# This is free software.  You may redistribute copies of it under the terms of\n"
480                       "# the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.\n"
481                       "# There is NO WARRANTY, to the extent permitted by law.\n#\n"
482                       "# http://www.pipapo.org/pipawiki/RegexPolicyDaemon\n");
483 }
484
485 void
486 rxpd_connection_cmd_HELP (struct rxpd_connection* self)
487 {
488   rxpd_buffer_printf (&self->out, "# Available commands:\n#\n");
489 #define RXPD_CMD(cmd, help)     rxpd_buffer_printf (&self->out, "# %s %s.\n", #cmd, help);
490   RXPD_COMMANDS
491 #undef RXPD_CMD
492     rxpd_buffer_printf (&self->out, 
493                         "#\n"
494                         "# general syntax is: 'COMMAND:listname\\n..data..'\n"
495                         "# see http://www.pipapo.org/pipawiki/RegexPolicyDaemon\n");
496 }
497
498 void
499 rxpd_connection_cmd_EXPIRE (struct rxpd_connection* self)
500 {
501   RXPD_FILENAME_REQUIRED;
502
503   struct rxpd_base* base = self->socket->base;
504
505   char* line = rxpd_buffer_readline (&self->in);
506   if (*line)
507     {
508       // TODO strtol, error handling
509       time_t since = time (NULL) - atoi (line);
510       rxpd_log (base, LOG_INFO, "expire all entries in '%s' since %ld\n",
511                 (const char*) self->file->node.key, since);
512
513       pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
514
515       LLIST_FOREACH (&self->file->rules, n)
516         {
517           struct rxpd_rule* rule = (struct rxpd_rule*)n;
518           if (rule->atime != -1 && rule->atime < since)
519             {
520               rxpd_log (base, LOG_DEBUG, "expiring %ld:%s\n", rule->atime, rule->string);
521               rxpd_buffer_printf (&self->out, "#OK: expiring '%s'\n", rule->string);
522               if (!rxpd_rule_comment (rule, "EXPIRED"))
523                 rxpd_die ("couldn't comment rule out");
524             }
525         }
526
527       pth_rwlock_release (&self->file->lock);
528     }
529   else
530     rxpd_buffer_printf (&self->out, "#ERROR: no age given\n");
531 }
532
533 void
534 rxpd_connection_cmd_UPDATE (struct rxpd_connection* self)
535 {
536   RXPD_FILENAME_REQUIRED;
537
538   struct rxpd_base* base = self->socket->base;
539
540   pth_mutex_acquire (&base->nest_lock, 0, NULL);
541   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
542
543   char* line;
544   while ((line = rxpd_buffer_readline (&self->in)))
545     {
546       /* for each line which is a listname */
547       struct rxpd_file* source;
548       source = (struct rxpd_file*) psplay_find (&base->files, line);
549       if (source)
550         {
551           pth_rwlock_acquire (&source->lock, PTH_RWLOCK_RD, FALSE, NULL);
552
553           /* find first line which is in source and self->file, this is our start */
554           LList source_itr = NULL;
555           LList file_itr = NULL;
556           LLIST_FOREACH (&source->rules, n)
557             {
558               struct rxpd_rule* rule_n = (struct rxpd_rule*)n;
559               LLIST_FOREACH (&self->file->rules, m)
560                 {
561                   struct rxpd_rule* rule_m = (struct rxpd_rule*)m;
562                   if (strcmp (rule_n->string, rule_m->string) == 0)
563                     {
564                       source_itr = &rule_n->node;
565                       file_itr = &rule_m->node;
566                       rxpd_log (base, LOG_DEBUG, "starting at '%s'\n", rule_n->string);
567                       goto leave_loop;
568                     }
569                 }
570             }
571         leave_loop:
572           if (!source_itr)
573             {
574               rxpd_log (base, LOG_DEBUG, "no common line found between '%s' and '%s'\n",
575                         (const char*)self->file->node.key, line);
576               pth_rwlock_release (&source->lock);
577               break;
578             }
579
580           /* Now we go for each rule in source which has a atime find the matching rule in self->file and update its atime */
581           /* This algorihm assumes that lists stay ordered (insertions/deletions are supported, but not reordering) */
582           /* advantage is that the algorithm has far less complexity */
583           LLIST_FORRANGE (source_itr, &source->rules, n)
584             {
585               struct rxpd_rule* rule_n = (struct rxpd_rule*)n;
586               if (rule_n->atime != (time_t)-1)
587                 {
588                   LLIST_FORRANGE (file_itr, &self->file->rules, m)
589                     {
590                       struct rxpd_rule* rule_m = (struct rxpd_rule*)m;
591                       if (strcmp (rule_n->string, rule_m->string) == 0)
592                         {
593                           /* found the rule */
594                           if (rule_m->atime != (time_t)-1 && rule_m->atime < rule_n->atime)
595                             {
596                               rxpd_log (base, LOG_DEBUG,
597                                         "updating atime of rule '%s' from %ld to %ld\n",
598                                         rule_m->string,
599                                         rule_m->atime,
600                                         rule_n->atime
601                                         );
602                               rule_m->atime = rule_n->atime;
603                             }
604
605                           file_itr = llist_next (m);
606                           break;
607                         }
608                     }
609                 }
610             }
611
612           pth_rwlock_release (&source->lock);
613         }
614       else
615         rxpd_buffer_printf (&self->out, "#ERROR: unknown file '%s'\n", line);
616     }
617
618   pth_rwlock_release (&self->file->lock);
619   pth_mutex_release (&base->nest_lock);
620
621   rxpd_buffer_printf (&self->out, "#OK:\n");
622 }
623
624
625 void
626 rxpd_connection_cmd_MERGE (struct rxpd_connection* self)
627 {
628   RXPD_FILENAME_REQUIRED;
629
630   struct rxpd_base* base = self->socket->base;
631
632   pth_mutex_acquire (&base->nest_lock, 0, NULL);
633   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
634
635   char* line;
636   while ((line = rxpd_buffer_readline (&self->in)))
637     {
638       /* for each line which is a listname */
639       struct rxpd_file* source;
640       source = (struct rxpd_file*) psplay_find (&base->files, line);
641       if (source)
642         {
643           pth_rwlock_acquire (&source->lock, PTH_RWLOCK_RD, FALSE, NULL);
644
645           LList source_start = llist_head (&source->rules);
646           LList file_start = llist_head (&self->file->rules);
647           LList source_itr = NULL;
648           LList file_itr = NULL;
649
650           /* find next line which is in source and self->file */
651           while (source_itr != &source->rules)
652             {
653               LLIST_FORRANGE (source_start, &source->rules, n)
654                 {
655                   LLIST_FORRANGE (file_start, &self->file->rules, m)
656                     {
657                       struct rxpd_rule* rule_n = (struct rxpd_rule*)n;
658                       struct rxpd_rule* rule_m = (struct rxpd_rule*)m;
659                       if (strcmp (rule_n->string, rule_m->string) == 0)
660                         {
661                           source_itr = &rule_n->node;
662                           file_itr = &rule_m->node;
663                           goto leave_loop;
664                         }
665                     }
666                 }
667               /* not found, place the itr's at the end of their lists */
668               source_itr = &source->rules;
669               file_itr = &self->file->rules;
670             leave_loop:
671               /* copy all rules from source_start to source_itr before file_itr */
672
673               LLIST_FORRANGE (source_start, source_itr, n)
674                 {
675                   struct rxpd_rule* rule = rxpd_rule_copy ((struct rxpd_rule*)n);
676                   if (!rule)
677                     rxpd_die ("rule copy failed\n");
678
679                   llist_insert_prev (file_itr, &rule->node);
680                 }
681
682               /*iterate for mismatch*/
683               file_start = llist_next (file_itr);
684               source_start = llist_next (source_itr);
685             }
686
687           pth_rwlock_release (&source->lock);
688         }
689       else
690         rxpd_buffer_printf (&self->out, "#ERROR: unknown file '%s'\n", line);
691     }
692
693   pth_rwlock_release (&self->file->lock);
694   pth_mutex_release (&base->nest_lock);
695
696   rxpd_buffer_printf (&self->out, "#OK:\n");
697 }
698
699 void
700 rxpd_connection_cmd_FILTER (struct rxpd_connection* self)
701 {
702   RXPD_FILENAME_REQUIRED;
703
704   /* Filter processes the target list by checking its rules against other lists */
705   /* The outcome of this check is used to decide how to process with a rule */
706   /* DELETE: delete rule from list */
707   /* ACTIVATE: try to reactivate a rule which was commented out */
708   /* any other: comment the rule out by using 'any other' as hint*/
709
710   struct rxpd_base* base = self->socket->base;
711
712   pth_mutex_acquire (&base->nest_lock, 0, NULL);
713   pth_rwlock_acquire (&self->file->lock, PTH_RWLOCK_RW, FALSE, NULL);
714
715   char* line;
716   while ((line = rxpd_buffer_readline (&self->in)))
717     {
718       if (*line && !RXPD_PREFIXCMP (line, "!EXIT"))
719         {
720
721           /* for each line which is a listname */
722           struct rxpd_file* source;
723           source = (struct rxpd_file*) psplay_find (&base->files, line);
724           if (source)
725             {
726               pth_rwlock_acquire (&source->lock, PTH_RWLOCK_RD, FALSE, NULL);
727
728               LLIST_FOREACH (&self->file->rules, n)
729                 {
730                   LLIST_FOREACH (&source->rules, m)
731                     {
732                       struct rxpd_rule* rule_n = (struct rxpd_rule*)n;
733                       struct rxpd_rule* rule_m = (struct rxpd_rule*)m;
734                       if (rule_m->string[0] != '#')
735                         {
736                           if (regexec (&rule_m->rx, rule_n->string, 0, NULL, 0) == 0)
737                             {
738                               if (rule_m->atime != (time_t) -1)
739                                 time (&rule_m->atime);
740
741                               if (RXPD_PREFIXCMP (rule_m->string, "DELETE:"))
742                                 {
743                                   n = llist_prev (n);
744                                   rxpd_log (base, LOG_DEBUG, "deleting rule '%s' from list '%s'\n",
745                                             rule_n->string, (const char*) self->file->node.key);
746                                   rxpd_rule_delete (rule_n);
747                                 }
748                               else if (RXPD_PREFIXCMP (rule_m->string, "ACTIVATE:"))
749                                 {
750                                   if (!rxpd_rule_activate (rule_n))
751                                     rxpd_buffer_printf (&self->out, "#ERROR: cant activate rule '%s'\n", rule_n->string);
752                                   else
753                                     rxpd_log (base, LOG_DEBUG, "activated rule '%s' in list '%s'\n",
754                                               rule_n->string, (const char*) self->file->node.key);
755                                 }
756                               else
757                                 {
758                                   char* c = strndupa (rule_m->string, strchr (rule_m->string, ':') - rule_m->string);
759                                   rxpd_log (base, LOG_DEBUG, "commenting rule '%s' as '%s', in list '%s'\n",
760                                             rule_n->string, c, (const char*) self->file->node.key);
761
762                                   rxpd_rule_comment (rule_n, c);
763                                 }
764                             }
765                         }
766                     }
767                 }
768               pth_rwlock_release (&source->lock);
769             }
770           else
771             rxpd_buffer_printf (&self->out, "#ERROR: unknown file '%s'\n", line);
772         }
773       else
774         break;  /* exit at empty line, error or whatever */
775     }
776
777   pth_rwlock_release (&self->file->lock);
778   pth_mutex_release (&base->nest_lock);
779
780   rxpd_buffer_printf (&self->out, "#OK:\n");
781 }
782
783 /* Template
784 void
785 rxpd_connection_cmd_ (struct rxpd_connection* self)
786 {
787   RXPD_FILENAME_REQUIRED;
788   //struct rxpd_base* base = self->socket->base;
789   rxpd_buffer_printf (&self->out, "#ERROR: Unimplemented\n");
790 }
791 */