summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRyo Nakamura <upa@haeena.net>2022-10-15 21:59:25 +0900
committerRyo Nakamura <upa@haeena.net>2022-10-15 21:59:25 +0900
commit303a9eb974f884b5f9f7e14fdd83a821f21e32e6 (patch)
tree91c535639e39e7f204db7a194854f4865dfff184 /src
initial commit
Diffstat (limited to 'src')
-rw-r--r--src/file.h28
-rw-r--r--src/main.c135
-rw-r--r--src/platform.c27
-rw-r--r--src/platform.h6
-rw-r--r--src/ssh.c237
-rw-r--r--src/ssh.h24
-rw-r--r--src/util.h47
7 files changed, 504 insertions, 0 deletions
diff --git a/src/file.h b/src/file.h
new file mode 100644
index 0000000..7c5168d
--- /dev/null
+++ b/src/file.h
@@ -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_ */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..bec9a00
--- /dev/null
+++ b/src/main.c
@@ -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;
+}
diff --git a/src/platform.c b/src/platform.c
new file mode 100644
index 0000000..5e0573e
--- /dev/null
+++ b/src/platform.c
@@ -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
+
diff --git a/src/platform.h b/src/platform.h
new file mode 100644
index 0000000..b93142b
--- /dev/null
+++ b/src/platform.h
@@ -0,0 +1,6 @@
+#ifndef _PLATFORM_H_
+#define _PLATFORM_H_
+
+int nr_cpus();
+
+#endif /* _PLATFORM_H_ */
diff --git a/src/ssh.c b/src/ssh.c
new file mode 100644
index 0000000..6643b8e
--- /dev/null
+++ b/src/ssh.c
@@ -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;
+}
diff --git a/src/ssh.h b/src/ssh.h
new file mode 100644
index 0000000..3397fd7
--- /dev/null
+++ b/src/ssh.h
@@ -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_ */
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..848827d
--- /dev/null
+++ b/src/util.h
@@ -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_ */