* Correct a bug introduced in 1.7. The wrong buffer was
[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 static int detail_init(void)
41 {
42         return 0;
43 }
44
45 /*
46  *      A temporary holding area for config values to be extracted
47  *      into, before they are copied into the instance data
48  */
49 static struct detail_instance config;
50
51 static CONF_PARSER module_config[] = {
52         { "detailfile",    PW_TYPE_STRING_PTR, &config.detailfile, "%A/%n/detail" },
53         { "detailperm",    PW_TYPE_INTEGER,    &config.detailperm, "0600" },
54         { "dirperm",       PW_TYPE_INTEGER,    &config.dirperm,    "0755" },
55         { NULL, -1, NULL, NULL }
56 };
57
58 /*
59  *      (Re-)read radiusd.conf into memory.
60  */
61 static int detail_instantiate(CONF_SECTION *conf, void **instance)
62 {
63         struct detail_instance *inst;
64         size_t p=0;
65
66         inst = malloc(sizeof *inst);
67         if (!inst) {
68                 radlog(L_ERR|L_CONS, "Out of memory\n");
69                 return -1;
70         }
71
72         if (cf_section_parse(conf, module_config) < 0) {
73                 free(inst);
74                 return -1;
75         }
76
77         /*
78          *      Sanitize the name for security!  Only permit letters, numbers,
79          *      -, _, / and \.  Anything else will be rejected.
80          */
81
82         p = strspn(config.detailfile, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_/%.");
83
84         if (p!=strlen(config.detailfile)) {
85                 radlog(L_ERR|L_CONS, "Illegal character.");
86                 return -1;
87         }
88                         
89         inst->detailfile = config.detailfile;
90         inst->detailperm = config.detailperm;
91         inst->dirperm = config.dirperm;
92         inst->last_made_directory = NULL;
93         config.detailfile = NULL;
94
95         *instance = inst;
96         return 0;
97 }
98
99 /*
100  *      Accounting - write the detail files.
101  */
102 static int detail_accounting(void *instance, REQUEST *request)
103 {
104         int             outfd;
105         FILE            *outfp;
106         char            nasname[128];
107         char            buffer[512];
108         char            filename[512];
109         char            *p;
110         size_t          l;
111         VALUE_PAIR      *pair;
112         uint32_t        nas;
113         NAS             *cl;
114         int             ret = RLM_MODULE_OK;
115
116         struct detail_instance *inst = instance;
117
118         /*
119          *      Find out the name of this terminal server. We try
120          *      to find the PW_NAS_IP_ADDRESS in the naslist file.
121          *      If that fails, we look for the originating address.
122          *      Only if that fails we resort to a name lookup.
123          */
124         cl = NULL;
125         nas = request->packet->src_ipaddr;
126         if ((pair = pairfind(request->packet->vps, PW_NAS_IP_ADDRESS)) != NULL)
127                 nas = pair->lvalue;
128         if (request->proxy && request->proxy->src_ipaddr)
129                 nas = request->proxy->src_ipaddr;
130
131         if ((cl = nas_find(nas)) != NULL) {
132                 if (cl->shortname[0])
133                         strcpy(nasname, cl->shortname);
134                 else
135                         strcpy(nasname, cl->longname);
136         }
137
138         if (cl == NULL) {
139                 ip_hostname(nasname, sizeof(nasname), nas);
140         }
141
142         /*
143          *      Create a directory for this nas.
144          *
145          *      Generate the path for the detail file.  Use the
146          *      same format, but truncate at the last /.  Then
147          *      feed it through radius_xlat2() to expand the
148          *      variables.
149          */
150         strNcpy(filename, inst->detailfile, sizeof(filename));
151
152         radius_xlat2(buffer, sizeof(buffer), filename, request,
153                      request->reply->vps);
154
155         /*
156          *      Sanitize the name for security!  Only permit letters, numbers,
157          *      -, _, / and \.  Anything else will be rejected.
158          */
159
160         if(strstr(buffer, "..")) {
161                 radlog(L_ERR, "Detail: Directory \"%s\" contains \"..\" which is not valid.",
162                         buffer);
163                 return RLM_MODULE_FAIL;
164         }
165
166         l = strspn(buffer, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_/.");
167
168         if (l!=strlen(buffer)) {
169                 radlog(L_ERR, "Detail: Directory \"%s\" contains an invalid character.",
170                        buffer);
171                 return RLM_MODULE_FAIL;
172         }
173                         
174         p = strrchr(buffer,'/');
175
176         /*
177          *      There WAS a directory delimiter there, so let's
178          *      go create the relevant directories.
179          */
180         if (p) {
181                 *p = '\0';
182                 
183                 /*
184                  *      NO previously cached directory name, so we've
185                  *      got to create a new one.
186                  *
187                  *      OR the new directory name is different than the old,
188                  *      so we've got to create a new one.
189                  */
190                 if ((inst->last_made_directory == NULL) ||
191                     (strcmp(inst->last_made_directory, buffer) != 0)) {
192                         
193                         /*
194                          *      Free any previously cached name.
195                          */
196                         if (inst->last_made_directory != NULL) {
197                                 free((char *) inst->last_made_directory);
198                                 inst->last_made_directory = NULL;
199                         }
200                         
201                         /*
202                          *      Try to create the new directory.
203                          */
204                         if ((mkdir(buffer, inst->dirperm) == -1) &&
205                             (errno != EEXIST)) {
206                                 radlog(L_ERR, "Detail: Couldn't create %s: %s",
207                                        buffer, strerror(errno));
208                                 
209                                 return RLM_MODULE_FAIL;
210                         }
211                         
212                         /*
213                          *      Save a copy of the directory name that
214                          *      we just created.
215                          */
216                         inst->last_made_directory = strdup(buffer);
217                 }
218                 
219                 /*
220                  *      Re-create the file name, by replacing the
221                  *      directory delimiter that we removed above.
222                  */
223                 *p = '/';
224         } /* else there was no directory delimiter. */
225
226         /*
227          *      Open & create the file, with the given permissions.
228          */
229         if ((outfd = open(buffer, O_WRONLY|O_APPEND|O_CREAT,
230                           inst->detailperm)) < 0) {
231                 radlog(L_ERR, "Detail: Couldn't open file %s: %s",
232                        buffer, strerror(errno));
233                 ret = RLM_MODULE_FAIL;
234
235         } else if ((outfp = fdopen(outfd, "a")) == NULL) {
236                 radlog(L_ERR, "Detail: Couldn't open file %s: %s",
237                        buffer, strerror(errno));
238                 ret = RLM_MODULE_FAIL;
239                 close(outfd);
240         } else {
241                 /* Post a timestamp */
242                 fputs(ctime(&request->timestamp), outfp);
243
244                 /* Write each attribute/value to the log file */
245                 pair = request->packet->vps;
246                 while (pair) {
247                         if (pair->attribute != PW_PASSWORD) {
248                                 fputs("\t", outfp);
249                                 vp_print(outfp, pair);
250                                 fputs("\n", outfp);
251                         }
252                         pair = pair->next;
253                 }
254
255                 /*
256                  *      Add non-protocol attibutes.
257                  */
258                 fprintf(outfp, "\tTimestamp = %ld\n", request->timestamp);
259                 if (request->packet->verified)
260                         fputs("\tRequest-Authenticator = Verified\n", outfp);
261                 else
262                         fputs("\tRequest-Authenticator = None\n", outfp);
263                 fputs("\n", outfp);
264                 fclose(outfp);
265         }
266
267         return ret;
268 }
269
270
271 /*
272  *      Clean up.
273  */
274 static int detail_detach(void *instance)
275 {
276         struct detail_instance *inst = instance;
277         free((char *) inst->detailfile);
278
279         if (inst->last_made_directory)
280                 free((char*) inst->last_made_directory);
281         free(inst);
282         return 0;
283 }
284
285
286 /* globally exported name */
287 module_t rlm_detail = {
288         "detail",
289         0,                              /* type: reserved */
290         detail_init,                    /* initialization */
291         detail_instantiate,             /* instantiation */
292         NULL,                           /* authorization */
293         NULL,                           /* authentication */
294         NULL,                           /* preaccounting */
295         detail_accounting,              /* accounting */
296         NULL,                           /* checksimul */
297         detail_detach,                  /* detach */
298         NULL                            /* destroy */
299 };
300