1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  14  */
  15 
  16 /*
  17  * Create context handler for "AAPL" extensions.
  18  * See: smbsrv/smb2_aapl.h for documentation.
  19  */
  20 
  21 #include <smbsrv/smb2_kproto.h>
  22 #include <smbsrv/smb2_aapl.h>
  23 #include <smbsrv/smb_fsops.h>
  24 
  25 /* SMB2 AAPL extensions: enabled? */
  26 int smb2_aapl_extensions = 1;
  27 
  28 /*
  29  * smb2_aapl_server_caps is a flags word containing detailed
  30  * capabilities as shown in smb2_aapl.h (kAPPL_...)
  31  *
  32  * We actually can support OSX_COPYFILE but modern MacOS clients
  33  * work better using the plain old FSCTL_SRV_COPYCHUNK ioctl, which
  34  * avoids our needing to handle all the meta-data and streams.
  35  *
  36  * We could turn on kAAPL_UNIX_BASED below and report UNIX modes in
  37  * directory listings (see smb2_aapl_get_macinfo below) but we don't
  38  * because the modes ZFS presents with non-trivial ACLs cause mac
  39  * clients to misbehave when copying files from the share to local.
  40  * For example, we may have a file that we can read, but which has
  41  * mode 0200.  When the mac copies such a file to the local disk,
  42  * the copy cannot be opened for read.  For now just turn off the
  43  * kAAPL_UNIX_BASED flag.  Later we might set this flag and return
  44  * modes only when we have a trivial ACL.
  45  */
  46 uint64_t smb2_aapl_server_caps =
  47         kAAPL_SUPPORTS_READ_DIR_ATTR;
  48         /* | kAAPL_SUPPORTS_OSX_COPYFILE */
  49         /* | kAAPL_UNIX_BASED; */
  50 
  51 uint64_t smb2_aapl_volume_caps = kAAPL_SUPPORTS_FULL_SYNC;
  52 
  53 /*
  54  * Normally suppress file IDs for MacOS because it
  55  * requires them to be unique per share, and ours
  56  * can have duplicates under .zfs or sub-mounts.
  57  */
  58 int smb2_aapl_use_file_ids = 0;
  59 
  60 static uint32_t smb2_aapl_srv_query(smb_request_t *,
  61         mbuf_chain_t *, mbuf_chain_t *);
  62 
  63 static int smb_aapl_ext_maxlen = 512;
  64 
  65 /*
  66  * Decode an AAPL create context (command code) and build the
  67  * corresponding AAPL c.c. response.
  68  */
  69 uint32_t
  70 smb2_aapl_crctx(smb_request_t *sr,
  71     mbuf_chain_t *mbcin,
  72     mbuf_chain_t *mbcout)
  73 {
  74         uint32_t cmdcode;
  75         uint32_t status;
  76         int rc;
  77 
  78         if (smb2_aapl_extensions == 0)
  79                 return (NT_STATUS_NOT_SUPPORTED);
  80 
  81         rc = smb_mbc_decodef(mbcin, "l4.", &cmdcode);
  82         if (rc != 0)
  83                 return (NT_STATUS_INFO_LENGTH_MISMATCH);
  84         mbcout->max_bytes = smb_aapl_ext_maxlen;
  85         (void) smb_mbc_encodef(mbcout, "ll", cmdcode, 0);
  86 
  87         switch (cmdcode) {
  88         case kAAPL_SERVER_QUERY:
  89                 status = smb2_aapl_srv_query(sr, mbcin, mbcout);
  90                 break;
  91         case kAAPL_RESOLVE_ID:
  92         default:
  93                 status = NT_STATUS_INVALID_INFO_CLASS;
  94                 break;
  95         }
  96 
  97         return (status);
  98 }
  99 
 100 /*
 101  * Handle an AAPL c.c. kAAPL_SERVER_QUERY
 102  * Return our Mac-ish capabilities.  We also need to remember
 103  * that this client wants AAPL readdir etc.
 104  * Typically see: client_bitmap=7, client_caps=7
 105  */
 106 static uint32_t
 107 smb2_aapl_srv_query(smb_request_t *sr,
 108     mbuf_chain_t *mbcin, mbuf_chain_t *mbcout)
 109 {
 110         uint64_t client_bitmap;
 111         uint64_t client_caps;
 112         uint64_t server_bitmap;
 113         int rc;
 114 
 115         rc = smb_mbc_decodef(
 116             mbcin, "qq",
 117             &client_bitmap,
 118             &client_caps);
 119         if (rc != 0)
 120                 return (NT_STATUS_INFO_LENGTH_MISMATCH);
 121 
 122         smb_rwx_rwenter(&sr->session->s_lock, RW_WRITER);
 123 
 124         /* Remember that this is a MacOS client. */
 125         sr->session->native_os = NATIVE_OS_MACOS;
 126         sr->session->s_flags |= SMB_SSN_AAPL_CCEXT;
 127 
 128         /*
 129          * Select which parts of the bitmap we use.
 130          */
 131         server_bitmap = client_bitmap &
 132             (kAAPL_SERVER_CAPS | kAAPL_VOLUME_CAPS);
 133         (void) smb_mbc_encodef(mbcout, "q", server_bitmap);
 134 
 135         if ((server_bitmap & kAAPL_SERVER_CAPS) != 0) {
 136                 uint64_t server_caps =
 137                     smb2_aapl_server_caps & client_caps;
 138                 if (server_caps & kAAPL_SUPPORTS_READ_DIR_ATTR)
 139                         sr->session->s_flags |= SMB_SSN_AAPL_READDIR;
 140                 (void) smb_mbc_encodef(mbcout, "q", server_caps);
 141         }
 142         if ((server_bitmap & kAAPL_VOLUME_CAPS) != 0) {
 143                 (void) smb_mbc_encodef(mbcout, "q", smb2_aapl_volume_caps);
 144         }
 145 
 146         /* Pad2, null model string. */
 147         (void) smb_mbc_encodef(mbcout, "ll", 0, 0);
 148 
 149         smb_rwx_rwexit(&sr->session->s_lock);
 150 
 151         return (0);
 152 }
 153 
 154 /*
 155  * Get additional information about a directory entry
 156  * needed when MacOS is using the AAPL extensions.
 157  * This is called after smb_odir_read_fileinfo has
 158  * filled in the fileinfo.  This fills in macinfo.
 159  *
 160  * This does a couple FS operations per directory entry.
 161  * That has some cost, but if we don't do it for them here,
 162  * the client has to make two more round trips for each
 163  * directory entry, which is much worse.
 164  */
 165 int
 166 smb2_aapl_get_macinfo(smb_request_t *sr, smb_odir_t *od,
 167     smb_fileinfo_t *fileinfo, smb_macinfo_t *mi,
 168     char *tbuf, size_t tbuflen)
 169 {
 170         int             rc;
 171         cred_t          *kcr = zone_kcred();
 172         smb_node_t      *fnode, *snode;
 173         smb_attr_t      attr;
 174         uint32_t        AfpInfo[15];
 175 
 176         bzero(mi, sizeof (*mi));
 177 
 178         rc = smb_fsop_lookup(sr, od->d_cred, SMB_CASE_SENSITIVE,
 179             od->d_tree->t_snode, od->d_dnode, fileinfo->fi_name, &fnode);
 180         if (rc != 0)
 181                 return (rc);
 182         /* Note: hold ref on fnode, must release */
 183 
 184         smb_fsop_eaccess(sr, od->d_cred, fnode, &mi->mi_maxaccess);
 185 
 186         /*
 187          * mi_rforksize
 188          * Get length of stream: "AFP_Resource"
 189          * Return size=zero if not found.
 190          */
 191         (void) snprintf(tbuf, tbuflen, "%s:AFP_Resource", fileinfo->fi_name);
 192         rc = smb_fsop_lookup_name(sr, kcr, 0, sr->tid_tree->t_snode,
 193             od->d_dnode, tbuf, &snode);
 194         if (rc == 0) {
 195                 bzero(&attr, sizeof (attr));
 196                 attr.sa_mask = SMB_AT_SIZE | SMB_AT_ALLOCSZ;
 197                 rc = smb_node_getattr(NULL, snode, kcr, NULL, &attr);
 198                 if (rc == 0) {
 199                         mi->mi_rforksize = attr.sa_vattr.va_size;
 200                 }
 201                 smb_node_release(snode);
 202                 snode = NULL;
 203         }
 204 
 205         /*
 206          * mi_finder
 207          * Get contents of stream: "AFP_AfpInfo"
 208          * read 60 bytes, copy 32 bytes at off 16
 209          */
 210         (void) snprintf(tbuf, tbuflen, "%s:AFP_AfpInfo", fileinfo->fi_name);
 211         rc = smb_fsop_lookup_name(sr, kcr, 0, sr->tid_tree->t_snode,
 212             od->d_dnode, tbuf, &snode);
 213         if (rc == 0) {
 214                 iovec_t iov;
 215                 uio_t uio;
 216 
 217                 bzero(&AfpInfo, sizeof (AfpInfo));
 218                 bzero(&uio, sizeof (uio));
 219 
 220                 iov.iov_base = (void *) &AfpInfo;
 221                 iov.iov_len = sizeof (AfpInfo);
 222                 uio.uio_iov = &iov;
 223                 uio.uio_iovcnt = 1;
 224                 uio.uio_resid = sizeof (AfpInfo);
 225                 uio.uio_segflg = UIO_SYSSPACE;
 226                 uio.uio_extflg = UIO_COPY_DEFAULT;
 227                 rc = smb_fsop_read(sr, kcr, snode, NULL, &uio, 0);
 228                 if (rc == 0 && uio.uio_resid == 0) {
 229                         bcopy(&AfpInfo[4], &mi->mi_finderinfo,
 230                             sizeof (mi->mi_finderinfo));
 231                 }
 232                 smb_node_release(snode);
 233                 snode = NULL;
 234         }
 235 
 236         /*
 237          * Later: Fill in the mode if we have a trivial ACL
 238          * (otherwise leaving it zero as we do now).
 239          */
 240         if (smb2_aapl_server_caps & kAAPL_UNIX_BASED) {
 241                 bzero(&attr, sizeof (attr));
 242                 attr.sa_mask = SMB_AT_MODE;
 243                 rc = smb_node_getattr(NULL, fnode, kcr, NULL, &attr);
 244                 if (rc == 0) {
 245                         mi->mi_unixmode = (uint16_t)attr.sa_vattr.va_mode;
 246                 }
 247         }
 248 
 249         smb_node_release(fnode);
 250         return (0);
 251 }