1 | /* |
2 | * Copyright (c) 2010, Oracle America, Inc. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions are |
6 | * met: |
7 | * |
8 | * * Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * * Redistributions in binary form must reproduce the above |
11 | * copyright notice, this list of conditions and the following |
12 | * disclaimer in the documentation and/or other materials |
13 | * provided with the distribution. |
14 | * * Neither the name of the "Oracle America, Inc." nor the names of its |
15 | * contributors may be used to endorse or promote products derived |
16 | * from this software without specific prior written permission. |
17 | * |
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
21 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
22 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
23 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
25 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
27 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | /* |
32 | * The original source is from the RPCSRC 4.0 package from Sun Microsystems. |
33 | * The Interface to keyserver protocoll 2, RPC over AF_UNIX and Linux/doors |
34 | * was added by Thorsten Kukuk <kukuk@suse.de> |
35 | * Since the Linux/doors project was stopped, I doubt that this code will |
36 | * ever be useful <kukuk@suse.de>. |
37 | */ |
38 | |
39 | #include <stdio.h> |
40 | #include <errno.h> |
41 | #include <fcntl.h> |
42 | #include <signal.h> |
43 | #include <unistd.h> |
44 | #include <string.h> |
45 | #include <rpc/rpc.h> |
46 | #include <rpc/auth.h> |
47 | #include <sys/wait.h> |
48 | #include <sys/param.h> |
49 | #include <sys/socket.h> |
50 | #include <rpc/key_prot.h> |
51 | #include <libc-lock.h> |
52 | #include <shlib-compat.h> |
53 | |
54 | #define KEY_TIMEOUT 5 /* per-try timeout in seconds */ |
55 | #define KEY_NRETRY 12 /* number of retries */ |
56 | |
57 | #define debug(msg) /* turn off debugging */ |
58 | |
59 | #ifndef SO_PASSCRED |
60 | extern int _openchild (const char *command, FILE **fto, FILE **ffrom); |
61 | #endif |
62 | |
63 | static int key_call (u_long, xdrproc_t xdr_arg, char *, |
64 | xdrproc_t xdr_rslt, char *); |
65 | |
66 | static const struct timeval trytimeout = {KEY_TIMEOUT, 0}; |
67 | static const struct timeval tottimeout = {KEY_TIMEOUT *KEY_NRETRY, 0}; |
68 | |
69 | int |
70 | key_setsecret (char *secretkey) |
71 | { |
72 | keystatus status; |
73 | |
74 | if (!key_call ((u_long) KEY_SET, (xdrproc_t) xdr_keybuf, secretkey, |
75 | (xdrproc_t) xdr_keystatus, (char *) &status)) |
76 | return -1; |
77 | if (status != KEY_SUCCESS) |
78 | { |
79 | debug ("set status is nonzero" ); |
80 | return -1; |
81 | } |
82 | return 0; |
83 | } |
84 | libc_hidden_nolink_sunrpc (key_setsecret, GLIBC_2_1) |
85 | |
86 | /* key_secretkey_is_set() returns 1 if the keyserver has a secret key |
87 | * stored for the caller's effective uid; it returns 0 otherwise |
88 | * |
89 | * N.B.: The KEY_NET_GET key call is undocumented. Applications shouldn't |
90 | * be using it, because it allows them to get the user's secret key. |
91 | */ |
92 | int |
93 | key_secretkey_is_set (void) |
94 | { |
95 | struct key_netstres kres; |
96 | |
97 | memset (&kres, 0, sizeof (kres)); |
98 | if (key_call ((u_long) KEY_NET_GET, (xdrproc_t) xdr_void, |
99 | (char *) NULL, (xdrproc_t) xdr_key_netstres, |
100 | (char *) &kres) && |
101 | (kres.status == KEY_SUCCESS) && |
102 | (kres.key_netstres_u.knet.st_priv_key[0] != 0)) |
103 | { |
104 | /* avoid leaving secret key in memory */ |
105 | memset (kres.key_netstres_u.knet.st_priv_key, 0, HEXKEYBYTES); |
106 | return 1; |
107 | } |
108 | return 0; |
109 | } |
110 | #ifdef EXPORT_RPC_SYMBOLS |
111 | libc_hidden_def (key_secretkey_is_set) |
112 | #else |
113 | libc_hidden_nolink_sunrpc (key_secretkey_is_set, GLIBC_2_1) |
114 | #endif |
115 | |
116 | int |
117 | key_encryptsession (char *remotename, des_block *deskey) |
118 | { |
119 | cryptkeyarg arg; |
120 | cryptkeyres res; |
121 | |
122 | arg.remotename = remotename; |
123 | arg.deskey = *deskey; |
124 | if (!key_call ((u_long) KEY_ENCRYPT, (xdrproc_t) xdr_cryptkeyarg, |
125 | (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
126 | (char *) &res)) |
127 | return -1; |
128 | |
129 | if (res.status != KEY_SUCCESS) |
130 | { |
131 | debug ("encrypt status is nonzero" ); |
132 | return -1; |
133 | } |
134 | *deskey = res.cryptkeyres_u.deskey; |
135 | return 0; |
136 | } |
137 | libc_hidden_nolink_sunrpc (key_encryptsession, GLIBC_2_1) |
138 | |
139 | int |
140 | key_decryptsession (char *remotename, des_block *deskey) |
141 | { |
142 | cryptkeyarg arg; |
143 | cryptkeyres res; |
144 | |
145 | arg.remotename = remotename; |
146 | arg.deskey = *deskey; |
147 | if (!key_call ((u_long) KEY_DECRYPT, (xdrproc_t) xdr_cryptkeyarg, |
148 | (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
149 | (char *) &res)) |
150 | return -1; |
151 | if (res.status != KEY_SUCCESS) |
152 | { |
153 | debug ("decrypt status is nonzero" ); |
154 | return -1; |
155 | } |
156 | *deskey = res.cryptkeyres_u.deskey; |
157 | return 0; |
158 | } |
159 | libc_hidden_nolink_sunrpc (key_decryptsession, GLIBC_2_1) |
160 | |
161 | int |
162 | key_encryptsession_pk (char *remotename, netobj *remotekey, |
163 | des_block *deskey) |
164 | { |
165 | cryptkeyarg2 arg; |
166 | cryptkeyres res; |
167 | |
168 | arg.remotename = remotename; |
169 | arg.remotekey = *remotekey; |
170 | arg.deskey = *deskey; |
171 | if (!key_call ((u_long) KEY_ENCRYPT_PK, (xdrproc_t) xdr_cryptkeyarg2, |
172 | (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
173 | (char *) &res)) |
174 | return -1; |
175 | |
176 | if (res.status != KEY_SUCCESS) |
177 | { |
178 | debug ("encrypt status is nonzero" ); |
179 | return -1; |
180 | } |
181 | *deskey = res.cryptkeyres_u.deskey; |
182 | return 0; |
183 | } |
184 | libc_hidden_nolink_sunrpc (key_encryptsession_pk, GLIBC_2_1) |
185 | |
186 | int |
187 | key_decryptsession_pk (char *remotename, netobj *remotekey, |
188 | des_block *deskey) |
189 | { |
190 | cryptkeyarg2 arg; |
191 | cryptkeyres res; |
192 | |
193 | arg.remotename = remotename; |
194 | arg.remotekey = *remotekey; |
195 | arg.deskey = *deskey; |
196 | if (!key_call ((u_long) KEY_DECRYPT_PK, (xdrproc_t) xdr_cryptkeyarg2, |
197 | (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
198 | (char *) &res)) |
199 | return -1; |
200 | |
201 | if (res.status != KEY_SUCCESS) |
202 | { |
203 | debug ("decrypt status is nonzero" ); |
204 | return -1; |
205 | } |
206 | *deskey = res.cryptkeyres_u.deskey; |
207 | return 0; |
208 | } |
209 | libc_hidden_nolink_sunrpc (key_decryptsession_pk, GLIBC_2_1) |
210 | |
211 | int |
212 | key_gendes (des_block *key) |
213 | { |
214 | struct sockaddr_in sin; |
215 | CLIENT *client; |
216 | int socket; |
217 | enum clnt_stat stat; |
218 | |
219 | sin.sin_family = AF_INET; |
220 | sin.sin_port = 0; |
221 | sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK); |
222 | memset (sin.sin_zero, 0, sizeof (sin.sin_zero)); |
223 | socket = RPC_ANYSOCK; |
224 | client = clntudp_bufcreate (&sin, (u_long) KEY_PROG, (u_long) KEY_VERS, |
225 | trytimeout, &socket, RPCSMALLMSGSIZE, |
226 | RPCSMALLMSGSIZE); |
227 | if (client == NULL) |
228 | return -1; |
229 | |
230 | stat = clnt_call (client, KEY_GEN, (xdrproc_t) xdr_void, NULL, |
231 | (xdrproc_t) xdr_des_block, (caddr_t) key, |
232 | tottimeout); |
233 | clnt_destroy (client); |
234 | __close (socket); |
235 | if (stat != RPC_SUCCESS) |
236 | return -1; |
237 | |
238 | return 0; |
239 | } |
240 | #ifdef EXPORT_RPC_SYMBOLS |
241 | libc_hidden_def (key_gendes) |
242 | #else |
243 | libc_hidden_nolink_sunrpc (key_gendes, GLIBC_2_1) |
244 | #endif |
245 | |
246 | int |
247 | key_setnet (struct key_netstarg *arg) |
248 | { |
249 | keystatus status; |
250 | |
251 | if (!key_call ((u_long) KEY_NET_PUT, (xdrproc_t) xdr_key_netstarg, |
252 | (char *) arg,(xdrproc_t) xdr_keystatus, |
253 | (char *) &status)) |
254 | return -1; |
255 | |
256 | if (status != KEY_SUCCESS) |
257 | { |
258 | debug ("key_setnet status is nonzero" ); |
259 | return -1; |
260 | } |
261 | return 1; |
262 | } |
263 | libc_hidden_nolink_sunrpc (key_setnet, GLIBC_2_1) |
264 | |
265 | int |
266 | key_get_conv (char *pkey, des_block *deskey) |
267 | { |
268 | cryptkeyres res; |
269 | |
270 | if (!key_call ((u_long) KEY_GET_CONV, (xdrproc_t) xdr_keybuf, pkey, |
271 | (xdrproc_t) xdr_cryptkeyres, (char *) &res)) |
272 | return -1; |
273 | |
274 | if (res.status != KEY_SUCCESS) |
275 | { |
276 | debug ("get_conv status is nonzero" ); |
277 | return -1; |
278 | } |
279 | *deskey = res.cryptkeyres_u.deskey; |
280 | return 0; |
281 | } |
282 | libc_hidden_nolink_sunrpc (key_get_conv, GLIBC_2_1) |
283 | |
284 | /* |
285 | * Hack to allow the keyserver to use AUTH_DES (for authenticated |
286 | * NIS+ calls, for example). The only functions that get called |
287 | * are key_encryptsession_pk, key_decryptsession_pk, and key_gendes. |
288 | * |
289 | * The approach is to have the keyserver fill in pointers to local |
290 | * implementations of these functions, and to call those in key_call(). |
291 | */ |
292 | |
293 | cryptkeyres *(*__key_encryptsession_pk_LOCAL) (uid_t, char *) |
294 | __attribute__ ((nocommon)); |
295 | cryptkeyres *(*__key_decryptsession_pk_LOCAL) (uid_t, char *) |
296 | __attribute__ ((nocommon)); |
297 | des_block *(*__key_gendes_LOCAL) (uid_t, char *) __attribute__ ((nocommon)); |
298 | #ifdef SHARED |
299 | # ifndef EXPORT_RPC_SYMBOLS |
300 | compat_symbol (libc, __key_encryptsession_pk_LOCAL, |
301 | __key_encryptsession_pk_LOCAL, GLIBC_2_1); |
302 | compat_symbol (libc, __key_decryptsession_pk_LOCAL, |
303 | __key_decryptsession_pk_LOCAL, GLIBC_2_1); |
304 | compat_symbol (libc, __key_gendes_LOCAL, __key_gendes_LOCAL, GLIBC_2_1); |
305 | # endif |
306 | #endif |
307 | |
308 | #ifndef SO_PASSCRED |
309 | static int |
310 | key_call_keyenvoy (u_long proc, xdrproc_t xdr_arg, char *arg, |
311 | xdrproc_t xdr_rslt, char *rslt) |
312 | { |
313 | XDR xdrargs; |
314 | XDR xdrrslt; |
315 | FILE *fargs; |
316 | FILE *frslt; |
317 | sigset_t oldmask, mask; |
318 | int status; |
319 | int pid; |
320 | int success; |
321 | uid_t ruid; |
322 | uid_t euid; |
323 | static const char MESSENGER[] = "/usr/etc/keyenvoy" ; |
324 | |
325 | success = 1; |
326 | sigemptyset (&mask); |
327 | sigaddset (&mask, SIGCHLD); |
328 | __sigprocmask (SIG_BLOCK, &mask, &oldmask); |
329 | |
330 | /* |
331 | * We are going to exec a set-uid program which makes our effective uid |
332 | * zero, and authenticates us with our real uid. We need to make the |
333 | * effective uid be the real uid for the setuid program, and |
334 | * the real uid be the effective uid so that we can change things back. |
335 | */ |
336 | euid = __geteuid (); |
337 | ruid = __getuid (); |
338 | __setreuid (euid, ruid); |
339 | pid = _openchild (MESSENGER, &fargs, &frslt); |
340 | __setreuid (ruid, euid); |
341 | if (pid < 0) |
342 | { |
343 | debug ("open_streams" ); |
344 | __sigprocmask (SIG_SETMASK, &oldmask, NULL); |
345 | return (0); |
346 | } |
347 | xdrstdio_create (&xdrargs, fargs, XDR_ENCODE); |
348 | xdrstdio_create (&xdrrslt, frslt, XDR_DECODE); |
349 | |
350 | if (!xdr_u_long (&xdrargs, &proc) || !(*xdr_arg) (&xdrargs, arg)) |
351 | { |
352 | debug ("xdr args" ); |
353 | success = 0; |
354 | } |
355 | fclose (fargs); |
356 | |
357 | if (success && !(*xdr_rslt) (&xdrrslt, rslt)) |
358 | { |
359 | debug ("xdr rslt" ); |
360 | success = 0; |
361 | } |
362 | fclose(frslt); |
363 | |
364 | wait_again: |
365 | if (__wait4 (pid, &status, 0, NULL) < 0) |
366 | { |
367 | if (errno == EINTR) |
368 | goto wait_again; |
369 | debug ("wait4" ); |
370 | if (errno == ECHILD || errno == ESRCH) |
371 | perror ("wait" ); |
372 | else |
373 | success = 0; |
374 | } |
375 | else |
376 | if (status != 0) |
377 | { |
378 | debug ("wait4 1" ); |
379 | success = 0; |
380 | } |
381 | __sigprocmask (SIG_SETMASK, &oldmask, NULL); |
382 | |
383 | return success; |
384 | } |
385 | #endif |
386 | |
387 | struct key_call_private { |
388 | CLIENT *client; /* Client handle */ |
389 | pid_t pid; /* process-id at moment of creation */ |
390 | uid_t uid; /* user-id at last authorization */ |
391 | }; |
392 | #define key_call_private_main RPC_THREAD_VARIABLE(key_call_private_s) |
393 | __libc_lock_define_initialized (static, keycall_lock) |
394 | |
395 | /* |
396 | * Keep the handle cached. This call may be made quite often. |
397 | */ |
398 | static CLIENT * |
399 | getkeyserv_handle (int vers) |
400 | { |
401 | struct key_call_private *kcp = key_call_private_main; |
402 | struct timeval wait_time; |
403 | int fd; |
404 | struct sockaddr_un name; |
405 | socklen_t namelen = sizeof(struct sockaddr_un); |
406 | |
407 | #define TOTAL_TIMEOUT 30 /* total timeout talking to keyserver */ |
408 | #define TOTAL_TRIES 5 /* Number of tries */ |
409 | |
410 | if (kcp == (struct key_call_private *)NULL) |
411 | { |
412 | kcp = (struct key_call_private *)malloc (sizeof (*kcp)); |
413 | if (kcp == (struct key_call_private *)NULL) |
414 | return (CLIENT *) NULL; |
415 | |
416 | key_call_private_main = kcp; |
417 | kcp->client = NULL; |
418 | } |
419 | |
420 | /* if pid has changed, destroy client and rebuild */ |
421 | if (kcp->client != NULL && kcp->pid != __getpid ()) |
422 | { |
423 | auth_destroy (kcp->client->cl_auth); |
424 | clnt_destroy (kcp->client); |
425 | kcp->client = NULL; |
426 | } |
427 | |
428 | if (kcp->client != NULL) |
429 | { |
430 | /* if other side closed socket, build handle again */ |
431 | clnt_control (kcp->client, CLGET_FD, (char *)&fd); |
432 | if (__getpeername (fd,(struct sockaddr *)&name,&namelen) == -1) |
433 | { |
434 | auth_destroy (kcp->client->cl_auth); |
435 | clnt_destroy (kcp->client); |
436 | kcp->client = NULL; |
437 | } |
438 | } |
439 | |
440 | if (kcp->client != NULL) |
441 | { |
442 | /* if uid has changed, build client handle again */ |
443 | if (kcp->uid != __geteuid ()) |
444 | { |
445 | kcp->uid = __geteuid (); |
446 | auth_destroy (kcp->client->cl_auth); |
447 | kcp->client->cl_auth = |
448 | authunix_create ((char *)"" , kcp->uid, 0, 0, NULL); |
449 | if (kcp->client->cl_auth == NULL) |
450 | { |
451 | clnt_destroy (kcp->client); |
452 | kcp->client = NULL; |
453 | return ((CLIENT *) NULL); |
454 | } |
455 | } |
456 | /* Change the version number to the new one */ |
457 | clnt_control (kcp->client, CLSET_VERS, (void *)&vers); |
458 | return kcp->client; |
459 | } |
460 | |
461 | if ((kcp->client == (CLIENT *) NULL)) |
462 | /* Use the AF_UNIX transport */ |
463 | kcp->client = clnt_create ("/var/run/keyservsock" , KEY_PROG, vers, "unix" ); |
464 | |
465 | if (kcp->client == (CLIENT *) NULL) |
466 | return (CLIENT *) NULL; |
467 | |
468 | kcp->uid = __geteuid (); |
469 | kcp->pid = __getpid (); |
470 | kcp->client->cl_auth = authunix_create ((char *)"" , kcp->uid, 0, 0, NULL); |
471 | if (kcp->client->cl_auth == NULL) |
472 | { |
473 | clnt_destroy (kcp->client); |
474 | kcp->client = NULL; |
475 | return (CLIENT *) NULL; |
476 | } |
477 | |
478 | wait_time.tv_sec = TOTAL_TIMEOUT/TOTAL_TRIES; |
479 | wait_time.tv_usec = 0; |
480 | clnt_control (kcp->client, CLSET_RETRY_TIMEOUT, |
481 | (char *)&wait_time); |
482 | if (clnt_control (kcp->client, CLGET_FD, (char *)&fd)) |
483 | __fcntl (fd, F_SETFD, FD_CLOEXEC); /* make it "close on exec" */ |
484 | |
485 | return kcp->client; |
486 | } |
487 | |
488 | /* returns 0 on failure, 1 on success */ |
489 | static int |
490 | key_call_socket (u_long proc, xdrproc_t xdr_arg, char *arg, |
491 | xdrproc_t xdr_rslt, char *rslt) |
492 | { |
493 | CLIENT *clnt; |
494 | struct timeval wait_time; |
495 | int result = 0; |
496 | |
497 | __libc_lock_lock (keycall_lock); |
498 | if ((proc == KEY_ENCRYPT_PK) || (proc == KEY_DECRYPT_PK) || |
499 | (proc == KEY_NET_GET) || (proc == KEY_NET_PUT) || |
500 | (proc == KEY_GET_CONV)) |
501 | clnt = getkeyserv_handle(2); /* talk to version 2 */ |
502 | else |
503 | clnt = getkeyserv_handle(1); /* talk to version 1 */ |
504 | |
505 | if (clnt != NULL) |
506 | { |
507 | wait_time.tv_sec = TOTAL_TIMEOUT; |
508 | wait_time.tv_usec = 0; |
509 | |
510 | if (clnt_call (clnt, proc, xdr_arg, arg, xdr_rslt, rslt, |
511 | wait_time) == RPC_SUCCESS) |
512 | result = 1; |
513 | } |
514 | |
515 | __libc_lock_unlock (keycall_lock); |
516 | |
517 | return result; |
518 | } |
519 | |
520 | |
521 | /* returns 0 on failure, 1 on success */ |
522 | static int |
523 | key_call (u_long proc, xdrproc_t xdr_arg, char *arg, |
524 | xdrproc_t xdr_rslt, char *rslt) |
525 | { |
526 | #ifndef SO_PASSCRED |
527 | static int use_keyenvoy; |
528 | #endif |
529 | |
530 | if (proc == KEY_ENCRYPT_PK && __key_encryptsession_pk_LOCAL) |
531 | { |
532 | cryptkeyres *res; |
533 | res = (*__key_encryptsession_pk_LOCAL) (__geteuid (), arg); |
534 | *(cryptkeyres *) rslt = *res; |
535 | return 1; |
536 | } |
537 | else if (proc == KEY_DECRYPT_PK && __key_decryptsession_pk_LOCAL) |
538 | { |
539 | cryptkeyres *res; |
540 | res = (*__key_decryptsession_pk_LOCAL) (__geteuid (), arg); |
541 | *(cryptkeyres *) rslt = *res; |
542 | return 1; |
543 | } |
544 | else if (proc == KEY_GEN && __key_gendes_LOCAL) |
545 | { |
546 | des_block *res; |
547 | res = (*__key_gendes_LOCAL) (__geteuid (), 0); |
548 | *(des_block *) rslt = *res; |
549 | return 1; |
550 | } |
551 | |
552 | #ifdef SO_PASSCRED |
553 | return key_call_socket (proc, xdr_arg, arg, xdr_rslt, rslt); |
554 | #else |
555 | if (!use_keyenvoy) |
556 | { |
557 | if (key_call_socket (proc, xdr_arg, arg, xdr_rslt, rslt)) |
558 | return 1; |
559 | use_keyenvoy = 1; |
560 | } |
561 | return key_call_keyenvoy (proc, xdr_arg, arg, xdr_rslt, rslt); |
562 | #endif |
563 | } |
564 | |
565 | void |
566 | __rpc_thread_key_cleanup (void) |
567 | { |
568 | struct key_call_private *kcp = RPC_THREAD_VARIABLE(key_call_private_s); |
569 | |
570 | if (kcp) { |
571 | if (kcp->client) { |
572 | if (kcp->client->cl_auth) |
573 | auth_destroy (kcp->client->cl_auth); |
574 | clnt_destroy(kcp->client); |
575 | } |
576 | free (kcp); |
577 | } |
578 | } |
579 | |