a78902c1c65e300dbfde10938a27c843e83bafde
[freeradius.git] / src / modules / rlm_detail / rlm_detail.c
1 /*
2  * rlm_detail.c accounting:    Write the "detail" files.
3  *
4  * Version:     $Id$
5  *
6  */
7
8 static const char rcsid[] = "$Id$";
9
10 #include        "autoconf.h"
11
12 #include        <sys/stat.h>
13
14 #include        <stdlib.h>
15 #include        <string.h>
16 #include        <ctype.h>
17 #include        <fcntl.h>
18
19 #if HAVE_MALLOC_H
20 #  include      <malloc.h>
21 #endif
22
23 #include        "radiusd.h"
24 #include        "modules.h"
25
26 struct detail_instance {
27         /* detail file */
28         const char *detailfile;
29
30         /* detail file permissions */
31         int detailperm;
32
33         /* directory permissions */
34         int dirperm;
35
36         /* last made directory */
37         const char *last_made_directory;
38 };
39
40 /*
41  *      A temporary holding area for config values to be extracted
42  *      into, before they are copied into the instance data
43  */
44 static struct detail_instance config;
45
46 static CONF_PARSER module_config[] = {
47         { "detailfile",    PW_TYPE_STRING_PTR, &config.detailfile, "%A/%n/detail" },
48         { "detailperm",    PW_TYPE_INTEGER,    &config.detailperm, "0600" },
49         { "dirperm",       PW_TYPE_INTEGER,    &config.dirperm,    "0755" },
50         { NULL, -1, NULL, NULL }
51 };
52
53 /*
54  *      (Re-)read radiusd.conf into memory.
55  */
56 static int detail_instantiate(CONF_SECTION *conf, void **instance)
57 {
58         struct detail_instance *inst;
59         size_t p=0;
60
61         inst = malloc(sizeof *inst);
62         if (!inst) {
63                 radlog(L_ERR|L_CONS, "rlm_detail: Out of memory\n");
64                 return -1;
65         }
66
67         if (cf_section_parse(conf, module_config) < 0) {
68                 free(inst);
69                 return -1;
70         }
71
72         /*
73          *      Sanitize the name for security!  Only permit letters, numbers,
74          *      -, _, / and \.  Anything else will be rejected.
75          */
76
77         p = strspn(config.detailfile, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_/%.");
78
79         if (p != strlen(config.detailfile)) {
80                 radlog(L_ERR|L_CONS, "rlm_detail: Illegal character in detail filename.");
81                 return -1;
82         }
83                         
84         inst->detailfile = config.detailfile;
85         inst->detailperm = config.detailperm;
86         inst->dirperm = config.dirperm;
87         inst->last_made_directory = NULL;
88         config.detailfile = NULL;
89
90         *instance = inst;
91         return 0;
92 }
93
94 /*
95  *      Accounting - write the detail files.
96  */
97 static int detail_accounting(void *instance, REQUEST *request)
98 {
99         int             outfd;
100         FILE            *outfp;
101         char            buffer[8192];
102         char            *p;
103         size_t          l;
104         VALUE_PAIR      *pair;
105         int             ret = RLM_MODULE_OK;
106         struct stat     st;
107
108         struct detail_instance *inst = instance;
109
110         /*
111          *      Create a directory for this nas.
112          *
113          *      Generate the path for the detail file.  Use the
114          *      same format, but truncate at the last /.  Then
115          *      feed it through radius_xlat2() to expand the
116          *      variables.
117          */
118         radius_xlat2(buffer, sizeof(buffer), inst->detailfile, request,
119                      request->reply->vps);
120
121         /*
122          *      Sanitize the name for security!  Only permit letters, numbers,
123          *      -, _, / and \.  Anything else will be rejected.
124          */
125
126         if (strstr(buffer, "..")) {
127                 radlog(L_ERR, "rlm_detail: Directory \"%s\" contains \"..\" which is not valid.",
128                         buffer);
129                 return RLM_MODULE_FAIL;
130         }
131
132         l = strspn(buffer, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_/.");
133
134         if (l != strlen(buffer)) {
135                 radlog(L_ERR, "rlm_detail: Directory \"%s\" contains an invalid character.",
136                        buffer);
137                 return RLM_MODULE_FAIL;
138         }
139                         
140         /*
141          *      Grab the last directory delimiter.
142          */
143         p = strrchr(buffer,'/');
144
145         /*
146          *      There WAS a directory delimiter there, so let's
147          *      go create the relevant directories.
148          */
149         if (p) {
150                 *p = '\0';
151                 
152                 /*
153                  *      NO previously cached directory name, so we've
154                  *      got to create a new one.
155                  *
156                  *      OR the new directory name is different than the old,
157                  *      so we've got to create a new one.
158                  *
159                  *      OR the cached directory has somehow gotten removed,
160                  *      so we've got to create a new one.
161                  */
162                 if ((inst->last_made_directory == NULL) ||
163                     (strcmp(inst->last_made_directory, buffer) != 0) ||
164                     (stat(buffer, &st) == -1)) {
165                         
166                         /*
167                          *      Free any previously cached name.
168                          */
169                         if (inst->last_made_directory != NULL) {
170                                 free((char *) inst->last_made_directory);
171                                 inst->last_made_directory = NULL;
172                         }
173                         
174                         /*
175                          *      Try to create the new directory.
176                          */
177                         if ((mkdir(buffer, inst->dirperm) == -1) &&
178                             (errno != EEXIST)) {
179                                 radlog(L_ERR, "rlm_detail: Couldn't create %s: %s",
180                                        buffer, strerror(errno));
181                                 
182                                 return RLM_MODULE_FAIL;
183                         }
184                         
185                         /*
186                          *      Save a copy of the directory name that
187                          *      we just created.
188                          */
189                         inst->last_made_directory = strdup(buffer);
190                 }
191                 
192                 /*
193                  *      Re-create the file name, by replacing the
194                  *      directory delimiter that we removed above.
195                  */
196                 *p = '/';
197         } /* else there was no directory delimiter. */
198
199         /*
200          *      Open & create the file, with the given permissions.
201          */
202         if ((outfd = open(buffer, O_WRONLY|O_APPEND|O_CREAT,
203                           inst->detailperm)) < 0) {
204                 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
205                        buffer, strerror(errno));
206                 ret = RLM_MODULE_FAIL;
207
208         } else if ((outfp = fdopen(outfd, "a")) == NULL) {
209                 radlog(L_ERR, "rlm_detail: Couldn't open file %s: %s",
210                        buffer, strerror(errno));
211                 ret = RLM_MODULE_FAIL;
212                 close(outfd);
213         } else {
214                 /* Post a timestamp */
215                 fputs(ctime(&request->timestamp), outfp);
216
217                 /* Write each attribute/value to the log file */
218                 pair = request->packet->vps;
219                 while (pair) {
220                         if (pair->attribute != PW_PASSWORD) {
221                                 fputs("\t", outfp);
222                                 vp_print(outfp, pair);
223                                 fputs("\n", outfp);
224                         }
225                         pair = pair->next;
226                 }
227
228                 /*
229                  *      Add non-protocol attibutes.
230                  */
231                 fprintf(outfp, "\tTimestamp = %ld\n", request->timestamp);
232                 if (request->packet->verified)
233                         fputs("\tRequest-Authenticator = Verified\n", outfp);
234                 else
235                         fputs("\tRequest-Authenticator = None\n", outfp);
236                 fputs("\n", outfp);
237                 fclose(outfp);
238         }
239
240         return ret;
241 }
242
243
244 /*
245  *      Clean up.
246  */
247 static int detail_detach(void *instance)
248 {
249         struct detail_instance *inst = instance;
250         free((char *) inst->detailfile);
251
252         if (inst->last_made_directory)
253                 free((char*) inst->last_made_directory);
254         free(inst);
255         return 0;
256 }
257
258
259 /* globally exported name */
260 module_t rlm_detail = {
261         "detail",
262         0,                              /* type: reserved */
263         NULL,                           /* initialization */
264         detail_instantiate,             /* instantiation */
265         NULL,                           /* authorization */
266         NULL,                           /* authentication */
267         NULL,                           /* preaccounting */
268         detail_accounting,              /* accounting */
269         NULL,                           /* checksimul */
270         detail_detach,                  /* detach */
271         NULL                            /* destroy */
272 };
273