use std::collections::HashMap;
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::{TcpStream, ToSocketAddrs};
use std::os::unix::io::AsRawFd;
use std::path::Path;
use std::sync::atomic::Ordering;
use std::time::Duration;
use crate::ported::builtin::{LASTVAL, SFCONTEXT};
use crate::ported::params::getiparam;
use crate::ported::utils::{errflag, getshfunc, zwarnnam};
use crate::ported::zsh_h::{module, options, SFC_HOOK};
use std::sync::{Mutex, OnceLock};
#[allow(unused_variables)]
pub fn zftp_session(name: &str, args: &[&str], flags: i32) -> i32 {
if args.is_empty() {
if let Ok(state) = zftp_state().lock() {
for sess_name in state.session_names() {
println!("{}", sess_name); }
}
return 0; }
let current = zftp_state()
.lock()
.ok()
.and_then(|s| s.current_name().map(|n| n.to_string()))
.unwrap_or_default();
if args[0] == current {
return 0; }
savesession(); switchsession(args[0]); 0 }
#[allow(non_camel_case_types)]
pub type Zftp_session = Box<zftp_session>;
#[allow(non_camel_case_types)]
pub struct zfheader {
pub flags: i8, pub bytes: [u8; 2], }
#[allow(non_camel_case_types)]
pub struct zftpcmd {
pub nam: &'static str, pub fun: fn(&str, &[&str], i32) -> i32, pub min: i32, pub max: i32,
pub flags: i32,
}
#[allow(non_camel_case_types)]
pub type Zftpcmd = Box<zftpcmd>;
pub static lastcode: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
#[allow(non_snake_case)]
#[inline]
pub fn ZFST_TYPE(x: i32) -> i32 {
x & ZFST_TMSK
}
#[allow(non_snake_case)]
#[inline]
pub fn ZFST_MODE(x: i32) -> i32 {
x & ZFST_MMSK
}
#[derive(Debug)]
#[allow(non_camel_case_types)]
pub struct zftp_session {
pub name: String, pub params: Vec<String>, pub userparams: Vec<String>, pub cin: Option<TcpStream>, pub control: Option<TcpStream>, pub dfd: i32, pub has_size: i32, pub has_mdtm: i32,
pub host: Option<String>, pub port: u16, pub user: Option<String>, pub pwd: Option<String>, pub connected: bool, pub logged_in: bool, pub transfer_type: i32,
pub transfer_mode: i32,
pub passive: bool,
pub syst_probed: bool,
}
#[allow(non_upper_case_globals)]
pub static zfprefs: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
#[allow(non_snake_case)]
pub extern "C" fn zfhandler(sig: i32) {
if sig == libc::SIGALRM {
ZFDRRRRING.store(1, Ordering::Relaxed); unsafe {
*errno_ptr() = libc::ETIMEDOUT;
}
}
}
#[allow(non_snake_case)]
pub fn zfalarm(tmout: i32) {
ZFDRRRRING.store(0, Ordering::Relaxed); if ZFALARMED.load(Ordering::Relaxed) != 0 {
unsafe {
libc::alarm(tmout as u32);
} return; }
unsafe {
libc::signal(libc::SIGALRM, zfhandler as libc::sighandler_t);
}
let oalremain = unsafe { libc::alarm(tmout as u32) };
OALREMAIN.store(oalremain, Ordering::Relaxed);
if oalremain != 0 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
OALTIME.store(now, Ordering::Relaxed);
}
ZFALARMED.store(1, Ordering::Relaxed); }
#[allow(non_snake_case)]
pub fn zfpipe() {
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_IGN);
}
}
#[allow(non_snake_case)]
pub fn zfunalarm() {
let oalremain = OALREMAIN.load(Ordering::Relaxed); if oalremain != 0 {
let oaltime = OALTIME.load(Ordering::Relaxed); let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let tdiff = now - oaltime; let secs = if (oalremain as i64) < tdiff {
1
} else {
(oalremain as i64 - tdiff) as u32
};
unsafe {
libc::alarm(secs);
} } else {
unsafe {
libc::alarm(0);
} }
}
#[allow(non_snake_case)]
pub fn zfunpipe() {
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
} }
#[allow(non_snake_case)]
pub fn zfmovefd(fd: i32) -> i32 {
if fd != -1 && fd < 10 {
let fe = unsafe { libc::fcntl(fd, libc::F_DUPFD, 10) };
unsafe {
libc::close(fd);
}
return fe;
}
fd }
#[allow(non_snake_case)]
pub fn zfsetparam(name: &str, val: &str, flags: i32) {
let _ = flags & ZFPM_INTEGER;
if (flags & ZFPM_IFUNSET) != 0 {
if crate::ported::params::paramtab()
.read()
.map_or(false, |t| t.contains_key(name))
{
return; }
}
crate::ported::params::setsparam(name, val);
let _ = (flags & ZFPM_READONLY) != 0; }
#[allow(non_snake_case)]
pub fn zfunsetparam(name: &str) {
std::env::remove_var(name); }
#[allow(non_snake_case)]
pub fn zfargstring(cmd: &str, args: &[&str]) -> String {
let mut s = cmd.to_string();
for a in args {
s.push(' ');
s.push_str(a);
}
s
}
#[allow(non_snake_case)]
pub fn zfgetline(ln: &mut [u8], lnsize: i32, tmout: i32) -> i32 {
let mut ch: i32; let mut added: i32 = 0; let mut pcur: usize = 0; let mut cmdbuf: [u8; 3] = [0; 3];
ZCFINISH.store(0, Ordering::Relaxed); let lnsize = lnsize - 1; if !ln.is_empty() {
ln[0] = 0; }
if ZFDRRRRING.load(Ordering::Relaxed) != 0 {
unsafe {
libc::alarm(0);
} zwarnnam("zftp", "timeout getting response"); return 6; }
zfalarm(tmout);
let mut state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 6,
};
let sess = match state.get_session_mut(None) {
Some(s) => s,
None => return 6,
};
let stream = match sess.cin.as_mut() {
Some(s) => s,
None => return 6,
};
let mut byte = [0u8; 1];
'main: loop {
ch = match stream.read(&mut byte) {
Ok(0) => -1, Ok(_) => byte[0] as i32,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, Err(_) => -1,
};
match ch {
-1 => {
ZCFINISH.store(2, Ordering::Relaxed); }
0x0d => {
ch = match stream.read(&mut byte) {
Ok(0) => -1,
Ok(_) => byte[0] as i32,
Err(_) => -1,
};
if ch == -1 {
ZCFINISH.store(2, Ordering::Relaxed); } else if ch == 0x0a {
ZCFINISH.store(1, Ordering::Relaxed); } else if ch == 0x00 {
ch = 0x0d; } else {
ch = 0x0d; }
}
0x0a => {
ZCFINISH.store(1, Ordering::Relaxed); }
255 => {
ch = match stream.read(&mut byte) {
Ok(0) => -1,
Ok(_) => byte[0] as i32,
Err(_) => -1,
};
match ch {
251 | 252 => {
ch = match stream.read(&mut byte) {
Ok(0) => -1,
Ok(_) => byte[0] as i32,
Err(_) => -1,
};
cmdbuf[0] = 255; cmdbuf[1] = 254; cmdbuf[2] = ch as u8; if let Some(ctrl) = sess.control.as_mut() {
let _ = ctrl.write_all(&cmdbuf);
}
continue 'main; }
253 | 254 => {
ch = match stream.read(&mut byte) {
Ok(0) => -1,
Ok(_) => byte[0] as i32,
Err(_) => -1,
};
cmdbuf[0] = 255; cmdbuf[1] = 252; cmdbuf[2] = ch as u8; if let Some(ctrl) = sess.control.as_mut() {
let _ = ctrl.write_all(&cmdbuf);
}
continue 'main; }
-1 => {
ZCFINISH.store(2, Ordering::Relaxed);
}
_ => {} }
}
_ => {}
}
if ZCFINISH.load(Ordering::Relaxed) != 0 {
break;
}
if added < lnsize && pcur < ln.len() {
ln[pcur] = ch as u8;
pcur += 1;
added += 1;
}
}
unsafe {
libc::alarm(0);
} if pcur < ln.len() {
ln[pcur] = 0; }
ZCFINISH.load(Ordering::Relaxed) & 2
}
#[allow(non_snake_case)]
pub fn zfgetmsg() -> i32 {
let mut line = [0u8; 256];
let mut printing: i32 = 0;
let stopit_initial: bool;
let tmout: i32;
{
let state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 6,
};
let sess = match state.get_session(None) {
Some(s) => s,
None => return 6,
};
if sess.control.is_none() {
return 6; }
}
if let Ok(mut m) = lastmsg.lock() {
m.clear();
}
tmout = getiparam("ZFTP_TMOUT") as i32;
zfgetline(&mut line, 256, tmout);
let mut ptr_off: usize = 0;
let line_str = std::str::from_utf8(&line)
.unwrap_or("")
.trim_end_matches('\0')
.to_string();
let is_digit = |b: u8| b.is_ascii_digit();
let timeout_or_bad = ZFDRRRRING.load(Ordering::Relaxed) != 0
|| line.len() < 3
|| !is_digit(line[0])
|| !is_digit(line[1])
|| !is_digit(line[2]);
if timeout_or_bad {
ZCFINISH.store(2, Ordering::Relaxed); if ZFCLOSING.load(Ordering::Relaxed) == 0 {
zfclose(0); }
if let Ok(mut m) = lastmsg.lock() {
m.clear();
} if let Ok(mut cs) = lastcodestr.lock() {
cs.copy_from_slice(b"000\0");
}
zfsetparam("ZFTP_REPLY", "", ZFPM_READONLY); return 6; }
let code_str: String = std::str::from_utf8(&line[..3]).unwrap_or("0").to_string();
if let Ok(mut cs) = lastcodestr.lock() {
cs[0] = line[0];
cs[1] = line[1];
cs[2] = line[2];
cs[3] = 0;
}
let code: i32 = code_str.parse().unwrap_or(0);
lastcode.store(code, Ordering::Relaxed);
ptr_off += 3;
zfsetparam("ZFTP_CODE", &code_str, ZFPM_READONLY);
stopit_initial = line.get(ptr_off).copied() != Some(b'-');
ptr_off += 1;
let mut stopit = stopit_initial;
let verbose = crate::ported::params::getsparam("ZFTP_VERBOSE").unwrap_or_default(); if verbose.contains(line[0] as char) {
printing = 1; eprint!("{}", line_str); } else if verbose.contains('0') && !stopit {
printing = 2; eprint!("{}", &line_str[ptr_off..]); }
if printing != 0 {
eprintln!(); }
while ZCFINISH.load(Ordering::Relaxed) != 2 && !stopit {
line.fill(0); ptr_off = 0;
zfgetline(&mut line, 256, tmout); if ZFDRRRRING.load(Ordering::Relaxed) != 0 {
line[0] = 0; break; }
if &line[..3] == &code_str.as_bytes()[..3] {
if line[3] == b' ' {
stopit = true; ptr_off = 4; } else if line[3] == b'-' {
ptr_off = 4; }
} else if &line[..4] == b" " {
ptr_off = 4; }
let cont_line = std::str::from_utf8(&line)
.unwrap_or("")
.trim_end_matches('\0');
if printing == 2 {
if !stopit {
eprintln!("{}", &cont_line[ptr_off..]); }
} else if printing != 0 {
eprintln!("{}", cont_line); }
}
if printing != 0 {
let _ = io::stderr().flush();
}
let last_msg_str: String = std::str::from_utf8(&line)
.unwrap_or("")
.trim_end_matches('\0')
.chars()
.skip(ptr_off)
.collect();
if let Ok(mut m) = lastmsg.lock() {
*m = last_msg_str.clone();
}
let whole_line = std::str::from_utf8(&line)
.unwrap_or("")
.trim_end_matches('\0');
zfsetparam("ZFTP_REPLY", whole_line, ZFPM_READONLY);
let zcfin = ZCFINISH.load(Ordering::Relaxed);
let cur_code = lastcode.load(Ordering::Relaxed);
if (zcfin == 2 || cur_code == 421) && ZFCLOSING.load(Ordering::Relaxed) == 0
{
ZCFINISH.store(2, Ordering::Relaxed); zfclose(0); zwarnnam(
"zftp", "remote server has closed connection",
);
return 6; }
if cur_code == 530 {
return 6; }
if cur_code == 120 {
zwarnnam(
"zftp", &format!("delay expected, waiting: {}", last_msg_str),
);
return zfgetmsg(); }
(code_str.as_bytes()[0] - b'0') as i32
}
#[allow(non_snake_case)]
pub fn zfsendcmd(cmd: &str) -> i32 {
let ret: isize;
let tmout: i32;
let mut state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 6,
};
let sess = match state.get_session_mut(None) {
Some(s) => s,
None => return 6,
};
if sess.control.is_none() {
return 6; }
tmout = getiparam("ZFTP_TMOUT") as i32;
zfalarm(tmout);
let bytes = cmd.as_bytes();
ret = match sess.control.as_mut() {
Some(stream) => match stream.write(bytes) {
Ok(n) => {
let _ = stream.flush();
n as isize
}
Err(_) => -1,
},
None => -1,
};
unsafe {
libc::alarm(0);
}
if ret <= 0 {
zwarnnam(
"zftp send",
&format!(
"failure sending control message: {}",
io::Error::last_os_error()
),
);
return 6; }
drop(state);
zfgetmsg()
}
#[allow(non_snake_case)]
pub fn zfopendata(name: &str) -> (i32, bool) {
let prefs = zfprefs.load(Ordering::Relaxed); if (prefs & (ZFPF_SNDP | ZFPF_PASV)) == 0 {
zwarnnam(name, "Must set preference S or P to transfer data"); return (1, false); }
let try_pasv: bool = (prefs & ZFPF_PASV) != 0;
if try_pasv {
if zfsendcmd("PASV\r\n") == 6 {
return (1, false); }
let code = lastcode.load(Ordering::Relaxed);
if (500..=504).contains(&code) {
zfclosedata(); } else {
let last = lastmsg.lock().ok().map(|m| m.clone()).unwrap_or_default();
let (ip, port) = match parse_pasv_response(&last) {
Ok(t) => t,
Err(_) => {
zwarnnam(
name,
&format!("bad response to PASV: {}", last),
);
zfclosedata();
return (1, false); }
};
let addr = format!("{}:{}", ip, port);
let stream = match TcpStream::connect(&addr) {
Ok(s) => s,
Err(_) => {
zwarnnam(name, "can't open data socket");
zfclosedata();
return (1, false);
}
};
let dfd_raw = stream.as_raw_fd();
std::mem::forget(stream);
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.dfd = dfd_raw; }
}
return (0, true); }
}
if (zfprefs.load(Ordering::Relaxed) & ZFPF_SNDP) == 0 {
zwarnnam(name, "only sendport mode available for data"); return (1, false); }
let listener = match std::net::TcpListener::bind("0.0.0.0:0") {
Ok(l) => l,
Err(_) => {
zwarnnam(name, "can't bind data socket");
return (1, false); }
};
let local = match listener.local_addr() {
Ok(a) => a,
Err(_) => {
zwarnnam(name, "getsockname failed");
return (1, false);
}
};
let ipv4 = match local.ip() {
std::net::IpAddr::V4(v) => v.octets(),
std::net::IpAddr::V6(_) => {
zwarnnam(name, "PORT mode requires IPv4");
return (1, false);
}
};
let port = local.port();
let port_cmd = format!(
"PORT {},{},{},{},{},{}\r\n",
ipv4[0],
ipv4[1],
ipv4[2],
ipv4[3],
port >> 8,
port & 0xff,
);
if zfsendcmd(&port_cmd) > 2 {
zwarnnam(name, "PORT command failed");
return (1, false); }
let lfd = listener.as_raw_fd();
std::mem::forget(listener);
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.dfd = lfd; }
}
(0, false) }
#[allow(non_snake_case)]
pub fn zfclosedata() {
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
if sess.dfd == -1 {
return; }
unsafe {
libc::close(sess.dfd);
} sess.dfd = -1; }
}
}
#[allow(non_snake_case)]
pub fn zfgetdata(name: &str, rest: &str, cmd: &str, getsize: i32) -> i32 {
let is_passive: bool;
let (rc, ip) = zfopendata(name); if rc != 0 {
return 1; }
is_passive = ip;
if !rest.is_empty() && zfsendcmd(rest) > 3 {
zfclosedata();
return 1;
}
if zfsendcmd(cmd) > 2 {
zfclosedata(); return 1; }
if getsize != 0 || cmd.starts_with("RETR") {
let cur_last = lastmsg.lock().ok().map(|m| m.clone()).unwrap_or_default();
if let Some(byte_idx) = cur_last.find("bytes") {
let prefix = &cur_last[..byte_idx];
let trimmed: String = prefix
.chars()
.rev()
.skip_while(|c| !c.is_ascii_digit())
.take_while(|c| c.is_ascii_digit())
.collect::<String>()
.chars()
.rev()
.collect();
if !trimmed.is_empty() && getsize != 0 {
zfsetparam("ZFTP_SIZE", &trimmed, ZFPM_READONLY | ZFPM_INTEGER);
}
}
}
let dfd_raw: i32;
if !is_passive {
let mut sa: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
let mut sl: libc::socklen_t = size_of::<libc::sockaddr_storage>() as _;
let listen_fd = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|x| x.dfd))
.unwrap_or(-1);
let newfd = unsafe { libc::accept(listen_fd, &mut sa as *mut _ as *mut _, &mut sl) };
if newfd < 0 {
zwarnnam(
name,
&format!("unable to accept data: {}", io::Error::last_os_error()),
);
zfclosedata(); return 1;
}
unsafe {
libc::close(listen_fd);
}
dfd_raw = zfmovefd(newfd);
} else {
let cur_dfd = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|x| x.dfd))
.unwrap_or(-1);
dfd_raw = zfmovefd(cur_dfd);
}
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.dfd = dfd_raw; }
}
let li = libc::linger {
l_onoff: 1,
l_linger: 120,
};
unsafe {
libc::setsockopt(
dfd_raw,
libc::SOL_SOCKET,
libc::SO_LINGER,
&li as *const _ as *const libc::c_void,
size_of::<libc::linger>() as libc::socklen_t,
);
}
let tos: libc::c_int = 0x08; unsafe {
libc::setsockopt(
dfd_raw,
libc::IPPROTO_IP,
libc::IP_TOS,
&tos as *const _ as *const libc::c_void,
size_of::<libc::c_int>() as libc::socklen_t,
);
}
unsafe {
libc::fcntl(dfd_raw, libc::F_SETFD, libc::FD_CLOEXEC);
}
0 }
#[allow(non_snake_case)]
pub fn zfstats(
fnam: &str,
remote: i32, retsize: &mut libc::off_t,
retmdtm: &mut Option<String>,
fd: i32,
) -> i32 {
let mut sz: libc::off_t = -1; let mut mt: Option<String> = None; let ret: i32;
*retsize = -1; *retmdtm = None;
if remote != 0 {
zfsettype(ZFST_IMAG);
let cmd = format!("SIZE {}\r\n", fnam); ret = zfsendcmd(&cmd); if ret == 6 {
return 1; }
let code = lastcode.load(Ordering::Relaxed);
if code < 300 {
sz = lastmsg
.lock()
.ok()
.map(|m| m.trim().parse::<libc::off_t>().unwrap_or(-1))
.unwrap_or(-1);
} else if (500..=504).contains(&code) {
return 2; } else if code == 550 {
return 1; }
let cmd = format!("MDTM {}\r\n", fnam); let ret2 = zfsendcmd(&cmd); if ret2 == 6 {
return 1; }
let code = lastcode.load(Ordering::Relaxed);
if code < 300 {
mt = lastmsg.lock().ok().map(|m| m.clone());
} else if (500..=504).contains(&code) {
return 2; } else if code == 550 {
return 1; }
} else {
let mut statbuf: libc::stat = unsafe { std::mem::zeroed() }; let cn = std::ffi::CString::new(fnam).unwrap_or_default();
let rc = if fd == -1 {
unsafe { libc::stat(cn.as_ptr(), &mut statbuf) }
} else {
unsafe { libc::fstat(fd, &mut statbuf) }
};
if rc < 0 {
return 1; }
sz = statbuf.st_size as libc::off_t;
let mtime = statbuf.st_mtime;
let mut tmbuf = [0u8; 20];
let tmbuf_len = unsafe {
let mut tm: libc::tm = std::mem::zeroed();
libc::gmtime_r(&mtime, &mut tm); let fmt = std::ffi::CString::new("%Y%m%d%H%M%S").unwrap();
libc::strftime(
tmbuf.as_mut_ptr() as *mut libc::c_char,
20,
fmt.as_ptr(),
&tm,
)
};
mt = std::str::from_utf8(&tmbuf[..tmbuf_len])
.ok()
.map(|s| s.to_string());
}
*retsize = sz; *retmdtm = mt; 0 }
#[allow(non_snake_case)]
pub fn zfstarttrans(nam: &str, recv: i32, sz: libc::off_t) {
let cnt: libc::off_t = 0; if sz > 0 {
zfsetparam("ZFTP_SIZE", &sz.to_string(), ZFPM_READONLY | ZFPM_INTEGER); }
zfsetparam("ZFTP_FILE", nam, ZFPM_READONLY); zfsetparam(
"ZFTP_TRANSFER", if recv != 0 { "G" } else { "P" },
ZFPM_READONLY,
);
zfsetparam("ZFTP_COUNT", &cnt.to_string(), ZFPM_READONLY | ZFPM_INTEGER); }
#[allow(non_snake_case)]
pub fn zfendtrans() {
zfunsetparam("ZFTP_SIZE"); zfunsetparam("ZFTP_FILE"); zfunsetparam("ZFTP_TRANSFER"); zfunsetparam("ZFTP_COUNT"); }
#[allow(non_snake_case)]
pub fn zfread(fd: i32, bf: &mut [u8], sz: libc::off_t, tmout: i32) -> i32 {
let ret: isize;
if tmout == 0 {
let n = unsafe { libc::read(fd, bf.as_mut_ptr() as *mut libc::c_void, sz as libc::size_t) };
return n as i32; }
if ZFDRRRRING.load(Ordering::Relaxed) != 0 {
unsafe {
libc::alarm(0);
} zwarnnam("zftp", "timeout on network read"); return -1; }
zfalarm(tmout);
ret = unsafe { libc::read(fd, bf.as_mut_ptr() as *mut libc::c_void, sz as libc::size_t) };
unsafe {
libc::alarm(0);
}
ret as i32 }
#[allow(non_snake_case)]
pub fn zfwrite(fd: i32, bf: &[u8], sz: i64, tmout: i32) -> i32 {
if tmout == 0 {
return unsafe {
libc::write(fd, bf.as_ptr() as *const _, sz as usize) as i32 };
}
zfalarm(tmout); let ret = unsafe {
libc::write(fd, bf.as_ptr() as *const _, sz as usize) as i32 };
unsafe {
libc::alarm(0);
} ret }
pub static zfread_eof: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
#[allow(non_snake_case)]
pub fn zfread_block(fd: i32, bf: &mut [u8], sz: libc::off_t, tmout: i32) -> i32 {
let mut n: i32; let mut hdr = zfheader {
flags: 0,
bytes: [0u8; 2],
}; let mut blksz: libc::off_t = 0; let mut cnt: libc::off_t; let mut bfptr: usize;
loop {
let mut hdr_buf = [0u8; 3];
loop {
n = zfread(fd, &mut hdr_buf, 3, tmout); if !(n < 0
&& io::Error::last_os_error().raw_os_error() == Some(libc::EINTR))
{
break;
}
}
if n != 3 && ZFDRRRRING.load(Ordering::Relaxed) == 0 {
zwarnnam("zftp", "failure reading FTP block header");
return n; }
hdr.flags = hdr_buf[0] as i8;
hdr.bytes[0] = hdr_buf[1];
hdr.bytes[1] = hdr_buf[2];
if (hdr.flags as i32 & ZFHD_EOFB) != 0 {
zfread_eof.store(1, Ordering::Relaxed); }
blksz = ((hdr.bytes[0] as libc::off_t) << 8) | (hdr.bytes[1] as libc::off_t);
if blksz > sz {
zwarnnam("zftp", "block too large to handle");
unsafe {
*errno_ptr() = libc::EIO;
} return -1; }
bfptr = 0; cnt = blksz; while cnt > 0 {
let want = cnt as usize;
let end = bfptr + want;
if end > bf.len() {
return -1;
}
n = zfread(fd, &mut bf[bfptr..end], cnt, tmout); if n > 0 {
bfptr += n as usize; cnt -= n as libc::off_t; } else if n < 0
&& (errflag.load(Ordering::Relaxed) != 0
|| ZFDRRRRING.load(Ordering::Relaxed) != 0
|| io::Error::last_os_error().raw_os_error() != Some(libc::EINTR))
{
return n; } else {
break; }
}
if cnt != 0 {
zwarnnam("zftp", "short data block");
unsafe {
*errno_ptr() = libc::EIO;
} return -1; }
if !((hdr.flags as i32 & ZFHD_MARK) != 0 && zfread_eof.load(Ordering::Relaxed) == 0) {
break;
}
}
if (hdr.flags as i32 & ZFHD_MARK) != 0 {
0
} else {
blksz as i32
}
}
#[allow(non_snake_case)]
pub fn zfwrite_block(fd: i32, bf: &[u8], sz: i64, tmout: i32) -> i32 {
let mut hdr = zfheader {
bytes: [0u8; 2],
flags: 0i8,
}; let mut n: i32;
loop {
hdr.bytes[0] = ((sz & 0xff00) >> 8) as u8; hdr.bytes[1] = (sz & 0xff) as u8; hdr.flags = if sz != 0 { 0i8 } else { ZFHD_EOFB as i8 }; let hdr_bytes = unsafe { std::slice::from_raw_parts(&hdr as *const _ as *const u8, 3) };
n = zfwrite(fd, hdr_bytes, 3, tmout); if !(n < 0 && unsafe { *errno_ptr() } == libc::EINTR) {
break;
} }
if n != 3 {
return n; }
if sz != 0 {
n = zfwrite(fd, bf, sz, tmout); }
n }
#[allow(non_snake_case)]
pub fn zfsenddata(name: &str, recv: i32, progress: i32, startat: libc::off_t) -> i32 {
const ZF_BUFSIZE: usize = 32768;
const ZF_ASCSIZE: usize = ZF_BUFSIZE / 2;
let mut n: i32; let mut ret: i32 = 0; let gotack: i32 = 0; let fdin: i32; let fdout: i32; let mut fromasc: i32 = 0; let mut toasc: i32 = 0; let mut rtmout: i32 = 0; let mut wtmout: i32 = 0; let mut lsbuf = vec![0u8; ZF_BUFSIZE]; let mut ascbuf: Vec<u8> = Vec::new(); let mut sofar: libc::off_t = 0; let mut last_sofar: libc::off_t = 0; let _ = progress;
let mut use_block_mode = false;
{
let state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 1,
};
let sess = match state.get_session(None) {
Some(s) => s,
None => return 1,
};
if recv != 0 {
fdin = sess.dfd; fdout = 1; rtmout = getiparam("ZFTP_TMOUT") as i32;
if sess.transfer_type == ZFST_ASCI as i32 {
fromasc = 1; }
if sess.transfer_mode == ZFST_BLOC as i32 {
use_block_mode = true; }
} else {
fdin = 0; fdout = sess.dfd; wtmout = getiparam("ZFTP_TMOUT") as i32;
if sess.transfer_type == ZFST_ASCI as i32 {
toasc = 1; }
if sess.transfer_mode == ZFST_BLOC as i32 {
use_block_mode = true; }
}
}
if progress != 0 {
sofar = startat; last_sofar = sofar;
}
let _ = last_sofar;
if toasc != 0 {
ascbuf = vec![0u8; ZF_ASCSIZE]; }
zfpipe(); zfread_eof.store(0, Ordering::Relaxed);
while ret == 0 && zfread_eof.load(Ordering::Relaxed) == 0 {
n = if toasc != 0 {
if use_block_mode {
zfread_block(fdin, &mut ascbuf, ZF_ASCSIZE as libc::off_t, rtmout)
} else {
zfread(fdin, &mut ascbuf, ZF_ASCSIZE as libc::off_t, rtmout)
}
} else if use_block_mode {
zfread_block(fdin, &mut lsbuf, ZF_BUFSIZE as libc::off_t, rtmout)
} else {
zfread(fdin, &mut lsbuf, ZF_BUFSIZE as libc::off_t, rtmout)
};
if n > 0 {
if toasc != 0 {
let mut iptr = 0usize;
let mut optr = 0usize;
let mut cnt = n;
while cnt > 0 {
if ascbuf[iptr] == b'\n' {
if optr < lsbuf.len() {
lsbuf[optr] = b'\r';
optr += 1;
}
n += 1; }
if optr < lsbuf.len() {
lsbuf[optr] = ascbuf[iptr];
optr += 1;
}
iptr += 1;
cnt -= 1;
}
}
if fromasc != 0 {
if let Some(_start) = lsbuf[..n as usize].iter().position(|&b| b == b'\r') {
let mut optr = 0usize;
let mut iptr = 0usize;
let len = n as usize;
while iptr < len {
if lsbuf[iptr] != b'\r' || iptr + 1 >= len || lsbuf[iptr + 1] != b'\n' {
lsbuf[optr] = lsbuf[iptr];
optr += 1;
} else {
n -= 1; }
iptr += 1;
}
}
}
let mut optr_off: usize = 0;
sofar += n as libc::off_t; loop {
let chunk = &lsbuf[optr_off..optr_off + n as usize];
let newn: i32 = if use_block_mode && recv == 0 {
zfwrite_block(fdout, chunk, n as libc::off_t, wtmout)
} else {
zfwrite(fdout, chunk, n as libc::off_t, wtmout)
};
if newn == n {
break;
} if newn < 0 {
let errno = io::Error::last_os_error().raw_os_error();
let drrr = ZFDRRRRING.load(Ordering::Relaxed) != 0;
let efl = errflag.load(Ordering::Relaxed) != 0;
if errno != Some(libc::EINTR) || efl || drrr {
if !drrr && (efl || errno != Some(libc::EPIPE)) {
ret = if recv != 0 { 2 } else { 1 };
zwarnnam(
name, &format!("write failed: {}", io::Error::last_os_error()),
);
} else {
ret = if recv != 0 { 3 } else { 1 };
}
break;
}
continue; }
optr_off += newn as usize; n -= newn; }
} else if n < 0 {
let errno = io::Error::last_os_error().raw_os_error();
let drrr = ZFDRRRRING.load(Ordering::Relaxed) != 0;
let efl = errflag.load(Ordering::Relaxed) != 0;
if errno != Some(libc::EINTR) || efl || drrr {
if !drrr && (efl || errno != Some(libc::EPIPE)) {
ret = if recv != 0 { 1 } else { 2 };
zwarnnam(
name, &format!("read failed: {}", io::Error::last_os_error()),
);
} else {
ret = if recv != 0 { 1 } else { 3 };
}
break;
}
} else {
break; }
if ret == 0 && sofar != last_sofar && progress != 0 {
if let Some(_shfunc) = getshfunc("zftp_progress") {
let osc = SFCONTEXT.load(Ordering::Relaxed); zfsetparam(
"ZFTP_COUNT",
&sofar.to_string(),
ZFPM_READONLY | ZFPM_INTEGER,
); SFCONTEXT
.store(SFC_HOOK, Ordering::Relaxed); let _ = LASTVAL.load(Ordering::Relaxed);
SFCONTEXT.store(osc, Ordering::Relaxed);
} else {
zfsetparam(
"ZFTP_COUNT",
&sofar.to_string(),
ZFPM_READONLY | ZFPM_INTEGER,
); }
last_sofar = sofar; }
}
zfunpipe(); ZFDRRRRING.store(0, Ordering::Relaxed);
if errflag.load(Ordering::Relaxed) == 0
&& ret == 0
&& recv == 0
&& use_block_mode
{
let eof_buf = [0u8; 1];
if zfwrite_block(fdout, &eof_buf, 0, wtmout) < 0 {
ret = 1; }
}
if errflag.load(Ordering::Relaxed) != 0 || ret > 1 {
let msg: [u8; 4] = [255, 244, 255, 242]; if ret == 2 {
zwarnnam(name, "aborting data transfer..."); }
crate::ported::signals::holdintr(); if let Ok(state) = zftp_state().lock() {
if let Some(sess) = state.get_session(None) {
if let Some(ref ctrl) = sess.control {
let cfd = ctrl.as_raw_fd();
unsafe {
libc::send(cfd, msg.as_ptr() as *const libc::c_void, 3, 0); libc::send(
cfd,
msg[3..].as_ptr() as *const libc::c_void,
1,
libc::MSG_OOB,
); }
}
}
}
zfsendcmd("ABOR\r\n"); if lastcode.load(Ordering::Relaxed) != 226 {
ret = 1; }
crate::ported::signals::noholdintr(); }
drop(ascbuf);
zfclosedata(); if gotack == 0 && zfgetmsg() > 2 {
ret = 1; }
if ret != 0 {
1
} else {
0
} }
pub const ZF_BUFSIZE: usize = 32_768;
pub const ZF_ASCSIZE: usize = ZF_BUFSIZE / 2;
pub fn zftp_open(name: &str, args: &[&str], flags: i32) -> i32 {
let mut port: i32 = -1; let portnam: String; let hostnam: String; let hostsuffix: String; let tmout: i32;
let mut effective: Vec<String> = args.iter().map(|s| s.to_string()).collect();
if effective.is_empty() {
let up = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.userparams.clone()))
.unwrap_or_default();
if !up.is_empty() {
effective = up; } else {
zwarnnam(name, "no host specified"); return 1; }
}
let already_open = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.control.is_some()))
.unwrap_or(false);
if already_open {
zfclose(0); }
let raw = effective[0].clone();
if let Some(stripped) = raw.strip_prefix('[') {
let close_idx = match stripped.find(']') {
Some(i) => i,
None => {
zwarnnam(name, &format!("Invalid host format: {}", raw)); return 1; }
};
let after = &stripped[close_idx + 1..];
if !after.is_empty() && !after.starts_with(':') {
zwarnnam(name, &format!("Invalid host format: {}", raw));
return 1;
}
hostnam = stripped[..close_idx].to_string(); hostsuffix = after.to_string(); } else {
hostnam = raw.clone();
hostsuffix = raw; }
if let Some((_pre, post)) = hostsuffix.split_once(':') {
let trimmed = post.trim();
match trimmed.parse::<i32>() {
Ok(p) => {
port = p;
portnam = "ftp".to_string();
}
Err(_) => {
portnam = trimmed.to_string(); port = -1; }
}
} else {
portnam = "ftp".to_string();
}
let resolved_port: u16 = if port > 0 {
port as u16
} else {
match portnam.as_str() {
"ftp" => 21,
"ftps" => 990,
_ => {
zwarnnam(
name,
&format!("Can't find port for service `{}'", portnam),
); return 1; }
}
};
ZCFINISH.store(2, Ordering::Relaxed);
tmout = {
let v = getiparam("ZFTP_TMOUT") as i32;
if v > 0 {
v
} else {
60
}
};
zfalarm(tmout);
let target = format!("{}:{}", hostnam, resolved_port);
let addrs: Vec<std::net::SocketAddr> = match target.to_socket_addrs() {
Ok(it) => it.collect(),
Err(_) => {
unsafe {
libc::alarm(0);
}
zwarnnam(name, &format!("host not found: {}", hostnam)); return 1; }
};
if addrs.is_empty() {
unsafe {
libc::alarm(0);
}
zwarnnam(name, &format!("host not found: {}", hostnam));
return 1;
}
zfsetparam("ZFTP_HOST", &hostnam, ZFPM_READONLY); zfsetparam(
"ZFTP_PORT",
&resolved_port.to_string(),
ZFPM_READONLY | ZFPM_INTEGER,
);
let mut last_err: Option<io::Error> = None;
let mut connected: Option<(TcpStream, std::net::SocketAddr)> = None;
for addr in addrs.iter() {
if errflag.load(Ordering::Relaxed) != 0 {
break;
}
match TcpStream::connect_timeout(addr, Duration::from_secs(tmout.max(1) as u64))
{
Ok(s) => {
connected = Some((s, *addr));
break;
} Err(e) => {
last_err = Some(e);
} }
}
let (stream, used_addr) = match connected {
Some(v) => v,
None => {
unsafe {
libc::alarm(0);
}
zfunsetparam("ZFTP_HOST"); zfunsetparam("ZFTP_PORT"); let msg = last_err
.as_ref()
.map(|e| e.to_string())
.unwrap_or_else(|| "connect failed".to_string());
zwarnnam(name, &format!("connect failed: {}", msg)); return 1; }
};
let pbuf = used_addr.ip().to_string();
zfsetparam("ZFTP_IP", &pbuf, ZFPM_READONLY);
unsafe {
libc::alarm(0);
}
ZFNOPEN.fetch_add(1, Ordering::Relaxed);
ZCFINISH.store(0, Ordering::Relaxed);
let fd = stream.as_raw_fd();
unsafe {
libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC);
}
unsafe {
let one: libc::c_int = 1;
libc::setsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_OOBINLINE,
&one as *const _ as *const _,
size_of::<libc::c_int>() as libc::socklen_t,
);
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
unsafe {
let lowdelay: libc::c_int = 0x10; libc::setsockopt(
fd,
libc::IPPROTO_IP,
libc::IP_TOS,
&lowdelay as *const _ as *const _,
size_of::<libc::c_int>() as libc::socklen_t,
);
}
let stream_clone = match stream.try_clone() {
Ok(c) => c,
Err(_) => {
zwarnnam(name, "file handling error"); zfclose(0);
return 1;
}
};
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.control = Some(stream);
sess.cin = Some(stream_clone);
sess.connected = true;
sess.host = Some(hostnam.clone());
sess.port = resolved_port;
}
}
if zfgetmsg() >= 4 {
zfclose(0); return 1; }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.has_size = ZFCP_UNKN; sess.has_mdtm = ZFCP_UNKN; sess.dfd = -1; sess.transfer_type = ZFST_ASCI; }
}
zfsetparam("ZFTP_MODE", "S", ZFPM_READONLY); if effective.len() > 1 {
let rest: Vec<&str> = effective[1..].iter().map(|s| s.as_str()).collect();
return zftp_login(name, &rest, flags); }
let alive = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.control.is_some()))
.unwrap_or(false);
if alive {
0
} else {
1
} }
#[allow(non_snake_case)]
pub fn zfgetinfo(prompt: &str, noecho: i32) -> Option<String> {
let mut resettty: i32 = 0; let mut instr = String::new(); let len: usize = 0; let _ = len;
let saved_termios: Option<libc::termios>;
if unsafe { libc::isatty(0) } != 0 {
if noecho != 0 {
let mut ti: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(0, &mut ti) } == 0 {
saved_termios = Some(ti);
ti.c_lflag &= !libc::ECHO; unsafe {
libc::tcsetattr(0, libc::TCSANOW, &ti);
} resettty = 1; } else {
saved_termios = None;
}
} else {
saved_termios = None;
}
eprint!("{}", prompt); let _ = io::stderr().flush(); } else {
saved_termios = None;
}
let stdin = io::stdin();
let mut handle = stdin.lock();
match handle.read_line(&mut instr) {
Ok(0) => instr.clear(), Ok(_) => {
if instr.ends_with('\n') {
instr.pop();
}
}
Err(_) => instr.clear(),
}
let strret = instr.clone();
if resettty != 0 {
println!(); let _ = io::stdout().flush(); if let Some(ti) = saved_termios {
unsafe {
libc::tcsetattr(0, libc::TCSANOW, &ti);
}
}
}
Some(strret) }
#[allow(unused_variables)]
pub fn zftp_params(name: &str, args: &[&str], flags: i32) -> i32 {
let prompts: [&str; 4] = ["Host: ", "User: ", "Password: ", "Account: "]; if args.is_empty() {
let state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 1,
};
let sess = match state.get_session(None) {
Some(s) => s,
None => return 1,
};
if sess.userparams.is_empty() {
return 1; }
let mut out = io::stdout().lock();
for (i, p) in sess.userparams.iter().enumerate() {
if i == 2 {
let len = p.len(); for _ in 0..len {
let _ = out.write_all(b"*"); }
let _ = out.write_all(b"\n"); } else {
let _ = writeln!(out, "{}", p); }
}
return 0; }
if args[0] == "-" {
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.userparams.clear(); }
}
return 0; }
let len = args.len(); let mut newarr: Vec<String> = Vec::with_capacity(len); let efl_atomic = &errflag;
for (i, aptr) in args.iter().enumerate() {
if efl_atomic.load(Ordering::Relaxed) != 0 {
break;
}
let str_val: String;
if let Some(rest) = aptr.strip_prefix('?') {
let prompt: &str = if !rest.is_empty() {
rest
} else {
prompts[i.min(3)]
}; match zfgetinfo(prompt, if i == 2 { 1 } else { 0 }) {
Some(s) => str_val = s,
None => {
return 1;
}
}
} else if let Some(rest) = aptr.strip_prefix('\\') {
str_val = rest.to_string();
} else {
str_val = (*aptr).to_string();
}
newarr.push(str_val); }
if efl_atomic.load(Ordering::Relaxed) != 0 {
return 1; }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.userparams = newarr; }
}
0 }
#[allow(unused_variables)]
pub fn zftp_login(name: &str, args: &[&str], flags: i32) -> i32 {
let mut ucmd: String; let mut passwd: Option<String> = None; let mut acct: Option<String> = None; let user: String; let mut stopit: i32; let mut arg_idx: usize = 0;
let already_logged_in = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.logged_in))
.unwrap_or(false);
if already_logged_in && zfsendcmd("REIN\r\n") >= 4 {
return 1; }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.logged_in = false; }
}
if arg_idx < args.len() {
user = args[arg_idx].to_string(); arg_idx += 1;
} else {
user = match zfgetinfo("User: ", 0) {
Some(s) => s,
None => return 1,
};
}
ucmd = format!("USER {}\r\n", user);
stopit = 0;
if zfsendcmd(&ucmd) == 6 {
stopit = 2; }
let efl_atomic = &errflag;
while stopit == 0 && efl_atomic.load(Ordering::Relaxed) == 0 {
let code = lastcode.load(Ordering::Relaxed);
match code {
230 | 202 => {
stopit = 1; }
331 => {
let pw = if arg_idx < args.len() {
let p = args[arg_idx].to_string(); arg_idx += 1;
p
} else {
match zfgetinfo("Password: ", 1) {
Some(s) => s,
None => {
stopit = 2;
break;
}
}
};
passwd = Some(pw.clone()); ucmd = format!("PASS {}\r\n", pw);
if zfsendcmd(&ucmd) == 6 {
stopit = 2; }
}
332 | 532 => {
let ac = if arg_idx < args.len() {
let a = args[arg_idx].to_string(); arg_idx += 1;
a
} else {
match zfgetinfo("Account: ", 0) {
Some(s) => s,
None => {
stopit = 2;
break;
}
}
};
acct = Some(ac.clone());
ucmd = format!("ACCT {}\r\n", ac); if zfsendcmd(&ucmd) == 6 {
stopit = 2; }
}
_ => {
stopit = 2; }
}
}
let _ = passwd;
let control_alive = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.control.is_some()))
.unwrap_or(false);
if !control_alive {
return 1; }
let code = lastcode.load(Ordering::Relaxed);
if stopit == 2 || (code != 230 && code != 202) {
zwarnnam(name, "login failed"); return 1; }
if arg_idx < args.len() {
let cnt = args.len() - arg_idx; zwarnnam(
name,
&format!("warning: {} command arguments not used", cnt), );
}
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.logged_in = true; sess.user = Some(user.clone());
}
}
zfsetparam("ZFTP_USER", &user, ZFPM_READONLY); if let Some(ref a) = acct {
zfsetparam("ZFTP_ACCOUNT", a, ZFPM_READONLY); }
let already_probed = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.syst_probed))
.unwrap_or(false);
let dumb = (zfprefs.load(Ordering::Relaxed) & ZFPF_DUMB) != 0; if !dumb && !already_probed && zfsendcmd("SYST\r\n") == 2 {
let systype = lastmsg.lock().ok().map(|m| m.clone()).unwrap_or_default();
if systype.starts_with("UNIX Type: L8") {
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.transfer_type = ZFST_IMAG; }
}
}
zfsetparam("ZFTP_SYSTEM", &systype, ZFPM_READONLY); if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.syst_probed = true; }
}
}
let ttype = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.transfer_type))
.unwrap_or(ZFST_ASCI);
let tbuf = if ZFST_TYPE(ttype) == ZFST_ASCI {
"A"
} else {
"I"
}; zfsetparam("ZFTP_TYPE", tbuf, ZFPM_READONLY);
zfgetcwd() }
#[allow(unused_variables)]
pub fn zftp_test(name: &str, args: &[&str], flags: i32) -> i32 {
let control_fd = zftp_state().lock().ok().and_then(|s| {
s.get_session(None)
.and_then(|sess| sess.control.as_ref().map(|c| c.as_raw_fd()))
});
let fd = match control_fd {
Some(f) => f,
None => return 1, };
let mut pfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
let ret = unsafe { libc::poll(&mut pfd, 1, 0) }; let errno = io::Error::last_os_error().raw_os_error().unwrap_or(0);
if ret < 0 && errno != libc::EINTR && errno != libc::EAGAIN {
zfclose(0); } else if ret > 0 && pfd.revents != 0 {
zfgetmsg(); }
let still_alive = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.control.is_some()))
.unwrap_or(false);
if still_alive {
0
} else {
2
} }
pub fn zftp_dir(name: &str, args: &[&str], flags: i32) -> i32 {
let cmd: String; let ret: i32; zfsettype(ZFST_ASCI);
let verb = if (flags & ZFTP_NLST) != 0 {
"NLST"
} else {
"LIST"
};
cmd = zfargstring(verb, args) + "\r\n";
ret = zfgetdata(name, "", &cmd, 0);
if ret != 0 {
return 1;
} let _ = io::stdout().flush(); zfsenddata(name, 1, 0, 0) }
#[allow(unused_variables)]
pub fn zftp_cd(name: &str, args: &[&str], flags: i32) -> i32 {
let ret: i32; let arg0 = args.first().copied().unwrap_or("");
if (flags & ZFTP_CDUP) != 0 || arg0 == ".." || arg0 == "../" {
ret = zfsendcmd("CDUP\r\n"); } else {
let cmd = format!("CWD {}\r\n", arg0); ret = zfsendcmd(&cmd); }
if ret > 2 {
return 1;
} if zfgetcwd() != 0 {
return 1;
}
0 }
#[allow(non_snake_case)]
pub fn zfgetcwd() -> i32 {
if (zfprefs.load(Ordering::Relaxed) & ZFPF_DUMB) != 0 {
return 1; }
if zfsendcmd("PWD\r\n") > 2 {
zfunsetparam("ZFTP_PWD"); return 1; }
if lastcode.load(Ordering::Relaxed) >= 200 {
0
} else {
1
}
}
#[allow(non_snake_case)]
pub fn zfsettype(typ: i32) -> i32 {
let typ_letter = if (typ & ZFST_IMAG) != 0 { "I" } else { "A" };
let _ = zfsendcmd(&format!("TYPE {}\r\n", typ_letter));
zfgetmsg()
}
pub fn zftp_type(name: &str, args: &[&str], flags: i32) -> i32 {
let mut tbuf: [u8; 2] = [b'A', 0]; let nt: u8; let str: &str; let _ = str;
if (flags & (ZFTP_TBIN | ZFTP_TASC)) != 0 {
nt = if (flags & ZFTP_TBIN) != 0 { b'I' } else { b'A' }; } else if args.first().copied().unwrap_or("").is_empty() {
let ttype = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.transfer_type))
.unwrap_or(ZFST_IMAG as i32);
let is_ascii = (ttype & ZFST_ASCI) != 0;
println!("{}", if is_ascii { 'A' } else { 'I' }); let _ = io::stdout().flush(); return 0; } else {
str = args[0];
let c0 = str.as_bytes()[0].to_ascii_uppercase(); if str.len() > 1 || (c0 != b'A' && c0 != b'B' && c0 != b'I') {
zwarnnam(
name, &format!("transfer type {} not recognised", str),
);
return 1; }
nt = if c0 == b'B' { b'I' } else { c0 }; }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.transfer_type = if nt == b'I' { ZFST_IMAG } else { ZFST_ASCI } as i32;
}
}
tbuf[0] = nt; let tb = std::str::from_utf8(&tbuf[..1]).unwrap_or("A");
zfsetparam("ZFTP_TYPE", tb, ZFPM_READONLY); 0 }
#[allow(unused_variables)]
pub fn zftp_mode(name: &str, args: &[&str], flags: i32) -> i32 {
let str: &str;
let nt: u8;
if args.first().copied().unwrap_or("").is_empty() {
let tmode = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.transfer_mode))
.unwrap_or(ZFST_STRE as i32);
let is_stream = tmode == ZFST_STRE;
println!("{}", if is_stream { 'S' } else { 'B' }); let _ = io::stdout().flush(); return 0; }
str = args[0];
nt = str.as_bytes()[0].to_ascii_uppercase(); if str.len() > 1 || (nt != b'S' && nt != b'B') {
zwarnnam(
name, &format!("transfer mode {} not recognised", str),
);
return 1; }
let cmd = format!("MODE {}\r\n", nt as char); if zfsendcmd(&cmd) > 2 {
return 1; }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.transfer_mode = if nt == b'S' { ZFST_STRE } else { ZFST_BLOC } as i32;
}
}
let mb = (nt as char).to_string();
zfsetparam("ZFTP_MODE", &mb, ZFPM_READONLY); 0 }
#[allow(unused_variables)]
pub fn zftp_local(name: &str, args: &[&str], flags: i32) -> i32 {
let more = args.len() > 1; let mut ret: i32 = 0; let dofd = args.is_empty(); let mut i = 0usize;
loop {
if !dofd && i >= args.len() {
break;
}
let arg = if dofd { "" } else { args[i] };
let mut sz: libc::off_t = 0; let mut mt: Option<String> = None; let remote = if (flags & ZFTP_HERE) != 0 { 0 } else { 1 };
let fd = if dofd { 0 } else { -1 };
let newret = zfstats(arg, remote, &mut sz, &mut mt, fd);
if newret == 2 {
return 2; } else if newret != 0 {
ret = 1; i += 1; continue; }
if more {
print!("{} ", arg); }
let mt_s = mt.unwrap_or_default();
println!("{} {}", sz, mt_s); if dofd {
break;
} i += 1; }
let _ = io::stdout().flush(); ret }
pub fn zftp_getput(name: &str, args: &[&str], flags: i32) -> i32 {
let mut ret: i32 = 0; let recv = (flags & ZFTP_RECV) != 0; let mut getsize: i32 = 0; let progress: i32 = 1; let cmd_pfx = if recv {
"RETR "
}
else if (flags & ZFTP_APPE) != 0 {
"APPE "
} else {
"STOR "
};
let ttype = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.transfer_type))
.unwrap_or(ZFST_IMAG as i32);
zfsettype(ttype);
if recv {
let _ = io::stdout().flush();
}
let mut i = 0usize;
while i < args.len() {
let arg = args[i];
let mut rest_cmd: String = String::new(); let mut startat: libc::off_t = 0;
if progress != 0 && getshfunc("zftp_progress").is_some() {
let mut sz: libc::off_t = -1; let mut _mdtm: Option<String> = None;
let dumb = (zfprefs.load(Ordering::Relaxed) & ZFPF_DUMB) != 0; if !dumb && (recv || (flags & ZFTP_REST) == 0) {
let _ = zfstats(
arg,
if recv { 1 } else { 0 }, &mut sz,
&mut _mdtm,
0,
);
if recv && sz == -1 {
getsize = 1; }
} else {
getsize = 1; }
zfstarttrans(arg, if recv { 1 } else { 0 }, sz); }
if (flags & ZFTP_REST) != 0 && i + 1 < args.len() {
startat = args[i + 1].parse().unwrap_or(0);
rest_cmd = format!("REST {}\r\n", args[i + 1]);
}
let ln = format!("{}{}\r\n", cmd_pfx, arg);
let gd = zfgetdata(name, &rest_cmd, &ln, getsize);
if gd != 0 {
ret = 2; } else {
if zfsenddata(name, if recv { 1 } else { 0 }, progress, startat) != 0 {
ret = 1; }
}
if progress != 0 && ret != 2 {
if let Some(_shfunc) = getshfunc("zftp_progress") {
let osc =
SFCONTEXT.load(Ordering::Relaxed); zfsetparam(
"ZFTP_TRANSFER", if recv { "GF" } else { "PF" },
ZFPM_READONLY,
);
SFCONTEXT.store(
SFC_HOOK,
Ordering::Relaxed,
); let _ = LASTVAL.load(Ordering::Relaxed);
SFCONTEXT.store(osc, Ordering::Relaxed);
} else {
zfsetparam(
"ZFTP_TRANSFER", if recv { "GF" } else { "PF" },
ZFPM_READONLY,
);
}
}
if (flags & ZFTP_REST) != 0 {
i += 1;
}
if errflag.load(Ordering::Relaxed) != 0 {
break;
}
let _ = getsize;
getsize = 0; i += 1;
}
zfendtrans(); if ret != 0 {
1
} else {
0
} }
#[allow(unused_variables)]
pub fn zftp_delete(name: &str, args: &[&str], flags: i32) -> i32 {
let mut ret: i32 = 0; let cmd: String; let _ = cmd;
for aptr in args.iter() {
let cmd = format!("DELE {}\r\n", aptr); if zfsendcmd(&cmd) > 2 {
ret = 1; }
}
ret }
#[allow(unused_variables)]
pub fn zftp_mkdir(name: &str, args: &[&str], flags: i32) -> i32 {
let ret: i32; if args.is_empty() {
return 1;
}
let cmd_pfx = if (flags & ZFTP_DELE) != 0 {
"RMD "
} else {
"MKD "
}; let cmd = format!("{}{}\r\n", cmd_pfx, args[0]); ret = (zfsendcmd(&cmd) > 2) as i32; ret }
#[allow(unused_variables)]
pub fn zftp_rename(name: &str, args: &[&str], flags: i32) -> i32 {
let mut ret: i32; let cmd: String; let _ = cmd;
if args.len() < 2 {
return 1;
}
let cmd = format!("RNFR {}\r\n", args[0]); ret = 1; if zfsendcmd(&cmd) == 3 {
let cmd = format!("RNTO {}\r\n", args[1]); if zfsendcmd(&cmd) == 2 {
ret = 0; }
}
ret }
#[allow(unused_variables)]
pub fn zftp_quote(name: &str, args: &[&str], flags: i32) -> i32 {
let ret: i32; let cmd: String; let _ = cmd;
let argv: Vec<&str> = args.to_vec();
let cmd = if (flags & ZFTP_SITE) != 0 {
zfargstring("SITE", &argv) + "\r\n"
} else {
if argv.is_empty() {
return 1;
}
zfargstring(argv[0], &argv[1..]) + "\r\n" };
ret = (zfsendcmd(&cmd) > 2) as i32; ret }
#[allow(non_snake_case)]
pub fn zfclose(leaveparams: i32) {
let alive = zftp_state()
.lock()
.ok()
.and_then(|s| s.get_session(None).map(|sess| sess.control.is_some()))
.unwrap_or(false);
if !alive {
return; }
ZFCLOSING.store(1, Ordering::Relaxed); if ZCFINISH.load(Ordering::Relaxed) != 2 {
let _ = zfsendcmd("QUIT\r\n"); }
if let Ok(mut state) = zftp_state().lock() {
if let Some(sess) = state.get_session_mut(None) {
sess.cin = None; sess.control = None; sess.dfd = -1;
sess.connected = false;
sess.logged_in = false;
}
}
ZFNOPEN.fetch_sub(1, Ordering::Relaxed);
if leaveparams == 0 {
for n in [
"ZFTP_HOST",
"ZFTP_PORT",
"ZFTP_IP",
"ZFTP_SYSTEM", "ZFTP_USER",
"ZFTP_ACCOUNT",
"ZFTP_PWD",
"ZFTP_TYPE",
"ZFTP_MODE",
] {
zfunsetparam(n); }
if getshfunc("zftp_chpwd").is_some() {
let osc = SFCONTEXT.load(Ordering::Relaxed);
SFCONTEXT
.store(SFC_HOOK, Ordering::Relaxed); let _ = LASTVAL.load(Ordering::Relaxed);
SFCONTEXT.store(osc, Ordering::Relaxed); }
}
ZFCLOSING.store(0, Ordering::Relaxed); ZFDRRRRING.store(0, Ordering::Relaxed); }
#[allow(unused_variables)]
pub fn zftp_close(name: &str, args: &[&str], flags: i32) -> i32 {
zfclose(0); 0 }
#[allow(non_snake_case)]
pub fn newsession(nm: &str) -> Box<zftp_session> {
if let Ok(state) = zftp_state().lock() {
if state.sessions.contains_key(nm) {
return Box::new(zftp_session::new(nm));
}
}
let mut sess = zftp_session::new(nm); sess.name = nm.to_string(); sess.dfd = -1; sess.params.clear();
if let Ok(mut state) = zftp_state().lock() {
let cloned = zftp_session::new(nm);
state.sessions.insert(nm.to_string(), cloned);
}
Box::new(sess)
}
#[allow(non_snake_case)]
pub fn savesession() {
let val: String;
let _ = val;
if let Ok(mut state) = zftp_state().lock() {
let sess = match state.get_session_mut(None) {
Some(s) => s,
None => return,
};
sess.params.clear();
for ps in ZFPARAMS {
let val = crate::ported::params::getsparam(ps).unwrap_or_default();
sess.params.push(val);
}
}
}
#[allow(non_snake_case)]
pub fn switchsession(nm: &str) {
if let Ok(mut state) = zftp_state().lock() {
let _ = state.create_session(nm);
state.set_current(nm);
}
}
#[allow(non_snake_case)]
pub fn freesession(sptr: &mut zftp_session) {
sptr.name.clear();
sptr.params.clear();
sptr.userparams.clear();
}
#[allow(unused_variables)]
pub fn zftp_rmsession(name: &str, args: &[&str], flags: i32) -> i32 {
let mut found_name: Option<String> = None; let mut is_current: bool = false; let mut newsess: Option<String> = None;
{
let state = match zftp_state().lock() {
Ok(s) => s,
Err(_) => return 1,
};
let current = state.current_name().unwrap_or("default").to_string();
let target = args
.first()
.copied()
.map(String::from)
.unwrap_or_else(|| current.clone());
for sess_name in state.session_names() {
if sess_name == target {
found_name = Some(sess_name.to_string());
is_current = sess_name == current;
break;
}
}
}
let target_name = match found_name {
Some(n) => n,
None => return 1, };
if is_current {
zfclosedata(); zfclose(0); let other = zftp_state()
.lock()
.ok()
.map(|s| {
s.session_names()
.into_iter()
.find(|n| *n != target_name)
.map(String::from)
})
.unwrap_or(None);
if let Some(o) = other {
newsess = Some(o);
}
} else {
let prev = zftp_state()
.lock()
.ok()
.and_then(|s| s.current_name().map(String::from));
if let Ok(mut state) = zftp_state().lock() {
state.set_current(&target_name); }
zfclosedata(); zfclose(1); if let (Some(p), Ok(mut state)) = (prev, zftp_state().lock()) {
state.set_current(&p); }
}
if let Ok(mut state) = zftp_state().lock() {
state.remove_session(&target_name);
}
if let Some(n) = newsess {
switchsession(&n); } else if zftp_state()
.lock()
.map(|s| s.session_names().is_empty())
.unwrap_or(false)
{
newsession("default");
}
0 }
#[allow(non_snake_case)]
pub fn bin_zftp(
_nam: &str,
args: &[String], _ops: &options,
_func: i32,
) -> i32 {
let argv: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
let mut zftp_guard = zftp_state().lock().unwrap_or_else(|e| {
zftp_state_clear_poison();
e.into_inner()
});
let zftp = &mut *zftp_guard;
let args = &argv[..];
let (status, output): (i32, String) = (|| {
if args.is_empty() {
return (1, "zftp: subcommand required\n".to_string());
}
match args[0] {
"open" => {
if args.len() < 2 {
return (1, "zftp open: host required\n".to_string());
}
let host = args[1];
let port: Option<u16> = args.get(2).and_then(|s| s.parse().ok());
let session_name = zftp.current_name().unwrap_or("default").to_string();
let sess = zftp.create_session(&session_name);
match sess.connect(host, port) {
Ok(resp) => {
if (resp.0 >= 100 && resp.0 < 400) {
zftp.set_current(&session_name);
(0, resp.1)
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp open: {}\n", e)),
}
}
"login" | "user" => {
if args.len() < 2 {
return (1, "zftp login: user required\n".to_string());
}
let user = args[1];
let pass = args.get(2).copied();
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp login: not connected\n".to_string()),
};
match sess.login(user, pass) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, resp.1)
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp login: {}\n", e)),
}
}
"cd" => {
if args.len() < 2 {
return (1, "zftp cd: path required\n".to_string());
}
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp cd: not connected\n".to_string()),
};
match sess.cd(args[1]) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, resp.1)
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp cd: {}\n", e)),
}
}
"cdup" => {
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp cdup: not connected\n".to_string()),
};
match sess.cdup() {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, resp.1)
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp cdup: {}\n", e)),
}
}
"pwd" => {
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp pwd: not connected\n".to_string()),
};
match sess.pwd() {
Ok((resp, pwd)) => {
if let Some(p) = pwd {
(0, format!("{}\n", p))
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp pwd: {}\n", e)),
}
}
"dir" | "ls" => {
let path = args.get(1).copied();
let use_nlst = args[0] == "ls";
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp dir: not connected\n".to_string()),
};
let result = if use_nlst {
sess.nlst(path)
} else {
sess.list(path)
};
match result {
Ok((resp, lines)) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, lines.join("\n") + "\n")
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp dir: {}\n", e)),
}
}
"get" => {
if args.len() < 2 {
return (1, "zftp get: remote file required\n".to_string());
}
let remote = args[1];
let local = args.get(2).unwrap_or(&remote);
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp get: not connected\n".to_string()),
};
match sess.get(remote, Path::new(local)) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp get: {}\n", e)),
}
}
"put" => {
if args.len() < 2 {
return (1, "zftp put: local file required\n".to_string());
}
let local = args[1];
let remote = args.get(2).unwrap_or(&local);
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp put: not connected\n".to_string()),
};
match sess.put(Path::new(local), remote) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp put: {}\n", e)),
}
}
"delete" => {
if args.len() < 2 {
return (1, "zftp delete: file required\n".to_string());
}
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp delete: not connected\n".to_string()),
};
match sess.delete(args[1]) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp delete: {}\n", e)),
}
}
"mkdir" => {
if args.len() < 2 {
return (1, "zftp mkdir: directory required\n".to_string());
}
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp mkdir: not connected\n".to_string()),
};
match sess.mkdir(args[1]) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp mkdir: {}\n", e)),
}
}
"rmdir" => {
if args.len() < 2 {
return (1, "zftp rmdir: directory required\n".to_string());
}
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp rmdir: not connected\n".to_string()),
};
match sess.rmdir(args[1]) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp rmdir: {}\n", e)),
}
}
"rename" => {
if args.len() < 3 {
return (1, "zftp rename: from and to required\n".to_string());
}
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp rename: not connected\n".to_string()),
};
match sess.rename(args[1], args[2]) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp rename: {}\n", e)),
}
}
"type" | "ascii" | "binary" => {
let transfer_type = match args[0] {
"ascii" => ZFST_ASCI,
"binary" => ZFST_IMAG,
"type" => {
if args.len() < 2 {
let sess = match zftp.get_session(None) {
Some(s) => s,
None => return (1, "zftp type: not connected\n".to_string()),
};
return (
0,
format!(
"{}\n",
if sess.transfer_type == ZFST_ASCI {
"ascii"
} else {
"binary"
}
),
);
}
match args[1].to_lowercase().as_str() {
"a" | "ascii" => ZFST_ASCI,
"i" | "binary" | "image" => ZFST_IMAG,
_ => return (1, format!("zftp type: unknown type {}\n", args[1])),
}
}
_ => unreachable!(),
};
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp type: not connected\n".to_string()),
};
match sess.set_type(transfer_type) {
Ok(resp) => {
if (resp.0 >= 200 && resp.0 < 300) {
(0, String::new())
} else {
(1, resp.1)
}
}
Err(e) => (1, format!("zftp type: {}\n", e)),
}
}
"bslashquote" => {
if args.len() < 2 {
return (1, "zftp bslashquote: command required\n".to_string());
}
let cmd = args[1..].join(" ");
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (1, "zftp bslashquote: not connected\n".to_string()),
};
match sess.bslashquote(&cmd) {
Ok(resp) => (
if (resp.0 >= 100 && resp.0 < 400) {
0
} else {
1
},
resp.1,
),
Err(e) => (1, format!("zftp bslashquote: {}\n", e)),
}
}
"close" | "quit" => {
let sess = match zftp.get_session_mut(None) {
Some(s) => s,
None => return (0, String::new()),
};
match sess.close() {
Ok(_) => (0, String::new()),
Err(e) => (1, format!("zftp close: {}\n", e)),
}
}
"session" => {
if args.len() < 2 {
let names = zftp.session_names();
let current = zftp.current_name();
let mut out = String::new();
for name in names {
let marker = if Some(name) == current { "* " } else { " " };
out.push_str(&format!("{}{}\n", marker, name));
}
return (0, out);
}
let name = args[1];
if zftp.sessions.contains_key(name) {
zftp.set_current(name);
} else {
zftp.create_session(name);
zftp.set_current(name);
}
(0, String::new())
}
"rmsession" => {
if args.len() < 2 {
return (1, "zftp rmsession: session name required\n".to_string());
}
if zftp.remove_session(args[1]).is_some() {
(0, String::new())
} else {
(
1,
format!("zftp rmsession: session {} not found\n", args[1]),
)
}
}
"test" => {
let sess = zftp.get_session(None);
if sess.map(|s| s.connected).unwrap_or(false) {
(0, String::new())
} else {
(1, String::new())
}
}
_ => (1, format!("zftp: unknown subcommand {}\n", args[0])),
}
})();
drop(zftp_guard);
if !output.is_empty() {
if status == 0 {
print!("{}", output);
} else {
eprint!("{}", output);
}
}
status
}
pub fn zftp_cleanup() -> i32 {
let cursess = zftp_state()
.lock()
.ok()
.and_then(|s| s.current_name().map(|n| n.to_string()));
let session_names: Vec<String> = zftp_state()
.lock()
.ok()
.map(|s| s.session_names().iter().map(|n| n.to_string()).collect())
.unwrap_or_default();
for nm in session_names {
if let Ok(mut s) = zftp_state().lock() {
let _ = s.set_current(&nm);
}
zfclosedata(); let leaveparams = if Some(&nm) != cursess.as_ref() { 1 } else { 0 };
zfclose(leaveparams); }
if let Ok(mut m) = lastmsg.lock() {
m.clear(); }
zfunsetparam("ZFTP_SESSION"); if let Ok(mut state) = zftp_state().lock() {
*state = zftp_globals::new(); }
0
}
impl zftp_session {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(), params: Vec::new(), userparams: Vec::new(), cin: None, control: None, dfd: -1, has_size: 0, has_mdtm: 0, host: None,
port: 21,
user: None,
pwd: None,
connected: false,
logged_in: false,
transfer_type: ZFST_IMAG,
transfer_mode: ZFST_STRE,
passive: true,
syst_probed: false,
}
}
fn send_command(&mut self, cmd: &str) -> io::Result<()> {
if let Some(ref mut stream) = self.cin {
write!(stream, "{}\r\n", cmd)?;
stream.flush()
} else {
Err(io::Error::new(io::ErrorKind::NotConnected, "not connected"))
}
}
fn read_response(&mut self) -> io::Result<FtpResponse> {
let stream = self
.cin
.as_mut()
.ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "not connected"))?;
let mut reader = BufReader::new(stream.try_clone()?);
let mut full_message = String::new();
let mut code = 0u32;
let mut multiline = false;
let mut first_code = String::new();
loop {
let mut line = String::new();
reader.read_line(&mut line)?;
let line = line.trim_end();
if line.len() < 3 {
continue;
}
if code == 0 {
first_code = line[..3].to_string();
code = first_code.parse().unwrap_or(0);
if line.len() > 3 && line.chars().nth(3) == Some('-') {
multiline = true;
}
}
full_message.push_str(line);
full_message.push('\n');
if multiline {
if line.starts_with(&first_code)
&& line.len() > 3
&& line.chars().nth(3) == Some(' ')
{
break;
}
} else {
break;
}
}
Ok((code as i32, full_message))
}
pub fn connect(&mut self, host: &str, port: Option<u16>) -> io::Result<FtpResponse> {
let port = port.unwrap_or(21);
let addr_str = format!("{}:{}", host, port);
let dns_timeout = Duration::from_secs(10);
let (tx, rx) = std::sync::mpsc::channel();
let dns = addr_str.clone();
std::thread::Builder::new()
.name("zftp-dns".to_string())
.spawn(move || {
let _ = tx.send(dns.to_socket_addrs().map(|a| a.collect::<Vec<_>>()));
})
.map_err(io::Error::other)?;
let addrs = rx
.recv_timeout(dns_timeout)
.map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "DNS resolution timed out"))?
.map_err(|e| {
zwarnnam("zftp", &format!("host not found: {}: {}", host, e));
e
})?;
let sock_addr = addrs
.into_iter()
.next()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid address"))?;
let stream = TcpStream::connect_timeout(&sock_addr, Duration::from_secs(30))?;
stream.set_read_timeout(Some(Duration::from_secs(60)))?;
stream.set_write_timeout(Some(Duration::from_secs(60)))?;
self.cin = Some(stream);
self.host = Some(host.to_string());
self.port = port;
self.connected = true;
self.read_response()
}
pub fn login(&mut self, user: &str, pass: Option<&str>) -> io::Result<FtpResponse> {
self.send_command(&format!("USER {}", user))?;
let resp = self.read_response()?;
if resp.0 == 331 {
let password = pass.unwrap_or("");
self.send_command(&format!("PASS {}", password))?;
let resp = self.read_response()?;
if (resp.0 >= 200 && resp.0 < 300) {
self.logged_in = true;
self.user = Some(user.to_string());
}
return Ok(resp);
}
if (resp.0 >= 200 && resp.0 < 300) {
self.logged_in = true;
self.user = Some(user.to_string());
}
Ok(resp)
}
pub fn set_type(&mut self, transfer_type: i32) -> io::Result<FtpResponse> {
let typ_letter = if (transfer_type & ZFST_IMAG) != 0 {
"I"
} else {
"A"
};
self.send_command(&format!("TYPE {}", typ_letter))?;
let resp = self.read_response()?;
if (resp.0 >= 200 && resp.0 < 300) {
self.transfer_type = transfer_type;
}
Ok(resp)
}
pub fn cd(&mut self, path: &str) -> io::Result<FtpResponse> {
self.send_command(&format!("CWD {}", path))?;
self.read_response()
}
pub fn cdup(&mut self) -> io::Result<FtpResponse> {
self.send_command("CDUP")?;
self.read_response()
}
pub fn pwd(&mut self) -> io::Result<(FtpResponse, Option<String>)> {
self.send_command("PWD")?;
let resp = self.read_response()?;
let pwd = if (resp.0 >= 200 && resp.0 < 300) {
if let Some(start) = resp.1.find('"') {
if let Some(end) = resp.1[start + 1..].find('"') {
Some(resp.1[start + 1..start + 1 + end].to_string())
} else {
None
}
} else {
None
}
} else {
None
};
Ok((resp, pwd))
}
pub fn list(&mut self, path: Option<&str>) -> io::Result<(FtpResponse, Vec<String>)> {
let data_stream = self.enter_passive_mode()?;
let cmd = match path {
Some(p) => format!("LIST {}", p),
None => "LIST".to_string(),
};
self.send_command(&cmd)?;
let resp = self.read_response()?;
if !(resp.0 >= 100 && resp.0 < 400) {
return Ok((resp, Vec::new()));
}
let mut reader = BufReader::new(data_stream);
let mut lines = Vec::new();
let mut line = String::new();
while reader.read_line(&mut line)? > 0 {
lines.push(line.trim_end().to_string());
line.clear();
}
let final_resp = self.read_response()?;
Ok((final_resp, lines))
}
pub fn nlst(&mut self, path: Option<&str>) -> io::Result<(FtpResponse, Vec<String>)> {
let data_stream = self.enter_passive_mode()?;
let cmd = match path {
Some(p) => format!("NLST {}", p),
None => "NLST".to_string(),
};
self.send_command(&cmd)?;
let resp = self.read_response()?;
if !(resp.0 >= 100 && resp.0 < 400) {
return Ok((resp, Vec::new()));
}
let mut reader = BufReader::new(data_stream);
let mut lines = Vec::new();
let mut line = String::new();
while reader.read_line(&mut line)? > 0 {
lines.push(line.trim_end().to_string());
line.clear();
}
let final_resp = self.read_response()?;
Ok((final_resp, lines))
}
fn enter_passive_mode(&mut self) -> io::Result<TcpStream> {
self.send_command("PASV")?;
let resp = self.read_response()?;
if !(resp.0 >= 200 && resp.0 < 300) {
return Err(io::Error::other(resp.1));
}
let (ip, port) = parse_pasv_response(&resp.1)?;
let addr = format!("{}:{}", ip, port);
TcpStream::connect_timeout(
&addr.to_socket_addrs()?.next().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "invalid PASV address")
})?,
Duration::from_secs(30),
)
}
pub fn get(&mut self, remote: &str, local: &Path) -> io::Result<FtpResponse> {
let mut data_stream = self.enter_passive_mode()?;
self.send_command(&format!("RETR {}", remote))?;
let resp = self.read_response()?;
if !(resp.0 >= 100 && resp.0 < 400) {
return Ok(resp);
}
let mut file = std::fs::File::create(local)?;
let mut buf = [0u8; 8192];
loop {
let n = data_stream.read(&mut buf)?;
if n == 0 {
break;
}
file.write_all(&buf[..n])?;
}
self.read_response()
}
pub fn put(&mut self, local: &Path, remote: &str) -> io::Result<FtpResponse> {
let mut data_stream = self.enter_passive_mode()?;
self.send_command(&format!("STOR {}", remote))?;
let resp = self.read_response()?;
if !(resp.0 >= 100 && resp.0 < 400) {
return Ok(resp);
}
let mut file = std::fs::File::open(local)?;
let mut buf = [0u8; 8192];
loop {
let n = file.read(&mut buf)?;
if n == 0 {
break;
}
data_stream.write_all(&buf[..n])?;
}
drop(data_stream);
self.read_response()
}
pub fn delete(&mut self, path: &str) -> io::Result<FtpResponse> {
self.send_command(&format!("DELE {}", path))?;
self.read_response()
}
pub fn mkdir(&mut self, path: &str) -> io::Result<FtpResponse> {
self.send_command(&format!("MKD {}", path))?;
self.read_response()
}
pub fn rmdir(&mut self, path: &str) -> io::Result<FtpResponse> {
self.send_command(&format!("RMD {}", path))?;
self.read_response()
}
pub fn rename(&mut self, from: &str, to: &str) -> io::Result<FtpResponse> {
self.send_command(&format!("RNFR {}", from))?;
let resp = self.read_response()?;
if !(resp.0 >= 300 && resp.0 < 400) {
return Ok(resp);
}
self.send_command(&format!("RNTO {}", to))?;
self.read_response()
}
pub fn size(&mut self, path: &str) -> io::Result<(FtpResponse, Option<u64>)> {
self.send_command(&format!("SIZE {}", path))?;
let resp = self.read_response()?;
let size = if (resp.0 >= 200 && resp.0 < 300) {
resp.1
.split_whitespace()
.last()
.and_then(|s| s.parse().ok())
} else {
None
};
Ok((resp, size))
}
pub fn bslashquote(&mut self, cmd: &str) -> io::Result<FtpResponse> {
self.send_command(cmd)?;
self.read_response()
}
pub fn close(&mut self) -> io::Result<FtpResponse> {
if !self.connected {
return Ok((0, "not connected".to_string()));
}
let resp = if let Ok(()) = self.send_command("QUIT") {
self.read_response()
.unwrap_or_else(|_| (221, "Goodbye".to_string()))
} else {
(221, "Goodbye".to_string())
};
self.cin = None;
self.connected = false;
self.logged_in = false;
self.host = None;
self.user = None;
self.pwd = None;
Ok(resp)
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn zftpexithook(d: *mut crate::ported::zsh_h::hookdef, dummy: *mut std::ffi::c_void) -> i32 {
let _ = (d, dummy);
zftp_cleanup(); 0 }
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 {
0
}
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 {
*features = featuresarray(m, module_features());
0
}
impl zftp_globals {
pub fn new() -> Self {
Self::default()
}
pub fn get_session(&self, name: Option<&str>) -> Option<&zftp_session> {
let key = name
.map(|s| s.to_string())
.or_else(|| self.current.clone())?;
self.sessions.get(&key)
}
pub fn get_session_mut(&mut self, name: Option<&str>) -> Option<&mut zftp_session> {
let key = name
.map(|s| s.to_string())
.or_else(|| self.current.clone())?;
self.sessions.get_mut(&key)
}
pub fn create_session(&mut self, name: &str) -> &mut zftp_session {
self.sessions
.entry(name.to_string())
.or_insert_with(|| zftp_session::new(name))
}
pub fn remove_session(&mut self, name: &str) -> Option<zftp_session> {
let sess = self.sessions.remove(name);
if self.current.as_deref() == Some(name) {
let mut keys: Vec<&String> = self.sessions.keys().collect();
keys.sort();
self.current = keys.first().map(|s| (*s).clone());
}
sess
}
pub fn set_current(&mut self, name: &str) -> bool {
if self.sessions.contains_key(name) {
self.current = Some(name.to_string());
true
} else {
false
}
}
pub fn current_name(&self) -> Option<&str> {
self.current.as_deref()
}
pub fn session_names(&self) -> Vec<&str> {
let mut names: Vec<&str> = self.sessions.keys().map(|s| s.as_str()).collect();
names.sort();
names
}
}
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 {
handlefeatures(m, module_features(), enables)
}
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 {
zfsetparam("ZFTP_VERBOSE", "450", ZFPM_IFUNSET); zfsetparam("ZFTP_TMOUT", "60", ZFPM_IFUNSET | ZFPM_INTEGER); zfsetparam("ZFTP_PREFS", "PS", ZFPM_IFUNSET); zfprefs.store(
ZFPF_SNDP | ZFPF_PASV, Ordering::Relaxed,
);
let _default = newsession("default"); crate::ported::module::addhookfunc("exit", zftpexithook as crate::ported::zsh_h::Hookfn);
0
}
pub fn cleanup_(m: *const module) -> i32 {
crate::ported::module::deletehookfunc(
"exit",
zftpexithook as crate::ported::zsh_h::Hookfn,
);
zftp_cleanup(); setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 {
0
}
pub static lastmsg: Mutex<String> = Mutex::new(String::new());
pub static lastcodestr: Mutex<[u8; 4]> = Mutex::new([b'0', b'0', b'0', 0]);
#[allow(non_camel_case_types)]
pub type FtpResponse = (i32, String);
pub const ZFHD_MARK: i32 = 16; pub const ZFHD_ERRS: i32 = 32; pub const ZFHD_EOFB: i32 = 64; pub const ZFHD_EORB: i32 = 128;
#[allow(non_camel_case_types)]
pub type readwrite_t = fn(i32, &mut [u8], libc::off_t, i32) -> i32;
pub const ZFTP_CONN: i32 = 0x0001; pub const ZFTP_LOGI: i32 = 0x0002; pub const ZFTP_TBIN: i32 = 0x0004; pub const ZFTP_TASC: i32 = 0x0008; pub const ZFTP_NLST: i32 = 0x0010; pub const ZFTP_DELE: i32 = 0x0020; pub const ZFTP_SITE: i32 = 0x0040; pub const ZFTP_APPE: i32 = 0x0080; pub const ZFTP_HERE: i32 = 0x0100; pub const ZFTP_CDUP: i32 = 0x0200; pub const ZFTP_REST: i32 = 0x0400; pub const ZFTP_RECV: i32 = 0x0800; pub const ZFTP_TEST: i32 = 0x1000; pub const ZFTP_SESS: i32 = 0x2000;
pub static ZFPARAMS: &[&str] = &[
"ZFTP_HOST",
"ZFTP_PORT",
"ZFTP_IP",
"ZFTP_SYSTEM",
"ZFTP_USER",
"ZFTP_ACCOUNT",
"ZFTP_PWD",
"ZFTP_TYPE",
"ZFTP_MODE", ];
pub const ZFPM_READONLY: i32 = 0x01; pub const ZFPM_IFUNSET: i32 = 0x02; pub const ZFPM_INTEGER: i32 = 0x04;
pub static ZFNOPEN: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static ZCFINISH: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static ZFCLOSING: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub const ZFCP_UNKN: i32 = 0;
pub const ZFCP_YUPP: i32 = 1;
pub const ZFCP_NOPE: i32 = 2;
pub const ZFST_ASCI: i32 = 0x0000;
pub const ZFST_IMAG: i32 = 0x0001;
pub const ZFST_TMSK: i32 = 0x0001;
pub const ZFST_TBIT: i32 = 0x0001;
pub const ZFST_CASC: i32 = 0x0000;
pub const ZFST_CIMA: i32 = 0x0002;
pub const ZFST_STRE: i32 = 0x0000;
pub const ZFST_BLOC: i32 = 0x0004;
pub const ZFST_MMSK: i32 = 0x0004;
pub const ZFST_LOGI: i32 = 0x0008;
pub const ZFST_SYST: i32 = 0x0010;
pub const ZFST_NOPS: i32 = 0x0020;
pub const ZFST_NOSZ: i32 = 0x0040;
pub const ZFST_TRSZ: i32 = 0x0080;
pub const ZFST_CLOS: i32 = 0x0100;
pub const ZFPF_SNDP: i32 = 0x01; pub const ZFPF_PASV: i32 = 0x02; pub const ZFPF_DUMB: i32 = 0x04;
#[allow(non_camel_case_types)]
#[derive(Debug, Default)]
pub struct zftp_globals {
sessions: HashMap<String, zftp_session>,
current: Option<String>,
}
static ZFTP_STATE_INNER: OnceLock<Mutex<zftp_globals>> =
OnceLock::new();
#[inline]
fn errno_ptr() -> *mut libc::c_int {
#[cfg(any(target_os = "linux", target_os = "android"))]
unsafe {
libc::__errno_location()
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "dragonfly"
))]
unsafe {
libc::__error()
}
#[cfg(any(target_os = "openbsd", target_os = "netbsd"))]
unsafe {
extern "C" {
fn __errno() -> *mut libc::c_int;
}
__errno()
}
}
fn parse_pasv_response(msg: &str) -> io::Result<(String, u16)> {
let start = msg
.find('(')
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid PASV response"))?;
let end = msg
.find(')')
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid PASV response"))?;
let nums: Vec<i32> = msg[start + 1..end]
.split(',')
.filter_map(|s| s.trim().parse().ok())
.collect();
if nums.len() != 6 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid PASV numbers",
));
}
let oct = |n: i32| -> u8 { n as u8 };
let ip = format!(
"{}.{}.{}.{}",
oct(nums[0]),
oct(nums[1]),
oct(nums[2]),
oct(nums[3])
);
let port = ((oct(nums[4]) as u16) << 8) | (oct(nums[5]) as u16);
Ok((ip, port))
}
pub fn zftp_state_clear_poison() {
if let Some(m) = ZFTP_STATE_INNER.get() {
m.clear_poison();
}
}
pub static ZFDRRRRING: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static ZFALARMED: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static OALREMAIN: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
pub static OALTIME: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
static MODULE_FEATURES: OnceLock<Mutex<crate::ported::zsh_h::features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<crate::ported::zsh_h::features>) -> Vec<String> {
vec!["b:zftp".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<crate::ported::zsh_h::features>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 1]);
}
0
}
fn setfeatureenables(_m: *const module, _f: &Mutex<crate::ported::zsh_h::features>, _e: Option<&[i32]>) -> i32 {
0
}
pub fn zftp_state() -> &'static Mutex<zftp_globals> {
ZFTP_STATE_INNER.get_or_init(|| Mutex::new(zftp_globals::new()))
}
fn module_features() -> &'static Mutex<crate::ported::zsh_h::features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(crate::ported::zsh_h::features {
bn_list: None,
bn_size: 1,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 0,
n_abstract: 0,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transfer_type() {
let _g = crate::test_util::global_state_lock();
let ascii_letter = if (ZFST_ASCI & ZFST_IMAG) != 0 {
"I"
} else {
"A"
};
let image_letter = if (ZFST_IMAG & ZFST_IMAG) != 0 {
"I"
} else {
"A"
};
assert_eq!(ascii_letter, "A");
assert_eq!(image_letter, "I");
}
#[test]
fn test_transfer_mode() {
let _g = crate::test_util::global_state_lock();
let stream_letter = if (ZFST_STRE & ZFST_BLOC) != 0 {
"B"
} else {
"S"
};
let block_letter = if (ZFST_BLOC & ZFST_BLOC) != 0 {
"B"
} else {
"S"
};
assert_eq!(stream_letter, "S");
assert_eq!(block_letter, "B");
}
fn is_positive(c: i32) -> bool {
c >= 100 && c < 400
}
fn is_positive_completion(c: i32) -> bool {
c >= 200 && c < 300
}
fn is_positive_intermediate(c: i32) -> bool {
c >= 300 && c < 400
}
fn is_negative(c: i32) -> bool {
c >= 400
}
#[test]
fn test_ftp_response_positive() {
let _g = crate::test_util::global_state_lock();
let resp: FtpResponse = (200, "OK".to_string());
assert!(is_positive(resp.0));
assert!(is_positive_completion(resp.0));
assert!(!is_negative(resp.0));
}
#[test]
fn test_ftp_response_intermediate() {
let _g = crate::test_util::global_state_lock();
let resp: FtpResponse = (331, "Password required".to_string());
assert!(is_positive(resp.0));
assert!(is_positive_intermediate(resp.0));
assert!(!is_positive_completion(resp.0));
}
#[test]
fn test_ftp_response_negative() {
let _g = crate::test_util::global_state_lock();
let resp: FtpResponse = (550, "File not found".to_string());
assert!(is_negative(resp.0));
assert!(!is_positive(resp.0));
}
#[test]
fn test_ftp_session_new() {
let _g = crate::test_util::global_state_lock();
let sess = zftp_session::new("test");
assert_eq!(sess.name, "test");
assert!(!sess.connected);
assert!(!sess.logged_in);
}
#[test]
fn test_parse_pasv_response() {
let _g = crate::test_util::global_state_lock();
let msg = "227 Entering Passive Mode (192,168,1,1,4,1)";
let (ip, port) = parse_pasv_response(msg).unwrap();
assert_eq!(ip, "192.168.1.1");
assert_eq!(port, 1025);
}
#[test]
fn test_parse_pasv_response_invalid() {
let _g = crate::test_util::global_state_lock();
let msg = "invalid";
assert!(parse_pasv_response(msg).is_err());
}
#[test]
fn test_zftp_new() {
let _g = crate::test_util::global_state_lock();
let zftp = zftp_globals::new();
assert!(zftp.session_names().is_empty());
}
#[test]
fn test_zftp_create_session() {
let _g = crate::test_util::global_state_lock();
let mut zftp = zftp_globals::new();
zftp.create_session("test");
assert!(zftp.sessions.contains_key("test"));
}
#[test]
fn test_zftp_remove_session() {
let _g = crate::test_util::global_state_lock();
let mut zftp = zftp_globals::new();
zftp.create_session("test");
assert!(zftp.remove_session("test").is_some());
assert!(zftp.remove_session("test").is_none());
}
#[test]
fn test_zftp_set_current() {
let _g = crate::test_util::global_state_lock();
let mut zftp = zftp_globals::new();
zftp.create_session("test");
assert!(zftp.set_current("test"));
assert!(!zftp.set_current("nonexistent"));
}
#[test]
fn test_builtin_zftp_no_args() {
let _g = crate::test_util::global_state_lock();
let mut zftp = zftp_globals::new();
let status = bin_zftp(
"zftp",
&[].iter().map(|s: &&str| s.to_string()).collect::<Vec<_>>(),
&options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
},
0,
);
assert_eq!(status, 1);
}
#[test]
fn test_builtin_zftp_session() {
let _g = crate::test_util::global_state_lock();
zftp_cleanup();
let status = bin_zftp(
"zftp",
&["session", "test"]
.iter()
.map(|s: &&str| s.to_string())
.collect::<Vec<_>>(),
&options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
},
0,
);
assert_eq!(status, 0);
assert!(zftp_state().lock().unwrap().sessions.contains_key("test"));
zftp_cleanup();
}
#[test]
fn test_builtin_zftp_test_not_connected() {
let _g = crate::test_util::global_state_lock();
let mut zftp = zftp_globals::new();
let status = bin_zftp(
"zftp",
&["test"]
.iter()
.map(|s: &&str| s.to_string())
.collect::<Vec<_>>(),
&options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
},
0,
);
assert_eq!(status, 1);
}
fn zftp_empty_ops() -> options {
options {
ind: [0u8; crate::ported::zsh_h::MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
#[test]
fn zftp_unknown_subcommand_returns_one() {
let _g = crate::test_util::global_state_lock();
zftp_cleanup();
let ops = zftp_empty_ops();
let r = bin_zftp(
"zftp",
&["definitely_not_a_subcommand_xyz".to_string()],
&ops,
0,
);
assert_eq!(r, 1);
zftp_cleanup();
}
#[test]
fn zftp_state_empty_after_cleanup() {
let _g = crate::test_util::global_state_lock();
zftp_cleanup();
assert!(zftp_state().lock().unwrap().sessions.is_empty());
}
#[test]
fn zfst_type_isolates_transfer_type_bit() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ZFST_TYPE(ZFST_ASCI), 0);
assert_eq!(ZFST_TYPE(ZFST_IMAG), 1);
assert_eq!(ZFST_TYPE(ZFST_IMAG | ZFST_BLOC), 1);
assert_eq!(ZFST_TYPE(ZFST_IMAG | ZFST_LOGI | ZFST_SYST), 1);
assert_eq!(ZFST_TYPE(ZFST_LOGI | ZFST_SYST | ZFST_BLOC), 0);
}
#[test]
fn zfst_mode_isolates_transfer_mode_bit() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ZFST_MODE(ZFST_STRE), 0);
assert_eq!(ZFST_MODE(ZFST_BLOC), 4);
assert_eq!(ZFST_MODE(ZFST_BLOC | ZFST_IMAG), 4);
assert_eq!(ZFST_MODE(ZFST_BLOC | ZFST_LOGI | ZFST_SYST), 4);
let many = ZFST_LOGI | ZFST_SYST | ZFST_NOPS | ZFST_NOSZ | ZFST_TRSZ | ZFST_CLOS;
assert_eq!(ZFST_MODE(many), 0);
}
#[test]
fn zfargstring_empty_args_returns_cmd() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zfargstring("RETR", &[]), "RETR");
assert_eq!(zfargstring("QUIT", &[]), "QUIT");
assert_eq!(zfargstring("", &[]), "");
}
#[test]
fn zfargstring_single_arg_one_space() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zfargstring("RETR", &["file.txt"]), "RETR file.txt");
assert_eq!(zfargstring("USER", &["anonymous"]), "USER anonymous");
}
#[test]
fn zfargstring_multi_arg_space_joined() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zfargstring("USER", &["anonymous", "pass@example.com"]),
"USER anonymous pass@example.com"
);
assert_eq!(
zfargstring("PORT", &["192", "168", "1", "1", "4", "1"]),
"PORT 192 168 1 1 4 1"
);
}
#[test]
fn zfargstring_empty_arg_emits_space() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zfargstring("CMD", &["", "after"]), "CMD after");
assert_eq!(zfargstring("CMD", &["before", ""]), "CMD before ");
}
#[test]
fn zfst_type_constants_are_zero_and_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ZFST_ASCI, 0);
assert_eq!(ZFST_IMAG, 1);
assert_eq!(ZFST_TMSK, 1);
assert_eq!(ZFST_ASCI | ZFST_IMAG, ZFST_TMSK);
}
#[test]
fn zfst_mode_constants_have_correct_bit_position() {
let _g = crate::test_util::global_state_lock();
assert_eq!(ZFST_STRE, 0);
assert_eq!(ZFST_BLOC, 0x04);
assert_eq!(ZFST_MMSK, 0x04);
assert_eq!(ZFST_STRE | ZFST_BLOC, ZFST_MMSK);
assert_eq!(
ZFST_TMSK & ZFST_MMSK,
0,
"c:3907/3919 — type and mode bit-fields must be disjoint"
);
}
#[test]
fn zfst_status_flag_bits_are_pairwise_distinct() {
let _g = crate::test_util::global_state_lock();
let flags = [
ZFST_LOGI, ZFST_SYST, ZFST_NOPS, ZFST_NOSZ, ZFST_TRSZ, ZFST_CLOS,
];
let mut sorted = flags.to_vec();
sorted.sort();
sorted.dedup();
assert_eq!(
sorted.len(),
flags.len(),
"ZFST_* status flags must be pairwise distinct"
);
for f in flags {
assert_eq!(
f & ZFST_TMSK,
0,
"ZFST flag 0x{:x} must not overlap type mask",
f
);
assert_eq!(
f & ZFST_MMSK,
0,
"ZFST flag 0x{:x} must not overlap mode mask",
f
);
}
}
#[test]
fn parse_pasv_response_well_formed_round_trips() {
let _g = crate::test_util::global_state_lock();
let (ip, port) =
parse_pasv_response("227 Entering Passive Mode (192,168,1,1,4,1)").unwrap();
assert_eq!(ip, "192.168.1.1");
assert_eq!(port, 4 * 256 + 1, "p1*256+p2 = 1025");
let (_, port) = parse_pasv_response("227 ok (10,0,0,1,255,255)").unwrap();
assert_eq!(port, 65535);
let (_, port) = parse_pasv_response("227 ok (127,0,0,1,0,21)").unwrap();
assert_eq!(port, 21);
}
#[test]
fn parse_pasv_response_out_of_range_octet_truncates_low_byte() {
let _g = crate::test_util::global_state_lock();
let (_, port) = parse_pasv_response("227 ok (192,168,1,1,300,1)").unwrap();
assert_eq!(
port,
44 * 256 + 1,
"c:949 — (unsigned char)300 = 44; port = 44*256+1"
);
let (_, port) = parse_pasv_response("227 ok (1,2,3,4,1000,2000)").unwrap();
let expected_p1 = (1000i32 as u8) as u16; let expected_p2 = (2000i32 as u8) as u16; assert_eq!(port, (expected_p1 << 8) | expected_p2);
}
#[test]
fn parse_pasv_response_ip_octets_truncate_to_u8() {
let _g = crate::test_util::global_state_lock();
let (ip, _) = parse_pasv_response("227 ok (300,168,1,1,0,21)").unwrap();
assert_eq!(
ip, "44.168.1.1",
"c:947 — octets truncate; out-of-range becomes low byte"
);
}
#[test]
fn parse_pasv_response_wrong_number_count_errors() {
let _g = crate::test_util::global_state_lock();
assert!(
parse_pasv_response("227 ok (1,2,3,4)").is_err(),
"only 4 numbers → error"
);
assert!(
parse_pasv_response("227 ok (1,2,3,4,5)").is_err(),
"only 5 numbers → error"
);
assert!(
parse_pasv_response("227 ok (1,2,3,4,5,6,7)").is_err(),
"7 numbers → error"
);
}
#[test]
fn parse_pasv_response_missing_parens_errors() {
let _g = crate::test_util::global_state_lock();
assert!(
parse_pasv_response("227 ok no parens here").is_err(),
"missing both parens → error"
);
assert!(
parse_pasv_response("227 ok (no_close").is_err(),
"missing close paren → error"
);
assert!(
parse_pasv_response("227 ok no_open)").is_err(),
"missing open paren → error"
);
}
#[test]
fn zftp_corpus_zfst_type_masks_type_bits() {
assert_eq!(ZFST_TYPE(ZFST_ASCI), ZFST_ASCI, "ASCI type bit");
assert_eq!(ZFST_TYPE(ZFST_IMAG), ZFST_IMAG, "IMAG type bit");
}
#[test]
fn zftp_corpus_zfst_type_ignores_mode_bits() {
let combined = ZFST_IMAG | ZFST_BLOC;
assert_eq!(ZFST_TYPE(combined), ZFST_IMAG,
"ZFST_TYPE strips mode bits");
}
#[test]
fn zftp_corpus_zfst_mode_masks_mode_bits() {
assert_eq!(ZFST_MODE(ZFST_STRE), ZFST_STRE, "stream mode");
assert_eq!(ZFST_MODE(ZFST_BLOC), ZFST_BLOC, "block mode");
}
#[test]
fn zftp_corpus_zfst_mode_ignores_type_bits() {
let combined = ZFST_IMAG | ZFST_BLOC;
assert_eq!(ZFST_MODE(combined), ZFST_BLOC,
"ZFST_MODE strips type bits");
}
#[test]
fn zftp_corpus_zfst_type_mode_masks_disjoint() {
assert_eq!(ZFST_TMSK & ZFST_MMSK, 0,
"type-mask and mode-mask must be disjoint");
}
#[test]
fn zftp_corpus_zfst_zero_input_returns_zero() {
assert_eq!(ZFST_TYPE(0), 0);
assert_eq!(ZFST_MODE(0), 0);
}
}