Coverage Report

Created: 2026-01-09 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proftpd/src/session.c
Line
Count
Source
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2009-2022 The ProFTPD Project team
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18
 *
19
 * As a special exemption, The ProFTPD Project team and other respective
20
 * copyright holders give permission to link this program with OpenSSL, and
21
 * distribute the resulting executable, without including the source code for
22
 * OpenSSL in the source distribution.
23
 */
24
25
#include "conf.h"
26
27
/* From src/main.c */
28
extern unsigned char is_master;
29
30
static int sess_connected = FALSE;
31
static const char *sess_ttyname = NULL;
32
33
0
static void sess_cleanup(int flags) {
34
0
  int log_sess_closed = FALSE;
35
36
  /* Clear the scoreboard entry. */
37
0
  if (ServerType == SERVER_STANDALONE) {
38
39
    /* For standalone daemons, we only clear the scoreboard slot if we are
40
     * an exiting child process.
41
     */
42
43
0
    if (!is_master) {
44
0
      if (pr_scoreboard_entry_del(TRUE) < 0 &&
45
0
          errno != EINVAL &&
46
0
          errno != ENOENT) {
47
0
        pr_log_debug(DEBUG1, "error deleting scoreboard entry: %s",
48
0
          strerror(errno));
49
0
      }
50
0
    }
51
52
0
  } else if (ServerType == SERVER_INETD) {
53
    /* For inetd-spawned daemons, we always clear the scoreboard slot. */
54
0
    if (pr_scoreboard_entry_del(TRUE) < 0 &&
55
0
        errno != EINVAL &&
56
0
        errno != ENOENT) {
57
0
      pr_log_debug(DEBUG1, "error deleting scoreboard entry: %s",
58
0
        strerror(errno));
59
0
    }
60
0
  }
61
62
  /* If session.user is set, we have a valid login. */
63
0
  if (session.user != NULL &&
64
0
      session.wtmp_log) {
65
0
    const char *tty_name;
66
67
0
    tty_name = pr_session_get_ttyname(session.pool);
68
0
    log_wtmp(tty_name, "", pr_netaddr_get_sess_remote_name(),
69
0
      pr_netaddr_get_sess_remote_addr());
70
0
  }
71
72
  /* These are necessary in order that cleanups associated with these pools
73
   * (and their subpools) are properly run.
74
   */
75
0
  if (session.d != NULL) {
76
0
    pr_inet_close(session.pool, session.d);
77
0
    session.d = NULL;
78
0
  }
79
80
0
  if (session.c != NULL) {
81
0
    pr_inet_close(session.pool, session.c);
82
0
    session.c = NULL;
83
0
  }
84
85
  /* Run all the exit handlers */
86
0
  pr_event_generate("core.exit", NULL);
87
88
0
  if (is_master == FALSE) {
89
0
    log_sess_closed = TRUE;
90
0
  }
91
92
0
  if (ServerType == SERVER_INETD &&
93
0
      !(flags & PR_SESS_END_FL_SYNTAX_CHECK)) {
94
0
    log_sess_closed = TRUE;
95
0
  }
96
97
0
  if (sess_connected == FALSE) {
98
0
    log_sess_closed = FALSE;
99
0
  }
100
101
0
  if (log_sess_closed == TRUE) {
102
0
    pr_log_pri(PR_LOG_INFO, "%s session closed.",
103
0
      pr_session_get_protocol(PR_SESS_PROTO_FL_LOGOUT));
104
0
  }
105
106
0
  log_closesyslog();
107
0
}
108
109
0
void session_set_connected(void) {
110
0
  sess_connected = TRUE;
111
0
}
112
113
void pr_session_disconnect(module *m, int reason_code,
114
0
    const char *details) {
115
0
  int flags = 0;
116
117
0
  session.disconnect_reason = reason_code;
118
0
  session.disconnect_module = m;
119
120
0
  if (details != NULL &&
121
0
      session.notes != NULL) {
122
    /* Stash any extra details in the session.notes table */
123
0
    if (pr_table_add_dup(session.notes, "core.disconnect-details",
124
0
        (char *) details, 0) < 0) {
125
0
      int xerrno = errno;
126
127
0
      if (xerrno != EEXIST) {
128
0
        pr_log_debug(DEBUG5, "error stashing 'core.disconnect-details' in "
129
0
          "session.notes: %s", strerror(xerrno));
130
0
      }
131
0
    }
132
0
  }
133
134
0
  if (reason_code == PR_SESS_DISCONNECT_SEGFAULT) {
135
0
    flags |= PR_SESS_END_FL_ERROR;
136
0
  }
137
138
0
  pr_session_end(flags);
139
0
}
140
141
0
void pr_session_end(int flags) {
142
0
  int exitcode = 0;
143
144
0
  sess_cleanup(flags);
145
146
0
  if (flags & PR_SESS_END_FL_NOEXIT) {
147
0
    return;
148
0
  }
149
150
0
  if (flags & PR_SESS_END_FL_ERROR) {
151
0
    exitcode = 1;
152
0
  }
153
154
#ifdef PR_USE_DEVEL
155
  destroy_pool(session.pool);
156
157
  if (is_master) {
158
    main_server = NULL;
159
    free_pools();
160
    pr_proctitle_free();
161
  }
162
#endif /* PR_USE_DEVEL */
163
164
#ifdef PR_DEVEL_PROFILE
165
  /* Populating the gmon.out gprof file requires that the process exit
166
   * via exit(3) or by returning from main().  Using _exit(2) doesn't allow
167
   * the process the time to write its profile data out.
168
   */
169
  exit(exitcode);
170
#else
171
0
  _exit(exitcode);
172
0
#endif /* PR_DEVEL_PROFILE */
173
0
}
174
175
0
const char *pr_session_get_disconnect_reason(const char **details) {
176
0
  const char *reason_str = NULL;
177
178
0
  switch (session.disconnect_reason) {
179
0
    case PR_SESS_DISCONNECT_UNSPECIFIED:
180
0
      reason_str = "Unknown/unspecified";
181
0
      break;
182
183
0
    case PR_SESS_DISCONNECT_CLIENT_QUIT:
184
0
      reason_str = "Quit";
185
0
      break;
186
187
0
    case PR_SESS_DISCONNECT_CLIENT_EOF:
188
0
      reason_str = "Read EOF from client";
189
0
      break;
190
191
0
    case PR_SESS_DISCONNECT_SESSION_INIT_FAILED:
192
0
      reason_str = "Session initialized failed";
193
0
      break;
194
195
0
    case PR_SESS_DISCONNECT_SIGNAL:
196
0
      reason_str = "Terminated by signal";
197
0
      break;
198
199
0
    case PR_SESS_DISCONNECT_NOMEM:
200
0
      reason_str = "Low memory";
201
0
      break;
202
203
0
    case PR_SESS_DISCONNECT_SERVER_SHUTDOWN:
204
0
      reason_str = "Server shutting down";
205
0
      break;
206
207
0
    case PR_SESS_DISCONNECT_TIMEOUT:
208
0
      reason_str = "Timeout exceeded";
209
0
      break;
210
211
0
    case PR_SESS_DISCONNECT_BANNED:
212
0
      reason_str = "Banned";
213
0
      break;
214
215
0
    case PR_SESS_DISCONNECT_CONFIG_ACL:
216
0
      reason_str = "Configured policy";
217
0
      break;
218
219
0
    case PR_SESS_DISCONNECT_MODULE_ACL:
220
0
      reason_str = "Module-specific policy";
221
0
      break;
222
223
0
    case PR_SESS_DISCONNECT_BAD_CONFIG:
224
0
      reason_str = "Server misconfiguration";
225
0
      break;
226
227
0
    case PR_SESS_DISCONNECT_BY_APPLICATION:
228
0
      reason_str = "Application error";
229
0
      break;
230
0
  }
231
232
0
  if (details != NULL) {
233
0
    *details = pr_table_get(session.notes, "core.disconnect-details", NULL);
234
0
  }
235
236
0
  return reason_str;
237
0
}
238
239
0
const char *pr_session_get_protocol(int flags) {
240
0
  const char *sess_proto;
241
242
0
  sess_proto = pr_table_get(session.notes, "protocol", NULL);
243
0
  if (sess_proto == NULL) {
244
0
    sess_proto = "ftp";
245
0
  }
246
247
0
  if (!(flags & PR_SESS_PROTO_FL_LOGOUT)) {
248
    /* Return the protocol as is. */
249
0
    return sess_proto;
250
0
  }
251
252
  /* Otherwise, we need to return either "FTP" or "SSH2", for consistency. */
253
0
  if (strcmp(sess_proto, "ftp") == 0 ||
254
0
      strcmp(sess_proto, "ftps") == 0) {
255
0
    return "FTP";
256
0
  }
257
258
0
  if (strcmp(sess_proto, "ssh2") == 0 ||
259
0
      strcmp(sess_proto, "sftp") == 0 ||
260
0
      strcmp(sess_proto, "scp") == 0 ||
261
0
      strcmp(sess_proto, "publickey") == 0) {
262
0
    return "SSH2";
263
0
  }
264
265
  /* Should never reach here, but just in case... */
266
0
  return "unknown";
267
0
}
268
269
0
void pr_session_send_banner(server_rec *s, int flags) {
270
0
  config_rec *c = NULL;
271
0
  char *display = NULL;
272
0
  const char *serveraddress = NULL;
273
0
  config_rec *masq = NULL;
274
275
0
  display = get_param_ptr(s->conf, "DisplayConnect", FALSE);
276
0
  if (display != NULL) {
277
0
    if (pr_display_file(display, NULL, R_220, flags) < 0) {
278
0
      pr_log_debug(DEBUG6, "unable to display DisplayConnect file '%s': %s",
279
0
        display, strerror(errno));
280
0
    }
281
0
  }
282
283
0
  serveraddress = pr_netaddr_get_ipstr(session.c->local_addr);
284
285
0
  masq = find_config(s->conf, CONF_PARAM, "MasqueradeAddress", FALSE);
286
0
  if (masq != NULL) {
287
0
    const pr_netaddr_t *masq_addr = NULL;
288
289
0
    if (masq->argv[0] != NULL) {
290
0
      masq_addr = masq->argv[0];
291
292
0
    } else {
293
0
      const char *name;
294
295
      /* Here we do a delayed lookup, to see if the configured name
296
       * can be resolved yet (e.g. the network is now up); see Bug#4104.
297
       */
298
299
0
      name = masq->argv[1];
300
301
0
      pr_log_debug(DEBUG10,
302
0
        "performing delayed resolution of MasqueradeAddress '%s'", name);
303
0
      masq_addr = pr_netaddr_get_addr(session.pool, name, NULL);
304
0
      if (masq_addr != NULL) {
305
        /* Stash the resolved pr_netaddr_t in the config_rec, so that other
306
         * code paths will find it (within this session process).
307
         */
308
0
        masq->argv[0] = (void *) masq_addr;
309
310
0
      } else {
311
0
        pr_log_debug(DEBUG5, "unable to resolve '%s'", name);
312
0
      }
313
0
    }
314
315
0
    if (masq_addr != NULL) {
316
0
      serveraddress = pr_netaddr_get_ipstr(masq_addr);
317
0
    }
318
0
  }
319
320
0
  c = find_config(s->conf, CONF_PARAM, "ServerIdent", FALSE);
321
0
  if (c == NULL ||
322
0
      *((unsigned char *) c->argv[0]) == TRUE) {
323
0
    unsigned char *defer_welcome;
324
325
0
    defer_welcome = get_param_ptr(s->conf, "DeferWelcome", FALSE);
326
327
0
    if (c &&
328
0
        c->argc > 1) {
329
0
      const char *server_ident;
330
331
0
      server_ident = c->argv[1];
332
333
0
      if (strstr(server_ident, "%L") != NULL) {
334
0
        server_ident = sreplace(session.pool, server_ident, "%L",
335
0
          serveraddress, NULL);
336
0
      }
337
338
0
      if (strstr(server_ident, "%V") != NULL) {
339
0
        server_ident = sreplace(session.pool, server_ident, "%V",
340
0
          main_server->ServerFQDN, NULL);
341
0
      }
342
343
0
      if (strstr(server_ident, "%v") != NULL) {
344
0
        server_ident = sreplace(session.pool, server_ident, "%v",
345
0
          main_server->ServerName, NULL);
346
0
      }
347
348
0
      if (strstr(server_ident, "%{version}") != NULL) {
349
0
        server_ident = sreplace(session.pool, server_ident, "%{version}",
350
0
          PROFTPD_VERSION_TEXT, NULL);
351
0
      }
352
353
0
      if (flags & PR_DISPLAY_FL_SEND_NOW) {
354
0
        pr_response_send(R_220, "%s", server_ident);
355
356
0
      } else {
357
0
        pr_response_add(R_220, "%s", server_ident);
358
0
      }
359
360
0
    } else if (defer_welcome &&
361
0
               *defer_welcome == TRUE) {
362
363
0
      if (flags & PR_DISPLAY_FL_SEND_NOW) {
364
0
        pr_response_send(R_220, _("ProFTPD Server ready."));
365
366
0
      } else {
367
0
        pr_response_add(R_220, _("ProFTPD Server ready."));
368
0
      }
369
370
0
    } else {
371
0
      if (flags & PR_DISPLAY_FL_SEND_NOW) {
372
0
        pr_response_send(R_220, _("ProFTPD Server (%s) [%s]"), s->ServerName,
373
0
          serveraddress);
374
375
0
      } else {
376
0
        pr_response_add(R_220, _("ProFTPD Server (%s) [%s]"), s->ServerName,
377
0
          serveraddress);
378
0
      }
379
0
    }
380
381
0
  } else {
382
0
    if (flags & PR_DISPLAY_FL_SEND_NOW) {
383
0
      pr_response_send(R_220, _("%s FTP server ready"), serveraddress);
384
385
0
    } else {
386
0
      pr_response_add(R_220, _("%s FTP server ready"), serveraddress);
387
0
    }
388
0
  }
389
0
}
390
391
0
int pr_session_set_idle(void) {
392
0
  const char *user = NULL;
393
394
0
  pr_scoreboard_entry_update(session.pid,
395
0
    PR_SCORE_BEGIN_IDLE, time(NULL),
396
0
    PR_SCORE_CMD, "%s", "idle", NULL, NULL);
397
398
0
  pr_scoreboard_entry_update(session.pid,
399
0
    PR_SCORE_CMD_ARG, "%s", "", NULL, NULL);
400
401
0
  if (session.user != NULL) {
402
0
    user = session.user;
403
404
0
  } else {
405
0
    user = "(authenticating)";
406
0
  }
407
408
0
  pr_proctitle_set("%s - %s: IDLE", user, session.proc_prefix);
409
0
  return 0;
410
0
}
411
412
0
int pr_session_set_protocol(const char *sess_proto) {
413
0
  int count, res = 0, xerrno = 0;
414
415
0
  if (sess_proto == NULL) {
416
0
    errno = EINVAL;
417
0
    return -1;
418
0
  }
419
420
0
  count = pr_table_exists(session.notes, "protocol");
421
0
  if (count > 0) {
422
0
    res = pr_table_set(session.notes, pstrdup(session.pool, "protocol"),
423
0
      pstrdup(session.pool, sess_proto), 0);
424
0
    xerrno = errno;
425
426
0
  } else {
427
0
    res = pr_table_add(session.notes, pstrdup(session.pool, "protocol"),
428
0
      pstrdup(session.pool, sess_proto), 0);
429
0
    xerrno = errno;
430
0
  }
431
432
  /* Update the scoreboard entry for this session with the protocol. */
433
0
  pr_scoreboard_entry_update(session.pid, PR_SCORE_PROTOCOL, sess_proto, NULL);
434
435
0
  errno = xerrno;
436
0
  return res;
437
0
}
438
439
0
const char *pr_session_get_ttyname(pool *p) {
440
0
  char ttybuf[32];
441
0
  const char *sess_proto, *tty_proto = NULL;
442
443
0
  if (p == NULL) {
444
0
    errno = EINVAL;
445
0
    return NULL;
446
0
  }
447
448
0
  if (sess_ttyname != NULL) {
449
    /* Return the cached name. */
450
0
    return pstrdup(p, sess_ttyname);
451
0
  }
452
453
0
  sess_proto = pr_table_get(session.notes, "protocol", NULL);
454
0
  if (sess_proto != NULL) {
455
0
    if (strcmp(sess_proto, "ftp") == 0 ||
456
0
        strcmp(sess_proto, "ftps") == 0) {
457
#if (defined(BSD) && (BSD >= 199103))
458
      tty_proto = "ftp";
459
#else
460
0
      tty_proto = "ftpd";
461
0
#endif
462
463
0
    } else if (strcmp(sess_proto, "ssh2") == 0 ||
464
0
               strcmp(sess_proto, "sftp") == 0 ||
465
0
               strcmp(sess_proto, "scp") == 0 ||
466
0
               strcmp(sess_proto, "publickey") == 0) {
467
468
      /* Just use the plain "ssh" string for the tty name for these cases. */
469
0
      tty_proto = "ssh";
470
471
      /* Cache the originally constructed tty name for any later retrievals. */
472
0
      sess_ttyname = pstrdup(session.pool, tty_proto);
473
0
      return pstrdup(p, sess_ttyname);
474
0
    }
475
0
  }
476
477
0
  if (tty_proto == NULL) {
478
#if (defined(BSD) && (BSD >= 199103))
479
    tty_proto = "ftp";
480
#else
481
0
    tty_proto = "ftpd";
482
0
#endif
483
0
  }
484
485
0
  memset(ttybuf, '\0', sizeof(ttybuf));
486
#if (defined(BSD) && (BSD >= 199103))
487
  pr_snprintf(ttybuf, sizeof(ttybuf), "%s%ld", tty_proto,
488
    (long) (session.pid ? session.pid : getpid()));
489
#else
490
0
  pr_snprintf(ttybuf, sizeof(ttybuf), "%s%d", tty_proto,
491
0
    (int) (session.pid ? session.pid : getpid()));
492
0
#endif
493
494
  /* Cache the originally constructed tty name for any later retrievals. */
495
0
  sess_ttyname = pstrdup(session.pool, ttybuf);
496
497
0
  return pstrdup(p, sess_ttyname);
498
0
}