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