/* pfs-ftp.c: * **************************************************************** * Copyright (C) 2002, 2003 Scott Parish * Copyright (C) 2003 Tom Lord * * See the file "COPYING" for further information about * the copyright and warranty status of this work. */ #include #include #include #include "config-options.h" #include "hackerlab/bugs/panic.h" #include "hackerlab/os/errno-to-string.h" #include "hackerlab/mem/alloc-limits.h" #include "hackerlab/mem/mem.h" #include "hackerlab/arrays/ar.h" #include "hackerlab/char/char-class.h" #include "hackerlab/char/str.h" #include "hackerlab/fmt/cvt.h" #include "hackerlab/fs/file-names.h" #include "hackerlab/vu-network/url-socket.h" #include "hackerlab/vu/safe.h" #include "tla/libfsutils/string-files.h" #include "tla/libfsutils/copy-file.h" #include "tla/libfsutils/tmp-files.h" #include "tla/libfsutils/file-contents.h" #include "tla/libarch/archives.h" #include "tla/libarch/pfs-ftp.h" struct arch_pfs_ftp_session { struct arch_pfs_session pfs; char * cwd; int in_fd; int out_fd; int is_wu; t_uchar * remote_pwd; }; /* __STDC__ prototypes for static functions */ static int pfs_ftp_file_exists (struct arch_pfs_session * p, const t_uchar * path); static int pfs_ftp_is_dir (struct arch_pfs_session * p, const t_uchar * path); static t_uchar * pfs_ftp_file_contents (struct arch_pfs_session * p, const t_uchar * path, int soft_errors); static int pfs_ftp_get_file (struct arch_pfs_session * p, int file_out_fd, const t_uchar * path, int soft_errors); static rel_table pfs_ftp_directory_files (struct arch_pfs_session * p, const t_uchar * path, int soft_errors); static int pfs_ftp_put_file (struct arch_pfs_session * p, const t_uchar * path, mode_t perms, int file_in_fd, int soft_errors); static int pfs_ftp_mkdir (struct arch_pfs_session * p, const t_uchar * path, mode_t perms, int soft_errors); static int pfs_ftp_rename (struct arch_pfs_session * p, t_uchar ** errstr, const t_uchar * from, const t_uchar * to, int soft_errors); static int pfs_ftp_rmdir (struct arch_pfs_session * p, const t_uchar * path, int soft_errors); static int pfs_ftp_rm (struct arch_pfs_session * p, const t_uchar * path, int soft_errors); static int ftp_client_greet (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * len, int in_fd, int out_fd); static int ftp_client_login (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * name, const t_uchar * passwd); static int ftp_client_read_reply (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int fd); static int ftp_client_read_simple_cmd_reply (alloc_limits limit, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd); static int ftp_client_printfmt (int * errn, int fd, const char * fmt, ...); static int ftp_client_pasv_parse_equal (const char *text, t_ulong *h, t_uint16 *p); static int ftp_client_pasv_parse_parenthesis (const char *text, t_ulong *h, t_uint16 *p); static int ftp_client_pasv (alloc_limits limits, int * errn, int * code, t_ulong * host, t_uint16 * port, t_uchar ** text, long * text_len, int in_fd, int out_fd); static int ftp_client_type_image (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd); static int ftp_client_begin_retr (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path); static int ftp_client_end_retr (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd); static int ftp_client_begin_stor (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path); static int ftp_client_end_stor (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd); static int ftp_client_begin_nlst (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, int is_wu_ftp, const t_uchar * path, t_ulong host_addr, t_uint16 port); static int ftp_client_end_nlst (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd); static int ftp_client_mkd (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path); static int ftp_client_rename (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * from, const t_uchar * to); static int ftp_client_dele (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path); static int ftp_client_rmd (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path); static int ftp_client_cwd (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path); static int ftp_client_pwd (alloc_limits limits, int * errn, int * code, t_uchar ** path, long * path_len, t_uchar ** text, long * text_len, int in_fd, int out_fd); struct arch_pfs_vtable ftp_pfs_fns = { pfs_ftp_file_exists, pfs_ftp_is_dir, pfs_ftp_file_contents, pfs_ftp_get_file, pfs_ftp_directory_files, pfs_ftp_put_file, pfs_ftp_mkdir, pfs_ftp_rename, pfs_ftp_rmdir, pfs_ftp_rm, }; int arch_pfs_ftp_supported_protocol (const t_uchar * uri) { if (!str_cmp_prefix ("ftp://", uri) || !str_cmp_prefix ("wu-ftp://", uri)) return 1; else return 0; } struct arch_pfs_session * arch_pfs_ftp_connect (const t_uchar * uri) { int errn; int code = 0; struct arch_pfs_ftp_session * answer = 0; t_uchar * host = 0; t_uchar * user = 0; t_uchar * passwd = 0; t_uchar * hostname = 0; int status = -1; t_uchar * text = 0; long text_len = 0; answer = (struct arch_pfs_ftp_session *)lim_malloc (0, sizeof (*answer)); mem_set0 ((t_uchar *)answer, sizeof (*answer)); answer->pfs.vtable = &ftp_pfs_fns; { const t_uchar * prefix; const t_uchar * start; const t_uchar * stop; if (!str_cmp_prefix ("ftp://", uri)) prefix = "ftp://"; else { invariant (!str_cmp_prefix ("wu-ftp://", uri)); prefix = "wu-ftp://"; answer->is_wu = 1; } start = uri + str_length (prefix); stop = str_chr_index (start, '/'); host = str_save_n (0, start, stop - start); answer->cwd = str_save (0, stop); } // BUG hostname = str_chr_rindex (host, '@'); if (!hostname) { hostname = str_save (0, host); user = str_save (0, "anonymous"); passwd = str_save (0, "arch(tla version: " cfg__std__package ")"); } else { user = host; passwd = str_chr_index_n (user, hostname - user, ':'); if (passwd) { ++passwd; user = str_save_n (0, user, (passwd - 1) - user); passwd = str_save_n (0, passwd, hostname - passwd); hostname = str_save (0, hostname + 1); } else { user = str_save_n (0, user, hostname - user); passwd = str_save (0, "ftp-utils"); hostname = str_save (0, hostname + 1); } } /* hostname, user, passwd allocated strings. */ answer->in_fd = url_inet_client (&errn, hostname, 21); if (answer->in_fd < 0) { status = -1; text = str_save (0, "Unable to connect to server"); text_len = str_length (text); } else { int e; answer->out_fd = vu_dup (&errn, answer->in_fd); if (answer->out_fd < 0) { vu_close (&e, answer->in_fd); text = str_save (0, "Unable to duplicate the socket descriptor"); text_len = str_length (text); status = -1; } else { if ( (0 > vfdbuf_buffer_fd (&errn, answer->in_fd, 0, O_RDONLY, 0)) || (0 > vfdbuf_buffer_fd (&errn, answer->out_fd, 0, O_WRONLY, 0)) || (0 > ftp_client_greet (0, &errn, &code, &text, &text_len, answer->in_fd, answer->out_fd)) || (0 > ftp_client_login (0, &errn, &code, &text, &text_len, answer->in_fd, answer->out_fd, user, passwd))) { vu_close (&e, answer->in_fd); vu_close (&e, answer->out_fd); status = -1; } else { long dir_len; if (0 > ftp_client_pwd (0, &errn, &code, &answer->remote_pwd, &dir_len, &text, &text_len, answer->in_fd, answer->out_fd)) { vu_close (&e, answer->in_fd); vu_close (&e, answer->out_fd); status = -1; } else { answer->remote_pwd = lim_realloc (0, answer->remote_pwd, dir_len + 1); answer->remote_pwd[dir_len] = 0; status = 0; } } } } lim_free (0, host); lim_free (0, user); lim_free (0, passwd); if (status) { safe_printfmt (2, "ftp_client_connect: ftp error connecting to %s\n", hostname); safe_printfmt (2, " %d: %.*s\n", code, (int)text_len, text); exit (2); } lim_free (0, hostname); return (struct arch_pfs_session *)answer; } static int pfs_ftp_file_exists (struct arch_pfs_session * p, const t_uchar * path) { struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p; t_uchar * abs_path = 0; t_uchar * abs_path_dir = 0; t_uchar * path_tail = 0; rel_table nlst = rel_table_nil; int x; int answer; abs_path = file_name_in_vicinity (0, arch->cwd, path); abs_path_dir = file_name_directory_file (0, abs_path); path_tail = file_name_tail (0, abs_path); nlst = pfs_ftp_directory_files (p, abs_path_dir, 1); answer = 0; for (x = 0; x < rel_n_records (nlst); ++x) { if (!str_cmp (path_tail, rel_peek_str (nlst, x, 0))) { answer = 1; break; } } if (!answer) { /* On some servers the NLST command does not return dot files. A simple * workaround is to NLST directly the file we expect to find */ nlst = pfs_ftp_directory_files (p, abs_path, 1); for (x = 0; x < rel_n_records (nlst); ++x) { if (!str_cmp (path_tail, rel_peek_str (nlst, x, 0))) { answer = 1; break; } } } lim_free (0, abs_path); lim_free (0, abs_path_dir); lim_free (0, path_tail); rel_free_table (nlst); return answer; } static int pfs_ftp_is_dir (struct arch_pfs_session * p, const t_uchar * path) { struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p; int errn; int code; t_uchar * text; long text_len; t_uchar * abs_path = 0; int chdir_stat; int return_chdir_stat; abs_path = file_name_in_vicinity (0, arch->cwd, path); chdir_stat = ftp_client_cwd (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, abs_path); lim_free (0, text); return_chdir_stat = ftp_client_cwd (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, arch->remote_pwd); lim_free (0, text); if (return_chdir_stat < 0) { safe_printfmt (2, "ftp_client_is_dir: unrecoverable chdir error for path %s\n", arch->remote_pwd); exit (2); } lim_free (0, abs_path); return (chdir_stat >= 0); } static t_uchar * pfs_ftp_file_contents (struct arch_pfs_session * p, const t_uchar * path, int soft_errors) { int string_fd; t_uchar * answer = 0; int status; string_fd = make_output_to_string_fd (); status = pfs_ftp_get_file (p, string_fd, path, soft_errors); answer = string_fd_close (string_fd); if (status < 0) { lim_free (0, answer); answer = 0; } return answer; } static int pfs_ftp_get_file (struct arch_pfs_session * p, int file_out_fd, const t_uchar * path, int soft_errors) { struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p; int errn; int code; t_uchar * text = 0; long text_len; int status = -1; t_uchar * get_path = 0; get_path = file_name_in_vicinity (0, arch->cwd, path); if (0 > ftp_client_type_image (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd)) { put_error: if (!soft_errors) { safe_printfmt (2, "ftp_client_get: error getting file %s\n", get_path); safe_printfmt (2, " %d: %.*s\n", code, (int)text_len, text); exit (2); } status = -1; } else { t_ulong host_addr; t_uint16 port; lim_free (0, text); text = 0; if (0 > ftp_client_pasv (0, &errn, &code, &host_addr, &port, &text, &text_len, arch->in_fd, arch->out_fd)) { goto put_error; } else { int file_in_fd; lim_free (0, text); text = 0; file_in_fd = url_inet_client_addr (&errn, host_addr, (int)port); if (file_in_fd < 0) { text = str_save (0, errno_to_string (errn)); text_len = str_length (text); goto put_error; } else { if (0 > ftp_client_begin_retr (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, get_path)) { goto put_error; } else { copy_fd (file_in_fd, file_out_fd); safe_close (file_in_fd); if (0 > ftp_client_end_retr (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd)) { goto put_error; } else { lim_free (0, text); text = 0; status = 0; } } } } } lim_free (0, text); lim_free (0, get_path); return status; } static rel_table pfs_ftp_directory_files (struct arch_pfs_session * p, const t_uchar * path, int soft_errors) { struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p; int errn; int code; t_uchar * text = 0; long text_len; rel_table answer = rel_table_nil; t_uchar * nlst_path = 0; t_uchar * nlst_path_lastc = 0; nlst_path = file_name_in_vicinity (0, arch->cwd, path); /* filter out trailing "/." since publicfile handles dots specially */ if (*nlst_path && *(nlst_path+1)) { nlst_path_lastc = nlst_path + strlen(nlst_path) - 1; if ((*nlst_path_lastc == '.') && (*(nlst_path_lastc-1) == '/')) *(nlst_path_lastc - 1) = 0; } if (0 > ftp_client_type_image (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd)) { put_error: if (!soft_errors) { safe_printfmt (2, "ftp_client_directory_files: error listing file %s\n", nlst_path); safe_printfmt (2, " %d: %.*s\n", code, (int)text_len, text); exit (2); } } else { t_ulong host_addr; t_uint16 port; lim_free (0, text); text = 0; if (0 > ftp_client_pasv (0, &errn, &code, &host_addr, &port, &text, &text_len, arch->in_fd, arch->out_fd)) { goto put_error; } else { int file_in_fd; lim_free (0, text); text = 0; file_in_fd = ftp_client_begin_nlst (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, arch->is_wu, nlst_path, host_addr, port); if (0 > file_in_fd) { /* Workaround for buggy ftp servers that respond a 450 or 550 code for empty directories. * 450 is reported by proftpd: * "450 No files found" * The pfs_ftp_is_dir ensures the directory exists as it chdir to the given path. * Btw, we could not have used pfs_ftp_file_exists otherwise we would have created * an infinte function call loop because pfs_ftp_file_exists relies on * pfs_ftp_directory_files */ if ((code != 450 && code != 550) || !pfs_ftp_is_dir (p, path)) goto put_error; } else { t_uchar * raw_list = 0; int file_out_fd; file_out_fd = make_output_to_string_fd (); copy_fd (file_in_fd, file_out_fd); safe_close (file_in_fd); raw_list = string_fd_close (file_out_fd); if (0 > ftp_client_end_nlst (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd)) { lim_free (0, raw_list); goto put_error; } else { int x; lim_free (0, text); text = 0; answer = rel_ws_split (raw_list); lim_free (0, raw_list); for (x = 0; x < rel_n_records (answer); ++x) { t_uchar * t; t = file_name_tail (0, rel_peek_str (answer, x, 0)); rel_set_taking (answer, x, 0, rel_make_field_str (t)); lim_free (0, t); } } } } } lim_free (0, text); lim_free (0, nlst_path); return answer; } static int pfs_ftp_put_file (struct arch_pfs_session * p, const t_uchar * path, mode_t perms, int file_in_fd, int soft_errors) { struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p; int errn; int code; t_uchar * text = 0; long text_len; int status = -1; t_uchar * put_path = 0; put_path = file_name_in_vicinity (0, arch->cwd, path); if (0 > ftp_client_type_image (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd)) { put_error: if (!soft_errors) { safe_printfmt (2, "ftp_client_put: error putting file %s\n", put_path); safe_printfmt (2, " %d: %.*s\n", code, (int)text_len, text); exit (2); } status = -1; } else { t_ulong host_addr; t_uint16 port; lim_free (0, text); text = 0; if (0 > ftp_client_pasv (0, &errn, &code, &host_addr, &port, &text, &text_len, arch->in_fd, arch->out_fd)) { goto put_error; } else { int file_out_fd; lim_free (0, text); text = 0; file_out_fd = url_inet_client_addr (&errn, host_addr, (int)port); if (file_out_fd < 0) { text = str_save (0, errno_to_string (errn)); text_len = str_length (text); goto put_error; } else { if (0 > ftp_client_begin_stor (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, put_path)) { goto put_error; } else { copy_fd (file_in_fd, file_out_fd); safe_close (file_out_fd); if (0 > ftp_client_end_stor (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd)) { goto put_error; } else { lim_free (0, text); text = 0; status = 0; } } } } } lim_free (0, text); lim_free (0, put_path); return status; } static int pfs_ftp_mkdir (struct arch_pfs_session * p, const t_uchar * path, mode_t perms, int soft_errors) { struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p; int errn; int code; t_uchar * text = 0; long text_len; int status; t_uchar * mkdir_path = 0; mkdir_path = file_name_in_vicinity (0, arch->cwd, path); status = ftp_client_mkd (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, mkdir_path); if (status && !soft_errors) { safe_printfmt (2, "ftp_client_mkdir: error making directory %s\n", mkdir_path); safe_printfmt (2, " %d: %.*s\n", code, (int)text_len, text); exit (2); } lim_free (0, text); lim_free (0, mkdir_path); return status; } static int pfs_ftp_rename (struct arch_pfs_session * p, t_uchar ** errstr, const t_uchar * from, const t_uchar * to, int soft_errors) { struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p; int errn; int code; t_uchar * text = 0; long text_len; int status; t_uchar * from_path = 0; t_uchar * to_path = 0; from_path = file_name_in_vicinity (0, arch->cwd, from); to_path = file_name_in_vicinity (0, arch->cwd, to); status = ftp_client_rename (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, from_path, to_path); if (status && !soft_errors) { safe_printfmt (2, "ftp_client_rename: error renaming %s to %s\n", from_path, to_path); safe_printfmt (2, " %d: %.*s\n", code, (int)text_len, text); exit (2); } lim_free (0, text); lim_free (0, from_path); lim_free (0, to_path); return status; } static int pfs_ftp_rmdir (struct arch_pfs_session * p, const t_uchar * path, int soft_errors) { struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p; int errn; int code; t_uchar * text = 0; long text_len; int status; t_uchar * dele_path = 0; dele_path = file_name_in_vicinity (0, arch->cwd, path); status = ftp_client_rmd (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, dele_path); if (status && !soft_errors) { safe_printfmt (2, "ftp_client_rmdir: error deleting %s\n", dele_path); safe_printfmt (2, " %d: %.*s\n", code, (int)text_len, text); exit (2); } lim_free (0, text); lim_free (0, dele_path); return status; } static int pfs_ftp_rm (struct arch_pfs_session * p, const t_uchar * path, int soft_errors) { struct arch_pfs_ftp_session * arch = (struct arch_pfs_ftp_session *)p; int errn; int code; t_uchar * text = 0; long text_len; int status; t_uchar * dele_path = 0; dele_path = file_name_in_vicinity (0, arch->cwd, path); status = ftp_client_dele (0, &errn, &code, &text, &text_len, arch->in_fd, arch->out_fd, dele_path); if (status && !soft_errors) { safe_printfmt (2, "ftp_client_rm: error deleting %s\n", dele_path); safe_printfmt (2, " %d: %.*s\n", code, (int)text_len, text); exit (2); } lim_free (0, text); lim_free (0, dele_path); return status; } static int ftp_client_greet (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * len, int in_fd, int out_fd) { int delayed; delayed = 0; retry: if (0 > ftp_client_read_reply (limits, errn, code, text, len, in_fd)) return -1; if (*code == 120) { if (delayed) { *errn = EINVAL; return -1; } else { delayed = 1; goto retry; } } if (*code == 220) return 0; if (*code == 421) { *errn = 0; return -1; } *errn = EINVAL; return -1; } /**************************************************************** * * ftp_login * USER * PASS * * not implemented * ACCT * */ static int ftp_client_login (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * name, const t_uchar * passwd) { int user_repl_code; t_uchar * user_repl_text; long user_repl_len; int pass_repl_code; t_uchar * pass_repl_text; long pass_repl_len; if (0 > ftp_client_printfmt (errn, out_fd, "USER %s\r\n", name)) return -1; if (0 > ftp_client_read_reply (limits, errn, &user_repl_code, &user_repl_text, &user_repl_len, in_fd)) return -1; *code = user_repl_code; if (!text) lim_free (limits, user_repl_text); else { *text = user_repl_text; *text_len = user_repl_len; } { int first_digit; first_digit = user_repl_code / 100; switch (first_digit) { default: case 1: *errn = EINVAL; return -1; case 2: return 0; case 3: break; case 4: case 5: *errn = 0; return -1; } } if (0 > ftp_client_printfmt (errn, out_fd, "PASS %s\r\n", passwd)) return -1; if (0 > ftp_client_read_reply (limits, errn, &pass_repl_code, &pass_repl_text, &pass_repl_len, in_fd)) return -1; *code = pass_repl_code; if (!text) lim_free (limits, pass_repl_text); else { *text = lim_realloc (limits, *text, user_repl_len + pass_repl_len + 1); mem_move (*text + user_repl_len, pass_repl_text, pass_repl_len); (*text)[user_repl_len + pass_repl_len] = 0; *text_len += pass_repl_len; lim_free (limits, pass_repl_text); } { int first_digit; first_digit = pass_repl_code / 100; switch (first_digit) { default: case 1: *errn = EINVAL; return -1; case 2: return 0; case 3: *errn = ENOSYS; return -1; case 4: case 5: *errn = 0; return -1; } } } /**************************************************************** * From rfc 959 * * An FTP reply consists of a three digit number (transmitted as * three alphanumeric characters) followed by some text. The number * is intended for use by automata to determine what state to enter * next; the text is intended for the human user. It is intended * that the three digits contain enough encoded information that the * user-process (the User-PI) will not need to examine the text and * may either discard it or pass it on to the user, as appropriate. * In particular, the text may be server-dependent, so there are * likely to be varying texts for each reply code. * * A reply is defined to contain the 3-digit code, followed by Space * , followed by one line of text (where some maximum line length * has been specified), and terminated by the Telnet end-of-line * code. There will be cases however, where the text is longer than * a single line. In these cases the complete text must be bracketed * so the User-process knows when it may stop reading the reply (i.e. * stop processing input on the control connection) and go do other * things. This requires a special format on the first line to * indicate that more than one line is coming, and another on the * last line to designate it as the last. At least one of these must * contain the appropriate reply code to indicate the state of the * transaction. To satisfy all factions, it was decided that both * the first and last line codes should be the same. * * Thus the format for multi-line replies is that the first line will * begin with the exact required reply code, followed immediately by a * Hyphen, "-" (also known as Minus), followed by text. The last line * will begin with the same code, followed immediately by Space , * optionally some text, and the Telnet end-of-line code. * * For example: * 123-First line * Second line * 234 A line beginning with numbers * 123 The last line * * The user-process then simply needs to search for the second * occurrence of the same reply code, followed by (Space), at the * beginning of a line, and ignore all intermediary lines. If an * intermediary line begins with a 3-digit number, the Server must pad * the front to avoid confusion. * * This scheme allows standard system routines to be used for reply * information (such as for the STAT reply), with "artificial" first * and last lines tacked on. In rare cases where these routines are * able to generate three digits and a Space at the beginning of any * line, the beginning of each text line should be offset by some * neutral text, like Space. * * This scheme assumes that multi-line replies may not be nested. */ static int ftp_client_read_reply (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int fd) { t_uchar * line; long len; t_uchar c0; t_uchar c1; t_uchar c2; if (vfdbuf_next_line (errn, &line, &len, fd)) return -1; if ( (len < 4) || !char_is_digit(line[0]) || !char_is_digit(line[1]) || !char_is_digit(line[2]) || ((line[3] != ' ') && (line[3] != '-'))) { *errn = EINVAL; return -1; } *code = ( (line[0] - '0') * 100 + (line[1] - '0') * 10 + (line[2] - '0')); c0 = line[0]; c1 = line[1]; c2 = line[2]; if (text) { *text = str_save_n (limits, line, len); *text_len = len; } if (line[3] != '-') { return 0; } while (1) { if (vfdbuf_next_line (errn, &line, &len, fd)) { if (text) lim_free (limits, *text); return -1; } if ( (len >= 4) && (char_is_digit(line[0]) && c0) && (char_is_digit(line[1]) && c1) && (char_is_digit(line[2]) && c2) && (line[3] == ' ')) { /* Got the last line of the reply. */ if (text) { *text = lim_realloc (limits, *text, len + *text_len + 1); mem_move (*text + *text_len, line, len); (*text)[*text_len + len] = 0; *text_len = len + *text_len; } return 0; } else { if (text) { *text = lim_realloc (limits, *text, len + *text_len + 1); mem_move (*text + *text_len, line, len); (*text)[*text_len + len] = 0; *text_len = len + *text_len; } } } /* not reached */ panic ("not reached in ftp_read_reply"); return -1; } /**************************************************************** * * We first present the diagram that represents the largest group of FTP * commands: * * * 1,3 +---+ * ----------->| E | * | +---+ * | * +---+ cmd +---+ 2 +---+ * | B |---------->| W |---------->| S | * +---+ +---+ +---+ * | * | 4,5 +---+ * ----------->| F | * +---+ * * * This diagram models the commands: * * ABOR, ALLO, DELE, CWD, CDUP, SMNT, HELP, MODE, NOOP, PASV, * QUIT, SITE, PORT, SYST, STAT, RMD, MKD, PWD, STRU, and TYPE. * */ static int ftp_client_read_simple_cmd_reply (alloc_limits limit, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd) { if (0 > ftp_client_read_reply (limit, errn, code, text, text_len, in_fd)) return -1; { int first_digit; first_digit = *code / 100; switch (first_digit) { default: case 1: case 3: *errn = EINVAL; return -1; case 2: return 0; case 4: case 5: *errn = 0; return -1; } } } static int ftp_client_printfmt (int * errn, int fd, const char * fmt, ...) { int answer; va_list ap; #if 0 printfmt (errn, 2, "sending: "); va_start (ap, fmt); printfmt_va_list (errn, 2, fmt, ap); va_end (ap); #endif va_start (ap, fmt); answer = printfmt_va_list (errn, fd, fmt, ap); va_end (ap); if ( (answer >= 0) && vfdbuf_is_buffered (fd) && (0 > vfdbuf_flush (errn, fd))) return -1; return answer; } /**************************************************************** * ftp_port * * PORT * */ #if 0 static int ftp_client_port (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, t_ulong host, t_uint16 port) { if (0 > ftp_client_printfmt (errn, out_fd, "PORT %d,%d,%d,%d,%d,%d\r\n", (int)((host >> 24) & 0xff), (int)((host >> 16) & 0xff), (int)((host >> 8) & 0xff), (int)(host & 0xff), (port >> 8) & 0xff, port & 0xff)) return -1; return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } #endif /**************************************************************** * ftp_client_pasv_parse_equal * * =ipdec1,ipdec2,ipdec3,ipdec4,portdec1,portdec2 * */ static int ftp_client_pasv_parse_equal (const char *text, t_ulong *h, t_uint16 *p) { int er; t_uchar * start_byte; t_uchar * end_byte; t_uint h1; t_uint h2; t_uint h3; t_uint h4; t_uint p1; t_uint p2; start_byte = str_chr_rindex (text, '='); if (!start_byte) return 1; ++start_byte; end_byte = str_chr_index (start_byte, ','); if (!end_byte) return 1; cvt_decimal_to_uint (&er, &h1, start_byte, end_byte - start_byte); start_byte = end_byte + 1; end_byte = str_chr_index (start_byte, ','); if (!end_byte) return 1; cvt_decimal_to_uint (&er, &h2, start_byte, end_byte - start_byte); start_byte = end_byte + 1; end_byte = str_chr_index (start_byte, ','); if (!end_byte) return 1; cvt_decimal_to_uint (&er, &h3, start_byte, end_byte - start_byte); start_byte = end_byte + 1; end_byte = str_chr_index (start_byte, ','); if (!end_byte) return 1; cvt_decimal_to_uint (&er, &h4, start_byte, end_byte - start_byte); start_byte = end_byte + 1; end_byte = str_chr_index (start_byte, ','); if (!end_byte) return 1; cvt_decimal_to_uint (&er, &p1, start_byte, end_byte - start_byte); start_byte = end_byte + 1; end_byte = start_byte; while ((*end_byte >= '0') && (*end_byte <= '9')) end_byte++; if (!end_byte) return 1; cvt_decimal_to_uint (&er, &p2, start_byte, end_byte - start_byte); *h = (h1 << 24) | (h2 << 16) | (h3 << 8) | h4; *p = (p1 << 8) | p2; return 0; } /**************************************************************** * ftp_client_pasv_parse_parenthesis * * (ipdec1,ipdec2,ipdec3,ipdec4,portdec1,portdec2) * */ static int ftp_client_pasv_parse_parenthesis (const char *text, t_ulong *h, t_uint16 *p) { int er; t_uchar * start_byte; t_uchar * end_byte; t_uint h1; t_uint h2; t_uint h3; t_uint h4; t_uint p1; t_uint p2; start_byte = str_chr_rindex (text, '('); if (!start_byte) return -1; ++start_byte; end_byte = str_chr_index (start_byte, ','); if (!end_byte) return -1; cvt_decimal_to_uint (&er, &h1, start_byte, end_byte - start_byte); start_byte = end_byte + 1; end_byte = str_chr_index (start_byte, ','); if (!end_byte) return -1; cvt_decimal_to_uint (&er, &h2, start_byte, end_byte - start_byte); start_byte = end_byte + 1; end_byte = str_chr_index (start_byte, ','); if (!end_byte) return -1; cvt_decimal_to_uint (&er, &h3, start_byte, end_byte - start_byte); start_byte = end_byte + 1; end_byte = str_chr_index (start_byte, ','); if (!end_byte) return -1; cvt_decimal_to_uint (&er, &h4, start_byte, end_byte - start_byte); start_byte = end_byte + 1; end_byte = str_chr_index (start_byte, ','); if (!end_byte) return -1; cvt_decimal_to_uint (&er, &p1, start_byte, end_byte - start_byte); start_byte = end_byte + 1; end_byte = str_chr_index (start_byte, ')'); if (!end_byte) return -1; cvt_decimal_to_uint (&er, &p2, start_byte, end_byte - start_byte); *h = (h1 << 24) | (h2 << 16) | (h3 << 8) | h4; *p = (p1 << 8) | p2; return 0; } /**************************************************************** * ftp_pasv * * PASV */ static int ftp_client_pasv (alloc_limits limits, int * errn, int * code, t_ulong * host, t_uint16 * port, t_uchar ** text, long * text_len, int in_fd, int out_fd) { t_uchar * t; long tl; int status; if (0 > ftp_client_printfmt (errn, out_fd, "PASV\r\n")) return -1; t = 0; tl = 0; if (!text) { text = &t; text_len = &tl; } if (0 > ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd)) return -1; status = 0; if (*code != 227) { switch (*code / 100) { default: case 1: case 2: case 3: bogus_reply: *errn = EINVAL; status = -1; break; case 4: case 5: *errn = 0; status = -1; break; } } else { t_ulong h; t_uint16 p; if (str_chr_rindex (*text, '(')) { if (0 > ftp_client_pasv_parse_parenthesis(*text, &h, &p)) goto bogus_reply; } else if (str_chr_rindex (*text, '=')) { if (0 > ftp_client_pasv_parse_equal(*text, &h, &p)) goto bogus_reply; } else goto bogus_reply; *host = h; *port = p; #if 0 if (host) *host = htonl (h); if (port) *port = htons (p); #endif } if (t) lim_free (limits, t); return status; } /**************************************************************** * ftp_type_image * ftp_type_ascii * * TYPE * */ static int ftp_client_type_image (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd) { if (0 > ftp_client_printfmt (errn, out_fd, "TYPE I\r\n")) return -1; return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } #if 0 static int ftp_client_type_ascii (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd) { if (0 > ftp_client_printfmt (errn, out_fd, "TYPE A\r\n")) return -1; return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } #endif /**************************************************************** * not implemented * * STRU * */ /**************************************************************** * ftp_stream_mode * * MODE * */ #if 0 static int ftp_client_stream_mode (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd) { if (0 > ftp_client_printfmt (errn, out_fd, "MODE S\r\n")) return -1; return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } #endif /**************************************************************** * ftp_client_begin_retr * ftp_client_end_retr * * RETR * */ static int ftp_client_begin_retr (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path) { int c; if (0 > ftp_client_printfmt (errn, out_fd, "RETR %s\r\n", path)) return -1; if (0 > ftp_client_read_reply (limits, errn, &c, text, text_len, in_fd)) return -1; if (code) *code = c; switch (c / 100) { case 1: /* Not doing anything special for markers yet. * * 125 Data connection already open; transfer starting. * 150 File status okay; about to open data connection. * 110 Restart marker reply. * In this case, the text is exact and not left to the * particular implementation; it must read: * MARK yyyy = mmmm * Where yyyy is User-process data stream marker, and mmmm * server's equivalent marker (note the spaces between markers * and "="). */ return 0; default: case 2: case 4: case 5: /* * 226 Closing data connection. * Requested file action successful (for example, file * transfer or file abort). * 250 Requested file action okay, completed. * * 421 Service not available, closing control connection. * This may be a reply to any command if the service knows it * must shut down. * 425 Can't open data connection. * 426 Connection closed; transfer aborted. * 451 Requested action aborted: local error in processing. * 450 Requested file action not taken. * File unavailable (e.g., file busy). * * 550 Requested action not taken. * File unavailable (e.g., file not found, no access). * 500 Syntax error, command unrecognized. * This may include errors such as command line too long. * 501 Syntax error in parameters or arguments. * 530 Not logged in. */ *errn = 0; return -1; case 3: *errn = EINVAL; return -1; } } static int ftp_client_end_retr (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd) { return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } /**************************************************************** * ftp_client_begin_stor * ftp_client_end_stor * * STOR * */ static int ftp_client_begin_stor (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path) { int c; if (0 > ftp_client_printfmt (errn, out_fd, "STOR %s\r\n", path)) return -1; if (0 > ftp_client_read_reply (limits, errn, &c, text, text_len, in_fd)) return -1; if (code) *code = c; switch (c / 100) { case 1: /* Not doing anything special for markers yet. */ return 0; default: case 2: case 4: case 5: *errn = 0; return -1; case 3: *errn = EINVAL; return -1; } } static int ftp_client_end_stor (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd) { return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } /**************************************************************** * ftp_client_begin_nlst * ftp_client_end_nlst * * NLST [ ] * * * with wu-ftp: * * NLST -a [ ] * */ static int ftp_client_begin_nlst (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, int is_wu_ftp, const t_uchar * path, t_ulong host_addr, t_uint16 port) { int c; int data_fd = 0; if (host_addr) { data_fd = url_inet_client_addr (errn, host_addr, (int)port); if (data_fd < 0) { safe_printfmt (2, "ftp_client_begin_nlst: %s to port %d\n", errno_to_string (*errn), (int) port); } } if (path && path[0]) { if (0 > ftp_client_printfmt (errn, out_fd, "NLST%s %s\r\n", (is_wu_ftp ? " -a" : ""), path)) goto leave; } else { if (0 > ftp_client_printfmt (errn, out_fd, "NLST%s\r\n", (is_wu_ftp ? " -a" : ""))) goto leave; } if (0 > ftp_client_read_reply (limits, errn, &c, text, text_len, in_fd)) { leave: if (host_addr && data_fd >= 0) safe_close (data_fd); return -1; } if (code) *code = c; else { } switch (c / 100) { case 1: return data_fd; default: case 2: case 4: case 5: *errn = 0; if (host_addr && (data_fd >= 0)) safe_close (data_fd); return -1; case 3: *errn = EINVAL; if (host_addr && (data_fd >= 0)) safe_close (data_fd); return -1; } } static int ftp_client_end_nlst (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd) { return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } /**************************************************************** * ftp_mkd * MKD * */ static int ftp_client_mkd (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path) { if (0 > ftp_client_printfmt (errn, out_fd, "MKD %s\r\n", path)) return -1; return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } /**************************************************************** * ftp_rename * * RNFR * RNTO * */ static int ftp_client_rename (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * from, const t_uchar * to) { int rnfr_code; t_uchar * rnfr_text; long rnfr_len; int rnto_code; t_uchar * rnto_text; long rnto_len; if (0 > ftp_client_printfmt (errn, out_fd, "RNFR %s\r\n", from)) return -1; if (0 > ftp_client_read_reply (limits, errn, &rnfr_code, &rnfr_text, &rnfr_len, in_fd)) return -1; *code = rnfr_code; if (!text) lim_free (limits, rnfr_text); else { *text = rnfr_text; *text_len = rnfr_len; } { int first_digit; first_digit = rnfr_code / 100; switch (first_digit) { default: case 1: case 2: *errn = EINVAL; return -1; case 3: break; case 4: case 5: *errn = 0; return -1; } } if (0 > ftp_client_printfmt (errn, out_fd, "RNTO %s\r\n", to)) return -1; if (0 > ftp_client_read_reply (limits, errn, &rnto_code, &rnto_text, &rnto_len, in_fd)) return -1; *code = rnto_code; if (!text) lim_free (limits, rnto_text); else { *text = lim_realloc (limits, *text, rnfr_len + rnto_len + 1); mem_move (*text + rnfr_len, rnto_text, rnto_len); (*text)[rnfr_len + rnto_len] = 0; *text_len += rnto_len; lim_free (limits, rnto_text); } { int first_digit; first_digit = rnto_code / 100; switch (first_digit) { default: case 1: case 3: *errn = EINVAL; return -1; case 2: return 0; case 4: case 5: *errn = 0; return -1; } } } static int ftp_client_dele (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path) { if (0 > ftp_client_printfmt (errn, out_fd, "DELE %s\r\n", path)) return -1; return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } /**************************************************************** * ftp_rmd * * RMD * */ static int ftp_client_rmd (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path) { if (0 > ftp_client_printfmt (errn, out_fd, "RMD %s\r\n", path)) return -1; return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } /**************************************************************** * ftp_cwd * CWD */ static int ftp_client_cwd (alloc_limits limits, int * errn, int * code, t_uchar ** text, long * text_len, int in_fd, int out_fd, const t_uchar * path) { if (0 > ftp_client_printfmt (errn, out_fd, "CWD %s\r\n", path)) return -1; return ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd); } /**************************************************************** * ftp_client_pwd * * PWD * */ static int ftp_client_pwd (alloc_limits limits, int * errn, int * code, t_uchar ** path, long * path_len, t_uchar ** text, long * text_len, int in_fd, int out_fd) { t_uchar * t; long tl; int status; if (0 > ftp_client_printfmt (errn, out_fd, "PWD\r\n")) return -1; t = 0; tl = 0; if (!text) { text = &t; text_len = &tl; } if (0 > ftp_client_read_simple_cmd_reply (limits, errn, code, text, text_len, in_fd)) return -1; status = 0; if (*code != 257) { switch (*code / 100) { default: case 1: case 2: case 3: bogus_reply: *errn = EINVAL; status = -1; break; case 4: case 5: *errn = 0; status = -1; break; } } else { t_uchar * path_start; t_uchar * path_end; long doubled_quotes; path_start = str_chr_index (*text, '"'); if (!path_start) goto bogus_reply; path_start += 1; path_end = path_start; doubled_quotes = 0; while (1) { if (path_end == (*text + *text_len)) goto bogus_reply; else if (*path_end == '"') { if (((path_end + 1) < (*text + *text_len)) && (*(path_end + 1) == '"')) { ++doubled_quotes; path_end += 2; } else break; } else ++path_end; } if (path) { t_uchar * p; *path = lim_malloc (limits, 1 + (path_end - path_start) - doubled_quotes); p = *path; while (path_start < path_end) { *p = *path_start; ++p; if (*path_start == '"') ++path_start; ++path_start; } *p = 0; *path_len = p - *path; } status = 0; } if (t) lim_free (limits, t); return status; } /* tag: Tom Lord Mon Jun 16 21:58:44 2003 (pfs-ftp.c) */