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