• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

proftpd / proftpd / 30135626854

25 Jul 2026 12:15AM UTC coverage: 93.034% (+0.6%) from 92.428%
30135626854

push

github

51363 of 55209 relevant lines covered (93.03%)

219.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.69
/src/jot.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2017-2026 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, see <https://www.gnu.org/licenses/>.
17
 *
18
 * As a special exemption, TJ Saunders and other respective copyright holders
19
 * give permission to link this program with OpenSSL, and distribute the
20
 * resulting executable, without including the source code for OpenSSL in the
21
 * source distribution.
22
 */
23

24
#include "conf.h"
25
#include "logfmt.h"
26
#include "json.h"
27
#include "jot.h"
28

29
struct jot_filters_rec {
30
  pool *pool;
31

32
  int included_classes;
33
  int excluded_classes;
34
  array_header *cmd_ids;
35

36
  /* Additional, refining sifting rules. */
37
  array_header *cmd_sifts;
38
};
39

40
/* For tracking the size of deleted files. */
41
static off_t jot_deleted_filesz = 0;
42

43
static const char *trace_channel = "jot";
44

45
/* Entries in the JSON map table identify the key, and the data type:
46
 * Boolean, number, or string.
47
 */
48
struct logfmt_json_info {
49
  unsigned int json_type;
50
  const char *json_key;
51
};
52

4✔
53
/* Key comparison for the ID/key table. */
54
static int logfmt_json_keycmp(const void *k1, size_t ksz1, const void *k2,
55
  size_t ksz2) {
4✔
56

4✔
57
  (void) ksz1;
58
  (void) ksz2;
59

4✔
60
  /* Return zero to indicate a match, non-zero otherwise. */
61
  return (*((unsigned char *) k1) == *((unsigned char *) k2) ? 0 : 1);
62
}
63

114✔
64
/* Key "hash" callback for ID/key table. */
114✔
65
static unsigned int logfmt_json_keyhash(const void *k, size_t ksz) {
114✔
66
  unsigned char c;
67
  unsigned int res;
114✔
68

69
  (void) ksz;
114✔
70

114✔
71
  c = *((unsigned char *) k);
72
  res = (c << 8);
114✔
73

74
  return res;
75
}
110✔
76

77
static void add_json_info(pool *p, pr_table_t *tab, unsigned char logfmt_id,
110✔
78
    const char *json_key, unsigned int json_type) {
110✔
79
  unsigned char *k;
80
  struct logfmt_json_info *lji;
110✔
81

110✔
82
  k = palloc(p, sizeof(unsigned char));
83
  *k = logfmt_id;
110✔
84

110✔
85
  lji = palloc(p, sizeof(struct logfmt_json_info));
110✔
86
  lji->json_type = json_type;
87
  lji->json_key = json_key;
110✔
88

89
  (void) pr_table_kadd(tab, (const void *) k, sizeof(unsigned char),
110✔
90
    lji, sizeof(struct logfmt_json_info *));
91
}
379✔
92

379✔
93
const char *pr_jot_get_logfmt_id_name(unsigned char logfmt_id) {
94
  const char *name = NULL;
379✔
95

96
  switch (logfmt_id) {
97
    case LOGFMT_META_BYTES_SENT:
98
      name = "BYTE_SENT";
99
      break;
29✔
100

29✔
101
    case LOGFMT_META_FILENAME:
29✔
102
      name = "FILENAME";
103
      break;
7✔
104

7✔
105
    case LOGFMT_META_ENV_VAR:
7✔
106
      name = "ENV_VAR";
107
      break;
3✔
108

3✔
109
    case LOGFMT_META_REMOTE_HOST:
3✔
110
      name = "REMOTE_HOST";
111
      break;
3✔
112

3✔
113
    case LOGFMT_META_REMOTE_IP:
3✔
114
      name = "REMOTE_IP";
115
      break;
5✔
116

5✔
117
    case LOGFMT_META_IDENT_USER:
5✔
118
      name = "IDENT_USER";
119
      break;
3✔
120

3✔
121
    case LOGFMT_META_PID:
3✔
122
      name = "PID";
123
      break;
3✔
124

3✔
125
    case LOGFMT_META_TIME:
3✔
126
      name = "TIME";
127
      break;
7✔
128

7✔
129
    case LOGFMT_META_SECONDS:
7✔
130
      name = "SECONDS";
131
      break;
11✔
132

11✔
133
    case LOGFMT_META_COMMAND:
11✔
134
      name = "COMMAND";
135
      break;
5✔
136

5✔
137
    case LOGFMT_META_LOCAL_NAME:
5✔
138
      name = "LOCAL_NAME";
139
      break;
5✔
140

5✔
141
    case LOGFMT_META_LOCAL_PORT:
5✔
142
      name = "LOCAL_PORT";
143
      break;
3✔
144

3✔
145
    case LOGFMT_META_LOCAL_IP:
3✔
146
      name = "LOCAL_IP";
147
      break;
3✔
148

3✔
149
    case LOGFMT_META_LOCAL_FQDN:
3✔
150
      name = "LOCAL_FQDN";
151
      break;
5✔
152

5✔
153
    case LOGFMT_META_USER:
5✔
154
      name = "USER";
155
      break;
5✔
156

5✔
157
    case LOGFMT_META_ORIGINAL_USER:
5✔
158
      name = "ORIGINAL_USER";
159
      break;
6✔
160

6✔
161
    case LOGFMT_META_RESPONSE_CODE:
6✔
162
      name = "RESPONSE_CODE";
163
      break;
5✔
164

5✔
165
    case LOGFMT_META_CLASS:
5✔
166
      name = "CLASS";
167
      break;
5✔
168

5✔
169
    case LOGFMT_META_ANON_PASS:
5✔
170
      name = "ANON_PASS";
171
      break;
7✔
172

7✔
173
    case LOGFMT_META_METHOD:
7✔
174
      name = "METHOD";
175
      break;
11✔
176

11✔
177
    case LOGFMT_META_XFER_PATH:
11✔
178
      name = "XFER_PATH";
179
      break;
15✔
180

15✔
181
    case LOGFMT_META_DIR_NAME:
15✔
182
      name = "DIR_NAME";
183
      break;
15✔
184

15✔
185
    case LOGFMT_META_DIR_PATH:
15✔
186
      name = "DIR_PATH";
187
      break;
9✔
188

9✔
189
    case LOGFMT_META_CMD_PARAMS:
9✔
190
      name = "CMD_PARAMS";
191
      break;
5✔
192

5✔
193
    case LOGFMT_META_RESPONSE_STR:
5✔
194
      name = "RESPONSE_STR";
195
      break;
3✔
196

3✔
197
    case LOGFMT_META_PROTOCOL:
3✔
198
      name = "PROTOCOL";
199
      break;
3✔
200

3✔
201
    case LOGFMT_META_VERSION:
3✔
202
      name = "VERSION";
203
      break;
6✔
204

6✔
205
    case LOGFMT_META_RENAME_FROM:
6✔
206
      name = "RENAME_FROM";
207
      break;
5✔
208

5✔
209
    case LOGFMT_META_FILE_MODIFIED:
5✔
210
      name = "FILE_MODIFIED";
211
      break;
5✔
212

5✔
213
    case LOGFMT_META_UID:
5✔
214
      name = "UID";
215
      break;
5✔
216

5✔
217
    case LOGFMT_META_GID:
5✔
218
      name = "GID";
219
      break;
3✔
220

3✔
221
    case LOGFMT_META_RAW_BYTES_IN:
3✔
222
      name = "RAW_BYTES_IN";
223
      break;
3✔
224

3✔
225
    case LOGFMT_META_RAW_BYTES_OUT:
3✔
226
      name = "RAW_BYTES_OUT";
227
      break;
6✔
228

6✔
229
    case LOGFMT_META_EOS_REASON:
6✔
230
      name = "EOS_REASON";
231
      break;
5✔
232

5✔
233
    case LOGFMT_META_VHOST_IP:
5✔
234
      name = "VHOST_IP";
235
      break;
7✔
236

7✔
237
    case LOGFMT_META_NOTE_VAR:
7✔
238
      name = "NOTE_VAR";
239
      break;
3✔
240

3✔
241
    case LOGFMT_META_VAR_VAR:
3✔
242
      name = "VAR_VAR";
243
      break;
13✔
244

13✔
245
    case LOGFMT_META_XFER_STATUS:
13✔
246
      name = "XFER_STATUS";
247
      break;
10✔
248

10✔
249
    case LOGFMT_META_XFER_FAILURE:
10✔
250
      name = "XFER_FAILURE";
251
      break;
3✔
252

3✔
253
    case LOGFMT_META_MICROSECS:
3✔
254
      name = "MICROSECS";
255
      break;
3✔
256

3✔
257
    case LOGFMT_META_MILLISECS:
3✔
258
      name = "MILLISECS";
259
      break;
3✔
260

3✔
261
    case LOGFMT_META_ISO8601:
3✔
262
      name = "ISO8601";
263
      break;
5✔
264

5✔
265
    case LOGFMT_META_GROUP:
5✔
266
      name = "GROUP";
267
      break;
31✔
268

31✔
269
    case LOGFMT_META_BASENAME:
31✔
270
      name = "BASENAME";
271
      break;
5✔
272

5✔
273
    case LOGFMT_META_FILE_OFFSET:
5✔
274
      name = "FILE_OFFSET";
275
      break;
7✔
276

7✔
277
    case LOGFMT_META_XFER_MS:
7✔
278
      name = "XFER_MS";
279
      break;
5✔
280

5✔
281
    case LOGFMT_META_RESPONSE_MS:
5✔
282
      name = "RESPONSE_MS";
283
      break;
5✔
284

5✔
285
    case LOGFMT_META_FILE_SIZE:
5✔
286
      name = "FILE_SIZE";
287
      break;
13✔
288

13✔
289
    case LOGFMT_META_XFER_PORT:
13✔
290
      name = "XFER_PORT";
291
      break;
×
292

×
293
    case LOGFMT_META_XFER_SPEED:
×
294
      name = "XFER_SPEED";
295
      break;
9✔
296

9✔
297
    case LOGFMT_META_XFER_TYPE:
9✔
298
      name = "XFER_TYPE";
299
      break;
5✔
300

5✔
301
    case LOGFMT_META_REMOTE_PORT:
5✔
302
      name = "REMOTE_PORT";
303
      break;
3✔
304

3✔
305
    case LOGFMT_META_EPOCH:
3✔
306
      name = "EPOCH";
307
      break;
11✔
308

11✔
309
    case LOGFMT_META_CONNECT:
11✔
310
      name = "CONNECT";
311
      break;
5✔
312

5✔
313
    case LOGFMT_META_DISCONNECT:
5✔
314
      name = "DISCONNECT";
315
      break;
4✔
316

4✔
317
    case LOGFMT_META_CUSTOM:
4✔
318
      name = "CUSTOM";
319
      break;
4✔
320

4✔
321
    default:
4✔
322
      errno = EINVAL;
4✔
323
      name = NULL;
324
      break;
325
  }
379✔
326

327
  return name;
328
}
3✔
329

3✔
330
pr_table_t *pr_jot_get_logfmt2json(pool *p) {
331
  pr_table_t *map;
3✔
332

1✔
333
  if (p == NULL) {
1✔
334
    errno = EINVAL;
335
    return NULL;
336
  }
2✔
337

338
  map = pr_table_alloc(p, 0);
2✔
339

340
  (void) pr_table_ctl(map, PR_TABLE_CTL_SET_KEY_CMP,
2✔
341
    (void *) logfmt_json_keycmp);
342
  (void) pr_table_ctl(map, PR_TABLE_CTL_SET_KEY_HASH,
343
    (void *) logfmt_json_keyhash);
344

345
  /* Now populate the map with the ID/name values.  The key is the
346
   * LogFormat "meta" ID, and the value is the corresponding name string,
347
   * for use e.g. as JSON object member names.
348
   */
2✔
349

350
  add_json_info(p, map, LOGFMT_META_BYTES_SENT, PR_JOT_LOGFMT_BYTES_SENT_KEY,
2✔
351
    PR_JSON_TYPE_NUMBER);
352
  add_json_info(p, map, LOGFMT_META_FILENAME, PR_JOT_LOGFMT_FILENAME_KEY,
2✔
353
    PR_JSON_TYPE_STRING);
354
  add_json_info(p, map, LOGFMT_META_ENV_VAR, PR_JOT_LOGFMT_ENV_VAR_KEY,
2✔
355
    PR_JSON_TYPE_STRING);
356
  add_json_info(p, map, LOGFMT_META_EPOCH, PR_JOT_LOGFMT_EPOCH_KEY,
2✔
357
    PR_JSON_TYPE_NUMBER);
358
  add_json_info(p, map, LOGFMT_META_REMOTE_HOST, PR_JOT_LOGFMT_REMOTE_HOST_KEY,
2✔
359
    PR_JSON_TYPE_STRING);
360
  add_json_info(p, map, LOGFMT_META_REMOTE_IP, PR_JOT_LOGFMT_REMOTE_IP_KEY,
2✔
361
    PR_JSON_TYPE_STRING);
362
  add_json_info(p, map, LOGFMT_META_IDENT_USER, PR_JOT_LOGFMT_IDENT_USER_KEY,
2✔
363
    PR_JSON_TYPE_STRING);
364
  add_json_info(p, map, LOGFMT_META_PID, PR_JOT_LOGFMT_PID_KEY,
2✔
365
    PR_JSON_TYPE_NUMBER);
366
  add_json_info(p, map, LOGFMT_META_TIME, PR_JOT_LOGFMT_TIME_KEY,
2✔
367
    PR_JSON_TYPE_STRING);
368
  add_json_info(p, map, LOGFMT_META_SECONDS, PR_JOT_LOGFMT_SECONDS_KEY,
2✔
369
    PR_JSON_TYPE_NUMBER);
370
  add_json_info(p, map, LOGFMT_META_COMMAND, PR_JOT_LOGFMT_COMMAND_KEY,
2✔
371
    PR_JSON_TYPE_STRING);
372
  add_json_info(p, map, LOGFMT_META_LOCAL_NAME, PR_JOT_LOGFMT_LOCAL_NAME_KEY,
2✔
373
    PR_JSON_TYPE_STRING);
374
  add_json_info(p, map, LOGFMT_META_LOCAL_PORT, PR_JOT_LOGFMT_LOCAL_PORT_KEY,
2✔
375
    PR_JSON_TYPE_NUMBER);
376
  add_json_info(p, map, LOGFMT_META_LOCAL_IP, PR_JOT_LOGFMT_LOCAL_IP_KEY,
2✔
377
    PR_JSON_TYPE_STRING);
378
  add_json_info(p, map, LOGFMT_META_LOCAL_FQDN, PR_JOT_LOGFMT_LOCAL_FQDN_KEY,
2✔
379
    PR_JSON_TYPE_STRING);
380
  add_json_info(p, map, LOGFMT_META_USER, PR_JOT_LOGFMT_USER_KEY,
2✔
381
    PR_JSON_TYPE_STRING);
382
  add_json_info(p, map, LOGFMT_META_ORIGINAL_USER, PR_JOT_LOGFMT_ORIG_USER_KEY,
2✔
383
    PR_JSON_TYPE_STRING);
384
  add_json_info(p, map, LOGFMT_META_RESPONSE_CODE,
2✔
385
    PR_JOT_LOGFMT_RESPONSE_CODE_KEY, PR_JSON_TYPE_NUMBER);
386
  add_json_info(p, map, LOGFMT_META_CLASS, PR_JOT_LOGFMT_CLASS_KEY,
2✔
387
    PR_JSON_TYPE_STRING);
388
  add_json_info(p, map, LOGFMT_META_ANON_PASS, PR_JOT_LOGFMT_ANON_PASSWD_KEY,
2✔
389
    PR_JSON_TYPE_STRING);
390
  add_json_info(p, map, LOGFMT_META_METHOD, PR_JOT_LOGFMT_METHOD_KEY,
2✔
391
    PR_JSON_TYPE_STRING);
392
  add_json_info(p, map, LOGFMT_META_XFER_PATH, PR_JOT_LOGFMT_XFER_PATH_KEY,
2✔
393
    PR_JSON_TYPE_STRING);
394
  add_json_info(p, map, LOGFMT_META_DIR_NAME, PR_JOT_LOGFMT_DIR_NAME_KEY,
2✔
395
    PR_JSON_TYPE_STRING);
396
  add_json_info(p, map, LOGFMT_META_DIR_PATH, PR_JOT_LOGFMT_DIR_PATH_KEY,
2✔
397
    PR_JSON_TYPE_STRING);
398
  add_json_info(p, map, LOGFMT_META_CMD_PARAMS, PR_JOT_LOGFMT_CMD_PARAMS_KEY,
2✔
399
    PR_JSON_TYPE_STRING);
400
  add_json_info(p, map, LOGFMT_META_RESPONSE_STR,
2✔
401
    PR_JOT_LOGFMT_RESPONSE_MSG_KEY, PR_JSON_TYPE_STRING);
402
  add_json_info(p, map, LOGFMT_META_PROTOCOL, PR_JOT_LOGFMT_PROTOCOL_KEY,
2✔
403
    PR_JSON_TYPE_STRING);
404
  add_json_info(p, map, LOGFMT_META_VERSION, PR_JOT_LOGFMT_VERSION_KEY,
2✔
405
    PR_JSON_TYPE_STRING);
406
  add_json_info(p, map, LOGFMT_META_RENAME_FROM, PR_JOT_LOGFMT_RENAME_FROM_KEY,
2✔
407
    PR_JSON_TYPE_STRING);
408
  add_json_info(p, map, LOGFMT_META_FILE_MODIFIED,
2✔
409
    PR_JOT_LOGFMT_FILE_MODIFIED_KEY, PR_JSON_TYPE_BOOL);
410
  add_json_info(p, map, LOGFMT_META_UID, PR_JOT_LOGFMT_UID_KEY,
2✔
411
    PR_JSON_TYPE_NUMBER);
412
  add_json_info(p, map, LOGFMT_META_GID, PR_JOT_LOGFMT_GID_KEY,
2✔
413
    PR_JSON_TYPE_NUMBER);
414
  add_json_info(p, map, LOGFMT_META_RAW_BYTES_IN,
2✔
415
    PR_JOT_LOGFMT_RAW_BYTES_IN_KEY, PR_JSON_TYPE_NUMBER);
416
  add_json_info(p, map, LOGFMT_META_RAW_BYTES_OUT,
2✔
417
    PR_JOT_LOGFMT_RAW_BYTES_OUT_KEY, PR_JSON_TYPE_NUMBER);
418
  add_json_info(p, map, LOGFMT_META_EOS_REASON, PR_JOT_LOGFMT_EOS_REASON_KEY,
2✔
419
    PR_JSON_TYPE_STRING);
420
  add_json_info(p, map, LOGFMT_META_VHOST_IP, PR_JOT_LOGFMT_VHOST_IP_KEY,
2✔
421
    PR_JSON_TYPE_STRING);
422
  add_json_info(p, map, LOGFMT_META_NOTE_VAR, PR_JOT_LOGFMT_NOTE_KEY,
2✔
423
    PR_JSON_TYPE_STRING);
424
  add_json_info(p, map, LOGFMT_META_VAR_VAR, PR_JOT_LOGFMT_VAR_KEY,
2✔
425
    PR_JSON_TYPE_STRING);
426
  add_json_info(p, map, LOGFMT_META_XFER_STATUS, PR_JOT_LOGFMT_XFER_STATUS_KEY,
2✔
427
    PR_JSON_TYPE_STRING);
428
  add_json_info(p, map, LOGFMT_META_XFER_FAILURE,
2✔
429
    PR_JOT_LOGFMT_XFER_FAILURE_KEY, PR_JSON_TYPE_STRING);
430
  add_json_info(p, map, LOGFMT_META_XFER_PORT, PR_JOT_LOGFMT_XFER_PORT_KEY,
2✔
431
    PR_JSON_TYPE_NUMBER);
432
  add_json_info(p, map, LOGFMT_META_XFER_SPEED, PR_JOT_LOGFMT_XFER_SPEED_KEY,
2✔
433
    PR_JSON_TYPE_STRING);
434
  add_json_info(p, map, LOGFMT_META_XFER_TYPE, PR_JOT_LOGFMT_XFER_TYPE_KEY,
2✔
435
    PR_JSON_TYPE_STRING);
436
  add_json_info(p, map, LOGFMT_META_MICROSECS, PR_JOT_LOGFMT_MICROSECS_KEY,
2✔
437
    PR_JSON_TYPE_NUMBER);
438
  add_json_info(p, map, LOGFMT_META_MILLISECS, PR_JOT_LOGFMT_MILLISECS_KEY,
2✔
439
    PR_JSON_TYPE_NUMBER);
440
  add_json_info(p, map, LOGFMT_META_ISO8601, PR_JOT_LOGFMT_ISO8601_KEY,
2✔
441
    PR_JSON_TYPE_STRING);
442
  add_json_info(p, map, LOGFMT_META_GROUP, PR_JOT_LOGFMT_GROUP_KEY,
2✔
443
    PR_JSON_TYPE_STRING);
444
  add_json_info(p, map, LOGFMT_META_BASENAME, PR_JOT_LOGFMT_BASENAME_KEY,
2✔
445
    PR_JSON_TYPE_STRING);
446
  add_json_info(p, map, LOGFMT_META_FILE_OFFSET, PR_JOT_LOGFMT_FILE_OFFSET_KEY,
2✔
447
    PR_JSON_TYPE_NUMBER);
448
  add_json_info(p, map, LOGFMT_META_XFER_MS, PR_JOT_LOGFMT_XFER_MS_KEY,
2✔
449
    PR_JSON_TYPE_NUMBER);
450
  add_json_info(p, map, LOGFMT_META_RESPONSE_MS, PR_JOT_LOGFMT_RESPONSE_MS_KEY,
2✔
451
    PR_JSON_TYPE_NUMBER);
452
  add_json_info(p, map, LOGFMT_META_FILE_SIZE, PR_JOT_LOGFMT_FILE_SIZE_KEY,
2✔
453
    PR_JSON_TYPE_NUMBER);
454
  add_json_info(p, map, LOGFMT_META_REMOTE_PORT, PR_JOT_LOGFMT_REMOTE_PORT_KEY,
2✔
455
    PR_JSON_TYPE_NUMBER);
456
  add_json_info(p, map, LOGFMT_META_CONNECT, PR_JOT_LOGFMT_CONNECT_KEY,
2✔
457
    PR_JSON_TYPE_BOOL);
458
  add_json_info(p, map, LOGFMT_META_DISCONNECT, PR_JOT_LOGFMT_DISCONNECT_KEY,
459
    PR_JSON_TYPE_BOOL);
2✔
460

461
  return map;
462
}
10✔
463

