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