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 | |
53 | #define KEY_TIMEOUT 5 /* per-try timeout in seconds */ |
54 | #define KEY_NRETRY 12 /* number of retries */ |
55 | |
56 | #define debug(msg) /* turn off debugging */ |
57 | |
58 | #ifndef SO_PASSCRED |
59 | extern int _openchild (const char *command, FILE **fto, FILE **ffrom); |
60 | #endif |
61 | |
62 | static int key_call (u_long, xdrproc_t xdr_arg, char *, |
63 | xdrproc_t xdr_rslt, char *) internal_function; |
64 | |
65 | static const struct timeval trytimeout = {KEY_TIMEOUT, 0}; |
66 | static const struct timeval tottimeout = {KEY_TIMEOUT *KEY_NRETRY, 0}; |
67 | |
68 | int |
69 | key_setsecret (char *secretkey) |
70 | { |
71 | keystatus status; |
72 | |
73 | if (!key_call ((u_long) KEY_SET, (xdrproc_t) xdr_keybuf, secretkey, |
74 | (xdrproc_t) xdr_keystatus, (char *) &status)) |
75 | return -1; |
76 | if (status != KEY_SUCCESS) |
77 | { |
78 | debug ("set status is nonzero" ); |
79 | return -1; |
80 | } |
81 | return 0; |
82 | } |
83 | libc_hidden_nolink_sunrpc (key_setsecret, GLIBC_2_1) |
84 | |
85 | /* key_secretkey_is_set() returns 1 if the keyserver has a secret key |
86 | * stored for the caller's effective uid; it returns 0 otherwise |
87 | * |
88 | * N.B.: The KEY_NET_GET key call is undocumented. Applications shouldn't |
89 | * be using it, because it allows them to get the user's secret key. |
90 | */ |
91 | int |
92 | key_secretkey_is_set (void) |
93 | { |
94 | struct key_netstres kres; |
95 | |
96 | memset (&kres, 0, sizeof (kres)); |
97 | if (key_call ((u_long) KEY_NET_GET, (xdrproc_t) xdr_void, |
98 | (char *) NULL, (xdrproc_t) xdr_key_netstres, |
99 | (char *) &kres) && |
100 | (kres.status == KEY_SUCCESS) && |
101 | (kres.key_netstres_u.knet.st_priv_key[0] != 0)) |
102 | { |
103 | /* avoid leaving secret key in memory */ |
104 | memset (kres.key_netstres_u.knet.st_priv_key, 0, HEXKEYBYTES); |
105 | return 1; |
106 | } |
107 | return 0; |
108 | } |
109 | #ifdef EXPORT_RPC_SYMBOLS |
110 | libc_hidden_def (key_secretkey_is_set) |
111 | #else |
112 | libc_hidden_nolink_sunrpc (key_secretkey_is_set, GLIBC_2_1) |
113 | #endif |
114 | |
115 | int |
116 | key_encryptsession (char *remotename, des_block *deskey) |
117 | { |
118 | cryptkeyarg arg; |
119 | cryptkeyres res; |
120 | |
121 | arg.remotename = remotename; |
122 | arg.deskey = *deskey; |
123 | if (!key_call ((u_long) KEY_ENCRYPT, (xdrproc_t) xdr_cryptkeyarg, |
124 | (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
125 | (char *) &res)) |
126 | return -1; |
127 | |
128 | if (res.status != KEY_SUCCESS) |
129 | { |
130 | debug ("encrypt status is nonzero" ); |
131 | return -1; |
132 | } |
133 | *deskey = res.cryptkeyres_u.deskey; |
134 | return 0; |
135 | } |
136 | libc_hidden_nolink_sunrpc (key_encryptsession, GLIBC_2_1) |
137 | |
138 | int |
139 | key_decryptsession (char *remotename, des_block *deskey) |
140 | { |
141 | cryptkeyarg arg; |
142 | cryptkeyres res; |
143 | |
144 | arg.remotename = remotename; |
145 | arg.deskey = *deskey; |
146 | if (!key_call ((u_long) KEY_DECRYPT, (xdrproc_t) xdr_cryptkeyarg, |
147 | (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
148 | (char *) &res)) |
149 | return -1; |
150 | if (res.status != KEY_SUCCESS) |
151 | { |
152 | debug ("decrypt status is nonzero" ); |
153 | return -1; |
154 | } |
155 | *deskey = res.cryptkeyres_u.deskey; |
156 | return 0; |
157 | } |
158 | libc_hidden_nolink_sunrpc (key_decryptsession, GLIBC_2_1) |
159 | |
160 | int |
161 | key_encryptsession_pk (char *remotename, netobj *remotekey, |
162 | des_block *deskey) |
163 | { |
164 | cryptkeyarg2 arg; |
165 | cryptkeyres res; |
166 | |
167 | arg.remotename = remotename; |
168 | arg.remotekey = *remotekey; |
169 | arg.deskey = *deskey; |
170 | if (!key_call ((u_long) KEY_ENCRYPT_PK, (xdrproc_t) xdr_cryptkeyarg2, |
171 | (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
172 | (char *) &res)) |
173 | return -1; |
174 | |
175 | if (res.status != KEY_SUCCESS) |
176 | { |
177 | debug ("encrypt status is nonzero" ); |
178 | return -1; |
179 | } |
180 | *deskey = res.cryptkeyres_u.deskey; |
181 | return 0; |
182 | } |
183 | libc_hidden_nolink_sunrpc (key_encryptsession_pk, GLIBC_2_1) |
184 | |
185 | int |
186 | key_decryptsession_pk (char *remotename, netobj *remotekey, |
187 | des_block *deskey) |
188 | { |
189 | cryptkeyarg2 arg; |
190 | cryptkeyres res; |
191 | |
192 | arg.remotename = remotename; |
193 | arg.remotekey = *remotekey; |
194 | arg.deskey = *deskey; |
195 | if (!key_call ((u_long) KEY_DECRYPT_PK, (xdrproc_t) xdr_cryptkeyarg2, |
196 | (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
197 | (char *) &res)) |
198 | return -1; |
199 | |
200 | if (res.status != KEY_SUCCESS) |
201 | { |
202 | debug ("decrypt status is nonzero" ); |
203 | return -1; |
204 | } |
205 | *deskey = res.cryptkeyres_u.deskey; |
206 | return 0; |
207 | } |
208 | libc_hidden_nolink_sunrpc (key_decryptsession_pk, GLIBC_2_1) |
209 | |
210 | int |
211 | key_gendes (des_block *key) |
212 | { |
213 | struct sockaddr_in sin; |
214 | CLIENT *client; |
215 | int socket; |
216 | enum clnt_stat stat; |
217 | |
218 | sin.sin_family = AF_INET; |
219 | sin.sin_port = 0; |
220 | sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK); |
221 | __bzero (sin.sin_zero, sizeof (sin.sin_zero)); |
222 | socket = RPC_ANYSOCK; |
223 | client = clntudp_bufcreate (&sin, (u_long) KEY_PROG, (u_long) KEY_VERS, |
224 | trytimeout, &socket, RPCSMALLMSGSIZE, |
225 | RPCSMALLMSGSIZE); |
226 | if (client == NULL) |
227 | return -1; |
228 | |
229 | stat = clnt_call (client, KEY_GEN, (xdrproc_t) xdr_void, NULL, |
230 | (xdrproc_t) xdr_des_block, (caddr_t) key, |
231 | tottimeout); |
232 | clnt_destroy (client); |
233 | __close (socket); |
234 | if (stat != RPC_SUCCESS) |
235 | return -1; |
236 | |
237 | return 0; |
238 | } |
239 | #ifdef EXPORT_RPC_SYMBOLS |
240 | libc_hidden_def (key_gendes) |
241 | #else |
242 | libc_hidden_nolink_sunrpc (key_gendes, GLIBC_2_1) |
243 | #endif |
244 | |
245 | int |
246 | key_setnet (struct key_netstarg *arg) |
247 | { |
248 | keystatus status; |
249 | |
250 | if (!key_call ((u_long) KEY_NET_PUT, (xdrproc_t) xdr_key_netstarg, |
251 | (char *) arg,(xdrproc_t) xdr_keystatus, |
252 | (char *) &status)) |
253 | return -1; |
254 | |
255 | if (status != KEY_SUCCESS) |
256 | { |
257 | debug ("key_setnet status is nonzero" ); |
258 | return -1; |
259 | } |
260 | return 1; |
261 | } |
262 | libc_hidden_nolink_sunrpc (key_setnet, GLIBC_2_1) |
263 | |
264 | int |
265 | key_get_conv (char *pkey, des_block *deskey) |
266 | { |
267 | cryptkeyres res; |
268 | |
269 | if (!key_call ((u_long) KEY_GET_CONV, (xdrproc_t) xdr_keybuf, pkey, |
270 | (xdrproc_t) xdr_cryptkeyres, (char *) &res)) |
271 | return -1; |
272 | |
273 | if (res.status != KEY_SUCCESS) |
274 | { |
275 | debug ("get_conv status is nonzero" ); |
276 | return -1; |
277 | } |
278 | *deskey = res.cryptkeyres_u.deskey; |
279 | return 0; |
280 | } |
281 | libc_hidden_nolink_sunrpc (key_get_conv, GLIBC_2_1) |
282 | |
283 | /* |
284 | * Hack to allow the keyserver to use AUTH_DES (for authenticated |
285 | * NIS+ calls, for example). The only functions that get called |
286 | * are key_encryptsession_pk, key_decryptsession_pk, and key_gendes. |
287 | * |
288 | * The approach is to have the keyserver fill in pointers to local |
289 | * implementations of these functions, and to call those in key_call(). |
290 | */ |
291 | |
292 | cryptkeyres *(*__key_encryptsession_pk_LOCAL) (uid_t, char *); |
293 | cryptkeyres *(*__key_decryptsession_pk_LOCAL) (uid_t, char *); |
294 | des_block *(*__key_gendes_LOCAL) (uid_t, char *); |
295 | |
296 | #ifndef SO_PASSCRED |
297 | static int |
298 | internal_function |
299 | key_call_keyenvoy (u_long proc, xdrproc_t xdr_arg, char *arg, |
300 | xdrproc_t xdr_rslt, char *rslt) |
301 | { |
302 | XDR xdrargs; |
303 | XDR xdrrslt; |
304 | FILE *fargs; |
305 | FILE *frslt; |
306 | sigset_t oldmask, mask; |
307 | int status; |
308 | int pid; |
309 | int success; |
310 | uid_t ruid; |
311 | uid_t euid; |
312 | static const char MESSENGER[] = "/usr/etc/keyenvoy" ; |
313 | |
314 | success = 1; |
315 | sigemptyset (&mask); |
316 | sigaddset (&mask, SIGCHLD); |
317 | __sigprocmask (SIG_BLOCK, &mask, &oldmask); |
318 | |
319 | /* |
320 | * We are going to exec a set-uid program which makes our effective uid |
321 | * zero, and authenticates us with our real uid. We need to make the |
322 | * effective uid be the real uid for the setuid program, and |
323 | * the real uid be the effective uid so that we can change things back. |
324 | */ |
325 | euid = __geteuid (); |
326 | ruid = __getuid (); |
327 | __setreuid (euid, ruid); |
328 | pid = _openchild (MESSENGER, &fargs, &frslt); |
329 | __setreuid (ruid, euid); |
330 | if (pid < 0) |
331 | { |
332 | debug ("open_streams" ); |
333 | __sigprocmask (SIG_SETMASK, &oldmask, NULL); |
334 | return (0); |
335 | } |
336 | xdrstdio_create (&xdrargs, fargs, XDR_ENCODE); |
337 | xdrstdio_create (&xdrrslt, frslt, XDR_DECODE); |
338 | |
339 | if (!xdr_u_long (&xdrargs, &proc) || !(*xdr_arg) (&xdrargs, arg)) |
340 | { |
341 | debug ("xdr args" ); |
342 | success = 0; |
343 | } |
344 | fclose (fargs); |
345 | |
346 | if (success && !(*xdr_rslt) (&xdrrslt, rslt)) |
347 | { |
348 | debug ("xdr rslt" ); |
349 | success = 0; |
350 | } |
351 | fclose(frslt); |
352 | |
353 | wait_again: |
354 | if (__wait4 (pid, &status, 0, NULL) < 0) |
355 | { |
356 | if (errno == EINTR) |
357 | goto wait_again; |
358 | debug ("wait4" ); |
359 | if (errno == ECHILD || errno == ESRCH) |
360 | perror ("wait" ); |
361 | else |
362 | success = 0; |
363 | } |
364 | else |
365 | if (status != 0) |
366 | { |
367 | debug ("wait4 1" ); |
368 | success = 0; |
369 | } |
370 | __sigprocmask (SIG_SETMASK, &oldmask, NULL); |
371 | |
372 | return success; |
373 | } |
374 | #endif |
375 | |
376 | struct key_call_private { |
377 | CLIENT *client; /* Client handle */ |
378 | pid_t pid; /* process-id at moment of creation */ |
379 | uid_t uid; /* user-id at last authorization */ |
380 | }; |
381 | #ifdef _RPC_THREAD_SAFE_ |
382 | #define key_call_private_main RPC_THREAD_VARIABLE(key_call_private_s) |
383 | #else |
384 | static struct key_call_private *key_call_private_main; |
385 | #endif |
386 | __libc_lock_define_initialized (static, keycall_lock) |
387 | |
388 | /* |
389 | * Keep the handle cached. This call may be made quite often. |
390 | */ |
391 | static CLIENT * |
392 | getkeyserv_handle (int vers) |
393 | { |
394 | struct key_call_private *kcp = key_call_private_main; |
395 | struct timeval wait_time; |
396 | int fd; |
397 | struct sockaddr_un name; |
398 | socklen_t namelen = sizeof(struct sockaddr_un); |
399 | |
400 | #define TOTAL_TIMEOUT 30 /* total timeout talking to keyserver */ |
401 | #define TOTAL_TRIES 5 /* Number of tries */ |
402 | |
403 | if (kcp == (struct key_call_private *)NULL) |
404 | { |
405 | kcp = (struct key_call_private *)malloc (sizeof (*kcp)); |
406 | if (kcp == (struct key_call_private *)NULL) |
407 | return (CLIENT *) NULL; |
408 | |
409 | key_call_private_main = kcp; |
410 | kcp->client = NULL; |
411 | } |
412 | |
413 | /* if pid has changed, destroy client and rebuild */ |
414 | if (kcp->client != NULL && kcp->pid != __getpid ()) |
415 | { |
416 | auth_destroy (kcp->client->cl_auth); |
417 | clnt_destroy (kcp->client); |
418 | kcp->client = NULL; |
419 | } |
420 | |
421 | if (kcp->client != NULL) |
422 | { |
423 | /* if other side closed socket, build handle again */ |
424 | clnt_control (kcp->client, CLGET_FD, (char *)&fd); |
425 | if (__getpeername (fd,(struct sockaddr *)&name,&namelen) == -1) |
426 | { |
427 | auth_destroy (kcp->client->cl_auth); |
428 | clnt_destroy (kcp->client); |
429 | kcp->client = NULL; |
430 | } |
431 | } |
432 | |
433 | if (kcp->client != NULL) |
434 | { |
435 | /* if uid has changed, build client handle again */ |
436 | if (kcp->uid != __geteuid ()) |
437 | { |
438 | kcp->uid = __geteuid (); |
439 | auth_destroy (kcp->client->cl_auth); |
440 | kcp->client->cl_auth = |
441 | authunix_create ((char *)"" , kcp->uid, 0, 0, NULL); |
442 | if (kcp->client->cl_auth == NULL) |
443 | { |
444 | clnt_destroy (kcp->client); |
445 | kcp->client = NULL; |
446 | return ((CLIENT *) NULL); |
447 | } |
448 | } |
449 | /* Change the version number to the new one */ |
450 | clnt_control (kcp->client, CLSET_VERS, (void *)&vers); |
451 | return kcp->client; |
452 | } |
453 | |
454 | if ((kcp->client == (CLIENT *) NULL)) |
455 | /* Use the AF_UNIX transport */ |
456 | kcp->client = clnt_create ("/var/run/keyservsock" , KEY_PROG, vers, "unix" ); |
457 | |
458 | if (kcp->client == (CLIENT *) NULL) |
459 | return (CLIENT *) NULL; |
460 | |
461 | kcp->uid = __geteuid (); |
462 | kcp->pid = __getpid (); |
463 | kcp->client->cl_auth = authunix_create ((char *)"" , kcp->uid, 0, 0, NULL); |
464 | if (kcp->client->cl_auth == NULL) |
465 | { |
466 | clnt_destroy (kcp->client); |
467 | kcp->client = NULL; |
468 | return (CLIENT *) NULL; |
469 | } |
470 | |
471 | wait_time.tv_sec = TOTAL_TIMEOUT/TOTAL_TRIES; |
472 | wait_time.tv_usec = 0; |
473 | clnt_control (kcp->client, CLSET_RETRY_TIMEOUT, |
474 | (char *)&wait_time); |
475 | if (clnt_control (kcp->client, CLGET_FD, (char *)&fd)) |
476 | __fcntl (fd, F_SETFD, FD_CLOEXEC); /* make it "close on exec" */ |
477 | |
478 | return kcp->client; |
479 | } |
480 | |
481 | /* returns 0 on failure, 1 on success */ |
482 | static int |
483 | internal_function |
484 | key_call_socket (u_long proc, xdrproc_t xdr_arg, char *arg, |
485 | xdrproc_t xdr_rslt, char *rslt) |
486 | { |
487 | CLIENT *clnt; |
488 | struct timeval wait_time; |
489 | int result = 0; |
490 | |
491 | __libc_lock_lock (keycall_lock); |
492 | if ((proc == KEY_ENCRYPT_PK) || (proc == KEY_DECRYPT_PK) || |
493 | (proc == KEY_NET_GET) || (proc == KEY_NET_PUT) || |
494 | (proc == KEY_GET_CONV)) |
495 | clnt = getkeyserv_handle(2); /* talk to version 2 */ |
496 | else |
497 | clnt = getkeyserv_handle(1); /* talk to version 1 */ |
498 | |
499 | if (clnt != NULL) |
500 | { |
501 | wait_time.tv_sec = TOTAL_TIMEOUT; |
502 | wait_time.tv_usec = 0; |
503 | |
504 | if (clnt_call (clnt, proc, xdr_arg, arg, xdr_rslt, rslt, |
505 | wait_time) == RPC_SUCCESS) |
506 | result = 1; |
507 | } |
508 | |
509 | __libc_lock_unlock (keycall_lock); |
510 | |
511 | return result; |
512 | } |
513 | |
514 | |
515 | /* returns 0 on failure, 1 on success */ |
516 | static int |
517 | internal_function |
518 | key_call (u_long proc, xdrproc_t xdr_arg, char *arg, |
519 | xdrproc_t xdr_rslt, char *rslt) |
520 | { |
521 | #ifndef SO_PASSCRED |
522 | static int use_keyenvoy; |
523 | #endif |
524 | |
525 | if (proc == KEY_ENCRYPT_PK && __key_encryptsession_pk_LOCAL) |
526 | { |
527 | cryptkeyres *res; |
528 | res = (*__key_encryptsession_pk_LOCAL) (__geteuid (), arg); |
529 | *(cryptkeyres *) rslt = *res; |
530 | return 1; |
531 | } |
532 | else if (proc == KEY_DECRYPT_PK && __key_decryptsession_pk_LOCAL) |
533 | { |
534 | cryptkeyres *res; |
535 | res = (*__key_decryptsession_pk_LOCAL) (__geteuid (), arg); |
536 | *(cryptkeyres *) rslt = *res; |
537 | return 1; |
538 | } |
539 | else if (proc == KEY_GEN && __key_gendes_LOCAL) |
540 | { |
541 | des_block *res; |
542 | res = (*__key_gendes_LOCAL) (__geteuid (), 0); |
543 | *(des_block *) rslt = *res; |
544 | return 1; |
545 | } |
546 | |
547 | #ifdef SO_PASSCRED |
548 | return key_call_socket (proc, xdr_arg, arg, xdr_rslt, rslt); |
549 | #else |
550 | if (!use_keyenvoy) |
551 | { |
552 | if (key_call_socket (proc, xdr_arg, arg, xdr_rslt, rslt)) |
553 | return 1; |
554 | use_keyenvoy = 1; |
555 | } |
556 | return key_call_keyenvoy (proc, xdr_arg, arg, xdr_rslt, rslt); |
557 | #endif |
558 | } |
559 | |
560 | #ifdef _RPC_THREAD_SAFE_ |
561 | void |
562 | __rpc_thread_key_cleanup (void) |
563 | { |
564 | struct key_call_private *kcp = RPC_THREAD_VARIABLE(key_call_private_s); |
565 | |
566 | if (kcp) { |
567 | if (kcp->client) { |
568 | if (kcp->client->cl_auth) |
569 | auth_destroy (kcp->client->cl_auth); |
570 | clnt_destroy(kcp->client); |
571 | } |
572 | free (kcp); |
573 | } |
574 | } |
575 | #endif /* _RPC_THREAD_SAFE_ */ |
576 | |