e86175d6d831cd259e91250b7d2dd9f9d0048e74
[freeradius.git] / src / modules / rlm_linelog / rlm_linelog.c
1 /*
2  * rlm_linelog.c
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2004,2006  The FreeRADIUS server project
21  * Copyright 2004  Alan DeKok <aland@freeradius.org>
22  */
23
24 #include <freeradius-devel/ident.h>
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29
30 #ifdef HAVE_FCNTL_H
31 #include <fcntl.h>
32 #endif
33
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif
37
38 #ifdef HAVE_GRP_H
39 #include <grp.h>
40 #endif
41
42 #ifdef HAVE_SYSLOG_H
43 #include <syslog.h>
44
45 #ifndef LOG_INFO
46 #define LOG_INFO (0)
47 #endif
48 #endif
49
50 /*
51  *      Syslog facilities from main/mainconfig.c
52  */
53 extern const FR_NAME_NUMBER syslog_str2fac[];
54
55 /*
56  *      Define a structure for our module configuration.
57  */
58 typedef struct rlm_linelog_t {
59         CONF_SECTION    *cs;
60         char            *filename;
61         char            *syslog_facility;
62         int             facility;
63         int             permissions;
64         char            *group;
65         char            *line;
66         char            *reference;
67 } rlm_linelog_t;
68
69 /*
70  *      A mapping of configuration file names to internal variables.
71  *
72  *      Note that the string is dynamically allocated, so it MUST
73  *      be freed.  When the configuration file parse re-reads the string,
74  *      it free's the old one, and strdup's the new one, placing the pointer
75  *      to the strdup'd string into 'config.string'.  This gets around
76  *      buffer over-flows.
77  */
78 static const CONF_PARSER module_config[] = {
79         { "filename",  PW_TYPE_STRING_PTR,
80           offsetof(rlm_linelog_t,filename), NULL,  NULL},
81         { "syslog_facility",  PW_TYPE_STRING_PTR,
82           offsetof(rlm_linelog_t,syslog_facility), NULL,  NULL},
83         { "permissions",  PW_TYPE_INTEGER,
84           offsetof(rlm_linelog_t,permissions), NULL,  "0600"},
85         { "group",  PW_TYPE_STRING_PTR,
86           offsetof(rlm_linelog_t,group), NULL,  NULL},
87         { "format",  PW_TYPE_STRING_PTR,
88           offsetof(rlm_linelog_t,line), NULL,  NULL},
89         { "reference",  PW_TYPE_STRING_PTR,
90           offsetof(rlm_linelog_t,reference), NULL,  NULL},
91         { NULL, -1, 0, NULL, NULL }             /* end the list */
92 };
93
94
95 static int linelog_detach(void *instance)
96 {
97         rlm_linelog_t *inst = instance;
98
99         free(inst);
100         return 0;
101 }
102
103 /*
104  *      Instantiate the module.
105  */
106 static int linelog_instantiate(CONF_SECTION *conf, void **instance)
107 {
108         rlm_linelog_t *inst;
109
110         /*
111          *      Set up a storage area for instance data
112          */
113         inst = rad_malloc(sizeof(*inst));
114         memset(inst, 0, sizeof(*inst));
115
116         /*
117          *      If the configuration parameters can't be parsed, then
118          *      fail.
119          */
120         if (cf_section_parse(conf, inst, module_config) < 0) {
121                 linelog_detach(inst);
122                 return -1;
123         }
124
125         if (!inst->filename) {
126                 radlog(L_ERR, "rlm_linelog: Must specify an output filename");
127                 linelog_detach(inst);
128                 return -1;
129         }
130
131 #ifndef HAVE_SYSLOG_H
132         if (strcmp(inst->filename, "syslog") == 0) {
133                 radlog(L_ERR, "rlm_linelog: Syslog output is not supported");
134                 linelog_detach(inst);
135                 return -1;
136         }
137 #else
138         inst->facility = 0;
139
140         if (inst->syslog_facility) {
141                 inst->facility = fr_str2int(syslog_str2fac, inst->syslog_facility, -1);
142                 if (inst->facility < 0) {
143                         radlog(L_ERR, "rlm_linelog: Bad syslog facility '%s'", inst->syslog_facility);
144                         linelog_detach(inst);
145                         return -1;
146                 }
147         }
148
149         inst->facility |= LOG_INFO;
150 #endif
151
152         if (!inst->line) {
153                 radlog(L_ERR, "rlm_linelog: Must specify a log format");
154                 linelog_detach(inst);
155                 return -1;
156         }
157
158         inst->cs = conf;
159         *instance = inst;
160
161         return 0;
162 }
163
164
165 /*
166  *      Escape unprintable characters.
167  */
168 static size_t linelog_escape_func(char *out, size_t outlen, const char *in)
169 {
170         int len = 0;
171
172         if (outlen == 0) return 0;
173         if (outlen == 1) {
174                 *out = '\0';
175                 return 0;
176         }
177
178         while (in[0]) {
179                 if (in[0] >= ' ') {
180                         if (in[0] == '\\') {
181                                 if (outlen <= 2) break;
182                                 outlen--;
183                                 *out++ = '\\';
184                                 len++;
185                         }
186
187                         outlen--;
188                         if (outlen == 1) break;
189                         *out++ = *in++;
190                         len++;
191                         continue;
192                 }
193
194                 switch (in[0]) {
195                 case '\n':
196                         if (outlen <= 2) break;
197                         *out++ = '\\';
198                         *out++ = 'n';
199                         in++;
200                         len += 2;
201                         break;
202
203                 case '\r':
204                         if (outlen <= 2) break;
205                         *out++ = '\\';
206                         *out++ = 'r';
207                         in++;
208                         len += 2;
209                         break;
210
211                 default:
212                         if (outlen <= 4) break;
213                         snprintf(out, outlen,  "\\%03o", *in);
214                         in++;
215                         out += 4;
216                         outlen -= 4;
217                         len += 4;
218                         break;
219                 }
220         }
221
222         *out = '\0';
223         return len;
224 }
225
226 static int do_linelog(void *instance, REQUEST *request)
227 {
228         int fd = -1;
229         char buffer[4096];
230         char *p;
231         char line[1024];
232         rlm_linelog_t *inst = (rlm_linelog_t*) instance;
233         const char *value = inst->line;
234
235 #ifdef HAVE_GRP_H
236         gid_t gid;
237         struct group *grp;
238         char *endptr;
239 #endif
240
241         if (inst->reference) {
242                 CONF_ITEM *ci;
243                 CONF_PAIR *cp;
244
245                 radius_xlat(line + 1, sizeof(line) - 2, inst->reference,
246                             request, linelog_escape_func);
247                 line[0] = '.';  /* force to be in current section */
248
249                 /*
250                  *      Don't allow it to go back up
251                  */
252                 if (line[1] == '.') goto do_log;
253
254                 ci = cf_reference_item(NULL, inst->cs, line);
255                 if (!ci) {
256                         RDEBUG2("No such entry \"%s\"", line);
257                         return RLM_MODULE_NOOP;
258                 }
259
260                 if (!cf_item_is_pair(ci)) {
261                         RDEBUG2("Entry \"%s\" is not a variable assignment ", line);
262                         goto do_log;
263                 }
264
265                 cp = cf_itemtopair(ci);
266                 value = cf_pair_value(cp);
267                 if (!value) {
268                         RDEBUG2("Entry \"%s\" has no value", line);
269                         goto do_log;
270                 }
271
272                 /*
273                  *      Value exists, but is empty.  Don't log anything.
274                  */
275                 if (!*value) return RLM_MODULE_OK;
276         }
277
278  do_log:
279         /*
280          *      FIXME: Check length.
281          */
282         if (strcmp(inst->filename, "syslog") != 0) {
283                 radius_xlat(buffer, sizeof(buffer), inst->filename, request,
284                             NULL);
285                 
286                 /* check path and eventually create subdirs */
287                 p = strrchr(buffer,'/');
288                 if (p) {
289                         *p = '\0';
290                         if (rad_mkdir(buffer, 0700) < 0) {
291                                 radlog_request(L_ERR, 0, request, "rlm_linelog: Failed to create directory %s: %s", buffer, strerror(errno));
292                                 return RLM_MODULE_FAIL;
293                         }
294                         *p = '/';
295                 }
296
297                 fd = open(buffer, O_WRONLY | O_APPEND | O_CREAT, inst->permissions);
298                 if (fd == -1) {
299                         radlog(L_ERR, "rlm_linelog: Failed to open %s: %s",
300                                buffer, strerror(errno));
301                         return RLM_MODULE_FAIL;
302                 }
303         }
304
305 #ifdef HAVE_GRP_H
306         if (inst->group != NULL) {
307                 gid = strtol(inst->group, &endptr, 10);
308                 if (*endptr != '\0') {
309                         grp = getgrnam(inst->group);
310                         if (grp == NULL) {
311                                 RDEBUG2("Unable to find system group \"%s\"", inst->group);
312                                 goto skip_group;
313                         }
314                         gid = grp->gr_gid;
315                 }
316
317                 if (chown(buffer, -1, gid) == -1) {
318                         RDEBUG2("Unable to change system group of \"%s\"", buffer);
319                 }
320         }
321
322  skip_group:
323 #endif
324
325         /*
326          *      FIXME: Check length.
327          */
328         radius_xlat(line, sizeof(line) - 1, value, request,
329                     linelog_escape_func);
330
331         if (fd >= 0) {
332                 strcat(line, "\n");
333                 
334                 write(fd, line, strlen(line));
335                 close(fd);
336
337 #ifdef HAVE_SYSLOG_H
338         } else {
339                 syslog(inst->facility, "%s", line);
340 #endif
341         }
342
343         return RLM_MODULE_OK;
344 }
345
346
347 /*
348  *      Externally visible module definition.
349  */
350 module_t rlm_linelog = {
351         RLM_MODULE_INIT,
352         "linelog",
353         RLM_TYPE_CHECK_CONFIG_SAFE,     /* type */
354         linelog_instantiate,            /* instantiation */
355         linelog_detach,                 /* detach */
356         {
357                 do_linelog,     /* authentication */
358                 do_linelog,     /* authorization */
359                 do_linelog,     /* preaccounting */
360                 do_linelog,     /* accounting */
361                 NULL,           /* checksimul */
362                 do_linelog,     /* pre-proxy */
363                 do_linelog,     /* post-proxy */
364                 do_linelog      /* post-auth */
365 #ifdef WITH_COA
366                 , do_linelog,   /* recv-coa */
367                 do_linelog      /* send-coa */
368 #endif
369         },
370 };