1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 /*
  28  * Copyright 2018 Nexenta Systems, Inc.
  29  */
  30 
  31 #include <sys/param.h>
  32 #include <sys/types.h>
  33 #include <sys/pathname.h>
  34 #include <sys/errno.h>
  35 #include <sys/cmn_err.h>
  36 #include <sys/debug.h>
  37 #include <sys/systm.h>
  38 #include <sys/unistd.h>
  39 #include <sys/door.h>
  40 #include <sys/socket.h>
  41 #include <nfs/export.h>
  42 #include <nfs/nfs_cmd.h>
  43 #include <sys/kmem.h>
  44 #include <sys/sunddi.h>
  45 
  46 #define NFSCMD_DR_TRYCNT        8
  47 
  48 #ifdef nextdp
  49 #undef nextdp
  50 #endif
  51 #define nextdp(dp)      ((struct dirent64 *)((char *)(dp) + (dp)->d_reclen))
  52 
  53 typedef struct nfscmd_globals {
  54         kmutex_t        nfscmd_lock;
  55         door_handle_t   nfscmd_dh;
  56 } nfscmd_globals_t;
  57 
  58 static zone_key_t nfscmd_zone_key;
  59 
  60 static struct charset_cache *nfscmd_charmap(exportinfo_t *exi,
  61     struct sockaddr *sp);
  62 static void *nfscmd_zone_init(zoneid_t);
  63 static void nfscmd_zone_fini(zoneid_t, void *);
  64 
  65 void
  66 nfscmd_args(uint_t did)
  67 {
  68         nfscmd_globals_t *ncg = zone_getspecific(nfscmd_zone_key, curzone);
  69 
  70         mutex_enter(&ncg->nfscmd_lock);
  71         if (ncg->nfscmd_dh != NULL)
  72                 door_ki_rele(ncg->nfscmd_dh);
  73         ncg->nfscmd_dh = door_ki_lookup(did);
  74         mutex_exit(&ncg->nfscmd_lock);
  75 }
  76 
  77 void
  78 nfscmd_init(void)
  79 {
  80         zone_key_create(&nfscmd_zone_key, nfscmd_zone_init,
  81             NULL, nfscmd_zone_fini);
  82 }
  83 
  84 void
  85 nfscmd_fini(void)
  86 {
  87         (void) zone_key_delete(nfscmd_zone_key);
  88 }
  89 
  90 /*ARGSUSED*/
  91 static void *
  92 nfscmd_zone_init(zoneid_t zoneid)
  93 {
  94         nfscmd_globals_t *ncg;
  95 
  96         ncg = kmem_zalloc(sizeof (*ncg), KM_SLEEP);
  97         mutex_init(&ncg->nfscmd_lock, NULL, MUTEX_DEFAULT, NULL);
  98 
  99         return (ncg);
 100 }
 101 
 102 /*ARGSUSED*/
 103 static void
 104 nfscmd_zone_fini(zoneid_t zoneid, void *data)
 105 {
 106         nfscmd_globals_t *ncg = data;
 107 
 108         mutex_destroy(&ncg->nfscmd_lock);
 109         if (ncg->nfscmd_dh)
 110                 door_ki_rele(ncg->nfscmd_dh);
 111         kmem_free(ncg, sizeof (*ncg));
 112 }
 113 
 114 /*
 115  * nfscmd_send(arg, result)
 116  *
 117  * Send a command to the daemon listening on the door. The result is
 118  * returned in the result pointer if the function return value is
 119  * NFSCMD_ERR_SUCCESS. Otherwise it is the error value.
 120  */
 121 int
 122 nfscmd_send(nfscmd_arg_t *arg, nfscmd_res_t *res)
 123 {
 124         door_handle_t   dh;
 125         door_arg_t      da;
 126         door_info_t     di;
 127         int             ntries = 0;
 128         int             last = 0;
 129         nfscmd_globals_t *ncg = zone_getspecific(nfscmd_zone_key, curzone);
 130 
 131 retry:
 132         mutex_enter(&ncg->nfscmd_lock);
 133         dh = ncg->nfscmd_dh;
 134         if (dh != NULL)
 135                 door_ki_hold(dh);
 136         mutex_exit(&ncg->nfscmd_lock);
 137 
 138         if (dh == NULL) {
 139                 /*
 140                  * The rendezvous point has not been established yet !
 141                  * This could mean that either mountd(1m) has not yet
 142                  * been started or that _this_ routine nuked the door
 143                  * handle after receiving an EINTR for a REVOKED door.
 144                  *
 145                  * Returning NFSAUTH_DROP will cause the NFS client
 146                  * to retransmit the request, so let's try to be more
 147                  * rescillient and attempt for ntries before we bail.
 148                  */
 149                 if (++ntries % NFSCMD_DR_TRYCNT) {
 150                         delay(hz);
 151                         goto retry;
 152                 }
 153                 return (NFSCMD_ERR_DROP);
 154         }
 155 
 156         da.data_ptr = (char *)arg;
 157         da.data_size = sizeof (nfscmd_arg_t);
 158         da.desc_ptr = NULL;
 159         da.desc_num = 0;
 160         da.rbuf = (char *)res;
 161         da.rsize = sizeof (nfscmd_res_t);
 162 
 163         switch (door_ki_upcall(dh, &da)) {
 164         case 0:
 165                 /* Success */
 166                 break;
 167         case EAGAIN:
 168                 /* Need to retry a couple of times */
 169                 door_ki_rele(dh);
 170                 delay(hz);
 171                 goto retry;
 172                 /* NOTREACHED */
 173         case EINTR:
 174                 if (!door_ki_info(dh, &di)) {
 175                         if (di.di_attributes & DOOR_REVOKED) {
 176                                 /*
 177                                  * The server barfed and revoked
 178                                  * the (existing) door on us; we
 179                                  * want to wait to give smf(5) a
 180                                  * chance to restart mountd(1m)
 181                                  * and establish a new door handle.
 182                                  */
 183                                 mutex_enter(&ncg->nfscmd_lock);
 184                                 if (dh == ncg->nfscmd_dh)
 185                                         ncg->nfscmd_dh = NULL;
 186                                 mutex_exit(&ncg->nfscmd_lock);
 187                                 door_ki_rele(dh);
 188                                 delay(hz);
 189                                 goto retry;
 190                         }
 191                         /*
 192                          * If the door was _not_ revoked on us,
 193                          * then more than likely we took an INTR,
 194                          * so we need to fail the operation.
 195                          */
 196                         door_ki_rele(dh);
 197                 }
 198                 /*
 199                  * The only failure that can occur from getting
 200                  * the door info is EINVAL, so we let the code
 201                  * below handle it.
 202                  */
 203                 /* FALLTHROUGH */
 204 
 205         case EBADF:
 206         case EINVAL:
 207         default:
 208                 /*
 209                  * If we have a stale door handle, give smf a last
 210                  * chance to start it by sleeping for a little bit.
 211                  * If we're still hosed, we'll fail the call.
 212                  *
 213                  * Since we're going to reacquire the door handle
 214                  * upon the retry, we opt to sleep for a bit and
 215                  * _not_ to clear mountd_dh. If mountd restarted
 216                  * and was able to set mountd_dh, we should see
 217                  * the new instance; if not, we won't get caught
 218                  * up in the retry/DELAY loop.
 219                  */
 220                 door_ki_rele(dh);
 221                 if (!last) {
 222                         delay(hz);
 223                         last++;
 224                         goto retry;
 225                 }
 226                 res->error = NFSCMD_ERR_FAIL;
 227                 break;
 228         }
 229         return (res->error);
 230 }
 231 
 232 /*
 233  * nfscmd_findmap(export, addr)
 234  *
 235  * Find a characterset map for the specified client address.
 236  * First try to find a cached entry. If not successful,
 237  * ask mountd daemon running in userland.
 238  *
 239  * For most of the clients this function is NOOP, since
 240  * EX_CHARMAP flag won't be set.
 241  */
 242 struct charset_cache *
 243 nfscmd_findmap(struct exportinfo *exi, struct sockaddr *sp)
 244 {
 245         struct charset_cache *charset;
 246 
 247         /*
 248          * In debug kernel we want to know about strayed nulls.
 249          * In non-debug kernel we behave gracefully.
 250          */
 251         ASSERT(exi != NULL);
 252         ASSERT(sp != NULL);
 253 
 254         if (exi == NULL || sp == NULL)
 255                 return (NULL);
 256 
 257         mutex_enter(&exi->exi_lock);
 258 
 259         if (!(exi->exi_export.ex_flags & EX_CHARMAP)) {
 260                 mutex_exit(&exi->exi_lock);
 261                 return (NULL);
 262         }
 263 
 264         for (charset = exi->exi_charset;
 265             charset != NULL;
 266             charset = charset->next) {
 267                 if (bcmp(sp, &charset->client_addr,
 268                     sizeof (struct sockaddr)) == 0)
 269                         break;
 270         }
 271         mutex_exit(&exi->exi_lock);
 272 
 273         /* the slooow way - ask daemon */
 274         if (charset == NULL)
 275                 charset = nfscmd_charmap(exi, sp);
 276 
 277         return (charset);
 278 }
 279 
 280 /*
 281  * nfscmd_insert_charmap(export, addr, name)
 282  *
 283  * Insert a new character set conversion map into the export structure
 284  * for the share. The entry has the IP address of the client and the
 285  * character set name.
 286  */
 287 
 288 static struct charset_cache *
 289 nfscmd_insert_charmap(struct exportinfo *exi, struct sockaddr *sp, char *name)
 290 {
 291         struct charset_cache *charset;
 292 
 293         charset = (struct charset_cache *)
 294             kmem_zalloc(sizeof (struct charset_cache), KM_SLEEP);
 295 
 296         if (charset == NULL)
 297                 return (NULL);
 298         if (name != NULL) {
 299                 charset->inbound = kiconv_open("UTF-8", name);
 300                 charset->outbound = kiconv_open(name, "UTF-8");
 301         }
 302         charset->client_addr = *sp;
 303         mutex_enter(&exi->exi_lock);
 304         charset->next = exi->exi_charset;
 305         exi->exi_charset = charset;
 306         mutex_exit(&exi->exi_lock);
 307 
 308         return (charset);
 309 }
 310 
 311 /*
 312  * nfscmd_charmap(response, sp, exi)
 313  *
 314  * Check to see if this client needs a character set conversion.
 315  */
 316 static struct charset_cache *
 317 nfscmd_charmap(exportinfo_t *exi, struct sockaddr *sp)
 318 {
 319         nfscmd_arg_t req;
 320         int ret;
 321         char *path;
 322         nfscmd_res_t res;
 323         struct charset_cache *charset;
 324 
 325         path = exi->exi_export.ex_path;
 326         if (path == NULL)
 327                 return (NULL);
 328 
 329         /*
 330          * nfscmd_findmap() did not find one in the cache so make
 331          * the request to the daemon. We need to add the entry in
 332          * either case since we want negative as well as
 333          * positive cacheing.
 334          */
 335         req.cmd = NFSCMD_CHARMAP_LOOKUP;
 336         req.version = NFSCMD_VERSION;
 337         req.arg.charmap.addr = *sp;
 338         (void) strncpy(req.arg.charmap.path, path, MAXPATHLEN);
 339         bzero((caddr_t)&res, sizeof (nfscmd_res_t));
 340         ret = nfscmd_send(&req, &res);
 341         if (ret == NFSCMD_ERR_SUCCESS)
 342                 charset = nfscmd_insert_charmap(exi, sp,
 343                     res.result.charmap.codeset);
 344         else
 345                 charset = nfscmd_insert_charmap(exi, sp, NULL);
 346 
 347         return (charset);
 348 }
 349 
 350 /*
 351  * nfscmd_convname(addr, export, name, inbound, size)
 352  *
 353  * Convert the given "name" string to the appropriate character set.
 354  * If inbound is true, convert from the client character set to UTF-8.
 355  * If inbound is false, convert from UTF-8 to the client characters set.
 356  *
 357  * In case of NFS v4 this is used for ill behaved clients, since
 358  * according to the standard all file names should be utf-8 encoded
 359  * on client-side.
 360  */
 361 
 362 char *
 363 nfscmd_convname(struct sockaddr *ca, struct exportinfo *exi, char *name,
 364     int inbound, size_t size)
 365 {
 366         char *newname;
 367         char *holdname;
 368         int err;
 369         int ret;
 370         size_t nsize;
 371         size_t osize;
 372         struct charset_cache *charset = NULL;
 373 
 374         charset = nfscmd_findmap(exi, ca);
 375         if (charset == NULL ||
 376             (charset->inbound == NULL && inbound) ||
 377             (charset->outbound == NULL && !inbound))
 378                 return (name);
 379 
 380         /* make sure we have more than enough space */
 381         newname = kmem_zalloc(size, KM_SLEEP);
 382         nsize = strlen(name);
 383         osize = size;
 384         holdname = newname;
 385         if (inbound)
 386                 ret = kiconv(charset->inbound, &name, &nsize,
 387                     &holdname, &osize, &err);
 388         else
 389                 ret = kiconv(charset->outbound, &name, &nsize,
 390                     &holdname, &osize, &err);
 391         if (ret == (size_t)-1) {
 392                 kmem_free(newname, size);
 393                 newname = NULL;
 394         }
 395 
 396         return (newname);
 397 }
 398 
 399 /*
 400  * nfscmd_convdirent()
 401  *
 402  * There is only one entry in the data.  Convert to new charset, if
 403  * required and only return a success if it fits.
 404  */
 405 char *
 406 nfscmd_convdirent(struct sockaddr *ca, struct exportinfo *exi, char *data,
 407     size_t size, enum nfsstat3 *error)
 408 {
 409         char *newdata;
 410         size_t ret;
 411         size_t nsize;
 412         size_t count;
 413         int err = 0;
 414         char *iname;
 415         char *oname;
 416         struct charset_cache *charset;
 417 
 418         charset = nfscmd_findmap(exi, ca);
 419         if (charset == NULL || charset->outbound == (void *)~0)
 420                 return (data);
 421 
 422         newdata = kmem_zalloc(size, KM_SLEEP);
 423 
 424         nsize = strlen(((struct dirent64 *)data)->d_name);
 425         count = size;
 426         bcopy(data, newdata, sizeof (struct dirent64));
 427 
 428         iname = ((struct dirent64 *)data)->d_name;
 429         oname = ((struct dirent64 *)newdata)->d_name;
 430 
 431         ret = kiconv(charset->outbound, &iname, &nsize, &oname, &count, &err);
 432         if (ret == (size_t)-1) {
 433                 kmem_free(newdata, size);
 434                 newdata = NULL;
 435                 if (err == E2BIG) {
 436                         if (error != NULL)
 437                                 *error = NFS3ERR_NAMETOOLONG;
 438                 } else {
 439                         newdata = data;
 440                 }
 441         } else {
 442                 ret = strlen(((struct dirent64 *)newdata)->d_name);
 443                 ((struct dirent64 *)newdata)->d_reclen =
 444                     DIRENT64_RECLEN(ret + 1);
 445         }
 446         return (newdata);
 447 }
 448 
 449 /*
 450  * nfscmd_convdirplus(addr, export, data, nents, maxsize, ndata)
 451  *
 452  * Convert the dirents in data into a new list of dirents in ndata.
 453  */
 454 
 455 size_t
 456 nfscmd_convdirplus(struct sockaddr *ca, struct exportinfo *exi, char *data,
 457     size_t nents, size_t maxsize, char **ndata)
 458 {
 459         char *newdata;
 460         size_t nsize;
 461         struct dirent64 *dp;
 462         struct dirent64 *ndp;
 463         size_t i;
 464         size_t ret;
 465         char *iname;
 466         char *oname;
 467         size_t ilen;
 468         size_t olen;
 469         int err;
 470         size_t skipped;
 471         struct charset_cache *charset;
 472         *ndata = data;  /* return the data if no changes to make */
 473 
 474         charset = nfscmd_findmap(exi, ca);
 475 
 476         if (charset == NULL || charset->outbound == (void *)~0)
 477                 return (0);
 478 
 479         newdata = kmem_zalloc(maxsize, KM_SLEEP);
 480         nsize = 0;
 481 
 482         dp = (struct dirent64 *)data;
 483         ndp = (struct dirent64 *)newdata;
 484 
 485         for (skipped = 0, i = 0; i < nents; i++) {
 486                 /*
 487                  * Copy the dp information if it fits. Then copy and
 488                  * convert the name in the entry.
 489                  */
 490                 if ((maxsize - nsize) < dp->d_reclen)
 491                         /* doesn't fit */
 492                         break;
 493                 *ndp = *dp;
 494                 iname = dp->d_name;
 495                 ilen = strlen(iname);
 496                 oname = ndp->d_name;
 497                 olen = MIN(MAXNAMELEN, maxsize - nsize);
 498                 ret = kiconv(charset->outbound, &iname, &ilen, &oname,
 499                     &olen, &err);
 500 
 501                 if (ret == (size_t)-1) {
 502                         switch (err) {
 503                         default:
 504                         case E2BIG:
 505                                 break;
 506                         case EILSEQ:
 507                                 skipped++;
 508                                 dp = nextdp(dp);
 509                                 continue;
 510                         }
 511                 }
 512                 ilen = MIN(MAXNAMELEN, maxsize - nsize) - olen;
 513                 ndp->d_name[ilen] = '\0';
 514                 /*
 515                  * What to do with other errors?
 516                  * For now, we return the unconverted string.
 517                  */
 518                 ndp->d_reclen = DIRENT64_RECLEN(strlen(ndp->d_name) + 1);
 519                 nsize += ndp->d_reclen;
 520                 dp = nextdp(dp);
 521                 ndp = nextdp(ndp);
 522         }
 523 
 524         *ndata = newdata;
 525         return (nents - (i + skipped));
 526 }
 527 
 528 /*
 529  * nfscmd_countents(data, len)
 530  *
 531  * How many dirents are there in the data buffer?
 532  */
 533 
 534 size_t
 535 nfscmd_countents(char *data, size_t len)
 536 {
 537         struct dirent64 *dp = (struct dirent64 *)data;
 538         size_t curlen;
 539         size_t reclen;
 540         size_t nents;
 541 
 542         for (nents = 0, curlen = 0; curlen < len; curlen += reclen, nents++) {
 543                 reclen = dp->d_reclen;
 544                 dp = nextdp(dp);
 545         }
 546         return (nents);
 547 }
 548 
 549 /*
 550  * nfscmd_dropped_entrysize(dir, drop, nents)
 551  *
 552  * We need to drop "drop" entries from dir in order to fit in the
 553  * buffer.  How much do we reduce the overall size by?
 554  */
 555 
 556 size_t
 557 nfscmd_dropped_entrysize(struct dirent64 *dir, size_t drop, size_t nents)
 558 {
 559         size_t size;
 560         size_t i;
 561 
 562         for (i = nents - drop; i > 0 && dir != NULL; i--)
 563                 dir = nextdp(dir);
 564 
 565         if (dir == NULL)
 566                 return (0);
 567 
 568         for (size = 0, i = 0; i < drop && dir != NULL; i++) {
 569                 size += dir->d_reclen;
 570                 dir = nextdp(dir);
 571         }
 572         return (size);
 573 }