464
int pr_jot_on_json(pool *p, pr_jot_ctx_t *ctx, unsigned char logfmt_id,
10✔
465
    const char *jot_hint, const void *val) {
10✔
466
  int res = 0;
10✔
467
  const struct logfmt_json_info *lji;
10✔
468
  pr_json_object_t *json;
469
  pr_table_t *logfmt_json_map;
10✔
470

10✔
471
  if (p == NULL ||
472
      ctx == NULL ||
3✔
473
      val == NULL) {
3✔
474
    errno = EINVAL;
475
    return -1;
476
  }
7✔
477

1✔
478
  if (ctx->log == NULL) {
479
    pr_trace_msg(trace_channel, 16,
480
      "missing required JSON object for jotting LogFormat ID %u",
1✔
481
      (unsigned int) logfmt_id);
1✔
482
    errno = EINVAL;
483
    return -1;
484
  }
6✔
485

1✔
486
  if (ctx->user_data == NULL) {
487
    pr_trace_msg(trace_channel, 16,
488
      "missing required JSON map for jotting LogFormat ID %u",
1✔
489
      (unsigned int) logfmt_id);
1✔
490
    errno = EINVAL;
491
    return -1;
492
  }
5✔
493

5✔
494
  json = ctx->log;
495
  logfmt_json_map = (pr_table_t *) ctx->user_data;
5✔
496

497
  lji = pr_table_kget(logfmt_json_map, (const void *) &logfmt_id,
5✔
498
    sizeof(unsigned char), NULL);
1✔
499
  if (lji == NULL) {
500
    pr_trace_msg(trace_channel, 16,
501
      "missing required JSON information for jotting LogFormat ID %u",
1✔
502
      (unsigned int) logfmt_id);
1✔
503
    errno = EINVAL;
504
    return -1;
505
  }
4✔
506

4✔
507
  pr_trace_msg(trace_channel, 18, "jotting LogFormat ID %u as JSON %s (%s)",
508
    (unsigned int) logfmt_id, pr_json_type_name(lji->json_type), lji->json_key);
4✔
509

2✔
510
  switch (lji->json_type) {
2✔
511
    case PR_JSON_TYPE_STRING: {
512
      const char *json_key;
2✔
513

514
      json_key = lji->json_key;
515

2✔
516
      /* Use the hinted key, if available (e.g. for ENV/NOTE variables). */
1✔
517
      if (jot_hint != NULL) {
518
        json_key = jot_hint;
519
      }
2✔
520

2✔
521
      res = pr_json_object_set_string(p, json, json_key, (const char *) val);
522
      break;
523
    }
1✔
524

1✔
525
    case PR_JSON_TYPE_NUMBER:
526
      res = pr_json_object_set_number(p, json, lji->json_key,
1✔
527
        *((double *) val));
528
      break;
1✔
529

1✔
530
    case PR_JSON_TYPE_BOOL:
1✔
531
      res = pr_json_object_set_bool(p, json, lji->json_key, *((int *) val));
532
      break;
533
  }
534

535
  return res;
536
}
37✔
537

72✔
538
static int is_data_xfer_cmd(cmd_rec *cmd) {
68✔
539
  if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 ||
64✔
540
      pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
61✔
541
      pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
45✔
542
      pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
27✔
543
      pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0 ||
12✔
544
      pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 ||
26✔
545
      pr_cmd_cmp(cmd, PR_CMD_STOU_ID) == 0) {
546
    return TRUE;
547
  }
548

549
  return FALSE;
550
}
8✔
551

8✔
552
static char *get_meta_arg(pool *p, unsigned char *meta, size_t *arg_len) {
8✔
553
  char buf[PR_TUNABLE_PATH_MAX+1], *ptr;
554
  size_t len;
8✔
555

8✔
556
  ptr = buf;
557
  len = 0;
33✔
558

25✔
559
  while (*meta != LOGFMT_META_ARG_END) {
25✔
560
    pr_signals_handle();
25✔
561
    *ptr++ = (char) *meta++;
562
    len++;
563
  }
8✔
564

8✔
565
  *ptr = '\0';
566
  *arg_len = len;
8✔
567

568
  return pstrndup(p, buf, len);
569
}
30✔
570

30✔
571
static const char *get_meta_basename(cmd_rec *cmd) {
30✔
572
  const char *base = NULL, *path = NULL;
573
  pool *p;
30✔
574

30✔
575
  p = cmd->tmp_pool;
1✔
576
  if (pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0) {
577
    path = pr_fs_decode_path(p, cmd->arg);
29✔
578

1✔
579
  } else if (pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0) {
580
    path = pr_table_get(cmd->notes, "mod_xfer.retr-path", NULL);
55✔
581

27✔
582
  } else if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 ||
2✔
583
             pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0) {
584
    path = pr_table_get(cmd->notes, "mod_xfer.store-path", NULL);
26✔
585

1✔
586
  } else if (session.xfer.p != NULL &&
587
             session.xfer.path != NULL) {
588
    path = session.xfer.path;
49✔
589

47✔
590
  } else if (pr_cmd_cmp(cmd, PR_CMD_CDUP_ID) == 0 ||
45✔
591
             pr_cmd_cmp(cmd, PR_CMD_PWD_ID) == 0 ||
22✔
592
             pr_cmd_cmp(cmd, PR_CMD_XCUP_ID) == 0 ||
4✔
593
             pr_cmd_cmp(cmd, PR_CMD_XPWD_ID) == 0) {
594
    path = pr_fs_getcwd();
41✔
595

20✔
596
  } else if (pr_cmd_cmp(cmd, PR_CMD_CWD_ID) == 0 ||
597
             pr_cmd_cmp(cmd, PR_CMD_XCWD_ID) == 0) {
598

599
    /* Note: by this point in the dispatch cycle, the current working
600
     * directory has already been changed.  For the CWD/XCWD commands, this
601
     * means that dir_abs_path() may return an improper path, with the target
602
     * directory being reported twice.  To deal with this, do not use
603
     * dir_abs_path(), and use pr_fs_getvwd()/pr_fs_getcwd() instead.
2✔
604
     */
605
    if (session.chroot_path != NULL) {
1✔
606
      /* Chrooted session. */
607
      path = strcmp(pr_fs_getvwd(), "/") ?  pr_fs_getvwd() :
608
        session.chroot_path;
609

610
    } else {
1✔
611
      /* Non-chrooted session. */
612
       path = pr_fs_getcwd();
613
    }
19✔
614

3✔
615
  } else if (pr_cmd_cmp(cmd, PR_CMD_SITE_ID) == 0 &&
3✔
616
             cmd->argc > 3 &&
2✔
617
             (strcasecmp(cmd->argv[1], "CHGRP") == 0 ||
1✔
618
              strcasecmp(cmd->argv[1], "CHMOD") == 0 ||
619
              strcasecmp(cmd->argv[1], "UTIME") == 0)) {
620
    register unsigned int i;
621
    char *ptr = "";
6✔
622

6✔
623
    for (i = 3; i <= cmd->argc-1; i++) {
3✔
624
      ptr = pstrcat(p, ptr, *ptr ? " " : "",
625
        pr_fs_decode_path(p, cmd->argv[i]), NULL);
626
    }
627

628
    path = ptr;
629

630
  } else {
631
    /* Some commands (i.e. DELE, MKD, RMD, XMKD, and XRMD) have associated
632
     * filenames that are not stored in the session.xfer structure; these
633
     * should be expanded properly as well.
31✔
634
     */
29✔
635
    if (pr_cmd_cmp(cmd, PR_CMD_DELE_ID) == 0 ||
27✔
636
        pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
25✔
637
        pr_cmd_cmp(cmd, PR_CMD_MDTM_ID) == 0 ||
23✔
638
        pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 ||
21✔
639
        pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
19✔
640
        pr_cmd_cmp(cmd, PR_CMD_MLST_ID) == 0 ||
17✔
641
        pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
15✔
642
        pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 ||
7✔
643
        pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 ||
10✔
644
        pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) {
645
       path = pr_fs_decode_path(p, cmd->arg);
6✔
646

647
    } else if (pr_cmd_cmp(cmd, PR_CMD_MFMT_ID) == 0) {
1✔
648
      /* MFMT has, as its filename, the second argument. */
649
      path = pr_fs_decode_path(p, cmd->argv[2]);
650
    }
651
  }
25✔
652

25✔
653
  if (path != NULL) {
654
    char *ptr = NULL;
25✔
655

25✔
656
    ptr = strrchr(path, '/');
18✔
657
    if (ptr != NULL) {
17✔
658
      if (ptr != path) {
659
        base = ptr + 1;
1✔
660

×
661
      } else if (*(ptr + 1) != '\0') {
662
        base = ptr + 1;
663

664
      } else {
665
        base = path;
666
      }
667

668
    } else {
669
      base = path;
670
    }
671
  }
30✔
672

673
  return base;
674
}
14✔
675

14✔
676
static const char *get_meta_dir_name(cmd_rec *cmd) {
14✔
677
  const char *dir_name = NULL;
678
  pool *p;
14✔
679

680
  p = cmd->tmp_pool;
27✔
681

25✔
682
  if (pr_cmd_cmp(cmd, PR_CMD_CDUP_ID) == 0 ||
23✔
683
      pr_cmd_cmp(cmd, PR_CMD_CWD_ID) == 0 ||
21✔
684
      pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
19✔
685
      pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 ||
17✔
686
      pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
15✔
687
      pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
13✔
688
      pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 ||
11✔
689
      pr_cmd_cmp(cmd, PR_CMD_XCWD_ID) == 0 ||
9✔
690
      pr_cmd_cmp(cmd, PR_CMD_XCUP_ID) == 0 ||
4✔
691
      pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 ||
11✔
692
      pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) {
693
    char *path, *ptr = NULL;
11✔
694

11✔
695
    path = pr_fs_decode_path(p, cmd->arg);
10✔
696
    if (path != NULL) {
697
      ptr = strrchr(path, '/');
698
    }
10✔
699

9✔
700
    if (ptr != NULL) {
8✔
701
      if (ptr != path) {
702
        dir_name = ptr + 1;
1✔
703

1✔
704
      } else if (*(ptr + 1) != '\0') {
705
        dir_name = ptr + 1;
706

707
      } else {
708
        dir_name = path;
709
      }
710

711
    } else {
712
      dir_name = path;
713
    }
714

3✔
715
  } else {
716
    dir_name = pr_fs_getvwd();
717
  }
14✔
718

719
  return dir_name;
720
}
14✔
721

14✔
722
static const char *get_meta_dir_path(cmd_rec *cmd) {
14✔
723
  const char *dir_path = NULL;
724
  pool *p;
14✔
725

726
  p = cmd->tmp_pool;
27✔
727

25✔
728
  if (pr_cmd_cmp(cmd, PR_CMD_CDUP_ID) == 0 ||
23✔
729
      pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
21✔
730
      pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 ||
19✔
731
      pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
17✔
732
      pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
15✔
733
      pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 ||
13✔
734
      pr_cmd_cmp(cmd, PR_CMD_XCUP_ID) == 0 ||
6✔
735
      pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 ||
9✔
736
      pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) {
737
    dir_path = dir_abs_path(p, pr_fs_decode_path(p, cmd->arg), TRUE);
8✔
738

3✔
739
  } else if (pr_cmd_cmp(cmd, PR_CMD_CWD_ID) == 0 ||
740
             pr_cmd_cmp(cmd, PR_CMD_XCWD_ID) == 0) {
741

742
    /* Note: by this point in the dispatch cycle, the current working
743
     * directory has already been changed.  For the CWD/XCWD commands, this
744
     * means that dir_abs_path() may return an improper path, with the target
745
     * directory being reported twice.  To deal with this, do not use
746
     * dir_abs_path(), and use pr_fs_getvwd()/pr_fs_getcwd() instead.
747
     */
2✔
748

749
    if (session.chroot_path != NULL) {
1✔
750
      /* Chrooted session. */
1✔
751
      if (strcmp(pr_fs_getvwd(), "/") == 0) {
752
        dir_path = session.chroot_path;
753

×
754
      } else {
755
        dir_path = pdircat(p, session.chroot_path, pr_fs_getvwd(), NULL);
756
      }
757

758
    } else {
1✔
759
      /* Non-chrooted session. */
760
      dir_path = pr_fs_getcwd();
761
    }
762
  }
14✔
763

764
  return dir_path;
765
}
28✔
766

