mirror of
https://github.com/upa/mscp.git
synced 2026-02-04 11:34:44 +08:00
initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build
|
||||
10
CMakeLists.txt
Normal file
10
CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
project(sscp C)
|
||||
|
||||
set(CMAKE_C_FLAGS "-Wall -g")
|
||||
|
||||
add_executable(sscp src/main.c src/platform.c src/ssh.c)
|
||||
target_include_directories(sscp PUBLIC ./src /usr/local/include)
|
||||
target_link_directories(sscp PUBLIC /usr/local/lib)
|
||||
target_link_libraries(sscp ssh)
|
||||
28
src/file.h
Normal file
28
src/file.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef _FILE_H_
|
||||
#define _FILE_H_
|
||||
|
||||
struct path {
|
||||
char *path;
|
||||
bool remote;
|
||||
};
|
||||
|
||||
struct file {
|
||||
struct path src; /* copy source */
|
||||
struct path dst; /* copy desitnation */
|
||||
size_t size; /* size of this file */
|
||||
};
|
||||
|
||||
struct chunk {
|
||||
struct file *f;
|
||||
size_t off; /* offset of this chunk on the file f */
|
||||
size_t len; /* length of this chunk */
|
||||
};
|
||||
|
||||
struct file *file_expand(char **src_array, char *dst)
|
||||
{
|
||||
/* return array of files expanded from sources and dst */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#endif /* _FILE_H_ */
|
||||
135
src/main.c
Normal file
135
src/main.c
Normal file
@@ -0,0 +1,135 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <util.h>
|
||||
#include <ssh.h>
|
||||
#include <file.h>
|
||||
#include <platform.h>
|
||||
|
||||
|
||||
#define DEFAULT_MIN_CHUNK_SZ (2 << 20) /* 2MB */
|
||||
|
||||
void usage(bool print_help) {
|
||||
printf("sscp: super scp, copy files over multiple ssh connections\n"
|
||||
"\n"
|
||||
"Usage: sscp [rvC] [-n max_conns] [-s min_chunk_sz] [-S max_chunk_sz]\n"
|
||||
" [-l login_name] [-p port] [-i identity_file]\n"
|
||||
" [-c cipher_spec] source ... target\n"
|
||||
"\n");
|
||||
|
||||
if (!print_help)
|
||||
return;
|
||||
|
||||
printf(" -r copy directory recusrively\n"
|
||||
" -n NR_CONNECTIONS max number of connections (default: # of cpu cores)\n"
|
||||
" -s CHUNKSIZE min chunk size (default: 2MB)\n"
|
||||
" -S CHUNKSIZE max chunk size (default: filesize / nr_conn)\n"
|
||||
"\n"
|
||||
" -l LOGIN_NAME login name\n"
|
||||
" -p PORT port number\n"
|
||||
" -i IDENTITY identity to be used for ssh\n"
|
||||
" -c CIPHER cipher spec, see `ssh -Q cipher`\n"
|
||||
" -C enable compression on libssh\n"
|
||||
" -v increment output level\n"
|
||||
" -h print this help\n"
|
||||
"\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct ssh_opts opts;
|
||||
int nr_conn = nr_cpus();
|
||||
bool recursive = false;
|
||||
int min_chunk_sz = DEFAULT_MIN_CHUNK_SZ;
|
||||
int max_chunk_sz = 0;
|
||||
char ch;
|
||||
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
|
||||
while ((ch = getopt(argc, argv, "r:n:s:S:l:p:i:c:Cvh")) != -1) {
|
||||
switch (ch) {
|
||||
case 'r':
|
||||
recursive = true;
|
||||
break;
|
||||
case 'n':
|
||||
nr_conn = atoi(optarg);
|
||||
if (nr_conn < 1) {
|
||||
pr_err("invalid number of connections: %s\n", optarg);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
min_chunk_sz = atoi(optarg);
|
||||
if (min_chunk_sz < getpagesize()) {
|
||||
pr_err("min chunk size must be "
|
||||
"larger than or equal to %d: %s\n",
|
||||
getpagesize(), optarg);
|
||||
return 1;
|
||||
}
|
||||
if (min_chunk_sz % getpagesize() != 0) {
|
||||
pr_err("min chunk size must be "
|
||||
"multiple of page size %d: %s\n",
|
||||
getpagesize(), optarg);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case 'S':
|
||||
max_chunk_sz = atoi(optarg);
|
||||
if (max_chunk_sz < getpagesize()) {
|
||||
pr_err("max chunk size must be "
|
||||
"larger than or equal to %d: %s\n",
|
||||
getpagesize(), optarg);
|
||||
return 1;
|
||||
}
|
||||
if (max_chunk_sz % getpagesize() != 0) {
|
||||
pr_err("max chunk size must be "
|
||||
"multiple of page size %d: %s\n",
|
||||
getpagesize(), optarg);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
opts.login_name = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
opts.port = optarg;
|
||||
break;
|
||||
case 'i':
|
||||
opts.identity = optarg;
|
||||
break;
|
||||
case 'c':
|
||||
opts.cipher = optarg;
|
||||
break;
|
||||
case 'C':
|
||||
opts.compress++;
|
||||
break;
|
||||
case 'v':
|
||||
opts.debuglevel++;
|
||||
break;
|
||||
case 'h':
|
||||
usage(true);
|
||||
return 1;
|
||||
default:
|
||||
usage(false);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (max_chunk_sz > 0 && min_chunk_sz > max_chunk_sz) {
|
||||
pr_err("smaller max chunk size than min chunk size: %d < %d\n",
|
||||
max_chunk_sz, min_chunk_sz);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("opts.port %s\n", opts.port);
|
||||
|
||||
int n;
|
||||
for (n = 0; n < argc; n++) {
|
||||
printf("%d %s\n", n, argv[n]);
|
||||
}
|
||||
printf("optind %d", optind);
|
||||
|
||||
return 0;
|
||||
}
|
||||
27
src/platform.c
Normal file
27
src/platform.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <util.h>
|
||||
#include <platform.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#elif linux
|
||||
#else
|
||||
#error unsupported platform
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __APPLE__
|
||||
int nr_cpus()
|
||||
{
|
||||
int n;
|
||||
size_t size = sizeof(n);
|
||||
|
||||
if (sysctlbyname("machdep.cpu.core_count", &n, &size, NULL, 0) != 0) {
|
||||
pr_err("failed to get number of cpu cores: %s\n", strerrno());
|
||||
return -1;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
#endif
|
||||
|
||||
6
src/platform.h
Normal file
6
src/platform.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef _PLATFORM_H_
|
||||
#define _PLATFORM_H_
|
||||
|
||||
int nr_cpus();
|
||||
|
||||
#endif /* _PLATFORM_H_ */
|
||||
237
src/ssh.c
Normal file
237
src/ssh.c
Normal file
@@ -0,0 +1,237 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ssh.h>
|
||||
#include <util.h>
|
||||
|
||||
static int ssh_verify_known_hosts(ssh_session session);
|
||||
|
||||
|
||||
static int ssh_set_opts(ssh_session ssh, struct ssh_opts *opts)
|
||||
{
|
||||
ssh_set_log_level(opts->debuglevel);
|
||||
|
||||
if (opts->login_name &&
|
||||
ssh_options_set(ssh, SSH_OPTIONS_USER, opts->login_name) < 0) {
|
||||
pr_err("failed to set login name\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opts->port &&
|
||||
ssh_options_set(ssh, SSH_OPTIONS_PORT_STR, opts->port) < 0) {
|
||||
pr_err("failed to set port number\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opts->identity &&
|
||||
ssh_options_set(ssh, SSH_OPTIONS_IDENTITY, opts->identity) < 0) {
|
||||
pr_err("failed to set identity\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opts->cipher) {
|
||||
if (ssh_options_set(ssh, SSH_OPTIONS_CIPHERS_C_S, opts->cipher) < 0) {
|
||||
pr_err("failed to set cipher client to server\n");
|
||||
return -1;
|
||||
}
|
||||
if (ssh_options_set(ssh, SSH_OPTIONS_CIPHERS_S_C, opts->cipher) < 0) {
|
||||
pr_err("failed to set cipher client to server\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->compress &&
|
||||
ssh_options_set(ssh, SSH_OPTIONS_COMPRESSION, "yes") < 0) {
|
||||
pr_err("failed to enable ssh compression\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssh_authenticate(ssh_session ssh, struct ssh_opts *opts)
|
||||
{
|
||||
int auth_bit_mask;
|
||||
int ret;
|
||||
|
||||
/* none method */
|
||||
ret = ssh_userauth_none(ssh, NULL);
|
||||
if (ret == SSH_AUTH_SUCCESS)
|
||||
return 0;
|
||||
|
||||
auth_bit_mask = ssh_userauth_list(ssh, NULL);
|
||||
|
||||
if (auth_bit_mask & SSH_AUTH_METHOD_NONE &&
|
||||
ssh_userauth_none(ssh, NULL) == SSH_AUTH_SUCCESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (auth_bit_mask & SSH_AUTH_METHOD_PUBLICKEY &&
|
||||
ssh_userauth_publickey_auto(ssh, NULL, NULL) == SSH_AUTH_SUCCESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (auth_bit_mask & SSH_AUTH_METHOD_PASSWORD) {
|
||||
if (!opts->password) {
|
||||
opts->password = getpass("Password: ");
|
||||
}
|
||||
if (ssh_userauth_password(ssh, NULL, opts->password) == SSH_AUTH_SUCCESS)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pr_err("authentication failure: %s\n", ssh_get_error(ssh));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static ssh_session ssh_make_ssh_session(char *sshdst, struct ssh_opts *opts)
|
||||
{
|
||||
ssh_session ssh = ssh_new();
|
||||
|
||||
if (ssh_set_opts(ssh, opts) != 0)
|
||||
goto free_out;
|
||||
|
||||
if (ssh_options_set(ssh, SSH_OPTIONS_HOST, sshdst) != SSH_OK) {
|
||||
pr_err("failed to set destination host\n");
|
||||
goto free_out;
|
||||
}
|
||||
|
||||
if (ssh_connect(ssh) != SSH_OK) {
|
||||
pr_err("failed to connect ssh server: %s\n", ssh_get_error(ssh));
|
||||
goto free_out;
|
||||
}
|
||||
|
||||
if (ssh_authenticate(ssh, opts) != 0) {
|
||||
pr_err("authentication failed: %s\n", ssh_get_error(ssh));
|
||||
goto disconnect_out;
|
||||
}
|
||||
|
||||
if (ssh_verify_known_hosts(ssh) != 0) {
|
||||
goto disconnect_out;
|
||||
}
|
||||
|
||||
return ssh;
|
||||
|
||||
disconnect_out:
|
||||
ssh_disconnect(ssh);
|
||||
free_out:
|
||||
ssh_free(ssh);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sftp_session ssh_make_sftp_session(char *sshdst, struct ssh_opts *opts)
|
||||
{
|
||||
sftp_session sftp;
|
||||
ssh_session ssh = ssh_make_ssh_session(sshdst, opts);
|
||||
|
||||
if (!ssh) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sftp = sftp_new(ssh);
|
||||
if (!sftp) {
|
||||
pr_err("failed to allocate sftp session: %s\n", ssh_get_error(ssh));
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
if (sftp_init(sftp) != SSH_OK) {
|
||||
pr_err("failed to initialize sftp session: err code %d\n",
|
||||
sftp_get_error(sftp));
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
return sftp;
|
||||
err_out:
|
||||
ssh_disconnect(ssh);
|
||||
ssh_free(ssh);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* copied from https://api.libssh.org/stable/libssh_tutor_guided_tour.html*/
|
||||
static int ssh_verify_known_hosts(ssh_session session)
|
||||
{
|
||||
enum ssh_known_hosts_e state;
|
||||
unsigned char *hash = NULL;
|
||||
ssh_key srv_pubkey = NULL;
|
||||
size_t hlen;
|
||||
char buf[10];
|
||||
char *hexa;
|
||||
char *p;
|
||||
int cmp;
|
||||
int rc;
|
||||
|
||||
rc = ssh_get_server_publickey(session, &srv_pubkey);
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = ssh_get_publickey_hash(srv_pubkey,
|
||||
SSH_PUBLICKEY_HASH_SHA1,
|
||||
&hash,
|
||||
&hlen);
|
||||
ssh_key_free(srv_pubkey);
|
||||
if (rc < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
state = ssh_session_is_known_server(session);
|
||||
switch (state) {
|
||||
case SSH_KNOWN_HOSTS_OK:
|
||||
/* OK */
|
||||
|
||||
break;
|
||||
case SSH_KNOWN_HOSTS_CHANGED:
|
||||
fprintf(stderr, "Host key for server changed: it is now:\n");
|
||||
//ssh_print_hexa("Public key hash", hash, hlen);
|
||||
fprintf(stderr, "For security reasons, connection will be stopped\n");
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
|
||||
return -1;
|
||||
case SSH_KNOWN_HOSTS_OTHER:
|
||||
fprintf(stderr, "The host key for this server was not found but an other"
|
||||
"type of key exists.\n");
|
||||
fprintf(stderr, "An attacker might change the default server key to"
|
||||
"confuse your client into thinking the key does not exist\n");
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
|
||||
return -1;
|
||||
case SSH_KNOWN_HOSTS_NOT_FOUND:
|
||||
fprintf(stderr, "Could not find known host file.\n");
|
||||
fprintf(stderr, "If you accept the host key here, the file will be"
|
||||
"automatically created.\n");
|
||||
|
||||
/* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */
|
||||
|
||||
case SSH_KNOWN_HOSTS_UNKNOWN:
|
||||
hexa = ssh_get_hexa(hash, hlen);
|
||||
fprintf(stderr,"The server is unknown. Do you trust the host key?\n");
|
||||
fprintf(stderr, "Public key hash: %s\n", hexa);
|
||||
ssh_string_free_char(hexa);
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
p = fgets(buf, sizeof(buf), stdin);
|
||||
if (p == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmp = strncasecmp(buf, "yes", 3);
|
||||
if (cmp != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = ssh_session_update_known_hosts(session);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Error %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
break;
|
||||
case SSH_KNOWN_HOSTS_ERROR:
|
||||
fprintf(stderr, "Error %s", ssh_get_error(session));
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssh_clean_pubkey_hash(&hash);
|
||||
return 0;
|
||||
}
|
||||
24
src/ssh.h
Normal file
24
src/ssh.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef _SSH_H_
|
||||
#define _SSH_H_
|
||||
|
||||
#include <libssh/libssh.h>
|
||||
#include <libssh/sftp.h>
|
||||
|
||||
|
||||
struct ssh_opts {
|
||||
char *login_name; /* -l */
|
||||
char *port; /* -p */
|
||||
char *identity; /* -i */
|
||||
char *cipher; /* -c */
|
||||
int compress; /* -C */
|
||||
int debuglevel; /* -v */
|
||||
|
||||
char *password; /* filled at the first connecting phase */
|
||||
};
|
||||
|
||||
/* ssh_make_sftp_session() creates sftp_session. sshdst accpets
|
||||
* user@hostname and hostname notations (by libssh).
|
||||
*/
|
||||
sftp_session ssh_make_sftp_session(char *sshdst, struct ssh_opts *opts);
|
||||
|
||||
#endif /* _SSH_H_ */
|
||||
47
src/util.h
Normal file
47
src/util.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef _UTIL_H_
|
||||
#define _UTIL_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
#define pr_v(level, fmt, ...) do { \
|
||||
if (verbose >= level) { \
|
||||
fprintf(stdout, "\x1b[1m\x1b[34m" \
|
||||
"%s(): \x1b[0m" fmt, \
|
||||
__func__, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define pr_v1(fmt, ...) pr_v(1, fmt, ##__VA_ARGS__)
|
||||
#define pr_v2(fmt, ...) pr_v(2, fmt, ##__VA_ARGS__)
|
||||
#define pr_v3(fmt, ...) pr_v(3, fmt, ##__VA_ARGS__)
|
||||
|
||||
|
||||
#define pr_info(fmt, ...) fprintf(stdout, "%s(): " fmt, \
|
||||
__func__, ##__VA_ARGS__)
|
||||
|
||||
#define pr_warn(fmt, ...) fprintf(stderr, "\x1b[1m\x1b[33m" \
|
||||
"WARN:%s(): " fmt "\x1b[0m", \
|
||||
__func__, ##__VA_ARGS__)
|
||||
|
||||
#define pr_err(fmt, ...) fprintf(stderr, "\x1b[1m\x1b[31m" \
|
||||
"ERR:%s(): " fmt "\x1b[0m", \
|
||||
__func__, ##__VA_ARGS__)
|
||||
|
||||
#define pr_debug(fmt, ...) \
|
||||
do { \
|
||||
if (unlikely(debug)) { \
|
||||
fprintf(stderr, "\x1b[1m\x1b[33m" \
|
||||
"DEBUG:%s(): " fmt "\x1b[0m", \
|
||||
__func__, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
#define strerrno() strerror(errno)
|
||||
|
||||
#endif /* _UTIL_H_ */
|
||||
Reference in New Issue
Block a user