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 }