28✔
767
static const char *get_meta_filename(cmd_rec *cmd) {
28✔
768
  const char *filename = NULL;
769
  pool *p;
28✔
770

771
  p = cmd->tmp_pool;
28✔
772

1✔
773
  if (pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0) {
774
    filename = dir_abs_path(p, pr_fs_decode_path(p, cmd->arg), TRUE);
27✔
775

1✔
776
  } else if (pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0) {
777
    const char *path;
1✔
778

1✔
779
    path = pr_table_get(cmd->notes, "mod_xfer.retr-path", NULL);
1✔
780
    if (path != NULL) {
781
      filename = dir_abs_path(p, path, TRUE);
782
    }
51✔
783

25✔
784
  } else if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 ||
2✔
785
             pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0) {
786
    const char *path;
2✔
787

2✔
788
    path = pr_table_get(cmd->notes, "mod_xfer.store-path", NULL);
2✔
789
    if (path != NULL) {
790
      filename = dir_abs_path(p, path, TRUE);
791
    }
24✔
792

1✔
793
  } else if (session.xfer.p != NULL &&
1✔
794
             session.xfer.path != NULL) {
795
    filename = dir_abs_path(p, session.xfer.path, TRUE);
45✔
796

43✔
797
  } else if (pr_cmd_cmp(cmd, PR_CMD_CDUP_ID) == 0 ||
41✔
798
             pr_cmd_cmp(cmd, PR_CMD_PWD_ID) == 0 ||
20✔
799
             pr_cmd_cmp(cmd, PR_CMD_XCUP_ID) == 0 ||
4✔
800
             pr_cmd_cmp(cmd, PR_CMD_XPWD_ID) == 0) {
801
    filename = dir_abs_path(p, pr_fs_getcwd(), TRUE);
37✔
802

18✔
803
  } else if (pr_cmd_cmp(cmd, PR_CMD_CWD_ID) == 0 ||
804
             pr_cmd_cmp(cmd, PR_CMD_XCWD_ID) == 0) {
805

806
    /* Note: by this point in the dispatch cycle, the current working
807
     * directory has already been changed.  For the CWD/XCWD commands, this
808
     * means that dir_abs_path() may return an improper path, with the target
809
     * directory being reported twice.  To deal with this, do not use
810
     * dir_abs_path(), and use pr_fs_getvwd()/pr_fs_getcwd() instead.
2✔
811
     */
812
    if (session.chroot_path != NULL) {
1✔
813
      /* Chrooted session. */
1✔
814
      if (strcmp(pr_fs_getvwd(), "/") == 0) {
815
        filename = session.chroot_path;
816

×
817
      } else {
818
        filename = pdircat(p, session.chroot_path, pr_fs_getvwd(), NULL);
819
      }
820

821
    } else {
1✔
822
      /* Non-chrooted session. */
823
      filename = pr_fs_getcwd();
824
    }
17✔
825

3✔
826
  } else if (pr_cmd_cmp(cmd, PR_CMD_SITE_ID) == 0 &&
3✔
827
             cmd->argc > 3 &&
2✔
828
             (strcasecmp(cmd->argv[1], "CHGRP") == 0 ||
4✔
829
              strcasecmp(cmd->argv[1], "CHMOD") == 0 ||
830
              strcasecmp(cmd->argv[1], "UTIME") == 0)) {
831
    register unsigned int i;
832
    char *ptr = "";
6✔
833

6✔
834
    for (i = 3; i <= cmd->argc-1; i++) {
3✔
835
      ptr = pstrcat(p, ptr, *ptr ? " " : "",
836
        pr_fs_decode_path(p, cmd->argv[i]), NULL);
837
    }
3✔
838

839
    filename = dir_abs_path(p, ptr, TRUE);
840

841
  } else {
842
    /* Some commands (i.e. DELE, MKD, RMD, XMKD, and XRMD) have associated
843
     * filenames that are not stored in the session.xfer structure; these
844
     * should be expanded properly as well.
27✔
845
     */
25✔
846
    if (pr_cmd_cmp(cmd, PR_CMD_DELE_ID) == 0 ||
23✔
847
        pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 ||
21✔
848
        pr_cmd_cmp(cmd, PR_CMD_MDTM_ID) == 0 ||
19✔
849
        pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 ||
17✔
850
        pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 ||
15✔
851
        pr_cmd_cmp(cmd, PR_CMD_MLST_ID) == 0 ||
13✔
852
        pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 ||
11✔
853
        pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 ||
5✔
854
        pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 ||
10✔
855
        pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) {
856
      char *decoded_path;
10✔
857

10✔
858
      decoded_path = pr_fs_decode_path(p, cmd->arg);
10✔
859
      filename = dir_abs_path(p, decoded_path, TRUE);
×
860
      if (filename == NULL) {
861
        filename = dir_abs_path(p, decoded_path, FALSE);
862
      }
10✔
863

×
864
      if (filename == NULL) {
865
        filename = decoded_path;
866
      }
4✔
867

1✔
868
    } else if (pr_cmd_cmp(cmd, PR_CMD_MFMT_ID) == 0) {
869
      char *decoded_path;
870

1✔
871
      /* MFMT has, as its filename, the second argument. */
1✔
872
      decoded_path = pr_fs_decode_path(p, cmd->argv[2]);
1✔
873
      filename = dir_abs_path(p, decoded_path, TRUE);
874
      if (filename == NULL) {
×
875
        /* This time, try without the interpolation. */
876
        filename = dir_abs_path(p, decoded_path, FALSE);
877
      }
1✔
878

×
879
      if (filename == NULL) {
880
        filename = decoded_path;
881
      }
882
    }
883
  }
884

885
  /* If we're in the PRE_CMD phase, then the filename can't be filled in
886
   * with the above techniques.
28✔
887
   */
888
  if (session.curr_phase == PRE_CMD &&
×
889
      filename == NULL) {
890
    char *decoded_path;
×
891

×
892
    decoded_path = pr_fs_decode_path(p, cmd->arg);
893
    filename = dir_abs_path(p, decoded_path, TRUE);
894
  }
895

896
  /* Use the realpath FSIO function as well, in case of mod_vroot usage
897
   * (Issue #1808).
28✔
898
   */
25✔
899
  if (filename != NULL) {
900
    filename = pr_fsio_realpath(p, filename);
901
  }
28✔
902

903
  return filename;
904
}
9✔
905

9✔
906
static const char *get_meta_transfer_failure(cmd_rec *cmd) {
907
  const char *transfer_failure = NULL;
908

909
  /* If the current command is one that incurs a data transfer, then we
910
   * need to do more work.  If not, it's an easy substitution.
9✔
911
   */
6✔
912
  if (is_data_xfer_cmd(cmd) == TRUE) {
913
    const char *proto;
6✔
914

915
    proto = pr_session_get_protocol(0);
6✔
916

2✔
917
    if (strcmp(proto, "ftp") == 0 ||
1✔
918
        strcmp(proto, "ftps") == 0 ||
919
        strcmp(proto, "sftp") == 0) {
6✔
920

5✔
921
      if (!(XFER_ABORTED)) {
5✔
922
        int res;
923
        const char *resp_code = NULL, *resp_msg = NULL;
924

925
        /* Get the last response code/message.  We use heuristics here to
926
         * determine when to use "failed" versus "success".
5✔
927
         */
5✔
928
        res = pr_response_get_last(cmd->tmp_pool, &resp_code, &resp_msg);
5✔
929
        if (res == 0 &&
3✔
930
            resp_code != NULL) {
931
          if (*resp_code != '2' &&
1✔
932
              *resp_code != '1') {
933
            char *ptr;
934

1✔
935
            /* Parse out/prettify the resp_msg here */
1✔
936
            ptr = strchr(resp_msg, '.');
×
937
            if (ptr != NULL) {
938
              transfer_failure = ptr + 2;
939

940
            } else {
941
              transfer_failure = resp_msg;
942
            }
943
          }
944

945
        } else {
946
          transfer_failure = "unknown";
947
        }
948

949
      } else {
950
        transfer_failure = "aborted";
951
      }
952
    }
953
  }
9✔
954

955
  return transfer_failure;
956
}
10✔
957

10✔
958
static const char *get_meta_transfer_path(cmd_rec *cmd) {
10✔
959
  const char *transfer_path = NULL;
960
  pool *p;
10✔
961

962
  p = cmd->tmp_pool;
10✔
963

1✔
964
  if (pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0) {
965
    transfer_path = dir_best_path(p, pr_fs_decode_path(p, cmd->arg));
9✔
966

1✔
967
  } else if (session.xfer.p != NULL &&
968
             session.xfer.path != NULL) {
969
    transfer_path = session.xfer.path;
970

971
  } else {
972
    /* Some commands (i.e. DELE, MKD, XMKD, RMD, XRMD) have associated
973
     * filenames that are not stored in the session.xfer structure; these
974
     * should be expanded properly as well.
15✔
975
     */
13✔
976
    if (pr_cmd_cmp(cmd, PR_CMD_DELE_ID) == 0 ||
11✔
977
        pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 ||
9✔
978
        pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 ||
4✔
979
        pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 ||
5✔
980
        pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) {
981
      transfer_path = dir_best_path(p, pr_fs_decode_path(p, cmd->arg));
982
    }
983
  }
984

985
  /* If we're in the PRE_CMD phase, then the filename can't be filled in
986
   * with the above techniques.
10✔
987
   */
988
  if (session.curr_phase == PRE_CMD &&
×
989
      transfer_path == NULL) {
990
    char *decoded_path;
×
991

×
992
    decoded_path = pr_fs_decode_path(p, cmd->arg);
993
    transfer_path = dir_best_path(p, decoded_path);
994
  }
10✔
995

996
  return transfer_path;
997
}
6✔
998

6✔
999
static int get_meta_transfer_secs(cmd_rec *cmd, double *transfer_secs) {
1000
  if (session.xfer.p == NULL) {
1001
    return -1;
1002
  }
1003

1004
  /* Make sure that session.xfer.start_time actually has values (which is
1005
   * not always the case).
3✔
1006
   */
2✔
1007
  if (session.xfer.start_time.tv_sec != 0 ||
2✔
1008
      session.xfer.start_time.tv_usec != 0) {
1009
    uint64_t start_ms = 0, end_ms = 0;
2✔
1010

2✔
1011
    pr_timeval2millis(&(session.xfer.start_time), &start_ms);
1012
    pr_gettimeofday_millis(&end_ms);
2✔
1013

2✔
1014
    *transfer_secs = (end_ms - start_ms) / 1000.0;
1015
    return 0;
1016
  }
1017

1018
  return -1;
1019
}
12✔
1020

12✔
1021
static const char *get_meta_transfer_status(cmd_rec *cmd) {
1022
  const char *transfer_status = NULL;
1023

1024
  /* If the current command is one that incurs a data transfer, then we need
1025
   * to do more work.  If not, it's an easy substitution.
16✔
1026
   */
4✔
1027
  if (is_data_xfer_cmd(cmd) == TRUE ||
9✔
1028
      pr_cmd_cmp(cmd, PR_CMD_ABOR_ID) == 0) {
1029
    const char *proto;
9✔
1030

1031
    proto = pr_session_get_protocol(0);
9✔
1032

3✔
1033
    if (strcmp(proto, "ftp") == 0 ||
7✔
1034
        strcmp(proto, "ftps") == 0) {
6✔
1035
      if (!(XFER_ABORTED)) {
6✔
1036
        int res;
1037
        const char *resp_code = NULL, *resp_msg = NULL;
1038

1039
        /* Get the last response code/message.  We use heuristics here to
1040
         * determine when to use "failed" versus "success".
6✔
1041
         */
6✔
1042
        res = pr_response_get_last(cmd->tmp_pool, &resp_code, &resp_msg);
6✔
1043
        if (res == 0 &&
4✔
1044
            resp_code != NULL) {
2✔
1045
          if (*resp_code == '2') {
1046
            if (pr_cmd_cmp(cmd, PR_CMD_ABOR_ID) != 0) {
1047
              transfer_status = "success";
1048

1049
            } else {
1050
              /* We're handling the ABOR command, so obviously the value
1051
               * should be 'cancelled'.
1✔
1052
               */
1053
              transfer_status = "cancelled";
1054
            }
2✔
1055

1056
          } else if (*resp_code == '1') {
1057
            /* If the first digit of the response code is 1, then the
1058
             * response code (for a data transfer command) is probably 150,
1059
             * which means that the transfer was still in progress (didn't
1060
             * complete with a 2xx/4xx response code) when we are called here,
1061
             * which in turn means a timeout kicked in.
1062
             */
1063
            transfer_status = "timeout";
1064

1✔
1065
          } else {
1066
            transfer_status = "failed";
1067
          }
1068

1069
        } else {
1070
          transfer_status = "success";
1071
        }
1072

1073
      } else {
1074
        transfer_status = "cancelled";
1075
      }
1076

1077
    } else {
1078
      /* mod_sftp stashes a note for us in the command notes if the transfer
1079
       * failed.
2✔
1080
       */
1081
      const char *sftp_status;
2✔
1082

2✔
1083
      sftp_status = pr_table_get(cmd->notes, "mod_sftp.file-status", NULL);
1084
      if (sftp_status == NULL) {
1085
        transfer_status = "success";
1086

1✔
1087
      } else {
1088
        transfer_status = "failed";
1089
      }
1090
    }
1091
  }
12✔
1092

1093
  return transfer_status;
1094
}
12✔
1095

12✔
1096
static int get_meta_transfer_port(cmd_rec *cmd) {
1097
  int transfer_port = 0;
23✔
1098

21✔
1099
  if (pr_cmd_cmp(cmd, PR_CMD_PASV_ID) == 0 ||
19✔
1100
      pr_cmd_cmp(cmd, PR_CMD_PORT_ID) == 0 ||
17✔
1101
      pr_cmd_cmp(cmd, PR_CMD_EPRT_ID) == 0 ||
8✔
1102
      pr_cmd_cmp(cmd, PR_CMD_EPSV_ID) == 0 ||
11✔
1103
      is_data_xfer_cmd(cmd) == TRUE) {
1104
    transfer_port = session.data_port;
1105
  }
12✔
1106

1107
  return transfer_port;
1108
}
×
1109

×
1110
static const char *get_meta_transfer_speed(cmd_rec *cmd) {
×
1111
  const off_t *size_note = NULL;
×
1112
  uint64_t start_ms = 0, end_ms = 0;
×
1113
  double file_size = 0.0, xfer_ms = 0.0, xfer_speed = 0.0;
1114
  char text[64];
×
1115

1116
  if (is_data_xfer_cmd(cmd) != TRUE) {
1117
    return NULL;
1118
  }
×
1119

×
1120
  size_note = pr_table_get(cmd->notes, "mod_xfer.file-size", NULL);
1121
  if (size_note == NULL) {
1122
    return NULL;
1123
  }
×
1124

×
1125
  if (session.xfer.p == NULL ||
×
1126
      (session.xfer.start_time.tv_sec == 0 &&
1127
       session.xfer.start_time.tv_usec == 0)) {
1128
    return NULL;
1129
  }
×
1130

1131
  file_size = (double) *size_note;
×
1132

×
1133
  pr_timeval2millis(&(session.xfer.start_time), &start_ms);
1134
  pr_gettimeofday_millis(&end_ms);
×
1135

1136
  xfer_ms = end_ms - start_ms;
×
1137

×
1138
  if (xfer_ms <= 0.0) {
1139
    xfer_ms = 0.1;
1140
  }
×
1141

×
1142
  if (file_size > (off_t) 0) {
1143
    xfer_speed = ((file_size / 1024) / (xfer_ms / 1000));
1144

1145
  } else {
1146
    xfer_speed = 0.0;
1147
  }
×
1148

×
1149
  memset(text, '\0', sizeof(text));
1150
  pr_snprintf(text, sizeof(text)-1, "%.2fKB/s", xfer_speed);
×
1151

1152
  return pstrdup(cmd->pool, text);
1153
}
8✔
1154

8✔
1155
static const char *get_meta_transfer_type(cmd_rec *cmd) {
1156
  const char *transfer_type = NULL;
1157

1158
  /* If the current command is one that incurs a data transfer, then we
1159
   * need to do more work.  If not, it's an easy substitution.
8✔
1160
   */
5✔
1161
  if (is_data_xfer_cmd(cmd) == TRUE) {
1162
    const char *proto;
5✔
1163

1164
    proto = pr_session_get_protocol(0);
5✔
1165

4✔
1166
    if (strcmp(proto, "sftp") == 0 ||
1167
        strcmp(proto, "scp") == 0) {
1168

1169
      /* Always binary. */
1170
      transfer_type = "binary";
1171

3✔
1172
    } else {
2✔
1173
      if ((session.sf_flags & SF_ASCII) ||
1174
          (session.sf_flags & SF_ASCII_OVERRIDE)) {
1175
        transfer_type = "ASCII";
1176

1✔
1177
      } else {
1178
        transfer_type = "binary";
1179
      }
1180
    }
1181
  }
8✔
1182

1183
  return transfer_type;
1184
}
307✔
1185

1186
static int resolve_logfmt_id(pool *p, unsigned char logfmt_id,
1187
    const char *logfmt_data, pr_jot_ctx_t *ctx, cmd_rec *cmd,
1188
    int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char,
1189
      const char *, const void *),
