use std::os::unix::io::RawFd;
use crate::ported::modules::tcp_h::tcp_sockaddr;
use crate::ported::modules::tcp_h::tcp_session;
use crate::ported::modules::tcp_h::ZTCP_LISTEN;
use crate::ported::modules::tcp_h::ZTCP_INBOUND;
use crate::ported::modules::tcp_h::ZTCP_ZFTP;
use std::net::ToSocketAddrs;
use crate::ported::zsh_h::{OPT_ISSET, OPT_ARG};
use crate::ported::utils::{zwarnnam, zerrnam};
impl Default for tcp_sockaddr {
fn default() -> Self {
Self { a: unsafe { std::mem::zeroed() } }
}
}
impl Default for tcp_session {
fn default() -> Self {
Self {
fd: -1,
sock: tcp_sockaddr::default(),
peer: tcp_sockaddr::default(),
flags: 0,
}
}
}
thread_local! {
static ZTCP_SESSIONS: std::cell::RefCell<Vec<tcp_session>> = const {
std::cell::RefCell::new(Vec::new())
};
}
pub fn zsh_inet_ntop(af: i32, addr_bytes: &[u8]) -> Option<String> { if af == libc::AF_INET && addr_bytes.len() >= 4 {
let v4 = std::net::Ipv4Addr::new(addr_bytes[0], addr_bytes[1], addr_bytes[2], addr_bytes[3]);
Some(v4.to_string())
} else if af == libc::AF_INET6 && addr_bytes.len() >= 16 {
let mut octets = [0u8; 16];
octets.copy_from_slice(&addr_bytes[..16]);
Some(std::net::Ipv6Addr::from(octets).to_string())
} else {
None }
}
pub fn zsh_inet_aton(src: &str) -> Option<u32> { src.parse::<std::net::Ipv4Addr>().ok().map(|a| u32::from(a).to_be())
}
pub fn zsh_inet_pton(af: i32, src: &str, dst: &mut [u8]) -> i32 { if af == libc::AF_INET {
match src.parse::<std::net::Ipv4Addr>() {
Ok(v4) if dst.len() >= 4 => {
dst[..4].copy_from_slice(&v4.octets());
1
}
_ => 0,
}
} else if af == libc::AF_INET6 {
match src.parse::<std::net::Ipv6Addr>() {
Ok(v6) if dst.len() >= 16 => {
dst[..16].copy_from_slice(&v6.octets());
1
}
_ => 0,
}
} else {
-1
}
}
pub fn zsh_gethostbyname2(name: &str, af: i32) -> Vec<[u8; 4]> { let mut out = Vec::new();
if af == libc::AF_INET { if let Ok(iter) = format!("{}:0", name).to_socket_addrs() {
for sa in iter {
if let std::net::SocketAddr::V4(v4) = sa {
out.push(v4.ip().octets());
}
}
}
}
out
}
pub fn zsh_getipnodebyname(name: &str, af: i32) -> Vec<[u8; 4]> { zsh_gethostbyname2(name, af) }
pub fn freehostent() { }
pub fn zts_alloc(ztflags: i32) -> usize { ZTCP_SESSIONS.with(|s| {
let mut sessions = s.borrow_mut();
let idx = sessions.len();
sessions.push(tcp_session { fd: -1, sock: tcp_sockaddr::default(),
peer: tcp_sockaddr::default(),
flags: ztflags, });
idx })
}
type TcpSessionHandle = Option<usize>;
fn sess_get<R, F: FnOnce(&tcp_session) -> R>(idx: usize, f: F) -> R {
ZTCP_SESSIONS.with(|s| {
let g = s.borrow();
f(&g[idx])
})
}
fn sess_with<F: FnOnce(&mut tcp_session)>(idx: usize, f: F) {
ZTCP_SESSIONS.with(|s| {
let mut g = s.borrow_mut();
f(&mut g[idx])
});
}
pub fn tcp_socket(domain: i32, ty: i32, protocol: i32, ztflags: i32) -> TcpSessionHandle { let idx = zts_alloc(ztflags); let fd = unsafe { libc::socket(domain, ty, protocol) }; sess_with(idx, |s| { s.fd = fd; }); if fd >= 0 {
crate::ported::utils::addmodulefd(fd); }
Some(idx) }
pub fn ztcp_free_session(sess: usize) -> i32 { 0 }
pub fn zts_delete(fd: i32) -> i32 { ZTCP_SESSIONS.with(|s| {
let mut sessions = s.borrow_mut();
let pos = sessions.iter().position(|sess| sess.fd == fd); match pos {
Some(i) => { sessions.remove(i);
0 }
None => 1, }
})
}
pub fn zts_byfd(fd: RawFd) -> TcpSessionHandle { ZTCP_SESSIONS.with(|s| {
s.borrow().iter().position(|sess| sess.fd == fd) })
}
pub fn tcp_cleanup() { ZTCP_SESSIONS.with(|s| {
let mut sessions = s.borrow_mut();
for sess in sessions.drain(..) { if sess.fd >= 0 {
unsafe { libc::close(sess.fd); }
}
}
});
}
pub fn tcp_close(sess: TcpSessionHandle) -> i32 { if let Some(idx) = sess { let fd = sess_get(idx, |s| s.fd);
let mut err = -1;
if fd != -1 { err = unsafe { libc::close(fd) }; if err != 0 {
crate::ported::utils::zwarn(&format!(
"connection close failed: {}",
std::io::Error::last_os_error()));
}
}
let _ = zts_delete(fd);
return err; }
0 }
pub fn tcp_connect(sess: TcpSessionHandle, addr: &[u8; 4], d_port: u16) -> i32 { let idx = match sess { Some(i) => i, None => return -1 };
let fd = sess_get(idx, |s| s.fd);
let mut peer: libc::sockaddr_in = unsafe { std::mem::zeroed() };
peer.sin_family = libc::AF_INET as _; peer.sin_port = d_port; peer.sin_addr.s_addr = u32::from_be_bytes(*addr).to_be(); sess_with(idx, |s| { s.peer.in_ = peer; });
unsafe {
libc::connect(fd, &peer as *const _ as *const libc::sockaddr,
std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t)
}
}
#[allow(non_snake_case)]
pub fn bin_ztcp(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let mut err: i32 = 1; let destport: u16; let mut force = 0i32; let mut verbose = 0i32; let mut test = 0i32; let mut targetfd: i32 = 0; let mut len: libc::socklen_t; let desthost: String; let mut sess: TcpSessionHandle = None;
if OPT_ISSET(ops, b'f') { force = 1; } if OPT_ISSET(ops, b'v') { verbose = 1; } if OPT_ISSET(ops, b't') { test = 1; }
if OPT_ISSET(ops, b'd') { let darg = OPT_ARG(ops, b'd').unwrap_or("");
targetfd = darg.parse::<i32>().unwrap_or(0); if targetfd == 0 { zwarnnam(nam, &format!("{} is an invalid argument to -d", darg));
return 1; }
}
if OPT_ISSET(ops, b'c') { if args.is_empty() { tcp_cleanup(); } else {
targetfd = args[0].parse::<i32>().unwrap_or(0); sess = zts_byfd(targetfd); if targetfd == 0 { zwarnnam(nam, &format!("{} is an invalid argument to -c", args[0]));
return 1; }
if let Some(sidx) = sess { let flags = sess_get(sidx, |s| s.flags);
if (flags & ZTCP_ZFTP) != 0 && force == 0 { zwarnnam(nam, "use -f to force closure of a zftp control connection");
return 1; }
tcp_close(sess); return 0; } else { zwarnnam(nam, &format!("fd {} not found in tcp table", args[0]));
return 1; }
}
} else if OPT_ISSET(ops, b'l') { let lport: u16; if args.is_empty() { zwarnnam(nam, "-l requires an argument");
return 1; }
let srv = {
let cname = std::ffi::CString::new(args[0].as_str()).ok();
let cproto = std::ffi::CString::new("tcp").unwrap();
cname.and_then(|c| {
let p = unsafe { libc::getservbyname(c.as_ptr(), cproto.as_ptr()) };
if p.is_null() { None } else { Some(unsafe { (*p).s_port } as u16) }
})
};
lport = match srv { Some(p) => p, None => (args[0].parse::<u16>().unwrap_or(0)).to_be(), };
if lport == 0 { zwarnnam(nam, "bad service name or port number");
return 1; }
sess = tcp_socket(libc::PF_INET, libc::SOCK_STREAM, 0, ZTCP_LISTEN); if sess.is_none() { zwarnnam(nam, "unable to allocate a TCP session slot");
return 1; }
let sidx = sess.unwrap();
let one: i32 = 1;
let fd = sess_get(sidx, |s| s.fd);
unsafe {
libc::setsockopt(fd, libc::SOL_SOCKET, libc::SO_OOBINLINE,
&one as *const _ as *const libc::c_void,
std::mem::size_of::<i32>() as libc::socklen_t);
}
let mut sin: libc::sockaddr_in = unsafe { std::mem::zeroed() };
sin.sin_family = libc::AF_INET as _; sin.sin_port = lport; sin.sin_addr.s_addr = 0u32.to_be(); sess_with(sidx, |s| { s.sock.in_ = sin; });
let r = unsafe {
libc::bind(fd, &sin as *const _ as *const libc::sockaddr,
std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t)
};
if r != 0 { zwarnnam(nam, &format!("could not bind to port {}: {}", u16::from_be(lport), std::io::Error::last_os_error()));
tcp_close(sess); return 1; }
if unsafe { libc::listen(fd, 1) } != 0 { zwarnnam(nam, &format!("could not listen on socket: {}", std::io::Error::last_os_error()));
tcp_close(sess); return 1; }
if targetfd != 0 { let nfd = crate::ported::utils::redup(fd, targetfd); sess_with(sidx, |s| { s.fd = nfd; });
} else {
let nfd = crate::ported::utils::movefd(fd); sess_with(sidx, |s| { s.fd = nfd; });
}
let nfd = sess_get(sidx, |s| s.fd);
if nfd == -1 { zwarnnam(nam, &format!("cannot duplicate fd {}: {}", nfd, std::io::Error::last_os_error()));
tcp_close(sess); return 1; }
crate::ported::params::setiparam("REPLY", nfd as i64); if verbose != 0 { println!("{} listener is on fd {}", u16::from_be(lport), nfd);
}
return 0; } else if OPT_ISSET(ops, b'a') { let lfd: i32;
let rfd: i32;
if args.is_empty() { zwarnnam(nam, "-a requires an argument");
return 1; }
lfd = args[0].parse::<i32>().unwrap_or(0); if lfd == 0 { zwarnnam(nam, "invalid numerical argument");
return 1; }
sess = zts_byfd(lfd); if sess.is_none() { zwarnnam(nam, &format!("fd {} is not registered as a tcp connection",
args[0]));
return 1; }
let flags = sess_get(sess.unwrap(), |s| s.flags);
if (flags & ZTCP_LISTEN) == 0 { zwarnnam(nam, "tcp connection not a listener");
return 1; }
if test != 0 { let mut pfd = libc::pollfd { fd: lfd, events: libc::POLLIN, revents: 0 };
let ret = unsafe { libc::poll(&mut pfd, 1, 0) }; if ret == 0 { return 1; } else if ret == -1 { zwarnnam(nam, &format!("poll error: {}", std::io::Error::last_os_error()));
return 1; }
}
sess = Some(zts_alloc(ZTCP_INBOUND)); let sidx = sess.unwrap();
let mut peer: libc::sockaddr_in = unsafe { std::mem::zeroed() };
len = std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t; loop { let r = unsafe { libc::accept(lfd,
&mut peer as *mut _ as *mut libc::sockaddr,
&mut len as *mut _) };
if r >= 0 || std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR)
|| crate::ported::utils::errflag
.load(std::sync::atomic::Ordering::Relaxed) != 0 {
rfd = r;
break;
}
}
sess_with(sidx, |s| { s.peer.in_ = peer; });
if rfd == -1 { zwarnnam(nam, &format!("could not accept connection: {}", std::io::Error::last_os_error()));
tcp_close(sess); return 1; }
crate::ported::utils::addmodulefd(rfd); if targetfd != 0 { let nfd = crate::ported::utils::redup(rfd, targetfd); sess_with(sidx, |s| { s.fd = nfd; });
if nfd < 0 { zerrnam(nam, &format!("could not duplicate socket fd to {}: {}",
targetfd, std::io::Error::last_os_error()));
return 1; }
} else {
sess_with(sidx, |s| { s.fd = rfd; }); }
let nfd = sess_get(sidx, |s| s.fd);
crate::ported::params::setiparam("REPLY", nfd as i64); if verbose != 0 { println!("{} is on fd {}", u16::from_be(peer.sin_port), nfd); }
} else { if args.is_empty() { ZTCP_SESSIONS.with(|s| {
for sess in s.borrow().iter() { if sess.fd != -1 { let lname = {
let b = u32::from_be(unsafe { sess.sock.in_.sin_addr.s_addr }).to_be_bytes();
format!("{}.{}.{}.{}", b[0], b[1], b[2], b[3])
};
let pname = {
let b = u32::from_be(unsafe { sess.peer.in_.sin_addr.s_addr }).to_be_bytes();
format!("{}.{}.{}.{}", b[0], b[1], b[2], b[3])
};
let lport = u16::from_be(unsafe { sess.sock.in_.sin_port });
let pport = u16::from_be(unsafe { sess.peer.in_.sin_port });
if OPT_ISSET(ops, b'L') { let schar = if (sess.flags & ZTCP_ZFTP) != 0 { 'Z' } else if (sess.flags & ZTCP_LISTEN) != 0 { 'L' } else if (sess.flags & ZTCP_INBOUND)!= 0 { 'I' } else { 'O' }; println!("{} {} {} {} {} {}", sess.fd, schar, lname, lport, pname, pport);
} else { let arrow = if (sess.flags & ZTCP_LISTEN) != 0 { "-<" }
else if (sess.flags & ZTCP_INBOUND) != 0 { "<-" }
else { "->" };
let zftp = if (sess.flags & ZTCP_ZFTP) != 0 { " ZFTP" } else { "" };
println!("{}:{} {} {}:{} is on fd {}{}", lname, lport, arrow, pname, pport, sess.fd, zftp);
}
}
}
});
return 0; } else if args.len() == 1 { destport = (23u16).to_be(); } else {
let srv = {
let cname = std::ffi::CString::new(args[1].as_str()).ok();
let cproto = std::ffi::CString::new("tcp").unwrap();
cname.and_then(|c| {
let p = unsafe { libc::getservbyname(c.as_ptr(), cproto.as_ptr()) };
if p.is_null() { None } else { Some(unsafe { (*p).s_port } as u16) }
})
};
destport = match srv { Some(p) => p, None => (args[1].parse::<u16>().unwrap_or(0)).to_be(), };
}
desthost = args[0].clone(); let zthost = zsh_getipnodebyname(&desthost, libc::AF_INET); if zthost.is_empty() { zwarnnam(nam, &format!("host resolution failure: {}", desthost));
return 1; }
sess = tcp_socket(libc::PF_INET, libc::SOCK_STREAM, 0, 0); if sess.is_none() { zwarnnam(nam, "unable to allocate a TCP session slot");
return 1; }
let sidx = sess.unwrap();
let one: i32 = 1;
let fd = sess_get(sidx, |s| s.fd);
unsafe { libc::setsockopt(fd, libc::SOL_SOCKET, libc::SO_OOBINLINE,
&one as *const _ as *const libc::c_void,
std::mem::size_of::<i32>() as libc::socklen_t);
}
if fd < 0 { zwarnnam(nam, &format!("socket creation failed: {}", std::io::Error::last_os_error()));
zts_delete(fd); return 1; }
for addr in &zthost { loop { err = tcp_connect(sess, addr, destport); if err == 0 || std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR)
|| crate::ported::utils::errflag
.load(std::sync::atomic::Ordering::Relaxed) != 0 { break; }
}
if err == 0 { break; }
}
if err != 0 { zwarnnam(nam, &format!("connection failed: {}", std::io::Error::last_os_error()));
tcp_close(sess); return 1; } else { if targetfd != 0 { let nfd = crate::ported::utils::redup(fd, targetfd); sess_with(sidx, |s| { s.fd = nfd; });
if nfd < 0 { zerrnam(nam, &format!("could not duplicate socket fd to {}: {}",
targetfd, std::io::Error::last_os_error())); tcp_close(sess); return 1; }
}
let nfd = sess_get(sidx, |s| s.fd);
crate::ported::params::setiparam("REPLY", nfd as i64); if verbose != 0 { println!("{}:{} is now on fd {}", desthost, u16::from_be(destport), nfd);
}
}
}
let _ = len; 0 }
use crate::ported::zsh_h::module;
#[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 }
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 { ZTCP_SESSIONS.with(|s| s.borrow_mut().clear()); 0
}
pub fn cleanup_(m: *const module) -> i32 { tcp_cleanup(); setfeatureenables(m, module_features(), None) }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 { 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zts_alloc_creates_session_with_default_fd() {
let _ = zts_alloc(ZTCP_LISTEN);
ZTCP_SESSIONS.with(|s| {
let sessions = s.borrow();
assert!(!sessions.is_empty());
let last = sessions.last().unwrap();
assert_eq!(last.fd, -1);
assert_eq!(last.flags, ZTCP_LISTEN);
});
}
#[test]
fn inet_ntop_v4_works() {
let bytes = [127u8, 0, 0, 1];
assert_eq!(zsh_inet_ntop(libc::AF_INET, &bytes).as_deref(), Some("127.0.0.1"));
}
#[test]
fn inet_pton_v4_works() {
let mut buf = [0u8; 4];
assert_eq!(zsh_inet_pton(libc::AF_INET, "127.0.0.1", &mut buf), 1);
assert_eq!(buf, [127, 0, 0, 1]);
}
#[test]
fn inet_pton_invalid_returns_zero() {
let mut buf = [0u8; 4];
assert_eq!(zsh_inet_pton(libc::AF_INET, "bad-ip", &mut buf), 0);
}
}
use crate::ported::zsh_h::features as features_t;
use std::sync::{Mutex, OnceLock};
use crate::modules::tcp_h::Tcp_session;
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
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,
}))
}
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["b:ztcp".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<features_t>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 1]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}