1 
2 /*
3  * Alternative logging formats for Pure-FTPd
4  */
5 
6 #include <config.h>
7 
8 #ifdef WITH_ALTLOG
9 
10 # include "ftpd.h"
11 # include "ftpwho-update.h"
12 # include "globals.h"
13 # include "altlog.h"
14 # include "safe_rw.h"
15 
16 # ifdef WITH_DMALLOC
17 #  include <dmalloc.h>
18 # endif
19 
altlog_write(const char * str)20 static int altlog_write(const char *str)
21 {
22     struct flock lock;
23     ssize_t left;
24 
25     if (altlog_fd == -1 || str == NULL ||
26         (left = (ssize_t) strlen(str)) <= (ssize_t) 0) {
27         return -1;
28     }
29     lock.l_whence = SEEK_SET;
30     lock.l_start = (off_t) 0;
31     lock.l_len = (off_t) 0;
32     lock.l_pid = getpid();
33     lock.l_type = F_WRLCK;
34     while (fcntl(altlog_fd, F_SETLKW, &lock) < 0 && errno == EINTR);
35     if (lseek(altlog_fd, (off_t) 0, SEEK_END) < (off_t) 0
36 # ifdef ESPIPE
37         && errno != ESPIPE
38 # endif
39 # ifdef EBADF
40         && errno != EBADF
41 # endif
42         ) {
43         return -1;
44     }
45     (void) safe_write(altlog_fd, str, (size_t) left, -1);
46     lock.l_type = F_UNLCK;
47     while (fcntl(altlog_fd, F_SETLK, &lock) < 0 && errno == EINTR);
48 
49     return 0;
50 }
51 
52 /* Verbose but compact log format for ftpStats */
53 
altlog_writexfer_stats(const int upload,const char * const filename,const off_t size,const double duration)54 static int altlog_writexfer_stats(const int upload,
55                                   const char * const filename,
56                                   const off_t size,
57                                   const double duration)
58 {
59     /*
60      * <date> <start.pid> <user> <ip> <u/d> <size> <duration> <file>
61      */
62 
63     const char *host_ = *host != 0 ? host : "-";
64     const char *account_ = *account != 0 ? account : "-";
65     char *alloca_line;
66     size_t line_size;
67 
68     line_size = 16U /* now */ + 1U + 16U /* start */ + 1U /* . */ +
69         16U /* pid */ + 1U + strlen(account_) + 1U + strlen(host_) + 1U +
70         1U /* U/D */ + 1U + 20U /* size */ + 1U + 16U /* duration */ +
71         strlen(filename) + 1U /* \n */ + 1U;
72     if ((alloca_line = ALLOCA(line_size)) == NULL) {
73         return -1;
74     }
75     if (!SNCHECK(snprintf(alloca_line, line_size,
76                           "%llu %llx.%lx %s %s %c %llu %lu %s\n",
77                           (unsigned long long) time(NULL),
78                           (unsigned long long) session_start_time,
79                           (unsigned long) getpid(),
80                           account_, host_,
81                           upload != 0 ? 'U' : 'D',
82                           (unsigned long long) size,
83                           (unsigned long) (duration + 0.5),
84                           filename), line_size)) {
85         altlog_write(alloca_line);
86     }
87 
88     ALLOCA_FREE(alloca_line);
89 
90     return 0;
91 }
92 
93 # define NO_URLENCODE(c) ( \
94                                ((c) >= 'a' && (c) <= 'z') || \
95                                ((c) >= 'A' && (c) <= 'Z') || \
96                                ((c) >= '0' && (c) <= '9') || \
97                                (c) == '.' || (c) == '/' || \
98                                (c) == '_' || (c) == '-' \
99                                )
100 # define HEXD(c) ((c) < 10 ? '0' + (c) : 'A' - 10 + (c))
101 
urlencode(const char * filename)102 static char *urlencode(const char *filename)
103 {
104     const char *ptr = filename;
105     char *quoted_filename;
106     char *quoted_filename_ptr;
107     size_t quoted_filename_size = (size_t) 1U;
108     int need_quote = 0;
109     char c;
110 
111     while (*ptr != 0) {
112         if (NO_URLENCODE(*ptr)) {
113             quoted_filename_size++;
114         } else {
115             quoted_filename_size += (size_t) 3U;
116             need_quote = 1;
117         }
118         ptr++;
119     }
120     if (need_quote == 0) {
121         return (char *) filename;
122     }
123     if ((quoted_filename = malloc(quoted_filename_size)) == NULL) {
124         return NULL;
125     }
126     quoted_filename_ptr = quoted_filename;
127     ptr = filename;
128     c = *ptr;
129     do {
130         if (NO_URLENCODE(c)) {
131             if (quoted_filename_size <= (size_t) 1U) {
132                 free(quoted_filename);
133                 return NULL;
134             }
135             quoted_filename_size--;
136             *quoted_filename_ptr++ = c;
137         } else {
138             if (quoted_filename_size <= (size_t) 3U) {
139                 free(quoted_filename);
140                 return NULL;
141             }
142             quoted_filename_size -= (size_t) 3U;
143             *quoted_filename_ptr++ = '%';
144             *quoted_filename_ptr++ = HEXD(((unsigned char) c) >> 4);
145             *quoted_filename_ptr++ = HEXD(((unsigned char) c) & 0xf);
146         }
147         ptr++;
148     } while ((c = *ptr) != 0);
149     *quoted_filename_ptr = 0;
150 
151     return quoted_filename;
152 }
153 
154 /* HTTPd-like common log format */
155 
altlog_writexfer_clf(const int upload,const char * const filename,const off_t size)156 static int altlog_writexfer_clf(const int upload,
157                                 const char * const filename,
158                                 const off_t size)
159 {
160     char date[sizeof "13/Apr/1975:12:34:56 +0100"];
161     struct tm *tm;
162     char *alloca_line;
163     const char *host_ = *host != 0 ? host : "-";
164     const char *account_ = *account != 0 ? account : "-";
165     char *quoted_filename;
166     time_t now;
167     long diff;
168     int sign;
169     size_t line_size;
170 
171     if ((now = time(NULL)) == (time_t) -1 ||
172         (tm = localtime(&now)) == NULL ||
173         tm->tm_mon > 11 || tm->tm_mon < 0) {
174         return -1;
175     }
176 # ifdef HAVE_STRUCT_TM_TM_GMTOFF
177     diff = -(tm->tm_gmtoff) / 60L;
178 # elif defined(HAVE_SCALAR_TIMEZONE)
179     diff = -(timezone) / 60L;
180 # else
181     {
182         struct tm gmt;
183         struct tm *t;
184         int days, hours, minutes;
185 
186         gmt = *gmtime(&now);
187         t = localtime(&now);
188         days = t->tm_yday - gmt.tm_yday;
189         hours = ((days < -1 ? 24 : 1 < days ? -24 : days * 24)
190                  + t->tm_hour - gmt.tm_hour);
191         minutes = hours * 60 + t->tm_min - gmt.tm_min;
192         diff = -minutes;
193     }
194 # endif
195     if (diff > 0L) {
196         sign = '+';
197     } else {
198         sign = '-';
199         diff = -diff;
200     }
201 
202     if (SNCHECK(snprintf(date, sizeof date,
203                          "%02d/%s/%d:%02d:%02d:%02d %c%02ld%02ld",
204                          tm->tm_mday, months[tm->tm_mon], tm->tm_year + 1900,
205                          tm->tm_hour, tm->tm_min, tm->tm_sec,
206                          sign, diff / 60L, diff % 60L), sizeof date)) {
207         return -1;
208     }
209     if ((quoted_filename = urlencode(filename)) == NULL) {
210         return -1;
211     }
212     line_size = strlen(host_) + (sizeof " - " - 1U) + strlen(account_) +
213         (sizeof " [" - 1U) + (sizeof date - 1U) + (sizeof "] \"" - 1U) +
214         3U /* GET / PUT */ + (sizeof " " - 1U) + strlen(quoted_filename) +
215         (sizeof "\" 200 18446744073709551616\n" - 1U) + 1U;
216     if ((alloca_line = ALLOCA(line_size)) == NULL) {
217         return -1;
218     }
219     if (!SNCHECK(snprintf(alloca_line, line_size,
220                           "%s - %s [%s] \"%s %s\" 200 %llu\n",
221                           host_, account_, date,
222                           upload == 0 ? "GET" : "PUT", quoted_filename,
223                           (unsigned long long) size), line_size)) {
224         altlog_write(alloca_line);
225     }
226     if (quoted_filename != filename) {
227         free(quoted_filename);
228     }
229     ALLOCA_FREE(alloca_line);
230 
231     return 0;
232 }
233 
234 /* WuFTPd-like log format */
235 
altlog_writexfer_xferlog(const int upload,const char * const filename,const off_t size,const double duration)236 static int altlog_writexfer_xferlog(const int upload,
237                                     const char * const filename,
238                                     const off_t size,
239                                     const double duration)
240 {
241     char date[sizeof "Mon Apr 13 12:34:56 1975"];
242     struct tm *tm;
243     char *alloca_line;
244     const char *host_ = *host != 0 ? host : "-";
245     const char *account_ = *account != 0 ? account : "-";
246     char *quoted_filename;
247     size_t filename_idx;
248     time_t now;
249     size_t line_size;
250     size_t filename_size;
251     char c;
252 
253     if ((now = time(NULL)) == (time_t) -1 ||
254         (tm = localtime(&now)) == NULL ||
255         tm->tm_mon > 11 || tm->tm_mon < 0 ||
256         tm->tm_wday > 6 || tm->tm_wday < 0) {
257         return -1;
258     }
259     if (SNCHECK(snprintf(date, sizeof date,
260                          "%s %s %02d %02d:%02d:%02d %d",
261                          week_days[tm->tm_wday], months[tm->tm_mon],
262                          tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
263                          tm->tm_year + 1900), sizeof date)) {
264         return -1;
265     }
266     if ((filename_idx = strlen(filename)) <= (size_t) 0U) {
267         return -1;
268     }
269     filename_size = filename_idx + (size_t) 1U;
270     if ((quoted_filename = ALLOCA(filename_size)) == NULL) {
271         return -1;
272     }
273 
274     quoted_filename[filename_idx] = 0;
275     do {
276         filename_idx--;
277         c = filename[filename_idx];
278         if (isspace((unsigned char) c) || ISCTRLCODE(c)) {
279             c = '_';
280         }
281         quoted_filename[filename_idx] = c;
282     } while (filename_idx > (size_t) 0U);
283 
284     line_size = (sizeof date - 1U) + (sizeof " " - 1U) +
285         (size_t) 16U /* duration */ + (sizeof " " - 1U) +
286         strlen(host_) + (sizeof " " - 1U) +
287         (size_t) 20U /* size */ + (sizeof " " - 1U) +
288         (filename_size - 1U) + (sizeof " " - 1U) +
289         (size_t) 1U /* type */ + (sizeof " _ " - 1U) +
290         (size_t) 1U /* direction */ + (sizeof " " - 1U) +
291         (size_t) 1U /* anonymous */ + (sizeof " " - 1U) +
292         strlen(account_) + (sizeof " ftp 1 * c\n" - 1U) + (size_t) 1U;
293     if ((alloca_line = ALLOCA(line_size)) == NULL) {
294         ALLOCA_FREE(quoted_filename);
295         return -1;
296     }
297     if (!SNCHECK(snprintf(alloca_line, line_size,
298                           "%s %lu %s %llu %s %c _ %c %c %s ftp 1 * c\n",
299                           date, (unsigned long) (duration + 0.5),
300                           host_, (unsigned long long) size,
301                           quoted_filename,
302                           type == 1 ? 'a' : 'b',
303                           upload != 0 ? 'i' : 'o',
304                           loggedin != 0 ? 'r' : 'a',
305                           account_), line_size)) {
306         altlog_write(alloca_line);
307     }
308     ALLOCA_FREE(quoted_filename);
309     ALLOCA_FREE(alloca_line);
310 
311     return 0;
312 }
313 
altlog_writexfer_w3c(const int upload,const char * const filename,const off_t size,const double duration)314 static int altlog_writexfer_w3c(const int upload,
315                                 const char * const filename,
316                                 const off_t size,
317                                 const double duration)
318 {
319     /*
320      * <date> <time> <ip> "[]sent" <file> "226" <user>
321      * date time c-ip cs-method cs-uri-stem sc-status cs-username
322      */
323 
324     struct tm *tm;
325     struct tm gmt;
326     const char *host_ = *host != 0 ? host : "-";
327     const char *account_ = *account != 0 ? account : "-";
328     char *alloca_line;
329     char *quoted_filename;
330     time_t now;
331     size_t line_size;
332 
333     (void) duration;
334     if ((now = time(NULL)) == (time_t) -1 ||
335         (tm = localtime(&now)) == NULL ||
336         tm->tm_mon > 11 || tm->tm_mon < 0) {
337         return -1;
338     }
339     gmt = *gmtime(&now);
340     if ((quoted_filename = urlencode(filename)) == NULL) {
341         return -1;
342     }
343     line_size = (sizeof "13-04-1975 12:34:56 " - 1U) +
344         strlen(host_) + 1U + (sizeof "[]created" - 1U) + 1U +
345         strlen(quoted_filename) + 1U + (sizeof "226" - 1U) +
346         strlen(account_) + 1U + 42U + 1U /* \n */ + 1U;
347 
348     if ((alloca_line = ALLOCA(line_size)) == NULL) {
349         return -1;
350     }
351     if (!SNCHECK(snprintf(alloca_line, line_size,
352                           "%d-%02d-%02d %02d:%02d:%02d %s []%s %s 226 %s %llu\n",
353                           gmt.tm_year + 1900, gmt.tm_mon + 1, gmt.tm_mday,
354                           gmt.tm_hour, gmt.tm_min, gmt.tm_sec,
355                           host_, (upload != 0 ? "created" : "sent"),
356                           quoted_filename,
357                           account, (unsigned long long) size), line_size)) {
358         altlog_write(alloca_line);
359     }
360     if (quoted_filename != filename) {
361         free(quoted_filename);
362     }
363     ALLOCA_FREE(alloca_line);
364 
365     return 0;
366 }
367 
altlog_write_w3c_header(void)368 int altlog_write_w3c_header(void)
369 {
370     time_t now;
371     struct tm *tm;
372     struct tm gmt;
373     char *alloca_line;
374     size_t line_size;
375 
376     if ((now = time(NULL)) == (time_t) -1 ||
377         (tm = localtime(&now)) == NULL ||
378         tm->tm_mon > 11 || tm->tm_mon < 0) {
379         return -1;
380     }
381     gmt = *gmtime(&now);
382     line_size = sizeof "#Date: 001975-04-13 12:34:56\n";   /* be year-999999 compliant :) */
383     if ((alloca_line = ALLOCA(line_size)) == NULL) {
384         return -1;
385     }
386 
387     altlog_write("#Software: Pure-FTPd " VERSION "\n");
388     altlog_write("#Version: 1.0\n");
389 
390     if (!SNCHECK(snprintf(alloca_line, line_size,
391                           "#Date: %04d-%02d-%02d %02d:%02d:%02d\n",
392                           gmt.tm_year + 1900, gmt.tm_mon + 1, gmt.tm_mday,
393                           gmt.tm_hour, gmt.tm_min, gmt.tm_sec),
394                           line_size)) {
395         altlog_write(alloca_line);
396     }
397 
398     altlog_write("#Fields: date time c-ip cs-method cs-uri-stem sc-status cs-username sc-bytes\n");
399 
400     ALLOCA_FREE(alloca_line);
401 
402     return 0;
403 }
404 
405 /*
406  * We should define a structure of function pointers,
407  * and associate a structure with each logging type in AltLogPrefixes.
408  * But yet we only have *three* logging methods, and the code would be
409  * complicated for nothing. So let's stick with simple tests for now. -j.
410  */
411 
altlog_writexfer(const int upload,const char * const filename,const off_t size,const double duration)412 int altlog_writexfer(const int upload,
413                      const char * const filename,
414                      const off_t size,
415                      const double duration)
416 {
417     switch (altlog_format) {
418     case ALTLOG_NONE:
419         return 0;
420     case ALTLOG_CLF:
421         return altlog_writexfer_clf(upload, filename, size);
422     case ALTLOG_STATS:
423         return altlog_writexfer_stats(upload, filename, size, duration);
424     case ALTLOG_W3C:
425         return altlog_writexfer_w3c(upload, filename, size, duration);
426     case ALTLOG_XFERLOG:
427         return altlog_writexfer_xferlog(upload, filename, size, duration);
428     }
429     return -1;
430 }
431 
432 #endif
433