| /* |
| * Copyright (c) 2010, Oracle America, Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * * Neither the name of the "Oracle America, Inc." nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
| * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
| * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| /* |
| * The original source is from the RPCSRC 4.0 package from Sun Microsystems. |
| * The Interface to keyserver protocoll 2, RPC over AF_UNIX and Linux/doors |
| * was added by Thorsten Kukuk <kukuk@suse.de> |
| * Since the Linux/doors project was stopped, I doubt that this code will |
| * ever be useful <kukuk@suse.de>. |
| */ |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <rpc/rpc.h> |
| #include <rpc/auth.h> |
| #include <sys/wait.h> |
| #include <sys/param.h> |
| #include <sys/socket.h> |
| #include <rpc/key_prot.h> |
| #include <bits/libc-lock.h> |
| |
| #define KEY_TIMEOUT 5 /* per-try timeout in seconds */ |
| #define KEY_NRETRY 12 /* number of retries */ |
| |
| #define debug(msg) /* turn off debugging */ |
| |
| #ifndef SO_PASSCRED |
| extern int _openchild (const char *command, FILE **fto, FILE **ffrom); |
| #endif |
| |
| static int key_call (u_long, xdrproc_t xdr_arg, char *, |
| xdrproc_t xdr_rslt, char *) internal_function; |
| |
| static const struct timeval trytimeout = {KEY_TIMEOUT, 0}; |
| static const struct timeval tottimeout = {KEY_TIMEOUT *KEY_NRETRY, 0}; |
| |
| int |
| key_setsecret (char *secretkey) |
| { |
| keystatus status; |
| |
| if (!key_call ((u_long) KEY_SET, (xdrproc_t) xdr_keybuf, secretkey, |
| (xdrproc_t) xdr_keystatus, (char *) &status)) |
| return -1; |
| if (status != KEY_SUCCESS) |
| { |
| debug ("set status is nonzero"); |
| return -1; |
| } |
| return 0; |
| } |
| libc_hidden_nolink_sunrpc (key_setsecret, GLIBC_2_1) |
| |
| /* key_secretkey_is_set() returns 1 if the keyserver has a secret key |
| * stored for the caller's effective uid; it returns 0 otherwise |
| * |
| * N.B.: The KEY_NET_GET key call is undocumented. Applications shouldn't |
| * be using it, because it allows them to get the user's secret key. |
| */ |
| int |
| key_secretkey_is_set (void) |
| { |
| struct key_netstres kres; |
| |
| memset (&kres, 0, sizeof (kres)); |
| if (key_call ((u_long) KEY_NET_GET, (xdrproc_t) xdr_void, |
| (char *) NULL, (xdrproc_t) xdr_key_netstres, |
| (char *) &kres) && |
| (kres.status == KEY_SUCCESS) && |
| (kres.key_netstres_u.knet.st_priv_key[0] != 0)) |
| { |
| /* avoid leaving secret key in memory */ |
| memset (kres.key_netstres_u.knet.st_priv_key, 0, HEXKEYBYTES); |
| return 1; |
| } |
| return 0; |
| } |
| #ifdef EXPORT_RPC_SYMBOLS |
| libc_hidden_def (key_secretkey_is_set) |
| #else |
| libc_hidden_nolink_sunrpc (key_secretkey_is_set, GLIBC_2_1) |
| #endif |
| |
| int |
| key_encryptsession (char *remotename, des_block *deskey) |
| { |
| cryptkeyarg arg; |
| cryptkeyres res; |
| |
| arg.remotename = remotename; |
| arg.deskey = *deskey; |
| if (!key_call ((u_long) KEY_ENCRYPT, (xdrproc_t) xdr_cryptkeyarg, |
| (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
| (char *) &res)) |
| return -1; |
| |
| if (res.status != KEY_SUCCESS) |
| { |
| debug ("encrypt status is nonzero"); |
| return -1; |
| } |
| *deskey = res.cryptkeyres_u.deskey; |
| return 0; |
| } |
| libc_hidden_nolink_sunrpc (key_encryptsession, GLIBC_2_1) |
| |
| int |
| key_decryptsession (char *remotename, des_block *deskey) |
| { |
| cryptkeyarg arg; |
| cryptkeyres res; |
| |
| arg.remotename = remotename; |
| arg.deskey = *deskey; |
| if (!key_call ((u_long) KEY_DECRYPT, (xdrproc_t) xdr_cryptkeyarg, |
| (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
| (char *) &res)) |
| return -1; |
| if (res.status != KEY_SUCCESS) |
| { |
| debug ("decrypt status is nonzero"); |
| return -1; |
| } |
| *deskey = res.cryptkeyres_u.deskey; |
| return 0; |
| } |
| libc_hidden_nolink_sunrpc (key_decryptsession, GLIBC_2_1) |
| |
| int |
| key_encryptsession_pk (char *remotename, netobj *remotekey, |
| des_block *deskey) |
| { |
| cryptkeyarg2 arg; |
| cryptkeyres res; |
| |
| arg.remotename = remotename; |
| arg.remotekey = *remotekey; |
| arg.deskey = *deskey; |
| if (!key_call ((u_long) KEY_ENCRYPT_PK, (xdrproc_t) xdr_cryptkeyarg2, |
| (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
| (char *) &res)) |
| return -1; |
| |
| if (res.status != KEY_SUCCESS) |
| { |
| debug ("encrypt status is nonzero"); |
| return -1; |
| } |
| *deskey = res.cryptkeyres_u.deskey; |
| return 0; |
| } |
| libc_hidden_nolink_sunrpc (key_encryptsession_pk, GLIBC_2_1) |
| |
| int |
| key_decryptsession_pk (char *remotename, netobj *remotekey, |
| des_block *deskey) |
| { |
| cryptkeyarg2 arg; |
| cryptkeyres res; |
| |
| arg.remotename = remotename; |
| arg.remotekey = *remotekey; |
| arg.deskey = *deskey; |
| if (!key_call ((u_long) KEY_DECRYPT_PK, (xdrproc_t) xdr_cryptkeyarg2, |
| (char *) &arg, (xdrproc_t) xdr_cryptkeyres, |
| (char *) &res)) |
| return -1; |
| |
| if (res.status != KEY_SUCCESS) |
| { |
| debug ("decrypt status is nonzero"); |
| return -1; |
| } |
| *deskey = res.cryptkeyres_u.deskey; |
| return 0; |
| } |
| libc_hidden_nolink_sunrpc (key_decryptsession_pk, GLIBC_2_1) |
| |
| int |
| key_gendes (des_block *key) |
| { |
| struct sockaddr_in sin; |
| CLIENT *client; |
| int socket; |
| enum clnt_stat stat; |
| |
| sin.sin_family = AF_INET; |
| sin.sin_port = 0; |
| sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK); |
| __bzero (sin.sin_zero, sizeof (sin.sin_zero)); |
| socket = RPC_ANYSOCK; |
| client = clntudp_bufcreate (&sin, (u_long) KEY_PROG, (u_long) KEY_VERS, |
| trytimeout, &socket, RPCSMALLMSGSIZE, |
| RPCSMALLMSGSIZE); |
| if (client == NULL) |
| return -1; |
| |
| stat = clnt_call (client, KEY_GEN, (xdrproc_t) xdr_void, NULL, |
| (xdrproc_t) xdr_des_block, (caddr_t) key, |
| tottimeout); |
| clnt_destroy (client); |
| __close (socket); |
| if (stat != RPC_SUCCESS) |
| return -1; |
| |
| return 0; |
| } |
| #ifdef EXPORT_RPC_SYMBOLS |
| libc_hidden_def (key_gendes) |
| #else |
| libc_hidden_nolink_sunrpc (key_gendes, GLIBC_2_1) |
| #endif |
| |
| int |
| key_setnet (struct key_netstarg *arg) |
| { |
| keystatus status; |
| |
| if (!key_call ((u_long) KEY_NET_PUT, (xdrproc_t) xdr_key_netstarg, |
| (char *) arg,(xdrproc_t) xdr_keystatus, |
| (char *) &status)) |
| return -1; |
| |
| if (status != KEY_SUCCESS) |
| { |
| debug ("key_setnet status is nonzero"); |
| return -1; |
| } |
| return 1; |
| } |
| libc_hidden_nolink_sunrpc (key_setnet, GLIBC_2_1) |
| |
| int |
| key_get_conv (char *pkey, des_block *deskey) |
| { |
| cryptkeyres res; |
| |
| if (!key_call ((u_long) KEY_GET_CONV, (xdrproc_t) xdr_keybuf, pkey, |
| (xdrproc_t) xdr_cryptkeyres, (char *) &res)) |
| return -1; |
| |
| if (res.status != KEY_SUCCESS) |
| { |
| debug ("get_conv status is nonzero"); |
| return -1; |
| } |
| *deskey = res.cryptkeyres_u.deskey; |
| return 0; |
| } |
| libc_hidden_nolink_sunrpc (key_get_conv, GLIBC_2_1) |
| |
| /* |
| * Hack to allow the keyserver to use AUTH_DES (for authenticated |
| * NIS+ calls, for example). The only functions that get called |
| * are key_encryptsession_pk, key_decryptsession_pk, and key_gendes. |
| * |
| * The approach is to have the keyserver fill in pointers to local |
| * implementations of these functions, and to call those in key_call(). |
| */ |
| |
| cryptkeyres *(*__key_encryptsession_pk_LOCAL) (uid_t, char *); |
| cryptkeyres *(*__key_decryptsession_pk_LOCAL) (uid_t, char *); |
| des_block *(*__key_gendes_LOCAL) (uid_t, char *); |
| |
| #ifndef SO_PASSCRED |
| static int |
| internal_function |
| key_call_keyenvoy (u_long proc, xdrproc_t xdr_arg, char *arg, |
| xdrproc_t xdr_rslt, char *rslt) |
| { |
| XDR xdrargs; |
| XDR xdrrslt; |
| FILE *fargs; |
| FILE *frslt; |
| sigset_t oldmask, mask; |
| union wait status; |
| int pid; |
| int success; |
| uid_t ruid; |
| uid_t euid; |
| static const char MESSENGER[] = "/usr/etc/keyenvoy"; |
| |
| success = 1; |
| sigemptyset (&mask); |
| sigaddset (&mask, SIGCHLD); |
| __sigprocmask (SIG_BLOCK, &mask, &oldmask); |
| |
| /* |
| * We are going to exec a set-uid program which makes our effective uid |
| * zero, and authenticates us with our real uid. We need to make the |
| * effective uid be the real uid for the setuid program, and |
| * the real uid be the effective uid so that we can change things back. |
| */ |
| euid = __geteuid (); |
| ruid = __getuid (); |
| __setreuid (euid, ruid); |
| pid = _openchild (MESSENGER, &fargs, &frslt); |
| __setreuid (ruid, euid); |
| if (pid < 0) |
| { |
| debug ("open_streams"); |
| __sigprocmask (SIG_SETMASK, &oldmask, NULL); |
| return (0); |
| } |
| xdrstdio_create (&xdrargs, fargs, XDR_ENCODE); |
| xdrstdio_create (&xdrrslt, frslt, XDR_DECODE); |
| |
| if (!xdr_u_long (&xdrargs, &proc) || !(*xdr_arg) (&xdrargs, arg)) |
| { |
| debug ("xdr args"); |
| success = 0; |
| } |
| fclose (fargs); |
| |
| if (success && !(*xdr_rslt) (&xdrrslt, rslt)) |
| { |
| debug ("xdr rslt"); |
| success = 0; |
| } |
| fclose(frslt); |
| |
| wait_again: |
| if (__wait4 (pid, &status, 0, NULL) < 0) |
| { |
| if (errno == EINTR) |
| goto wait_again; |
| debug ("wait4"); |
| if (errno == ECHILD || errno == ESRCH) |
| perror ("wait"); |
| else |
| success = 0; |
| } |
| else |
| if (status.w_retcode) |
| { |
| debug ("wait4 1"); |
| success = 0; |
| } |
| __sigprocmask (SIG_SETMASK, &oldmask, NULL); |
| |
| return success; |
| } |
| #endif |
| |
| struct key_call_private { |
| CLIENT *client; /* Client handle */ |
| pid_t pid; /* process-id at moment of creation */ |
| uid_t uid; /* user-id at last authorization */ |
| }; |
| #ifdef _RPC_THREAD_SAFE_ |
| #define key_call_private_main RPC_THREAD_VARIABLE(key_call_private_s) |
| #else |
| static struct key_call_private *key_call_private_main; |
| #endif |
| __libc_lock_define_initialized (static, keycall_lock) |
| |
| /* |
| * Keep the handle cached. This call may be made quite often. |
| */ |
| static CLIENT * |
| getkeyserv_handle (int vers) |
| { |
| struct key_call_private *kcp = key_call_private_main; |
| struct timeval wait_time; |
| int fd; |
| struct sockaddr_un name; |
| socklen_t namelen = sizeof(struct sockaddr_un); |
| |
| #define TOTAL_TIMEOUT 30 /* total timeout talking to keyserver */ |
| #define TOTAL_TRIES 5 /* Number of tries */ |
| |
| if (kcp == (struct key_call_private *)NULL) |
| { |
| kcp = (struct key_call_private *)malloc (sizeof (*kcp)); |
| if (kcp == (struct key_call_private *)NULL) |
| return (CLIENT *) NULL; |
| |
| key_call_private_main = kcp; |
| kcp->client = NULL; |
| } |
| |
| /* if pid has changed, destroy client and rebuild */ |
| if (kcp->client != NULL && kcp->pid != __getpid ()) |
| { |
| auth_destroy (kcp->client->cl_auth); |
| clnt_destroy (kcp->client); |
| kcp->client = NULL; |
| } |
| |
| if (kcp->client != NULL) |
| { |
| /* if other side closed socket, build handle again */ |
| clnt_control (kcp->client, CLGET_FD, (char *)&fd); |
| if (__getpeername (fd,(struct sockaddr *)&name,&namelen) == -1) |
| { |
| auth_destroy (kcp->client->cl_auth); |
| clnt_destroy (kcp->client); |
| kcp->client = NULL; |
| } |
| } |
| |
| if (kcp->client != NULL) |
| { |
| /* if uid has changed, build client handle again */ |
| if (kcp->uid != __geteuid ()) |
| { |
| kcp->uid = __geteuid (); |
| auth_destroy (kcp->client->cl_auth); |
| kcp->client->cl_auth = |
| authunix_create ((char *)"", kcp->uid, 0, 0, NULL); |
| if (kcp->client->cl_auth == NULL) |
| { |
| clnt_destroy (kcp->client); |
| kcp->client = NULL; |
| return ((CLIENT *) NULL); |
| } |
| } |
| /* Change the version number to the new one */ |
| clnt_control (kcp->client, CLSET_VERS, (void *)&vers); |
| return kcp->client; |
| } |
| |
| if ((kcp->client == (CLIENT *) NULL)) |
| /* Use the AF_UNIX transport */ |
| kcp->client = clnt_create ("/var/run/keyservsock", KEY_PROG, vers, "unix"); |
| |
| if (kcp->client == (CLIENT *) NULL) |
| return (CLIENT *) NULL; |
| |
| kcp->uid = __geteuid (); |
| kcp->pid = __getpid (); |
| kcp->client->cl_auth = authunix_create ((char *)"", kcp->uid, 0, 0, NULL); |
| if (kcp->client->cl_auth == NULL) |
| { |
| clnt_destroy (kcp->client); |
| kcp->client = NULL; |
| return (CLIENT *) NULL; |
| } |
| |
| wait_time.tv_sec = TOTAL_TIMEOUT/TOTAL_TRIES; |
| wait_time.tv_usec = 0; |
| clnt_control (kcp->client, CLSET_RETRY_TIMEOUT, |
| (char *)&wait_time); |
| if (clnt_control (kcp->client, CLGET_FD, (char *)&fd)) |
| __fcntl (fd, F_SETFD, FD_CLOEXEC); /* make it "close on exec" */ |
| |
| return kcp->client; |
| } |
| |
| /* returns 0 on failure, 1 on success */ |
| static int |
| internal_function |
| key_call_socket (u_long proc, xdrproc_t xdr_arg, char *arg, |
| xdrproc_t xdr_rslt, char *rslt) |
| { |
| CLIENT *clnt; |
| struct timeval wait_time; |
| int result = 0; |
| |
| __libc_lock_lock (keycall_lock); |
| if ((proc == KEY_ENCRYPT_PK) || (proc == KEY_DECRYPT_PK) || |
| (proc == KEY_NET_GET) || (proc == KEY_NET_PUT) || |
| (proc == KEY_GET_CONV)) |
| clnt = getkeyserv_handle(2); /* talk to version 2 */ |
| else |
| clnt = getkeyserv_handle(1); /* talk to version 1 */ |
| |
| if (clnt != NULL) |
| { |
| wait_time.tv_sec = TOTAL_TIMEOUT; |
| wait_time.tv_usec = 0; |
| |
| if (clnt_call (clnt, proc, xdr_arg, arg, xdr_rslt, rslt, |
| wait_time) == RPC_SUCCESS) |
| result = 1; |
| } |
| |
| __libc_lock_unlock (keycall_lock); |
| |
| return result; |
| } |
| |
| |
| /* returns 0 on failure, 1 on success */ |
| static int |
| internal_function |
| key_call (u_long proc, xdrproc_t xdr_arg, char *arg, |
| xdrproc_t xdr_rslt, char *rslt) |
| { |
| #ifndef SO_PASSCRED |
| static int use_keyenvoy; |
| #endif |
| |
| if (proc == KEY_ENCRYPT_PK && __key_encryptsession_pk_LOCAL) |
| { |
| cryptkeyres *res; |
| res = (*__key_encryptsession_pk_LOCAL) (__geteuid (), arg); |
| *(cryptkeyres *) rslt = *res; |
| return 1; |
| } |
| else if (proc == KEY_DECRYPT_PK && __key_decryptsession_pk_LOCAL) |
| { |
| cryptkeyres *res; |
| res = (*__key_decryptsession_pk_LOCAL) (__geteuid (), arg); |
| *(cryptkeyres *) rslt = *res; |
| return 1; |
| } |
| else if (proc == KEY_GEN && __key_gendes_LOCAL) |
| { |
| des_block *res; |
| res = (*__key_gendes_LOCAL) (__geteuid (), 0); |
| *(des_block *) rslt = *res; |
| return 1; |
| } |
| |
| #ifdef SO_PASSCRED |
| return key_call_socket (proc, xdr_arg, arg, xdr_rslt, rslt); |
| #else |
| if (!use_keyenvoy) |
| { |
| if (key_call_socket (proc, xdr_arg, arg, xdr_rslt, rslt)) |
| return 1; |
| use_keyenvoy = 1; |
| } |
| return key_call_keyenvoy (proc, xdr_arg, arg, xdr_rslt, rslt); |
| #endif |
| } |
| |
| #ifdef _RPC_THREAD_SAFE_ |
| void |
| __rpc_thread_key_cleanup (void) |
| { |
| struct key_call_private *kcp = RPC_THREAD_VARIABLE(key_call_private_s); |
| |
| if (kcp) { |
| if (kcp->client) { |
| if (kcp->client->cl_auth) |
| auth_destroy (kcp->client->cl_auth); |
| clnt_destroy(kcp->client); |
| } |
| free (kcp); |
| } |
| } |
| #endif /* _RPC_THREAD_SAFE_ */ |