Added support for OS/2 EA's in smbd server. Test with smbtorture eatest.
authorJeremy Allison <jra@samba.org>
Wed, 31 Mar 2004 02:20:16 +0000 (02:20 +0000)
committerJeremy Allison <jra@samba.org>
Wed, 31 Mar 2004 02:20:16 +0000 (02:20 +0000)
New protocol option "ea support" to turn them on (off by default). Conrad
at Apple may like this as it allows MacOS resource forks to be stored on
a file. Passes valgrind. Documentation to follow.
Jeremy.

source/include/smb.h
source/param/loadparm.c
source/smbd/posix_acls.c
source/smbd/trans2.c

index 32fa1b57659344a13aadbb233f813f8340ee7e4f..24c0100b5b9e5a26da487ab23f1f0d598b6b32b2 100644 (file)
@@ -1666,4 +1666,6 @@ struct ea_struct {
        DATA_BLOB value;
 };
 
+/* EA names used internally in Samba. KEEP UP TO DATE with prohibited_ea_names in trans2.c !. */
+#define SAMBA_POSIX_INHERITANCE_EA_NAME "user.SAMBA_PAI"
 #endif /* _SMB_H */
index 5313491f7efb3e6250370f3d74dede41a2ca4984..4c0a3948f89fe3c4c6c9ad40a216b707c53b75a8 100644 (file)
@@ -412,6 +412,7 @@ typedef struct
        BOOL bProfileAcls;
        BOOL bMap_acl_inherit;
        BOOL bAfs_Share;
+       BOOL bEASupport;
        param_opt_struct *param_opt;
 
        char dummy[3];          /* for alignment */
