mirror of
https://github.com/clearml/dropbear
synced 2025-05-10 06:41:07 +00:00
merge
This commit is contained in:
commit
be4f9ce8e7
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@ -65,6 +65,8 @@ jobs:
|
|||||||
# configure_flags: --enable-fuzz --disable-harden --enable-bundled-libtom --enable-werror
|
# configure_flags: --enable-fuzz --disable-harden --enable-bundled-libtom --enable-werror
|
||||||
# ldflags: -fsanitize=address
|
# ldflags: -fsanitize=address
|
||||||
# extracflags: -fsanitize=address
|
# extracflags: -fsanitize=address
|
||||||
|
# # -fsanitize=address prevents aslr, don't test it
|
||||||
|
# pytest_addopts: -k "not aslr"
|
||||||
# fuzz: True
|
# fuzz: True
|
||||||
# cc: clang
|
# cc: clang
|
||||||
|
|
||||||
@ -74,6 +76,7 @@ jobs:
|
|||||||
# ldflags: -fsanitize=undefined
|
# ldflags: -fsanitize=undefined
|
||||||
# # don't fail with alignment due to https://github.com/libtom/libtomcrypt/issues/549
|
# # don't fail with alignment due to https://github.com/libtom/libtomcrypt/issues/549
|
||||||
# extracflags: -fsanitize=undefined -fno-sanitize-recover=undefined -fsanitize-recover=alignment
|
# extracflags: -fsanitize=undefined -fno-sanitize-recover=undefined -fsanitize-recover=alignment
|
||||||
|
# pytest_addopts: -k "not aslr"
|
||||||
# fuzz: True
|
# fuzz: True
|
||||||
# cc: clang
|
# cc: clang
|
||||||
|
|
||||||
@ -86,6 +89,10 @@ jobs:
|
|||||||
# for fuzzing
|
# for fuzzing
|
||||||
CXX: clang++
|
CXX: clang++
|
||||||
RANLIB: ${{ matrix.ranlib || 'ranlib' }}
|
RANLIB: ${{ matrix.ranlib || 'ranlib' }}
|
||||||
|
# pytest in "make check" recognises this for extra arguments
|
||||||
|
PYTEST_ADDOPTS: ${{ matrix.pytest_addopts }}
|
||||||
|
# some pytests depend on special setup from this file. see authorized_keys below.
|
||||||
|
DBTEST_IN_ACTION: true
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: deps
|
- name: deps
|
||||||
@ -128,7 +135,14 @@ jobs:
|
|||||||
- name: keys
|
- name: keys
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
|
# remove old files so we can rerun in-place with "act -r" during test development
|
||||||
|
rm -vf ~/.ssh/id_dropbear*
|
||||||
~/inst/bin/dropbearkey -t ecdsa -f ~/.ssh/id_dropbear | grep ^ecdsa > ~/.ssh/authorized_keys
|
~/inst/bin/dropbearkey -t ecdsa -f ~/.ssh/id_dropbear | grep ^ecdsa > ~/.ssh/authorized_keys
|
||||||
|
|
||||||
|
# to test setting SSH_PUBKEYINFO, replace the trailing comment
|
||||||
|
~/inst/bin/dropbearkey -t ecdsa -f ~/.ssh/id_dropbear_key2 | grep ^ecdsa | sed 's/[^ ]*$/key2 extra/' >> ~/.ssh/authorized_keys
|
||||||
|
~/inst/bin/dropbearkey -t ecdsa -f ~/.ssh/id_dropbear_key3 | grep ^ecdsa | sed 's/[^ ]*$/key3%char/' >> ~/.ssh/authorized_keys
|
||||||
|
~/inst/bin/dropbearkey -t ecdsa -f ~/.ssh/id_dropbear_key4 | grep ^ecdsa | sed 's/[^ ]*$/key4,char/' >> ~/.ssh/authorized_keys
|
||||||
chmod 700 ~ ~/.ssh ~/.ssh/authorized_keys
|
chmod 700 ~ ~/.ssh ~/.ssh/authorized_keys
|
||||||
ls -ld ~ ~/.ssh ~/.ssh/authorized_keys
|
ls -ld ~ ~/.ssh ~/.ssh/authorized_keys
|
||||||
|
|
||||||
|
1
auth.h
1
auth.h
@ -125,6 +125,7 @@ struct AuthState {
|
|||||||
char *pw_passwd;
|
char *pw_passwd;
|
||||||
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
|
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
|
||||||
struct PubKeyOptions* pubkey_options;
|
struct PubKeyOptions* pubkey_options;
|
||||||
|
char *pubkey_info;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
39
cli-auth.c
39
cli-auth.c
@ -85,31 +85,32 @@ void recv_msg_userauth_banner() {
|
|||||||
banner = buf_getstring(ses.payload, &bannerlen);
|
banner = buf_getstring(ses.payload, &bannerlen);
|
||||||
buf_eatstring(ses.payload); /* The language string */
|
buf_eatstring(ses.payload); /* The language string */
|
||||||
|
|
||||||
if (bannerlen > MAX_BANNER_SIZE) {
|
if (cli_opts.quiet == 0) {
|
||||||
TRACE(("recv_msg_userauth_banner: bannerlen too long: %d", bannerlen))
|
if (bannerlen > MAX_BANNER_SIZE) {
|
||||||
truncated = 1;
|
TRACE(("recv_msg_userauth_banner: bannerlen too long: %d", bannerlen))
|
||||||
} else {
|
truncated = 1;
|
||||||
cleantext(banner);
|
} else {
|
||||||
|
cleantext(banner);
|
||||||
|
|
||||||
/* Limit to 24 lines */
|
/* Limit to 24 lines */
|
||||||
linecount = 1;
|
linecount = 1;
|
||||||
for (i = 0; i < bannerlen; i++) {
|
for (i = 0; i < bannerlen; i++) {
|
||||||
if (banner[i] == '\n') {
|
if (banner[i] == '\n') {
|
||||||
if (linecount >= MAX_BANNER_LINES) {
|
if (linecount >= MAX_BANNER_LINES) {
|
||||||
banner[i] = '\0';
|
banner[i] = '\0';
|
||||||
truncated = 1;
|
truncated = 1;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
linecount++;
|
||||||
}
|
}
|
||||||
linecount++;
|
|
||||||
}
|
}
|
||||||
|
fprintf(stderr, "%s\n", banner);
|
||||||
}
|
}
|
||||||
fprintf(stderr, "%s\n", banner);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (truncated) {
|
if (truncated) {
|
||||||
fprintf(stderr, "[Banner from the server is too long]\n");
|
fprintf(stderr, "[Banner from the server is too long]\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_free(banner);
|
m_free(banner);
|
||||||
TRACE(("leave recv_msg_userauth_banner"))
|
TRACE(("leave recv_msg_userauth_banner"))
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ static void printhelp() {
|
|||||||
"-T Don't allocate a pty\n"
|
"-T Don't allocate a pty\n"
|
||||||
"-N Don't run a remote command\n"
|
"-N Don't run a remote command\n"
|
||||||
"-f Run in background after auth\n"
|
"-f Run in background after auth\n"
|
||||||
|
"-q quiet, don't show remote banner\n"
|
||||||
"-y Always accept remote host key if unknown\n"
|
"-y Always accept remote host key if unknown\n"
|
||||||
"-y -y Don't perform any remote host key checking (caution)\n"
|
"-y -y Don't perform any remote host key checking (caution)\n"
|
||||||
"-s Request a subsystem (use by external sftp)\n"
|
"-s Request a subsystem (use by external sftp)\n"
|
||||||
@ -141,6 +142,7 @@ void cli_getopts(int argc, char ** argv) {
|
|||||||
cli_opts.username = NULL;
|
cli_opts.username = NULL;
|
||||||
cli_opts.cmd = NULL;
|
cli_opts.cmd = NULL;
|
||||||
cli_opts.no_cmd = 0;
|
cli_opts.no_cmd = 0;
|
||||||
|
cli_opts.quiet = 0;
|
||||||
cli_opts.backgrounded = 0;
|
cli_opts.backgrounded = 0;
|
||||||
cli_opts.wantpty = 9; /* 9 means "it hasn't been touched", gets set later */
|
cli_opts.wantpty = 9; /* 9 means "it hasn't been touched", gets set later */
|
||||||
cli_opts.always_accept_key = 0;
|
cli_opts.always_accept_key = 0;
|
||||||
@ -214,6 +216,9 @@ void cli_getopts(int argc, char ** argv) {
|
|||||||
}
|
}
|
||||||
cli_opts.always_accept_key = 1;
|
cli_opts.always_accept_key = 1;
|
||||||
break;
|
break;
|
||||||
|
case 'q': /* quiet */
|
||||||
|
cli_opts.quiet = 1;
|
||||||
|
break;
|
||||||
case 'p': /* remoteport */
|
case 'p': /* remoteport */
|
||||||
next = (char**)&cli_opts.remoteport;
|
next = (char**)&cli_opts.remoteport;
|
||||||
break;
|
break;
|
||||||
@ -540,6 +545,12 @@ multihop_passthrough_args() {
|
|||||||
ret = m_malloc(len);
|
ret = m_malloc(len);
|
||||||
total = 0;
|
total = 0;
|
||||||
|
|
||||||
|
if (cli_opts.quiet)
|
||||||
|
{
|
||||||
|
int written = snprintf(ret+total, len-total, "-q ");
|
||||||
|
total += written;
|
||||||
|
}
|
||||||
|
|
||||||
if (cli_opts.no_hostkey_check)
|
if (cli_opts.no_hostkey_check)
|
||||||
{
|
{
|
||||||
int written = snprintf(ret+total, len-total, "-y -y ");
|
int written = snprintf(ret+total, len-total, "-y -y ");
|
||||||
|
@ -92,7 +92,6 @@ typedef struct svr_runopts {
|
|||||||
/* whether to print the MOTD */
|
/* whether to print the MOTD */
|
||||||
int domotd;
|
int domotd;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int norootlogin;
|
int norootlogin;
|
||||||
|
|
||||||
#ifdef HAVE_GETGROUPLIST
|
#ifdef HAVE_GETGROUPLIST
|
||||||
@ -155,6 +154,7 @@ typedef struct cli_runopts {
|
|||||||
int always_accept_key;
|
int always_accept_key;
|
||||||
int no_hostkey_check;
|
int no_hostkey_check;
|
||||||
int no_cmd;
|
int no_cmd;
|
||||||
|
int quiet;
|
||||||
int backgrounded;
|
int backgrounded;
|
||||||
int is_subsystem;
|
int is_subsystem;
|
||||||
#if DROPBEAR_CLI_PUBKEY_AUTH
|
#if DROPBEAR_CLI_PUBKEY_AUTH
|
||||||
|
@ -257,11 +257,15 @@ static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Content for SSH_PUBKEYINFO is optionally returned malloced in ret_info (will be
|
||||||
|
freed if already set */
|
||||||
static int checkpubkey_line(buffer* line, int line_num, const char* filename,
|
static int checkpubkey_line(buffer* line, int line_num, const char* filename,
|
||||||
const char* algo, unsigned int algolen,
|
const char* algo, unsigned int algolen,
|
||||||
const unsigned char* keyblob, unsigned int keybloblen) {
|
const unsigned char* keyblob, unsigned int keybloblen,
|
||||||
|
char ** ret_info) {
|
||||||
buffer *options_buf = NULL;
|
buffer *options_buf = NULL;
|
||||||
unsigned int pos, len;
|
char *info_str = NULL;
|
||||||
|
unsigned int pos, len, infopos, infolen;
|
||||||
int ret = DROPBEAR_FAILURE;
|
int ret = DROPBEAR_FAILURE;
|
||||||
|
|
||||||
if (line->len < MIN_AUTHKEYS_LINE || line->len > MAX_AUTHKEYS_LINE) {
|
if (line->len < MIN_AUTHKEYS_LINE || line->len > MAX_AUTHKEYS_LINE) {
|
||||||
@ -339,11 +343,36 @@ static int checkpubkey_line(buffer* line, int line_num, const char* filename,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* truncate the line at the space after the base64 data */
|
/* find the length of base64 data */
|
||||||
pos = line->pos;
|
pos = line->pos;
|
||||||
for (len = 0; line->pos < line->len; len++) {
|
for (len = 0; line->pos < line->len; len++) {
|
||||||
if (buf_getbyte(line) == ' ') break;
|
if (buf_getbyte(line) == ' ') {
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* find out the length of the public key info, stop at the first space */
|
||||||
|
infopos = line->pos;
|
||||||
|
for (infolen = 0; line->pos < line->len; infolen++) {
|
||||||
|
const char c = buf_getbyte(line);
|
||||||
|
if (c == ' ') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* We have an allowlist - authorized_keys lines can't be fully trusted,
|
||||||
|
some shell scripts may do unsafe things with env var values */
|
||||||
|
if (!(isalnum(c) || strchr(".,_-+@", c))) {
|
||||||
|
TRACE(("Not setting SSH_PUBKEYINFO, special characters"))
|
||||||
|
infolen = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (infolen > 0) {
|
||||||
|
info_str = m_malloc(infolen + 1);
|
||||||
|
buf_setpos(line, infopos);
|
||||||
|
strncpy(info_str, buf_getptr(line, infolen), infolen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* truncate to base64 data length */
|
||||||
buf_setpos(line, pos);
|
buf_setpos(line, pos);
|
||||||
buf_setlen(line, line->pos + len);
|
buf_setlen(line, line->pos + len);
|
||||||
|
|
||||||
@ -351,14 +380,30 @@ static int checkpubkey_line(buffer* line, int line_num, const char* filename,
|
|||||||
|
|
||||||
ret = cmp_base64_key(keyblob, keybloblen, (const unsigned char *) algo, algolen, line, NULL);
|
ret = cmp_base64_key(keyblob, keybloblen, (const unsigned char *) algo, algolen, line, NULL);
|
||||||
|
|
||||||
if (ret == DROPBEAR_SUCCESS && options_buf) {
|
/* free pubkey_info if it is filled */
|
||||||
ret = svr_add_pubkey_options(options_buf, line_num, filename);
|
if (ret_info && *ret_info) {
|
||||||
|
m_free(*ret_info);
|
||||||
|
*ret_info = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == DROPBEAR_SUCCESS) {
|
||||||
|
if (options_buf) {
|
||||||
|
ret = svr_add_pubkey_options(options_buf, line_num, filename);
|
||||||
|
}
|
||||||
|
if (ret_info) {
|
||||||
|
/* take the (optional) public key information */
|
||||||
|
*ret_info = info_str;
|
||||||
|
info_str = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (options_buf) {
|
if (options_buf) {
|
||||||
buf_free(options_buf);
|
buf_free(options_buf);
|
||||||
}
|
}
|
||||||
|
if (info_str) {
|
||||||
|
m_free(info_str);
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,7 +476,8 @@ static int checkpubkey(const char* keyalgo, unsigned int keyalgolen,
|
|||||||
}
|
}
|
||||||
line_num++;
|
line_num++;
|
||||||
|
|
||||||
ret = checkpubkey_line(line, line_num, filename, keyalgo, keyalgolen, keyblob, keybloblen);
|
ret = checkpubkey_line(line, line_num, filename, keyalgo, keyalgolen,
|
||||||
|
keyblob, keybloblen, &ses.authstate.pubkey_info);
|
||||||
if (ret == DROPBEAR_SUCCESS) {
|
if (ret == DROPBEAR_SUCCESS) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -548,7 +594,7 @@ static int checkfileperm(char * filename) {
|
|||||||
int fuzz_checkpubkey_line(buffer* line, int line_num, char* filename,
|
int fuzz_checkpubkey_line(buffer* line, int line_num, char* filename,
|
||||||
const char* algo, unsigned int algolen,
|
const char* algo, unsigned int algolen,
|
||||||
const unsigned char* keyblob, unsigned int keybloblen) {
|
const unsigned char* keyblob, unsigned int keybloblen) {
|
||||||
return checkpubkey_line(line, line_num, filename, algo, algolen, keyblob, keybloblen);
|
return checkpubkey_line(line, line_num, filename, algo, algolen, keyblob, keybloblen, NULL);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -115,6 +115,9 @@ void svr_pubkey_options_cleanup() {
|
|||||||
}
|
}
|
||||||
m_free(ses.authstate.pubkey_options);
|
m_free(ses.authstate.pubkey_options);
|
||||||
}
|
}
|
||||||
|
if (ses.authstate.pubkey_info) {
|
||||||
|
m_free(ses.authstate.pubkey_info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* helper for svr_add_pubkey_options. returns DROPBEAR_SUCCESS if the option is matched,
|
/* helper for svr_add_pubkey_options. returns DROPBEAR_SUCCESS if the option is matched,
|
||||||
|
@ -1030,6 +1030,9 @@ static void execchild(const void *user_data) {
|
|||||||
if (chansess->original_command) {
|
if (chansess->original_command) {
|
||||||
addnewvar("SSH_ORIGINAL_COMMAND", chansess->original_command);
|
addnewvar("SSH_ORIGINAL_COMMAND", chansess->original_command);
|
||||||
}
|
}
|
||||||
|
if (ses.authstate.pubkey_info != NULL) {
|
||||||
|
addnewvar("SSH_PUBKEYINFO", ses.authstate.pubkey_info);
|
||||||
|
}
|
||||||
|
|
||||||
/* change directory */
|
/* change directory */
|
||||||
if (chdir(ses.authstate.pw_dir) < 0) {
|
if (chdir(ses.authstate.pw_dir) < 0) {
|
||||||
|
@ -72,6 +72,10 @@ def own_venv_command():
|
|||||||
return f"source {venv}/bin/activate"
|
return f"source {venv}/bin/activate"
|
||||||
|
|
||||||
class HandleTcp(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
class HandleTcp(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||||
|
|
||||||
|
# override TCPServer's default, avoids TIME_WAIT
|
||||||
|
allow_reuse_addr = True
|
||||||
|
|
||||||
""" Listens for a single incoming request, sends a response if given,
|
""" Listens for a single incoming request, sends a response if given,
|
||||||
and returns the inbound data.
|
and returns the inbound data.
|
||||||
Reponse can be a queue object, in which case each item in the queue will
|
Reponse can be a queue object, in which case each item in the queue will
|
||||||
|
30
test/test_svrauth.py
Normal file
30
test/test_svrauth.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from test_dropbear import *
|
||||||
|
import signal
|
||||||
|
import queue
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Tests for server side authentication
|
||||||
|
|
||||||
|
# Requires keyfile and authorized_keys set up in github action build.yml
|
||||||
|
@pytest.mark.skipif('DBTEST_IN_ACTION' not in os.environ, reason="DBTEST_IN_ACTION not set")
|
||||||
|
def test_pubkeyinfo(request, dropbear):
|
||||||
|
kf = str(Path.home() / ".ssh/id_dropbear_key2")
|
||||||
|
r = dbclient(request, "-i", kf, "echo -n $SSH_PUBKEYINFO", capture_output=True)
|
||||||
|
# stop at first space
|
||||||
|
assert r.stdout.decode() == "key2"
|
||||||
|
|
||||||
|
@pytest.mark.skipif('DBTEST_IN_ACTION' not in os.environ, reason="DBTEST_IN_ACTION not set")
|
||||||
|
def test_pubkeyinfo_special(request, dropbear):
|
||||||
|
kf = str(Path.home() / ".ssh/id_dropbear_key3")
|
||||||
|
r = dbclient(request, "-i", kf, "echo -n $SSH_PUBKEYINFO", capture_output=True)
|
||||||
|
# comment contains special characters so the SSH_PUBKEYINFO should not be set
|
||||||
|
assert r.stdout.decode() == ""
|
||||||
|
|
||||||
|
@pytest.mark.skipif('DBTEST_IN_ACTION' not in os.environ, reason="DBTEST_IN_ACTION not set")
|
||||||
|
def test_pubkeyinfo_okchar(request, dropbear):
|
||||||
|
kf = str(Path.home() / ".ssh/id_dropbear_key4")
|
||||||
|
r = dbclient(request, "-i", kf, "echo -n $SSH_PUBKEYINFO", capture_output=True)
|
||||||
|
# comment contains special characters so the SSH_PUBKEYINFO should not be set
|
||||||
|
assert r.stdout.decode() == "key4,char"
|
Loading…
Reference in New Issue
Block a user