307✔
1190
    int (*on_default)(pool *, pr_jot_ctx_t *, unsigned char)) {
1191
  int res = 0;
307✔
1192

307✔
1193
  if (pr_trace_get_level(trace_channel) >= 17) {
1194
    const char *id_name;
307✔
1195

307✔
1196
    id_name = pr_jot_get_logfmt_id_name(logfmt_id);
1197
    if (id_name != NULL) {
305✔
1198

9✔
1199
      if (logfmt_data != NULL) {
1200
        pr_trace_msg(trace_channel, 17,
1201
          "resolving LogFormat ID %u (%s) with data '%s' (%lu)",
9✔
1202
          (unsigned int) logfmt_id, id_name, logfmt_data,
1203
          (unsigned long) strlen(logfmt_data));
1204

296✔
1205
      } else {
1206
        pr_trace_msg(trace_channel, 17, "resolving LogFormat ID %u (%s)",
1207
          (unsigned int) logfmt_id, id_name);
1208
      }
1209
    }
1210
  }
307✔
1211

30✔
1212
  switch (logfmt_id) {
30✔
1213
    case LOGFMT_META_BASENAME: {
1214
      const char *basename;
30✔
1215

30✔
1216
      basename = get_meta_basename(cmd);
25✔
1217
      if (basename != NULL) {
1218
        res = (on_meta)(p, ctx, logfmt_id, NULL, basename);
1219

5✔
1220
      } else {
1221
        res = (on_default)(p, ctx, logfmt_id);
1222
      }
1223

1224
      break;
1225
    }
5✔
1226

5✔
1227
    case LOGFMT_META_BYTES_SENT: {
5✔
1228
      double bytes_sent;
1229
      int have_bytes = FALSE;
5✔
1230

1✔
1231
      if (session.xfer.p != NULL) {
1✔
1232
        bytes_sent = session.xfer.total_bytes;
1233
        have_bytes = TRUE;
4✔
1234

1✔
1235
      } else if (pr_cmd_cmp(cmd, PR_CMD_DELE_ID) == 0) {
1✔
1236
        bytes_sent = jot_deleted_filesz;
1237
        have_bytes = TRUE;
1238
      }
2✔
1239

2✔
1240
      if (have_bytes == TRUE) {
1241
        res = (on_meta)(p, ctx, logfmt_id, NULL, &bytes_sent);
1242

3✔
1243
      } else {
1244
        res = (on_default)(p, ctx, logfmt_id);
1245
      }
5✔
1246

1247
      break;
1248
    }
2✔
1249

2✔
1250
    case LOGFMT_META_CUSTOM: {
2✔
1251
      if (logfmt_data != NULL) {
1252
        res = (on_meta)(p, ctx, logfmt_id, NULL, logfmt_data);
1253
      }
1254

1255
      break;
1256
    }
2✔
1257

2✔
1258
    case LOGFMT_META_EPOCH: {
2✔
1259
      double epoch;
1260
      struct timeval tv;
2✔
1261

2✔
1262
      (void) gettimeofday(&tv, NULL);
2✔
1263
      epoch = (double) tv.tv_sec;
2✔
1264
      res = (on_meta)(p, ctx, logfmt_id, NULL, &epoch);
1265
      break;
1266
    }
28✔
1267

28✔
1268
    case LOGFMT_META_FILENAME: {
1269
      const char *filename;
28✔
1270

28✔
1271
      filename = get_meta_filename(cmd);
25✔
1272
      if (filename != NULL) {
1273
        res = (on_meta)(p, ctx, logfmt_id, NULL, filename);
1274

3✔
1275
      } else {
1276
        res = (on_default)(p, ctx, logfmt_id);
1277
      }
1278

1279
      break;
1280
    }
4✔
1281

4✔
1282
    case LOGFMT_META_FILE_OFFSET: {
1283
      const off_t *note;
4✔
1284

4✔
1285
      note = pr_table_get(cmd->notes, "mod_xfer.file-offset", NULL);
1✔
1286
      if (note != NULL) {
1287
        double file_offset;
1✔
1288

1✔
1289
        file_offset = (double) *note;;
1290
        res = (on_meta)(p, ctx, logfmt_id, NULL, &file_offset);
1291

3✔
1292
      } else {
1293
        res = (on_default)(p, ctx, logfmt_id);
1294
      }
1295

1296
      break;
1297
    }
4✔
1298

4✔
1299
    case LOGFMT_META_FILE_SIZE: {
1300
      const off_t *note;
4✔
1301

4✔
1302
      note = pr_table_get(cmd->notes, "mod_xfer.file-size", NULL);
1✔
1303
      if (note != NULL) {
1304
        double file_size;
1✔
1305

1✔
1306
        file_size = (double) *note;
1307
        res = (on_meta)(p, ctx, logfmt_id, NULL, &file_size);
1308

3✔
1309
      } else {
1310
        res = (on_default)(p, ctx, logfmt_id);
1311
      }
1312

1313
      break;
1314
    }
5✔
1315

5✔
1316
    case LOGFMT_META_ENV_VAR: {
2✔
1317
      if (logfmt_data != NULL) {
2✔
1318
        const char *key;
1319
        char *env;
2✔
1320

2✔
1321
        key = logfmt_data;
2✔
1322
        env = pr_env_get(p, key);
1✔
1323
        if (env != NULL) {
1324
          char *field_name;
1✔
1325

1✔
1326
          field_name = pstrcat(p, PR_JOT_LOGFMT_ENV_VAR_KEY, key, NULL);
1327
          res = (on_meta)(p, ctx, logfmt_id, field_name, env);
1328

1✔
1329
        } else {
1330
          res = (on_default)(p, ctx, logfmt_id);
1331
        }
1332
      }
1333

1334
      break;
1335
    }
2✔
1336

2✔
1337
    case LOGFMT_META_REMOTE_HOST: {
1338
      const char *name;
2✔
1339

2✔
1340
      name = pr_netaddr_get_sess_remote_name();
2✔
1341
      res = (on_meta)(p, ctx, logfmt_id, NULL, name);
1342
      break;
1343
    }
2✔
1344

2✔
1345
    case LOGFMT_META_REMOTE_IP: {
1346
      const char *ipstr;
2✔
1347

2✔
1348
      ipstr = pr_netaddr_get_ipstr(pr_netaddr_get_sess_remote_addr());
2✔
1349
      res = (on_meta)(p, ctx, logfmt_id, NULL, ipstr);
1350
      break;
1351
    }
4✔
1352

4✔
1353
    case LOGFMT_META_REMOTE_PORT: {
4✔
1354
      double client_port;
1355
      const pr_netaddr_t *remote_addr;
4✔
1356

4✔
1357
      remote_addr = pr_netaddr_get_sess_remote_addr();
1✔
1358
      if (remote_addr != NULL) {
1✔
1359
        client_port = ntohs(pr_netaddr_get_port(remote_addr));
1360
        res = (on_meta)(p, ctx, logfmt_id, NULL, &client_port);
1361

3✔
1362
      } else {
1363
        res = (on_default)(p, ctx, logfmt_id);
1364
      }
4✔
1365

1366
      break;
1367
    }
4✔
1368

4✔
1369
    case LOGFMT_META_IDENT_USER: {
1370
      const char *ident_user;
4✔
1371

4✔
1372
      ident_user = pr_table_get(session.notes, "mod_ident.rfc1413-ident", NULL);
1✔
1373
      if (ident_user != NULL) {
1374
        res = (on_meta)(p, ctx, logfmt_id, NULL, ident_user);
1375

3✔
1376
      } else {
1377
        res = (on_default)(p, ctx, logfmt_id);
1378
      }
1379

1380
      break;
1381
    }
2✔
1382

2✔
1383
    case LOGFMT_META_PID: {
1384
      double sess_pid;
2✔
1385

2✔
1386
      sess_pid = session.pid;
2✔
1387
      res = (on_meta)(p, ctx, logfmt_id, NULL, &sess_pid);
1388
      break;
1389
    }
2✔
1390

2✔
1391
    case LOGFMT_META_TIME: {
2✔
1392
      struct tm *tm;
1393
      time_t now;
2✔
1394

2✔
1395
      now = time(NULL);
2✔
1396
      tm = pr_gmtime(p, &now);
2✔
1397
      if (tm != NULL) {
2✔
1398
        char ts[128];
1399
        const char *time_fmt = "%Y-%m-%d %H:%M:%S %z";
2✔
1400

×
1401
        if (logfmt_data != NULL) {
1402
          time_fmt = logfmt_data;
1403
        }
2✔
1404

2✔
1405
        strftime(ts, sizeof(ts)-1, time_fmt, tm);
1406
        res = (on_meta)(p, ctx, logfmt_id, logfmt_data, ts);
1407

×
1408
      } else {
1409
        res = (on_default)(p, ctx, logfmt_id);
1410
      }
2✔
1411

1412
      break;
1413
    }
6✔
1414

6✔
1415
    case LOGFMT_META_SECONDS: {
1416
      double transfer_secs;
6✔
1417

2✔
1418
      if (get_meta_transfer_secs(cmd, &transfer_secs) == 0) {
1419
        res = (on_meta)(p, ctx, logfmt_id, NULL, &transfer_secs);
1420

4✔
1421
      } else {
1422
        res = (on_default)(p, ctx, logfmt_id);
1423
      }
6✔
1424

1425
      break;
1426
    }
10✔
1427

10✔
1428
    case LOGFMT_META_COMMAND: {
1429
      const char *full_cmd;
1430

10✔
1431
      /* Note: Ignore "fake" commands like CONNECT, DISCONNECT, EXIT. */
1432
      if ((cmd->cmd_class & CL_CONNECT) ||
1433
          (cmd->cmd_class & CL_DISCONNECT)) {
1434
        full_cmd = NULL;
1435

8✔
1436
      } else {
2✔
1437
        if (pr_cmd_cmp(cmd, PR_CMD_PASS_ID) == 0 &&
1438
            session.hide_password) {
1439
          full_cmd = "PASS (hidden)";
7✔
1440

1441
        } else if (pr_cmd_cmp(cmd, PR_CMD_ADAT_ID) == 0) {
1442
          full_cmd = "ADAT (hidden)";
1443

6✔
1444
        } else {
1445
          full_cmd = get_full_cmd(cmd);
1446
        }
1447
      }
8✔
1448

8✔
1449
      if (full_cmd != NULL) {
1450
        res = (on_meta)(p, ctx, logfmt_id, NULL, full_cmd);
1451

2✔
1452
      } else {
1453
        res = (on_default)(p, ctx, logfmt_id);
1454
      }
1455

1456
      break;
1457
    }
4✔
1458

4✔
1459
    case LOGFMT_META_LOCAL_NAME: {
1✔
1460
      if (cmd->server != NULL) {
1461
        res = (on_meta)(p, ctx, logfmt_id, NULL, cmd->server->ServerName);
1462

3✔
1463
      } else {
1464
        res = (on_default)(p, ctx, logfmt_id);
1465
      }
1466

1467
      break;
1468
    }
4✔
1469

4✔
1470
    case LOGFMT_META_LOCAL_PORT: {
1✔
1471
      if (cmd->server != NULL) {
1472
        double server_port;
1✔
1473

1✔
1474
        server_port = cmd->server->ServerPort;
1475
        res = (on_meta)(p, ctx, logfmt_id, NULL, &server_port);
1476

3✔
1477
      } else {
1478
        res = (on_default)(p, ctx, logfmt_id);
1479
      }
1480

1481
      break;
1482
    }
2✔
1483

2✔
1484
    case LOGFMT_META_LOCAL_IP: {
1485
      const char *ipstr;
2✔
1486

2✔
1487
      ipstr = pr_netaddr_get_ipstr(pr_netaddr_get_sess_local_addr());
2✔
1488
      res = (on_meta)(p, ctx, logfmt_id, NULL, ipstr);
1489
      break;
1490
    }
2✔
1491

2✔
1492
    case LOGFMT_META_LOCAL_FQDN: {
1493
      const char *dnsstr;
2✔
1494

2✔
1495
      dnsstr = pr_netaddr_get_dnsstr(pr_netaddr_get_sess_local_addr());
2✔
1496
      res = (on_meta)(p, ctx, logfmt_id, NULL, dnsstr);
1497
      break;
1498
    }
4✔
1499

4✔
1500
    case LOGFMT_META_USER: {
1✔
1501
      if (session.user != NULL) {
1502
        res = (on_meta)(p, ctx, logfmt_id, NULL, session.user);
1503

3✔
1504
      } else {
1505
        res = (on_default)(p, ctx, logfmt_id);
1506
      }
1507

1508
      break;
1509
    }
4✔
1510

4✔
1511
    case LOGFMT_META_ORIGINAL_USER: {
1512
      const char *orig_user = NULL;
4✔
1513

4✔
1514
      orig_user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
1✔
1515
      if (orig_user != NULL) {
1516
        res = (on_meta)(p, ctx, logfmt_id, NULL, orig_user);
1517

3✔
1518
      } else {
1519
        res = (on_default)(p, ctx, logfmt_id);
1520
      }
1521

1522
      break;
1523
    }
5✔
1524

5✔
1525
    case LOGFMT_META_RESPONSE_CODE: {
5✔
1526
      const char *resp_code = NULL;
5✔
1527
      double resp_num;
1528
      int have_code = FALSE, last;
5✔
1529

5✔
1530
      last = pr_response_get_last(cmd->tmp_pool, &resp_code, NULL);
5✔
1531
      if (last == 0 &&
1✔
1532
          resp_code != NULL) {
1✔
1533
        resp_num = atoi(resp_code);
1534
        have_code = TRUE;
1535

4✔
1536
      /* Hack to add return code for proper logging of QUIT command. */
1✔
1537
      } else if (pr_cmd_cmp(cmd, PR_CMD_QUIT_ID) == 0) {
1✔
1538
        resp_num = 221;
1539
        have_code = TRUE;
1540
      }
2✔
1541

2✔
1542
      if (have_code == TRUE) {
1543
        res = (on_meta)(p, ctx, logfmt_id, NULL, &resp_num);
1544

3✔
1545
      } else {
1546
        res = (on_default)(p, ctx, logfmt_id);
1547
      }
5✔
1548

1549
      break;
1550
    }
4✔
1551

4✔
1552
    case LOGFMT_META_CLASS: {
1✔
1553
      if (session.conn_class != NULL) {
1554
        res = (on_meta)(p, ctx, logfmt_id, NULL, session.conn_class);
1555

3✔
1556
      } else {
1557
        res = (on_default)(p, ctx, logfmt_id);
1558
      }
1559

1560
      break;
1561
    }
4✔
1562

4✔
1563
    case LOGFMT_META_ANON_PASS: {
1564
      const char *anon_pass;
4✔
1565

4✔
1566
      anon_pass = pr_table_get(session.notes, "mod_auth.anon-passwd", NULL);
1✔
1567
      if (anon_pass != NULL) {
1568
        res = (on_meta)(p, ctx, logfmt_id, NULL, anon_pass);
1569

3✔
1570
      } else {
1571
        res = (on_default)(p, ctx, logfmt_id);
1572
      }
1573

1574
      break;
1575
    }
6✔
1576

6✔
1577
    case LOGFMT_META_METHOD: {
1578
      const char *method = NULL;
6✔
1579

1580
      if (pr_cmd_cmp(cmd, PR_CMD_SITE_ID) != 0) {
1581
        /* Note: Ignore "fake" commands like CONNECT, but NOT DISCONNECT/EXIT.
1582
         * This is for backward compatibility, for better/worse.
4✔
1583
         */
3✔
1584
        if (!(cmd->cmd_class & CL_CONNECT)) {
1585
          method = cmd->argv[0];
1586
        }
2✔
1587

1✔
1588
      } else if (cmd->argc > 1) {
1✔
1589
        char buf[128], *ch;
1590
        size_t len;
1591

1592
        /* Make sure that the SITE command used is all in uppercase, for
1593
         * logging purposes.
7✔
1594
         */
6✔
1595
        for (ch = cmd->argv[1]; *ch; ch++) {
6✔
1596
          if (PR_ISALPHA((int) *ch)) {
1597
            *ch = toupper((int) *ch);
1598
          }
1599
        }
2✔
1600

1✔
1601
        len = pr_snprintf(buf, sizeof(buf)-1, "%s %s", (char *) cmd->argv[0],
1602
          (char *) cmd->argv[1]);
1✔
1603

1604
        method = pstrndup(p, buf, len);
1605
      }
4✔
1606

4✔
1607
      if (method != NULL) {
1608
        res = (on_meta)(p, ctx, logfmt_id, NULL, method);
1609

2✔
1610
      } else {
1611
        res = (on_default)(p, ctx, logfmt_id);
1612
      }
1613

1614
      break;
1615
    }
10✔
1616

10✔
1617
    case LOGFMT_META_XFER_PATH: {
1618
      const char *transfer_path;
10✔
1619

10✔
1620
      transfer_path = get_meta_transfer_path(cmd);
7✔
1621
      if (transfer_path != NULL) {
1622
        res = (on_meta)(p, ctx, logfmt_id, NULL, transfer_path);
1623

3✔
1624
      } else {
1625
        res = (on_default)(p, ctx, logfmt_id);
1626
      }
1627

1628
      break;
1629
    }
14✔
1630

14✔
1631
    case LOGFMT_META_DIR_NAME: {
1632
      const char *dir_name;
14✔
1633

14✔
1634
      dir_name = get_meta_dir_name(cmd);
13✔
1635
      if (dir_name != NULL) {
1636
        res = (on_meta)(p, ctx, logfmt_id, NULL, dir_name);
1637

1✔
1638
      } else {
1639
        res = (on_default)(p, ctx, logfmt_id);
1640
      }
1641

1642
      break;
1643
    }
14✔
1644

14✔
1645
    case LOGFMT_META_DIR_PATH: {
1646
      const char *dir_path;
14✔
1647

14✔
1648
      dir_path = get_meta_dir_path(cmd);
11✔
1649
      if (dir_path != NULL) {
1650
        res = (on_meta)(p, ctx, logfmt_id, NULL, dir_path);
1651

3✔
1652
      } else {
1653
        res = (on_default)(p, ctx, logfmt_id);
1654
      }
1655

1656
      break;
1657
    }
8✔
1658

8✔
1659
    case LOGFMT_META_CMD_PARAMS: {
1660
      const char *params = NULL;
1661

8✔
1662
      /* Note: Ignore "fake" commands like CONNECT, DISCONNECT, EXIT. */
1663
      if ((cmd->cmd_class & CL_CONNECT) ||
1664
          (cmd->cmd_class & CL_DISCONNECT)) {
1665
        params = NULL;
1666

11✔
1667
      } else {
5✔
1668
        if (pr_cmd_cmp(cmd, PR_CMD_ADAT_ID) == 0 ||
1669
            pr_cmd_cmp(cmd, PR_CMD_PASS_ID) == 0) {
1670
          params = "(hidden)";
4✔
1671

1✔
1672
        } else if (cmd->argc > 1) {
1673
          params = pr_fs_decode_path(p, cmd->arg);
1674
        }
1675
      }
3✔
1676

3✔
1677
      if (params != NULL) {
1678
        res = (on_meta)(p, ctx, logfmt_id, NULL, params);
1679

5✔
1680
      } else {
1681
        res = (on_default)(p, ctx, logfmt_id);
1682
      }
1683

1684
      break;
1685
    }
4✔
1686

4✔
1687
    case LOGFMT_META_RESPONSE_STR: {
4✔
1688
      const char *resp_msg = NULL;
1689
      int last;
4✔
1690

4✔
1691
      last = pr_response_get_last(p, NULL, &resp_msg);
4✔
1692
      if (last == 0 &&
1✔
1693
          resp_msg != NULL) {
1694
        res = (on_meta)(p, ctx, logfmt_id, NULL, resp_msg);
1695

3✔
1696
      } else {
1697
        res = (on_default)(p, ctx, logfmt_id);
1698
      }
4✔
1699

1700
      break;
1701
    }
4✔
1702

4✔
1703
    case LOGFMT_META_RESPONSE_MS: {
1704
      const uint64_t *start_ms;
4✔
1705

4✔
1706
      start_ms = pr_table_get(cmd->notes, "start_ms", NULL);
1✔
1707
      if (start_ms != NULL) {
1✔
1708
        uint64_t end_ms = 0;
1709
        double response_ms;
1✔
1710

1711
        pr_gettimeofday_millis(&end_ms);
1✔
1712

1✔
1713
        response_ms = end_ms - *start_ms;
1714
        res = (on_meta)(p, ctx, logfmt_id, NULL, &response_ms);
1715

3✔
1716
      } else {
1717
        res = (on_default)(p, ctx, logfmt_id);
1718
      }
1719

1720
      break;
1721
    }
2✔
1722

2✔
1723
    case LOGFMT_META_PROTOCOL: {
1724
      const char *proto;
2✔
1725

2✔
1726
      proto = pr_session_get_protocol(0);
2✔
1727
      res = (on_meta)(p, ctx, logfmt_id, NULL, proto);
1728
      break;
1729
    }
2✔
1730

2✔
1731
    case LOGFMT_META_VERSION: {
1732
      const char *version;
2✔
1733

2✔
1734
      version = PROFTPD_VERSION_TEXT;
2✔
1735
      res = (on_meta)(p, ctx, logfmt_id, NULL, version);
1736
      break;
1737
    }
5✔
1738

5✔
1739
    case LOGFMT_META_RENAME_FROM: {
2✔
1740
      if (pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0) {
1741
        const char *rnfr_path;
2✔
1742

2✔
1743
        rnfr_path = pr_table_get(session.notes, "mod_core.rnfr-path", NULL);
1✔
1744
        if (rnfr_path != NULL) {
1745
          res = (on_meta)(p, ctx, logfmt_id, NULL, rnfr_path);
1746

1✔
1747
        } else {
1748
          res = (on_default)(p, ctx, logfmt_id);
1749
        }
1750

3✔
1751
      } else {
1752
        res = (on_default)(p, ctx, logfmt_id);
1753
      }
1754

1755
      break;
1756
    }
4✔
1757

4✔
1758
    case LOGFMT_META_FILE_MODIFIED: {
4✔
1759
      int modified = FALSE;
1760
      const char *val;
4✔
1761

4✔
1762
      val = pr_table_get(cmd->notes, "mod_xfer.file-modified", NULL);
1✔
1763
      if (val != NULL) {
1✔
1764
        if (strcasecmp(val, "true") == 0) {
1765
          modified = TRUE;
1766
        }
1767
      }
4✔
1768

4✔
1769
      res = (on_meta)(p, ctx, logfmt_id, NULL, &modified);
1770
      break;
1771
    }
4✔
1772

4✔
1773
    case LOGFMT_META_UID: {
1774
      double sess_uid;
4✔
1775

1✔
1776
      if (session.auth_mech != NULL) {
1777
        sess_uid = session.login_uid;
1778

3✔
1779
      } else {
1780
        sess_uid = geteuid();
1781
      }
4✔
1782

4✔
1783
      res = (on_meta)(p, ctx, logfmt_id, NULL, &sess_uid);
1784
      break;
1785
    }
4✔
1786

4✔
1787
    case LOGFMT_META_GID: {
1788
      double sess_gid;
4✔
1789

1✔
1790
      if (session.auth_mech != NULL) {
1791
        sess_gid = session.login_gid;
1792

3✔
1793
      } else {
1794
        sess_gid = getegid();
1795
      }
4✔
1796

4✔
1797
      res = (on_meta)(p, ctx, logfmt_id, NULL, &sess_gid);
1798
      break;
1799
    }
2✔
1800

2✔
1801
    case LOGFMT_META_RAW_BYTES_IN: {
1802
      double bytes_rcvd;
2✔
1803

2✔
1804
      bytes_rcvd = session.total_raw_in;
2✔
1805
      res = (on_meta)(p, ctx, logfmt_id, NULL, &bytes_rcvd);
1806
      break;
1807
    }
2✔
1808

2✔
1809
    case LOGFMT_META_RAW_BYTES_OUT: {
1810
      double bytes_sent;
2✔
1811

2✔
1812
      bytes_sent = session.total_raw_out;
2✔
1813
      res = (on_meta)(p, ctx, logfmt_id, NULL, &bytes_sent);
1814
      break;
1815
    }
5✔
1816

5✔
1817
    case LOGFMT_META_EOS_REASON: {
1818
      const char *reason = NULL, *details = NULL, *eos = NULL;
5✔
1819

2✔
1820
      if (session.disconnect_reason != PR_SESS_DISCONNECT_UNSPECIFIED) {
2✔
1821
        eos = pr_session_get_disconnect_reason(&details);
1✔
1822
        if (eos != NULL) {
1✔
1823
          if (details != NULL) {
1824
            reason = pstrcat(p, eos, ": ", details, NULL);
1825

1826
          } else {
1827
            reason = eos;
1828
          }
1829
        }
1830
      }
1✔
1831

1✔
1832
      if (reason != NULL) {
1833
        res = (on_meta)(p, ctx, logfmt_id, NULL, reason);
1834

4✔
1835
      } else {
1836
        res = (on_default)(p, ctx, logfmt_id);
1837
      }
5✔
1838

1839
      break;
1840
    }
4✔
1841

4✔
1842
    case LOGFMT_META_VHOST_IP:
1✔
1843
      if (cmd->server != NULL) {
1844
        res = (on_meta)(p, ctx, logfmt_id, NULL, cmd->server->ServerAddress);
1845

3✔
1846
      } else {
1847
        res = (on_default)(p, ctx, logfmt_id);
1848
      }
1849

1850
      break;
6✔
1851

6✔
1852
    case LOGFMT_META_NOTE_VAR: {
3✔
1853
      if (logfmt_data != NULL) {
1854
        const char *note = NULL;
3✔
1855

1856
        pr_trace_msg(trace_channel, 19,
1857
          "resolving NOTE_VAR using note key '%s'", logfmt_data);
1858

3✔
1859
        /* Check in the cmd->notes table first. */
3✔
1860
        note = pr_table_get(cmd->notes, logfmt_data, NULL);
1861
        if (note == NULL) {
1862

2✔
1863
          /* If not there, check in the session.notes table. */
1864
          note = pr_table_get(session.notes, logfmt_data, NULL);
1865
        }
3✔
1866

2✔
1867
        if (note != NULL) {
1868
          char *field_name;
2✔
1869

2✔
1870
          field_name = pstrcat(p, PR_JOT_LOGFMT_NOTE_KEY, note, NULL);
1871
          res = (on_meta)(p, ctx, logfmt_id, field_name, note);
1872

1✔
1873
        } else {
1874
          res = (on_default)(p, ctx, logfmt_id);
1875
        }
1876
      }
1877

1878
      break;
1879
    }
3✔
1880

3✔
1881
    case LOGFMT_META_VAR_VAR: {
2✔
1882
      if (logfmt_data != NULL) {
2✔
1883
        char *key;
1884
        const char *var = NULL;
2✔
1885

1886
        pr_trace_msg(trace_channel, 19,
1887
          "resolving VAR_VAR using var key '%s'", logfmt_data);
1888

1889
        /* Note that the Var API is particular about its lookup keys; it
1890
         * expects the enclosing "%{...}" to be present.  Fun.
2✔
1891
         */
1892
        key = pstrcat(p, "%{", logfmt_data, "}", NULL);
2✔
1893

2✔
1894
        var = pr_var_get(key);
1✔
1895
        if (var != NULL) {
1896
          char *field_name;
1✔
1897

1✔
1898
          field_name = pstrcat(p, PR_JOT_LOGFMT_VAR_KEY, var, NULL);
1899
          res = (on_meta)(p, ctx, logfmt_id, field_name, var);
1900

1✔
1901
        } else {
1✔
1902
          pr_trace_msg(trace_channel, 7, "error resolving VAR_VAR '%s': %s",
1✔
1903
            logfmt_data, strerror(errno));
1904
          res = (on_default)(p, ctx, logfmt_id);
1905
        }
1906
      }
1907

1908
      break;
1909
    }
12✔
1910

12✔
1911
    case LOGFMT_META_XFER_STATUS: {
1912
      const char *transfer_status;
12✔
1913

12✔
1914
      transfer_status = get_meta_transfer_status(cmd);
9✔
1915
      if (transfer_status != NULL) {
1916
        res = (on_meta)(p, ctx, logfmt_id, NULL, transfer_status);
1917

3✔
1918
      } else {
1919
        res = (on_default)(p, ctx, logfmt_id);
1920
      }
1921

1922
      break;
1923
    }
9✔
1924

9✔
1925
    case LOGFMT_META_XFER_FAILURE: {
1926
      const char *transfer_failure;
9✔
1927

9✔
1928
      transfer_failure = get_meta_transfer_failure(cmd);
4✔
1929
      if (transfer_failure != NULL) {
1930
        res = (on_meta)(p, ctx, logfmt_id, NULL, transfer_failure);
1931

5✔
1932
      } else {
1933
        res = (on_default)(p, ctx, logfmt_id);
1934
      }
1935

1936
      break;
1937
    }
6✔
1938

6✔
1939
    case LOGFMT_META_XFER_MS: {
1940
      if (session.xfer.p != NULL) {
1941
        /* Make sure that session.xfer.start_time actually has values (which
1942
         * is not always the case).
3✔
1943
         */
2✔
1944
        if (session.xfer.start_time.tv_sec != 0 ||
2✔
1945
            session.xfer.start_time.tv_usec != 0) {
2✔
1946
          uint64_t start_ms = 0, end_ms = 0;
1947
          double transfer_ms;
2✔
1948

2✔
1949
          pr_timeval2millis(&(session.xfer.start_time), &start_ms);
1950
          pr_gettimeofday_millis(&end_ms);
2✔
1951

2✔
1952
          transfer_ms = end_ms - start_ms;
1953
          res = (on_meta)(p, ctx, logfmt_id, NULL, &transfer_ms);
1954

1✔
1955
        } else {
1956
          res = (on_default)(p, ctx, logfmt_id);
1957
        }
1958

3✔
1959
      } else {
1960
        res = (on_default)(p, ctx, logfmt_id);
1961
      }
1962

1963
      break;
1964
    }
12✔
1965

12✔
1966
    case LOGFMT_META_XFER_PORT: {
1967
      int transfer_port;
12✔
1968

12✔
1969
      transfer_port = get_meta_transfer_port(cmd);
11✔
1970
      if (transfer_port > 0) {
1971
        double xfer_port;
11✔
1972

11✔
1973
        xfer_port = (double) transfer_port;
1974
        res = (on_meta)(p, ctx, logfmt_id, NULL, &xfer_port);
1975

1✔
1976
      } else {
1977
        res = (on_default)(p, ctx, logfmt_id);
1978
      }
1979

1980
      break;
1981
    }
×
1982

×
1983
    case LOGFMT_META_XFER_SPEED: {
1984
      const char *transfer_speed;
×
1985

×
1986
      transfer_speed = get_meta_transfer_speed(cmd);
×
1987
      if (transfer_speed != NULL) {
1988
        res = (on_meta)(p, ctx, logfmt_id, NULL, transfer_speed);
1989

×
1990
      } else {
1991
        res = (on_default)(p, ctx, logfmt_id);
1992
      }
1993

1994
      break;
1995
    }
8✔
1996

8✔
1997
    case LOGFMT_META_XFER_TYPE: {
1998
      const char *transfer_type;
8✔
1999

8✔
2000
      transfer_type = get_meta_transfer_type(cmd);
5✔
2001
      if (transfer_type != NULL) {
2002
        res = (on_meta)(p, ctx, logfmt_id, NULL, transfer_type);
2003

3✔
2004
      } else {
2005
        res = (on_default)(p, ctx, logfmt_id);
2006
      }
2007

2008
      break;
2009
    }
2✔
2010

2✔
2011
    case LOGFMT_META_MICROSECS: {
2✔
2012
      double sess_usecs;
2013
      struct timeval now;
2✔
2014

2✔
2015
      gettimeofday(&now, NULL);
2016
      sess_usecs = now.tv_usec;
2✔
2017

2✔
2018
      res = (on_meta)(p, ctx, logfmt_id, NULL, &sess_usecs);
2019
      break;
2020
    }
2✔
2021

2✔
2022
    case LOGFMT_META_MILLISECS: {
2✔
2023
      double sess_msecs;
2024
      struct timeval now;
2✔
2025

2026
      gettimeofday(&now, NULL);
2027

2✔
2028
      /* Convert microsecs to millisecs. */
2029
      sess_msecs = (now.tv_usec / 1000);
2✔
2030

2✔
2031
      res = (on_meta)(p, ctx, logfmt_id, NULL, &sess_msecs);
2032
      break;
2033
    }
2✔
2034

2✔
2035
    case LOGFMT_META_ISO8601: {
2✔
2036
      struct tm *tm;
2037
      struct timeval now;
2✔
2038

2✔
2039
      gettimeofday(&now, NULL);
2✔
2040
      tm = pr_localtime(p, (const time_t *) &(now.tv_sec));
2✔
2041
      if (tm != NULL) {
2✔
2042
        char ts[128];
2✔
2043
        size_t len;
2044
        unsigned long millis;
2✔
2045

2046
        len = strftime(ts, sizeof(ts)-1, "%Y-%m-%d %H:%M:%S", tm);
2047

2✔
2048
        /* Convert microsecs to millisecs. */
2049
        millis = now.tv_usec / 1000;
2✔
2050

2✔
2051
        pr_snprintf(ts + len, sizeof(ts) - len - 1, ",%03lu", millis);
2052
        res = (on_meta)(p, ctx, logfmt_id, NULL, ts);
2053

×
2054
      } else {
2055
        res = (on_default)(p, ctx, logfmt_id);
2056
      }
2✔
2057

2058
      break;
2059
    }
4✔
2060

4✔
2061
    case LOGFMT_META_GROUP: {
1✔
2062
      if (session.group != NULL) {
2063
        res = (on_meta)(p, ctx, logfmt_id, NULL, session.group);
2064

3✔
2065
      } else {
2066
        res = (on_default)(p, ctx, logfmt_id);
2067
      }
2068

2069
      break;
2070
    }
2✔
2071

2✔
2072
    default:
2073
      pr_trace_msg(trace_channel, 2, "skipping unsupported LogFormat ID %u",
2✔
2074
        (unsigned int) logfmt_id);
2075
      break;
2076
  }
300✔
2077

×
2078
  if (res < 0) {
2079
    return -1;
2080
  }
2081

2082
  return 0;
2083
}
253✔
2084