@@ -533,6 +534,7 @@ static service sDefault = {
        False,                  /* bProfileAcls */
        False,                  /* bMap_acl_inherit */
        False,                  /* bAfs_Share */
+       False,                  /* bEASupport */
        
        NULL,                   /* Parametric options */
 
@@ -885,6 +887,7 @@ static struct parm_struct parm_table[] = {
        {"disable netbios", P_BOOL, P_GLOBAL, &Globals.bDisableNetbios, NULL, NULL, FLAG_ADVANCED}, 
 
        {"acl compatibility", P_STRING, P_GLOBAL, &Globals.szAclCompat, handle_acl_compatibility,  NULL, FLAG_ADVANCED | FLAG_SHARE | FLAG_GLOBAL}, 
+       {"ea support", P_BOOL, P_LOCAL, &sDefault.bEASupport, NULL, NULL, FLAG_ADVANCED | FLAG_SHARE | FLAG_GLOBAL}, 
        {"nt acl support", P_BOOL, P_LOCAL, &sDefault.bNTAclSupport, NULL, NULL, FLAG_ADVANCED | FLAG_SHARE | FLAG_GLOBAL}, 
        {"nt pipe support", P_BOOL, P_GLOBAL, &Globals.bNTPipeSupport, NULL, NULL, FLAG_ADVANCED}, 
        {"nt status support", P_BOOL, P_GLOBAL, &Globals.bNTStatusSupport, NULL, NULL, FLAG_ADVANCED}, 
@@ -1868,6 +1871,7 @@ FN_LOCAL_BOOL(lp_inherit_acls, bInheritACLS)
 FN_LOCAL_BOOL(lp_use_client_driver, bUseClientDriver)
 FN_LOCAL_BOOL(lp_default_devmode, bDefaultDevmode)
 FN_LOCAL_BOOL(lp_nt_acl_support, bNTAclSupport)
+FN_LOCAL_BOOL(lp_ea_support, bEASupport)
 FN_LOCAL_BOOL(_lp_use_sendfile, bUseSendfile)
 FN_LOCAL_BOOL(lp_profile_acls, bProfileAcls)
 FN_LOCAL_BOOL(lp_map_acl_inherit, bMap_acl_inherit)
index 8033c694f5db8ab7bba72b7edc528a5b3e795ae5..ee370437ec35d23da3797b849d0f40b4925e962c 100644 (file)
@@ -57,8 +57,6 @@ typedef struct canon_ace {
  * +------+------+-------------+---------------------+-------------+--------------------+
  */
 
-#define SAMBA_POSIX_INHERITANCE_EA_NAME "user.SAMBA_PAI"
-
 #define PAI_VERSION_OFFSET     0
 #define PAI_FLAG_OFFSET                1
 #define PAI_NUM_ENTRIES_OFFSET 2
index 2f164dafa284e145920c68a6516ebdb8c40ab85d..b1807705a0bf353ac512c938910e96d65a833b4d 100644 (file)
@@ -49,6 +49,324 @@ SMB_BIG_UINT get_allocation_size(files_struct *fsp, SMB_STRUCT_STAT *sbuf)
        return ret;
 }
 
+/****************************************************************************
+ Utility functions for dealing with extended attributes.
+****************************************************************************/
+
+static const char *prohibited_ea_names[] = {
+       SAMBA_POSIX_INHERITANCE_EA_NAME,
+       NULL
+};
+
+/****************************************************************************
+ Refuse to allow clients to overwrite our private xattrs.
+****************************************************************************/
+
+static BOOL samba_private_attr_name(const char *unix_ea_name)
+{
+       int i;
+
+       for (i = 0; prohibited_ea_names[i]; i++) {
+               if (strequal( prohibited_ea_names[i], unix_ea_name))
+                       return True;
+       }
+       return False;
+}
+
+struct ea_list {
+       struct ea_list *next, *prev;
+       struct ea_struct ea;
+};
+
+/****************************************************************************
+ Get one EA value. Fill in a struct ea_struct.
+****************************************************************************/
+
+static BOOL get_ea_value(TALLOC_CTX *mem_ctx, connection_struct *conn, files_struct *fsp,
+                               const char *fname, char *ea_name, struct ea_struct *pea)
+{
+       /* Get the value of this xattr. Max size is 64k. */
+       size_t attr_size = 256;
+       char *val = NULL;
+       ssize_t sizeret;
+
+ again:
+
+       val = talloc_realloc(mem_ctx, val, attr_size);
+       if (!val) {
+               return False;
+       }
+
+       if (fsp && fsp->fd != -1) {
+               sizeret = SMB_VFS_FGETXATTR(fsp, fsp->fd, ea_name, val, attr_size);
+       } else {
+               sizeret = SMB_VFS_GETXATTR(conn, fname, ea_name, val, attr_size);
+       }
+
+       if (sizeret == -1 && errno == ERANGE && attr_size != 65536) {
+               attr_size = 65536;
+               goto again;
+       }
+
+       if (sizeret == -1) {
+               return False;
+       }
+
+       DEBUG(10,("get_ea_value: EA %s is of length %d: ", ea_name, sizeret));
+       dump_data(10, val, sizeret);
+
+       pea->flags = 0;
+       if (strnequal(ea_name, "user.", 5)) {
+               pea->name = &ea_name[5];
+       } else {
+               pea->name = ea_name;
+       }
+       pea->value.data = val;
+       pea->value.length = (size_t)sizeret;
+       return True;
+}
+
+/****************************************************************************
+ Return a linked list of the total EA's. Plus a guess as to the total size
+ (NB. The is not the total size on the wire - we need to convert to DOS
+ codepage for that).
+****************************************************************************/
+
+static struct ea_list *get_ea_list(TALLOC_CTX *mem_ctx, connection_struct *conn, files_struct *fsp, const char *fname, size_t *pea_total_len)
+{
+       /* Get a list of all xattrs. Max namesize is 64k. */
+       size_t ea_namelist_size = 1024;
+       char *ea_namelist;
+       char *p;
+       ssize_t sizeret;
+       int i;
+       struct ea_list *ea_list_head = NULL;
+
+       if (pea_total_len) {
+               *pea_total_len = 0;
+       }
+
+       if (!lp_ea_support(SNUM(conn))) {
+               return NULL;
+       }
+
+       for (i = 0, ea_namelist = talloc(mem_ctx, ea_namelist_size); i < 6;
+                       ea_namelist = talloc_realloc(mem_ctx, ea_namelist, ea_namelist_size), i++) {
+               if (fsp && fsp->fd != -1) {
+                       sizeret = SMB_VFS_FLISTXATTR(fsp, fsp->fd, ea_namelist, ea_namelist_size);
+               } else {
+                       sizeret = SMB_VFS_LISTXATTR(conn, fname, ea_namelist, ea_namelist_size);
+               }
+
+               if (sizeret == -1 && errno == ERANGE) {
+                       ea_namelist_size *= 2;
+               } else {
+                       break;
+               }
+       }
+
+       if (sizeret == -1)
+               return NULL;
+
+       DEBUG(10,("get_ea_list: ea_namelist size = %d\n", sizeret ));
+
+       if (sizeret) {
+               for (p = ea_namelist; p - ea_namelist < sizeret; p += strlen(p) + 1) {
+                       struct ea_list *listp, *tmp;
+
+                       if (strnequal(p, "system.", 7) || samba_private_attr_name(p))
+                               continue;
+               
+                       listp = talloc(mem_ctx, sizeof(struct ea_list));
+                       if (!listp)
+                               return NULL;
+
+                       if (!get_ea_value(mem_ctx, conn, fsp, fname, p, &listp->ea)) {
+                               return NULL;
+                       }
+
+                       if (pea_total_len) {
+                               *pea_total_len += 4 + strlen(p) + 1 + listp->ea.value.length;
+                       }
+                       DLIST_ADD_END(ea_list_head, listp, tmp);
+               }
+       }
+
+       /* Add on 4 for total length. */
+       if (pea_total_len) {
+               *pea_total_len += 4;
+       }
+       return ea_list_head;
+}
+
+/****************************************************************************
+ Fill a qfilepathinfo buffer with EA's.
+****************************************************************************/
+
+static unsigned int fill_ea_buffer(char *pdata, unsigned int total_data_size,
+       connection_struct *conn, files_struct *fsp, const char *fname)
+{
+       unsigned int ret_data_size = 4;
+       char *p = pdata;
+       size_t total_ea_len;
+       TALLOC_CTX *mem_ctx = talloc_init("fill_ea_buffer");
+       struct ea_list *ea_list = get_ea_list(mem_ctx, conn, fsp, fname, &total_ea_len);
+
+       SMB_ASSERT(total_data_size >= 4);
+
+       SIVAL(pdata,0,0);
+       if (!mem_ctx) {
+               return 4;
+       }
+
+       if (!ea_list) {
+               talloc_destroy(mem_ctx);
+               return 4;
+       }
+
+       if (total_ea_len > total_data_size) {
+               talloc_destroy(mem_ctx);
+               return 4;
+       }
+
+       total_data_size -= 4;
+       for (p = pdata + 4; ea_list; ea_list = ea_list->next) {
+               size_t dos_namelen;
+               fstring dos_ea_name;
+               push_ascii_fstring(dos_ea_name, ea_list->ea.name);
+               dos_namelen = strlen(dos_ea_name);
+               if (dos_namelen > 255 || dos_namelen == 0) {
+                       break;
+               }
+               if (ea_list->ea.value.length > 65535) {
+                       break;
+               }
+               if (4 + dos_namelen + 1 + ea_list->ea.value.length > total_data_size) {
+                       break;
+               }
+
+               /* We know we have room. */
+               SCVAL(p,0,ea_list->ea.flags);
+               SCVAL(p,1,dos_namelen);
+               SSVAL(p,2,ea_list->ea.value.length);
+               fstrcpy(p+4, dos_ea_name);
+               memcpy( p + 4 + dos_namelen + 1, ea_list->ea.value.data, ea_list->ea.value.length);
+
+               total_data_size -= 4 + dos_namelen + 1 + ea_list->ea.value.length;
+               p += 4 + dos_namelen + 1 + ea_list->ea.value.length;
+       }
+
+       ret_data_size = PTR_DIFF(p, pdata);
+       talloc_destroy(mem_ctx);
+       SIVAL(pdata,0,ret_data_size);
+       return ret_data_size;
+}
+
+static unsigned int estimate_ea_size(connection_struct *conn, files_struct *fsp, const char *fname)
+{
+       size_t total_ea_len = 0;
+       TALLOC_CTX *mem_ctx = talloc_init("estimate_ea_size");
+
+       (void)get_ea_list(mem_ctx, conn, fsp, fname, &total_ea_len);
+       talloc_destroy(mem_ctx);
+       return total_ea_len;
+}
+
+/****************************************************************************
+ Set or delete an extended attribute.
+****************************************************************************/
+
+static NTSTATUS set_ea(connection_struct *conn, files_struct *fsp, const char *fname,
+                       char *pdata, int total_data)
+{
+       unsigned int namelen;
+       unsigned int ealen;
+       int ret;
+       fstring unix_ea_name;
+
+       if (!lp_ea_support(SNUM(conn))) {
+               return NT_STATUS_EAS_NOT_SUPPORTED;
+       }
+
+       if (total_data < 8) {
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (IVAL(pdata,0) > total_data) {
+               DEBUG(10,("set_ea: bad total data size (%u) > %u\n", IVAL(pdata,0), (unsigned int)total_data));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       pdata += 4;
+       namelen = CVAL(pdata,1);
+       ealen = SVAL(pdata,2);
+       pdata += 4;
+       if (total_data < 8 + namelen + 1 + ealen) {
+               DEBUG(10,("set_ea: bad total data size (%u) < 8 + namelen (%u) + 1 + ealen (%u)\n",
+                       (unsigned int)total_data, namelen, ealen));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       if (pdata[namelen] != '\0') {
+               DEBUG(10,("set_ea: ea name not null terminated\n"));
+               return NT_STATUS_INVALID_PARAMETER;
+       }
+
+       fstrcpy(unix_ea_name, "user."); /* All EA's must start with user. */
+       pull_ascii(&unix_ea_name[5], pdata, sizeof(fstring) - 5, -1, STR_TERMINATE);
+       pdata += (namelen + 1);
+
+       DEBUG(10,("set_ea: ea_name %s ealen = %u\n", unix_ea_name, ealen));
+       if (ealen) {
+               DEBUG(10,("set_ea: data :\n"));
+               dump_data(10, pdata, ealen);
+       }
+
+       if (samba_private_attr_name(unix_ea_name)) {
+               DEBUG(10,("set_ea: ea name %s is a private Samba name.\n", unix_ea_name));
+               return NT_STATUS_ACCESS_DENIED;
+       }
+
+       if (ealen == 0) {
+               /* Remove the attribute. */
+               if (fsp && (fsp->fd != -1)) {
+                       DEBUG(10,("set_ea: deleting ea name %s on file %s by file descriptor.\n",
+                               unix_ea_name, fsp->fsp_name));
+                       ret = SMB_VFS_FREMOVEXATTR(fsp, fsp->fd, unix_ea_name);
+               } else {
+                       DEBUG(10,("set_ea: deleting ea name %s on file %s.\n",
+                               unix_ea_name, fname));
+                       ret = SMB_VFS_REMOVEXATTR(conn, fname, unix_ea_name);
+               }
+#ifdef ENOATTR
+               /* Removing a non existent attribute always succeeds. */
+               DEBUG(10,("set_ea: deleting ea name %s didn't exist - succeeding by default.\n", unix_ea_name));
+               if (ret == -1 && errno == ENOATTR) {
+                       ret = 0;
+               }
+#endif
+       } else {
+               if (fsp && (fsp->fd != -1)) {
+                       DEBUG(10,("set_ea: setting ea name %s on file %s by file descriptor.\n",
+                               unix_ea_name, fsp->fsp_name));
+                       ret = SMB_VFS_FSETXATTR(fsp, fsp->fd, unix_ea_name, pdata, ealen, 0);
+               } else {
+                       DEBUG(10,("set_ea: setting ea name %s on file %s.\n",
+                               unix_ea_name, fname));
+                       ret = SMB_VFS_SETXATTR(conn, fname, unix_ea_name, pdata, ealen, 0);
+               }
+       }
+
+       if (ret == -1) {
+               if (errno == ENOTSUP) {
+                       return NT_STATUS_EAS_NOT_SUPPORTED;
+               }
+               return map_nt_error_from_unix(errno);
+       }
+
+       return NT_STATUS_OK;
+}
+
 /****************************************************************************
   Send the required number of replies back.
   We assume all fields other than the data fields are
@@ -2048,8 +2366,8 @@ static int call_trans2qfilepathinfo(connection_struct *conn,
                        break;
 
                case SMB_INFO_QUERY_ALL_EAS:
-                       data_size = 4;
-                       SIVAL(pdata,0,0); /* ea size */
+                       /* We have data_size bytes to put EA's into. */
+                       data_size = fill_ea_buffer(pdata, data_size, conn, fsp, fname);
                        break;
 
                case SMB_FILE_BASIC_INFORMATION:
@@ -2095,8 +2413,12 @@ static int call_trans2qfilepathinfo(connection_struct *conn,
 
                case SMB_FILE_EA_INFORMATION:
                case SMB_QUERY_FILE_EA_INFO:
+               {
+                       unsigned int ea_size = estimate_ea_size(conn, fsp, fname);
                        data_size = 4;
+                       SIVAL(pdata,0,ea_size);
                        break;
+               }
 
                /* Get the 8.3 name - used if NT SMB was negotiated. */
                case SMB_QUERY_FILE_ALT_NAME_INFO:
@@ -2703,7 +3025,10 @@ static int call_trans2setfilepathinfo(connection_struct *conn,
                }
 
                case SMB_INFO_SET_EA:
-                       return(ERROR_DOS(ERRDOS,ERReasnotsupported));
+                       status = set_ea(conn, fsp, fname, pdata, total_data);
+                       if (NT_STATUS_V(status) !=  NT_STATUS_V(NT_STATUS_OK))
+                               return ERROR_NT(status);
+                       break;
 
                /* XXXX um, i don't think this is right.
                        it's also not in the cifs6.txt spec.