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 2008 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 #include <netdb.h>
  28 #include <netconfig.h>
  29 #include <netdir.h>
  30 #include <rpc/rpc.h>
  31 #include <rpc/clnt.h>
  32 #include <rpc/clnt_soc.h>
  33 #include <sys/socket.h>
  34 #include <unistd.h>
  35 #include "nfs4_prot.h"
  36 #include "nfstcl4.h"
  37 
  38 /* maximal size of argv[] for nfs_connection */
  39 #define MAX_RECONNECTION_ARGS   7
  40 
  41 CLIENT *client;         /* Global client handle */
  42 
  43 /* tcl procedure table */
  44 NFSPROC nfs_proc[] = {
  45         {"connect",     nfs_connect             },
  46         {"disconnect",  nfs_disconnect          },
  47         {"nullproc",    nfs_nullproc            },
  48         {"compound",    nfs_compound            },
  49         {0,             0       },
  50 };
  51 
  52 static int reconnection(ClientData clientData,
  53     Tcl_Interp *interp, int argc, char *argv[]);
  54 
  55 /*
  56  * Avoid calling clnt_create() here because this
  57  * client needs to connect to the server on a well-known
  58  * port - 2049.  This used to be easy with the original
  59  * RPC API, but since TLI and TI-RPC, it's become extraordinarly
  60  * difficult - as the following code shows.
  61  */
  62 int
  63 nfs_connect(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
  64 {
  65         struct timeval tv;
  66         char *host;
  67         struct t_bind *tbind = NULL;
  68         int fd;
  69         struct t_info tinfo;
  70         struct nd_hostserv hs;
  71         struct nd_addrlist *retaddrs;
  72         struct netconfig *nconf;
  73         int c;
  74         ushort_t port = NFS_PORT;
  75         char *transport = "tcp";
  76         int err = 0;
  77         char *secflav = "sys";
  78         char mech_krb[] = "kerberos_v5";
  79         char service_name[128];
  80         struct hostent *he;
  81         rpc_gss_service_t service = rpc_gss_svc_none;
  82         rpc_gss_options_ret_t o;
  83 
  84 #ifdef DEBUG_PROC
  85         {
  86                 int     i;
  87 
  88                 (void) fprintf(stderr,
  89                     "debug nfs_connect:\n"
  90                     "    &clientData == %p\n",
  91                     "    interp = %p\n"
  92                     "    argc == %d\n",
  93                     &clientData, interp, argc);
  94                 for (i = 0; i < argc; i++) {
  95                         (void) fprintf(stderr, "    argv[%d] == %s\n",
  96                             i, argv[i]);
  97                 }
  98                 (void) fprintf(stderr,
  99                     "debug nfs_connect: starting with client == %p\n",
 100                     client);
 101         }
 102 #endif  /* DEBUG_PROC */
 103         /* reset option index to one */
 104         optind = 1;
 105         while ((c = getopt(argc, argv, "p:t:s:")) != EOF) {
 106                 switch (c) {
 107                 case 'p':
 108                         port = atoi(optarg);
 109                         break;
 110                 case 't':
 111                         transport = optarg;
 112                         break;
 113                 case 's':
 114                         secflav = optarg;
 115                         break;
 116                 default:
 117                         err++;
 118                         break;
 119                 }
 120         }
 121 
 122         if (err || (argc - optind != 1)) {
 123                 interp->result =
 124                     "Usage: connect [-p port] "
 125                     "[-t tcp|udp] [-s sys|krb5|krb5i|krb5p] <hostname>";
 126                 return (TCL_ERROR);
 127         }
 128 
 129         if (client != NULL) {
 130                 clnt_destroy(client);
 131                 client = NULL;
 132         }
 133 
 134         host = argv[optind];
 135 
 136         /* save host */
 137         if (reconnection(clientData, interp, argc, argv) != 0)
 138                 return (TCL_ERROR);
 139 
 140         nconf = getnetconfigent(transport);
 141         if (nconf == NULL)
 142                 goto done;
 143 
 144         if ((fd = t_open(nconf->nc_device, O_RDWR, &tinfo)) < 0)
 145                 goto done;
 146 
 147         /* LINTED pointer alignment */
 148         if ((tbind = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR))
 149             == NULL)
 150                 goto done;
 151 
 152         hs.h_host = host;
 153         hs.h_serv = NULL;
 154 
 155         if (netdir_getbyname(nconf, &hs, &retaddrs) != ND_OK) {
 156                 goto done;
 157         }
 158         (void) memcpy(tbind->addr.buf, retaddrs->n_addrs->buf,
 159             retaddrs->n_addrs->len);
 160         tbind->addr.len = retaddrs->n_addrs->len;
 161         netdir_free((void *)retaddrs, ND_ADDRLIST);
 162 
 163         if (strcmp(nconf->nc_protofmly, NC_INET) == NULL)
 164                 /* LINTED pointer cast may result in improper alignment */
 165                 ((struct sockaddr_in *)
 166                     tbind->addr.buf)->sin_port =
 167                     htons(port);
 168 #ifdef INET6
 169         else if (strcmp(nconf->nc_protofmly, NC_INET6) == NULL)
 170                 ((struct sockaddr_in6 *)
 171                     tbind->addr.buf)->sin6_port =
 172                     htons((ushort_t)NFS_PORT);
 173 #endif /* INET6 */
 174 
 175         client = clnt_tli_create(fd, nconf, &tbind->addr, NFS4_PROGRAM,
 176             4, 0, 0);
 177 
 178         if (client == NULL) {
 179                 if (reconnection(clientData, interp, 0, NULL) == 0)
 180                         return (TCL_OK);
 181                 interp->result =
 182                     "connect failed - can't create client handle";
 183                 clnt_pcreateerror("clnt_tli_create");
 184 #ifdef DEBUG_PROC
 185                 (void) fprintf(stderr,
 186                     "debug nfs_connect %s: client == %p, TCL_ERROR\n",
 187                     interp->result, client);
 188 #endif  /* DEBUG_PROC */
 189                 return (TCL_ERROR);
 190         }
 191 
 192         tv.tv_sec = 0;
 193         tv.tv_usec = 700000;
 194         clnt_control(client, CLSET_RETRY_TIMEOUT, (char *)&tv);
 195 
 196         tv.tv_usec = 0;
 197         clnt_control(client, CLGET_RETRY_TIMEOUT, (char *)&tv);
 198 
 199         if (strcmp(secflav, "sys") == 0)
 200                 client->cl_auth = authunix_create_default();
 201 #ifdef _RPCGSS
 202         else {
 203                 if (strcmp(secflav, "krb5") == 0)
 204                         service = rpc_gss_svc_none;
 205                 if (strcmp(secflav, "krb5i") == 0)
 206                         service = rpc_gss_svc_integrity;
 207                 if (strcmp(secflav, "krb5p") == 0)
 208                         service = rpc_gss_svc_privacy;
 209 
 210                 strcpy(service_name, "nfs@");
 211                 he = gethostbyname(host);
 212                 strcat(service_name, he->h_name);
 213 
 214                 client->cl_auth = rpc_gss_seccreate(client,
 215                     service_name, mech_krb, service, NULL, NULL, &o);
 216         }
 217 #endif
 218         if (client->cl_auth == NULL) {
 219                 interp->result =
 220                     "connect failed - can't create cl_auth.";
 221                 clnt_pcreateerror("auth creation");
 222                 clnt_destroy(client);
 223                 client = NULL;
 224 #ifdef DEBUG_PROC
 225                 (void) fprintf(stderr,
 226                     "debug nfs_connect: client == %p, TCL_ERROR\n", client);
 227 #endif  /* DEBUG_PROC */
 228                 return (TCL_ERROR);
 229         }
 230 
 231 #ifdef DEBUG_PROC
 232         (void) fprintf(stderr, "debug nfs_connect: client == %p, TCL_OK\n",
 233             client);
 234 #endif  /* DEBUG_PROC */
 235         return (TCL_OK);
 236 
 237 done:
 238         if (tbind)
 239                 t_free((char *)tbind, T_BIND);
 240 
 241         if (fd >= 0)
 242                 (void) t_close(fd);
 243 
 244         interp->result = "connect failed - unable to netconfig.";
 245 #ifdef DEBUG_PROC
 246         (void) fprintf(stderr,
 247             "debug nfs_connect %s: client == %p, TCL_ERROR\n",
 248             interp->result, client);
 249 #endif  /* DEBUG_PROC */
 250         return (TCL_ERROR);
 251 }
 252 
 253 /*
 254  * Break the connection to the server and
 255  * destroy the client handle.
 256  */
 257 /* ARGSUSED0 */
 258 int
 259 nfs_disconnect(ClientData clientData, Tcl_Interp *interp,
 260     int argc, char *argv[])
 261 {
 262 #ifdef DEBUG_PROC
 263         (void) fprintf(stderr, "debug nfs_disconnect: client == %p\n", client);
 264 #endif  /* DEBUG_PROC */
 265         if (client == NULL) {
 266                 interp->result = "No connection to server";
 267                 return (TCL_ERROR);
 268         }
 269 
 270         auth_destroy(client->cl_auth);
 271         clnt_destroy(client);
 272         client = NULL;
 273 
 274 #ifdef DEBUG_PROC
 275         (void) fprintf(stderr,
 276             "debug nfs_disconnect: client == %p, TCL_OK\n", client);
 277 #endif  /* DEBUG_PROC */
 278         return (TCL_OK);
 279 }
 280 
 281 /*
 282  * Call the null procedure of the server
 283  */
 284 /* ARGSUSED0 */
 285 int
 286 nfs_nullproc(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
 287 {
 288         if (client == NULL) {
 289                 interp->result = "No connection to server";
 290                 return (TCL_ERROR);
 291         }
 292 
 293         nfsproc4_null_4(NULL, client);
 294 
 295         return (TCL_OK);
 296 }
 297 
 298 
 299 COMPOUND4args compound_args;
 300 COMPOUND4res  compound_res;
 301 int opmax, opcount;
 302 nfs_argop4 *opvals;
 303 
 304 /*
 305  * This function provides a new argop from a sequence
 306  * of compound ops that is ready to be filled-in by
 307  * an op* routine.  If the compound op array in opvals
 308  * is too small, the routine will double its size.
 309  */
 310 nfs_argop4 *
 311 new_argop()
 312 {
 313         if (opvals == NULL) {
 314                 opmax = 8;
 315                 opcount = 0;
 316                 opvals = malloc(opmax * sizeof (nfs_argop4));
 317         }
 318 
 319         if (opcount >= opmax) {
 320                 opmax *= 2;
 321                 opvals = realloc(opvals, opmax * sizeof (nfs_argop4));
 322         }
 323 
 324         if (opvals == NULL) {
 325                 (void) printf("new_argop: malloc failed");
 326                 exit(1);
 327         }
 328 
 329         return (&opvals[opcount++]);
 330 }
 331 
 332 /*
 333  * Generate a compound request.
 334  */
 335 int
 336 nfs_compound(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
 337 {
 338         int err;
 339         COMPOUND4res *resp;
 340         const char *tag;
 341         struct rpc_err rpc_err;
 342         int retry_cnt;
 343         int mvers;              /* minor version */
 344 
 345         if (argc != 2) {
 346                 interp->result = "Usage: compound { ops ... }";
 347                 return (TCL_ERROR);
 348         }
 349 
 350         if (client == NULL) {
 351                 interp->result = "No connection to server";
 352                 return (TCL_ERROR);
 353         }
 354 
 355         /*
 356          * Allow the script writer to set the "tag"
 357          * string and minor version on the compound call.
 358          */
 359         tag = Tcl_GetVar(interp, "minorversion", 0);
 360         if (tag == NULL)                /* use "tag" temporary */
 361                 mvers = 0;
 362         else
 363                 mvers = (int)atoi(tag);
 364 
 365         tag = Tcl_GetVar(interp, "tag", 0);
 366         if (tag == NULL)
 367                 tag = "";
 368 
 369         compound_args.tag = *str2utf8(tag);
 370         compound_args.minorversion = mvers;
 371         opcount = 0;
 372         opvals = NULL;
 373 #ifdef DEBUG_PROC
 374                 (void) fprintf(stderr,
 375                 "debug nfs_compound: compound_args.tag=%s, minorversion=%d\n",
 376                     tag, mvers);
 377 #endif  /* DEBUG_PROC */
 378 
 379         /*
 380          * The body of the compound call (the ops in
 381          * the curly braces) is handled by this Eval.
 382          */
 383         err = Tcl_Eval(interp, argv[1]);
 384         if (err != TCL_OK) {
 385 #ifdef DEBUG_PROC
 386                 (void) fprintf(stderr,
 387                     "debug nfs_compound: Tcl_Eval(interp, argv[1]) == %s\n",
 388                     interp->result);
 389 #endif  /* DEBUG_PROC */
 390                 return (err);
 391         }
 392 
 393         Tcl_UnsetVar(interp, "status", 0);
 394 
 395         /*
 396          * Now have a completely encoded compound op.
 397          * Just plug it into the RPC args and call
 398          * the server ...
 399          */
 400         compound_args.argarray.argarray_len = opcount;
 401         compound_args.argarray.argarray_val = opvals;
 402 
 403         for (retry_cnt = 0; retry_cnt <= 1; retry_cnt++) {
 404 
 405 #ifdef DEBUG_PROC
 406                 (void) fprintf(stderr,
 407                     "debug nfs_compound: for (retry_cnt = %d...\n",
 408                     retry_cnt);
 409                 (void) fprintf(stderr, "\tclient == %p\n", client);
 410                 if (client != (CLIENT *)NULL) {
 411                         (void) fprintf(stderr,
 412                             "\t\tcl_auth == %p\n"
 413                             "\t\tcl_private == %p\n"
 414                             "\t\tcl_netid == %p\n"
 415                             "\t\tcl_tp == %p\n",
 416                             client->cl_auth, client->cl_private,
 417                             client->cl_netid, client->cl_tp);
 418                         if (client->cl_netid) {
 419                                 (void) fprintf(stderr,
 420                                     "\t\tclient->cl_netid == %s\n",
 421                                     client->cl_netid);
 422                         }
 423                         if (client->cl_tp) {
 424                                 (void) fprintf(stderr,
 425                                     "\t\tclient->cl_tp == %s\n",
 426                                     client->cl_tp);
 427                         }
 428                 }
 429 #endif  /* DEBUG_PROC */
 430 
 431                 if (client == (CLIENT *)NULL)
 432                         break;
 433                 resp = nfsproc4_compound_4(&compound_args, client);
 434 #ifdef DEBUG_PROC
 435                 (void) fprintf(stderr,
 436                     "debug nfs_compound: nfsproc4_compound_4() == %p\n",
 437                     resp);
 438 #endif  /* DEBUG_PROC */
 439                 if (resp == NULL) {
 440                         clnt_geterr(client, &rpc_err);
 441 #ifdef DEBUG_PROC
 442                         (void) fprintf(stderr,
 443                             "debug nfs_compound:\n"
 444                             "\tclnt_sperrno(rpc_err.re_status) == %s\n",
 445                             clnt_sperrno(rpc_err.re_status));
 446 #endif  /* DEBUG_PROC */
 447                         if (rpc_err.re_status == RPC_CANTRECV ||
 448                             rpc_err.re_status == RPC_TIMEDOUT) {
 449                                 /* reconnect and retry */
 450                                 if (reconnection(clientData, interp, 0, NULL) !=
 451                                     -1)
 452                                         continue; /* retry compound */
 453                         }
 454 #ifdef DEBUG_PROC
 455                         (void) fprintf(stderr,
 456                             "debug nfs_compound: RPC error\n");
 457 #endif  /* DEBUG_PROC */
 458                         (void) snprintf(interp->result, sizeof (interp->result),
 459                             "RPC error: %s\n", clnt_sperrno(rpc_err.re_status));
 460                         return (TCL_ERROR);
 461                 }
 462                 break;
 463         }
 464         if (resp == NULL) {
 465                 (void) snprintf(interp->result, sizeof (interp->result),
 466                     "RPC error: %s\n", clnt_sperrno(rpc_err.re_status));
 467                 return (TCL_ERROR);
 468         }
 469 
 470         /*
 471          * Evaluate the compound result here.
 472          */
 473         return (compound_result(interp, resp));
 474 }
 475 
 476 /*
 477  * This is called from the generic main() function
 478  * to register the new Tcl commands.
 479  */
 480 void
 481 nfs_initialize(Tcl_Interp *interp)
 482 {
 483         int i;
 484 
 485         client = NULL;
 486 
 487         for (i = 0; nfs_proc[i].name != NULL; i++) {
 488                 Tcl_CreateCommand(interp,
 489                     nfs_proc[i].name, nfs_proc[i].func,
 490                     (ClientData) NULL,
 491                     (Tcl_CmdDeleteProc *) NULL);
 492         }
 493 
 494         /*
 495          * Register the compound ops.
 496          */
 497         op_createcom(interp);
 498 }
 499 
 500 /*
 501  * Reconnection for the case server failed.
 502  */
 503 static int
 504 reconnection(ClientData clientData, Tcl_Interp *interp,
 505     int argc, char *argv[])
 506 {
 507         static int      connect_argc;
 508         static char     *connect_argv[MAX_RECONNECTION_ARGS];
 509         static size_t   connect_argv_size[MAX_RECONNECTION_ARGS];
 510         static int      args_saved = 0;
 511         static int      recursion = 0;
 512         size_t          arg_size;
 513         int             ind;
 514         int             sleep_cnt;
 515         int             reconnect_cnt;
 516         int             reconnected = 0;
 517 
 518         if (argc != 0) {
 519                 /* save arguments to connection */
 520                 if (connect_argc == 0) {
 521                         /* get argument vector */
 522                         arg_size = 64;
 523                         for (ind = 0; ind < MAX_RECONNECTION_ARGS;
 524                             ind++) {
 525                                 connect_argv[ind] =
 526                                     (char *)malloc(arg_size);
 527                                 if (connect_argv[ind] == NULL) {
 528                                         perror("malloc(arg_size)");
 529                                         return (-1);
 530                                 }
 531                                 connect_argv_size[ind] = arg_size;
 532                         }
 533                 }
 534                 /* save arguments counter */
 535                 connect_argc = argc;
 536                 /* save arguments vector */
 537                 if (argc > MAX_RECONNECTION_ARGS) {
 538                         (void) fprintf(stderr,
 539                             "Test ERROR: MAX_RECONNECTION_ARGS "
 540                             "should be %d!!!\n",
 541                             argc);
 542                         return (-1);
 543                 }
 544                 for (ind = 0; ind < argc; ind++) {
 545                         arg_size = strlen(argv[ind]) + 1;
 546                         if (strlcpy(connect_argv[ind], argv[ind],
 547                             connect_argv_size[ind]) >=
 548                             connect_argv_size[ind]) {
 549                                 connect_argv[ind] = realloc(connect_argv[ind],
 550                                     arg_size);
 551                                 if (connect_argv[ind] == NULL) {
 552                                         perror("realloc(connect_argv[ind], "
 553                                             "arg_size)");
 554                                         return (-1);
 555                                 }
 556                                 connect_argv_size[ind] = arg_size;
 557                                 (void) strlcpy(connect_argv[ind], argv[ind],
 558                                     connect_argv_size[ind]);
 559                         }
 560                 }
 561                 args_saved = 1;
 562                 return (0);
 563         } else {
 564                 /* reconnect */
 565                 if (!args_saved) {
 566                         (void) fprintf(stderr,
 567                             "ERROR:\nfile %s, line %d:\n"
 568                             "connect_argv not initialized!!!\n",
 569                             __FILE__, __LINE__);
 570                         recursion = 0;
 571                         return (-1);
 572                 }
 573                 if (recursion == 0) {
 574                         recursion = 20; /* 20 times of 30 sec == 10 min */
 575                 } else {
 576                         return (-1);
 577                 }
 578                 for (reconnect_cnt = 0; reconnect_cnt < recursion;
 579                     reconnect_cnt++) {
 580 #ifdef DEBUG_PROC
 581                         (void) fprintf(stderr,
 582                             "\tdebug reconnection: reconnect_cnt == %d: "
 583                             "30 sec delay to restart server\n",
 584                             reconnect_cnt);
 585 #endif  /* DEBUG_PROC */
 586                         sleep_cnt = 30; /* retry each 30 sec */
 587                         while (sleep_cnt > 0) {
 588                                 sleep_cnt = sleep(sleep_cnt);
 589                         }
 590                         (void) nfs_disconnect(clientData, interp, 0, NULL);
 591                         if (nfs_connect(clientData, interp,
 592                             connect_argc, connect_argv) ==
 593                             TCL_OK) {
 594                                 reconnected = 1;
 595 #ifdef DEBUG_PROC
 596                                 (void) fprintf(stderr,
 597                                     "debug reconnection: reconnected.\n");
 598 #endif  /* DEBUG_PROC */
 599                                 break;
 600 #ifdef DEBUG_PROC
 601                         } else {
 602                                 (void) fprintf(stderr,
 603                                     "debug reconnection: "
 604                                     "reconnection try failed.\n");
 605 #endif  /* DEBUG_PROC */
 606                         }
 607                 }       /* reconnection loop */
 608                 recursion = 0;
 609                 return (reconnected ? 0 : -1);
 610         }
 611 }