| 1 | /* |
|---|
| 2 | * ProFTPD - FTP server daemon |
|---|
| 3 | * Copyright (c) 2009-2011 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 | * $Id: session.c,v 1.14 2011/05/23 21:22:24 castaglia Exp $ |
|---|
| 25 | */ |
|---|
| 26 | |
|---|
| 27 | #include "conf.h" |
|---|
| 28 | |
|---|
| 29 | /* From src/main.c */ |
|---|
| 30 | extern unsigned char is_master; |
|---|
| 31 | |
|---|
| 32 | static void sess_cleanup(int flags) { |
|---|
| 33 | |
|---|
| 34 | /* Clear the scoreboard entry. */ |
|---|
| 35 | if (ServerType == SERVER_STANDALONE) { |
|---|
| 36 | |
|---|
| 37 | /* For standalone daemons, we only clear the scoreboard slot if we are |
|---|
| 38 | * an exiting child process. |
|---|
| 39 | */ |
|---|
| 40 | |
|---|
| 41 | if (!is_master) { |
|---|
| 42 | if (pr_scoreboard_entry_del(TRUE) < 0 && |
|---|
| 43 | errno != EINVAL && |
|---|
| 44 | errno != ENOENT) { |
|---|
| 45 | pr_log_debug(DEBUG1, "error deleting scoreboard entry: %s", |
|---|
| 46 | strerror(errno)); |
|---|
| 47 | } |
|---|
| 48 | } |
|---|
| 49 | |
|---|
| 50 | } else if (ServerType == SERVER_INETD) { |
|---|
| 51 | /* For inetd-spawned daemons, we always clear the scoreboard slot. */ |
|---|
| 52 | if (pr_scoreboard_entry_del(TRUE) < 0 && |
|---|
| 53 | errno != EINVAL && |
|---|
| 54 | errno != ENOENT) { |
|---|
| 55 | pr_log_debug(DEBUG1, "error deleting scoreboard entry: %s", |
|---|
| 56 | strerror(errno)); |
|---|
| 57 | } |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | /* If session.user is set, we have a valid login. */ |
|---|
| 61 | if (session.user && |
|---|
| 62 | session.wtmp_log) { |
|---|
| 63 | const char *sess_ttyname; |
|---|
| 64 | |
|---|
| 65 | sess_ttyname = pr_session_get_ttyname(session.pool); |
|---|
| 66 | log_wtmp(sess_ttyname, "", pr_netaddr_get_sess_remote_name(), |
|---|
| 67 | pr_netaddr_get_sess_remote_addr()); |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | /* These are necessary in order that cleanups associated with these pools |
|---|
| 71 | * (and their subpools) are properly run. |
|---|
| 72 | */ |
|---|
| 73 | if (session.d) { |
|---|
| 74 | pr_inet_close(session.pool, session.d); |
|---|
| 75 | session.d = NULL; |
|---|
| 76 | } |
|---|
| 77 | |
|---|
| 78 | if (session.c) { |
|---|
| 79 | pr_inet_close(session.pool, session.c); |
|---|
| 80 | session.c = NULL; |
|---|
| 81 | } |
|---|
| 82 | |
|---|
| 83 | /* Run all the exit handlers */ |
|---|
| 84 | pr_event_generate("core.exit", NULL); |
|---|
| 85 | |
|---|
| 86 | if (!is_master || |
|---|
| 87 | (ServerType == SERVER_INETD && |
|---|
| 88 | !(flags & PR_SESS_END_FL_SYNTAX_CHECK))) { |
|---|
| 89 | pr_log_pri(PR_LOG_INFO, "%s session closed.", |
|---|
| 90 | pr_session_get_protocol(PR_SESS_PROTO_FL_LOGOUT)); |
|---|
| 91 | } |
|---|
| 92 | |
|---|
| 93 | log_closesyslog(); |
|---|
| 94 | } |
|---|
| 95 | |
|---|
| 96 | void pr_session_disconnect(module *m, int reason_code, |
|---|
| 97 | const char *details) { |
|---|
| 98 | |
|---|
| 99 | session.disconnect_reason = reason_code; |
|---|
| 100 | session.disconnect_module = m; |
|---|
| 101 | |
|---|
| 102 | if (details != NULL && |
|---|
| 103 | session.notes != NULL) { |
|---|
| 104 | /* Stash any extra details in the session.notes table */ |
|---|
| 105 | if (pr_table_add_dup(session.notes, "core.disconnect-details", |
|---|
| 106 | (char *) details, 0) < 0) { |
|---|
| 107 | int xerrno = errno; |
|---|
| 108 | |
|---|
| 109 | if (xerrno != EEXIST) { |
|---|
| 110 | pr_log_debug(DEBUG5, "error stashing 'core.disconnect-details' in " |
|---|
| 111 | "session.notes: %s", strerror(xerrno)); |
|---|
| 112 | } |
|---|
| 113 | } |
|---|
| 114 | } |
|---|
| 115 | |
|---|
| 116 | pr_session_end(0); |
|---|
| 117 | } |
|---|
| 118 | |
|---|
| 119 | void pr_session_end(int flags) { |
|---|
| 120 | int exitcode = 0; |
|---|
| 121 | |
|---|
| 122 | sess_cleanup(flags); |
|---|
| 123 | |
|---|
| 124 | if (flags & PR_SESS_END_FL_NOEXIT) { |
|---|
| 125 | return; |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | #ifdef PR_USE_DEVEL |
|---|
| 129 | destroy_pool(session.pool); |
|---|
| 130 | |
|---|
| 131 | if (is_master) { |
|---|
| 132 | main_server = NULL; |
|---|
| 133 | free_pools(); |
|---|
| 134 | pr_proctitle_free(); |
|---|
| 135 | } |
|---|
| 136 | #endif /* PR_USE_DEVEL */ |
|---|
| 137 | |
|---|
| 138 | #ifdef PR_DEVEL_PROFILE |
|---|
| 139 | /* Populating the gmon.out gprof file requires that the process exit |
|---|
| 140 | * via exit(3) or by returning from main(). Using _exit(2) doesn't allow |
|---|
| 141 | * the process the time to write its profile data out. |
|---|
| 142 | */ |
|---|
| 143 | exit(exitcode); |
|---|
| 144 | #else |
|---|
| 145 | _exit(exitcode); |
|---|
| 146 | #endif /* PR_DEVEL_PROFILE */ |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | const char *pr_session_get_disconnect_reason(char **details) { |
|---|
| 150 | const char *reason_str = NULL; |
|---|
| 151 | |
|---|
| 152 | switch (session.disconnect_reason) { |
|---|
| 153 | case PR_SESS_DISCONNECT_UNSPECIFIED: |
|---|
| 154 | reason_str = "Unknown/unspecified"; |
|---|
| 155 | break; |
|---|
| 156 | |
|---|
| 157 | case PR_SESS_DISCONNECT_CLIENT_QUIT: |
|---|
| 158 | reason_str = "Quit"; |
|---|
| 159 | break; |
|---|
| 160 | |
|---|
| 161 | case PR_SESS_DISCONNECT_CLIENT_EOF: |
|---|
| 162 | reason_str = "Read EOF from client"; |
|---|
| 163 | break; |
|---|
| 164 | |
|---|
| 165 | case PR_SESS_DISCONNECT_SESSION_INIT_FAILED: |
|---|
| 166 | reason_str = "Session initialized failed"; |
|---|
| 167 | break; |
|---|
| 168 | |
|---|
| 169 | case PR_SESS_DISCONNECT_SIGNAL: |
|---|
| 170 | reason_str = "Terminated by signal"; |
|---|
| 171 | break; |
|---|
| 172 | |
|---|
| 173 | case PR_SESS_DISCONNECT_NOMEM: |
|---|
| 174 | reason_str = "Low memory"; |
|---|
| 175 | break; |
|---|
| 176 | |
|---|
| 177 | case PR_SESS_DISCONNECT_SERVER_SHUTDOWN: |
|---|
| 178 | reason_str = "Server shutting down"; |
|---|
| 179 | break; |
|---|
| 180 | |
|---|
| 181 | case PR_SESS_DISCONNECT_TIMEOUT: |
|---|
| 182 | reason_str = "Timeout exceeded"; |
|---|
| 183 | break; |
|---|
| 184 | |
|---|
| 185 | case PR_SESS_DISCONNECT_BANNED: |
|---|
| 186 | reason_str = "Banned"; |
|---|
| 187 | break; |
|---|
| 188 | |
|---|
| 189 | case PR_SESS_DISCONNECT_CONFIG_ACL: |
|---|
| 190 | reason_str = "Configured policy"; |
|---|
| 191 | break; |
|---|
| 192 | |
|---|
| 193 | case PR_SESS_DISCONNECT_MODULE_ACL: |
|---|
| 194 | reason_str = "Module-specific policy"; |
|---|
| 195 | break; |
|---|
| 196 | |
|---|
| 197 | case PR_SESS_DISCONNECT_BAD_CONFIG: |
|---|
| 198 | reason_str = "Server misconfiguration"; |
|---|
| 199 | break; |
|---|
| 200 | |
|---|
| 201 | case PR_SESS_DISCONNECT_BY_APPLICATION: |
|---|
| 202 | reason_str = "Application error"; |
|---|
| 203 | break; |
|---|
| 204 | } |
|---|
| 205 | |
|---|
| 206 | if (details != NULL) { |
|---|
| 207 | *details = pr_table_get(session.notes, "core.disconnect-details", NULL); |
|---|
| 208 | } |
|---|
| 209 | |
|---|
| 210 | return reason_str; |
|---|
| 211 | } |
|---|
| 212 | |
|---|
| 213 | const char *pr_session_get_protocol(int flags) { |
|---|
| 214 | const char *sess_proto; |
|---|
| 215 | |
|---|
| 216 | sess_proto = pr_table_get(session.notes, "protocol", NULL); |
|---|
| 217 | if (sess_proto == NULL) { |
|---|
| 218 | sess_proto = "ftp"; |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | if (!(flags & PR_SESS_PROTO_FL_LOGOUT)) { |
|---|
| 222 | /* Return the protocol as is. */ |
|---|
| 223 | return sess_proto; |
|---|
| 224 | } |
|---|
| 225 | |
|---|
| 226 | /* Otherwise, we need to return either "FTP" or "SSH2", for consistency. */ |
|---|
| 227 | if (strncmp(sess_proto, "ftp", 4) == 0 || |
|---|
| 228 | strncmp(sess_proto, "ftps", 5) == 0) { |
|---|
| 229 | return "FTP"; |
|---|
| 230 | |
|---|
| 231 | } else if (strncmp(sess_proto, "ssh2", 5) == 0 || |
|---|
| 232 | strncmp(sess_proto, "sftp", 5) == 0 || |
|---|
| 233 | strncmp(sess_proto, "scp", 4) == 0 || |
|---|
| 234 | strncmp(sess_proto, "publickey", 10) == 0) { |
|---|
| 235 | return "SSH2"; |
|---|
| 236 | } |
|---|
| 237 | |
|---|
| 238 | /* Should never reach here, but just in case... */ |
|---|
| 239 | return "unknown"; |
|---|
| 240 | } |
|---|
| 241 | |
|---|
| 242 | int pr_session_set_idle(void) { |
|---|
| 243 | char *user = NULL; |
|---|
| 244 | |
|---|
| 245 | pr_scoreboard_entry_update(session.pid, |
|---|
| 246 | PR_SCORE_BEGIN_IDLE, time(NULL), |
|---|
| 247 | PR_SCORE_CMD, "%s", "idle", NULL, NULL); |
|---|
| 248 | |
|---|
| 249 | pr_scoreboard_entry_update(session.pid, |
|---|
| 250 | PR_SCORE_CMD_ARG, "%s", "", NULL, NULL); |
|---|
| 251 | |
|---|
| 252 | if (session.user) { |
|---|
| 253 | user = session.user; |
|---|
| 254 | |
|---|
| 255 | } else { |
|---|
| 256 | user = "(authenticating)"; |
|---|
| 257 | } |
|---|
| 258 | |
|---|
| 259 | pr_proctitle_set("%s - %s: IDLE", user, session.proc_prefix); |
|---|
| 260 | return 0; |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | int pr_session_set_protocol(const char *sess_proto) { |
|---|
| 264 | int count, res; |
|---|
| 265 | |
|---|
| 266 | if (sess_proto == NULL) { |
|---|
| 267 | errno = EINVAL; |
|---|
| 268 | return -1; |
|---|
| 269 | } |
|---|
| 270 | |
|---|
| 271 | count = pr_table_exists(session.notes, "protocol"); |
|---|
| 272 | if (count > 0) { |
|---|
| 273 | res = pr_table_set(session.notes, pstrdup(session.pool, "protocol"), |
|---|
| 274 | pstrdup(session.pool, sess_proto), 0); |
|---|
| 275 | |
|---|
| 276 | if (res == 0) { |
|---|
| 277 | /* Update the scoreboard entry for this session with the protocol. */ |
|---|
| 278 | pr_scoreboard_entry_update(session.pid, PR_SCORE_PROTOCOL, sess_proto, |
|---|
| 279 | NULL); |
|---|
| 280 | } |
|---|
| 281 | |
|---|
| 282 | return res; |
|---|
| 283 | } |
|---|
| 284 | |
|---|
| 285 | res = pr_table_add(session.notes, pstrdup(session.pool, "protocol"), |
|---|
| 286 | pstrdup(session.pool, sess_proto), 0); |
|---|
| 287 | |
|---|
| 288 | if (res == 0) { |
|---|
| 289 | /* Update the scoreboard entry for this session with the protocol. */ |
|---|
| 290 | pr_scoreboard_entry_update(session.pid, PR_SCORE_PROTOCOL, sess_proto, |
|---|
| 291 | NULL); |
|---|
| 292 | } |
|---|
| 293 | |
|---|
| 294 | return res; |
|---|
| 295 | } |
|---|
| 296 | |
|---|
| 297 | static const char *sess_ttyname = NULL; |
|---|
| 298 | |
|---|
| 299 | const char *pr_session_get_ttyname(pool *p) { |
|---|
| 300 | char ttybuf[32]; |
|---|
| 301 | const char *sess_proto, *tty_proto = NULL; |
|---|
| 302 | |
|---|
| 303 | if (p == NULL) { |
|---|
| 304 | errno = EINVAL; |
|---|
| 305 | return NULL; |
|---|
| 306 | } |
|---|
| 307 | |
|---|
| 308 | if (sess_ttyname) { |
|---|
| 309 | /* Return the cached name. */ |
|---|
| 310 | return pstrdup(p, sess_ttyname); |
|---|
| 311 | } |
|---|
| 312 | |
|---|
| 313 | sess_proto = pr_table_get(session.notes, "protocol", NULL); |
|---|
| 314 | if (sess_proto) { |
|---|
| 315 | if (strncmp(sess_proto, "ftp", 4) == 0 || |
|---|
| 316 | strncmp(sess_proto, "ftps", 5) == 0) { |
|---|
| 317 | #if (defined(BSD) && (BSD >= 199103)) |
|---|
| 318 | tty_proto = "ftp"; |
|---|
| 319 | #else |
|---|
| 320 | tty_proto = "ftpd"; |
|---|
| 321 | #endif |
|---|
| 322 | |
|---|
| 323 | } else if (strncmp(sess_proto, "ssh2", 5) == 0 || |
|---|
| 324 | strncmp(sess_proto, "sftp", 5) == 0 || |
|---|
| 325 | strncmp(sess_proto, "scp", 4) == 0 || |
|---|
| 326 | strncmp(sess_proto, "publickey", 10) == 0) { |
|---|
| 327 | |
|---|
| 328 | /* Just use the plain "ssh" string for the tty name for these cases. */ |
|---|
| 329 | tty_proto = "ssh"; |
|---|
| 330 | |
|---|
| 331 | /* Cache the originally constructed tty name for any later retrievals. */ |
|---|
| 332 | sess_ttyname = pstrdup(session.pool, tty_proto); |
|---|
| 333 | return pstrdup(p, sess_ttyname); |
|---|
| 334 | } |
|---|
| 335 | } |
|---|
| 336 | |
|---|
| 337 | if (tty_proto == NULL) { |
|---|
| 338 | #if (defined(BSD) && (BSD >= 199103)) |
|---|
| 339 | tty_proto = "ftp"; |
|---|
| 340 | #else |
|---|
| 341 | tty_proto = "ftpd"; |
|---|
| 342 | #endif |
|---|
| 343 | } |
|---|
| 344 | |
|---|
| 345 | memset(ttybuf, '\0', sizeof(ttybuf)); |
|---|
| 346 | #if (defined(BSD) && (BSD >= 199103)) |
|---|
| 347 | snprintf(ttybuf, sizeof(ttybuf), "%s%ld", tty_proto, |
|---|
| 348 | (long) (session.pid ? session.pid : getpid())); |
|---|
| 349 | #else |
|---|
| 350 | snprintf(ttybuf, sizeof(ttybuf), "%s%d", tty_proto, |
|---|
| 351 | (int) (session.pid ? session.pid : getpid())); |
|---|
| 352 | #endif |
|---|
| 353 | |
|---|
| 354 | /* Cache the originally constructed tty name for any later retrievals. */ |
|---|
| 355 | sess_ttyname = pstrdup(session.pool, ttybuf); |
|---|
| 356 | |
|---|
| 357 | return pstrdup(p, sess_ttyname); |
|---|
| 358 | } |
|---|