2085
static int resolve_meta(pool *p, unsigned char **logfmt, pr_jot_ctx_t *ctx,
2086
    cmd_rec *cmd,
2087
    int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *,
2088
      const void *),
253✔
2089
    int (*on_default)(pool *, pr_jot_ctx_t *, unsigned char)) {
253✔
2090
  int res = 0;
253✔
2091
  unsigned char *ptr, logfmt_id;
253✔
2092
  const char *logfmt_data = NULL;
2093
  size_t consumed_bytes = 0, logfmt_datalen = 0;
253✔
2094

253✔
2095
  ptr = (*logfmt) + 1;
2096
  logfmt_id = *ptr;
253✔
2097

14✔
2098
  switch (logfmt_id) {
2099
    case LOGFMT_META_CUSTOM:
2100
    case LOGFMT_META_ENV_VAR:
2101
    case LOGFMT_META_NOTE_VAR:
2102
    case LOGFMT_META_VAR_VAR:
14✔
2103
    case LOGFMT_META_TIME: {
8✔
2104
      if (*(ptr + 1) == LOGFMT_META_START &&
8✔
2105
          *(ptr + 2) == LOGFMT_META_ARG) {
2106
        logfmt_data = get_meta_arg(p, (ptr + 3), &logfmt_datalen);
2107

8✔
2108
        /* Skip past the META_START, META_ARG, META_ARG_END, and the data. */
2109
        consumed_bytes += (3 + logfmt_datalen);
2110
      }
2111
    }
2112

253✔
2113
    default:
2114
      consumed_bytes += 1;
2115
  }
2116

2117
  /* Note: the LogFormat data, if present, is always text.  Callbacks ASSUME
2118
   * that that text will be a NUL-terminated string.
253✔
2119
   */
2120
  logfmt_data = pstrndup(p, logfmt_data, logfmt_datalen);
253✔
2121

2122
  res = resolve_logfmt_id(p, logfmt_id, logfmt_data, ctx, cmd, on_meta,
253✔
2123
    on_default);
2124
  if (res < 0) {
2125
    return -1;
2126
  }
2127

2128
  /* Most of the time, a meta is encoded in just one byte, so we adjust the
2129
   * pointer by incrementing by one.  Some meta are encoded using multiple
2130
   * bytes (e.g. environment variables, notes, etc).  The resolving of these
2131
   * meta will adjust the `consumed_bytes` value themselves.
2132
   */
253✔
2133

253✔
2134
  ptr += consumed_bytes;
253✔
2135
  *logfmt = ptr;
2136
  return 0;
2137
}
14✔
2138

