1 /*
2 * ProFTPD - FTP server daemon
3 * Copyright (c) 2017-2020 The ProFTPD Project team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 *
19 * As a special exemption, TJ Saunders and other respective copyright holders
20 * give permission to link this program with OpenSSL, and distribute the
21 * resulting executable, without including the source code for OpenSSL in the
22 * source distribution.
23 */
24
25 #include "conf.h"
26 #include "logfmt.h"
27 #include "json.h"
28 #include "jot.h"
29
30 struct jot_filters_rec {
31 pool *pool;
32
33 int included_classes;
34 int excluded_classes;
35 array_header *cmd_ids;
36 };
37
38 /* For tracking the size of deleted files. */
39 static off_t jot_deleted_filesz = 0;
40
41 static const char *trace_channel = "jot";
42
43 /* Entries in the JSON map table identify the key, and the data type:
44 * Boolean, number, or string.
45 */
46 struct logfmt_json_info {
47 unsigned int json_type;
48 const char *json_key;
49 };
50
51 /* Key comparison for the ID/key table. */
logfmt_json_keycmp(const void * k1,size_t ksz1,const void * k2,size_t ksz2)52 static int logfmt_json_keycmp(const void *k1, size_t ksz1, const void *k2,
53 size_t ksz2) {
54
55 /* Return zero to indicate a match, non-zero otherwise. */
56 return (*((unsigned char *) k1) == *((unsigned char *) k2) ? 0 : 1);
57 }
58
59 /* Key "hash" callback for ID/key table. */
logfmt_json_keyhash(const void * k,size_t ksz)60 static unsigned int logfmt_json_keyhash(const void *k, size_t ksz) {
61 unsigned char c;
62 unsigned int res;
63
64 c = *((unsigned char *) k);
65 res = (c << 8);
66
67 return res;
68 }
69
add_json_info(pool * p,pr_table_t * tab,unsigned char logfmt_id,const char * json_key,unsigned int json_type)70 static void add_json_info(pool *p, pr_table_t *tab, unsigned char logfmt_id,
71 const char *json_key, unsigned int json_type) {
72 unsigned char *k;
73 struct logfmt_json_info *lji;
74
75 k = palloc(p, sizeof(unsigned char));
76 *k = logfmt_id;
77
78 lji = palloc(p, sizeof(struct logfmt_json_info));
79 lji->json_type = json_type;
80 lji->json_key = json_key;
81
82 (void) pr_table_kadd(tab, (const void *) k, sizeof(unsigned char),
83 lji, sizeof(struct logfmt_json_info *));
84 }
85
pr_jot_get_logfmt_id_name(unsigned char logfmt_id)86 const char *pr_jot_get_logfmt_id_name(unsigned char logfmt_id) {
87 const char *name = NULL;
88
89 switch (logfmt_id) {
90 case LOGFMT_META_BYTES_SENT:
91 name = "BYTE_SENT";
92 break;
93
94 case LOGFMT_META_FILENAME:
95 name = "FILENAME";
96 break;
97
98 case LOGFMT_META_ENV_VAR:
99 name = "ENV_VAR";
100 break;
101
102 case LOGFMT_META_REMOTE_HOST:
103 name = "REMOTE_HOST";
104 break;
105
106 case LOGFMT_META_REMOTE_IP:
107 name = "REMOTE_IP";
108 break;
109
110 case LOGFMT_META_IDENT_USER:
111 name = "IDENT_USER";
112 break;
113
114 case LOGFMT_META_PID:
115 name = "PID";
116 break;
117
118 case LOGFMT_META_TIME:
119 name = "TIME";
120 break;
121
122 case LOGFMT_META_SECONDS:
123 name = "SECONDS";
124 break;
125
126 case LOGFMT_META_COMMAND:
127 name = "COMMAND";
128 break;
129
130 case LOGFMT_META_LOCAL_NAME:
131 name = "LOCAL_NAME";
132 break;
133
134 case LOGFMT_META_LOCAL_PORT:
135 name = "LOCAL_PORT";
136 break;
137
138 case LOGFMT_META_LOCAL_IP:
139 name = "LOCAL_IP";
140 break;
141
142 case LOGFMT_META_LOCAL_FQDN:
143 name = "LOCAL_FQDN";
144 break;
145
146 case LOGFMT_META_USER:
147 name = "USER";
148 break;
149
150 case LOGFMT_META_ORIGINAL_USER:
151 name = "ORIGINAL_USER";
152 break;
153
154 case LOGFMT_META_RESPONSE_CODE:
155 name = "RESPONSE_CODE";
156 break;
157
158 case LOGFMT_META_CLASS:
159 name = "CLASS";
160 break;
161
162 case LOGFMT_META_ANON_PASS:
163 name = "ANON_PASS";
164 break;
165
166 case LOGFMT_META_METHOD:
167 name = "METHOD";
168 break;
169
170 case LOGFMT_META_XFER_PATH:
171 name = "XFER_PATH";
172 break;
173
174 case LOGFMT_META_DIR_NAME:
175 name = "DIR_NAME";
176 break;
177
178 case LOGFMT_META_DIR_PATH:
179 name = "DIR_PATH";
180 break;
181
182 case LOGFMT_META_CMD_PARAMS:
183 name = "CMD_PARAMS";
184 break;
185
186 case LOGFMT_META_RESPONSE_STR:
187 name = "RESPONSE_STR";
188 break;
189
190 case LOGFMT_META_PROTOCOL:
191 name = "PROTOCOL";
192 break;
193
194 case LOGFMT_META_VERSION:
195 name = "VERSION";
196 break;
197
198 case LOGFMT_META_RENAME_FROM:
199 name = "RENAME_FROM";
200 break;
201
202 case LOGFMT_META_FILE_MODIFIED:
203 name = "FILE_MODIFIED";
204 break;
205
206 case LOGFMT_META_UID:
207 name = "UID";
208 break;
209
210 case LOGFMT_META_GID:
211 name = "GID";
212 break;
213
214 case LOGFMT_META_RAW_BYTES_IN:
215 name = "RAW_BYTES_IN";
216 break;
217
218 case LOGFMT_META_RAW_BYTES_OUT:
219 name = "RAW_BYTES_OUT";
220 break;
221
222 case LOGFMT_META_EOS_REASON:
223 name = "EOS_REASON";
224 break;
225
226 case LOGFMT_META_VHOST_IP:
227 name = "VHOST_IP";
228 break;
229
230 case LOGFMT_META_NOTE_VAR:
231 name = "NOTE_VAR";
232 break;
233
234 case LOGFMT_META_XFER_STATUS:
235 name = "XFER_STATUS";
236 break;
237
238 case LOGFMT_META_XFER_FAILURE:
239 name = "XFER_FAILURE";
240 break;
241
242 case LOGFMT_META_MICROSECS:
243 name = "MICROSECS";
244 break;
245
246 case LOGFMT_META_MILLISECS:
247 name = "MILLISECS";
248 break;
249
250 case LOGFMT_META_ISO8601:
251 name = "ISO8601";
252 break;
253
254 case LOGFMT_META_GROUP:
255 name = "GROUP";
256 break;
257
258 case LOGFMT_META_BASENAME:
259 name = "BASENAME";
260 break;
261
262 case LOGFMT_META_FILE_OFFSET:
263 name = "FILE_OFFSET";
264 break;
265
266 case LOGFMT_META_XFER_MS:
267 name = "XFER_MS";
268 break;
269
270 case LOGFMT_META_RESPONSE_MS:
271 name = "RESPONSE_MS";
272 break;
273
274 case LOGFMT_META_FILE_SIZE:
275 name = "FILE_SIZE";
276 break;
277
278 case LOGFMT_META_XFER_PORT:
279 name = "XFER_PORT";
280 break;
281
282 case LOGFMT_META_XFER_TYPE:
283 name = "XFER_TYPE";
284 break;
285
286 case LOGFMT_META_REMOTE_PORT:
287 name = "REMOTE_PORT";
288 break;
289
290 case LOGFMT_META_EPOCH:
291 name = "EPOCH";
292 break;
293
294 case LOGFMT_META_CONNECT:
295 name = "CONNECT";
296 break;
297
298 case LOGFMT_META_DISCONNECT:
299 name = "DISCONNECT";
300 break;
301
302 case LOGFMT_META_CUSTOM:
303 name = "CUSTOM";
304 break;
305
306 default:
307 errno = EINVAL;
308 name = NULL;
309 break;
310 }
311
312 return name;
313 }
314
pr_jot_get_logfmt2json(pool * p)315 pr_table_t *pr_jot_get_logfmt2json(pool *p) {
316 pr_table_t *map;
317
318 if (p == NULL) {
319 errno = EINVAL;
320 return NULL;
321 }
322
323 map = pr_table_alloc(p, 0);
324
325 (void) pr_table_ctl(map, PR_TABLE_CTL_SET_KEY_CMP,
326 (void *) logfmt_json_keycmp);
327 (void) pr_table_ctl(map, PR_TABLE_CTL_SET_KEY_HASH,
328 (void *) logfmt_json_keyhash);
329
330 /* Now populate the map with the ID/name values. The key is the
331 * LogFormat "meta" ID, and the value is the corresponding name string,
332 * for use e.g. as JSON object member names.
333 */
334
335 add_json_info(p, map, LOGFMT_META_BYTES_SENT, PR_JOT_LOGFMT_BYTES_SENT_KEY,
336 PR_JSON_TYPE_NUMBER);
337 add_json_info(p, map, LOGFMT_META_FILENAME, PR_JOT_LOGFMT_FILENAME_KEY,
338 PR_JSON_TYPE_STRING);
339 add_json_info(p, map, LOGFMT_META_ENV_VAR, PR_JOT_LOGFMT_ENV_VAR_KEY,
340 PR_JSON_TYPE_STRING);
341 add_json_info(p, map, LOGFMT_META_EPOCH, PR_JOT_LOGFMT_EPOCH_KEY,
342 PR_JSON_TYPE_NUMBER);
343 add_json_info(p, map, LOGFMT_META_REMOTE_HOST, PR_JOT_LOGFMT_REMOTE_HOST_KEY,
344 PR_JSON_TYPE_STRING);
345 add_json_info(p, map, LOGFMT_META_REMOTE_IP, PR_JOT_LOGFMT_REMOTE_IP_KEY,
346 PR_JSON_TYPE_STRING);
347 add_json_info(p, map, LOGFMT_META_IDENT_USER, PR_JOT_LOGFMT_IDENT_USER_KEY,
348 PR_JSON_TYPE_STRING);
349 add_json_info(p, map, LOGFMT_META_PID, PR_JOT_LOGFMT_PID_KEY,
350 PR_JSON_TYPE_NUMBER);
351 add_json_info(p, map, LOGFMT_META_TIME, PR_JOT_LOGFMT_TIME_KEY,
352 PR_JSON_TYPE_STRING);
353 add_json_info(p, map, LOGFMT_META_SECONDS, PR_JOT_LOGFMT_SECONDS_KEY,
354 PR_JSON_TYPE_NUMBER);
355 add_json_info(p, map, LOGFMT_META_COMMAND, PR_JOT_LOGFMT_COMMAND_KEY,
356 PR_JSON_TYPE_STRING);
357 add_json_info(p, map, LOGFMT_META_LOCAL_NAME, PR_JOT_LOGFMT_LOCAL_NAME_KEY,
358 PR_JSON_TYPE_STRING);
359 add_json_info(p, map, LOGFMT_META_LOCAL_PORT, PR_JOT_LOGFMT_LOCAL_PORT_KEY,
360 PR_JSON_TYPE_NUMBER);
361 add_json_info(p, map, LOGFMT_META_LOCAL_IP, PR_JOT_LOGFMT_LOCAL_IP_KEY,
362 PR_JSON_TYPE_STRING);
363 add_json_info(p, map, LOGFMT_META_LOCAL_FQDN, PR_JOT_LOGFMT_LOCAL_FQDN_KEY,
364 PR_JSON_TYPE_STRING);
365 add_json_info(p, map, LOGFMT_META_USER, PR_JOT_LOGFMT_USER_KEY,
366 PR_JSON_TYPE_STRING);
367 add_json_info(p, map, LOGFMT_META_ORIGINAL_USER, PR_JOT_LOGFMT_ORIG_USER_KEY,
368 PR_JSON_TYPE_STRING);
369 add_json_info(p, map, LOGFMT_META_RESPONSE_CODE,
370 PR_JOT_LOGFMT_RESPONSE_CODE_KEY, PR_JSON_TYPE_NUMBER);
371 add_json_info(p, map, LOGFMT_META_CLASS, PR_JOT_LOGFMT_CLASS_KEY,
372 PR_JSON_TYPE_STRING);
373 add_json_info(p, map, LOGFMT_META_ANON_PASS, PR_JOT_LOGFMT_ANON_PASSWD_KEY,
374 PR_JSON_TYPE_STRING);
375 add_json_info(p, map, LOGFMT_META_METHOD, PR_JOT_LOGFMT_METHOD_KEY,
376 PR_JSON_TYPE_STRING);
377 add_json_info(p, map, LOGFMT_META_XFER_PATH, PR_JOT_LOGFMT_XFER_PATH_KEY,
378 PR_JSON_TYPE_STRING);
379 add_json_info(p, map, LOGFMT_META_DIR_NAME, PR_JOT_LOGFMT_DIR_NAME_KEY,
380 PR_JSON_TYPE_STRING);
381 add_json_info(p, map, LOGFMT_META_DIR_PATH, PR_JOT_LOGFMT_DIR_PATH_KEY,
382 PR_JSON_TYPE_STRING);
383 add_json_info(p, map, LOGFMT_META_CMD_PARAMS, PR_JOT_LOGFMT_CMD_PARAMS_KEY,
384 PR_JSON_TYPE_STRING);
385 add_json_info(p, map, LOGFMT_META_RESPONSE_STR,
386 PR_JOT_LOGFMT_RESPONSE_MSG_KEY, PR_JSON_TYPE_STRING);
387 add_json_info(p, map, LOGFMT_META_PROTOCOL, PR_JOT_LOGFMT_PROTOCOL_KEY,
388 PR_JSON_TYPE_STRING);
389 add_json_info(p, map, LOGFMT_META_VERSION, PR_JOT_LOGFMT_VERSION_KEY,
390 PR_JSON_TYPE_STRING);
391 add_json_info(p, map, LOGFMT_META_RENAME_FROM, PR_JOT_LOGFMT_RENAME_FROM_KEY,
392 PR_JSON_TYPE_STRING);
393 add_json_info(p, map, LOGFMT_META_FILE_MODIFIED,
394 PR_JOT_LOGFMT_FILE_MODIFIED_KEY, PR_JSON_TYPE_BOOL);
395 add_json_info(p, map, LOGFMT_META_UID, PR_JOT_LOGFMT_UID_KEY,
396 PR_JSON_TYPE_NUMBER);
397 add_json_info(p, map, LOGFMT_META_GID, PR_JOT_LOGFMT_GID_KEY,
398 PR_JSON_TYPE_NUMBER);
399 add_json_info(p, map, LOGFMT_META_RAW_BYTES_IN,
400 PR_JOT_LOGFMT_RAW_BYTES_IN_KEY, PR_JSON_TYPE_NUMBER);
401 add_json_info(p, map, LOGFMT_META_RAW_BYTES_OUT,
402 PR_JOT_LOGFMT_RAW_BYTES_OUT_KEY, PR_JSON_TYPE_NUMBER);
403 add_json_info(p, map, LOGFMT_META_EOS_REASON, PR_JOT_LOGFMT_EOS_REASON_KEY,
404 PR_JSON_TYPE_STRING);
405 add_json_info(p, map, LOGFMT_META_VHOST_IP, PR_JOT_LOGFMT_VHOST_IP_KEY,
406 PR_JSON_TYPE_STRING);
407 add_json_info(p, map, LOGFMT_META_NOTE_VAR, PR_JOT_LOGFMT_NOTE_KEY,
408 PR_JSON_TYPE_STRING);
409 add_json_info(p, map, LOGFMT_META_XFER_STATUS, PR_JOT_LOGFMT_XFER_STATUS_KEY,
410 PR_JSON_TYPE_STRING);
411 add_json_info(p, map, LOGFMT_META_XFER_FAILURE,
412 PR_JOT_LOGFMT_XFER_FAILURE_KEY, PR_JSON_TYPE_STRING);
413 add_json_info(p, map, LOGFMT_META_XFER_PORT, PR_JOT_LOGFMT_XFER_PORT_KEY,
414 PR_JSON_TYPE_NUMBER);
415 add_json_info(p, map, LOGFMT_META_XFER_TYPE, PR_JOT_LOGFMT_XFER_TYPE_KEY,
416 PR_JSON_TYPE_STRING);
417 add_json_info(p, map, LOGFMT_META_MICROSECS, PR_JOT_LOGFMT_MICROSECS_KEY,
418 PR_JSON_TYPE_NUMBER);
419 add_json_info(p, map, LOGFMT_META_MILLISECS, PR_JOT_LOGFMT_MILLISECS_KEY,
420 PR_JSON_TYPE_NUMBER);
421 add_json_info(p, map, LOGFMT_META_ISO8601, PR_JOT_LOGFMT_ISO8601_KEY,
422 PR_JSON_TYPE_STRING);
423 add_json_info(p, map, LOGFMT_META_GROUP, PR_JOT_LOGFMT_GROUP_KEY,
424 PR_JSON_TYPE_STRING);
425 add_json_info(p, map, LOGFMT_META_BASENAME, PR_JOT_LOGFMT_BASENAME_KEY,
426 PR_JSON_TYPE_STRING);
427 add_json_info(p, map, LOGFMT_META_FILE_OFFSET, PR_JOT_LOGFMT_FILE_OFFSET_KEY,
428 PR_JSON_TYPE_NUMBER);
429 add_json_info(p, map, LOGFMT_META_XFER_MS, PR_JOT_LOGFMT_XFER_MS_KEY,
430 PR_JSON_TYPE_NUMBER);
431 add_json_info(p, map, LOGFMT_META_RESPONSE_MS, PR_JOT_LOGFMT_RESPONSE_MS_KEY,
432 PR_JSON_TYPE_NUMBER);
433 add_json_info(p, map, LOGFMT_META_FILE_SIZE, PR_JOT_LOGFMT_FILE_SIZE_KEY,
434 PR_JSON_TYPE_NUMBER);
435 add_json_info(p, map, LOGFMT_META_REMOTE_PORT, PR_JOT_LOGFMT_REMOTE_PORT_KEY,
436 PR_JSON_TYPE_NUMBER);
437 add_json_info(p, map, LOGFMT_META_CONNECT, PR_JOT_LOGFMT_CONNECT_KEY,
438 PR_JSON_TYPE_BOOL);
439 add_json_info(p, map, LOGFMT_META_DISCONNECT, PR_JOT_LOGFMT_DISCONNECT_KEY,
440 PR_JSON_TYPE_BOOL);
441
442 return map;
443 }
444
pr_jot_on_json(pool * p,pr_jot_ctx_t * ctx,unsigned char logfmt_id,const char * jot_hint,const void * val)445 int pr_jot_on_json(pool *p, pr_jot_ctx_t *ctx, unsigned char logfmt_id,
446 const char *jot_hint, const void *val) {
447 int res = 0;
448 const struct logfmt_json_info *lji;
449 pr_json_object_t *json;
450 pr_table_t *logfmt_json_map;
451
452 if (p == NULL ||
453 ctx == NULL ||
454 val == NULL) {
455 errno = EINVAL;
456 return -1;
457 }
458
459 if (ctx->log == NULL) {
460 pr_trace_msg(trace_channel, 16,
461 "missing required JSON object for jotting LogFormat ID %u",
462 (unsigned int) logfmt_id);
463 errno = EINVAL;
464 return -1;
465 }
466
467 if (ctx->user_data == NULL) {
468 pr_trace_msg(trace_channel, 16,
469 "missing required JSON map for jotting LogFormat ID %u",
470 (unsigned int) logfmt_id);
471 errno = EINVAL;
472 return -1;
473 }
474
475 json = ctx->log;
476 logfmt_json_map = (pr_table_t *) ctx->user_data;
477
478 lji = pr_table_kget(logfmt_json_map, (const void *) &logfmt_id,
479 sizeof(unsigned char), NULL);
480 if (lji == NULL) {
481 pr_trace_msg(trace_channel, 16,
482 "missing required JSON information for jotting LogFormat ID %u",
483 (unsigned int) logfmt_id);
484 errno = EINVAL;
485 return -1;
486 }
487
488 pr_trace_msg(trace_channel, 18, "jotting LogFormat ID %u as JSON %s (%s)",
489 (unsigned int) logfmt_id, pr_json_type_name(lji->json_type), lji->json_key);
490
491 switch (lji->json_type) {
492 case PR_JSON_TYPE_STRING: {
493 const char *json_key;
494
495 json_key = lji->json_key;
496
497 /* Use the hinted key, if available (e.g. for ENV/NOTE variables). */
498 if (jot_hint != NULL) {
499 json_key = jot_hint;
500 }
501
502 res = pr_json_object_set_string(p, json, json_key, (const char *) val);
503 break;
504 }
505
506 case PR_JSON_TYPE_NUMBER:
507 res = pr_json_object_set_number(p, json, lji->json_key,
508 *((double *) val));
509 break;
510
511 case PR_JSON_TYPE_BOOL:
512 res = pr_json_object_set_bool(p, json, lji->json_key, *((int *) val));
513 break;
514 }
515
516 return res;
517 }
518
get_meta_arg(pool * p,unsigned char * meta,size_t * arg_len)519 static char *get_meta_arg(pool *p, unsigned char *meta, size_t *arg_len) {
520 char buf[PR_TUNABLE_PATH_MAX+1], *ptr;
521 size_t len;
522
523 ptr = buf;
524 len = 0;
525
526 while (*meta != LOGFMT_META_ARG_END) {
527 pr_signals_handle();
528 *ptr++ = (char) *meta++;
529 len++;
530 }
531
532 *ptr = '\0';
533 *arg_len = len;
534
535 return pstrndup(p, buf, len);
536 }
537
get_meta_basename(cmd_rec * cmd)538 static const char *get_meta_basename(cmd_rec *cmd) {
539 const char *base = NULL, *path = NULL;
540 pool *p;
541
542 p = cmd->tmp_pool;
543 if (pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0) {
544 path = pr_fs_decode_path(p, cmd->arg);
545
546 } else if (pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0) {
547 path = pr_table_get(cmd->notes, "mod_xfer.retr-path", NULL);
548
549 } else if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 ||
550 pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0) {
551 path = pr_table_get(cmd->notes, "mod_xfer.store-path", NULL);
552
553 } else if (session.xfer.p != NULL &&
554 session.xfer.path != NULL) {
555 path = session.xfer.path;
556
557 } else if (pr_cmd_cmp(cmd, PR_CMD_CDUP_ID) == 0 ||
558 pr_cmd_cmp(cmd, PR_CMD_PWD_ID) == 0 ||
559 pr_cmd_cmp(cmd, PR_CMD_XCUP_ID) == 0 ||
560 pr_cmd_cmp(cmd, PR_CMD_XPWD_ID) == 0) {
561 path = pr_fs_getcwd();
562
563 } else if (pr_cmd_cmp(cmd, PR_CMD_CWD_ID) == 0 ||
564 pr_cmd_cmp(cmd, PR_CMD_XCWD_ID) == 0) {
565
566 /* Note: by this point in the dispatch cycle, the current working
567 * directory has already been changed. For the CWD/XCWD commands, this
568 * means that dir_abs_path() may return an improper path, with the target
569 * directory being reported twice. To deal with this, do not use
570 * dir_abs_path(), and use pr_fs_getvwd()/pr_fs_getcwd() instead.
571 */
572 if (session.chroot_path != NULL) {
573 /* Chrooted session. */
574 path = strcmp(pr_fs_getvwd(), "/") ? pr_fs_getvwd() :
575 session.chroot_path;
576
577 } else {
578 /* Non-chrooted session. */
579 path = pr_fs_getcwd();
580 }
581
582 } else if (pr_cmd_cmp(cmd, PR_CMD_SITE_ID) == 0 &&
583 (strncasecmp(cmd->argv[1], "CHGRP", 6) == 0 ||
584 strncasecmp(cmd->argv[1], "CHMOD", 6) == 0 ||
585 strncasecmp(cmd->argv[1], "UTIME", 6) == 0)) {
586 register unsigned int i;
587 char *ptr = "";
588
589 for (i = 3; i <= cmd->argc-1; i++) {
590 ptr = pstrcat(p, ptr, *ptr ? " " : "",
591 pr_fs_decode_path(p, cmd->argv[i]), NULL);
592 }
593
594 path = ptr;
595
596 } else {
597 /* Some commands (i.e. DELE, MKD, RMD, XMKD, and XRMD) have associated
598 * filenames that are not stored in the session.xfer structure; these
599 * should be expanded properly as well.
600 */
601 if (pr_cmd_cmp(cmd, PR_CMD_DELE_ID) == 0 ||
602 pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
603 pr_cmd_cmp(cmd, PR_CMD_MDTM_ID) == 0 ||
604 pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 ||
605 pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
606 pr_cmd_cmp(cmd, PR_CMD_MLST_ID) == 0 ||
607 pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
608 pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 ||
609 pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 ||
610 pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) {
611 path = pr_fs_decode_path(p, cmd->arg);
612
613 } else if (pr_cmd_cmp(cmd, PR_CMD_MFMT_ID) == 0) {
614 /* MFMT has, as its filename, the second argument. */
615 path = pr_fs_decode_path(p, cmd->argv[2]);
616 }
617 }
618
619 if (path != NULL) {
620 char *ptr = NULL;
621
622 ptr = strrchr(path, '/');
623 if (ptr != NULL) {
624 if (ptr != path) {
625 base = ptr + 1;
626
627 } else if (*(ptr + 1) != '\0') {
628 base = ptr + 1;
629
630 } else {
631 base = path;
632 }
633
634 } else {
635 base = path;
636 }
637 }
638
639 return base;
640 }
641
get_meta_dir_name(cmd_rec * cmd)642 static const char *get_meta_dir_name(cmd_rec *cmd) {
643 const char *dir_name = NULL;
644 pool *p;
645
646 p = cmd->tmp_pool;
647
648 if (pr_cmd_cmp(cmd, PR_CMD_CDUP_ID) == 0 ||
649 pr_cmd_cmp(cmd, PR_CMD_CWD_ID) == 0 ||
650 pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
651 pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
652 pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 ||
653 pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
654 pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 ||
655 pr_cmd_cmp(cmd, PR_CMD_XCWD_ID) == 0 ||
656 pr_cmd_cmp(cmd, PR_CMD_XCUP_ID) == 0 ||
657 pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 ||
658 pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) {
659 char *path, *ptr;
660
661 path = pr_fs_decode_path(p, cmd->arg);
662 ptr = strrchr(path, '/');
663
664 if (ptr != NULL) {
665 if (ptr != path) {
666 dir_name = ptr + 1;
667
668 } else if (*(ptr + 1) != '\0') {
669 dir_name = ptr + 1;
670
671 } else {
672 dir_name = path;
673 }
674
675 } else {
676 dir_name = path;
677 }
678
679 } else {
680 dir_name = pr_fs_getvwd();
681 }
682
683 return dir_name;
684 }
685
get_meta_dir_path(cmd_rec * cmd)686 static const char *get_meta_dir_path(cmd_rec *cmd) {
687 const char *dir_path = NULL;
688 pool *p;
689
690 p = cmd->tmp_pool;
691
692 if (pr_cmd_cmp(cmd, PR_CMD_CDUP_ID) == 0 ||
693 pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
694 pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
695 pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 ||
696 pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
697 pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 ||
698 pr_cmd_cmp(cmd, PR_CMD_XCUP_ID) == 0 ||
699 pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 ||
700 pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) {
701 dir_path = dir_abs_path(p, pr_fs_decode_path(p, cmd->arg), TRUE);
702
703 } else if (pr_cmd_cmp(cmd, PR_CMD_CWD_ID) == 0 ||
704 pr_cmd_cmp(cmd, PR_CMD_XCWD_ID) == 0) {
705
706 /* Note: by this point in the dispatch cycle, the current working
707 * directory has already been changed. For the CWD/XCWD commands, this
708 * means that dir_abs_path() may return an improper path, with the target
709 * directory being reported twice. To deal with this, do not use
710 * dir_abs_path(), and use pr_fs_getvwd()/pr_fs_getcwd() instead.
711 */
712
713 if (session.chroot_path != NULL) {
714 /* Chrooted session. */
715 if (strncmp(pr_fs_getvwd(), "/", 2) == 0) {
716 dir_path = session.chroot_path;
717
718 } else {
719 dir_path = pdircat(p, session.chroot_path, pr_fs_getvwd(), NULL);
720 }
721
722 } else {
723 /* Non-chrooted session. */
724 dir_path = pr_fs_getcwd();
725 }
726 }
727
728 return dir_path;
729 }
730
get_meta_filename(cmd_rec * cmd)731 static const char *get_meta_filename(cmd_rec *cmd) {
732 const char *filename = NULL;
733 pool *p;
734
735 p = cmd->tmp_pool;
736
737 if (pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0) {
738 filename = dir_abs_path(p, pr_fs_decode_path(p, cmd->arg), TRUE);
739
740 } else if (pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0) {
741 const char *path;
742
743 path = pr_table_get(cmd->notes, "mod_xfer.retr-path", NULL);
744 if (path != NULL) {
745 filename = dir_abs_path(p, path, TRUE);
746 }
747
748 } else if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 ||
749 pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0) {
750 const char *path;
751
752 path = pr_table_get(cmd->notes, "mod_xfer.store-path", NULL);
753 if (path != NULL) {
754 filename = dir_abs_path(p, path, TRUE);
755 }
756
757 } else if (session.xfer.p != NULL &&
758 session.xfer.path != NULL) {
759 filename = dir_abs_path(p, session.xfer.path, TRUE);
760
761 } else if (pr_cmd_cmp(cmd, PR_CMD_CDUP_ID) == 0 ||
762 pr_cmd_cmp(cmd, PR_CMD_PWD_ID) == 0 ||
763 pr_cmd_cmp(cmd, PR_CMD_XCUP_ID) == 0 ||
764 pr_cmd_cmp(cmd, PR_CMD_XPWD_ID) == 0) {
765 filename = dir_abs_path(p, pr_fs_getcwd(), TRUE);
766
767 } else if (pr_cmd_cmp(cmd, PR_CMD_CWD_ID) == 0 ||
768 pr_cmd_cmp(cmd, PR_CMD_XCWD_ID) == 0) {
769
770 /* Note: by this point in the dispatch cycle, the current working
771 * directory has already been changed. For the CWD/XCWD commands, this
772 * means that dir_abs_path() may return an improper path, with the target
773 * directory being reported twice. To deal with this, do not use
774 * dir_abs_path(), and use pr_fs_getvwd()/pr_fs_getcwd() instead.
775 */
776 if (session.chroot_path != NULL) {
777 /* Chrooted session. */
778 if (strncmp(pr_fs_getvwd(), "/", 2) == 0) {
779 filename = session.chroot_path;
780
781 } else {
782 filename = pdircat(p, session.chroot_path, pr_fs_getvwd(), NULL);
783 }
784
785 } else {
786 /* Non-chrooted session. */
787 filename = pr_fs_getcwd();
788 }
789
790 } else if (pr_cmd_cmp(cmd, PR_CMD_SITE_ID) == 0 &&
791 (strncasecmp(cmd->argv[1], "CHGRP", 6) == 0 ||
792 strncasecmp(cmd->argv[1], "CHMOD", 6) == 0 ||
793 strncasecmp(cmd->argv[1], "UTIME", 6) == 0)) {
794 register unsigned int i;
795 char *ptr = "";
796
797 for (i = 3; i <= cmd->argc-1; i++) {
798 ptr = pstrcat(p, ptr, *ptr ? " " : "",
799 pr_fs_decode_path(p, cmd->argv[i]), NULL);
800 }
801
802 filename = dir_abs_path(p, ptr, TRUE);
803
804 } else {
805 /* Some commands (i.e. DELE, MKD, RMD, XMKD, and XRMD) have associated
806 * filenames that are not stored in the session.xfer structure; these
807 * should be expanded properly as well.
808 */
809 if (pr_cmd_cmp(cmd, PR_CMD_DELE_ID) == 0 ||
810 pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
811 pr_cmd_cmp(cmd, PR_CMD_MDTM_ID) == 0 ||
812 pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 ||
813 pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
814 pr_cmd_cmp(cmd, PR_CMD_MLST_ID) == 0 ||
815 pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
816 pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 ||
817 pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 ||
818 pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) {
819 char *decoded_path;
820
821 decoded_path = pr_fs_decode_path(p, cmd->arg);
822 filename = dir_abs_path(p, decoded_path, TRUE);
823 if (filename == NULL) {
824 filename = dir_abs_path(p, decoded_path, FALSE);
825 }
826
827 if (filename == NULL) {
828 filename = decoded_path;
829 }
830
831 } else if (pr_cmd_cmp(cmd, PR_CMD_MFMT_ID) == 0) {
832 char *decoded_path;
833
834 /* MFMT has, as its filename, the second argument. */
835 decoded_path = pr_fs_decode_path(p, cmd->argv[2]);
836 filename = dir_abs_path(p, decoded_path, TRUE);
837 if (filename == NULL) {
838 /* This time, try without the interpolation. */
839 filename = dir_abs_path(p, decoded_path, FALSE);
840 }
841
842 if (filename == NULL) {
843 filename = decoded_path;
844 }
845 }
846 }
847
848 return filename;
849 }
850
get_meta_transfer_failure(cmd_rec * cmd)851 static const char *get_meta_transfer_failure(cmd_rec *cmd) {
852 const char *transfer_failure = NULL;
853
854 /* If the current command is one that incurs a data transfer, then we
855 * need to do more work. If not, it's an easy substitution.
856 */
857 if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 ||
858 pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
859 pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
860 pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
861 pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0 ||
862 pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 ||
863 pr_cmd_cmp(cmd, PR_CMD_STOU_ID) == 0) {
864 const char *proto;
865
866 proto = pr_session_get_protocol(0);
867
868 if (strncmp(proto, "ftp", 4) == 0 ||
869 strncmp(proto, "ftps", 5) == 0) {
870
871 if (!(XFER_ABORTED)) {
872 int res;
873 const char *resp_code = NULL, *resp_msg = NULL;
874
875 /* Get the last response code/message. We use heuristics here to
876 * determine when to use "failed" versus "success".
877 */
878 res = pr_response_get_last(cmd->tmp_pool, &resp_code, &resp_msg);
879 if (res == 0 &&
880 resp_code != NULL) {
881 if (*resp_code != '2' &&
882 *resp_code != '1') {
883 char *ptr;
884
885 /* Parse out/prettify the resp_msg here */
886 ptr = strchr(resp_msg, '.');
887 if (ptr != NULL) {
888 transfer_failure = ptr + 2;
889
890 } else {
891 transfer_failure = resp_msg;
892 }
893 }
894 }
895 }
896 }
897 }
898
899 return transfer_failure;
900 }
901
get_meta_transfer_path(cmd_rec * cmd)902 static const char *get_meta_transfer_path(cmd_rec *cmd) {
903 const char *transfer_path = NULL;
904 pool *p;
905
906 p = cmd->tmp_pool;
907
908 if (pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0) {
909 transfer_path = dir_best_path(p, pr_fs_decode_path(p, cmd->arg));
910
911 } else if (session.xfer.p != NULL &&
912 session.xfer.path != NULL) {
913 transfer_path = session.xfer.path;
914
915 } else {
916 /* Some commands (i.e. DELE, MKD, XMKD, RMD, XRMD) have associated
917 * filenames that are not stored in the session.xfer structure; these
918 * should be expanded properly as well.
919 */
920 if (pr_cmd_cmp(cmd, PR_CMD_DELE_ID) == 0 ||
921 pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 ||
922 pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 ||
923 pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 ||
924 pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) {
925 transfer_path = dir_best_path(p, pr_fs_decode_path(p, cmd->arg));
926 }
927 }
928
929 return transfer_path;
930 }
931
get_meta_transfer_secs(cmd_rec * cmd,double * transfer_secs)932 static int get_meta_transfer_secs(cmd_rec *cmd, double *transfer_secs) {
933 if (session.xfer.p == NULL) {
934 return -1;
935 }
936
937 /* Make sure that session.xfer.start_time actually has values (which is
938 * not always the case).
939 */
940 if (session.xfer.start_time.tv_sec != 0 ||
941 session.xfer.start_time.tv_usec != 0) {
942 uint64_t start_ms = 0, end_ms = 0;
943
944 pr_timeval2millis(&(session.xfer.start_time), &start_ms);
945 pr_gettimeofday_millis(&end_ms);
946
947 *transfer_secs = (end_ms - start_ms) / 1000.0;
948 return 0;
949 }
950
951 return -1;
952 }
953
get_meta_transfer_status(cmd_rec * cmd)954 static const char *get_meta_transfer_status(cmd_rec *cmd) {
955 const char *transfer_status = NULL;
956
957 /* If the current command is one that incurs a data transfer, then we need
958 * to do more work. If not, it's an easy substitution.
959 */
960 if (pr_cmd_cmp(cmd, PR_CMD_ABOR_ID) == 0 ||
961 pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 ||
962 pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
963 pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
964 pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
965 pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0 ||
966 pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 ||
967 pr_cmd_cmp(cmd, PR_CMD_STOU_ID) == 0) {
968 const char *proto;
969
970 proto = pr_session_get_protocol(0);
971
972 if (strncmp(proto, "ftp", 4) == 0 ||
973 strncmp(proto, "ftps", 5) == 0) {
974 if (!(XFER_ABORTED)) {
975 int res;
976 const char *resp_code = NULL, *resp_msg = NULL;
977
978 /* Get the last response code/message. We use heuristics here to
979 * determine when to use "failed" versus "success".
980 */
981 res = pr_response_get_last(cmd->tmp_pool, &resp_code, &resp_msg);
982 if (res == 0 &&
983 resp_code != NULL) {
984 if (*resp_code == '2') {
985 if (pr_cmd_cmp(cmd, PR_CMD_ABOR_ID) != 0) {
986 transfer_status = "success";
987
988 } else {
989 /* We're handling the ABOR command, so obviously the value
990 * should be 'cancelled'.
991 */
992 transfer_status = "cancelled";
993 }
994
995 } else if (*resp_code == '1') {
996 /* If the first digit of the response code is 1, then the
997 * response code (for a data transfer command) is probably 150,
998 * which means that the transfer was still in progress (didn't
999 * complete with a 2xx/4xx response code) when we are called here,
1000 * which in turn means a timeout kicked in.
1001 */
1002 transfer_status = "timeout";
1003
1004 } else {
1005 transfer_status = "failed";
1006 }
1007
1008 } else {
1009 transfer_status = "success";
1010 }
1011
1012 } else {
1013 transfer_status = "cancelled";
1014 }
1015
1016 } else {
1017 /* mod_sftp stashes a note for us in the command notes if the transfer
1018 * failed.
1019 */
1020 const char *sftp_status;
1021
1022 sftp_status = pr_table_get(cmd->notes, "mod_sftp.file-status", NULL);
1023 if (sftp_status == NULL) {
1024 transfer_status = "success";
1025
1026 } else {
1027 transfer_status = "failed";
1028 }
1029 }
1030 }
1031
1032 return transfer_status;
1033 }
1034
get_meta_transfer_port(cmd_rec * cmd)1035 static int get_meta_transfer_port(cmd_rec *cmd) {
1036 int transfer_port = 0;
1037
1038 if (pr_cmd_cmp(cmd, PR_CMD_PASV_ID) == 0 ||
1039 pr_cmd_cmp(cmd, PR_CMD_PORT_ID) == 0 ||
1040 pr_cmd_cmp(cmd, PR_CMD_EPRT_ID) == 0 ||
1041 pr_cmd_cmp(cmd, PR_CMD_EPSV_ID) == 0 ||
1042 pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 ||
1043 pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
1044 pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
1045 pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
1046 pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0 ||
1047 pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 ||
1048 pr_cmd_cmp(cmd, PR_CMD_STOU_ID) == 0) {
1049 transfer_port = session.data_port;
1050 }
1051
1052 return transfer_port;
1053 }
1054
get_meta_transfer_type(cmd_rec * cmd)1055 static const char *get_meta_transfer_type(cmd_rec *cmd) {
1056 const char *transfer_type = NULL;
1057
1058 /* If the current command is one that incurs a data transfer, then we
1059 * need to do more work. If not, it's an easy substitution.
1060 */
1061 if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 ||
1062 pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
1063 pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
1064 pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
1065 pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0 ||
1066 pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 ||
1067 pr_cmd_cmp(cmd, PR_CMD_STOU_ID) == 0) {
1068 const char *proto;
1069
1070 proto = pr_session_get_protocol(0);
1071
1072 if (strncmp(proto, "sftp", 5) == 0 ||
1073 strncmp(proto, "scp", 4) == 0) {
1074
1075 /* Always binary. */
1076 transfer_type = "binary";
1077
1078 } else {
1079 if ((session.sf_flags & SF_ASCII) ||
1080 (session.sf_flags & SF_ASCII_OVERRIDE)) {
1081 transfer_type = "ASCII";
1082
1083 } else {
1084 transfer_type = "binary";
1085 }
1086 }
1087 }
1088
1089 return transfer_type;
1090 }
1091
resolve_logfmt_id(pool * p,unsigned char logfmt_id,const char * logfmt_data,pr_jot_ctx_t * ctx,cmd_rec * cmd,int (* on_meta)(pool *,pr_jot_ctx_t *,unsigned char,const char *,const void *),int (* on_default)(pool *,pr_jot_ctx_t *,unsigned char))1092 static int resolve_logfmt_id(pool *p, unsigned char logfmt_id,
1093 const char *logfmt_data, pr_jot_ctx_t *ctx, cmd_rec *cmd,
1094 int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char,
1095 const char *, const void *),
1096 int (*on_default)(pool *, pr_jot_ctx_t *, unsigned char)) {
1097 int res = 0;
1098
1099 if (pr_trace_get_level(trace_channel) >= 17) {
1100 const char *id_name;
1101
1102 id_name = pr_jot_get_logfmt_id_name(logfmt_id);
1103 if (id_name != NULL) {
1104
1105 if (logfmt_data != NULL) {
1106 pr_trace_msg(trace_channel, 17,
1107 "resolving LogFormat ID %u (%s) with data '%s' (%lu)",
1108 (unsigned int) logfmt_id, id_name, logfmt_data,
1109 (unsigned long) strlen(logfmt_data));
1110
1111 } else {
1112 pr_trace_msg(trace_channel, 17, "resolving LogFormat ID %u (%s)",
1113 (unsigned int) logfmt_id, id_name);
1114 }
1115 }
1116 }
1117
1118 switch (logfmt_id) {
1119 case LOGFMT_META_BASENAME: {
1120 const char *basename;
1121
1122 basename = get_meta_basename(cmd);
1123 if (basename != NULL) {
1124 res = (on_meta)(p, ctx, logfmt_id, NULL, basename);
1125
1126 } else {
1127 res = (on_default)(p, ctx, logfmt_id);
1128 }
1129
1130 break;
1131 }
1132
1133 case LOGFMT_META_BYTES_SENT: {
1134 double bytes_sent;
1135 int have_bytes = FALSE;
1136
1137 if (session.xfer.p != NULL) {
1138 bytes_sent = session.xfer.total_bytes;
1139 have_bytes = TRUE;
1140
1141 } else if (pr_cmd_cmp(cmd, PR_CMD_DELE_ID) == 0) {
1142 bytes_sent = jot_deleted_filesz;
1143 have_bytes = TRUE;
1144 }
1145
1146 if (have_bytes == TRUE) {
1147 res = (on_meta)(p, ctx, logfmt_id, NULL, &bytes_sent);
1148
1149 } else {
1150 res = (on_default)(p, ctx, logfmt_id);
1151 }
1152
1153 break;
1154 }
1155
1156 case LOGFMT_META_CUSTOM: {
1157 if (logfmt_data != NULL) {
1158 res = (on_meta)(p, ctx, logfmt_id, NULL, logfmt_data);
1159 }
1160
1161 break;
1162 }
1163
1164 case LOGFMT_META_EPOCH: {
1165 double epoch;
1166 struct timeval tv;
1167
1168 (void) gettimeofday(&tv, NULL);
1169 epoch = (double) tv.tv_sec;
1170 res = (on_meta)(p, ctx, logfmt_id, NULL, &epoch);
1171 break;
1172 }
1173
1174 case LOGFMT_META_FILENAME: {
1175 const char *filename;
1176
1177 filename = get_meta_filename(cmd);
1178 if (filename != NULL) {
1179 res = (on_meta)(p, ctx, logfmt_id, NULL, filename);
1180
1181 } else {
1182 res = (on_default)(p, ctx, logfmt_id);
1183 }
1184
1185 break;
1186 }
1187
1188 case LOGFMT_META_FILE_OFFSET: {
1189 const off_t *note;
1190
1191 note = pr_table_get(cmd->notes, "mod_xfer.file-offset", NULL);
1192 if (note != NULL) {
1193 double file_offset;
1194
1195 file_offset = (double) *note;;
1196 res = (on_meta)(p, ctx, logfmt_id, NULL, &file_offset);
1197
1198 } else {
1199 res = (on_default)(p, ctx, logfmt_id);
1200 }
1201
1202 break;
1203 }
1204
1205 case LOGFMT_META_FILE_SIZE: {
1206 const off_t *note;
1207
1208 note = pr_table_get(cmd->notes, "mod_xfer.file-size", NULL);
1209 if (note != NULL) {
1210 double file_size;
1211
1212 file_size = (double) *note;
1213 res = (on_meta)(p, ctx, logfmt_id, NULL, &file_size);
1214
1215 } else {
1216 res = (on_default)(p, ctx, logfmt_id);
1217 }
1218
1219 break;
1220 }
1221
1222 case LOGFMT_META_ENV_VAR: {
1223 if (logfmt_data != NULL) {
1224 const char *key;
1225 char *env;
1226
1227 key = logfmt_data;
1228 env = pr_env_get(p, key);
1229 if (env != NULL) {
1230 char *field_name;
1231
1232 field_name = pstrcat(p, PR_JOT_LOGFMT_ENV_VAR_KEY, key, NULL);
1233 res = (on_meta)(p, ctx, logfmt_id, field_name, env);
1234
1235 } else {
1236 res = (on_default)(p, ctx, logfmt_id);
1237 }
1238 }
1239
1240 break;
1241 }
1242
1243 case LOGFMT_META_REMOTE_HOST: {
1244 const char *name;
1245
1246 name = pr_netaddr_get_sess_remote_name();
1247 res = (on_meta)(p, ctx, logfmt_id, NULL, name);
1248 break;
1249 }
1250
1251 case LOGFMT_META_REMOTE_IP: {
1252 const char *ipstr;
1253
1254 ipstr = pr_netaddr_get_ipstr(pr_netaddr_get_sess_remote_addr());
1255 res = (on_meta)(p, ctx, logfmt_id, NULL, ipstr);
1256 break;
1257 }
1258
1259 case LOGFMT_META_REMOTE_PORT: {
1260 double client_port;
1261 const pr_netaddr_t *remote_addr;
1262
1263 remote_addr = pr_netaddr_get_sess_remote_addr();
1264 if (remote_addr != NULL) {
1265 client_port = ntohs(pr_netaddr_get_port(remote_addr));
1266 res = (on_meta)(p, ctx, logfmt_id, NULL, &client_port);
1267
1268 } else {
1269 res = (on_default)(p, ctx, logfmt_id);
1270 }
1271
1272 break;
1273 }
1274
1275 case LOGFMT_META_IDENT_USER: {
1276 const char *ident_user;
1277
1278 ident_user = pr_table_get(session.notes, "mod_ident.rfc1413-ident", NULL);
1279 if (ident_user != NULL) {
1280 res = (on_meta)(p, ctx, logfmt_id, NULL, ident_user);
1281
1282 } else {
1283 res = (on_default)(p, ctx, logfmt_id);
1284 }
1285
1286 break;
1287 }
1288
1289 case LOGFMT_META_PID: {
1290 double sess_pid;
1291
1292 sess_pid = session.pid;
1293 res = (on_meta)(p, ctx, logfmt_id, NULL, &sess_pid);
1294 break;
1295 }
1296
1297 case LOGFMT_META_TIME: {
1298 struct tm *tm;
1299 time_t now;
1300
1301 now = time(NULL);
1302 tm = pr_gmtime(p, &now);
1303 if (tm != NULL) {
1304 char ts[128];
1305 const char *time_fmt = "%Y-%m-%d %H:%M:%S %z";
1306
1307 if (logfmt_data != NULL) {
1308 time_fmt = logfmt_data;
1309 }
1310
1311 strftime(ts, sizeof(ts)-1, time_fmt, tm);
1312 res = (on_meta)(p, ctx, logfmt_id, logfmt_data, ts);
1313
1314 } else {
1315 res = (on_default)(p, ctx, logfmt_id);
1316 }
1317
1318 break;
1319 }
1320
1321 case LOGFMT_META_SECONDS: {
1322 double transfer_secs;
1323
1324 if (get_meta_transfer_secs(cmd, &transfer_secs) == 0) {
1325 res = (on_meta)(p, ctx, logfmt_id, NULL, &transfer_secs);
1326
1327 } else {
1328 res = (on_default)(p, ctx, logfmt_id);
1329 }
1330
1331 break;
1332 }
1333
1334 case LOGFMT_META_COMMAND: {
1335 const char *full_cmd;
1336
1337 /* Note: Ignore "fake" commands like CONNECT, DISCONNECT, EXIT. */
1338 if ((cmd->cmd_class & CL_CONNECT) ||
1339 (cmd->cmd_class & CL_DISCONNECT)) {
1340 full_cmd = NULL;
1341
1342 } else {
1343 if (pr_cmd_cmp(cmd, PR_CMD_PASS_ID) == 0 &&
1344 session.hide_password) {
1345 full_cmd = "PASS (hidden)";
1346
1347 } else if (pr_cmd_cmp(cmd, PR_CMD_ADAT_ID) == 0) {
1348 full_cmd = "ADAT (hidden)";
1349
1350 } else {
1351 full_cmd = get_full_cmd(cmd);
1352 }
1353 }
1354
1355 if (full_cmd != NULL) {
1356 res = (on_meta)(p, ctx, logfmt_id, NULL, full_cmd);
1357
1358 } else {
1359 res = (on_default)(p, ctx, logfmt_id);
1360 }
1361
1362 break;
1363 }
1364
1365 case LOGFMT_META_LOCAL_NAME: {
1366 if (cmd->server != NULL) {
1367 res = (on_meta)(p, ctx, logfmt_id, NULL, cmd->server->ServerName);
1368
1369 } else {
1370 res = (on_default)(p, ctx, logfmt_id);
1371 }
1372
1373 break;
1374 }
1375
1376 case LOGFMT_META_LOCAL_PORT: {
1377 if (cmd->server != NULL) {
1378 double server_port;
1379
1380 server_port = cmd->server->ServerPort;
1381 res = (on_meta)(p, ctx, logfmt_id, NULL, &server_port);
1382
1383 } else {
1384 res = (on_default)(p, ctx, logfmt_id);
1385 }
1386
1387 break;
1388 }
1389
1390 case LOGFMT_META_LOCAL_IP: {
1391 const char *ipstr;
1392
1393 ipstr = pr_netaddr_get_ipstr(pr_netaddr_get_sess_local_addr());
1394 res = (on_meta)(p, ctx, logfmt_id, NULL, ipstr);
1395 break;
1396 }
1397
1398 case LOGFMT_META_LOCAL_FQDN: {
1399 const char *dnsstr;
1400
1401 dnsstr = pr_netaddr_get_dnsstr(pr_netaddr_get_sess_local_addr());
1402 res = (on_meta)(p, ctx, logfmt_id, NULL, dnsstr);
1403 break;
1404 }
1405
1406 case LOGFMT_META_USER: {
1407 if (session.user != NULL) {
1408 res = (on_meta)(p, ctx, logfmt_id, NULL, session.user);
1409
1410 } else {
1411 res = (on_default)(p, ctx, logfmt_id);
1412 }
1413
1414 break;
1415 }
1416
1417 case LOGFMT_META_ORIGINAL_USER: {
1418 const char *orig_user = NULL;
1419
1420 orig_user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
1421 if (orig_user != NULL) {
1422 res = (on_meta)(p, ctx, logfmt_id, NULL, orig_user);
1423
1424 } else {
1425 res = (on_default)(p, ctx, logfmt_id);
1426 }
1427
1428 break;
1429 }
1430
1431 case LOGFMT_META_RESPONSE_CODE: {
1432 const char *resp_code = NULL;
1433 double resp_num;
1434 int have_code = FALSE, last;
1435
1436 last = pr_response_get_last(cmd->tmp_pool, &resp_code, NULL);
1437 if (last == 0 &&
1438 resp_code != NULL) {
1439 resp_num = atoi(resp_code);
1440 have_code = TRUE;
1441
1442 /* Hack to add return code for proper logging of QUIT command. */
1443 } else if (pr_cmd_cmp(cmd, PR_CMD_QUIT_ID) == 0) {
1444 resp_num = 221;
1445 have_code = TRUE;
1446 }
1447
1448 if (have_code == TRUE) {
1449 res = (on_meta)(p, ctx, logfmt_id, NULL, &resp_num);
1450
1451 } else {
1452 res = (on_default)(p, ctx, logfmt_id);
1453 }
1454
1455 break;
1456 }
1457
1458 case LOGFMT_META_CLASS: {
1459 if (session.conn_class != NULL) {
1460 res = (on_meta)(p, ctx, logfmt_id, NULL, session.conn_class);
1461
1462 } else {
1463 res = (on_default)(p, ctx, logfmt_id);
1464 }
1465
1466 break;
1467 }
1468
1469 case LOGFMT_META_ANON_PASS: {
1470 const char *anon_pass;
1471
1472 anon_pass = pr_table_get(session.notes, "mod_auth.anon-passwd", NULL);
1473 if (anon_pass != NULL) {
1474 res = (on_meta)(p, ctx, logfmt_id, NULL, anon_pass);
1475
1476 } else {
1477 res = (on_default)(p, ctx, logfmt_id);
1478 }
1479
1480 break;
1481 }
1482
1483 case LOGFMT_META_METHOD: {
1484 const char *method = NULL;
1485
1486 if (pr_cmd_cmp(cmd, PR_CMD_SITE_ID) != 0) {
1487 /* Note: Ignore "fake" commands like CONNECT, but NOT DISCONNECT/EXIT.
1488 * This is for backward compatibility, for better/worse.
1489 */
1490 if (!(cmd->cmd_class & CL_CONNECT)) {
1491 method = cmd->argv[0];
1492 }
1493
1494 } else {
1495 char buf[128], *ch;
1496 size_t len;
1497
1498 /* Make sure that the SITE command used is all in uppercase, for
1499 * logging purposes.
1500 */
1501 for (ch = cmd->argv[1]; *ch; ch++) {
1502 *ch = toupper((int) *ch);
1503 }
1504
1505 len = pr_snprintf(buf, sizeof(buf)-1, "%s %s", (char *) cmd->argv[0],
1506 (char *) cmd->argv[1]);
1507
1508 method = pstrndup(p, buf, len);
1509 }
1510
1511 if (method != NULL) {
1512 res = (on_meta)(p, ctx, logfmt_id, NULL, method);
1513
1514 } else {
1515 res = (on_default)(p, ctx, logfmt_id);
1516 }
1517
1518 break;
1519 }
1520
1521 case LOGFMT_META_XFER_PATH: {
1522 const char *transfer_path;
1523
1524 transfer_path = get_meta_transfer_path(cmd);
1525 if (transfer_path != NULL) {
1526 res = (on_meta)(p, ctx, logfmt_id, NULL, transfer_path);
1527
1528 } else {
1529 res = (on_default)(p, ctx, logfmt_id);
1530 }
1531
1532 break;
1533 }
1534
1535 case LOGFMT_META_DIR_NAME: {
1536 const char *dir_name;
1537
1538 dir_name = get_meta_dir_name(cmd);
1539 if (dir_name != NULL) {
1540 res = (on_meta)(p, ctx, logfmt_id, NULL, dir_name);
1541
1542 } else {
1543 res = (on_default)(p, ctx, logfmt_id);
1544 }
1545
1546 break;
1547 }
1548
1549 case LOGFMT_META_DIR_PATH: {
1550 const char *dir_path;
1551
1552 dir_path = get_meta_dir_path(cmd);
1553 if (dir_path != NULL) {
1554 res = (on_meta)(p, ctx, logfmt_id, NULL, dir_path);
1555
1556 } else {
1557 res = (on_default)(p, ctx, logfmt_id);
1558 }
1559
1560 break;
1561 }
1562
1563 case LOGFMT_META_CMD_PARAMS: {
1564 const char *params = NULL;
1565
1566 /* Note: Ignore "fake" commands like CONNECT, DISCONNECT, EXIT. */
1567 if ((cmd->cmd_class & CL_CONNECT) ||
1568 (cmd->cmd_class & CL_DISCONNECT)) {
1569 params = NULL;
1570
1571 } else {
1572 if (pr_cmd_cmp(cmd, PR_CMD_ADAT_ID) == 0 ||
1573 pr_cmd_cmp(cmd, PR_CMD_PASS_ID) == 0) {
1574 params = "(hidden)";
1575
1576 } else if (cmd->argc > 1) {
1577 params = pr_fs_decode_path(p, cmd->arg);
1578 }
1579 }
1580
1581 if (params != NULL) {
1582 res = (on_meta)(p, ctx, logfmt_id, NULL, params);
1583
1584 } else {
1585 res = (on_default)(p, ctx, logfmt_id);
1586 }
1587
1588 break;
1589 }
1590
1591 case LOGFMT_META_RESPONSE_STR: {
1592 const char *resp_msg = NULL;
1593 int last;
1594
1595 last = pr_response_get_last(p, NULL, &resp_msg);
1596 if (last == 0 &&
1597 resp_msg != NULL) {
1598 res = (on_meta)(p, ctx, logfmt_id, NULL, resp_msg);
1599
1600 } else {
1601 res = (on_default)(p, ctx, logfmt_id);
1602 }
1603
1604 break;
1605 }
1606
1607 case LOGFMT_META_RESPONSE_MS: {
1608 const uint64_t *start_ms;
1609
1610 start_ms = pr_table_get(cmd->notes, "start_ms", NULL);
1611 if (start_ms != NULL) {
1612 uint64_t end_ms = 0;
1613 double response_ms;
1614
1615 pr_gettimeofday_millis(&end_ms);
1616
1617 response_ms = end_ms - *start_ms;
1618 res = (on_meta)(p, ctx, logfmt_id, NULL, &response_ms);
1619
1620 } else {
1621 res = (on_default)(p, ctx, logfmt_id);
1622 }
1623
1624 break;
1625 }
1626
1627 case LOGFMT_META_PROTOCOL: {
1628 const char *proto;
1629
1630 proto = pr_session_get_protocol(0);
1631 res = (on_meta)(p, ctx, logfmt_id, NULL, proto);
1632 break;
1633 }
1634
1635 case LOGFMT_META_VERSION: {
1636 const char *version;
1637
1638 version = PROFTPD_VERSION_TEXT;
1639 res = (on_meta)(p, ctx, logfmt_id, NULL, version);
1640 break;
1641 }
1642
1643 case LOGFMT_META_RENAME_FROM: {
1644 if (pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0) {
1645 const char *rnfr_path;
1646
1647 rnfr_path = pr_table_get(session.notes, "mod_core.rnfr-path", NULL);
1648 if (rnfr_path != NULL) {
1649 res = (on_meta)(p, ctx, logfmt_id, NULL, rnfr_path);
1650
1651 } else {
1652 res = (on_default)(p, ctx, logfmt_id);
1653 }
1654
1655 } else {
1656 res = (on_default)(p, ctx, logfmt_id);
1657 }
1658
1659 break;
1660 }
1661
1662 case LOGFMT_META_FILE_MODIFIED: {
1663 int modified = FALSE;
1664 const char *val;
1665
1666 val = pr_table_get(cmd->notes, "mod_xfer.file-modified", NULL);
1667 if (val != NULL) {
1668 if (strncmp(val, "true", 5) == 0) {
1669 modified = TRUE;
1670 }
1671 }
1672
1673 res = (on_meta)(p, ctx, logfmt_id, NULL, &modified);
1674 break;
1675 }
1676
1677 case LOGFMT_META_UID: {
1678 double sess_uid;
1679
1680 if (session.auth_mech != NULL) {
1681 sess_uid = session.login_uid;
1682
1683 } else {
1684 sess_uid = geteuid();
1685 }
1686
1687 res = (on_meta)(p, ctx, logfmt_id, NULL, &sess_uid);
1688 break;
1689 }
1690
1691 case LOGFMT_META_GID: {
1692 double sess_gid;
1693
1694 if (session.auth_mech != NULL) {
1695 sess_gid = session.login_gid;
1696
1697 } else {
1698 sess_gid = getegid();
1699 }
1700
1701 res = (on_meta)(p, ctx, logfmt_id, NULL, &sess_gid);
1702 break;
1703 }
1704
1705 case LOGFMT_META_RAW_BYTES_IN: {
1706 double bytes_rcvd;
1707
1708 bytes_rcvd = session.total_raw_in;
1709 res = (on_meta)(p, ctx, logfmt_id, NULL, &bytes_rcvd);
1710 break;
1711 }
1712
1713 case LOGFMT_META_RAW_BYTES_OUT: {
1714 double bytes_sent;
1715
1716 bytes_sent = session.total_raw_out;
1717 res = (on_meta)(p, ctx, logfmt_id, NULL, &bytes_sent);
1718 break;
1719 }
1720
1721 case LOGFMT_META_EOS_REASON: {
1722 const char *reason = NULL, *details = NULL, *eos = NULL;
1723
1724 if (session.disconnect_reason != PR_SESS_DISCONNECT_UNSPECIFIED) {
1725 eos = pr_session_get_disconnect_reason(&details);
1726 if (eos != NULL) {
1727 if (details != NULL) {
1728 reason = pstrcat(p, eos, ": ", details, NULL);
1729
1730 } else {
1731 reason = eos;
1732 }
1733 }
1734 }
1735
1736 if (reason != NULL) {
1737 res = (on_meta)(p, ctx, logfmt_id, NULL, reason);
1738
1739 } else {
1740 res = (on_default)(p, ctx, logfmt_id);
1741 }
1742
1743 break;
1744 }
1745
1746 case LOGFMT_META_VHOST_IP:
1747 if (cmd->server != NULL) {
1748 res = (on_meta)(p, ctx, logfmt_id, NULL, cmd->server->ServerAddress);
1749
1750 } else {
1751 res = (on_default)(p, ctx, logfmt_id);
1752 }
1753
1754 break;
1755
1756 case LOGFMT_META_NOTE_VAR: {
1757 if (logfmt_data != NULL) {
1758 const char *note = NULL;
1759
1760 pr_trace_msg(trace_channel, 19,
1761 "resolving NOTE_VAR using note key '%s'", logfmt_data);
1762
1763 /* Check in the cmd->notes table first. */
1764 note = pr_table_get(cmd->notes, logfmt_data, NULL);
1765 if (note == NULL) {
1766
1767 /* If not there, check in the session.notes table. */
1768 note = pr_table_get(session.notes, logfmt_data, NULL);
1769 }
1770
1771 if (note != NULL) {
1772 char *field_name;
1773
1774 field_name = pstrcat(p, PR_JOT_LOGFMT_NOTE_KEY, note, NULL);
1775 res = (on_meta)(p, ctx, logfmt_id, field_name, note);
1776
1777 } else {
1778 res = (on_default)(p, ctx, logfmt_id);
1779 }
1780 }
1781
1782 break;
1783 }
1784
1785 case LOGFMT_META_XFER_STATUS: {
1786 const char *transfer_status;
1787
1788 transfer_status = get_meta_transfer_status(cmd);
1789 if (transfer_status != NULL) {
1790 res = (on_meta)(p, ctx, logfmt_id, NULL, transfer_status);
1791
1792 } else {
1793 res = (on_default)(p, ctx, logfmt_id);
1794 }
1795
1796 break;
1797 }
1798
1799 case LOGFMT_META_XFER_FAILURE: {
1800 const char *transfer_failure;
1801
1802 transfer_failure = get_meta_transfer_failure(cmd);
1803 if (transfer_failure != NULL) {
1804 res = (on_meta)(p, ctx, logfmt_id, NULL, transfer_failure);
1805
1806 } else {
1807 res = (on_default)(p, ctx, logfmt_id);
1808 }
1809
1810 break;
1811 }
1812
1813 case LOGFMT_META_XFER_MS: {
1814 if (session.xfer.p != NULL) {
1815 /* Make sure that session.xfer.start_time actually has values (which
1816 * is not always the case).
1817 */
1818 if (session.xfer.start_time.tv_sec != 0 ||
1819 session.xfer.start_time.tv_usec != 0) {
1820 uint64_t start_ms = 0, end_ms = 0;
1821 double transfer_ms;
1822
1823 pr_timeval2millis(&(session.xfer.start_time), &start_ms);
1824 pr_gettimeofday_millis(&end_ms);
1825
1826 transfer_ms = end_ms - start_ms;
1827 res = (on_meta)(p, ctx, logfmt_id, NULL, &transfer_ms);
1828
1829 } else {
1830 res = (on_default)(p, ctx, logfmt_id);
1831 }
1832
1833 } else {
1834 res = (on_default)(p, ctx, logfmt_id);
1835 }
1836
1837 break;
1838 }
1839
1840 case LOGFMT_META_XFER_PORT: {
1841 int transfer_port;
1842
1843 transfer_port = get_meta_transfer_port(cmd);
1844 if (transfer_port > 0) {
1845 double xfer_port;
1846
1847 xfer_port = (double) transfer_port;
1848 res = (on_meta)(p, ctx, logfmt_id, NULL, &xfer_port);
1849
1850 } else {
1851 res = (on_default)(p, ctx, logfmt_id);
1852 }
1853
1854 break;
1855 }
1856
1857 case LOGFMT_META_XFER_TYPE: {
1858 const char *transfer_type;
1859
1860 transfer_type = get_meta_transfer_type(cmd);
1861 if (transfer_type != NULL) {
1862 res = (on_meta)(p, ctx, logfmt_id, NULL, transfer_type);
1863
1864 } else {
1865 res = (on_default)(p, ctx, logfmt_id);
1866 }
1867
1868 break;
1869 }
1870
1871 case LOGFMT_META_MICROSECS: {
1872 double sess_usecs;
1873 struct timeval now;
1874
1875 gettimeofday(&now, NULL);
1876 sess_usecs = now.tv_usec;
1877
1878 res = (on_meta)(p, ctx, logfmt_id, NULL, &sess_usecs);
1879 break;
1880 }
1881
1882 case LOGFMT_META_MILLISECS: {
1883 double sess_msecs;
1884 struct timeval now;
1885
1886 gettimeofday(&now, NULL);
1887
1888 /* Convert microsecs to millisecs. */
1889 sess_msecs = (now.tv_usec / 1000);
1890
1891 res = (on_meta)(p, ctx, logfmt_id, NULL, &sess_msecs);
1892 break;
1893 }
1894
1895 case LOGFMT_META_ISO8601: {
1896 struct tm *tm;
1897 struct timeval now;
1898
1899 gettimeofday(&now, NULL);
1900 tm = pr_localtime(p, (const time_t *) &(now.tv_sec));
1901 if (tm != NULL) {
1902 char ts[128];
1903 size_t len;
1904 unsigned long millis;
1905
1906 len = strftime(ts, sizeof(ts)-1, "%Y-%m-%d %H:%M:%S", tm);
1907
1908 /* Convert microsecs to millisecs. */
1909 millis = now.tv_usec / 1000;
1910
1911 pr_snprintf(ts + len, sizeof(ts) - len - 1, ",%03lu", millis);
1912 res = (on_meta)(p, ctx, logfmt_id, NULL, ts);
1913
1914 } else {
1915 res = (on_default)(p, ctx, logfmt_id);
1916 }
1917
1918 break;
1919 }
1920
1921 case LOGFMT_META_GROUP: {
1922 if (session.group != NULL) {
1923 res = (on_meta)(p, ctx, logfmt_id, NULL, session.group);
1924
1925 } else {
1926 res = (on_default)(p, ctx, logfmt_id);
1927 }
1928
1929 break;
1930 }
1931
1932 default:
1933 pr_trace_msg(trace_channel, 2, "skipping unsupported LogFormat ID %u",
1934 (unsigned int) logfmt_id);
1935 break;
1936 }
1937
1938 if (res < 0) {
1939 return -1;
1940 }
1941
1942 return 0;
1943 }
1944
resolve_meta(pool * p,unsigned char ** logfmt,pr_jot_ctx_t * ctx,cmd_rec * cmd,int (* on_meta)(pool *,pr_jot_ctx_t *,unsigned char,const char *,const void *),int (* on_default)(pool *,pr_jot_ctx_t *,unsigned char))1945 static int resolve_meta(pool *p, unsigned char **logfmt, pr_jot_ctx_t *ctx,
1946 cmd_rec *cmd,
1947 int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *,
1948 const void *),
1949 int (*on_default)(pool *, pr_jot_ctx_t *, unsigned char)) {
1950 int res = 0;
1951 unsigned char *ptr, logfmt_id;
1952 const char *logfmt_data = NULL;
1953 size_t consumed_bytes = 0, logfmt_datalen = 0;
1954
1955 ptr = (*logfmt) + 1;
1956 logfmt_id = *ptr;
1957
1958 switch (logfmt_id) {
1959 case LOGFMT_META_CUSTOM:
1960 case LOGFMT_META_ENV_VAR:
1961 case LOGFMT_META_NOTE_VAR:
1962 case LOGFMT_META_TIME: {
1963 if (*(ptr + 1) == LOGFMT_META_START &&
1964 *(ptr + 2) == LOGFMT_META_ARG) {
1965 logfmt_data = get_meta_arg(p, (ptr + 3), &logfmt_datalen);
1966
1967 /* Skip past the META_START, META_ARG, META_ARG_END, and the data. */
1968 consumed_bytes += (3 + logfmt_datalen);
1969 }
1970 }
1971
1972 default:
1973 consumed_bytes += 1;
1974 }
1975
1976 /* Note: the LogFormat data, if present, is always text. Callbacks ASSUME
1977 * that that text will be a NUL-terminated string.
1978 */
1979 logfmt_data = pstrndup(p, logfmt_data, logfmt_datalen);
1980
1981 res = resolve_logfmt_id(p, logfmt_id, logfmt_data, ctx, cmd, on_meta,
1982 on_default);
1983 if (res < 0) {
1984 return -1;
1985 }
1986
1987 /* Most of the time, a meta is encoded in just one byte, so we adjust the
1988 * pointer by incrementing by one. Some meta are encoded using multiple
1989 * bytes (e.g. environment variables, notes, etc). The resolving of these
1990 * meta will adjust the `consumed_bytes` value themselves.
1991 */
1992
1993 ptr += consumed_bytes;
1994 *logfmt = ptr;
1995 return 0;
1996 }
1997
is_jottable_class(cmd_rec * cmd,int included_classes,int excluded_classes)1998 static int is_jottable_class(cmd_rec *cmd, int included_classes,
1999 int excluded_classes) {
2000 int jottable = FALSE;
2001
2002 if (cmd->cmd_class != 0) {
2003 /* If the command is unknown, then we only want to log if this filter is
2004 * configured to log ALL commands (Bug#4313).
2005 */
2006 if (cmd->cmd_id >= 0) {
2007 if (cmd->cmd_class & included_classes) {
2008 jottable = TRUE;
2009 }
2010
2011 if (cmd->cmd_class & excluded_classes) {
2012 jottable = FALSE;
2013 }
2014
2015 } else {
2016 /* Handle unknown command. The "CONNECT" and "EXIT" commands are
2017 * internally generated, and thus have special treatment.
2018 */
2019
2020 if ((cmd->cmd_class & CL_CONNECT) ||
2021 (cmd->cmd_class & CL_DISCONNECT)) {
2022 if (cmd->cmd_class & included_classes) {
2023 jottable = TRUE;
2024 }
2025
2026 if (cmd->cmd_class & excluded_classes) {
2027 jottable = FALSE;
2028 }
2029
2030 } else {
2031 if (included_classes == CL_ALL) {
2032 jottable = TRUE;
2033 }
2034 }
2035 }
2036
2037 } else {
2038 /* If the logging class of this command is unknown (defaults to zero),
2039 * AND this filter logs ALL events, it is jottable.
2040 */
2041 if (included_classes == CL_ALL) {
2042 jottable = TRUE;
2043 }
2044 }
2045
2046 return jottable;
2047 }
2048
is_jottable_cmd(cmd_rec * cmd,int * cmd_ids,size_t ncmd_ids)2049 static int is_jottable_cmd(cmd_rec *cmd, int *cmd_ids, size_t ncmd_ids) {
2050 register unsigned int i;
2051 int jottable = FALSE;
2052
2053 for (i = 0; i < ncmd_ids; i++) {
2054 if (pr_cmd_cmp(cmd, cmd_ids[i]) == 0) {
2055 jottable = TRUE;
2056 break;
2057 }
2058 }
2059
2060 return jottable;
2061 }
2062
is_jottable(pool * p,cmd_rec * cmd,pr_jot_filters_t * filters)2063 static int is_jottable(pool *p, cmd_rec *cmd, pr_jot_filters_t *filters) {
2064 int jottable = FALSE;
2065
2066 if (filters == NULL) {
2067 return TRUE;
2068 }
2069
2070 jottable = is_jottable_class(cmd, filters->included_classes,
2071 filters->excluded_classes);
2072 if (jottable == TRUE) {
2073 return TRUE;
2074 }
2075
2076 if (filters->cmd_ids != NULL) {
2077 jottable = is_jottable_cmd(cmd, filters->cmd_ids->elts,
2078 filters->cmd_ids->nelts);
2079 }
2080
2081 return jottable;
2082 }
2083
jot_resolve_on_default(pool * p,pr_jot_ctx_t * ctx,unsigned char meta)2084 static int jot_resolve_on_default(pool *p, pr_jot_ctx_t *ctx,
2085 unsigned char meta) {
2086 return 0;
2087 }
2088
jot_resolve_on_other(pool * p,pr_jot_ctx_t * ctx,unsigned char * text,size_t text_len)2089 static int jot_resolve_on_other(pool *p, pr_jot_ctx_t *ctx, unsigned char *text,
2090 size_t text_len) {
2091 return 0;
2092 }
2093
pr_jot_resolve_logfmt_id(pool * p,cmd_rec * cmd,pr_jot_filters_t * filters,unsigned char logfmt_id,const char * logfmt_data,size_t logfmt_datalen,pr_jot_ctx_t * ctx,int (* on_meta)(pool *,pr_jot_ctx_t *,unsigned char,const char *,const void *),int (* on_default)(pool *,pr_jot_ctx_t *,unsigned char))2094 int pr_jot_resolve_logfmt_id(pool *p, cmd_rec *cmd, pr_jot_filters_t *filters,
2095 unsigned char logfmt_id, const char *logfmt_data, size_t logfmt_datalen,
2096 pr_jot_ctx_t *ctx,
2097 int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *,
2098 const void *),
2099 int (*on_default)(pool *, pr_jot_ctx_t *, unsigned char)) {
2100 int jottable = FALSE, res = 0;
2101
2102 if (p == NULL ||
2103 cmd == NULL ||
2104 on_meta == NULL) {
2105 errno = EINVAL;
2106 return -1;
2107 }
2108
2109 /* There are some IDs which are reserved. */
2110 if (logfmt_id == 0 ||
2111 logfmt_id == LOGFMT_META_START ||
2112 logfmt_id == LOGFMT_META_ARG_END) {
2113 errno = EINVAL;
2114 return -1;
2115 }
2116
2117 if (on_default == NULL) {
2118 on_default = jot_resolve_on_default;
2119 }
2120
2121 jottable = is_jottable(p, cmd, filters);
2122 if (jottable == FALSE) {
2123 pr_trace_msg(trace_channel, 17, "ignoring filtered event '%s'",
2124 (const char *) cmd->argv[0]);
2125 errno = EPERM;
2126 return -1;
2127 }
2128
2129 /* Special handling for the CONNECT/DISCONNECT meta. */
2130 switch (logfmt_id) {
2131 case LOGFMT_META_CONNECT: {
2132 pr_trace_msg(trace_channel, 17, "resolving LogFormat ID %u (%s)",
2133 (unsigned int) logfmt_id, pr_jot_get_logfmt_id_name(logfmt_id));
2134 if (cmd->cmd_class == CL_CONNECT) {
2135 int val = TRUE;
2136 res = (on_meta)(p, ctx, LOGFMT_META_CONNECT, NULL, &val);
2137 }
2138
2139 return res;
2140 }
2141
2142 case LOGFMT_META_DISCONNECT: {
2143 pr_trace_msg(trace_channel, 17, "resolving LogFormat ID %u (%s)",
2144 (unsigned int) logfmt_id, pr_jot_get_logfmt_id_name(logfmt_id));
2145 if (cmd->cmd_class == CL_DISCONNECT) {
2146 int val = TRUE;
2147 res = (on_meta)(p, ctx, LOGFMT_META_DISCONNECT, NULL, &val);
2148 }
2149 return res;
2150 }
2151
2152 default:
2153 break;
2154 }
2155
2156 res = resolve_logfmt_id(p, logfmt_id, logfmt_data, ctx, cmd, on_meta,
2157 on_default);
2158 return res;
2159 }
2160
pr_jot_resolve_logfmt(pool * p,cmd_rec * cmd,pr_jot_filters_t * filters,unsigned char * logfmt,pr_jot_ctx_t * ctx,int (* on_meta)(pool *,pr_jot_ctx_t *,unsigned char,const char *,const void *),int (* on_default)(pool *,pr_jot_ctx_t *,unsigned char),int (* on_other)(pool *,pr_jot_ctx_t *,unsigned char *,size_t))2161 int pr_jot_resolve_logfmt(pool *p, cmd_rec *cmd, pr_jot_filters_t *filters,
2162 unsigned char *logfmt, pr_jot_ctx_t *ctx,
2163 int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *,
2164 const void *),
2165 int (*on_default)(pool *, pr_jot_ctx_t *, unsigned char),
2166 int (*on_other)(pool *, pr_jot_ctx_t *, unsigned char *, size_t)) {
2167 int jottable = FALSE, res;
2168 size_t text_len;
2169
2170 if (p == NULL ||
2171 cmd == NULL ||
2172 logfmt == NULL ||
2173 on_meta == NULL) {
2174 errno = EINVAL;
2175 return -1;
2176 }
2177
2178 jottable = is_jottable(p, cmd, filters);
2179 if (jottable == FALSE) {
2180 pr_trace_msg(trace_channel, 17, "ignoring filtered event '%s'",
2181 (const char *) cmd->argv[0]);
2182 errno = EPERM;
2183 return -1;
2184 }
2185
2186 if (on_default == NULL) {
2187 on_default = jot_resolve_on_default;
2188 }
2189
2190 if (on_other == NULL) {
2191 on_other = jot_resolve_on_other;
2192 }
2193
2194 text_len = 0;
2195 res = 0;
2196
2197 while (*logfmt) {
2198 pr_signals_handle();
2199
2200 if (res < 0) {
2201 return -1;
2202 }
2203
2204 /* Scan the buffer until we reach a variable. Keep track of how much
2205 * we've scanned, so that that entire segment of text can be given
2206 * to the `on_other` callback at once.
2207 */
2208 if (*logfmt != LOGFMT_META_START) {
2209 logfmt++;
2210 text_len++;
2211 continue;
2212 }
2213
2214 if (text_len > 0) {
2215 res = (on_other)(p, ctx, logfmt - text_len, text_len);
2216 if (res < 0) {
2217 return -1;
2218 }
2219
2220 /* Reset our non-variable segment length for the next iteration. */
2221 text_len = 0;
2222 }
2223
2224 /* Special handling for the CONNECT/DISCONNECT meta. */
2225 switch (*(logfmt + 1)) {
2226 case LOGFMT_META_CONNECT:
2227 if (cmd->cmd_class == CL_CONNECT) {
2228 int val = TRUE;
2229 pr_trace_msg(trace_channel, 17, "resolving LogFormat ID %u (%s)",
2230 LOGFMT_META_CONNECT,
2231 pr_jot_get_logfmt_id_name(LOGFMT_META_CONNECT));
2232 res = (on_meta)(p, ctx, LOGFMT_META_CONNECT, NULL, &val);
2233 }
2234
2235 /* Don't forget to advance past the META_START and META_CONNECT. */
2236 logfmt += 2;
2237 break;
2238
2239 case LOGFMT_META_DISCONNECT:
2240 if (cmd->cmd_class == CL_DISCONNECT) {
2241 int val = TRUE;
2242 pr_trace_msg(trace_channel, 17, "resolving LogFormat ID %u (%s)",
2243 LOGFMT_META_DISCONNECT,
2244 pr_jot_get_logfmt_id_name(LOGFMT_META_DISCONNECT));
2245 res = (on_meta)(p, ctx, LOGFMT_META_DISCONNECT, NULL, &val);
2246 }
2247
2248 /* Don't forget to advance past the META_START and
2249 * META_DISCONNECT.
2250 */
2251 logfmt += 2;
2252 break;
2253
2254 default:
2255 res = resolve_meta(p, &logfmt, ctx, cmd, on_meta, on_default);
2256 }
2257 }
2258
2259 /* "Flush" any remaining non-variable text. */
2260 if (text_len > 0) {
2261 res = (on_other)(p, ctx, logfmt - text_len, text_len);
2262 if (res < 0) {
2263 return -1;
2264 }
2265 }
2266
2267 return 0;
2268 }
2269
jot_parse_on_unknown(pool * p,pr_jot_ctx_t * ctx,const char * text,size_t text_len)2270 static int jot_parse_on_unknown(pool *p, pr_jot_ctx_t *ctx, const char *text,
2271 size_t text_len) {
2272 return 0;
2273 }
2274
jot_parse_on_other(pool * p,pr_jot_ctx_t * ctx,char ch)2275 static int jot_parse_on_other(pool *p, pr_jot_ctx_t *ctx, char ch) {
2276 return 0;
2277 }
2278
parse_short_id(const char * text,unsigned char * logfmt_id)2279 static int parse_short_id(const char *text, unsigned char *logfmt_id) {
2280 switch (*text) {
2281 case 'A':
2282 *logfmt_id = LOGFMT_META_ANON_PASS;
2283 break;
2284
2285 case 'D':
2286 *logfmt_id = LOGFMT_META_DIR_PATH;
2287 break;
2288
2289 case 'E':
2290 *logfmt_id = LOGFMT_META_EOS_REASON;
2291 break;
2292
2293 case 'F':
2294 *logfmt_id = LOGFMT_META_XFER_PATH;
2295 break;
2296
2297 case 'H':
2298 *logfmt_id = LOGFMT_META_VHOST_IP;
2299 break;
2300
2301 case 'I':
2302 *logfmt_id = LOGFMT_META_RAW_BYTES_IN;
2303 break;
2304
2305 case 'J':
2306 *logfmt_id = LOGFMT_META_CMD_PARAMS;
2307 break;
2308
2309 case 'L':
2310 *logfmt_id = LOGFMT_META_LOCAL_IP;
2311 break;
2312
2313 case 'O':
2314 *logfmt_id = LOGFMT_META_RAW_BYTES_OUT;
2315 break;
2316
2317 case 'P':
2318 *logfmt_id = LOGFMT_META_PID;
2319 break;
2320
2321 case 'R':
2322 *logfmt_id = LOGFMT_META_RESPONSE_MS;
2323 break;
2324
2325 case 'S':
2326 *logfmt_id = LOGFMT_META_RESPONSE_STR;
2327 break;
2328
2329 case 'T':
2330 *logfmt_id = LOGFMT_META_SECONDS;
2331 break;
2332
2333 case 'U':
2334 *logfmt_id = LOGFMT_META_ORIGINAL_USER;
2335 break;
2336
2337 case 'V':
2338 *logfmt_id = LOGFMT_META_LOCAL_FQDN;
2339 break;
2340
2341 case 'a':
2342 *logfmt_id = LOGFMT_META_REMOTE_IP;
2343 break;
2344
2345 case 'b':
2346 *logfmt_id = LOGFMT_META_BYTES_SENT;
2347 break;
2348
2349 case 'c':
2350 *logfmt_id = LOGFMT_META_CLASS;
2351 break;
2352
2353 case 'd':
2354 *logfmt_id = LOGFMT_META_DIR_NAME;
2355 break;
2356
2357 case 'f':
2358 *logfmt_id = LOGFMT_META_FILENAME;
2359 break;
2360
2361 case 'g':
2362 *logfmt_id = LOGFMT_META_GROUP;
2363 break;
2364
2365 case 'h':
2366 *logfmt_id = LOGFMT_META_REMOTE_HOST;
2367 break;
2368
2369 case 'l':
2370 *logfmt_id = LOGFMT_META_IDENT_USER;
2371 break;
2372
2373 case 'm':
2374 *logfmt_id = LOGFMT_META_METHOD;
2375 break;
2376
2377 case 'p':
2378 *logfmt_id = LOGFMT_META_LOCAL_PORT;
2379 break;
2380
2381 case 'r':
2382 *logfmt_id = LOGFMT_META_COMMAND;
2383 break;
2384
2385 case 's':
2386 *logfmt_id = LOGFMT_META_RESPONSE_CODE;
2387 break;
2388
2389 case 't':
2390 *logfmt_id = LOGFMT_META_TIME;
2391 break;
2392
2393 case 'u':
2394 *logfmt_id = LOGFMT_META_USER;
2395 break;
2396
2397 case 'v':
2398 *logfmt_id = LOGFMT_META_LOCAL_NAME;
2399 break;
2400
2401 case 'w':
2402 *logfmt_id = LOGFMT_META_RENAME_FROM;
2403 break;
2404
2405 default:
2406 errno = ENOENT;
2407 return -1;
2408 }
2409
2410 return 1;
2411 }
2412
parse_unknown_id(const char * text,const char ** logfmt_data,size_t * logfmt_datalen)2413 static int parse_unknown_id(const char *text, const char **logfmt_data,
2414 size_t *logfmt_datalen) {
2415 char *ptr;
2416
2417 if (*text != '{') {
2418 errno = ENOENT;
2419 return -1;
2420 }
2421
2422 ptr = strchr(text + 1, '}');
2423 if (ptr == NULL) {
2424 errno = ENOENT;
2425 return -1;
2426 }
2427
2428 *logfmt_data = (text + 1);
2429 *logfmt_datalen = (ptr - text - 1);
2430 return (2 + *logfmt_datalen);
2431 }
2432
parse_long_id(const char * text,unsigned char * logfmt_id,const char ** logfmt_data,size_t * logfmt_datalen)2433 static int parse_long_id(const char *text, unsigned char *logfmt_id,
2434 const char **logfmt_data, size_t *logfmt_datalen) {
2435 int res;
2436
2437 if (strncmp(text, "{basename}", 10) == 0) {
2438 *logfmt_id = LOGFMT_META_BASENAME;
2439 return 10;
2440 }
2441
2442 if (strncmp(text, "{env:", 5) == 0) {
2443 char *ptr;
2444
2445 ptr = strchr(text + 5, '}');
2446 if (ptr != NULL) {
2447 *logfmt_id = LOGFMT_META_ENV_VAR;
2448 *logfmt_data = text + 5;
2449 *logfmt_datalen = (ptr - text) - 5;
2450
2451 /* Advance 5 for the leading '{env:', and one more for the
2452 * trailing '}' character.
2453 */
2454 return (6 + *logfmt_datalen);
2455 }
2456 }
2457
2458 if (strncmp(text, "{epoch}", 7) == 0) {
2459 *logfmt_id = LOGFMT_META_EPOCH;
2460 return 7;
2461 }
2462
2463 if (strncmp(text, "{file-modified}", 15) == 0) {
2464 *logfmt_id = LOGFMT_META_FILE_MODIFIED;
2465 return 15;
2466 }
2467
2468 if (strncmp(text, "{file-offset}", 13) == 0) {
2469 *logfmt_id = LOGFMT_META_FILE_OFFSET;
2470 return 13;
2471 }
2472
2473 if (strncmp(text, "{file-size}", 11) == 0) {
2474 *logfmt_id = LOGFMT_META_FILE_SIZE;
2475 return 11;
2476 }
2477
2478 if (strncmp(text, "{gid}", 5) == 0) {
2479 *logfmt_id = LOGFMT_META_GID;
2480 return 5;
2481 }
2482
2483 if (strncasecmp(text, "{iso8601}", 9) == 0) {
2484 *logfmt_id = LOGFMT_META_ISO8601;
2485 return 9;
2486 }
2487
2488 if (strncmp(text, "{microsecs}", 11) == 0) {
2489 *logfmt_id = LOGFMT_META_MICROSECS;
2490 return 11;
2491 }
2492
2493 if (strncmp(text, "{millisecs}", 11) == 0) {
2494 *logfmt_id = LOGFMT_META_MILLISECS;
2495 return 11;
2496 }
2497
2498 if (strncmp(text, "{note:", 6) == 0) {
2499 char *ptr;
2500
2501 ptr = strchr(text + 6, '}');
2502 if (ptr != NULL) {
2503 *logfmt_id = LOGFMT_META_NOTE_VAR;
2504 *logfmt_data = text + 6;
2505 *logfmt_datalen = (ptr - text) - 6;
2506
2507 /* Advance 6 for the leading '{note:', and one more for the
2508 * trailing '}' character.
2509 */
2510 return (7 + *logfmt_datalen);
2511 }
2512 }
2513
2514 if (strncmp(text, "{protocol}", 10) == 0) {
2515 *logfmt_id = LOGFMT_META_PROTOCOL;
2516 return 10;
2517 }
2518
2519 if (strncmp(text, "{remote-port}", 13) == 0) {
2520 *logfmt_id = LOGFMT_META_REMOTE_PORT;
2521 return 13;
2522 }
2523
2524 if (strncmp(text, "{time:", 6) == 0) {
2525 char *ptr;
2526
2527 ptr = strchr(text + 6, '}');
2528 if (ptr != NULL) {
2529 *logfmt_id = LOGFMT_META_TIME;
2530 *logfmt_data = text + 6;
2531 *logfmt_datalen = (ptr - text) - 6;
2532
2533 /* Advance 6 for the leading '{time:', and one more for the
2534 * trailing '}' character.
2535 */
2536 return (7 + *logfmt_datalen);
2537 }
2538 }
2539
2540 if (strncmp(text, "{transfer-failure}", 18) == 0) {
2541 *logfmt_id = LOGFMT_META_XFER_FAILURE;
2542 return 18;
2543 }
2544
2545 if (strncmp(text, "{transfer-millisecs}", 20) == 0) {
2546 *logfmt_id = LOGFMT_META_XFER_MS;
2547 return 20;
2548 }
2549
2550 if (strncmp(text, "{transfer-port}", 15) == 0) {
2551 *logfmt_id = LOGFMT_META_XFER_PORT;
2552 return 15;
2553 }
2554
2555 if (strncmp(text, "{transfer-status}", 17) == 0) {
2556 *logfmt_id = LOGFMT_META_XFER_STATUS;
2557 return 17;
2558 }
2559
2560 if (strncmp(text, "{transfer-type}", 15) == 0) {
2561 *logfmt_id = LOGFMT_META_XFER_TYPE;
2562 return 15;
2563 }
2564
2565 if (strncmp(text, "{uid}", 5) == 0) {
2566 *logfmt_id = LOGFMT_META_UID;
2567 return 5;
2568 }
2569
2570 if (strncmp(text, "{version}", 9) == 0) {
2571 *logfmt_id = LOGFMT_META_VERSION;
2572 return 9;
2573 }
2574
2575 /* Check whether the text looks like it might be a long variable with the
2576 * odd syntax that mod_log uses, e.g. "%{...}e" or "%{...}t".
2577 */
2578 res = parse_unknown_id(text, logfmt_data, logfmt_datalen);
2579 if (res > 0) {
2580 if (*(text + res) == 'e') {
2581 *logfmt_id = LOGFMT_META_ENV_VAR;
2582 return (1 + res);
2583 }
2584
2585 if (*(text + res) == 't') {
2586 *logfmt_id = LOGFMT_META_TIME;
2587 return (1 + res);
2588 }
2589 }
2590
2591 errno = ENOENT;
2592 return -1;
2593 }
2594
jot_parsed_append_byte(pr_jot_parsed_t * parsed,char ch)2595 static void jot_parsed_append_byte(pr_jot_parsed_t *parsed, char ch) {
2596 if (parsed->buflen > 0) {
2597 pr_trace_msg(trace_channel, 19, "appending character (%c) to format", ch);
2598 *(parsed->buf++) = (unsigned char) ch;
2599 parsed->buflen -= 1;
2600 }
2601 }
2602
jot_parsed_append_text(pr_jot_parsed_t * parsed,const char * text,size_t text_len)2603 static void jot_parsed_append_text(pr_jot_parsed_t *parsed, const char *text,
2604 size_t text_len) {
2605 register unsigned int i;
2606
2607 if (text == NULL ||
2608 text_len == 0) {
2609 return;
2610 }
2611
2612 if (text_len > parsed->buflen) {
2613 text_len = parsed->buflen;
2614 }
2615
2616 pr_trace_msg(trace_channel, 19, "appending text '%.*s' to format",
2617 (int) text_len, text);
2618
2619 for (i = 0; i < text_len; i++) {
2620 *(parsed->buf++) = (unsigned char) text[i];
2621 }
2622
2623 parsed->buflen -= text_len;
2624 }
2625
jot_parsed_append_var(pr_jot_parsed_t * parsed,unsigned char logfmt_id)2626 static void jot_parsed_append_var(pr_jot_parsed_t *parsed,
2627 unsigned char logfmt_id) {
2628
2629 if (parsed->buflen >= 2) {
2630 pr_trace_msg(trace_channel, 19, "appending LogFormat ID %u (%s) to format",
2631 logfmt_id, pr_jot_get_logfmt_id_name(logfmt_id));
2632 *(parsed->buf++) = LOGFMT_META_START;
2633 *(parsed->buf++) = logfmt_id;
2634 parsed->buflen -= 2;
2635 }
2636 }
2637
jot_parsed_append_arg(pr_jot_parsed_t * parsed,const char * text,size_t text_len)2638 static void jot_parsed_append_arg(pr_jot_parsed_t *parsed, const char *text,
2639 size_t text_len) {
2640
2641 if (text == NULL ||
2642 text_len == 0) {
2643 return;
2644 }
2645
2646 if (parsed->buflen >= (text_len + 3)) {
2647 *(parsed->buf++) = LOGFMT_META_START;
2648 *(parsed->buf++) = LOGFMT_META_ARG;
2649 parsed->buflen -= 2;
2650
2651 jot_parsed_append_text(parsed, text, text_len);
2652
2653 *(parsed->buf++) = LOGFMT_META_ARG_END;
2654 parsed->buflen -= 1;
2655 }
2656 }
2657
pr_jot_parse_on_meta(pool * p,pr_jot_ctx_t * jot_ctx,unsigned char logfmt_id,const char * logfmt_data,size_t logfmt_datalen)2658 int pr_jot_parse_on_meta(pool *p, pr_jot_ctx_t *jot_ctx,
2659 unsigned char logfmt_id, const char *logfmt_data, size_t logfmt_datalen) {
2660 pr_jot_parsed_t *parsed;
2661
2662 if (jot_ctx == NULL) {
2663 errno = EINVAL;
2664 return -1;
2665 }
2666
2667 parsed = jot_ctx->log;
2668 if (parsed == NULL) {
2669 errno = EINVAL;
2670 return -1;
2671 }
2672
2673 jot_parsed_append_var(parsed, logfmt_id);
2674 jot_parsed_append_arg(parsed, logfmt_data, logfmt_datalen);
2675 return 0;
2676 }
2677
pr_jot_parse_on_unknown(pool * p,pr_jot_ctx_t * jot_ctx,const char * text,size_t text_len)2678 int pr_jot_parse_on_unknown(pool *p, pr_jot_ctx_t *jot_ctx, const char *text,
2679 size_t text_len) {
2680 pr_jot_parsed_t *parsed;
2681
2682 if (jot_ctx == NULL) {
2683 errno = EINVAL;
2684 return -1;
2685 }
2686
2687 parsed = jot_ctx->log;
2688 if (parsed == NULL) {
2689 errno = EINVAL;
2690 return -1;
2691 }
2692
2693 jot_parsed_append_text(parsed, text, text_len);
2694 return 0;
2695 }
2696
pr_jot_parse_on_other(pool * p,pr_jot_ctx_t * jot_ctx,char ch)2697 int pr_jot_parse_on_other(pool *p, pr_jot_ctx_t *jot_ctx, char ch) {
2698 pr_jot_parsed_t *parsed;
2699
2700 if (jot_ctx == NULL) {
2701 errno = EINVAL;
2702 return -1;
2703 }
2704
2705 parsed = jot_ctx->log;
2706 if (parsed == NULL) {
2707 errno = EINVAL;
2708 return -1;
2709 }
2710
2711 jot_parsed_append_byte(parsed, ch);
2712 return 0;
2713 }
2714
pr_jot_parse_logfmt(pool * p,const char * text,pr_jot_ctx_t * ctx,int (* on_meta)(pool *,pr_jot_ctx_t *,unsigned char,const char *,size_t),int (* on_unknown)(pool *,pr_jot_ctx_t *,const char *,size_t),int (* on_other)(pool *,pr_jot_ctx_t *,char),int flags)2715 int pr_jot_parse_logfmt(pool *p, const char *text, pr_jot_ctx_t *ctx,
2716 int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *, size_t),
2717 int (*on_unknown)(pool *, pr_jot_ctx_t *, const char *, size_t),
2718 int (*on_other)(pool *, pr_jot_ctx_t *, char), int flags) {
2719 int res = 0;
2720 const char *ptr;
2721
2722 if (p == NULL ||
2723 text == NULL ||
2724 on_meta == NULL) {
2725 errno = EINVAL;
2726 return -1;
2727 }
2728
2729 if (on_unknown == NULL) {
2730 on_unknown = jot_parse_on_unknown;
2731 }
2732
2733 if (on_other == NULL) {
2734 on_other = jot_parse_on_other;
2735 }
2736
2737 pr_trace_msg(trace_channel, 19, "parsing text: %s", text);
2738
2739 for (ptr = text; *ptr; ) {
2740 int len;
2741 unsigned char logfmt_id = 0;
2742 const char *logfmt_data = NULL;
2743 size_t logfmt_datalen = 0;
2744
2745 pr_signals_handle();
2746
2747 if (res < 0) {
2748 return -1;
2749 }
2750
2751 if (*ptr != '%') {
2752 res = (on_other)(p, ctx, *ptr);
2753 ptr += 1;
2754 continue;
2755 }
2756
2757 len = parse_short_id(ptr + 1, &logfmt_id);
2758 if (len > 0) {
2759 res = (on_meta)(p, ctx, logfmt_id, NULL, 0);
2760 ptr += (len + 1);
2761 continue;
2762 }
2763
2764 len = parse_long_id(ptr + 1, &logfmt_id, &logfmt_data, &logfmt_datalen);
2765 if (len > 0) {
2766 res = (on_meta)(p, ctx, logfmt_id, logfmt_data, logfmt_datalen);
2767 ptr += (len + 1);
2768 continue;
2769 }
2770
2771 len = parse_unknown_id(ptr + 1, &logfmt_data, &logfmt_datalen);
2772 if (len > 0) {
2773 if (flags & PR_JOT_LOGFMT_PARSE_FL_UNKNOWN_AS_CUSTOM) {
2774 pr_trace_msg(trace_channel, 19,
2775 "handling unknown variable '%.*s' as CUSTOM", (int) logfmt_datalen,
2776 logfmt_data);
2777 res = (on_meta)(p, ctx, LOGFMT_META_CUSTOM, logfmt_data,
2778 logfmt_datalen);
2779
2780 } else {
2781 res = (on_unknown)(p, ctx, logfmt_data, logfmt_datalen);
2782 }
2783
2784 ptr += (len + 1);
2785 continue;
2786 }
2787
2788 res = (on_other)(p, ctx, *ptr);
2789 ptr += 1;
2790 }
2791
2792 return 0;
2793 }
2794
scan_meta(pool * p,unsigned char ** logfmt,pr_jot_ctx_t * ctx,int (* on_meta)(pool *,pr_jot_ctx_t *,unsigned char,const char *,size_t))2795 static int scan_meta(pool *p, unsigned char **logfmt, pr_jot_ctx_t *ctx,
2796 int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *,
2797 size_t)) {
2798 int res = 0;
2799 unsigned char *ptr, logfmt_id;
2800 const char *logfmt_data = NULL;
2801 size_t consumed_bytes = 0;
2802
2803 logfmt_id = **logfmt;
2804 ptr = (*logfmt) + 1;
2805
2806 switch (logfmt_id) {
2807 case LOGFMT_META_CUSTOM:
2808 case LOGFMT_META_ENV_VAR:
2809 case LOGFMT_META_NOTE_VAR:
2810 case LOGFMT_META_TIME: {
2811 if (*(ptr + 1) == LOGFMT_META_START &&
2812 *(ptr + 2) == LOGFMT_META_ARG) {
2813 size_t logfmt_datalen = 0;
2814
2815 logfmt_data = get_meta_arg(p, (ptr + 3), &logfmt_datalen);
2816 res = (on_meta)(p, ctx, logfmt_id, logfmt_data, logfmt_datalen);
2817
2818 /* Skip past the META_START, META_ARG, META_ARG_END, and the data. */
2819 consumed_bytes += (3 + logfmt_datalen);
2820 break;
2821 }
2822 }
2823
2824 default:
2825 res = (on_meta)(p, ctx, logfmt_id, NULL, 0);
2826 consumed_bytes += 1;
2827 }
2828
2829 if (res < 0) {
2830 return -1;
2831 }
2832
2833 ptr += consumed_bytes;
2834 *logfmt = ptr;
2835 return 0;
2836 }
2837
pr_jot_scan_logfmt(pool * p,unsigned char * logfmt,unsigned char logfmt_id,pr_jot_ctx_t * ctx,int (* on_meta)(pool *,pr_jot_ctx_t *,unsigned char,const char *,size_t),int flags)2838 int pr_jot_scan_logfmt(pool *p, unsigned char *logfmt, unsigned char logfmt_id,
2839 pr_jot_ctx_t *ctx,
2840 int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *, size_t),
2841 int flags) {
2842 int res = 0;
2843
2844 if (p == NULL ||
2845 logfmt == NULL ||
2846 on_meta == NULL) {
2847 errno = EINVAL;
2848 return -1;
2849 }
2850
2851 if (pr_jot_get_logfmt_id_name(logfmt_id) == NULL) {
2852 errno = EINVAL;
2853 return -1;
2854 }
2855
2856 while (*logfmt) {
2857 pr_signals_handle();
2858
2859 if (res < 0) {
2860 return -1;
2861 }
2862
2863 if (*logfmt == logfmt_id) {
2864 res = scan_meta(p, &logfmt, ctx, on_meta);
2865 continue;
2866 }
2867
2868 logfmt++;
2869 }
2870
2871 return 0;
2872 }
2873
filter_text_to_array(pool * p,char * text)2874 static array_header *filter_text_to_array(pool *p, char *text) {
2875 char delim;
2876 size_t text_len;
2877
2878 text_len = strlen(text);
2879
2880 /* What delimiter to use? By default, we will assume CSV, and thus use
2881 * a comma. For backward compatibility, we also support pipes; first one
2882 * seen wins.
2883 */
2884 delim = ',';
2885 if (memchr(text, '|', text_len) != NULL) {
2886 delim = '|';
2887 }
2888
2889 return pr_str_text_to_array(p, text, delim);
2890 }
2891
filter_get_classes(pool * p,array_header * names,int * included_classes,int * excluded_classes,int flags)2892 static int filter_get_classes(pool *p, array_header *names,
2893 int *included_classes, int *excluded_classes, int flags) {
2894 register unsigned int i;
2895 int incl, excl, exclude = FALSE;
2896
2897 incl = excl = CL_NONE;
2898
2899 for (i = 0; i < names->nelts; i++) {
2900 const char *name;
2901
2902 pr_signals_handle();
2903
2904 name = ((const char **) names->elts)[i];
2905
2906 if (*name == '!') {
2907 exclude = TRUE;
2908 name++;
2909 }
2910
2911 if (strcasecmp(name, "NONE") == 0) {
2912 if (exclude) {
2913 incl = CL_ALL;
2914 excl = CL_NONE;
2915
2916 } else {
2917 incl = CL_NONE;
2918 }
2919
2920 } else if (strcasecmp(name, "ALL") == 0) {
2921 if (exclude) {
2922 incl = CL_NONE;
2923 excl = CL_ALL;
2924
2925 } else {
2926 incl = CL_ALL;
2927 }
2928
2929 } else if (strcasecmp(name, "AUTH") == 0) {
2930 if (exclude) {
2931 incl &= ~CL_AUTH;
2932 excl |= CL_AUTH;
2933
2934 } else {
2935 incl |= CL_AUTH;
2936 }
2937
2938 } else if (strcasecmp(name, "INFO") == 0) {
2939 if (exclude) {
2940 incl &= ~CL_INFO;
2941 excl |= CL_INFO;
2942
2943 } else {
2944 incl |= CL_INFO;
2945 }
2946
2947 } else if (strcasecmp(name, "DIRS") == 0) {
2948 if (exclude) {
2949 incl &= ~CL_DIRS;
2950 excl |= CL_DIRS;
2951
2952 } else {
2953 incl |= CL_DIRS;
2954 }
2955
2956 } else if (strcasecmp(name, "READ") == 0) {
2957 if (exclude) {
2958 incl &= ~CL_READ;
2959 excl |= CL_READ;
2960
2961 } else {
2962 incl |= CL_READ;
2963 }
2964
2965 } else if (strcasecmp(name, "WRITE") == 0) {
2966 if (exclude) {
2967 incl &= ~CL_WRITE;
2968 excl |= CL_WRITE;
2969
2970 } else {
2971 incl |= CL_WRITE;
2972 }
2973
2974 } else if (strcasecmp(name, "MISC") == 0) {
2975 if (exclude) {
2976 incl &= ~CL_MISC;
2977 excl |= CL_MISC;
2978
2979 } else {
2980 incl |= CL_MISC;
2981 }
2982
2983 } else if (strcasecmp(name, "SEC") == 0 ||
2984 strcasecmp(name, "SECURE") == 0) {
2985 if (exclude) {
2986 incl &= ~CL_SEC;
2987 excl |= CL_SEC;
2988
2989 } else {
2990 incl |= CL_SEC;
2991 }
2992
2993 } else if (strcasecmp(name, "CONNECT") == 0) {
2994 if (exclude) {
2995 incl &= ~CL_CONNECT;
2996 excl |= CL_CONNECT;
2997
2998 } else {
2999 incl |= CL_CONNECT;
3000 }
3001
3002 } else if (strcasecmp(name, "EXIT") == 0 ||
3003 strcasecmp(name, "DISCONNECT") == 0) {
3004 if (exclude) {
3005 incl &= ~CL_DISCONNECT;
3006 excl |= CL_DISCONNECT;
3007
3008 } else {
3009 incl |= CL_DISCONNECT;
3010 }
3011
3012 } else if (strcasecmp(name, "SSH") == 0) {
3013 if (exclude) {
3014 incl &= ~CL_SSH;
3015 excl |= CL_SSH;
3016
3017 } else {
3018 incl |= CL_SSH;
3019 }
3020
3021 } else if (strcasecmp(name, "SFTP") == 0) {
3022 if (exclude) {
3023 incl &= ~CL_SFTP;
3024 excl |= CL_SFTP;
3025
3026 } else {
3027 incl |= CL_SFTP;
3028 }
3029
3030 } else {
3031 pr_trace_msg(trace_channel, 2, "ignoring unknown/unsupported class '%s'",
3032 name);
3033 errno = ENOENT;
3034 return -1;
3035 }
3036 }
3037
3038 *included_classes = incl;
3039 *excluded_classes = excl;
3040 return 0;
3041 }
3042
filter_get_cmd_ids(pool * p,array_header * names,int * included_classes,int * excluded_classes,int rules_type,int flags)3043 static array_header *filter_get_cmd_ids(pool *p, array_header *names,
3044 int *included_classes, int *excluded_classes, int rules_type, int flags) {
3045 register unsigned int i;
3046 array_header *cmd_ids;
3047
3048 cmd_ids = make_array(p, names->nelts, sizeof(int));
3049 for (i = 0; i < names->nelts; i++) {
3050 const char *name;
3051 int cmd_id, valid = TRUE;
3052
3053 pr_signals_handle();
3054
3055 name = ((const char **) names->elts)[i];
3056
3057 cmd_id = pr_cmd_get_id(name);
3058 if (cmd_id < 0) {
3059 valid = FALSE;
3060
3061 if (rules_type == PR_JOT_FILTER_TYPE_COMMANDS_WITH_CLASSES) {
3062 if (strcmp(name, "ALL") == 0) {
3063 *included_classes = CL_ALL;
3064 valid = TRUE;
3065
3066 if (flags & PR_JOT_FILTER_FL_ALL_INCL_ALL) {
3067 *included_classes |= (CL_CONNECT|CL_DISCONNECT);
3068 }
3069
3070 } else if (strcmp(name, "AUTH") == 0) {
3071 *included_classes |= CL_AUTH;
3072 valid = TRUE;
3073
3074 } else if (strcmp(name, "CONNECT") == 0) {
3075 *included_classes |= CL_CONNECT;
3076 valid = TRUE;
3077
3078 } else if (strcmp(name, "DIRS") == 0) {
3079 *included_classes |= CL_DIRS;
3080 valid = TRUE;
3081
3082 } else if (strcmp(name, "DISCONNECT") == 0) {
3083 *included_classes |= CL_DISCONNECT;
3084 valid = TRUE;
3085
3086 } else if (strcmp(name, "INFO") == 0) {
3087 *included_classes |= CL_INFO;
3088 valid = TRUE;
3089
3090 } else if (strcmp(name, "MISC") == 0) {
3091 *included_classes |= CL_MISC;
3092 valid = TRUE;
3093
3094 } else if (strcmp(name, "READ") == 0) {
3095 *included_classes |= CL_READ;
3096 valid = TRUE;
3097
3098 } else if (strcmp(name, "SEC") == 0) {
3099 *included_classes |= CL_SEC;
3100 valid = TRUE;
3101
3102 } else if (strcmp(name, "SFTP") == 0) {
3103 *included_classes |= CL_SFTP;
3104 valid = TRUE;
3105
3106 } else if (strcmp(name, "SSH") == 0) {
3107 *included_classes |= CL_SSH;
3108 valid = TRUE;
3109
3110 } else if (strcmp(name, "WRITE") == 0) {
3111 *included_classes |= CL_WRITE;
3112 valid = TRUE;
3113 }
3114 }
3115
3116 if (valid == FALSE) {
3117 pr_trace_msg(trace_channel, 2, "ignoring unknown command '%s'", name);
3118 }
3119 }
3120
3121 if (valid == TRUE) {
3122 *((int *) push_array(cmd_ids)) = cmd_id;
3123 }
3124 }
3125
3126 return cmd_ids;
3127 }
3128
pr_jot_filters_create(pool * p,const char * rules,int rules_type,int flags)3129 pr_jot_filters_t *pr_jot_filters_create(pool *p, const char *rules,
3130 int rules_type, int flags) {
3131 int included_classes, excluded_classes;
3132 pool *sub_pool, *tmp_pool;
3133 array_header *cmd_ids, *names;
3134 pr_jot_filters_t *filters;
3135
3136 if (p == NULL ||
3137 rules == NULL) {
3138 errno = EINVAL;
3139 return NULL;
3140 }
3141
3142 included_classes = excluded_classes = CL_NONE;
3143 cmd_ids = NULL;
3144
3145 sub_pool = make_sub_pool(p);
3146 pr_pool_tag(sub_pool, "Jot Filters pool");
3147
3148 tmp_pool = make_sub_pool(p);
3149 names = filter_text_to_array(tmp_pool, pstrdup(tmp_pool, rules));
3150
3151 switch (rules_type) {
3152 case PR_JOT_FILTER_TYPE_CLASSES: {
3153 int res;
3154
3155 res = filter_get_classes(sub_pool, names, &included_classes,
3156 &excluded_classes, flags);
3157 if (res < 0) {
3158 int xerrno = errno;
3159
3160 destroy_pool(tmp_pool);
3161 destroy_pool(sub_pool);
3162 errno = xerrno;
3163 return NULL;
3164 }
3165
3166 break;
3167 }
3168
3169 case PR_JOT_FILTER_TYPE_COMMANDS:
3170 case PR_JOT_FILTER_TYPE_COMMANDS_WITH_CLASSES:
3171 cmd_ids = filter_get_cmd_ids(sub_pool, names, &included_classes,
3172 &excluded_classes, rules_type, flags);
3173 break;
3174
3175 default:
3176 destroy_pool(tmp_pool);
3177 destroy_pool(sub_pool);
3178 errno = EINVAL;
3179 return NULL;
3180 }
3181
3182 destroy_pool(tmp_pool);
3183
3184 filters = pcalloc(sub_pool, sizeof(pr_jot_filters_t));
3185 filters->pool = sub_pool;
3186 filters->included_classes = included_classes;
3187 filters->excluded_classes = excluded_classes;
3188 filters->cmd_ids = cmd_ids;
3189
3190 return filters;
3191 }
3192
pr_jot_filters_destroy(pr_jot_filters_t * filters)3193 int pr_jot_filters_destroy(pr_jot_filters_t *filters) {
3194 if (filters == NULL) {
3195 errno = EINVAL;
3196 return -1;
3197 }
3198
3199 destroy_pool(filters->pool);
3200 return 0;
3201 }
3202
pr_jot_filters_include_classes(pr_jot_filters_t * filters,int log_class)3203 int pr_jot_filters_include_classes(pr_jot_filters_t *filters, int log_class) {
3204 if (filters == NULL) {
3205 errno = EINVAL;
3206 return -1;
3207 }
3208
3209 return (filters->included_classes == log_class);
3210 }
3211
jot_set_deleted_filesz(off_t deleted_filesz)3212 void jot_set_deleted_filesz(off_t deleted_filesz) {
3213 jot_deleted_filesz = deleted_filesz;
3214 }
3215