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