2139
static int is_jottable_class(cmd_rec *cmd, int included_classes,
14✔
2140
    int excluded_classes) {
2141
  int jottable = FALSE;
14✔
2142

2143
  if (cmd->cmd_class != 0) {
2144
    /* If the command is unknown, then we only want to log if this filter is
2145
     * configured to log ALL commands (Bug#4313).
12✔
2146
     */
6✔
2147
    if (cmd->cmd_id >= 0) {
×
2148
      if (cmd->cmd_class & included_classes) {
2149
        jottable = TRUE;
2150
      }
6✔
2151

×
2152
      if (cmd->cmd_class & excluded_classes) {
2153
        jottable = FALSE;
2154
      }
2155

2156
    } else {
2157
      /* Handle unknown command.  The "CONNECT" and "EXIT" commands are
2158
       * internally generated, and thus have special treatment.
2159
       */
6✔
2160

2161
      pr_trace_msg(trace_channel, 25,
6✔
2162
        "checking if '%s', with unknown command ID, is jottable",
2163
        (char *) cmd->argv[0]);
6✔
2164

2165
      if ((cmd->cmd_class & CL_CONNECT) ||
6✔
2166
          (cmd->cmd_class & CL_DISCONNECT)) {
2✔
2167
        if (cmd->cmd_class & included_classes) {
2168
          jottable = TRUE;
2169
        }
6✔
2170

2✔
2171
        if (cmd->cmd_class & excluded_classes) {
2172
          jottable = FALSE;
2173
        }
2174

×
2175
      } else {
×
2176
        if (included_classes == CL_ALL) {
2177
          jottable = TRUE;
2178
        }
2179
      }
2180
    }
2181

2✔
2182
  } else {
2183
    pr_trace_msg(trace_channel, 25,
2✔
2184
      "checking if '%s', with unknown command classes, is jottable",
2185
      (char *) cmd->argv[0]);
2186

2187
    /* If the logging class of this command is unknown (defaults to zero),
2188
     * AND this filter logs ALL events, it is jottable.
2✔
2189
     */
2✔
2190
    if (included_classes == CL_ALL) {
2191
      jottable = TRUE;
2192
    }
2193
  }
14✔
2194

2195
  return jottable;
2196
}
6✔
2197

6✔
2198
static int is_jottable_cmd_id(cmd_rec *cmd, int *cmd_ids, size_t ncmd_ids) {
6✔
2199
  register unsigned int i;
2200
  int jottable = FALSE;
6✔
2201

2✔
2202
  for (i = 0; i < ncmd_ids; i++) {
2203
    if (pr_cmd_cmp(cmd, cmd_ids[i]) == 0) {
2204
      jottable = TRUE;
2205
      break;
2206
    }
2207
  }
6✔
2208

2209
  return jottable;
2210
}
335✔
2211

335✔
2212
/* Here, we will return -1 if we don't explicitly know whether the given
2213
 * command is jottable.
335✔
2214
 */
2215
static int is_jottable_cmd_name(cmd_rec *cmd, const char **cmd_names,
2216
    size_t ncmd_names) {
2217
  register unsigned int i;
14✔
2218
  int jottable = -1;
14✔
2219

2220
  for (i = 0; i < ncmd_names; i++) {
2221
    const char *cmd_name;
2222
    int negated = FALSE;
335✔
2223

10✔
2224
    cmd_name = cmd_names[i];
6✔
2225

6✔
2226
    if (cmd_name[0] == '!') {
2227
      negated = TRUE;
2228
      cmd_name++;
2229
    }
335✔
2230

2231
    if (strcmp(cmd_name, (const char *) cmd->argv[0]) == 0) {
2232
      jottable = TRUE;
×
2233

2234
      if (negated == TRUE) {
×
2235
        /* This particular command is being sifted out via '!' prefix. */
2236
        jottable = FALSE;
2237
      }
×
2238

2239
      break;
×
2240
    }
2241
  }
2242

73✔
2243
  return jottable;
2244
}
2245

2246
static int is_jottable(pool *p, cmd_rec *cmd, pr_jot_filters_t *filters) {
2247
  int jottable = FALSE;
2248

73✔
2249
  if (filters == NULL) {
2250
    jottable = TRUE;
73✔
2251
  }
73✔
2252

2253
  if (filters == NULL ||
3✔
2254
      filters->cmd_sifts == NULL) {
3✔
2255
    if (jottable == FALSE) {
2256
      jottable = is_jottable_class(cmd, filters->included_classes,
2257
        filters->excluded_classes);
2258
    }
70✔
2259

70✔
2260
    if (jottable == FALSE) {
2261
      if (filters->cmd_ids != NULL) {
3✔
2262
        jottable = is_jottable_cmd_id(cmd, filters->cmd_ids->elts,
3✔
2263
          filters->cmd_ids->nelts);
2264
      }
2265
    }
67✔
2266

13✔
2267
  } else {
2268
    int class_jottable = FALSE, cmd_jottable = FALSE;
2269

67✔
2270
    /* When sifting adds refinement, determination of jottability is more
67✔
2271
     * nuanced.
4✔
2272
     */
4✔
2273

4✔
2274
    class_jottable = is_jottable_class(cmd, filters->included_classes,
4✔
2275
      filters->excluded_classes);
2276
    cmd_jottable = is_jottable_cmd_name(cmd, filters->cmd_sifts->elts,
2277
      filters->cmd_sifts->nelts);
2278

63✔
2279
    if (class_jottable == TRUE) {
6✔
2280
      jottable = TRUE;
6✔
2281

2282
      if (cmd_jottable == FALSE) {
6✔
2283
        jottable = FALSE;
4✔
2284
      }
4✔
2285

2286
    } else {
2287
      jottable = FALSE;
2288

2289
      if (cmd_jottable == TRUE) {
2290
        jottable = TRUE;
3✔
2291
      }
3✔
2292
    }
2293
  }
3✔
2294

1✔
2295
  return jottable;
1✔
2296
}
2297

2298
static int jot_resolve_on_default(pool *p, pr_jot_ctx_t *ctx,
2299
    unsigned char meta) {
2300
  return 0;
2301
}
54✔
2302

2303
static int jot_resolve_on_other(pool *p, pr_jot_ctx_t *ctx, unsigned char *text,
2304
    size_t text_len) {
54✔
2305
  return 0;
2306
}
54✔
2307

2308
int pr_jot_resolve_logfmt_id(pool *p, cmd_rec *cmd, pr_jot_filters_t *filters,
2309
    unsigned char logfmt_id, const char *logfmt_data, size_t logfmt_datalen,
272✔
2310
    pr_jot_ctx_t *ctx,
2311
    int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *,
2312
      const void *),
2313
    int (*on_default)(pool *, pr_jot_ctx_t *, unsigned char)) {
2314
  int jottable = FALSE, res = 0;
2315

272✔
2316
  if (p == NULL ||
272✔
2317
      cmd == NULL ||
2318
      on_meta == NULL) {
272✔
2319
    errno = EINVAL;
272✔
2320
    return -1;
270✔
2321
  }
270✔
2322

4✔
2323
  /* There are some IDs which are reserved. */
4✔
2324
  if (logfmt_id == 0 ||
2325
      logfmt_id == LOGFMT_META_START ||
2326
      logfmt_id == LOGFMT_META_ARG_END) {
268✔
2327
    errno = EINVAL;
268✔
2328
    return -1;
4✔
2329
  }
4✔
2330

4✔
2331
  if (on_default == NULL) {
4✔
2332
    on_default = jot_resolve_on_default;
2333
  }
2334

264✔
2335
  jottable = is_jottable(p, cmd, filters);
5✔
2336
  if (jottable == FALSE) {
2337
    pr_trace_msg(trace_channel, 17, "ignoring filtered event '%s'",
2338
      (const char *) cmd->argv[0]);
264✔
2339
    errno = EPERM;
6✔
2340
    return -1;
2341
  }
2342

264✔
2343
  /* Special handling for the CONNECT/DISCONNECT meta. */
264✔
2344
  switch (logfmt_id) {
2345
    case LOGFMT_META_CONNECT: {
528✔
2346
      pr_trace_msg(trace_channel, 17, "resolving LogFormat ID %u (%s)",
264✔
2347
        (unsigned int) logfmt_id, pr_jot_get_logfmt_id_name(logfmt_id));
2348
      if (cmd->cmd_class == CL_CONNECT) {
264✔
2349
        int val = TRUE;
2350
        res = (on_meta)(p, ctx, LOGFMT_META_CONNECT, NULL, &val);
2351
      }
2352

2353
      return res;
2354
    }
2355

2356
    case LOGFMT_META_DISCONNECT: {
264✔
2357
      pr_trace_msg(trace_channel, 17, "resolving LogFormat ID %u (%s)",
2✔
2358
        (unsigned int) logfmt_id, pr_jot_get_logfmt_id_name(logfmt_id));
2✔
2359
      if (cmd->cmd_class == CL_DISCONNECT) {
2✔
2360
        int val = TRUE;
2361
        res = (on_meta)(p, ctx, LOGFMT_META_DISCONNECT, NULL, &val);
2362
      }
262✔
2363
      return res;
×
2364
    }
×
2365

2366
    default:
2367
      break;
2368
  }
2369

2370
  res = resolve_logfmt_id(p, logfmt_id, logfmt_data, ctx, cmd, on_meta,
2371
    on_default);
2372
  return res;
2373
}
262✔
2374

6✔
2375
int pr_jot_resolve_logfmt(pool *p, cmd_rec *cmd, pr_jot_filters_t *filters,
6✔
2376
    unsigned char *logfmt, pr_jot_ctx_t *ctx,
4✔
2377
    int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *,
4✔
2378
      const void *),
2379
    int (*on_default)(pool *, pr_jot_ctx_t *, unsigned char),
2380
    int (*on_other)(pool *, pr_jot_ctx_t *, unsigned char *, size_t)) {
4✔
2381
  int jottable = FALSE, res;
2382
  size_t text_len;
2383

2384
  if (p == NULL ||
6✔
2385
      cmd == NULL ||
6✔
2386
      logfmt == NULL ||
2387
      on_meta == NULL) {
3✔
2388
    errno = EINVAL;
3✔
2389
    return -1;
1✔
2390
  }
1✔
2391

2392
  jottable = is_jottable(p, cmd, filters);
2393
  if (jottable == FALSE) {
1✔
2394
    pr_trace_msg(trace_channel, 17, "ignoring filtered event '%s'",
2395
      (const char *) cmd->argv[0]);
2396
    errno = EPERM;
2397
    return -1;
2398
  }
2399

3✔
2400
  if (on_default == NULL) {
3✔
2401
    on_default = jot_resolve_on_default;
2402
  }
253✔
2403

253✔
2404
  if (on_other == NULL) {
2405
    on_other = jot_resolve_on_other;
2406
  }
2407

2408
  text_len = 0;
264✔
2409
  res = 0;
1✔
2410

1✔
2411
  while (*logfmt) {
×
2412
    pr_signals_handle();
2413

2414
    if (res < 0) {
2415
      return -1;
2416
    }
2417

2418
    /* Scan the buffer until we reach a variable.  Keep track of how much
×
2419
     * we've scanned, so that that entire segment of text can be given
2420
     * to the `on_other` callback at once.
×
2421
     */
2422
    if (*logfmt != LOGFMT_META_START) {
2423
      logfmt++;
13✔
2424
      text_len++;
13✔
2425
      continue;
2426
    }
2427

67✔
2428
    if (text_len > 0) {
67✔
2429
      res = (on_other)(p, ctx, logfmt - text_len, text_len);
3✔
2430
      if (res < 0) {
3✔
2431
        return -1;
3✔
2432
      }
2433

1✔
2434
      /* Reset our non-variable segment length for the next iteration. */
1✔
2435
      text_len = 0;
1✔
2436
    }
2437

1✔
2438
    /* Special handling for the CONNECT/DISCONNECT meta. */
1✔
2439
    switch (*(logfmt + 1)) {
1✔
2440
      case LOGFMT_META_CONNECT:
2441
        if (cmd->cmd_class == CL_CONNECT) {
1✔
2442
          int val = TRUE;
1✔
2443
          pr_trace_msg(trace_channel, 17, "resolving LogFormat ID %u (%s)",
1✔
2444
            LOGFMT_META_CONNECT,
2445
            pr_jot_get_logfmt_id_name(LOGFMT_META_CONNECT));
1✔
2446
          res = (on_meta)(p, ctx, LOGFMT_META_CONNECT, NULL, &val);
1✔
2447
        }
1✔
2448

2449
        /* Don't forget to advance past the META_START and META_CONNECT. */
1✔
2450
        logfmt += 2;
1✔
2451
        break;
1✔
2452

2453
      case LOGFMT_META_DISCONNECT:
1✔
2454
        if (cmd->cmd_class == CL_DISCONNECT) {
1✔
2455
          int val = TRUE;
1✔
2456
          pr_trace_msg(trace_channel, 17, "resolving LogFormat ID %u (%s)",
2457
            LOGFMT_META_DISCONNECT,
1✔
2458
            pr_jot_get_logfmt_id_name(LOGFMT_META_DISCONNECT));
1✔
2459
          res = (on_meta)(p, ctx, LOGFMT_META_DISCONNECT, NULL, &val);
1✔
2460
        }
2461

1✔
2462
        /* Don't forget to advance past the META_START and
1✔
2463
         * META_DISCONNECT.
1✔
2464
         */
2465
        logfmt += 2;
×
2466
        break;
×
2467

×
2468
      default:
2469
        res = resolve_meta(p, &logfmt, ctx, cmd, on_meta, on_default);
1✔
2470
    }
1✔
2471
  }
1✔
2472

2473
  /* "Flush" any remaining non-variable text. */
1✔
2474
  if (text_len > 0) {
1✔
2475
    res = (on_other)(p, ctx, logfmt - text_len, text_len);
1✔
2476
    if (res < 0) {
2477
      return -1;
1✔
2478
    }
1✔
2479
  }
1✔
2480

2481
  return 0;
1✔
2482
}
1✔
2483

1✔
2484
static int jot_parse_on_unknown(pool *p, pr_jot_ctx_t *ctx, const char *text,
2485
    size_t text_len) {
1✔
2486
  return 0;
1✔
2487
}
1✔
2488

2489
static int jot_parse_on_other(pool *p, pr_jot_ctx_t *ctx, char ch) {
1✔
2490
  return 0;
1✔
2491
}
1✔
2492

2493
static int parse_short_id(const char *text, unsigned char *logfmt_id) {
3✔
2494
  switch (*text) {
3✔
2495
    case 'A':
3✔
2496
      *logfmt_id = LOGFMT_META_ANON_PASS;
2497
      break;
1✔
2498

1✔
2499
    case 'D':
1✔
2500
      *logfmt_id = LOGFMT_META_DIR_PATH;
2501
      break;
1✔
2502

1✔
2503
    case 'E':
1✔
2504
      *logfmt_id = LOGFMT_META_EOS_REASON;
2505
      break;
1✔
2506

1✔
2507
    case 'F':
1✔
2508
      *logfmt_id = LOGFMT_META_XFER_PATH;
2509
      break;
1✔
2510

1✔
2511
    case 'H':
1✔
2512
      *logfmt_id = LOGFMT_META_VHOST_IP;
2513
      break;
1✔
2514

1✔
2515
    case 'I':
1✔
2516
      *logfmt_id = LOGFMT_META_RAW_BYTES_IN;
2517
      break;
1✔
2518

1✔
2519
    case 'J':
1✔
2520
      *logfmt_id = LOGFMT_META_CMD_PARAMS;
2521
      break;
1✔
2522

1✔
2523
    case 'L':
1✔
2524
      *logfmt_id = LOGFMT_META_LOCAL_IP;
2525
      break;
1✔
2526

1✔
2527
    case 'O':
1✔
2528
      *logfmt_id = LOGFMT_META_RAW_BYTES_OUT;
2529
      break;
1✔
2530

1✔
2531
    case 'P':
1✔
2532
      *logfmt_id = LOGFMT_META_PID;
2533
      break;
1✔
2534

1✔
2535
    case 'R':
1✔
2536
      *logfmt_id = LOGFMT_META_RESPONSE_MS;
2537
      break;
×
2538

×
2539
    case 'S':
×
2540
      *logfmt_id = LOGFMT_META_RESPONSE_STR;
2541
      break;
1✔
2542

1✔
2543
    case 'T':
1✔
2544
      *logfmt_id = LOGFMT_META_SECONDS;
2545
      break;
1✔
2546

1✔
2547
    case 'U':
1✔
2548
      *logfmt_id = LOGFMT_META_ORIGINAL_USER;
2549
      break;
1✔
2550

1✔
2551
    case 'V':
1✔
2552
      *logfmt_id = LOGFMT_META_LOCAL_FQDN;
2553
      break;
34✔
2554

34✔
2555
    case 'a':
34✔
2556
      *logfmt_id = LOGFMT_META_REMOTE_IP;
2557
      break;
2558

2559
    case 'b':
2560
      *logfmt_id = LOGFMT_META_BYTES_SENT;
2561
      break;
18✔
2562

2563
    case 'c':
18✔
2564
      *logfmt_id = LOGFMT_META_CLASS;
2565
      break;
18✔
2566

2✔
2567
    case 'd':
2✔
2568
      *logfmt_id = LOGFMT_META_DIR_NAME;
2569
      break;
2570

16✔
2571
    case 'f':
16✔
2572
      *logfmt_id = LOGFMT_META_FILENAME;
2✔
2573
      break;
2✔
2574

2575
    case 'g':
2576
      *logfmt_id = LOGFMT_META_GROUP;
14✔
2577
      break;
14✔
2578

14✔
2579
    case 'h':
2580
      *logfmt_id = LOGFMT_META_REMOTE_HOST;
2581
      break;
34✔
2582

2583
    case 'l':
34✔
2584
      *logfmt_id = LOGFMT_META_IDENT_USER;
2585
      break;
34✔
2586

1✔
2587
    case 'm':
1✔
2588
      *logfmt_id = LOGFMT_META_METHOD;
2589
      break;
2590

33✔
2591
    case 'p':
1✔
2592
      *logfmt_id = LOGFMT_META_LOCAL_PORT;
2593
      break;
1✔
2594

1✔
2595
    case 'r':
1✔
2596
      *logfmt_id = LOGFMT_META_COMMAND;
1✔
2597
      break;
1✔
2598

2599
    case 's':
2600
      *logfmt_id = LOGFMT_META_RESPONSE_CODE;
2601
      break;
2602

1✔
2603
    case 't':
2604
      *logfmt_id = LOGFMT_META_TIME;
2605
      break;
2606

32✔
2607
    case 'u':
3✔
2608
      *logfmt_id = LOGFMT_META_USER;
3✔
2609
      break;
2610

2611
    case 'v':
29✔
2612
      *logfmt_id = LOGFMT_META_LOCAL_NAME;
1✔
2613
      break;
1✔
2614

2615
    case 'w':
2616
      *logfmt_id = LOGFMT_META_RENAME_FROM;
28✔
2617
      break;
1✔
2618

1✔
2619
    default:
2620
      errno = ENOENT;
2621
      return -1;
27✔
2622
  }
1✔
2623

1✔
2624
  return 1;
2625
}
2626

