From 37039a773f676b18445693fc73e6dbb497c544f5 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Sat, 30 May 2026 19:35:27 +0000 Subject: [PATCH] nwconn: implement AFP create file through NetWare file creation --- src/nwconn.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/nwconn.c b/src/nwconn.c index 7292566..d56d243 100644 --- a/src/nwconn.c +++ b/src/nwconn.c @@ -921,6 +921,130 @@ static int afp_alloc_temporary_dir_handle(uint8 *afp_req, int afp_len, } + +static int afp_create_file(uint8 *afp_req, int afp_len, + uint8 *response, const char *call_name) +/* + * WebSDK / nwafp.h calls 0x02 and 0x0e create an AFP file and return the + * new AFP file ID. AFP Create File creates the file but does not leave it + * open, so route creation through the existing mars_nwe node helpers rather + * than open(2) directly. FinderInfo remains AFP-only metadata and is written + * after the file exists. + */ +{ + uint8 volume_number; + uint32 base_entry_id; + int delete_existing; + uint8 *finder_info; + int is_afp20; + int path_len_off; + int path_off; + int path_len; + uint8 create_path[512]; + char unixname[PATH_MAX]; + int volume; + int result; + struct stat stbuff; + uint32 entry_id = 0; + int fallback = 0; + + if (afp_len < 40) { + XDPRINTF((2,0, "%s rejected: short request len=%d", + call_name, afp_len)); + return(-0x7e); + } + + is_afp20 = (afp_req[0] == 0x0e); + path_len_off = is_afp20 ? 45 : 39; + path_off = is_afp20 ? 46 : 40; + if (afp_len <= path_len_off) { + XDPRINTF((2,0, "%s rejected: short request len=%d", + call_name, afp_len)); + return(-0x7e); + } + + volume_number = afp_req[1]; + base_entry_id = GET_BE32(afp_req + 2); + delete_existing = afp_req[6] ? 1 : 0; + finder_info = afp_req + 7; + path_len = (int)afp_req[path_len_off]; + if (path_len < 0 || afp_len < path_off + path_len) { + XDPRINTF((2,0, "%s rejected: boundary check len=%d path_len=%d", + call_name, afp_len, path_len)); + return(-0x7e); + } + + if (!nwatalk_backend_available()) { + XDPRINTF((3,0, "%s rejected: libatalk backend unavailable", call_name)); + return(-0xbf); + } + if (!path_len) { + XDPRINTF((2,0, "%s rejected: empty path vol=%d base=0x%08x", + call_name, (int)volume_number, base_entry_id)); + return(-0x9c); + } + + result = afp_build_base_relative_path((int)volume_number, base_entry_id, + afp_req + path_off, path_len, + create_path, sizeof(create_path)); + if (result < 0) { + XDPRINTF((2,0, "%s path build failed: vol=%d base=0x%08x path='%s' result=-0x%x", + call_name, (int)volume_number, base_entry_id, + visable_data(afp_req + path_off, path_len), -result)); + return(result); + } + + volume = conn_get_kpl_unxname(unixname, sizeof(unixname), 0, + create_path, strlen((char *)create_path)); + if (volume < 0) return(volume); + + if (!stat(unixname, &stbuff)) { + if (S_ISDIR(stbuff.st_mode)) { + XDPRINTF((2,0, "%s rejected: existing directory vol=%d base=0x%08x path='%s' unix='%s'", + call_name, (int)volume_number, base_entry_id, + visable_data(afp_req + path_off, path_len), unixname)); + return(-0xff); + } + if (!delete_existing) { + XDPRINTF((2,0, "%s rejected: file exists vol=%d base=0x%08x path='%s' unix='%s'", + call_name, (int)volume_number, base_entry_id, + visable_data(afp_req + path_off, path_len), unixname)); + return(-0xff); + } + + result = nw_delete_files(0, 0, create_path, strlen((char *)create_path)); + if (result < 0) { + XDPRINTF((2,0, "%s delete-existing failed: vol=%d base=0x%08x path='%s' mars_path='%s' result=-0x%x", + call_name, (int)volume_number, base_entry_id, + visable_data(afp_req + path_off, path_len), create_path, + -result)); + return(result); + } + } + + result = nw_creat_node(volume, (uint8 *)unixname, 0); + if (result < 0) { + XDPRINTF((2,0, "%s create failed: vol=%d base=0x%08x path='%s' mars_path='%s' unix='%s' result=-0x%x", + call_name, (int)volume_number, base_entry_id, + visable_data(afp_req + path_off, path_len), create_path, + unixname, -result)); + return(result); + } + + if (stat(unixname, &stbuff)) return(-0x9c); + (void)nwatalk_set_finder_info(unixname, finder_info, 32); + + entry_id = afp_get_or_create_entry_id(unixname, volume, &stbuff, &fallback); + U32_TO_BE32(entry_id, response); + + XDPRINTF((3,0, "%s: request_vol=%d resolved_vol=%d base=0x%08x path='%s' entry=0x%08x%s", + call_name, (int)volume_number, volume, base_entry_id, + visable_data(afp_req + path_off, path_len), entry_id, + fallback ? " fallback" : "")); + return(4); +} + + static int afp_create_directory(uint8 *afp_req, int afp_len, uint8 *response, const char *call_name) /* @@ -4059,6 +4183,12 @@ static int handle_ncp_serv(void) afp_call_name(ufunc)); if (result > -1) data_len = result; else completition = (uint8)-result; + } else if (ufunc == 0x02 || ufunc == 0x0e) { + int result = afp_create_file(afp_req, + afp_len, responsedata, + afp_call_name(ufunc)); + if (result > -1) data_len = result; + else completition = (uint8)-result; } else if (ufunc == 0x04) { int result = afp_get_entry_id_from_name(afp_req, afp_len, responsedata);