reject packets which contain multiple kinds of authentication protocols
[freeradius.git] / src / main / exfile.c
1 /*
2  *   This program is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or
5  *   (at your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /*
18  * $Id$
19  *
20  * @file exfile.c
21  * @brief Allow multiple threads to write to the same set of files.
22  *
23  * @author Alan DeKok <aland@freeradius.org>
24  * @copyright 2014  The FreeRADIUS server project
25  */
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/exfile.h>
28
29 #include <sys/stat.h>
30 #include <fcntl.h>
31
32 typedef struct exfile_entry_t {
33         int             fd;             //!< File descriptor associated with an entry.
34         int             dup;
35         uint32_t        hash;           //!< Hash for cheap comparison.
36         time_t          last_used;      //!< Last time the entry was used.
37         char            *filename;      //!< Filename.
38 } exfile_entry_t;
39
40
41 struct exfile_t {
42         uint32_t        max_entries;    //!< How many file descriptors we keep track of.
43         uint32_t        max_idle;       //!< Maximum idle time for a descriptor.
44         time_t          last_cleaned;
45
46 #ifdef HAVE_PTHREAD_H
47         pthread_mutex_t mutex;
48 #endif
49         exfile_entry_t *entries;
50         bool            locking;
51 };
52
53
54 #ifdef HAVE_PTHREAD_H
55 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
56 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
57
58 #else
59 /*
60  *      This is easier than ifdef's throughout the code.
61  */
62 #define PTHREAD_MUTEX_LOCK(_x)
63 #define PTHREAD_MUTEX_UNLOCK(_x)
64 #endif
65
66 #define MAX_TRY_LOCK 4                  //!< How many times we attempt to acquire a lock
67                                         //!< before giving up.
68
69 static int _exfile_free(exfile_t *ef)
70 {
71         uint32_t i;
72
73         PTHREAD_MUTEX_LOCK(&ef->mutex);
74
75         for (i = 0; i < ef->max_entries; i++) {
76                 if (!ef->entries[i].filename) continue;
77
78                 close(ef->entries[i].fd);
79         }
80
81         PTHREAD_MUTEX_UNLOCK(&ef->mutex);
82
83 #ifdef HAVE_PTHREAD_H
84         pthread_mutex_destroy(&ef->mutex);
85 #endif
86
87         return 0;
88 }
89
90
91 /** Initialize a way for multiple threads to log to one or more files.
92  *
93  * @param ctx The talloc context
94  * @param max_entries Max file descriptors to cache, and manage locks for.
95  * @param max_idle Maximum time a file descriptor can be idle before it's closed.
96  * @param locking whether or not to lock the files.
97  * @return the new context, or NULL on error.
98  */
99 exfile_t *exfile_init(TALLOC_CTX *ctx, uint32_t max_entries, uint32_t max_idle, bool locking)
100 {
101         exfile_t *ef;
102
103         ef = talloc_zero(ctx, exfile_t);
104         if (!ef) return NULL;
105
106         ef->entries = talloc_zero_array(ef, exfile_entry_t, max_entries);
107         if (!ef->entries) {
108                 talloc_free(ef);
109                 return NULL;
110         }
111
112 #ifdef HAVE_PTHREAD_H
113         if (pthread_mutex_init(&ef->mutex, NULL) != 0) {
114                 talloc_free(ef);
115                 return NULL;
116         }
117 #endif
118
119         ef->max_entries = max_entries;
120         ef->max_idle = max_idle;
121         ef->locking = locking;
122
123         talloc_set_destructor(ef, _exfile_free);
124
125         return ef;
126 }
127
128
129 static void exfile_cleanup_entry(exfile_entry_t *entry)
130 {
131         TALLOC_FREE(entry->filename);
132
133         close(entry->fd);
134         entry->hash = 0;
135         entry->fd = -1;
136         entry->dup = -1;
137 }
138
139 /** Open a new log file, or maybe an existing one.
140  *
141  * When multithreaded, the FD is locked via a mutex.  This way we're
142  * sure that no other thread is writing to the file.
143  *
144  * @param ef The logfile context returned from exfile_init().
145  * @param filename the file to open.
146  * @param permissions to use.
147  * @param append If true seek to the end of the file.
148  * @return an FD used to write to the file, or -1 on error.
149  */
150 int exfile_open(exfile_t *ef, char const *filename, mode_t permissions, bool append)
151 {
152         int i, tries, unused, oldest;
153         uint32_t hash;
154         time_t now = time(NULL);
155         struct stat st;
156
157         if (!ef || !filename) return -1;
158
159         hash = fr_hash_string(filename);
160         unused = -1;
161
162         PTHREAD_MUTEX_LOCK(&ef->mutex);
163
164         /*
165          *      Clean up idle entries.
166          */
167         if (now > (ef->last_cleaned + 1)) {
168                 ef->last_cleaned = now;
169
170                 for (i = 0; i < (int) ef->max_entries; i++) {
171                         if (!ef->entries[i].filename) continue;
172
173                         if ((ef->entries[i].last_used + ef->max_idle) >= now) continue;
174
175                         /*
176                          *      This will block forever if a thread is
177                          *      doing something stupid.
178                          */
179                         exfile_cleanup_entry(&ef->entries[i]);
180                 }
181         }
182
183         /*
184          *      Find the matching entry, or an unused one.
185          *
186          *      Also track which entry is the oldest, in case there
187          *      are no unused entries.
188          */
189         oldest = -1;
190         for (i = 0; i < (int) ef->max_entries; i++) {
191                 if (!ef->entries[i].filename) {
192                         if (unused < 0) unused = i;
193                         continue;
194                 }
195
196                 if ((oldest < 0) ||
197                     (ef->entries[i].last_used < ef->entries[oldest].last_used)) {
198                         oldest = i;
199                 }
200
201                 /*
202                  *      Hash comparisons are fast.  String comparisons are slow.
203                  */
204                 if (ef->entries[i].hash != hash) continue;
205
206                 /*
207                  *      But we still need to do string comparisons if
208                  *      the hash matches, because 1/2^16 filenames
209                  *      will result in a hash collision.  And that's
210                  *      enough filenames in a long-running server to
211                  *      ensure that it happens.
212                  */
213                 if (strcmp(ef->entries[i].filename, filename) != 0) continue;
214
215                 goto do_return;
216         }
217
218         /*
219          *      There are no unused entries, free the oldest one.
220          */
221         if (unused < 0) {
222                 exfile_cleanup_entry(&ef->entries[oldest]);
223                 unused = oldest;
224         }
225
226         /*
227          *      Create a new entry.
228          */
229         i = unused;
230
231         ef->entries[i].hash = hash;
232         ef->entries[i].filename = talloc_strdup(ef->entries, filename);
233         ef->entries[i].fd = -1;
234         ef->entries[i].dup = -1;
235
236         ef->entries[i].fd = open(filename, O_RDWR | O_APPEND | O_CREAT, permissions);
237         if (ef->entries[i].fd < 0) {
238                 mode_t dirperm;
239                 char *p, *dir;
240
241                 /*
242                  *      Maybe the directory doesn't exist.  Try to
243                  *      create it.
244                  */
245                 dir = talloc_strdup(ef, filename);
246                 if (!dir) goto error;
247                 p = strrchr(dir, FR_DIR_SEP);
248                 if (!p) {
249                         fr_strerror_printf("No '/' in '%s'", filename);
250                         goto error;
251                 }
252                 *p = '\0';
253
254                 /*
255                  *      Ensure that the 'x' bit is set, so that we can
256                  *      read the directory.
257                  */
258                 dirperm = permissions;
259                 if ((dirperm & 0600) != 0) dirperm |= 0100;
260                 if ((dirperm & 0060) != 0) dirperm |= 0010;
261                 if ((dirperm & 0006) != 0) dirperm |= 0001;
262
263                 if (rad_mkdir(dir, dirperm, -1, -1) < 0) {
264                         fr_strerror_printf("Failed to create directory %s: %s",
265                                            dir, strerror(errno));
266                         talloc_free(dir);
267                         goto error;
268                 }
269                 talloc_free(dir);
270
271                 ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
272                 if (ef->entries[i].fd < 0) {
273                         fr_strerror_printf("Failed to open file %s: %s",
274                                            filename, strerror(errno));
275                         goto error;
276                 } /* else fall through to creating the rest of the entry */
277         } /* else the file was already opened */
278
279 do_return:
280         /*
281          *      Lock from the start of the file.
282          */
283         if (lseek(ef->entries[i].fd, 0, SEEK_SET) < 0) {
284                 fr_strerror_printf("Failed to seek in file %s: %s", filename, strerror(errno));
285
286         error:
287                 exfile_cleanup_entry(&ef->entries[i]);
288
289                 PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
290                 return -1;
291         }
292
293         /*
294          *      Try to lock it.  If we can't lock it, it's because
295          *      some reader has re-named the file to "foo.work" and
296          *      locked it.  So, we close the current file, re-open it,
297          *      and try again/
298          */
299         if (ef->locking) {
300                 for (tries = 0; tries < MAX_TRY_LOCK; tries++) {
301                         if (rad_lockfd_nonblock(ef->entries[i].fd, 0) >= 0) break;
302
303                         if (errno != EAGAIN) {
304                                 fr_strerror_printf("Failed to lock file %s: %s", filename, strerror(errno));
305                                 goto error;
306                         }
307
308                         close(ef->entries[i].fd);
309                         ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
310                         if (ef->entries[i].fd < 0) {
311                                 fr_strerror_printf("Failed to open file %s: %s",
312                                                    filename, strerror(errno));
313                                 goto error;
314                         }
315                 }
316
317                 if (tries >= MAX_TRY_LOCK) {
318                         fr_strerror_printf("Failed to lock file %s: too many tries", filename);
319                         goto error;
320                 }
321         }
322
323         /*
324          *      Maybe someone deleted the file while we were waiting
325          *      for the lock.  If so, re-open it.
326          */
327         if (fstat(ef->entries[i].fd, &st) < 0) {
328                 fr_strerror_printf("Failed to stat file %s: %s", filename, strerror(errno));
329                 goto error;
330         }
331
332         if (st.st_nlink == 0) {
333                 close(ef->entries[i].fd);
334                 ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
335                 if (ef->entries[i].fd < 0) {
336                         fr_strerror_printf("Failed to open file %s: %s",
337                                            filename, strerror(errno));
338                         goto error;
339                 }
340         }
341
342         /*
343          *      Seek to the end of the file before returning the FD to
344          *      the caller.
345          */
346         if (append) lseek(ef->entries[i].fd, 0, SEEK_END);
347
348         /*
349          *      Return holding the mutex for the entry.
350          */
351         ef->entries[i].last_used = now;
352         ef->entries[i].dup = dup(ef->entries[i].fd);
353         if (ef->entries[i].dup < 0) {
354                 fr_strerror_printf("Failed calling dup(): %s", strerror(errno));
355                 goto error;
356         }
357
358         return ef->entries[i].dup;
359 }
360
361 /** Close the log file.  Really just return it to the pool.
362  *
363  * When multithreaded, the FD is locked via a mutex.  This way we're
364  * sure that no other thread is writing to the file.  This function
365  * will unlock the mutex, so that other threads can write to the file.
366  *
367  * @param ef The logfile context returned from exfile_init()
368  * @param fd the FD to close (i.e. return to the pool)
369  * @return 0 on success, or -1 on error
370  */
371 int exfile_close(exfile_t *ef, int fd)
372 {
373         uint32_t i;
374
375         for (i = 0; i < ef->max_entries; i++) {
376                 if (!ef->entries[i].filename) continue;
377
378                 /*
379                  *      Unlock the bytes that we had previously locked.
380                  */
381                 if (ef->entries[i].dup == fd) {
382                         if (ef->locking) (void) rad_unlockfd(ef->entries[i].dup, 0);
383                         close(ef->entries[i].dup); /* releases the fcntl lock */
384                         ef->entries[i].dup = -1;
385
386                         PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
387                         return 0;
388                 }
389         }
390
391         PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
392
393         fr_strerror_printf("Attempt to unlock file which is not tracked");
394         return -1;
395 }
396
397 int exfile_unlock(exfile_t *ef, int fd)
398 {
399         uint32_t i;
400
401         for (i = 0; i < ef->max_entries; i++) {
402                 if (!ef->entries[i].filename) continue;
403
404                 if (ef->entries[i].dup == fd) {
405                         ef->entries[i].dup = -1;
406                         PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
407                         return 0;
408                 }
409         }
410
411         PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
412
413         fr_strerror_printf("Attempt to unlock file which does not exist");
414         return -1;
415 }