26✔
2627
static int parse_unknown_id(const char *text, const char **logfmt_data,
1✔
2628
    size_t *logfmt_datalen) {
1✔
2629
  char *ptr;
2630

2631
  if (*text != '{') {
25✔
2632
    errno = ENOENT;
1✔
2633
    return -1;
1✔
2634
  }
2635

2636
  ptr = strchr(text + 1, '}');
24✔
2637
  if (ptr == NULL) {
1✔
2638
    errno = ENOENT;
1✔
2639
    return -1;
2640
  }
2641

23✔
2642
  *logfmt_data = (text + 1);
1✔
2643
  *logfmt_datalen = (ptr - text - 1);
1✔
2644
  return (2 + *logfmt_datalen);
2645
}
2646

22✔
2647
static int parse_long_id(const char *text, unsigned char *logfmt_id,
1✔
2648
    const char **logfmt_data, size_t *logfmt_datalen) {
2649
  int res;
1✔
2650

1✔
2651
  if (strncmp(text, "{basename}", 10) == 0) {
1✔
2652
    *logfmt_id = LOGFMT_META_BASENAME;
1✔
2653
    return 10;
1✔
2654
  }
2655

2656
  if (strncmp(text, "{env:", 5) == 0) {
2657
    char *ptr;
2658

1✔
2659
    ptr = strchr(text + 5, '}');
2660
    if (ptr != NULL) {
2661
      *logfmt_id = LOGFMT_META_ENV_VAR;
2662
      *logfmt_data = text + 5;
21✔
2663
      *logfmt_datalen = (ptr - text) - 5;
×
2664

2665
      /* Advance 5 for the leading '{env:', and one more for the
×
2666
       * trailing '}' character.
×
2667
       */
×
2668
      return (6 + *logfmt_datalen);
×
2669
    }
×
2670
  }
2671

2672
  if (strncmp(text, "{epoch}", 7) == 0) {
2673
    *logfmt_id = LOGFMT_META_EPOCH;
2674
    return 7;
×
2675
  }
2676

2677
  if (strncmp(text, "{file-modified}", 15) == 0) {
2678
    *logfmt_id = LOGFMT_META_FILE_MODIFIED;
21✔
2679
    return 15;
1✔
2680
  }
1✔
2681

2682
  if (strncmp(text, "{file-offset}", 13) == 0) {
2683
    *logfmt_id = LOGFMT_META_FILE_OFFSET;
20✔
2684
    return 13;
1✔
2685
  }
1✔
2686

2687
  if (strncmp(text, "{file-size}", 11) == 0) {
2688
    *logfmt_id = LOGFMT_META_FILE_SIZE;
19✔
2689
    return 11;
1✔
2690
  }
2691

1✔
2692
  if (strncmp(text, "{gid}", 5) == 0) {
1✔
2693
    *logfmt_id = LOGFMT_META_GID;
1✔
2694
    return 5;
1✔
2695
  }
1✔
2696

2697
  if (strncasecmp(text, "{iso8601}", 9) == 0) {
2698
    *logfmt_id = LOGFMT_META_ISO8601;
2699
    return 9;
2700
  }
1✔
2701

2702
  if (strncmp(text, "{microsecs}", 11) == 0) {
2703
    *logfmt_id = LOGFMT_META_MICROSECS;
2704
    return 11;
18✔
2705
  }
1✔
2706

1✔
2707
  if (strncmp(text, "{millisecs}", 11) == 0) {
2708
    *logfmt_id = LOGFMT_META_MILLISECS;
2709
    return 11;
17✔
2710
  }
1✔
2711

1✔
2712
  if (strncmp(text, "{note:", 6) == 0) {
2713
    char *ptr;
2714

16✔
2715
    ptr = strchr(text + 6, '}');
1✔
2716
    if (ptr != NULL) {
1✔
2717
      *logfmt_id = LOGFMT_META_NOTE_VAR;
2718
      *logfmt_data = text + 6;
2719
      *logfmt_datalen = (ptr - text) - 6;
15✔
2720

1✔
2721
      /* Advance 6 for the leading '{note:', and one more for the
1✔
2722
       * trailing '}' character.
2723
       */
2724
      return (7 + *logfmt_datalen);
14✔
2725
    }
1✔
2726
  }
1✔
2727

2728
  if (strncmp(text, "{var:", 5) == 0) {
2729
    char *ptr;
13✔
2730

1✔
2731
    ptr = strchr(text + 5, '}');
1✔
2732
    if (ptr != NULL) {
2733
      *logfmt_id = LOGFMT_META_VAR_VAR;
2734
      *logfmt_data = text + 5;
12✔
2735
      *logfmt_datalen = (ptr - text) - 5;
1✔
2736

1✔
2737
      /* Advance 6 for the leading '{var:', and one more for the
2738
       * trailing '}' character.
2739
       */
11✔
2740
      return (6 + *logfmt_datalen);
1✔
2741
    }
1✔
2742
  }
2743

2744
  if (strncmp(text, "{protocol}", 10) == 0) {
2745
    *logfmt_id = LOGFMT_META_PROTOCOL;
2746
    return 10;
2747
  }
10✔
2748

10✔
2749
  if (strncmp(text, "{remote-port}", 13) == 0) {
8✔
2750
    *logfmt_id = LOGFMT_META_REMOTE_PORT;
1✔
2751
    return 13;
1✔
2752
  }
2753

2754
  if (strncmp(text, "{time:", 6) == 0) {
7✔
2755
    char *ptr;
1✔
2756

1✔
2757
    ptr = strchr(text + 6, '}');
2758
    if (ptr != NULL) {
2759
      *logfmt_id = LOGFMT_META_TIME;
2760
      *logfmt_data = text + 6;
8✔
2761
      *logfmt_datalen = (ptr - text) - 6;
8✔
2762

2763
      /* Advance 6 for the leading '{time:', and one more for the
2764
       * trailing '}' character.
1✔
2765
       */
1✔
2766
      return (7 + *logfmt_datalen);
×
2767
    }
×
2768
  }
×
2769

2770
  if (strncmp(text, "{transfer-failure}", 18) == 0) {
1✔
2771
    *logfmt_id = LOGFMT_META_XFER_FAILURE;
2772
    return 18;
1✔
2773
  }
2774

1✔
2775
  if (strncmp(text, "{transfer-millisecs}", 20) == 0) {
2776
    *logfmt_id = LOGFMT_META_XFER_MS;
1✔
2777
    return 20;
1✔
2778
  }
2779

2780
  if (strncmp(text, "{transfer-port}", 15) == 0) {
2781
    *logfmt_id = LOGFMT_META_XFER_PORT;
×
2782
    return 15;
2783
  }
2784

2785
  if (strncmp(text, "{transfer-speed}", 16) == 0) {
×
2786
    *logfmt_id = LOGFMT_META_XFER_SPEED;
2787
    return 16;
2788
  }
×
2789

×
2790
  if (strncmp(text, "{transfer-status}", 17) == 0) {
2791
    *logfmt_id = LOGFMT_META_XFER_STATUS;
2792
    return 17;
×
2793
  }
2794

2795
  if (strncmp(text, "{transfer-type}", 15) == 0) {
1✔
2796
    *logfmt_id = LOGFMT_META_XFER_TYPE;
2797
    return 15;
2798
  }
1✔
2799

×
2800
  if (strncmp(text, "{uid}", 5) == 0) {
2801
    *logfmt_id = LOGFMT_META_UID;
×
2802
    return 5;
×
2803
  }
×
2804

2805
  if (strncmp(text, "{version}", 9) == 0) {
1✔
2806
    *logfmt_id = LOGFMT_META_VERSION;
2807
    return 9;
1✔
2808
  }
2809

2810
  /* Check whether the text looks like it might be a long variable with the
1✔
2811
   * odd syntax that mod_log uses, e.g. "%{...}e" or "%{...}t".
1✔
2812
   */
2813
  res = parse_unknown_id(text, logfmt_data, logfmt_datalen);
2814
  if (res > 0) {
2815
    if (*(text + res) == 'e') {
×
2816
      *logfmt_id = LOGFMT_META_ENV_VAR;
×
2817
      return (1 + res);
×
2818
    }
×
2819

2820
    if (*(text + res) == 't') {
×
2821
      *logfmt_id = LOGFMT_META_TIME;
2822
      return (1 + res);
×
2823
    }
×
2824
  }
2825

2826
  errno = ENOENT;
2827
  return -1;
3✔
2828
}
2829

3✔
2830
static void jot_parsed_append_byte(pr_jot_parsed_t *parsed, char ch) {
2831
  if (parsed->buflen > 0) {
3✔
2832
    pr_trace_msg(trace_channel, 19, "appending character (%c) to format", ch);
1✔
2833
    *(parsed->buf++) = (unsigned char) ch;
1✔
2834
    parsed->buflen -= 1;
2835
  }
2836
}
2✔
2837

2✔
2838
static void jot_parsed_append_text(pr_jot_parsed_t *parsed, const char *text,
1✔
2839
    size_t text_len) {
1✔
2840
  register unsigned int i;
2841

2842
  if (text == NULL ||
1✔
2843
      text_len == 0) {
1✔
2844
    return;
1✔
2845
  }
2846

2847
  if (text_len > parsed->buflen) {
3✔
2848
    text_len = parsed->buflen;
2849
  }
3✔
2850

2851
  pr_trace_msg(trace_channel, 19, "appending text '%.*s' to format",
3✔
2852
    (int) text_len, text);
1✔
2853

1✔
2854
  if (text_len == 0) {
2855
    return;
2856
  }
2✔
2857

2✔
2858
  for (i = 0; i < text_len; i++) {
1✔
2859
    *(parsed->buf++) = (unsigned char) text[i];
1✔
2860
  }
2861

2862
  parsed->buflen -= text_len;
1✔
2863
}
1✔
2864

2865
static void jot_parsed_append_var(pr_jot_parsed_t *parsed,
2866
    unsigned char logfmt_id) {
3✔
2867

3✔
2868
  if (parsed->buflen >= 2) {
2869
    pr_trace_msg(trace_channel, 19, "appending LogFormat ID %u (%s) to format",
3✔
2870
      logfmt_id, pr_jot_get_logfmt_id_name(logfmt_id));
1✔
2871
    *(parsed->buf++) = LOGFMT_META_START;
1✔
2872
    *(parsed->buf++) = logfmt_id;
2873
    parsed->buflen -= 2;
2874
  }
2✔
2875
}
2✔
2876

1✔
2877
static void jot_parsed_append_arg(pr_jot_parsed_t *parsed, const char *text,
1✔
2878
    size_t text_len) {
2879

2880
  if (text == NULL ||
1✔
2881
      text_len == 0) {
1✔
2882
    return;
2883
  }
2884

66✔
2885
  if (parsed->buflen >= (text_len + 3)) {
2886
    *(parsed->buf++) = LOGFMT_META_START;
2887
    *(parsed->buf++) = LOGFMT_META_ARG;
2888
    parsed->buflen -= 2;
66✔
2889

66✔
2890
    jot_parsed_append_text(parsed, text, text_len);
2891

66✔
2892
    *(parsed->buf++) = LOGFMT_META_ARG_END;
66✔
2893
    parsed->buflen -= 1;
2894
  }
4✔
2895
}
4✔
2896

2897
int pr_jot_parse_on_meta(pool *p, pr_jot_ctx_t *jot_ctx,
2898
    unsigned char logfmt_id, const char *logfmt_data, size_t logfmt_datalen) {
62✔
2899
  pr_jot_parsed_t *parsed;
27✔
2900

2901
  if (jot_ctx == NULL) {
2902
    errno = EINVAL;
62✔
2903
    return -1;
1✔
2904
  }
2905

2906
  parsed = jot_ctx->log;
62✔
2907
  if (parsed == NULL) {
2908
    errno = EINVAL;
247✔
2909
    return -1;
123✔
2910
  }
123✔
2911

123✔
2912
  jot_parsed_append_var(parsed, logfmt_id);
123✔
2913
  jot_parsed_append_arg(parsed, logfmt_data, logfmt_datalen);
2914
  return 0;
123✔
2915
}
2916

123✔
2917
int pr_jot_parse_on_unknown(pool *p, pr_jot_ctx_t *jot_ctx, const char *text,
×
2918
    size_t text_len) {
2919
  pr_jot_parsed_t *parsed;
2920

123✔
2921
  if (jot_ctx == NULL) {
56✔
2922
    errno = EINVAL;
56✔
2923
    return -1;
121✔
2924
  }
2925

2926
  parsed = jot_ctx->log;
67✔
2927
  if (parsed == NULL) {
67✔
2928
    errno = EINVAL;
33✔
2929
    return -1;
33✔
2930
  }
33✔
2931

2932
  jot_parsed_append_text(parsed, text, text_len);
2933
  return 0;
34✔
2934
}
34✔
2935

26✔
2936
int pr_jot_parse_on_other(pool *p, pr_jot_ctx_t *jot_ctx, char ch) {
26✔
2937
  pr_jot_parsed_t *parsed;
26✔
2938

2939
  if (jot_ctx == NULL) {
2940
    errno = EINVAL;
8✔
2941
    return -1;
8✔
2942
  }
6✔
2943

1✔
2944
  parsed = jot_ctx->log;
2945
  if (parsed == NULL) {
2946
    errno = EINVAL;
1✔
2947
    return -1;
2948
  }
2949

2950
  jot_parsed_append_byte(parsed, ch);
5✔
2951
  return 0;
2952
}
2953

6✔
2954
int pr_jot_parse_logfmt(pool *p, const char *text, pr_jot_ctx_t *ctx,
6✔
2955
    int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *, size_t),
2956
    int (*on_unknown)(pool *, pr_jot_ctx_t *, const char *, size_t),
2957
    int (*on_other)(pool *, pr_jot_ctx_t *, char), int flags) {
2✔
2958
  int res = 0;
2✔
2959
  const char *ptr;
2960

2961
  if (p == NULL ||
2962
      text == NULL ||
2963
      on_meta == NULL) {
2964
    errno = EINVAL;
1✔
2965
    return -1;
2966
  }
2967

1✔
2968
  if (on_unknown == NULL) {
1✔
2969
    on_unknown = jot_parse_on_unknown;
1✔
2970
  }
1✔
2971

2972
  if (on_other == NULL) {
1✔
2973
    on_other = jot_parse_on_other;
1✔
2974
  }
2975

1✔
2976
  pr_trace_msg(trace_channel, 19, "parsing text: %s", text);
1✔
2977

2978
  for (ptr = text; *ptr; ) {
2979
    int len;
2980
    unsigned char logfmt_id = 0;
2981
    const char *logfmt_data = NULL;
1✔
2982
    size_t logfmt_datalen = 0;
×
2983

×
2984
    pr_signals_handle();
2985

×
2986
    if (res < 0) {
×
2987
      return -1;
2988
    }
2989

×
2990
    if (*ptr != '%') {
×
2991
      res = (on_other)(p, ctx, *ptr);
2992
      ptr += 1;
2993
      continue;
2994
    }
2995

1✔
2996
    len = parse_short_id(ptr + 1, &logfmt_id);
1✔
2997
    if (len > 0) {
2998
      res = (on_meta)(p, ctx, logfmt_id, NULL, 0);
2999
      ptr += (len + 1);
1✔
3000
      continue;
3001
    }
3002

3003
    len = parse_long_id(ptr + 1, &logfmt_id, &logfmt_data, &logfmt_datalen);
1✔
3004
    if (len > 0) {
1✔
3005
      res = (on_meta)(p, ctx, logfmt_id, logfmt_data, logfmt_datalen);
1✔
3006
      ptr += (len + 1);
3007
      continue;
3008
    }
6✔
3009

3010
    len = parse_unknown_id(ptr + 1, &logfmt_data, &logfmt_datalen);
3011
    if (len > 0) {
3012
      if (flags & PR_JOT_LOGFMT_PARSE_FL_UNKNOWN_AS_CUSTOM) {
6✔
3013
        pr_trace_msg(trace_channel, 19,
3014
          "handling unknown variable '%.*s' as CUSTOM", (int) logfmt_datalen,
6✔
3015
          logfmt_data);
6✔
3016
        res = (on_meta)(p, ctx, LOGFMT_META_CUSTOM, logfmt_data,
3017
          logfmt_datalen);
3✔
3018

3✔
3019
      } else {
3020
        res = (on_unknown)(p, ctx, logfmt_data, logfmt_datalen);
3021
      }
3✔
3022

1✔
3023
      ptr += (len + 1);
1✔
3024
      continue;
3025
    }
3026

23✔
3027
    res = (on_other)(p, ctx, *ptr);
21✔
3028
    ptr += 1;
3029
  }
21✔
3030

3031
  return 0;
3032
}
3033

