/* * ProFTPD - mod_sftp keyboard-interactive driver mgmt * Copyright (c) 2008-2020 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. */ #include "mod_sftp.h" #include "ssh2.h" #include "packet.h" #include "msg.h" #include "utf8.h" #include "kbdint.h" #define SFTP_KBDINT_MAX_RESPONSES 500 extern pr_response_t *resp_list, *resp_err_list; struct kbdint_driver { struct kbdint_driver *next, *prev; const char *name; sftp_kbdint_driver_t *driver; }; static pool *kbdint_pool = NULL; static struct kbdint_driver *drivers = NULL; static unsigned int ndrivers = 0; static const char *trace_channel = "ssh2"; unsigned int sftp_kbdint_have_drivers(void) { return ndrivers; } sftp_kbdint_driver_t *sftp_kbdint_get_driver(const char *name) { struct kbdint_driver *kd; if (name == NULL) { errno = EINVAL; return NULL; } for (kd = drivers; kd; kd = kd->next) { if (strcmp(kd->name, name) == 0) { return kd->driver; } } errno = ENOENT; return NULL; } static struct kbdint_driver *kdi = NULL; sftp_kbdint_driver_t *sftp_kbdint_first_driver(void) { sftp_kbdint_driver_t *d = NULL; if (drivers == NULL) { errno = ENOENT; return NULL; } if (kdi != NULL) { errno = EPERM; return NULL; } d = drivers->driver; kdi = drivers->next; return d; } sftp_kbdint_driver_t *sftp_kbdint_next_driver(void) { sftp_kbdint_driver_t *d = NULL; if (drivers == NULL) { errno = ENOENT; return NULL; } if (kdi == NULL) { errno = EPERM; return NULL; } d = kdi->driver; kdi = kdi->next; return d; } int sftp_kbdint_register_driver(const char *name, sftp_kbdint_driver_t *driver) { struct kbdint_driver *kd; if (name == NULL || driver == NULL) { errno = EINVAL; return -1; } if (kbdint_pool == NULL) { kbdint_pool = make_sub_pool(permanent_pool); pr_pool_tag(kbdint_pool, "SFTP keyboard-interactive API Pool"); } /* Make sure that the driver hasn't already been registered. */ if (sftp_kbdint_get_driver(name) != NULL) { errno = EEXIST; return -1; } kd = pcalloc(kbdint_pool, sizeof(struct kbdint_driver)); /* XXX Should this name string be dup'd from the kbdint_pool? */ kd->name = name; driver->driver_name = pstrdup(kbdint_pool, name); kd->driver = driver; if (drivers) { kd->next = drivers; } else { kd->next = NULL; } drivers = kd; ndrivers++; return 0; } int sftp_kbdint_unregister_driver(const char *name) { struct kbdint_driver *kd; if (name == NULL) { errno = EINVAL; return -1; } for (kd = drivers; kd; kd = kd->next) { if (strcmp(kd->name, name) == 0) { if (kd->prev) { kd->prev->next = kd->next; } else { /* If prev is NULL, this is the head of the list. */ drivers = kd->next; } if (kd->next) kd->next->prev = kd->prev; kd->next = kd->prev = NULL; ndrivers--; /* NOTE: a counter should be kept of the number of unregistrations, * as the memory for a registration is not freed on unregistration. */ return 0; } } errno = ENOENT; return -1; } /* Convenience functions that can be used by the driver implementations * for communicating with the client, without needing to worry about * SSH2 message formatting, I/O, etc. */ int sftp_kbdint_send_challenge(const char *user, const char *instruction, uint32_t count, sftp_kbdint_challenge_t *challenges) { register unsigned int i; unsigned char *buf, *ptr; uint32_t buflen, bufsz; struct ssh2_packet *pkt; int res; if (count == 0 || challenges == NULL) { errno = EINVAL; return -1; } pkt = sftp_ssh2_packet_create(kbdint_pool); /* XXX Is this large enough? Too large? */ buflen = bufsz = 3072; buf = ptr = palloc(pkt->pool, bufsz); /* See RFC4256, Section 3.2. */ sftp_msg_write_byte(&buf, &buflen, SFTP_SSH2_MSG_USER_AUTH_INFO_REQ); if (user) { sftp_msg_write_string(&buf, &buflen, sftp_utf8_encode_str(pkt->pool, user)); } else { /* Empty user strings are allowed. */ sftp_msg_write_string(&buf, &buflen, ""); } if (instruction) { sftp_msg_write_string(&buf, &buflen, sftp_utf8_encode_str(pkt->pool, instruction)); } else { /* Empty instruction strings are allowed. */ sftp_msg_write_string(&buf, &buflen, ""); } /* Empty language string. */ sftp_msg_write_string(&buf, &buflen, ""); sftp_msg_write_int(&buf, &buflen, count); for (i = 0; i < count; i++) { sftp_msg_write_string(&buf, &buflen, challenges[i].challenge); sftp_msg_write_bool(&buf, &buflen, challenges[i].display_response); } pkt->payload = ptr; pkt->payload_len = (bufsz - buflen); pr_trace_msg(trace_channel, 9, "sending USER_AUTH_INFO_REQ message to client"); res = sftp_ssh2_packet_write(sftp_conn->wfd, pkt); destroy_pool(pkt->pool); return res; } static struct ssh2_packet *read_response_packet(pool *p) { struct ssh2_packet *pkt = NULL; /* Keep looping until we get the desired message, or we time out. */ while (pkt == NULL) { int res; char mesg_type; pr_signals_handle(); pkt = sftp_ssh2_packet_create(kbdint_pool); res = sftp_ssh2_packet_read(sftp_conn->rfd, pkt); if (res < 0) { int xerrno = errno; destroy_pool(pkt->pool); errno = xerrno; return NULL; } pr_response_clear(&resp_list); pr_response_clear(&resp_err_list); /* Per RFC 4253, Section 11, DEBUG, DISCONNECT, IGNORE, and UNIMPLEMENTED * messages can occur at any time, even during KEX. We have to be prepared * for this, and Do The Right Thing(tm). */ mesg_type = sftp_ssh2_packet_get_mesg_type(pkt); switch (mesg_type) { case SFTP_SSH2_MSG_DEBUG: sftp_ssh2_packet_handle_debug(pkt); pkt = NULL; break; case SFTP_SSH2_MSG_DISCONNECT: sftp_ssh2_packet_handle_disconnect(pkt); pkt = NULL; break; case SFTP_SSH2_MSG_IGNORE: sftp_ssh2_packet_handle_ignore(pkt); pkt = NULL; break; case SFTP_SSH2_MSG_UNIMPLEMENTED: sftp_ssh2_packet_handle_unimplemented(pkt); pkt = NULL; break; case SFTP_SSH2_MSG_USER_AUTH_INFO_RESP: pr_trace_msg(trace_channel, 13, "received expected %s message", sftp_ssh2_packet_get_mesg_type_desc(mesg_type)); break; default: (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, "expecting USER_AUTH_INFO_RESP message, received %s (%d)", sftp_ssh2_packet_get_mesg_type_desc(mesg_type), mesg_type); destroy_pool(pkt->pool); errno = EPERM; return NULL; } } return pkt; } int sftp_kbdint_recv_response(pool *p, uint32_t expected_count, uint32_t *rcvd_count, const char ***responses) { register unsigned int i; unsigned char *buf; cmd_rec *cmd; array_header *list; uint32_t buflen, resp_count; struct ssh2_packet *pkt = NULL; pool *resp_pool = NULL; if (p == NULL || rcvd_count == NULL || responses == NULL) { errno = EINVAL; return -1; } pkt = read_response_packet(p); if (pkt == NULL) { return -1; } /* Cache a reference to the current response pool used. */ resp_pool = pr_response_get_pool(); pr_response_set_pool(pkt->pool); cmd = pr_cmd_alloc(pkt->pool, 2, pstrdup(pkt->pool, "USER_AUTH_INFO_RESP")); cmd->arg = "(data)"; pr_trace_msg(trace_channel, 9, "reading USER_AUTH_INFO_RESP message from client"); buf = pkt->payload; buflen = pkt->payload_len; resp_count = sftp_msg_read_int(pkt->pool, &buf, &buflen); /* Ensure that the number of responses sent by the client is the same * as the number of challenges sent, lest a malicious client attempt to * trick us into allocating too much memory (Bug#3973). */ if (resp_count != expected_count) { (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, "sent %lu %s, but received %lu %s", (unsigned long) expected_count, expected_count != 1 ? "challenges" : "challenge", (unsigned long) resp_count, resp_count != 1 ? "responses" : "response"); destroy_pool(pkt->pool); pr_response_set_pool(resp_pool); errno = EPERM; return -1; } if (resp_count > SFTP_KBDINT_MAX_RESPONSES) { (void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION, "received too many responses (%lu > max %lu), rejecting", (unsigned long) resp_count, (unsigned long) SFTP_KBDINT_MAX_RESPONSES); destroy_pool(pkt->pool); pr_response_set_pool(resp_pool); errno = EPERM; return -1; } list = make_array(p, resp_count, sizeof(char *)); for (i = 0; i < resp_count; i++) { char *resp; resp = sftp_msg_read_string(pkt->pool, &buf, &buflen); *((char **) push_array(list)) = pstrdup(p, sftp_utf8_decode_str(p, resp)); } *rcvd_count = resp_count; *responses = ((const char **) list->elts); destroy_pool(pkt->pool); pr_response_set_pool(resp_pool); return 0; }