From 8f888fb1a3469b87e557efad93b293dd36288ba9 Mon Sep 17 00:00:00 2001 From: Sunil Nimmagadda Date: Sat, 14 Sep 2024 12:32:01 +0530 Subject: A HTTP(S), FTP client --- cmd.c | 637 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 637 insertions(+) create mode 100644 cmd.c (limited to 'cmd.c') diff --git a/cmd.c b/cmd.c new file mode 100644 index 0000000..faf7949 --- /dev/null +++ b/cmd.c @@ -0,0 +1,637 @@ +/* + * Copyright (c) 2018 Sunil Nimmagadda + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ftp.h" + +#define ARGVMAX 64 + +static void cmd_interrupt(int); +static int cmd_lookup(const char *); +static FILE *data_fopen(const char *); +static void do_open(int, char **); +static void do_help(int, char **); +static void do_quit(int, char **); +static void do_ls(int, char **); +static void do_pwd(int, char **); +static void do_cd(int, char **); +static void do_get(int, char **); +static void do_passive(int, char **); +static void do_lcd(int, char **); +static void do_lpwd(int, char **); +static void do_put(int, char **); +static void do_mget(int, char **); +static void ftp_abort(void); +static char *prompt(void); + +static FILE *ctrl_fp, *data_fp; + +static struct { + const char *name; + const char *info; + void (*cmd)(int, char **); + int conn_required; +} cmd_tbl[] = { + { "open", "connect to remote ftp server", do_open, 0 }, + { "close", "terminate ftp session", do_quit, 1 }, + { "help", "print local help information", do_help, 0 }, + { "?", "print local help information", do_help, 0 }, + { "quit", "terminate ftp session and exit", do_quit, 0 }, + { "exit", "terminate ftp session and exit", do_quit, 0 }, + { "ls", "list contents of remote directory", do_ls, 1 }, + { "pwd", "print working directory on remote machine", do_pwd, 1 }, + { "cd", "change remote working directory", do_cd, 1 }, + { "nlist", "nlist contents of remote directory", do_ls, 1 }, + { "get", "receive file", do_get, 1 }, + { "passive", "toggle passive transfer mode", do_passive, 0 }, + { "lcd", "change local working directory", do_lcd, 0 }, + { "lpwd", "print local working directory", do_lpwd, 0 }, + { "put", "send one file", do_put, 1 }, + { "mget", "get multiple files", do_mget, 1 }, + { "mput", "send multiple files", do_mget, 1 }, +}; + +static void +cmd_interrupt(int signo) +{ + const char msg[] = "\rwaiting for remote to finish abort\n"; + int save_errno = errno; + + if (data_fp != NULL) + (void)write(STDERR_FILENO, msg, sizeof(msg) - 1); + + interrupted = 1; + errno = save_errno; +} + +void +cmd(const char *host, const char *port, const char *path) +{ + HistEvent hev; + EditLine *el; + History *hist; + const char *line; + char **ap, *argv[ARGVMAX], *cp; + int count, i; + + if ((el = el_init(getprogname(), stdin, stdout, stderr)) == NULL) + err(1, "couldn't initialise editline"); + + if ((hist = history_init()) == NULL) + err(1, "couldn't initialise editline history"); + + history(hist, &hev, H_SETSIZE, 100); + el_set(el, EL_HIST, history, hist); + el_set(el, EL_PROMPT, prompt); + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_TERMINAL, NULL); + el_set(el, EL_SIGNAL, 1); + el_source(el, NULL); + + if (host != NULL) { + argv[0] = "open"; + argv[1] = (char *)host; + argv[2] = port ? (char *)port : "21"; + do_open(3, argv); + /* If we don't have a connection, exit */ + if (ctrl_fp == NULL) + exit(1); + + if (path != NULL) { + argv[0] = "cd"; + argv[1] = (char *)path; + do_cd(2, argv); + } + } + + for (;;) { + signal(SIGINT, SIG_IGN); + if ((line = el_gets(el, &count)) == NULL || count <= 0) { + if (verbose) + fprintf(stderr, "\n"); + argv[0] = "quit"; + do_quit(1, argv); + break; + } + + if (count <= 1) + continue; + + if ((cp = strrchr(line, '\n')) != NULL) + *cp = '\0'; + + history(hist, &hev, H_ENTER, line); + for (ap = argv; ap < &argv[ARGVMAX - 1] && + (*ap = strsep((char **)&line, " \t")) != NULL;) { + if (**ap != '\0') + ap++; + } + *ap = NULL; + + if (argv[0] == NULL) + continue; + + if ((i = cmd_lookup(argv[0])) == -1) { + fprintf(stderr, "Invalid command.\n"); + continue; + } + + if (cmd_tbl[i].conn_required && ctrl_fp == NULL) { + fprintf(stderr, "Not connected.\n"); + continue; + } + + interrupted = 0; + signal(SIGINT, cmd_interrupt); + cmd_tbl[i].cmd(ap - argv, argv); + + if (strcmp(cmd_tbl[i].name, "quit") == 0 || + strcmp(cmd_tbl[i].name, "exit") == 0) + break; + } + + el_end(el); +} + +static int +cmd_lookup(const char *cmd) +{ + size_t i; + + for (i = 0; i < nitems(cmd_tbl); i++) + if (strcmp(cmd, cmd_tbl[i].name) == 0) + return i; + + return -1; +} + +static char * +prompt(void) +{ + return "ftp> "; +} + +static FILE * +data_fopen(const char *mode) +{ + int fd; + + fd = activemode ? ftp_eprt(ctrl_fp) : ftp_epsv(ctrl_fp); + if (fd == -1) { + if (io_debug) + fprintf(stderr, "Failed to open data connection"); + + return NULL; + } + + return fdopen(fd, mode); +} + +static void +ftp_abort(void) +{ + char buf[BUFSIZ]; + + snprintf(buf, sizeof buf, "%c%c%c", IAC, IP, IAC); + if (send(fileno(ctrl_fp), buf, 3, MSG_OOB) != 3) + warn("abort"); + + ftp_command(ctrl_fp, "%cABOR", DM); +} + +static void +do_open(int argc, char **argv) +{ + const char *host = NULL, *port = "21"; + char *buf = NULL; + size_t n = 0; + int sock; + + if (ctrl_fp != NULL) { + fprintf(stderr, "already connected, use close first.\n"); + return; + } + + switch (argc) { + case 3: + port = argv[2]; + /* FALLTHROUGH */ + case 2: + host = argv[1]; + break; + default: + fprintf(stderr, "usage: open host [port]\n"); + return; + } + + if ((sock = tcp_connect(host, port, 0)) == -1) + return; + + fprintf(stderr, "Connected to %s.\n", host); + if ((ctrl_fp = fdopen(sock, "r+")) == NULL) + err(1, "%s: fdopen", __func__); + + /* greeting */ + ftp_getline(&buf, &n, 0, ctrl_fp); + free(buf); + if (ftp_auth(ctrl_fp, NULL, NULL) != P_OK) { + fclose(ctrl_fp); + ctrl_fp = NULL; + } +} + +static void +do_help(int argc, char **argv) +{ + size_t i; + int j; + + if (argc == 1) { + for (i = 0; i < nitems(cmd_tbl); i++) + fprintf(stderr, "%s\n", cmd_tbl[i].name); + + return; + } + + for (i = 1; i < (size_t)argc; i++) { + if ((j = cmd_lookup(argv[i])) == -1) + fprintf(stderr, "invalid help command %s\n", argv[i]); + else + fprintf(stderr, "%s\t%s\n", argv[i], cmd_tbl[j].info); + } +} + +static void +do_quit(int argc, char **argv) +{ + if (ctrl_fp == NULL) + return; + + ftp_command(ctrl_fp, "QUIT"); + fclose(ctrl_fp); + ctrl_fp = NULL; +} + +static void +do_ls(int argc, char **argv) +{ + FILE *dst_fp = stdout; + const char *cmd, *local_fname = NULL, *remote_dir = NULL; + char *buf = NULL; + size_t n = 0; + ssize_t len; + int r; + + switch (argc) { + case 3: + if (strcmp(argv[2], "-") != 0) + local_fname = argv[2]; + /* FALLTHROUGH */ + case 2: + remote_dir = argv[1]; + /* FALLTHROUGH */ + case 1: + break; + default: + fprintf(stderr, "usage: ls [remote-directory [local-file]]\n"); + return; + } + + if ((data_fp = data_fopen("r")) == NULL) + return; + + if (local_fname && (dst_fp = fopen(local_fname, "w")) == NULL) { + warn("fopen %s", local_fname); + fclose(data_fp); + data_fp = NULL; + return; + } + + cmd = (strcmp(argv[0], "ls") == 0) ? "LIST" : "NLST"; + if (remote_dir != NULL) + r = ftp_command(ctrl_fp, "%s %s", cmd, remote_dir); + else + r = ftp_command(ctrl_fp, "%s", cmd); + + if (r != P_PRE) { + fclose(data_fp); + data_fp = NULL; + if (dst_fp != stdout) + fclose(dst_fp); + + return; + } + + while ((len = getline(&buf, &n, data_fp)) != -1 && !interrupted) { + buf[len - 1] = '\0'; + if (len >= 2 && buf[len - 2] == '\r') + buf[len - 2] = '\0'; + + fprintf(dst_fp, "%s\n", buf); + } + + if (interrupted) + ftp_abort(); + + fclose(data_fp); + data_fp = NULL; + ftp_getline(&buf, &n, 0, ctrl_fp); + free(buf); + if (dst_fp != stdout) + fclose(dst_fp); +} + +static void +do_get(int argc, char **argv) +{ + FILE *dst_fp; + const char *local_fname = NULL, *p, *remote_fname; + char *buf = NULL; + size_t n = 0; + off_t file_sz, offset = 0; + + switch (argc) { + case 3: + local_fname = argv[2]; + /* FALLTHROUGH */ + case 2: + remote_fname = argv[1]; + break; + default: + fprintf(stderr, "usage: get remote-file [local-file]\n"); + return; + } + + if (local_fname == NULL) + local_fname = remote_fname; + + if (ftp_command(ctrl_fp, "TYPE I") != P_OK) + return; + + log_info("local: %s remote: %s\n", local_fname, remote_fname); + if (ftp_size(ctrl_fp, remote_fname, &file_sz, &buf) != P_OK) { + fprintf(stderr, "%s", buf); + return; + } + + if ((data_fp = data_fopen("r")) == NULL) + return; + + if ((dst_fp = fopen(local_fname, "w")) == NULL) { + warn("%s", local_fname); + fclose(data_fp); + data_fp = NULL; + return; + } + + if (ftp_command(ctrl_fp, "RETR %s", remote_fname) != P_PRE) { + fclose(data_fp); + data_fp = NULL; + fclose(dst_fp); + return; + } + + if (progressmeter) { + p = basename(remote_fname); + start_progress_meter(p, NULL, file_sz, &offset); + } + + copy_file(dst_fp, data_fp, &offset); + if (progressmeter) + stop_progress_meter(); + + if (interrupted) + ftp_abort(); + + fclose(data_fp); + data_fp = NULL; + fclose(dst_fp); + ftp_getline(&buf, &n, 0, ctrl_fp); + free(buf); +} + +static void +do_pwd(int argc, char **argv) +{ + ftp_command(ctrl_fp, "PWD"); +} + +static void +do_cd(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "usage: cd remote-directory\n"); + return; + } + + ftp_command(ctrl_fp, "CWD %s", argv[1]); +} + +static void +do_passive(int argc, char **argv) +{ + switch (argc) { + case 1: + break; + case 2: + if (strcmp(argv[1], "on") == 0 || strcmp(argv[1], "off") == 0) + break; + + /* FALLTHROUGH */ + default: + fprintf(stderr, "usage: passive [on | off]\n"); + return; + } + + if (argv[1] != NULL) { + activemode = (strcmp(argv[1], "off") == 0) ? 1 : 0; + fprintf(stderr, "passive mode is %s\n", argv[1]); + return; + } + + activemode = !activemode; + fprintf(stderr, "passive mode is %s\n", activemode ? "off" : "on"); +} + +static void +do_lcd(int argc, char **argv) +{ + struct passwd *pw = NULL; + const char *dir, *login; + char cwd[PATH_MAX]; + + switch (argc) { + case 1: + case 2: + break; + default: + fprintf(stderr, "usage: lcd [local-directory]\n"); + return; + } + + if ((login = getlogin()) != NULL) + pw = getpwnam(login); + + if (pw == NULL && (pw = getpwuid(getuid())) == NULL) { + fprintf(stderr, "Failed to get home directory\n"); + return; + } + + dir = argv[1] ? argv[1] : pw->pw_dir; + if (chdir(dir) != 0) { + warn("local: %s", dir); + return; + } + + if (getcwd(cwd, sizeof cwd) == NULL) { + warn("getcwd"); + return; + } + + fprintf(stderr, "Local directory now %s\n", cwd); +} + +static void +do_lpwd(int argc, char **argv) +{ + char cwd[PATH_MAX]; + + if (getcwd(cwd, sizeof cwd) == NULL) { + warn("getcwd"); + return; + } + + fprintf(stderr, "Local directory %s\n", cwd); +} + +static void +do_put(int argc, char **argv) +{ + struct stat sb; + FILE *src_fp; + const char *local_fname, *p, *remote_fname = NULL; + char *buf = NULL; + size_t n = 0; + off_t file_sz, offset = 0; + + switch (argc) { + case 3: + remote_fname = argv[2]; + /* FALLTHROUGH */ + case 2: + local_fname = argv[1]; + break; + default: + fprintf(stderr, "usage: put local-file [remote-file]\n"); + return; + } + + if (remote_fname == NULL) + remote_fname = local_fname; + + if (ftp_command(ctrl_fp, "TYPE I") != P_OK) + return; + + log_info("local: %s remote: %s\n", local_fname, remote_fname); + if ((data_fp = data_fopen("w")) == NULL) + return; + + if ((src_fp = fopen(local_fname, "r")) == NULL) { + warn("%s", local_fname); + fclose(data_fp); + data_fp = NULL; + return; + } + + if (fstat(fileno(src_fp), &sb) != 0) { + warn("%s", local_fname); + fclose(data_fp); + data_fp = NULL; + fclose(src_fp); + return; + } + file_sz = sb.st_size; + + if (ftp_command(ctrl_fp, "STOR %s", remote_fname) != P_PRE) { + fclose(data_fp); + data_fp = NULL; + fclose(src_fp); + return; + } + + if (progressmeter) { + p = basename(remote_fname); + start_progress_meter(p, NULL, file_sz, &offset); + } + + copy_file(data_fp, src_fp, &offset); + if (progressmeter) + stop_progress_meter(); + + if (interrupted) + ftp_abort(); + + fclose(data_fp); + data_fp = NULL; + fclose(src_fp); + ftp_getline(&buf, &n, 0, ctrl_fp); + free(buf); +} + +static void +do_mget(int argc, char **argv) +{ + void (*fn)(int, char **); + const char *usage; + char *args[2]; + int i; + + if (strcmp(argv[0], "mget") == 0) { + fn = do_get; + args[0] = "get"; + usage = "mget remote-files"; + } else { + fn = do_put; + args[0] = "put"; + usage = "mput local-files"; + } + + if (argc == 1) { + fprintf(stderr, "usage: %s\n", usage); + return; + } + + for (i = 1; i < argc && !interrupted; i++) { + args[1] = argv[i]; + fn(2, args); + } +} -- cgit v1.2.3