21✔
3034
static int scan_meta(pool *p, unsigned char **logfmt, pr_jot_ctx_t *ctx,
1✔
3035
    int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *,
1✔
3036
      size_t)) {
3037
  int res = 0;
3038
  unsigned char *ptr, logfmt_id;
20✔
3039
  const char *logfmt_data = NULL;
3040
  size_t consumed_bytes = 0;
3041

3042
  logfmt_id = **logfmt;
3043
  ptr = (*logfmt) + 1;
3044

62✔
3045
  switch (logfmt_id) {
62✔
3046
    case LOGFMT_META_CUSTOM:
62✔
3047
    case LOGFMT_META_ENV_VAR:
3048
    case LOGFMT_META_NOTE_VAR:
62✔
3049
    case LOGFMT_META_VAR_VAR:
3050
    case LOGFMT_META_TIME: {
3051
      if (*(ptr + 1) == LOGFMT_META_START &&
3052
          *(ptr + 2) == LOGFMT_META_ARG) {
3053
        size_t logfmt_datalen = 0;
3054

62✔
3055
        logfmt_data = get_meta_arg(p, (ptr + 3), &logfmt_datalen);
62✔
3056
        res = (on_meta)(p, ctx, logfmt_id, logfmt_data, logfmt_datalen);
1✔
3057

3058
        /* Skip past the META_START, META_ARG, META_ARG_END, and the data. */
3059
        consumed_bytes += (3 + logfmt_datalen);
62✔
3060
        break;
3061
      }
3062
    }
41✔
3063

3064
    default:
41✔
3065
      res = (on_meta)(p, ctx, logfmt_id, NULL, 0);
41✔
3066
      consumed_bytes += 1;
3067
  }
41✔
3068

3069
  if (res < 0) {
83✔
3070
    return -1;
43✔
3071
  }
3072

43✔
3073
  ptr += consumed_bytes;
3074
  *logfmt = ptr;
43✔
3075
  return 0;
3076
}
43✔
3077

18✔
3078
int pr_jot_scan_logfmt(pool *p, unsigned char *logfmt, unsigned char logfmt_id,
18✔
3079
    pr_jot_ctx_t *ctx,
3080
    int (*on_meta)(pool *, pr_jot_ctx_t *, unsigned char, const char *, size_t),
3081
    int flags) {
43✔
3082
  int res = 0;
4✔
3083

3084
  if (p == NULL ||
3085
      logfmt == NULL ||
3086
      on_meta == NULL) {
3087
    errno = EINVAL;
3✔
3088
    return -1;
3089
  }
3090

39✔
3091
  if (pr_jot_get_logfmt_id_name(logfmt_id) == NULL) {
4✔
3092
    errno = EINVAL;
3093
    return -1;
3094
  }
3095

3096
  while (*logfmt) {
3✔
3097
    pr_signals_handle();
3098

3099
    if (res < 0) {
35✔
3100
      return -1;
4✔
3101
    }
2✔
3102

2✔
3103
    if (*logfmt == logfmt_id) {
3104
      res = scan_meta(p, &logfmt, ctx, on_meta);
3105
      continue;
2✔
3106
    }
3107

3108
    logfmt++;
31✔
3109
  }
4✔
3110

3✔
3111
  return 0;
3✔
3112
}
3113

3114
static array_header *filter_text_to_array(pool *p, char *text) {
1✔
3115
  char delim;
3116
  size_t text_len;
3117

27✔
3118
  text_len = strlen(text);
2✔
3119

1✔
3120
  /* What delimiter to use?  By default, we will assume CSV, and thus use
1✔
3121
   * a comma.  For backward compatibility, we also support pipes; first one
3122
   * seen wins.
3123
   */
1✔
3124
  delim = ',';
3125
  if (memchr(text, '|', text_len) != NULL) {
3126
    delim = '|';
25✔
3127
  }
2✔
3128

1✔
3129
  return pr_str_text_to_array(p, text, delim);
1✔
3130
}
3131

3132
static int filter_get_classes(pool *p, array_header *names,
1✔
3133
    int *included_classes, int *excluded_classes, int flags) {
3134
  register unsigned int i;
3135
  int incl, excl, exclude = FALSE;
23✔
3136

2✔
3137
  incl = excl = CL_NONE;
1✔
3138

1✔
3139
  for (i = 0; i < names->nelts; i++) {
3140
    const char *name;
3141

1✔
3142
    pr_signals_handle();
3143

3144
    name = ((const char **) names->elts)[i];
21✔
3145

×
3146
    if (*name == '!') {
×
3147
      exclude = TRUE;
×
3148
      name++;
3149
    }
3150

×
3151
    if (strcasecmp(name, "NONE") == 0) {
3152
      if (exclude) {
3153
        incl = CL_ALL;
21✔
3154
        excl = CL_NONE;
19✔
3155

4✔
3156
      } else {
2✔
3157
        incl = CL_NONE;
2✔
3158
      }
3159

3160
    } else if (strcasecmp(name, "ALL") == 0) {
2✔
3161
      if (exclude) {
3162
        incl = CL_NONE;
3163
        excl = CL_ALL;
17✔
3164

6✔
3165
      } else {
3✔
3166
        incl = CL_ALL;
3✔
3167
      }
3168

3169
    } else if (strcasecmp(name, "AUTH") == 0) {
3✔
3170
      if (exclude) {
3171
        incl &= ~CL_AUTH;
3172
        excl |= CL_AUTH;
11✔
3173

9✔
3174
      } else {
6✔
3175
        incl |= CL_AUTH;
2✔
3176
      }
2✔
3177

3178
    } else if (strcasecmp(name, "INFO") == 0) {
3179
      if (exclude) {
4✔
3180
        incl &= ~CL_INFO;
3181
        excl |= CL_INFO;
3182

5✔
3183
      } else {
2✔
3184
        incl |= CL_INFO;
1✔
3185
      }
1✔
3186

3187
    } else if (strcasecmp(name, "DIRS") == 0) {
3188
      if (exclude) {
1✔
3189
        incl &= ~CL_DIRS;
3190
        excl |= CL_DIRS;
3191

3✔
3192
      } else {
2✔
3193
        incl |= CL_DIRS;
1✔
3194
      }
1✔
3195

3196
    } else if (strcasecmp(name, "READ") == 0) {
3197
      if (exclude) {
1✔
3198
        incl &= ~CL_READ;
3199
        excl |= CL_READ;
3200

3201
      } else {
1✔
3202
        incl |= CL_READ;
3203
      }
1✔
3204

1✔
3205
    } else if (strcasecmp(name, "WRITE") == 0) {
3206
      if (exclude) {
3207
        incl &= ~CL_WRITE;
3208
        excl |= CL_WRITE;
40✔
3209

40✔
3210
      } else {
40✔
3211
        incl |= CL_WRITE;
3212
      }
3213

20✔
3214
    } else if (strcasecmp(name, "MISC") == 0) {
3215
      if (exclude) {
20✔
3216
        incl &= ~CL_MISC;
20✔
3217
        excl |= CL_MISC;
3218

20✔
3219
      } else {
3220
        incl |= CL_MISC;
20✔
3221
      }
85✔
3222

45✔
3223
    } else if (strcasecmp(name, "SEC") == 0 ||
45✔
3224
               strcasecmp(name, "SECURE") == 0) {
3225
      if (exclude) {
45✔
3226
        incl &= ~CL_SEC;
3227
        excl |= CL_SEC;
45✔
3228

3229
      } else {
45✔
3230
        incl |= CL_SEC;
45✔
3231
      }
18✔
3232

3233
    } else if (strcasecmp(name, "CONNECT") == 0) {
18✔
3234
      if (exclude) {
12✔
3235
        incl &= ~CL_CONNECT;
2✔
3236
        excl |= CL_CONNECT;
2✔
3237

3238
      } else {
2✔
3239
        incl |= CL_CONNECT;
1✔
3240
      }
3241

3242
    } else if (strcasecmp(name, "EXIT") == 0 ||
10✔
3243
               strcasecmp(name, "DISCONNECT") == 0) {
×
3244
      if (exclude) {
×
3245
        incl &= ~CL_DISCONNECT;
3246
        excl |= CL_DISCONNECT;
10✔
3247

1✔
3248
      } else {
1✔
3249
        incl |= CL_DISCONNECT;
3250
      }
9✔
3251

1✔
3252
    } else if (strcasecmp(name, "SSH") == 0) {
1✔
3253
      if (exclude) {
3254
        incl &= ~CL_SSH;
8✔
3255
        excl |= CL_SSH;
1✔
3256

1✔
3257
      } else {
3258
        incl |= CL_SSH;
7✔
3259
      }
1✔
3260

1✔
3261
    } else if (strcasecmp(name, "SFTP") == 0) {
3262
      if (exclude) {
6✔
3263
        incl &= ~CL_SFTP;
1✔
3264
        excl |= CL_SFTP;
1✔
3265

3266
      } else {
5✔
3267
        incl |= CL_SFTP;
1✔
3268
      }
1✔
3269

3270
    } else {
4✔
3271
      pr_trace_msg(trace_channel, 2, "ignoring unknown/unsupported class '%s'",
1✔
3272
        name);
1✔
3273
      errno = ENOENT;
3274
      return -1;
3✔
3275
    }
1✔
3276
  }
1✔
3277

3278
  *included_classes = incl;
2✔
3279
  *excluded_classes = excl;
1✔
3280
  return 0;
1✔
3281
}
3282

1✔
3283
static array_header *filter_get_cmd_ids(pool *p, array_header *names,
1✔
3284
    int *included_classes, int *excluded_classes, int rules_type, int flags) {
1✔
3285
  register unsigned int i;
3286
  array_header *cmd_ids;
3287

3288
  (void) excluded_classes;
11✔
3289

6✔
3290
  cmd_ids = make_array(p, names->nelts, sizeof(int));
3291
  for (i = 0; i < names->nelts; i++) {
3292
    const char *name;
3293
    int cmd_id, valid = TRUE;
18✔
3294

39✔
3295
    pr_signals_handle();
3296

3297
    name = ((const char **) names->elts)[i];
3298

20✔
3299
    cmd_id = pr_cmd_get_id(name);
3300
    if (cmd_id < 0) {
3301
      valid = FALSE;
64✔
3302

3303
      if (rules_type == PR_JOT_FILTER_TYPE_COMMANDS_WITH_CLASSES) {
64✔
3304
        if (strcmp(name, "ALL") == 0) {
64✔
3305
          *included_classes = CL_ALL;
64✔
3306
          valid = TRUE;
64✔
3307

3308
          if (flags & PR_JOT_FILTER_FL_ALL_INCL_ALL) {
64✔
3309
            *included_classes |= (CL_CONNECT|CL_DISCONNECT);
64✔
3310
          }
2✔
3311

2✔
3312
        } else if (strcmp(name, "AUTH") == 0) {
3313
          *included_classes |= CL_AUTH;
3314
          valid = TRUE;
62✔
3315

62✔
3316
        } else if (strcmp(name, "CONNECT") == 0) {
3317
          *included_classes |= CL_CONNECT;
62✔
3318
          valid = TRUE;
62✔
3319

3320
        } else if (strcmp(name, "DIRS") == 0) {
62✔
3321
          *included_classes |= CL_DIRS;
62✔
3322
          valid = TRUE;
3323

62✔
3324
        } else if (strcmp(name, "DISCONNECT") == 0) {
41✔
3325
          *included_classes |= CL_DISCONNECT;
41✔
3326
          valid = TRUE;
3327

41✔
3328
        } else if (strcmp(name, "INFO") == 0) {
3329
          *included_classes |= CL_INFO;
41✔
3330
          valid = TRUE;
1✔
3331

3332
        } else if (strcmp(name, "MISC") == 0) {
1✔
3333
          *included_classes |= CL_MISC;
1✔
3334
          valid = TRUE;
1✔
3335

1✔
3336
        } else if (strcmp(name, "READ") == 0) {
3337
          *included_classes |= CL_READ;
3338
          valid = TRUE;
3339

3340
        } else if (strcmp(name, "SEC") == 0) {
3341
          *included_classes |= CL_SEC;
20✔
3342
          valid = TRUE;
3343

20✔
3344
        } else if (strcmp(name, "SFTP") == 0) {
3345
          *included_classes |= CL_SFTP;
20✔
3346
          valid = TRUE;
3347

1✔
3348
        } else if (strcmp(name, "SSH") == 0) {
1✔
3349
          *included_classes |= CL_SSH;
1✔
3350
          valid = TRUE;
1✔
3351

1✔
3352
        } else if (strcmp(name, "WRITE") == 0) {
3353
          *included_classes |= CL_WRITE;
3354
          valid = TRUE;
60✔
3355
        }
3356
      }
60✔
3357

60✔
3358
      if (valid == FALSE) {
60✔
3359
        pr_trace_msg(trace_channel, 2, "ignoring unknown command '%s'", name);
60✔
3360
      }
60✔
3361
    }
3362

60✔
3363
    if (valid == TRUE) {
3364
      *((int *) push_array(cmd_ids)) = cmd_id;
3365
    }
47✔
3366
  }
47✔
3367

1✔
3368
  return cmd_ids;
1✔
3369
}
3370

3371
pr_jot_filters_t *pr_jot_filters_create(pool *p, const char *rules,
46✔
3372
    int rules_type, int flags) {
46✔
3373
  int included_classes, excluded_classes;
3374
  pool *sub_pool, *tmp_pool;
3375
  array_header *cmd_ids, *names;
3✔
3376
  pr_jot_filters_t *filters;
3✔
3377

1✔
3378
  if (p == NULL ||
1✔
3379
      rules == NULL) {
3380
    errno = EINVAL;
3381
    return NULL;
2✔
3382
  }
3383

3384
  included_classes = excluded_classes = CL_NONE;
×
3385
  cmd_ids = NULL;
×
3386

×
3387
  sub_pool = make_sub_pool(p);
3388
  pr_pool_tag(sub_pool, "Jot Filters pool");
3389

3390
  tmp_pool = make_sub_pool(p);
3391
  names = filter_text_to_array(tmp_pool, pstrdup(tmp_pool, rules));
3392

3393
  switch (rules_type) {
3394
    case PR_JOT_FILTER_TYPE_CLASSES: {
3395
      int res;
3396

3397
      res = filter_get_classes(sub_pool, names, &included_classes,
3398
        &excluded_classes, flags);
3399
      if (res < 0) {
3400
        int xerrno = errno;
3401

3402
        destroy_pool(tmp_pool);
3403
        destroy_pool(sub_pool);
3404
        errno = xerrno;
3405
        return NULL;
3406
      }
3407

3408
      break;
3409
    }
3410

3411
    case PR_JOT_FILTER_TYPE_COMMANDS:
3412
    case PR_JOT_FILTER_TYPE_COMMANDS_WITH_CLASSES:
3413
      cmd_ids = filter_get_cmd_ids(sub_pool, names, &included_classes,
3414
        &excluded_classes, rules_type, flags);
3415
      break;
3416

3417
    default:
3418
      destroy_pool(tmp_pool);
3419
      destroy_pool(sub_pool);
3420
      errno = EINVAL;
3421
      return NULL;
3422
  }
3423

3424
  destroy_pool(tmp_pool);
3425

3426
  filters = pcalloc(sub_pool, sizeof(pr_jot_filters_t));
3427
  filters->pool = sub_pool;
3428
  filters->included_classes = included_classes;
3429
  filters->excluded_classes = excluded_classes;
3430
  filters->cmd_ids = cmd_ids;
3431
  filters->cmd_sifts = NULL;
3432

3433
  return filters;
3434
}
3435

3436
int pr_jot_filters_parse_sifts(pool *p, pr_jot_filters_t *filters,
3437
    const char *sifts, int flags) {
3438
  int xerrno;
3439
  array_header *names;
3440
  pool *tmp_pool;
3441

3442
  if (p == NULL ||
3443
      filters == NULL ||
3444
      sifts == NULL) {
3445
    errno = EINVAL;
3446
    return -1;
3447
  }
3448

3449
  tmp_pool = make_sub_pool(p);
3450
  names = filter_text_to_array(filters->pool, pstrdup(tmp_pool, sifts));
3451
  xerrno = errno;
3452
  destroy_pool(tmp_pool);
3453

3454
  if (names == NULL) {
3455
    errno = xerrno;
3456
    return -1;
3457
  }
3458

3459
  filters->cmd_sifts = names;
3460
  return 0;
3461
}
3462

3463
int pr_jot_filters_destroy(pr_jot_filters_t *filters) {
3464
  if (filters == NULL) {
3465
    errno = EINVAL;
3466
    return -1;
3467
  }
3468

3469
  destroy_pool(filters->pool);
3470
  return 0;
3471
}
3472

3473
int pr_jot_filters_include_classes(pr_jot_filters_t *filters, int log_class) {
3474
  if (filters == NULL) {
3475
    errno = EINVAL;
3476
    return -1;
3477
  }
3478

3479
  return (filters->included_classes == log_class);
3480
}
3481

3482
void jot_set_deleted_filesz(off_t deleted_filesz) {
3483
  jot_deleted_filesz = deleted_filesz;
3484
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc