mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
c7d71dd125
Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2851 lines
66 KiB
C
2851 lines
66 KiB
C
/*
|
|
* Samba AppleDouble helpers
|
|
*
|
|
* Copyright (C) Ralph Boehme, 2019
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "adouble.h"
|
|
#include "MacExtensions.h"
|
|
#include "string_replace.h"
|
|
#include "smbd/smbd.h"
|
|
#include "system/filesys.h"
|
|
#include "libcli/security/security.h"
|
|
#include "lib/util_macstreams.h"
|
|
#include "auth.h"
|
|
|
|
/*
|
|
"._" AppleDouble Header File Layout:
|
|
|
|
MAGIC 0x00051607
|
|
VERSION 0x00020000
|
|
FILLER 0
|
|
COUNT 2
|
|
.-- AD ENTRY[0] Finder Info Entry (must be first)
|
|
.--+-- AD ENTRY[1] Resource Fork Entry (must be last)
|
|
| | /////////////
|
|
| '-> FINDER INFO Fixed Size Data (32 Bytes)
|
|
| ~~~~~~~~~~~~~ 2 Bytes Padding
|
|
| EXT ATTR HDR Fixed Size Data (36 Bytes)
|
|
| /////////////
|
|
| ATTR ENTRY[0] --.
|
|
| ATTR ENTRY[1] --+--.
|
|
| ATTR ENTRY[2] --+--+--.
|
|
| ... | | |
|
|
| ATTR ENTRY[N] --+--+--+--.
|
|
| ATTR DATA 0 <-' | | |
|
|
| //////////// | | |
|
|
| ATTR DATA 1 <----' | |
|
|
| ///////////// | |
|
|
| ATTR DATA 2 <-------' |
|
|
| ///////////// |
|
|
| ... |
|
|
| ATTR DATA N <----------'
|
|
| /////////////
|
|
| ... Attribute Free Space
|
|
|
|
|
'----> RESOURCE FORK
|
|
... Variable Sized Data
|
|
...
|
|
*/
|
|
|
|
/* Number of actually used entries */
|
|
#define ADEID_NUM_XATTR 8
|
|
#define ADEID_NUM_DOT_UND 2
|
|
#define ADEID_NUM_RSRC_XATTR 1
|
|
|
|
/* Sizes of relevant entry bits */
|
|
#define ADEDLEN_MAGIC 4
|
|
#define ADEDLEN_VERSION 4
|
|
#define ADEDLEN_FILLER 16
|
|
#define AD_FILLER_TAG "Netatalk " /* should be 16 bytes */
|
|
#define AD_FILLER_TAG_OSX "Mac OS X " /* should be 16 bytes */
|
|
#define ADEDLEN_NENTRIES 2
|
|
#define AD_HEADER_LEN (ADEDLEN_MAGIC + ADEDLEN_VERSION + \
|
|
ADEDLEN_FILLER + ADEDLEN_NENTRIES) /* 26 */
|
|
#define AD_ENTRY_LEN_EID 4
|
|
#define AD_ENTRY_LEN_OFF 4
|
|
#define AD_ENTRY_LEN_LEN 4
|
|
#define AD_ENTRY_LEN (AD_ENTRY_LEN_EID + AD_ENTRY_LEN_OFF + AD_ENTRY_LEN_LEN)
|
|
|
|
/* Offsets */
|
|
#define ADEDOFF_MAGIC 0
|
|
#define ADEDOFF_VERSION (ADEDOFF_MAGIC + ADEDLEN_MAGIC)
|
|
#define ADEDOFF_FILLER (ADEDOFF_VERSION + ADEDLEN_VERSION)
|
|
#define ADEDOFF_NENTRIES (ADEDOFF_FILLER + ADEDLEN_FILLER)
|
|
|
|
#define ADEDOFF_FINDERI_XATTR (AD_HEADER_LEN + \
|
|
(ADEID_NUM_XATTR * AD_ENTRY_LEN))
|
|
#define ADEDOFF_COMMENT_XATTR (ADEDOFF_FINDERI_XATTR + ADEDLEN_FINDERI)
|
|
#define ADEDOFF_FILEDATESI_XATTR (ADEDOFF_COMMENT_XATTR + ADEDLEN_COMMENT)
|
|
#define ADEDOFF_AFPFILEI_XATTR (ADEDOFF_FILEDATESI_XATTR + \
|
|
ADEDLEN_FILEDATESI)
|
|
#define ADEDOFF_PRIVDEV_XATTR (ADEDOFF_AFPFILEI_XATTR + ADEDLEN_AFPFILEI)
|
|
#define ADEDOFF_PRIVINO_XATTR (ADEDOFF_PRIVDEV_XATTR + ADEDLEN_PRIVDEV)
|
|
#define ADEDOFF_PRIVSYN_XATTR (ADEDOFF_PRIVINO_XATTR + ADEDLEN_PRIVINO)
|
|
#define ADEDOFF_PRIVID_XATTR (ADEDOFF_PRIVSYN_XATTR + ADEDLEN_PRIVSYN)
|
|
|
|
#define ADEDOFF_FINDERI_DOT_UND (AD_HEADER_LEN + \
|
|
(ADEID_NUM_DOT_UND * AD_ENTRY_LEN))
|
|
#define ADEDOFF_RFORK_DOT_UND (ADEDOFF_FINDERI_DOT_UND + ADEDLEN_FINDERI)
|
|
|
|
#define AD_DATASZ_XATTR (AD_HEADER_LEN + \
|
|
(ADEID_NUM_XATTR * AD_ENTRY_LEN) + \
|
|
ADEDLEN_FINDERI + ADEDLEN_COMMENT + \
|
|
ADEDLEN_FILEDATESI + ADEDLEN_AFPFILEI + \
|
|
ADEDLEN_PRIVDEV + ADEDLEN_PRIVINO + \
|
|
ADEDLEN_PRIVSYN + ADEDLEN_PRIVID)
|
|
|
|
#if AD_DATASZ_XATTR != 402
|
|
#error bad size for AD_DATASZ_XATTR
|
|
#endif
|
|
|
|
#define AD_DATASZ_DOT_UND (AD_HEADER_LEN + \
|
|
(ADEID_NUM_DOT_UND * AD_ENTRY_LEN) + \
|
|
ADEDLEN_FINDERI)
|
|
#if AD_DATASZ_DOT_UND != 82
|
|
#error bad size for AD_DATASZ_DOT_UND
|
|
#endif
|
|
|
|
#define AD_XATTR_HDR_MAGIC 0x41545452 /* 'ATTR' */
|
|
#define AD_XATTR_MAX_ENTRIES 1024 /* Some arbitrarily enforced limit */
|
|
#define AD_XATTR_HDR_SIZE 36
|
|
#define AD_XATTR_MAX_HDR_SIZE 65536
|
|
#define ADX_ENTRY_FIXED_SIZE (4+4+2+1)
|
|
|
|
/*
|
|
* Both struct ad_xattr_header and struct ad_xattr_entry describe the in memory
|
|
* representation as well as the on-disk format.
|
|
*
|
|
* The ad_xattr_header follows the FinderInfo data in the FinderInfo entry if
|
|
* the length of the FinderInfo entry is larger then 32 bytes. It is then
|
|
* preceded with 2 bytes padding.
|
|
*
|
|
* Cf: https://opensource.apple.com/source/xnu/xnu-4570.1.46/bsd/vfs/vfs_xattr.c
|
|
*/
|
|
|
|
struct ad_xattr_header {
|
|
uint32_t adx_magic; /* ATTR_HDR_MAGIC */
|
|
uint32_t adx_debug_tag; /* for debugging == file id of owning file */
|
|
uint32_t adx_total_size; /* file offset of end of attribute header + entries + data */
|
|
uint32_t adx_data_start; /* file offset to attribute data area */
|
|
uint32_t adx_data_length; /* length of attribute data area */
|
|
uint32_t adx_reserved[3];
|
|
uint16_t adx_flags;
|
|
uint16_t adx_num_attrs;
|
|
};
|
|
|
|
/* On-disk entries are aligned on 4 byte boundaries */
|
|
struct ad_xattr_entry {
|
|
uint32_t adx_offset; /* file offset to data */
|
|
uint32_t adx_length; /* size of attribute data */
|
|
uint16_t adx_flags;
|
|
uint8_t adx_namelen; /* included the NULL terminator */
|
|
char *adx_name; /* NULL-terminated UTF-8 name */
|
|
};
|
|
|
|
struct ad_entry {
|
|
size_t ade_off;
|
|
size_t ade_len;
|
|
};
|
|
|
|
struct adouble {
|
|
files_struct *ad_fsp;
|
|
bool ad_opened;
|
|
adouble_type_t ad_type;
|
|
uint32_t ad_magic;
|
|
uint32_t ad_version;
|
|
uint8_t ad_filler[ADEDLEN_FILLER];
|
|
struct ad_entry ad_eid[ADEID_MAX];
|
|
char *ad_data;
|
|
char *ad_rsrc_data;
|
|
struct ad_xattr_header adx_header;
|
|
struct ad_xattr_entry *adx_entries;
|
|
char *adx_data;
|
|
};
|
|
|
|
struct ad_entry_order {
|
|
uint32_t id, offset, len;
|
|
};
|
|
|
|
/* Netatalk AppleDouble metadata xattr */
|
|
static const
|
|
struct ad_entry_order entry_order_meta_xattr[ADEID_NUM_XATTR + 1] = {
|
|
{ADEID_FINDERI, ADEDOFF_FINDERI_XATTR, ADEDLEN_FINDERI},
|
|
{ADEID_COMMENT, ADEDOFF_COMMENT_XATTR, 0},
|
|
{ADEID_FILEDATESI, ADEDOFF_FILEDATESI_XATTR, ADEDLEN_FILEDATESI},
|
|
{ADEID_AFPFILEI, ADEDOFF_AFPFILEI_XATTR, ADEDLEN_AFPFILEI},
|
|
{ADEID_PRIVDEV, ADEDOFF_PRIVDEV_XATTR, 0},
|
|
{ADEID_PRIVINO, ADEDOFF_PRIVINO_XATTR, 0},
|
|
{ADEID_PRIVSYN, ADEDOFF_PRIVSYN_XATTR, 0},
|
|
{ADEID_PRIVID, ADEDOFF_PRIVID_XATTR, 0},
|
|
{0, 0, 0}
|
|
};
|
|
|
|
/* AppleDouble resource fork file (the ones prefixed by "._") */
|
|
static const
|
|
struct ad_entry_order entry_order_dot_und[ADEID_NUM_DOT_UND + 1] = {
|
|
{ADEID_FINDERI, ADEDOFF_FINDERI_DOT_UND, ADEDLEN_FINDERI},
|
|
{ADEID_RFORK, ADEDOFF_RFORK_DOT_UND, 0},
|
|
{0, 0, 0}
|
|
};
|
|
|
|
/* Conversion from enumerated id to on-disk AppleDouble id */
|
|
#define AD_EID_DISK(a) (set_eid[a])
|
|
static const uint32_t set_eid[] = {
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
|
AD_DEV, AD_INO, AD_SYN, AD_ID
|
|
};
|
|
|
|
static char empty_resourcefork[] = {
|
|
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E,
|
|
0x54, 0x68, 0x69, 0x73, 0x20, 0x72, 0x65, 0x73,
|
|
0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x66, 0x6F,
|
|
0x72, 0x6B, 0x20, 0x69, 0x6E, 0x74, 0x65, 0x6E,
|
|
0x74, 0x69, 0x6F, 0x6E, 0x61, 0x6C, 0x6C, 0x79,
|
|
0x20, 0x6C, 0x65, 0x66, 0x74, 0x20, 0x62, 0x6C,
|
|
0x61, 0x6E, 0x6B, 0x20, 0x20, 0x20, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x1C, 0x00, 0x1E, 0xFF, 0xFF
|
|
};
|
|
|
|
size_t ad_getentrylen(const struct adouble *ad, int eid)
|
|
{
|
|
return ad->ad_eid[eid].ade_len;
|
|
}
|
|
|
|
size_t ad_getentryoff(const struct adouble *ad, int eid)
|
|
{
|
|
return ad->ad_eid[eid].ade_off;
|
|
}
|
|
|
|
size_t ad_setentrylen(struct adouble *ad, int eid, size_t len)
|
|
{
|
|
return ad->ad_eid[eid].ade_len = len;
|
|
}
|
|
|
|
size_t ad_setentryoff(struct adouble *ad, int eid, size_t off)
|
|
{
|
|
return ad->ad_eid[eid].ade_off = off;
|
|
}
|
|
|
|
/*
|
|
* All entries besides FinderInfo and resource fork must fit into the
|
|
* buffer. FinderInfo is special as it may be larger then the default 32 bytes
|
|
* if it contains marshalled xattrs, which we will fixup that in
|
|
* ad_convert(). The first 32 bytes however must also be part of the buffer.
|
|
*
|
|
* The resource fork is never accessed directly by the ad_data buf.
|
|
*/
|
|
static bool ad_entry_check_size(uint32_t eid,
|
|
size_t bufsize,
|
|
uint32_t off,
|
|
uint32_t got_len)
|
|
{
|
|
struct {
|
|
off_t expected_len;
|
|
bool fixed_size;
|
|
bool minimum_size;
|
|
} ad_checks[] = {
|
|
[ADEID_DFORK] = {-1, false, false}, /* not applicable */
|
|
[ADEID_RFORK] = {-1, false, false}, /* no limit */
|
|
[ADEID_NAME] = {ADEDLEN_NAME, false, false},
|
|
[ADEID_COMMENT] = {ADEDLEN_COMMENT, false, false},
|
|
[ADEID_ICONBW] = {ADEDLEN_ICONBW, true, false},
|
|
[ADEID_ICONCOL] = {ADEDLEN_ICONCOL, false, false},
|
|
[ADEID_FILEI] = {ADEDLEN_FILEI, true, false},
|
|
[ADEID_FILEDATESI] = {ADEDLEN_FILEDATESI, true, false},
|
|
[ADEID_FINDERI] = {ADEDLEN_FINDERI, false, true},
|
|
[ADEID_MACFILEI] = {ADEDLEN_MACFILEI, true, false},
|
|
[ADEID_PRODOSFILEI] = {ADEDLEN_PRODOSFILEI, true, false},
|
|
[ADEID_MSDOSFILEI] = {ADEDLEN_MSDOSFILEI, true, false},
|
|
[ADEID_SHORTNAME] = {ADEDLEN_SHORTNAME, false, false},
|
|
[ADEID_AFPFILEI] = {ADEDLEN_AFPFILEI, true, false},
|
|
[ADEID_DID] = {ADEDLEN_DID, true, false},
|
|
[ADEID_PRIVDEV] = {ADEDLEN_PRIVDEV, true, false},
|
|
[ADEID_PRIVINO] = {ADEDLEN_PRIVINO, true, false},
|
|
[ADEID_PRIVSYN] = {ADEDLEN_PRIVSYN, true, false},
|
|
[ADEID_PRIVID] = {ADEDLEN_PRIVID, true, false},
|
|
};
|
|
|
|
if (eid >= ADEID_MAX) {
|
|
return false;
|
|
}
|
|
if (got_len == 0) {
|
|
/* Entry present, but empty, allow */
|
|
return true;
|
|
}
|
|
if (ad_checks[eid].expected_len == 0) {
|
|
/*
|
|
* Shouldn't happen: implicitly initialized to zero because
|
|
* explicit initializer missing.
|
|
*/
|
|
return false;
|
|
}
|
|
if (ad_checks[eid].expected_len == -1) {
|
|
/* Unused or no limit */
|
|
return true;
|
|
}
|
|
if (ad_checks[eid].fixed_size) {
|
|
if (ad_checks[eid].expected_len != got_len) {
|
|
/* Wrong size fo fixed size entry. */
|
|
return false;
|
|
}
|
|
} else {
|
|
if (ad_checks[eid].minimum_size) {
|
|
if (got_len < ad_checks[eid].expected_len) {
|
|
/*
|
|
* Too small for variable sized entry with
|
|
* minimum size.
|
|
*/
|
|
return false;
|
|
}
|
|
} else {
|
|
if (got_len > ad_checks[eid].expected_len) {
|
|
/* Too big for variable sized entry. */
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (off + got_len < off) {
|
|
/* wrap around */
|
|
return false;
|
|
}
|
|
if (off + got_len > bufsize) {
|
|
/* overflow */
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Return a pointer to an AppleDouble entry
|
|
*
|
|
* Returns NULL if the entry is not present
|
|
**/
|
|
char *ad_get_entry(const struct adouble *ad, int eid)
|
|
{
|
|
size_t bufsize = talloc_get_size(ad->ad_data);
|
|
off_t off = ad_getentryoff(ad, eid);
|
|
size_t len = ad_getentrylen(ad, eid);
|
|
bool valid;
|
|
|
|
valid = ad_entry_check_size(eid, bufsize, off, len);
|
|
if (!valid) {
|
|
return NULL;
|
|
}
|
|
|
|
if (off == 0 || len == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
return ad->ad_data + off;
|
|
}
|
|
|
|
/**
|
|
* Get a date
|
|
**/
|
|
int ad_getdate(const struct adouble *ad, unsigned int dateoff, uint32_t *date)
|
|
{
|
|
bool xlate = (dateoff & AD_DATE_UNIX);
|
|
char *p = NULL;
|
|
|
|
dateoff &= AD_DATE_MASK;
|
|
p = ad_get_entry(ad, ADEID_FILEDATESI);
|
|
if (p == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (dateoff > AD_DATE_ACCESS) {
|
|
return -1;
|
|
}
|
|
|
|
memcpy(date, p + dateoff, sizeof(uint32_t));
|
|
|
|
if (xlate) {
|
|
*date = AD_DATE_TO_UNIX(*date);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Set a date
|
|
**/
|
|
int ad_setdate(struct adouble *ad, unsigned int dateoff, uint32_t date)
|
|
{
|
|
bool xlate = (dateoff & AD_DATE_UNIX);
|
|
char *p = NULL;
|
|
|
|
p = ad_get_entry(ad, ADEID_FILEDATESI);
|
|
if (p == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
dateoff &= AD_DATE_MASK;
|
|
if (xlate) {
|
|
date = AD_DATE_FROM_UNIX(date);
|
|
}
|
|
|
|
if (dateoff > AD_DATE_ACCESS) {
|
|
return -1;
|
|
}
|
|
|
|
memcpy(p + dateoff, &date, sizeof(date));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Map on-disk AppleDouble id to enumerated id
|
|
**/
|
|
static uint32_t get_eid(uint32_t eid)
|
|
{
|
|
if (eid <= 15) {
|
|
return eid;
|
|
}
|
|
|
|
switch (eid) {
|
|
case AD_DEV:
|
|
return ADEID_PRIVDEV;
|
|
case AD_INO:
|
|
return ADEID_PRIVINO;
|
|
case AD_SYN:
|
|
return ADEID_PRIVSYN;
|
|
case AD_ID:
|
|
return ADEID_PRIVID;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Move resourcefork data in an AppleDouble file
|
|
*
|
|
* This is supposed to make room in an AppleDouble file by moving the
|
|
* resourcefork data behind the space required for packing additional xattr data
|
|
* in the extended FinderInfo entry.
|
|
*
|
|
* When we're called we're expecting an AppleDouble file with just two entries
|
|
* (FinderInfo an Resourcefork) and the resourcefork is expected at a fixed
|
|
* offset of ADEDOFF_RFORK_DOT_UND.
|
|
*/
|
|
static bool ad_pack_move_reso(struct vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
files_struct *fsp)
|
|
{
|
|
size_t reso_len;
|
|
size_t reso_off;
|
|
size_t n;
|
|
bool ok;
|
|
|
|
reso_len = ad_getentrylen(ad, ADEID_RFORK);
|
|
reso_off = ad_getentryoff(ad, ADEID_RFORK);
|
|
|
|
if (reso_len == 0) {
|
|
return true;
|
|
}
|
|
|
|
if (ad->ad_rsrc_data == NULL) {
|
|
/*
|
|
* This buffer is already set when converting a resourcefork
|
|
* stream from vfs_streams_depot backend via ad_unconvert(). It
|
|
* is NULL with vfs_streams_xattr where the resourcefork stream
|
|
* is stored in an AppleDouble sidecar file vy vfs_fruit.
|
|
*/
|
|
ad->ad_rsrc_data = talloc_size(ad, reso_len);
|
|
if (ad->ad_rsrc_data == NULL) {
|
|
return false;
|
|
}
|
|
|
|
n = SMB_VFS_NEXT_PREAD(handle,
|
|
fsp,
|
|
ad->ad_rsrc_data,
|
|
reso_len,
|
|
ADEDOFF_RFORK_DOT_UND);
|
|
if (n != reso_len) {
|
|
DBG_ERR("Read on [%s] failed\n",
|
|
fsp_str_dbg(fsp));
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
n = SMB_VFS_NEXT_PWRITE(handle,
|
|
fsp,
|
|
ad->ad_rsrc_data,
|
|
reso_len,
|
|
reso_off);
|
|
if (n != reso_len) {
|
|
DBG_ERR("Write on [%s] failed\n",
|
|
fsp_str_dbg(fsp));
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
ok = true;
|
|
out:
|
|
return ok;
|
|
}
|
|
|
|
static bool ad_pack_xattrs(struct vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
files_struct *fsp)
|
|
{
|
|
struct ad_xattr_header *h = &ad->adx_header;
|
|
size_t oldsize;
|
|
uint32_t off;
|
|
uint32_t data_off;
|
|
uint16_t i;
|
|
bool ok;
|
|
|
|
if (ad->adx_entries == NULL) {
|
|
/* No xattrs, nothing to pack */
|
|
return true;
|
|
}
|
|
|
|
if (fsp == NULL) {
|
|
DBG_ERR("fsp unexpectedly NULL\n");
|
|
return false;
|
|
}
|
|
|
|
oldsize = talloc_get_size(ad->ad_data);
|
|
if (oldsize < AD_XATTR_MAX_HDR_SIZE) {
|
|
ad->ad_data = talloc_realloc(ad,
|
|
ad->ad_data,
|
|
char,
|
|
AD_XATTR_MAX_HDR_SIZE);
|
|
if (ad->ad_data == NULL) {
|
|
return false;
|
|
}
|
|
memset(ad->ad_data + oldsize,
|
|
0,
|
|
AD_XATTR_MAX_HDR_SIZE - oldsize);
|
|
}
|
|
|
|
/*
|
|
* First, let's calculate the start of the xattr data area which will be
|
|
* after the xattr header + header entries.
|
|
*/
|
|
|
|
data_off = ad_getentryoff(ad, ADEID_FINDERI);
|
|
data_off += ADEDLEN_FINDERI + AD_XATTR_HDR_SIZE;
|
|
/* 2 bytes padding */
|
|
data_off += 2;
|
|
|
|
for (i = 0; i < h->adx_num_attrs; i++) {
|
|
struct ad_xattr_entry *e = &ad->adx_entries[i];
|
|
|
|
/* Align on 4 byte boundary */
|
|
data_off = (data_off + 3) & ~3;
|
|
|
|
data_off += e->adx_namelen + ADX_ENTRY_FIXED_SIZE;
|
|
if (data_off >= AD_XATTR_MAX_HDR_SIZE) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
off = ad_getentryoff(ad, ADEID_FINDERI);
|
|
off += ADEDLEN_FINDERI + AD_XATTR_HDR_SIZE;
|
|
/* 2 bytes padding */
|
|
off += 2;
|
|
|
|
for (i = 0; i < h->adx_num_attrs; i++) {
|
|
struct ad_xattr_entry *e = &ad->adx_entries[i];
|
|
|
|
/* Align on 4 byte boundary */
|
|
off = (off + 3) & ~3;
|
|
|
|
e->adx_offset = data_off;
|
|
data_off += e->adx_length;
|
|
|
|
DBG_DEBUG("%zu(%s){%zu}: off [%zu] adx_length [%zu] "
|
|
"adx_data_off [%zu]\n",
|
|
(size_t)i,
|
|
e->adx_name,
|
|
(size_t)e->adx_namelen,
|
|
(size_t)off,
|
|
(size_t)e->adx_length,
|
|
(size_t)e->adx_offset);
|
|
|
|
if (off + 4 >= AD_XATTR_MAX_HDR_SIZE) {
|
|
return false;
|
|
}
|
|
RSIVAL(ad->ad_data, off, e->adx_offset);
|
|
off += 4;
|
|
|
|
if (off + 4 >= AD_XATTR_MAX_HDR_SIZE) {
|
|
return false;
|
|
}
|
|
RSIVAL(ad->ad_data, off, e->adx_length);
|
|
off += 4;
|
|
|
|
if (off + 2 >= AD_XATTR_MAX_HDR_SIZE) {
|
|
return false;
|
|
}
|
|
RSSVAL(ad->ad_data, off, e->adx_flags);
|
|
off += 2;
|
|
|
|
if (off + 1 >= AD_XATTR_MAX_HDR_SIZE) {
|
|
return false;
|
|
}
|
|
SCVAL(ad->ad_data, off, e->adx_namelen);
|
|
off += 1;
|
|
|
|
if (off + e->adx_namelen >= AD_XATTR_MAX_HDR_SIZE) {
|
|
return false;
|
|
}
|
|
memcpy(ad->ad_data + off, e->adx_name, e->adx_namelen);
|
|
off += e->adx_namelen;
|
|
}
|
|
|
|
h->adx_data_start = off;
|
|
h->adx_data_length = talloc_get_size(ad->adx_data);
|
|
h->adx_total_size = h->adx_data_start + h->adx_data_length;
|
|
|
|
if (talloc_get_size(ad->ad_data) < h->adx_total_size) {
|
|
ad->ad_data = talloc_realloc(ad,
|
|
ad->ad_data,
|
|
char,
|
|
h->adx_total_size);
|
|
if (ad->ad_data == NULL) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
memcpy(ad->ad_data + h->adx_data_start,
|
|
ad->adx_data,
|
|
h->adx_data_length);
|
|
|
|
ad_setentrylen(ad,
|
|
ADEID_FINDERI,
|
|
h->adx_total_size - ad_getentryoff(ad, ADEID_FINDERI));
|
|
|
|
ad_setentryoff(ad,
|
|
ADEID_RFORK,
|
|
ad_getentryoff(ad, ADEID_FINDERI) +
|
|
ad_getentrylen(ad, ADEID_FINDERI));
|
|
|
|
memcpy(ad->ad_data + ADEDOFF_FILLER, AD_FILLER_TAG_OSX, ADEDLEN_FILLER);
|
|
|
|
/*
|
|
* Rewind, then update the header fields.
|
|
*/
|
|
|
|
off = ad_getentryoff(ad, ADEID_FINDERI) + ADEDLEN_FINDERI;
|
|
/* 2 bytes padding */
|
|
off += 2;
|
|
|
|
RSIVAL(ad->ad_data, off, AD_XATTR_HDR_MAGIC);
|
|
off += 4;
|
|
RSIVAL(ad->ad_data, off, 0);
|
|
off += 4;
|
|
RSIVAL(ad->ad_data, off, h->adx_total_size);
|
|
off += 4;
|
|
RSIVAL(ad->ad_data, off, h->adx_data_start);
|
|
off += 4;
|
|
RSIVAL(ad->ad_data, off, h->adx_data_length);
|
|
off += 4;
|
|
|
|
/* adx_reserved and adx_flags */
|
|
memset(ad->ad_data + off, 0, 3 * 4 + 2);
|
|
off += 3 * 4 + 2;
|
|
|
|
RSSVAL(ad->ad_data, off, h->adx_num_attrs);
|
|
off += 2;
|
|
|
|
ok = ad_pack_move_reso(handle, ad, fsp);
|
|
if (!ok) {
|
|
DBG_ERR("Moving resourcefork of [%s] failed\n",
|
|
fsp_str_dbg(fsp));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Pack AppleDouble structure into data buffer
|
|
**/
|
|
static bool ad_pack(struct vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
files_struct *fsp)
|
|
{
|
|
uint32_t eid;
|
|
uint16_t nent;
|
|
uint32_t bufsize;
|
|
uint32_t offset = 0;
|
|
bool ok;
|
|
|
|
bufsize = talloc_get_size(ad->ad_data);
|
|
if (bufsize < AD_DATASZ_DOT_UND) {
|
|
DBG_ERR("bad buffer size [0x%" PRIx32 "]\n", bufsize);
|
|
return false;
|
|
}
|
|
|
|
if (offset + ADEDLEN_MAGIC < offset ||
|
|
offset + ADEDLEN_MAGIC >= bufsize) {
|
|
return false;
|
|
}
|
|
RSIVAL(ad->ad_data, offset, ad->ad_magic);
|
|
offset += ADEDLEN_MAGIC;
|
|
|
|
if (offset + ADEDLEN_VERSION < offset ||
|
|
offset + ADEDLEN_VERSION >= bufsize) {
|
|
return false;
|
|
}
|
|
RSIVAL(ad->ad_data, offset, ad->ad_version);
|
|
offset += ADEDLEN_VERSION;
|
|
|
|
if (offset + ADEDLEN_FILLER < offset ||
|
|
offset + ADEDLEN_FILLER >= bufsize) {
|
|
return false;
|
|
}
|
|
if (ad->ad_type == ADOUBLE_RSRC) {
|
|
memcpy(ad->ad_data + offset, AD_FILLER_TAG, ADEDLEN_FILLER);
|
|
}
|
|
offset += ADEDLEN_FILLER;
|
|
|
|
if (offset + ADEDLEN_NENTRIES < offset ||
|
|
offset + ADEDLEN_NENTRIES >= bufsize) {
|
|
return false;
|
|
}
|
|
offset += ADEDLEN_NENTRIES;
|
|
|
|
ok = ad_pack_xattrs(handle, ad, fsp);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
for (eid = 0, nent = 0; eid < ADEID_MAX; eid++) {
|
|
if (ad->ad_eid[eid].ade_off == 0) {
|
|
/*
|
|
* ade_off is also used as indicator whether a
|
|
* specific entry is used or not
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
if (offset + AD_ENTRY_LEN_EID < offset ||
|
|
offset + AD_ENTRY_LEN_EID >= bufsize) {
|
|
return false;
|
|
}
|
|
RSIVAL(ad->ad_data, offset, AD_EID_DISK(eid));
|
|
offset += AD_ENTRY_LEN_EID;
|
|
|
|
if (offset + AD_ENTRY_LEN_OFF < offset ||
|
|
offset + AD_ENTRY_LEN_OFF >= bufsize) {
|
|
return false;
|
|
}
|
|
RSIVAL(ad->ad_data, offset, ad->ad_eid[eid].ade_off);
|
|
offset += AD_ENTRY_LEN_OFF;
|
|
|
|
if (offset + AD_ENTRY_LEN_LEN < offset ||
|
|
offset + AD_ENTRY_LEN_LEN >= bufsize) {
|
|
return false;
|
|
}
|
|
RSIVAL(ad->ad_data, offset, ad->ad_eid[eid].ade_len);
|
|
offset += AD_ENTRY_LEN_LEN;
|
|
|
|
nent++;
|
|
}
|
|
|
|
if (ADEDOFF_NENTRIES + 2 >= bufsize) {
|
|
return false;
|
|
}
|
|
RSSVAL(ad->ad_data, ADEDOFF_NENTRIES, nent);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ad_unpack_xattrs(struct adouble *ad)
|
|
{
|
|
struct ad_xattr_header *h = &ad->adx_header;
|
|
size_t bufsize = talloc_get_size(ad->ad_data);
|
|
const char *p = ad->ad_data;
|
|
uint32_t hoff;
|
|
uint32_t i;
|
|
|
|
if (ad->ad_type != ADOUBLE_RSRC) {
|
|
return false;
|
|
}
|
|
|
|
if (ad_getentrylen(ad, ADEID_FINDERI) <= ADEDLEN_FINDERI) {
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Ensure the buffer ad->ad_data was allocated by ad_alloc() for an
|
|
* ADOUBLE_RSRC type (._ AppleDouble file on-disk).
|
|
*/
|
|
if (bufsize != AD_XATTR_MAX_HDR_SIZE) {
|
|
return false;
|
|
}
|
|
|
|
/* 2 bytes padding */
|
|
hoff = ad_getentryoff(ad, ADEID_FINDERI) + ADEDLEN_FINDERI + 2;
|
|
|
|
h->adx_magic = RIVAL(p, hoff + 0);
|
|
h->adx_debug_tag = RIVAL(p, hoff + 4); /* Not used -> not checked */
|
|
h->adx_total_size = RIVAL(p, hoff + 8);
|
|
h->adx_data_start = RIVAL(p, hoff + 12);
|
|
h->adx_data_length = RIVAL(p, hoff + 16);
|
|
h->adx_flags = RSVAL(p, hoff + 32); /* Not used -> not checked */
|
|
h->adx_num_attrs = RSVAL(p, hoff + 34);
|
|
|
|
if (h->adx_magic != AD_XATTR_HDR_MAGIC) {
|
|
DBG_ERR("Bad magic: 0x%" PRIx32 "\n", h->adx_magic);
|
|
return false;
|
|
}
|
|
|
|
if (h->adx_total_size > ad_getentryoff(ad, ADEID_RFORK)) {
|
|
DBG_ERR("Bad total size: 0x%" PRIx32 "\n", h->adx_total_size);
|
|
return false;
|
|
}
|
|
if (h->adx_total_size > AD_XATTR_MAX_HDR_SIZE) {
|
|
DBG_ERR("Bad total size: 0x%" PRIx32 "\n", h->adx_total_size);
|
|
return false;
|
|
}
|
|
|
|
if (h->adx_data_start < (hoff + AD_XATTR_HDR_SIZE)) {
|
|
DBG_ERR("Bad start: 0x%" PRIx32 "\n", h->adx_data_start);
|
|
return false;
|
|
}
|
|
|
|
if ((h->adx_data_start + h->adx_data_length) < h->adx_data_start) {
|
|
DBG_ERR("Bad length: %" PRIu32 "\n", h->adx_data_length);
|
|
return false;
|
|
}
|
|
if ((h->adx_data_start + h->adx_data_length) >
|
|
ad->adx_header.adx_total_size)
|
|
{
|
|
DBG_ERR("Bad length: %" PRIu32 "\n", h->adx_data_length);
|
|
return false;
|
|
}
|
|
|
|
if (h->adx_num_attrs > AD_XATTR_MAX_ENTRIES) {
|
|
DBG_ERR("Bad num xattrs: %" PRIu16 "\n", h->adx_num_attrs);
|
|
return false;
|
|
}
|
|
|
|
if (h->adx_num_attrs == 0) {
|
|
return true;
|
|
}
|
|
|
|
ad->adx_entries = talloc_zero_array(
|
|
ad, struct ad_xattr_entry, h->adx_num_attrs);
|
|
if (ad->adx_entries == NULL) {
|
|
return false;
|
|
}
|
|
|
|
hoff += AD_XATTR_HDR_SIZE;
|
|
|
|
for (i = 0; i < h->adx_num_attrs; i++) {
|
|
struct ad_xattr_entry *e = &ad->adx_entries[i];
|
|
|
|
hoff = (hoff + 3) & ~3;
|
|
|
|
e->adx_offset = RIVAL(p, hoff + 0);
|
|
e->adx_length = RIVAL(p, hoff + 4);
|
|
e->adx_flags = RSVAL(p, hoff + 8);
|
|
e->adx_namelen = *(p + hoff + 10);
|
|
|
|
if (e->adx_offset >= ad->adx_header.adx_total_size) {
|
|
DBG_ERR("Bad adx_offset: %" PRIx32 "\n",
|
|
e->adx_offset);
|
|
return false;
|
|
}
|
|
|
|
if ((e->adx_offset + e->adx_length) < e->adx_offset) {
|
|
DBG_ERR("Bad adx_length: %" PRIx32 "\n",
|
|
e->adx_length);
|
|
return false;
|
|
}
|
|
|
|
if ((e->adx_offset + e->adx_length) >
|
|
ad->adx_header.adx_total_size)
|
|
{
|
|
DBG_ERR("Bad adx_length: %" PRIx32 "\n",
|
|
e->adx_length);
|
|
return false;
|
|
}
|
|
|
|
if (e->adx_namelen == 0) {
|
|
DBG_ERR("Bad adx_namelen: %" PRIx32 "\n",
|
|
e->adx_namelen);
|
|
return false;
|
|
}
|
|
if ((hoff + 11 + e->adx_namelen) < hoff + 11) {
|
|
DBG_ERR("Bad adx_namelen: %" PRIx32 "\n",
|
|
e->adx_namelen);
|
|
return false;
|
|
}
|
|
if ((hoff + 11 + e->adx_namelen) >
|
|
ad->adx_header.adx_data_start)
|
|
{
|
|
DBG_ERR("Bad adx_namelen: %" PRIx32 "\n",
|
|
e->adx_namelen);
|
|
return false;
|
|
}
|
|
|
|
e->adx_name = talloc_strndup(ad->adx_entries,
|
|
p + hoff + 11,
|
|
e->adx_namelen);
|
|
if (e->adx_name == NULL) {
|
|
return false;
|
|
}
|
|
|
|
DBG_DEBUG("xattr [%s] offset [0x%x] size [0x%x]\n",
|
|
e->adx_name, e->adx_offset, e->adx_length);
|
|
dump_data(10, (uint8_t *)(ad->ad_data + e->adx_offset),
|
|
e->adx_length);
|
|
|
|
hoff += 11 + e->adx_namelen;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Unpack an AppleDouble blob into a struct adoble
|
|
**/
|
|
static bool ad_unpack(struct adouble *ad, const size_t nentries,
|
|
size_t filesize)
|
|
{
|
|
size_t bufsize = talloc_get_size(ad->ad_data);
|
|
size_t adentries, i;
|
|
uint32_t eid, len, off;
|
|
bool ok;
|
|
|
|
/*
|
|
* The size of the buffer ad->ad_data is checked when read, so
|
|
* we wouldn't have to check our own offsets, a few extra
|
|
* checks won't hurt though. We have to check the offsets we
|
|
* read from the buffer anyway.
|
|
*/
|
|
|
|
if (bufsize < (AD_HEADER_LEN + (AD_ENTRY_LEN * nentries))) {
|
|
DBG_NOTICE("Bad size\n");
|
|
return false;
|
|
}
|
|
|
|
ad->ad_magic = RIVAL(ad->ad_data, 0);
|
|
ad->ad_version = RIVAL(ad->ad_data, ADEDOFF_VERSION);
|
|
if ((ad->ad_magic != AD_MAGIC) || (ad->ad_version != AD_VERSION)) {
|
|
DBG_NOTICE("Wrong magic or version\n");
|
|
return false;
|
|
}
|
|
|
|
memcpy(ad->ad_filler, ad->ad_data + ADEDOFF_FILLER, ADEDLEN_FILLER);
|
|
|
|
adentries = RSVAL(ad->ad_data, ADEDOFF_NENTRIES);
|
|
if (adentries != nentries) {
|
|
DBG_NOTICE("Invalid number of entries: %zu\n", adentries);
|
|
return false;
|
|
}
|
|
|
|
/* now, read in the entry bits */
|
|
for (i = 0; i < adentries; i++) {
|
|
eid = RIVAL(ad->ad_data, AD_HEADER_LEN + (i * AD_ENTRY_LEN));
|
|
eid = get_eid(eid);
|
|
off = RIVAL(ad->ad_data, AD_HEADER_LEN + (i * AD_ENTRY_LEN) + 4);
|
|
len = RIVAL(ad->ad_data, AD_HEADER_LEN + (i * AD_ENTRY_LEN) + 8);
|
|
|
|
if (!eid || eid >= ADEID_MAX) {
|
|
DBG_NOTICE("Bogus eid %d\n", eid);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* All entries other than the resource fork are
|
|
* expected to be read into the ad_data buffer, so
|
|
* ensure the specified offset is within that bound
|
|
*/
|
|
if ((off > bufsize) && (eid != ADEID_RFORK)) {
|
|
DBG_NOTICE("Bogus eid %d: off: %" PRIu32
|
|
", len: %" PRIu32 "\n",
|
|
eid,
|
|
off,
|
|
len);
|
|
return false;
|
|
}
|
|
|
|
ok = ad_entry_check_size(eid, bufsize, off, len);
|
|
if (!ok) {
|
|
DBG_NOTICE("Bogus eid [%" PRIu32 "] bufsize [%zu] "
|
|
"off [%" PRIu32 "] len [%" PRIu32 "]\n",
|
|
eid,
|
|
bufsize,
|
|
off,
|
|
len);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* That would be obviously broken
|
|
*/
|
|
if (off > filesize) {
|
|
DBG_NOTICE("Bogus eid %d: off: %" PRIu32
|
|
", len: %" PRIu32 "\n",
|
|
eid,
|
|
off,
|
|
len);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Check for any entry that has its end beyond the
|
|
* filesize.
|
|
*/
|
|
if (off + len < off) {
|
|
DBG_NOTICE("offset wrap in eid %d: off: %" PRIu32
|
|
", len: %" PRIu32 "\n",
|
|
eid,
|
|
off,
|
|
len);
|
|
return false;
|
|
|
|
}
|
|
if (off + len > filesize) {
|
|
/*
|
|
* If this is the resource fork entry, we fix
|
|
* up the length, for any other entry we bail
|
|
* out.
|
|
*/
|
|
if (eid != ADEID_RFORK) {
|
|
DBG_NOTICE("Bogus eid %d: off: %" PRIu32
|
|
", len: %" PRIu32 "\n",
|
|
eid,
|
|
off,
|
|
len);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Fixup the resource fork entry by limiting
|
|
* the size to entryoffset - filesize.
|
|
*/
|
|
len = filesize - off;
|
|
DBG_NOTICE("Limiting ADEID_RFORK: off: %" PRIu32
|
|
", len: %" PRIu32 "\n",
|
|
off,
|
|
len);
|
|
}
|
|
|
|
ad->ad_eid[eid].ade_off = off;
|
|
ad->ad_eid[eid].ade_len = len;
|
|
}
|
|
|
|
if (ad->ad_type == ADOUBLE_RSRC) {
|
|
ok = ad_unpack_xattrs(ad);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ad_convert_move_reso(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
const struct smb_filename *smb_fname)
|
|
{
|
|
char *buf = NULL;
|
|
size_t rforklen;
|
|
size_t rforkoff;
|
|
ssize_t n;
|
|
int ret;
|
|
|
|
rforklen = ad_getentrylen(ad, ADEID_RFORK);
|
|
if (rforklen == 0) {
|
|
return true;
|
|
}
|
|
|
|
buf = talloc_size(ad, rforklen);
|
|
if (buf == NULL) {
|
|
/*
|
|
* This allocates a buffer for reading the resource fork data in
|
|
* one big swoop. Resource forks won't be larger then, say, 64
|
|
* MB, I swear, so just doing the allocation with the talloc
|
|
* limit as safeguard seems safe.
|
|
*/
|
|
DBG_ERR("Failed to allocate %zu bytes for rfork\n",
|
|
rforklen);
|
|
return false;
|
|
}
|
|
|
|
rforkoff = ad_getentryoff(ad, ADEID_RFORK);
|
|
|
|
n = SMB_VFS_PREAD(ad->ad_fsp, buf, rforklen, rforkoff);
|
|
if (n != rforklen) {
|
|
DBG_ERR("Reading %zu bytes from rfork [%s] failed: %s\n",
|
|
rforklen, fsp_str_dbg(ad->ad_fsp), strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
rforkoff = ADEDOFF_RFORK_DOT_UND;
|
|
|
|
n = SMB_VFS_PWRITE(ad->ad_fsp, buf, rforklen, rforkoff);
|
|
if (n != rforklen) {
|
|
DBG_ERR("Writing %zu bytes to rfork [%s] failed: %s\n",
|
|
rforklen, fsp_str_dbg(ad->ad_fsp), strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
ad_setentryoff(ad, ADEID_RFORK, ADEDOFF_RFORK_DOT_UND);
|
|
|
|
ret = ad_fset(handle, ad, ad->ad_fsp);
|
|
if (ret != 0) {
|
|
DBG_ERR("ad_fset on [%s] failed\n", fsp_str_dbg(ad->ad_fsp));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ad_convert_xattr(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
const struct smb_filename *smb_fname,
|
|
const char *catia_mappings,
|
|
bool *converted_xattr)
|
|
{
|
|
static struct char_mappings **string_replace_cmaps = NULL;
|
|
uint16_t i;
|
|
int saved_errno = 0;
|
|
NTSTATUS status;
|
|
int rc;
|
|
bool ok;
|
|
|
|
*converted_xattr = false;
|
|
|
|
if (ad_getentrylen(ad, ADEID_FINDERI) == ADEDLEN_FINDERI) {
|
|
return true;
|
|
}
|
|
|
|
if (string_replace_cmaps == NULL) {
|
|
const char **mappings = NULL;
|
|
|
|
mappings = str_list_make_v3_const(
|
|
talloc_tos(), catia_mappings, NULL);
|
|
if (mappings == NULL) {
|
|
return false;
|
|
}
|
|
string_replace_cmaps = string_replace_init_map(
|
|
handle->conn->sconn, mappings);
|
|
TALLOC_FREE(mappings);
|
|
}
|
|
|
|
for (i = 0; i < ad->adx_header.adx_num_attrs; i++) {
|
|
struct ad_xattr_entry *e = &ad->adx_entries[i];
|
|
char *mapped_name = NULL;
|
|
char *tmp = NULL;
|
|
struct smb_filename *stream_name = NULL;
|
|
files_struct *fsp = NULL;
|
|
ssize_t nwritten;
|
|
|
|
status = string_replace_allocate(handle->conn,
|
|
e->adx_name,
|
|
string_replace_cmaps,
|
|
talloc_tos(),
|
|
&mapped_name,
|
|
vfs_translate_to_windows);
|
|
if (!NT_STATUS_IS_OK(status) &&
|
|
!NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))
|
|
{
|
|
DBG_ERR("string_replace_allocate failed\n");
|
|
ok = false;
|
|
goto fail;
|
|
}
|
|
|
|
tmp = mapped_name;
|
|
mapped_name = talloc_asprintf(talloc_tos(), ":%s", tmp);
|
|
TALLOC_FREE(tmp);
|
|
if (mapped_name == NULL) {
|
|
ok = false;
|
|
goto fail;
|
|
}
|
|
|
|
stream_name = synthetic_smb_fname(talloc_tos(),
|
|
smb_fname->base_name,
|
|
mapped_name,
|
|
NULL,
|
|
smb_fname->twrp,
|
|
smb_fname->flags);
|
|
TALLOC_FREE(mapped_name);
|
|
if (stream_name == NULL) {
|
|
DBG_ERR("synthetic_smb_fname failed\n");
|
|
ok = false;
|
|
goto fail;
|
|
}
|
|
|
|
DBG_DEBUG("stream_name: %s\n", smb_fname_str_dbg(stream_name));
|
|
|
|
status = openat_pathref_fsp(handle->conn->cwd_fsp, stream_name);
|
|
if (!NT_STATUS_IS_OK(status) &&
|
|
!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND))
|
|
{
|
|
ok = false;
|
|
goto fail;
|
|
}
|
|
|
|
status = SMB_VFS_CREATE_FILE(
|
|
handle->conn, /* conn */
|
|
NULL, /* req */
|
|
NULL, /* dirfsp */
|
|
stream_name, /* fname */
|
|
FILE_GENERIC_WRITE, /* access_mask */
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, /* share_access */
|
|
FILE_OPEN_IF, /* create_disposition */
|
|
0, /* create_options */
|
|
0, /* file_attributes */
|
|
INTERNAL_OPEN_ONLY, /* oplock_request */
|
|
NULL, /* lease */
|
|
0, /* allocation_size */
|
|
0, /* private_flags */
|
|
NULL, /* sd */
|
|
NULL, /* ea_list */
|
|
&fsp, /* result */
|
|
NULL, /* psbuf */
|
|
NULL, NULL); /* create context */
|
|
TALLOC_FREE(stream_name);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("SMB_VFS_CREATE_FILE failed\n");
|
|
ok = false;
|
|
goto fail;
|
|
}
|
|
|
|
nwritten = SMB_VFS_PWRITE(fsp,
|
|
ad->ad_data + e->adx_offset,
|
|
e->adx_length,
|
|
0);
|
|
if (nwritten == -1) {
|
|
DBG_ERR("SMB_VFS_PWRITE failed\n");
|
|
saved_errno = errno;
|
|
close_file_free(NULL, &fsp, ERROR_CLOSE);
|
|
errno = saved_errno;
|
|
ok = false;
|
|
goto fail;
|
|
}
|
|
|
|
status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
ok = false;
|
|
goto fail;
|
|
}
|
|
fsp = NULL;
|
|
}
|
|
|
|
ad->adx_header.adx_num_attrs = 0;
|
|
TALLOC_FREE(ad->adx_entries);
|
|
|
|
ad_setentrylen(ad, ADEID_FINDERI, ADEDLEN_FINDERI);
|
|
|
|
rc = ad_fset(handle, ad, ad->ad_fsp);
|
|
if (rc != 0) {
|
|
DBG_ERR("ad_fset on [%s] failed: %s\n",
|
|
fsp_str_dbg(ad->ad_fsp), strerror(errno));
|
|
ok = false;
|
|
goto fail;
|
|
}
|
|
|
|
ok = ad_convert_move_reso(handle, ad, smb_fname);
|
|
if (!ok) {
|
|
goto fail;
|
|
}
|
|
|
|
*converted_xattr = true;
|
|
ok = true;
|
|
|
|
fail:
|
|
return ok;
|
|
}
|
|
|
|
static bool ad_convert_finderinfo(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
const struct smb_filename *smb_fname)
|
|
{
|
|
char *p_ad = NULL;
|
|
AfpInfo *ai = NULL;
|
|
DATA_BLOB aiblob;
|
|
struct smb_filename *stream_name = NULL;
|
|
files_struct *fsp = NULL;
|
|
size_t size;
|
|
ssize_t nwritten;
|
|
NTSTATUS status;
|
|
int saved_errno = 0;
|
|
int cmp;
|
|
|
|
cmp = memcmp(ad->ad_filler, AD_FILLER_TAG_OSX, ADEDLEN_FILLER);
|
|
if (cmp != 0) {
|
|
return true;
|
|
}
|
|
|
|
p_ad = ad_get_entry(ad, ADEID_FINDERI);
|
|
if (p_ad == NULL) {
|
|
return false;
|
|
}
|
|
|
|
ai = afpinfo_new(talloc_tos());
|
|
if (ai == NULL) {
|
|
return false;
|
|
}
|
|
|
|
memcpy(ai->afpi_FinderInfo, p_ad, ADEDLEN_FINDERI);
|
|
|
|
aiblob = data_blob_talloc(talloc_tos(), NULL, AFP_INFO_SIZE);
|
|
if (aiblob.data == NULL) {
|
|
TALLOC_FREE(ai);
|
|
return false;
|
|
}
|
|
|
|
size = afpinfo_pack(ai, (char *)aiblob.data);
|
|
TALLOC_FREE(ai);
|
|
if (size != AFP_INFO_SIZE) {
|
|
return false;
|
|
}
|
|
|
|
stream_name = synthetic_smb_fname(talloc_tos(),
|
|
smb_fname->base_name,
|
|
AFPINFO_STREAM,
|
|
NULL,
|
|
smb_fname->twrp,
|
|
smb_fname->flags);
|
|
if (stream_name == NULL) {
|
|
data_blob_free(&aiblob);
|
|
DBG_ERR("synthetic_smb_fname failed\n");
|
|
return false;
|
|
}
|
|
|
|
DBG_DEBUG("stream_name: %s\n", smb_fname_str_dbg(stream_name));
|
|
|
|
status = openat_pathref_fsp(handle->conn->cwd_fsp, stream_name);
|
|
if (!NT_STATUS_IS_OK(status) &&
|
|
!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
status = SMB_VFS_CREATE_FILE(
|
|
handle->conn, /* conn */
|
|
NULL, /* req */
|
|
NULL, /* dirfsp */
|
|
stream_name, /* fname */
|
|
FILE_GENERIC_WRITE, /* access_mask */
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
|
|
FILE_OPEN_IF, /* create_disposition */
|
|
0, /* create_options */
|
|
0, /* file_attributes */
|
|
INTERNAL_OPEN_ONLY, /* oplock_request */
|
|
NULL, /* lease */
|
|
0, /* allocation_size */
|
|
0, /* private_flags */
|
|
NULL, /* sd */
|
|
NULL, /* ea_list */
|
|
&fsp, /* result */
|
|
NULL, /* psbuf */
|
|
NULL, NULL); /* create context */
|
|
TALLOC_FREE(stream_name);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("SMB_VFS_CREATE_FILE failed\n");
|
|
return false;
|
|
}
|
|
|
|
nwritten = SMB_VFS_PWRITE(fsp,
|
|
aiblob.data,
|
|
aiblob.length,
|
|
0);
|
|
if (nwritten == -1) {
|
|
DBG_ERR("SMB_VFS_PWRITE failed\n");
|
|
saved_errno = errno;
|
|
close_file_free(NULL, &fsp, ERROR_CLOSE);
|
|
errno = saved_errno;
|
|
return false;
|
|
}
|
|
|
|
status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
return false;
|
|
}
|
|
fsp = NULL;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ad_convert_truncate(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
const struct smb_filename *smb_fname)
|
|
{
|
|
int rc;
|
|
off_t newlen;
|
|
|
|
newlen = ADEDOFF_RFORK_DOT_UND + ad_getentrylen(ad, ADEID_RFORK);
|
|
|
|
rc = SMB_VFS_FTRUNCATE(ad->ad_fsp, newlen);
|
|
if (rc != 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ad_convert_blank_rfork(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
uint32_t flags,
|
|
bool *blank)
|
|
{
|
|
size_t rforklen = sizeof(empty_resourcefork);
|
|
char buf[rforklen];
|
|
ssize_t nread;
|
|
int cmp;
|
|
int rc;
|
|
|
|
*blank = false;
|
|
|
|
if (!(flags & AD_CONV_WIPE_BLANK)) {
|
|
return true;
|
|
}
|
|
|
|
if (ad_getentrylen(ad, ADEID_RFORK) != rforklen) {
|
|
return true;
|
|
}
|
|
|
|
nread = SMB_VFS_PREAD(ad->ad_fsp, buf, rforklen, ADEDOFF_RFORK_DOT_UND);
|
|
if (nread != rforklen) {
|
|
DBG_ERR("Reading %zu bytes from rfork [%s] failed: %s\n",
|
|
rforklen, fsp_str_dbg(ad->ad_fsp), strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
cmp = memcmp(buf, empty_resourcefork, rforklen);
|
|
if (cmp != 0) {
|
|
return true;
|
|
}
|
|
|
|
ad_setentrylen(ad, ADEID_RFORK, 0);
|
|
|
|
rc = ad_fset(handle, ad, ad->ad_fsp);
|
|
if (rc != 0) {
|
|
DBG_ERR("ad_fset on [%s] failed\n", fsp_str_dbg(ad->ad_fsp));
|
|
return false;
|
|
}
|
|
|
|
*blank = true;
|
|
return true;
|
|
}
|
|
|
|
static bool ad_convert_delete_adfile(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
const struct smb_filename *smb_fname,
|
|
uint32_t flags)
|
|
{
|
|
struct smb_filename *parent_fname = NULL;
|
|
struct smb_filename *at_fname = NULL;
|
|
struct smb_filename *ad_name = NULL;
|
|
NTSTATUS status;
|
|
int rc;
|
|
|
|
if (ad_getentrylen(ad, ADEID_RFORK) > 0) {
|
|
return true;
|
|
}
|
|
|
|
if (!(flags & AD_CONV_DELETE)) {
|
|
return true;
|
|
}
|
|
|
|
rc = adouble_path(talloc_tos(), smb_fname, &ad_name);
|
|
if (rc != 0) {
|
|
return false;
|
|
}
|
|
|
|
status = parent_pathref(talloc_tos(),
|
|
handle->conn->cwd_fsp,
|
|
ad_name,
|
|
&parent_fname,
|
|
&at_fname);
|
|
TALLOC_FREE(ad_name);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
return false;
|
|
}
|
|
|
|
rc = SMB_VFS_NEXT_UNLINKAT(handle,
|
|
parent_fname->fsp,
|
|
at_fname,
|
|
0);
|
|
if (rc != 0) {
|
|
DBG_ERR("Unlinking [%s/%s] failed: %s\n",
|
|
smb_fname_str_dbg(parent_fname),
|
|
smb_fname_str_dbg(at_fname), strerror(errno));
|
|
TALLOC_FREE(parent_fname);
|
|
return false;
|
|
}
|
|
|
|
DBG_WARNING("Unlinked [%s/%s] after conversion\n",
|
|
smb_fname_str_dbg(parent_fname),
|
|
smb_fname_str_dbg(at_fname));
|
|
TALLOC_FREE(parent_fname);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Convert from Apple's ._ file to Netatalk
|
|
*
|
|
* Apple's AppleDouble may contain a FinderInfo entry longer then 32
|
|
* bytes containing packed xattrs.
|
|
*
|
|
* @return -1 in case an error occurred, 0 if no conversion was done, 1
|
|
* otherwise
|
|
**/
|
|
int ad_convert(struct vfs_handle_struct *handle,
|
|
const struct smb_filename *smb_fname,
|
|
const char *catia_mappings,
|
|
uint32_t flags)
|
|
{
|
|
struct adouble *ad = NULL;
|
|
bool ok;
|
|
bool converted_xattr = false;
|
|
bool blank;
|
|
int ret;
|
|
|
|
ad = ad_get(talloc_tos(), handle, smb_fname, ADOUBLE_RSRC);
|
|
if (ad == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
ok = ad_convert_xattr(handle,
|
|
ad,
|
|
smb_fname,
|
|
catia_mappings,
|
|
&converted_xattr);
|
|
if (!ok) {
|
|
ret = -1;
|
|
goto done;
|
|
}
|
|
|
|
ok = ad_convert_blank_rfork(handle, ad, flags, &blank);
|
|
if (!ok) {
|
|
ret = -1;
|
|
goto done;
|
|
}
|
|
|
|
if (converted_xattr || blank) {
|
|
ok = ad_convert_truncate(handle, ad, smb_fname);
|
|
if (!ok) {
|
|
ret = -1;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
ok = ad_convert_finderinfo(handle, ad, smb_fname);
|
|
if (!ok) {
|
|
DBG_ERR("Failed to convert [%s]\n",
|
|
smb_fname_str_dbg(smb_fname));
|
|
ret = -1;
|
|
goto done;
|
|
}
|
|
|
|
ok = ad_convert_delete_adfile(handle,
|
|
ad,
|
|
smb_fname,
|
|
flags);
|
|
if (!ok) {
|
|
ret = -1;
|
|
goto done;
|
|
}
|
|
|
|
ret = 0;
|
|
done:
|
|
TALLOC_FREE(ad);
|
|
return ret;
|
|
}
|
|
|
|
static bool ad_unconvert_open_ad(TALLOC_CTX *mem_ctx,
|
|
struct vfs_handle_struct *handle,
|
|
struct smb_filename *smb_fname,
|
|
struct smb_filename *adpath,
|
|
files_struct **_fsp)
|
|
{
|
|
files_struct *fsp = NULL;
|
|
NTSTATUS status;
|
|
int ret;
|
|
|
|
status = openat_pathref_fsp(handle->conn->cwd_fsp, adpath);
|
|
if (!NT_STATUS_IS_OK(status) &&
|
|
!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
|
|
return false;
|
|
}
|
|
|
|
status = SMB_VFS_CREATE_FILE(
|
|
handle->conn,
|
|
NULL, /* req */
|
|
NULL, /* dirfsp */
|
|
adpath,
|
|
FILE_READ_DATA|FILE_WRITE_DATA,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
|
FILE_OPEN_IF,
|
|
0, /* create_options */
|
|
0, /* file_attributes */
|
|
INTERNAL_OPEN_ONLY,
|
|
NULL, /* lease */
|
|
0, /* allocation_size */
|
|
0, /* private_flags */
|
|
NULL, /* sd */
|
|
NULL, /* ea_list */
|
|
&fsp,
|
|
NULL, /* info */
|
|
NULL, NULL); /* create context */
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("SMB_VFS_CREATE_FILE [%s] failed: %s\n",
|
|
smb_fname_str_dbg(adpath), nt_errstr(status));
|
|
return false;
|
|
}
|
|
|
|
if (fsp->fsp_name->st.st_ex_uid != smb_fname->st.st_ex_uid ||
|
|
fsp->fsp_name->st.st_ex_gid != smb_fname->st.st_ex_gid)
|
|
{
|
|
ret = SMB_VFS_FCHOWN(fsp,
|
|
smb_fname->st.st_ex_uid,
|
|
smb_fname->st.st_ex_gid);
|
|
if (ret != 0) {
|
|
DBG_ERR("SMB_VFS_FCHOWN [%s] failed: %s\n",
|
|
fsp_str_dbg(fsp), nt_errstr(status));
|
|
close_file_free(NULL, &fsp, NORMAL_CLOSE);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
*_fsp = fsp;
|
|
return true;
|
|
}
|
|
|
|
static bool ad_unconvert_get_streams(struct vfs_handle_struct *handle,
|
|
struct smb_filename *smb_fname,
|
|
TALLOC_CTX *mem_ctx,
|
|
unsigned int *num_streams,
|
|
struct stream_struct **streams)
|
|
{
|
|
files_struct *fsp = NULL;
|
|
NTSTATUS status;
|
|
|
|
status = openat_pathref_fsp(handle->conn->cwd_fsp, smb_fname);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
return false;
|
|
}
|
|
|
|
status = SMB_VFS_CREATE_FILE(
|
|
handle->conn, /* conn */
|
|
NULL, /* req */
|
|
NULL, /* dirfsp */
|
|
smb_fname, /* fname */
|
|
FILE_READ_ATTRIBUTES, /* access_mask */
|
|
(FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
|
|
FILE_SHARE_DELETE),
|
|
FILE_OPEN, /* create_disposition*/
|
|
0, /* create_options */
|
|
0, /* file_attributes */
|
|
INTERNAL_OPEN_ONLY, /* oplock_request */
|
|
NULL, /* lease */
|
|
0, /* allocation_size */
|
|
0, /* private_flags */
|
|
NULL, /* sd */
|
|
NULL, /* ea_list */
|
|
&fsp, /* result */
|
|
NULL, /* pinfo */
|
|
NULL, NULL); /* create context */
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("Opening [%s] failed: %s\n",
|
|
smb_fname_str_dbg(smb_fname),
|
|
nt_errstr(status));
|
|
return false;
|
|
}
|
|
|
|
status = vfs_fstreaminfo(fsp,
|
|
mem_ctx,
|
|
num_streams,
|
|
streams);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
close_file_free(NULL, &fsp, NORMAL_CLOSE);
|
|
DBG_ERR("streaminfo on [%s] failed: %s\n",
|
|
smb_fname_str_dbg(smb_fname),
|
|
nt_errstr(status));
|
|
return false;
|
|
}
|
|
|
|
status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("close_file [%s] failed: %s\n",
|
|
smb_fname_str_dbg(smb_fname),
|
|
nt_errstr(status));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
struct ad_collect_state {
|
|
bool have_adfile;
|
|
size_t adx_data_off;
|
|
char *rsrc_data_buf;
|
|
};
|
|
|
|
static bool ad_collect_one_stream(struct vfs_handle_struct *handle,
|
|
struct char_mappings **cmaps,
|
|
struct smb_filename *smb_fname,
|
|
const struct stream_struct *stream,
|
|
struct adouble *ad,
|
|
struct ad_collect_state *state)
|
|
{
|
|
struct smb_filename *sname = NULL;
|
|
files_struct *fsp = NULL;
|
|
struct ad_xattr_entry *e = NULL;
|
|
char *mapped_name = NULL;
|
|
char *p = NULL;
|
|
size_t needed_size;
|
|
ssize_t nread;
|
|
NTSTATUS status;
|
|
bool ok;
|
|
|
|
sname = synthetic_smb_fname(ad,
|
|
smb_fname->base_name,
|
|
stream->name,
|
|
NULL,
|
|
smb_fname->twrp,
|
|
0);
|
|
if (sname == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (is_ntfs_default_stream_smb_fname(sname)) {
|
|
TALLOC_FREE(sname);
|
|
return true;
|
|
}
|
|
|
|
DBG_DEBUG("Collecting stream [%s]\n", smb_fname_str_dbg(sname));
|
|
|
|
status = openat_pathref_fsp(handle->conn->cwd_fsp, sname);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
status = SMB_VFS_CREATE_FILE(
|
|
handle->conn,
|
|
NULL, /* req */
|
|
NULL, /* dirfsp */
|
|
sname,
|
|
FILE_READ_DATA|DELETE_ACCESS,
|
|
FILE_SHARE_READ,
|
|
FILE_OPEN,
|
|
0, /* create_options */
|
|
0, /* file_attributes */
|
|
INTERNAL_OPEN_ONLY, /* oplock_request */
|
|
NULL, /* lease */
|
|
0, /* allocation_size */
|
|
0, /* private_flags */
|
|
NULL, /* sd */
|
|
NULL, /* ea_list */
|
|
&fsp,
|
|
NULL, /* info */
|
|
NULL, NULL); /* create context */
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("SMB_VFS_CREATE_FILE [%s] failed\n",
|
|
smb_fname_str_dbg(sname));
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
if (is_afpinfo_stream(stream->name)) {
|
|
char buf[AFP_INFO_SIZE];
|
|
|
|
if (stream->size != AFP_INFO_SIZE) {
|
|
DBG_ERR("Bad size [%zd] on [%s]\n",
|
|
(ssize_t)stream->size,
|
|
smb_fname_str_dbg(sname));
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
nread = SMB_VFS_PREAD(fsp, buf, stream->size, 0);
|
|
if (nread != AFP_INFO_SIZE) {
|
|
DBG_ERR("Bad size [%zd] on [%s]\n",
|
|
(ssize_t)stream->size,
|
|
smb_fname_str_dbg(sname));
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
memcpy(ad->ad_data + ADEDOFF_FINDERI_DOT_UND,
|
|
buf + AFP_OFF_FinderInfo,
|
|
AFP_FinderSize);
|
|
|
|
ok = set_delete_on_close(fsp,
|
|
true,
|
|
fsp->conn->session_info->security_token,
|
|
fsp->conn->session_info->unix_token);
|
|
if (!ok) {
|
|
DBG_ERR("Deleting [%s] failed\n",
|
|
smb_fname_str_dbg(sname));
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
ok = true;
|
|
goto out;
|
|
}
|
|
|
|
if (is_afpresource_stream(stream->name)) {
|
|
ad->ad_rsrc_data = talloc_size(ad, stream->size);
|
|
if (ad->ad_rsrc_data == NULL) {
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
nread = SMB_VFS_PREAD(fsp,
|
|
ad->ad_rsrc_data,
|
|
stream->size,
|
|
0);
|
|
if (nread != stream->size) {
|
|
DBG_ERR("Bad size [%zd] on [%s]\n",
|
|
(ssize_t)stream->size,
|
|
smb_fname_str_dbg(sname));
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
ad_setentrylen(ad, ADEID_RFORK, stream->size);
|
|
|
|
if (!state->have_adfile) {
|
|
/*
|
|
* We have a resource *stream* but no AppleDouble
|
|
* sidecar file, this means the share is configured with
|
|
* fruit:resource=stream. So we should delete the
|
|
* resource stream.
|
|
*/
|
|
ok = set_delete_on_close(
|
|
fsp,
|
|
true,
|
|
fsp->conn->session_info->security_token,
|
|
fsp->conn->session_info->unix_token);
|
|
if (!ok) {
|
|
DBG_ERR("Deleting [%s] failed\n",
|
|
smb_fname_str_dbg(sname));
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
}
|
|
ok = true;
|
|
goto out;
|
|
}
|
|
|
|
ad->adx_entries = talloc_realloc(ad,
|
|
ad->adx_entries,
|
|
struct ad_xattr_entry,
|
|
ad->adx_header.adx_num_attrs + 1);
|
|
if (ad->adx_entries == NULL) {
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
e = &ad->adx_entries[ad->adx_header.adx_num_attrs];
|
|
*e = (struct ad_xattr_entry) {
|
|
.adx_length = stream->size,
|
|
};
|
|
e->adx_name = talloc_strdup(ad, stream->name + 1);
|
|
if (e->adx_name == NULL) {
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
p = strchr(e->adx_name, ':');
|
|
if (p != NULL) {
|
|
*p = '\0';
|
|
}
|
|
|
|
status = string_replace_allocate(handle->conn,
|
|
e->adx_name,
|
|
cmaps,
|
|
ad,
|
|
&mapped_name,
|
|
vfs_translate_to_unix);
|
|
if (!NT_STATUS_IS_OK(status) &&
|
|
!NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))
|
|
{
|
|
DBG_ERR("string_replace_allocate failed\n");
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
e->adx_name = mapped_name;
|
|
e->adx_namelen = strlen(e->adx_name) + 1,
|
|
|
|
DBG_DEBUG("%u: name (%s) size (%zu)\n",
|
|
ad->adx_header.adx_num_attrs,
|
|
e->adx_name,
|
|
(size_t)e->adx_length);
|
|
|
|
ad->adx_header.adx_num_attrs++;
|
|
|
|
needed_size = state->adx_data_off + stream->size;
|
|
if (needed_size > talloc_get_size(ad->adx_data)) {
|
|
ad->adx_data = talloc_realloc(ad,
|
|
ad->adx_data,
|
|
char,
|
|
needed_size);
|
|
if (ad->adx_data == NULL) {
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
nread = SMB_VFS_PREAD(fsp,
|
|
ad->adx_data + state->adx_data_off,
|
|
stream->size,
|
|
0);
|
|
if (nread != stream->size) {
|
|
DBG_ERR("Bad size [%zd] on [%s]\n",
|
|
(ssize_t)stream->size,
|
|
smb_fname_str_dbg(sname));
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
state->adx_data_off += nread;
|
|
|
|
ok = set_delete_on_close(fsp,
|
|
true,
|
|
fsp->conn->session_info->security_token,
|
|
fsp->conn->session_info->unix_token);
|
|
if (!ok) {
|
|
DBG_ERR("Deleting [%s] failed\n",
|
|
smb_fname_str_dbg(sname));
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
TALLOC_FREE(sname);
|
|
if (fsp != NULL) {
|
|
status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("close_file [%s] failed: %s\n",
|
|
smb_fname_str_dbg(smb_fname),
|
|
nt_errstr(status));
|
|
ok = false;
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* Convert filesystem metadata to AppleDouble file
|
|
**/
|
|
bool ad_unconvert(TALLOC_CTX *mem_ctx,
|
|
struct vfs_handle_struct *handle,
|
|
const char *catia_mappings,
|
|
struct smb_filename *smb_fname,
|
|
bool *converted)
|
|
{
|
|
static struct char_mappings **cmaps = NULL;
|
|
TALLOC_CTX *frame = talloc_stackframe();
|
|
struct ad_collect_state state;
|
|
struct stream_struct *streams = NULL;
|
|
struct smb_filename *adpath = NULL;
|
|
struct adouble *ad = NULL;
|
|
unsigned int num_streams = 0;
|
|
size_t to_convert = 0;
|
|
bool have_rsrc = false;
|
|
files_struct *fsp = NULL;
|
|
size_t i;
|
|
NTSTATUS status;
|
|
int ret;
|
|
bool ok;
|
|
|
|
*converted = false;
|
|
|
|
if (cmaps == NULL) {
|
|
const char **mappings = NULL;
|
|
|
|
mappings = str_list_make_v3_const(
|
|
frame, catia_mappings, NULL);
|
|
if (mappings == NULL) {
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
cmaps = string_replace_init_map(mem_ctx, mappings);
|
|
TALLOC_FREE(mappings);
|
|
}
|
|
|
|
ok = ad_unconvert_get_streams(handle,
|
|
smb_fname,
|
|
frame,
|
|
&num_streams,
|
|
&streams);
|
|
if (!ok) {
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < num_streams; i++) {
|
|
if (strcasecmp_m(streams[i].name, "::$DATA") == 0) {
|
|
continue;
|
|
}
|
|
to_convert++;
|
|
if (is_afpresource_stream(streams[i].name)) {
|
|
have_rsrc = true;
|
|
}
|
|
}
|
|
|
|
if (to_convert == 0) {
|
|
ok = true;
|
|
goto out;
|
|
}
|
|
|
|
state = (struct ad_collect_state) {
|
|
.adx_data_off = 0,
|
|
};
|
|
|
|
ret = adouble_path(frame, smb_fname, &adpath);
|
|
if (ret != 0) {
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
ret = SMB_VFS_STAT(handle->conn, adpath);
|
|
if (ret == 0) {
|
|
state.have_adfile = true;
|
|
} else {
|
|
if (errno != ENOENT) {
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
state.have_adfile = false;
|
|
}
|
|
|
|
if (to_convert == 1 && have_rsrc && state.have_adfile) {
|
|
/*
|
|
* So we have just a single stream, the resource fork stream
|
|
* from an AppleDouble file. Fine, that means there's nothing to
|
|
* convert.
|
|
*/
|
|
ok = true;
|
|
goto out;
|
|
}
|
|
|
|
ad = ad_init(frame, ADOUBLE_RSRC);
|
|
if (ad == NULL) {
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < num_streams; i++) {
|
|
ok = ad_collect_one_stream(handle,
|
|
cmaps,
|
|
smb_fname,
|
|
&streams[i],
|
|
ad,
|
|
&state);
|
|
if (!ok) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ok = ad_unconvert_open_ad(frame, handle, smb_fname, adpath, &fsp);
|
|
if (!ok) {
|
|
DBG_ERR("Failed to open adfile [%s]\n",
|
|
smb_fname_str_dbg(smb_fname));
|
|
goto out;
|
|
}
|
|
|
|
ret = ad_fset(handle, ad, fsp);
|
|
if (ret != 0) {
|
|
ok = false;
|
|
goto out;
|
|
}
|
|
|
|
*converted = true;
|
|
ok = true;
|
|
|
|
out:
|
|
if (fsp != NULL) {
|
|
status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("close_file_free() [%s] failed: %s\n",
|
|
smb_fname_str_dbg(smb_fname),
|
|
nt_errstr(status));
|
|
ok = false;
|
|
}
|
|
}
|
|
TALLOC_FREE(frame);
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* Read and parse Netatalk AppleDouble metadata xattr
|
|
**/
|
|
static ssize_t ad_read_meta(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
const struct smb_filename *smb_fname)
|
|
{
|
|
int rc = 0;
|
|
ssize_t ealen;
|
|
bool ok;
|
|
struct files_struct *fsp = smb_fname->fsp;
|
|
|
|
DEBUG(10, ("reading meta xattr for %s\n", smb_fname->base_name));
|
|
|
|
fsp = metadata_fsp(fsp);
|
|
|
|
ealen = SMB_VFS_FGETXATTR(fsp,
|
|
AFPINFO_EA_NETATALK,
|
|
ad->ad_data,
|
|
AD_DATASZ_XATTR);
|
|
|
|
if (ealen == -1) {
|
|
switch (errno) {
|
|
case ENOATTR:
|
|
case ENOENT:
|
|
if (errno == ENOATTR) {
|
|
errno = ENOENT;
|
|
}
|
|
rc = -1;
|
|
goto exit;
|
|
default:
|
|
DEBUG(2, ("error reading meta xattr: %s\n",
|
|
strerror(errno)));
|
|
rc = -1;
|
|
goto exit;
|
|
}
|
|
}
|
|
if (ealen != AD_DATASZ_XATTR) {
|
|
DEBUG(2, ("bad size %zd\n", ealen));
|
|
errno = EINVAL;
|
|
rc = -1;
|
|
goto exit;
|
|
}
|
|
|
|
/* Now parse entries */
|
|
ok = ad_unpack(ad, ADEID_NUM_XATTR, AD_DATASZ_XATTR);
|
|
if (!ok) {
|
|
DBG_WARNING(
|
|
"Invalid AppleDouble xattr metadata (%s) in file: %s - "
|
|
"Consider deleting the corrupted file.\n",
|
|
smb_fname->base_name,
|
|
ad->ad_fsp->fsp_name->base_name);
|
|
errno = EINVAL;
|
|
rc = -1;
|
|
goto exit;
|
|
}
|
|
|
|
if (!ad_getentryoff(ad, ADEID_FINDERI)
|
|
|| !ad_getentryoff(ad, ADEID_COMMENT)
|
|
|| !ad_getentryoff(ad, ADEID_FILEDATESI)
|
|
|| !ad_getentryoff(ad, ADEID_AFPFILEI)
|
|
|| !ad_getentryoff(ad, ADEID_PRIVDEV)
|
|
|| !ad_getentryoff(ad, ADEID_PRIVINO)
|
|
|| !ad_getentryoff(ad, ADEID_PRIVSYN)
|
|
|| !ad_getentryoff(ad, ADEID_PRIVID)) {
|
|
DEBUG(2, ("invalid AppleDouble metadata xattr\n"));
|
|
errno = EINVAL;
|
|
rc = -1;
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
DEBUG(10, ("reading meta xattr for %s, rc: %d\n",
|
|
smb_fname->base_name, rc));
|
|
|
|
if (rc != 0) {
|
|
ealen = -1;
|
|
if (errno == EINVAL) {
|
|
become_root();
|
|
(void)SMB_VFS_FREMOVEXATTR(fsp,
|
|
AFPINFO_EA_NETATALK);
|
|
unbecome_root();
|
|
errno = ENOENT;
|
|
}
|
|
}
|
|
return ealen;
|
|
}
|
|
|
|
static NTSTATUS adouble_open_rsrc_fsp(const struct files_struct *dirfsp,
|
|
const struct smb_filename *smb_base_fname,
|
|
int in_flags,
|
|
mode_t mode,
|
|
struct files_struct **_ad_fsp)
|
|
{
|
|
int rc = 0;
|
|
struct adouble *ad = NULL;
|
|
struct smb_filename *adp_smb_fname = NULL;
|
|
struct files_struct *ad_fsp = NULL;
|
|
NTSTATUS status;
|
|
struct vfs_open_how how = { .flags = in_flags, .mode = mode, };
|
|
|
|
rc = adouble_path(talloc_tos(),
|
|
smb_base_fname,
|
|
&adp_smb_fname);
|
|
if (rc != 0) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
status = create_internal_fsp(dirfsp->conn,
|
|
adp_smb_fname,
|
|
&ad_fsp);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
return status;
|
|
}
|
|
|
|
#ifdef O_PATH
|
|
how.flags &= ~(O_PATH);
|
|
#endif
|
|
if (how.flags & (O_CREAT | O_TRUNC | O_WRONLY)) {
|
|
/* We always need read/write access for the metadata header too */
|
|
how.flags &= ~(O_WRONLY);
|
|
how.flags |= O_RDWR;
|
|
}
|
|
|
|
status = fd_openat(dirfsp,
|
|
adp_smb_fname,
|
|
ad_fsp,
|
|
&how);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
file_free(NULL, ad_fsp);
|
|
return status;
|
|
}
|
|
|
|
if (how.flags & (O_CREAT | O_TRUNC)) {
|
|
ad = ad_init(talloc_tos(), ADOUBLE_RSRC);
|
|
if (ad == NULL) {
|
|
file_free(NULL, ad_fsp);
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
rc = ad_fset(ad_fsp->conn->vfs_handles, ad, ad_fsp);
|
|
if (rc != 0) {
|
|
file_free(NULL, ad_fsp);
|
|
return NT_STATUS_IO_DEVICE_ERROR;
|
|
}
|
|
TALLOC_FREE(ad);
|
|
}
|
|
|
|
*_ad_fsp = ad_fsp;
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
NTSTATUS adouble_open_from_base_fsp(const struct files_struct *dirfsp,
|
|
struct files_struct *base_fsp,
|
|
adouble_type_t type,
|
|
int flags,
|
|
mode_t mode,
|
|
struct files_struct **_ad_fsp)
|
|
{
|
|
*_ad_fsp = NULL;
|
|
|
|
SMB_ASSERT(base_fsp != NULL);
|
|
SMB_ASSERT(!fsp_is_alternate_stream(base_fsp));
|
|
|
|
switch (type) {
|
|
case ADOUBLE_META:
|
|
return NT_STATUS_INTERNAL_ERROR;
|
|
case ADOUBLE_RSRC:
|
|
return adouble_open_rsrc_fsp(dirfsp,
|
|
base_fsp->fsp_name,
|
|
flags,
|
|
mode,
|
|
_ad_fsp);
|
|
}
|
|
|
|
return NT_STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Here's the deal: for ADOUBLE_META we can do without an fd as we can issue
|
|
* path based xattr calls. For ADOUBLE_RSRC however we need a full-fledged fd
|
|
* for file IO on the ._ file.
|
|
*/
|
|
static int ad_open(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
files_struct *fsp,
|
|
const struct smb_filename *smb_fname,
|
|
int flags,
|
|
mode_t mode)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
DBG_DEBUG("Path [%s] type [%s]\n", smb_fname->base_name,
|
|
ad->ad_type == ADOUBLE_META ? "meta" : "rsrc");
|
|
|
|
if (ad->ad_type == ADOUBLE_META) {
|
|
return 0;
|
|
}
|
|
|
|
if (fsp != NULL) {
|
|
ad->ad_fsp = fsp;
|
|
ad->ad_opened = false;
|
|
return 0;
|
|
}
|
|
|
|
status = adouble_open_rsrc_fsp(handle->conn->cwd_fsp,
|
|
smb_fname,
|
|
flags,
|
|
mode,
|
|
&ad->ad_fsp);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
errno = map_errno_from_nt_status(status);
|
|
return -1;
|
|
}
|
|
ad->ad_opened = true;
|
|
|
|
DBG_DEBUG("Path [%s] type [%s]\n",
|
|
smb_fname->base_name,
|
|
ad->ad_type == ADOUBLE_META ? "meta" : "rsrc");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t ad_read_rsrc_adouble(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
const struct smb_filename *smb_fname)
|
|
{
|
|
size_t to_read;
|
|
ssize_t len;
|
|
int ret;
|
|
bool ok;
|
|
|
|
ret = SMB_VFS_NEXT_FSTAT(handle, ad->ad_fsp, &ad->ad_fsp->fsp_name->st);
|
|
if (ret != 0) {
|
|
DBG_ERR("fstat [%s] failed: %s\n",
|
|
fsp_str_dbg(ad->ad_fsp), strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
to_read = ad->ad_fsp->fsp_name->st.st_ex_size;
|
|
if (to_read > AD_XATTR_MAX_HDR_SIZE) {
|
|
to_read = AD_XATTR_MAX_HDR_SIZE;
|
|
}
|
|
|
|
len = SMB_VFS_NEXT_PREAD(handle,
|
|
ad->ad_fsp,
|
|
ad->ad_data,
|
|
to_read,
|
|
0);
|
|
if (len != to_read) {
|
|
DBG_NOTICE("%s %s: bad size: %zd\n",
|
|
smb_fname->base_name, strerror(errno), len);
|
|
return -1;
|
|
}
|
|
|
|
/* Now parse entries */
|
|
ok = ad_unpack(ad,
|
|
ADEID_NUM_DOT_UND,
|
|
ad->ad_fsp->fsp_name->st.st_ex_size);
|
|
if (!ok) {
|
|
DBG_WARNING("Invalid AppleDouble resource (%s) in file: %s - "
|
|
"Consider deleting the corrupted file.\n",
|
|
smb_fname->base_name,
|
|
ad->ad_fsp->fsp_name->base_name);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if ((ad_getentryoff(ad, ADEID_FINDERI) != ADEDOFF_FINDERI_DOT_UND)
|
|
|| (ad_getentrylen(ad, ADEID_FINDERI) < ADEDLEN_FINDERI)
|
|
|| (ad_getentryoff(ad, ADEID_RFORK) < ADEDOFF_RFORK_DOT_UND))
|
|
{
|
|
DBG_WARNING("Invalid AppleDouble resource (%s) in file: %s - "
|
|
"Consider deleting the corrupted file.\n",
|
|
smb_fname->base_name,
|
|
ad->ad_fsp->fsp_name->base_name);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* Read and parse resource fork, either ._ AppleDouble file or xattr
|
|
**/
|
|
static ssize_t ad_read_rsrc(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
const struct smb_filename *smb_fname)
|
|
{
|
|
return ad_read_rsrc_adouble(handle, ad, smb_fname);
|
|
}
|
|
|
|
/**
|
|
* Read and unpack an AppleDouble metadata xattr or resource
|
|
**/
|
|
static ssize_t ad_read(vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
const struct smb_filename *smb_fname)
|
|
{
|
|
switch (ad->ad_type) {
|
|
case ADOUBLE_META:
|
|
return ad_read_meta(handle, ad, smb_fname);
|
|
case ADOUBLE_RSRC:
|
|
return ad_read_rsrc(handle, ad, smb_fname);
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int adouble_destructor(struct adouble *ad)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (!ad->ad_opened) {
|
|
return 0;
|
|
}
|
|
|
|
SMB_ASSERT(ad->ad_fsp != NULL);
|
|
|
|
status = fd_close(ad->ad_fsp);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("Closing [%s] failed: %s\n",
|
|
fsp_str_dbg(ad->ad_fsp), nt_errstr(status));
|
|
}
|
|
file_free(NULL, ad->ad_fsp);
|
|
ad->ad_fsp = NULL;
|
|
ad->ad_opened = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Allocate a struct adouble without initialiing it
|
|
*
|
|
* The struct is either hang of the fsp extension context or if fsp is
|
|
* NULL from ctx.
|
|
*
|
|
* @param[in] ctx talloc context
|
|
* @param[in] handle vfs handle
|
|
* @param[in] type type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
|
|
*
|
|
* @return adouble handle
|
|
**/
|
|
static struct adouble *ad_alloc(TALLOC_CTX *ctx,
|
|
adouble_type_t type)
|
|
{
|
|
int rc = 0;
|
|
size_t adsize = 0;
|
|
struct adouble *ad;
|
|
|
|
switch (type) {
|
|
case ADOUBLE_META:
|
|
adsize = AD_DATASZ_XATTR;
|
|
break;
|
|
case ADOUBLE_RSRC:
|
|
/*
|
|
* AppleDouble ._ file case, optimize for fewer (but larger)
|
|
* IOs. Two cases:
|
|
*
|
|
* - without xattrs size of the header is exactly
|
|
* AD_DATASZ_DOT_UND (82) bytes
|
|
*
|
|
* - with embedded xattrs it can be larger, up to
|
|
* AD_XATTR_MAX_HDR_SIZE
|
|
*
|
|
* Larger headers are not supported, but this is a reasonable
|
|
* limit that is also employed by the macOS client.
|
|
*
|
|
* We used the largest possible size to be able to read the full
|
|
* header with one IO.
|
|
*/
|
|
adsize = AD_XATTR_MAX_HDR_SIZE;
|
|
break;
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
ad = talloc_zero(ctx, struct adouble);
|
|
if (ad == NULL) {
|
|
rc = -1;
|
|
goto exit;
|
|
}
|
|
|
|
if (adsize) {
|
|
ad->ad_data = talloc_zero_array(ad, char, adsize);
|
|
if (ad->ad_data == NULL) {
|
|
rc = -1;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
ad->ad_type = type;
|
|
ad->ad_magic = AD_MAGIC;
|
|
ad->ad_version = AD_VERSION;
|
|
|
|
talloc_set_destructor(ad, adouble_destructor);
|
|
|
|
exit:
|
|
if (rc != 0) {
|
|
TALLOC_FREE(ad);
|
|
}
|
|
return ad;
|
|
}
|
|
|
|
/**
|
|
* Allocate and initialize a new struct adouble
|
|
*
|
|
* @param[in] ctx talloc context
|
|
* @param[in] type type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
|
|
*
|
|
* @return adouble handle, initialized
|
|
**/
|
|
struct adouble *ad_init(TALLOC_CTX *ctx, adouble_type_t type)
|
|
{
|
|
int rc = 0;
|
|
const struct ad_entry_order *eid;
|
|
struct adouble *ad = NULL;
|
|
time_t t = time(NULL);
|
|
|
|
switch (type) {
|
|
case ADOUBLE_META:
|
|
eid = entry_order_meta_xattr;
|
|
break;
|
|
case ADOUBLE_RSRC:
|
|
eid = entry_order_dot_und;
|
|
break;
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
ad = ad_alloc(ctx, type);
|
|
if (ad == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
while (eid->id) {
|
|
ad->ad_eid[eid->id].ade_off = eid->offset;
|
|
ad->ad_eid[eid->id].ade_len = eid->len;
|
|
eid++;
|
|
}
|
|
|
|
/* put something sane in the date fields */
|
|
ad_setdate(ad, AD_DATE_CREATE | AD_DATE_UNIX, t);
|
|
ad_setdate(ad, AD_DATE_MODIFY | AD_DATE_UNIX, t);
|
|
ad_setdate(ad, AD_DATE_ACCESS | AD_DATE_UNIX, t);
|
|
ad_setdate(ad, AD_DATE_BACKUP, htonl(AD_DATE_START));
|
|
|
|
if (rc != 0) {
|
|
TALLOC_FREE(ad);
|
|
}
|
|
return ad;
|
|
}
|
|
|
|
static struct adouble *ad_get_internal(TALLOC_CTX *ctx,
|
|
vfs_handle_struct *handle,
|
|
files_struct *fsp,
|
|
const struct smb_filename *smb_fname,
|
|
adouble_type_t type)
|
|
{
|
|
int rc = 0;
|
|
ssize_t len;
|
|
struct adouble *ad = NULL;
|
|
int mode;
|
|
|
|
if (fsp != NULL) {
|
|
if (fsp_is_alternate_stream(fsp)) {
|
|
smb_fname = fsp->base_fsp->fsp_name;
|
|
} else {
|
|
smb_fname = fsp->fsp_name;
|
|
}
|
|
}
|
|
|
|
DEBUG(10, ("ad_get(%s) called for %s\n",
|
|
type == ADOUBLE_META ? "meta" : "rsrc",
|
|
smb_fname != NULL ? smb_fname->base_name : "???"));
|
|
|
|
ad = ad_alloc(ctx, type);
|
|
if (ad == NULL) {
|
|
rc = -1;
|
|
goto exit;
|
|
}
|
|
|
|
/* Try rw first so we can use the fd in ad_convert() */
|
|
mode = O_RDWR;
|
|
|
|
rc = ad_open(handle, ad, fsp, smb_fname, mode, 0);
|
|
if (rc == -1 && ((errno == EROFS) || (errno == EACCES))) {
|
|
mode = O_RDONLY;
|
|
rc = ad_open(handle, ad, fsp, smb_fname, mode, 0);
|
|
}
|
|
if (rc == -1) {
|
|
DBG_DEBUG("ad_open [%s] error [%s]\n",
|
|
smb_fname->base_name, strerror(errno));
|
|
goto exit;
|
|
|
|
}
|
|
|
|
len = ad_read(handle, ad, smb_fname);
|
|
if (len == -1) {
|
|
DEBUG(10, ("error reading AppleDouble for %s\n",
|
|
smb_fname->base_name));
|
|
rc = -1;
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
DEBUG(10, ("ad_get(%s) for %s returning %d\n",
|
|
type == ADOUBLE_META ? "meta" : "rsrc",
|
|
smb_fname->base_name, rc));
|
|
|
|
if (rc != 0) {
|
|
TALLOC_FREE(ad);
|
|
}
|
|
return ad;
|
|
}
|
|
|
|
/**
|
|
* Return AppleDouble data for a file
|
|
*
|
|
* @param[in] ctx talloc context
|
|
* @param[in] handle vfs handle
|
|
* @param[in] smb_fname pathname to file or directory
|
|
* @param[in] type type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
|
|
*
|
|
* @return talloced struct adouble or NULL on error
|
|
**/
|
|
struct adouble *ad_get(TALLOC_CTX *ctx,
|
|
vfs_handle_struct *handle,
|
|
const struct smb_filename *smb_fname,
|
|
adouble_type_t type)
|
|
{
|
|
return ad_get_internal(ctx, handle, NULL, smb_fname, type);
|
|
}
|
|
|
|
/**
|
|
* Return AppleDouble data for a file
|
|
*
|
|
* @param[in] ctx talloc context
|
|
* @param[in] handle vfs handle
|
|
* @param[in] fsp fsp to use for IO
|
|
* @param[in] type type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
|
|
*
|
|
* @return talloced struct adouble or NULL on error
|
|
**/
|
|
struct adouble *ad_fget(TALLOC_CTX *ctx, vfs_handle_struct *handle,
|
|
files_struct *fsp, adouble_type_t type)
|
|
{
|
|
return ad_get_internal(ctx, handle, fsp, NULL, type);
|
|
}
|
|
|
|
/**
|
|
* Set AppleDouble metadata on a file or directory
|
|
*
|
|
* @param[in] ad adouble handle
|
|
* @param[in] fsp file handle
|
|
*
|
|
* @return status code, 0 means success
|
|
**/
|
|
int ad_fset(struct vfs_handle_struct *handle,
|
|
struct adouble *ad,
|
|
files_struct *fsp)
|
|
{
|
|
int rc = -1;
|
|
ssize_t len;
|
|
bool ok;
|
|
|
|
DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp));
|
|
|
|
ok = ad_pack(handle, ad, fsp);
|
|
if (!ok) {
|
|
return -1;
|
|
}
|
|
|
|
switch (ad->ad_type) {
|
|
case ADOUBLE_META:
|
|
rc = SMB_VFS_NEXT_FSETXATTR(handle,
|
|
fsp->base_fsp ? fsp->base_fsp : fsp,
|
|
AFPINFO_EA_NETATALK,
|
|
ad->ad_data,
|
|
AD_DATASZ_XATTR, 0);
|
|
break;
|
|
case ADOUBLE_RSRC:
|
|
len = SMB_VFS_NEXT_PWRITE(handle,
|
|
fsp,
|
|
ad->ad_data,
|
|
ad_getentryoff(ad, ADEID_RFORK),
|
|
0);
|
|
if (len != ad_getentryoff(ad, ADEID_RFORK)) {
|
|
DBG_ERR("short write on %s: %zd\n", fsp_str_dbg(fsp), len);
|
|
return -1;
|
|
}
|
|
rc = 0;
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
DBG_DEBUG("Path [%s] rc [%d]\n", fsp_str_dbg(fsp), rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool is_adouble_file(const char *path)
|
|
{
|
|
const char *p = NULL;
|
|
int match;
|
|
|
|
p = strrchr(path, '/');
|
|
if (p == NULL) {
|
|
p = path;
|
|
} else {
|
|
p++;
|
|
}
|
|
|
|
match = strncmp(p,
|
|
ADOUBLE_NAME_PREFIX,
|
|
strlen(ADOUBLE_NAME_PREFIX));
|
|
if (match != 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Prepend "._" to a basename
|
|
* Return a new struct smb_filename with stream_name == NULL.
|
|
**/
|
|
int adouble_path(TALLOC_CTX *ctx,
|
|
const struct smb_filename *smb_fname_in,
|
|
struct smb_filename **pp_smb_fname_out)
|
|
{
|
|
char *parent;
|
|
const char *base;
|
|
struct smb_filename *smb_fname = NULL;
|
|
|
|
smb_fname = cp_smb_filename_nostream(ctx, smb_fname_in);
|
|
if (smb_fname == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
/* We're replacing base_name. */
|
|
TALLOC_FREE(smb_fname->base_name);
|
|
|
|
SET_STAT_INVALID(smb_fname->st);
|
|
|
|
if (!parent_dirname(smb_fname, smb_fname_in->base_name,
|
|
&parent, &base)) {
|
|
TALLOC_FREE(smb_fname);
|
|
return -1;
|
|
}
|
|
|
|
if (ISDOT(parent)) {
|
|
smb_fname->base_name = talloc_asprintf(smb_fname,
|
|
"._%s", base);
|
|
} else {
|
|
smb_fname->base_name = talloc_asprintf(smb_fname,
|
|
"%s/._%s", parent, base);
|
|
}
|
|
if (smb_fname->base_name == NULL) {
|
|
TALLOC_FREE(smb_fname);
|
|
return -1;
|
|
}
|
|
|
|
*pp_smb_fname_out = smb_fname;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Allocate and initialize an AfpInfo struct
|
|
**/
|
|
AfpInfo *afpinfo_new(TALLOC_CTX *ctx)
|
|
{
|
|
AfpInfo *ai = talloc_zero(ctx, AfpInfo);
|
|
if (ai == NULL) {
|
|
return NULL;
|
|
}
|
|
ai->afpi_Signature = AFP_Signature;
|
|
ai->afpi_Version = AFP_Version;
|
|
ai->afpi_BackupTime = AD_DATE_START;
|
|
return ai;
|
|
}
|
|
|
|
/**
|
|
* Pack an AfpInfo struct into a buffer
|
|
*
|
|
* Buffer size must be at least AFP_INFO_SIZE
|
|
* Returns size of packed buffer
|
|
**/
|
|
ssize_t afpinfo_pack(const AfpInfo *ai, char *buf)
|
|
{
|
|
memset(buf, 0, AFP_INFO_SIZE);
|
|
|
|
RSIVAL(buf, 0, ai->afpi_Signature);
|
|
RSIVAL(buf, 4, ai->afpi_Version);
|
|
RSIVAL(buf, 12, ai->afpi_BackupTime);
|
|
memcpy(buf + 16, ai->afpi_FinderInfo, sizeof(ai->afpi_FinderInfo));
|
|
|
|
return AFP_INFO_SIZE;
|
|
}
|
|
|
|
/**
|
|
* Unpack a buffer into a AfpInfo structure
|
|
*
|
|
* Buffer size must be at least AFP_INFO_SIZE
|
|
* Returns allocated AfpInfo struct
|
|
**/
|
|
AfpInfo *afpinfo_unpack(TALLOC_CTX *ctx, const void *data)
|
|
{
|
|
AfpInfo *ai = talloc_zero(ctx, AfpInfo);
|
|
if (ai == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
ai->afpi_Signature = RIVAL(data, 0);
|
|
ai->afpi_Version = RIVAL(data, 4);
|
|
ai->afpi_BackupTime = RIVAL(data, 12);
|
|
memcpy(ai->afpi_FinderInfo, (const char *)data + 16,
|
|
sizeof(ai->afpi_FinderInfo));
|
|
|
|
if (ai->afpi_Signature != AFP_Signature
|
|
|| ai->afpi_Version != AFP_Version) {
|
|
DEBUG(1, ("Bad AfpInfo signature or version\n"));
|
|
TALLOC_FREE(ai);
|
|
}
|
|
|
|
return ai;
|
|
}
|