/* * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Copyright (c) 1983 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms and that any documentation, * advertising materials, and other materials related to such * distribution and use acknowledge that the software was developed * by the University of California, Berkeley. The name of the * University may not be used to endorse or promote products derived * from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* derived from @(#)rcmd.c 5.17 (Berkeley) 6/27/88 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char *default_service = "host"; #define KCMD_BUFSIZ 102400 #define KCMD_KEYUSAGE 1026 static char storage[KCMD_BUFSIZ]; static int nstored = 0; static int MAXSIZE = (KCMD_BUFSIZ + 8); static char *store_ptr = storage; static krb5_data desinbuf, desoutbuf; static boolean_t encrypt_flag = B_FALSE; static krb5_context kcmd_context; /* XXX Overloaded: use_ivecs!=0 -> new protocol, inband signalling, etc. */ static boolean_t use_ivecs = B_FALSE; static krb5_data encivec_i[2], encivec_o[2]; static krb5_keyusage enc_keyusage_i[2], enc_keyusage_o[2]; static krb5_enctype final_enctype; static krb5_keyblock *skey; /* ARGSUSED */ int kcmd(int *sock, char **ahost, ushort_t rport, char *locuser, char *remuser, char *cmd, int *fd2p, char *service, char *realm, krb5_context bsd_context, krb5_auth_context *authconp, krb5_creds **cred, krb5_int32 *seqno, krb5_int32 *server_seqno, krb5_flags authopts, int anyport, enum kcmd_proto *protonump) { int s = -1; sigset_t oldmask, urgmask; struct sockaddr_in sin; struct sockaddr_storage from; krb5_creds *get_cred = NULL; krb5_creds *ret_cred = NULL; char c; struct hostent *hp; int rc; char *host_save = NULL; krb5_error_code status; krb5_ap_rep_enc_part *rep_ret; krb5_error *error = 0; krb5_ccache cc; krb5_data outbuf; krb5_flags options = authopts; krb5_auth_context auth_context = NULL; char *cksumbuf; krb5_data cksumdat; int bsize = 0; char *kcmd_version; enum kcmd_proto protonum = *protonump; bsize = strlen(cmd) + strlen(remuser) + 64; if ((cksumbuf = malloc(bsize)) == 0) { (void) fprintf(stderr, gettext("Unable to allocate" " memory for checksum buffer.\n")); return (-1); } (void) snprintf(cksumbuf, bsize, "%u:", ntohs(rport)); if (strlcat(cksumbuf, cmd, bsize) >= bsize) { (void) fprintf(stderr, gettext("cmd buffer too long.\n")); free(cksumbuf); return (-1); } if (strlcat(cksumbuf, remuser, bsize) >= bsize) { (void) fprintf(stderr, gettext("remuser too long.\n")); free(cksumbuf); return (-1); } cksumdat.data = cksumbuf; cksumdat.length = strlen(cksumbuf); hp = gethostbyname(*ahost); if (hp == 0) { (void) fprintf(stderr, gettext("%s: unknown host\n"), *ahost); return (-1); } if ((host_save = (char *)strdup(hp->h_name)) == NULL) { (void) fprintf(stderr, gettext("kcmd: no memory\n")); return (-1); } /* If no service is given set to the default service */ if (!service) service = default_service; if (!(get_cred = (krb5_creds *)calloc(1, sizeof (krb5_creds)))) { (void) fprintf(stderr, gettext("kcmd: no memory\n")); return (-1); } (void) sigemptyset(&urgmask); (void) sigaddset(&urgmask, SIGURG); (void) sigprocmask(SIG_BLOCK, &urgmask, &oldmask); status = krb5_sname_to_principal(bsd_context, host_save, service, KRB5_NT_SRV_HST, &get_cred->server); if (status) { (void) fprintf(stderr, gettext("kcmd: " "krb5_sname_to_principal failed: %s\n"), error_message(status)); status = -1; goto bad; } if (realm && *realm) { (void) krb5_xfree( krb5_princ_realm(bsd_context, get_cred->server)->data); krb5_princ_set_realm_length(bsd_context, get_cred->server, strlen(realm)); krb5_princ_set_realm_data(bsd_context, get_cred->server, strdup(realm)); } s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { perror(gettext("Error creating socket")); status = -1; goto bad; } /* * Kerberos only supports IPv4 addresses for now. */ if (hp->h_addrtype == AF_INET) { sin.sin_family = hp->h_addrtype; (void) memcpy((void *)&sin.sin_addr, hp->h_addr, hp->h_length); sin.sin_port = rport; } else { syslog(LOG_ERR, "Address type %d not supported for " "Kerberos", hp->h_addrtype); status = -1; goto bad; } if (connect(s, (struct sockaddr *)&sin, sizeof (sin)) < 0) { perror(host_save); status = -1; goto bad; } if (fd2p == 0) { (void) write(s, "", 1); } else { char num[16]; int s2; int s3; struct sockaddr_storage sname; struct sockaddr_in *sp; int len = sizeof (struct sockaddr_storage); s2 = socket(AF_INET, SOCK_STREAM, 0); if (s2 < 0) { status = -1; goto bad; } (void) memset((char *)&sin, 0, sizeof (sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = 0; if (bind(s2, (struct sockaddr *)&sin, sizeof (sin)) < 0) { perror(gettext("error binding socket")); (void) close(s2); status = -1; goto bad; } if (getsockname(s2, (struct sockaddr *)&sname, &len) < 0) { perror(gettext("getsockname error")); (void) close(s2); status = -1; goto bad; } sp = (struct sockaddr_in *)&sname; (void) listen(s2, 1); (void) snprintf(num, sizeof (num), "%d", htons((ushort_t)sp->sin_port)); if (write(s, num, strlen(num)+1) != strlen(num)+1) { perror(gettext("write: error setting up stderr")); (void) close(s2); status = -1; goto bad; } s3 = accept(s2, (struct sockaddr *)&from, &len); (void) close(s2); if (s3 < 0) { perror(gettext("accept")); status = -1; goto bad; } *fd2p = s3; if (SOCK_FAMILY(from) == AF_INET) { if (!anyport && SOCK_PORT(from) >= IPPORT_RESERVED) { (void) fprintf(stderr, gettext("socket: protocol " "failure in circuit setup.\n")); status = -1; goto bad2; } } else { (void) fprintf(stderr, gettext("Kerberos does not support " "address type %d\n"), SOCK_FAMILY(from)); status = -1; goto bad2; } } if (status = krb5_cc_default(bsd_context, &cc)) goto bad2; status = krb5_cc_get_principal(bsd_context, cc, &get_cred->client); if (status) { (void) krb5_cc_close(bsd_context, cc); goto bad2; } /* Get ticket from credentials cache or kdc */ status = krb5_get_credentials(bsd_context, 0, cc, get_cred, &ret_cred); (void) krb5_cc_close(bsd_context, cc); if (status) goto bad2; /* Reset internal flags; these should not be sent. */ authopts &= (~OPTS_FORWARD_CREDS); authopts &= (~OPTS_FORWARDABLE_CREDS); if ((status = krb5_auth_con_init(bsd_context, &auth_context))) goto bad2; if ((status = krb5_auth_con_setflags(bsd_context, auth_context, KRB5_AUTH_CONTEXT_RET_TIME))) goto bad2; /* Only need local address for mk_cred() to send to krlogind */ if ((status = krb5_auth_con_genaddrs(bsd_context, auth_context, s, KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR))) goto bad2; if (protonum == KCMD_PROTOCOL_COMPAT_HACK) { krb5_boolean is_des; status = krb5_c_enctype_compare(bsd_context, ENCTYPE_DES_CBC_CRC, ret_cred->keyblock.enctype, &is_des); if (status) goto bad2; protonum = is_des ? KCMD_OLD_PROTOCOL : KCMD_NEW_PROTOCOL; } switch (protonum) { case KCMD_NEW_PROTOCOL: authopts |= AP_OPTS_USE_SUBKEY; kcmd_version = "KCMDV0.2"; break; case KCMD_OLD_PROTOCOL: kcmd_version = "KCMDV0.1"; break; default: status = -1; goto bad2; } /* * Call the Kerberos library routine to obtain an authenticator, * pass it over the socket to the server, and obtain mutual * authentication. */ status = krb5_sendauth(bsd_context, &auth_context, (krb5_pointer) &s, kcmd_version, ret_cred->client, ret_cred->server, authopts, &cksumdat, ret_cred, 0, &error, &rep_ret, NULL); krb5_xfree(cksumdat.data); if (status) { (void) fprintf(stderr, gettext("Couldn't authenticate" " to server: %s\n"), error_message(status)); if (error) { (void) fprintf(stderr, gettext("Server returned error" " code %d (%s)\n"), error->error, error_message(ERROR_TABLE_BASE_krb5 + error->error)); if (error->text.length) (void) fprintf(stderr, gettext("Error text" " sent from server: %s\n"), error->text.data); } if (error) { krb5_free_error(bsd_context, error); error = 0; } goto bad2; } if (rep_ret && server_seqno) { *server_seqno = rep_ret->seq_number; krb5_free_ap_rep_enc_part(bsd_context, rep_ret); } (void) write(s, remuser, strlen(remuser)+1); (void) write(s, cmd, strlen(cmd)+1); if (locuser) (void) write(s, locuser, strlen(locuser)+1); else (void) write(s, "", 1); if (options & OPTS_FORWARD_CREDS) { /* Forward credentials */ if (status = krb5_fwd_tgt_creds(bsd_context, auth_context, host_save, ret_cred->client, ret_cred->server, 0, options & OPTS_FORWARDABLE_CREDS, &outbuf)) { (void) fprintf(stderr, gettext("kcmd: Error getting" " forwarded creds\n")); goto bad2; } /* Send forwarded credentials */ if (status = krb5_write_message(bsd_context, (krb5_pointer)&s, &outbuf)) goto bad2; } else { /* Dummy write to signal no forwarding */ outbuf.length = 0; if (status = krb5_write_message(bsd_context, (krb5_pointer)&s, &outbuf)) goto bad2; } if ((rc = read(s, &c, 1)) != 1) { if (rc == -1) { perror(*ahost); } else { (void) fprintf(stderr, gettext("kcmd: bad connection " "with remote host\n")); } status = -1; goto bad2; } if (c != 0) { while (read(s, &c, 1) == 1) { (void) write(2, &c, 1); if (c == '\n') break; } status = -1; goto bad2; } (void) sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)0); *sock = s; /* pass back credentials if wanted */ if (cred) krb5_copy_creds(bsd_context, ret_cred, cred); krb5_free_creds(bsd_context, ret_cred); /* * Initialize *authconp to auth_context, so * that the clients can make use of it */ *authconp = auth_context; return (0); bad2: if (fd2p != NULL) (void) close(*fd2p); bad: if (s > 0) (void) close(s); if (get_cred) krb5_free_creds(bsd_context, get_cred); if (ret_cred) krb5_free_creds(bsd_context, ret_cred); if (host_save) free(host_save); (void) sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)0); return (status); } /* * Strsave was a routine in the version 4 krb library: we put it here * for compatablilty with version 5 krb library, since kcmd.o is linked * into all programs. */ char * strsave(char *sp) { char *ret; if ((ret = (char *)strdup(sp)) == NULL) { (void) fprintf(stderr, gettext("no memory for saving args\n")); exit(1); } return (ret); } /* * Decode, decrypt and store the forwarded creds in the local ccache. */ krb5_error_code rd_and_store_for_creds(krb5_context context, krb5_auth_context auth_context, krb5_data *inbuf, krb5_ticket *ticket, char *lusername, krb5_ccache *ccache) { krb5_creds ** creds; krb5_error_code retval; char ccname[64]; struct passwd *pwd; uid_t uid; *ccache = NULL; if (!(pwd = (struct passwd *)getpwnam(lusername))) return (ENOENT); uid = getuid(); if (seteuid(pwd->pw_uid)) return (-1); if ((retval = krb5_rd_cred(context, auth_context, inbuf, &creds, NULL)) != 0) return (retval); (void) snprintf(ccname, sizeof (ccname), "FILE:/tmp/krb5cc_%ld", pwd->pw_uid); if ((retval = krb5_cc_resolve(context, ccname, ccache)) != 0) goto cleanup; if ((retval = krb5_cc_initialize(context, *ccache, ticket->enc_part2->client)) != 0) goto cleanup; if ((retval = krb5_cc_store_cred(context, *ccache, *creds)) != 0) goto cleanup; if ((retval = krb5_cc_close(context, *ccache)) != 0) goto cleanup; cleanup: (void) seteuid(uid); krb5_free_creds(context, *creds); return (retval); } /* * This routine is to initialize the desinbuf, desoutbuf and the session key * structures to carry out desread()'s and deswrite()'s successfully */ void init_encrypt(int enc, krb5_context ctxt, enum kcmd_proto protonum, krb5_data *inbuf, krb5_data *outbuf, int amclient, krb5_encrypt_block *block) { krb5_error_code statuscode; size_t blocksize; int i; krb5_error_code ret; kcmd_context = ctxt; if (enc > 0) { desinbuf.data = inbuf->data; desoutbuf.data = outbuf->data + 4; desinbuf.length = inbuf->length; desoutbuf.length = outbuf->length + 4; encrypt_flag = B_TRUE; } else { encrypt_flag = B_FALSE; return; } skey = block->key; final_enctype = skey->enctype; enc_keyusage_i[0] = KCMD_KEYUSAGE; enc_keyusage_i[1] = KCMD_KEYUSAGE; enc_keyusage_o[0] = KCMD_KEYUSAGE; enc_keyusage_o[1] = KCMD_KEYUSAGE; if (protonum == KCMD_OLD_PROTOCOL) { use_ivecs = B_FALSE; return; } use_ivecs = B_TRUE; switch (skey->enctype) { /* * For the DES-based enctypes and the 3DES enctype we * want to use a non-zero IV because that's what we did. * In the future we use different keyusage for each * channel and direction and a fresh cipher state. */ case ENCTYPE_DES_CBC_CRC: case ENCTYPE_DES_CBC_MD4: case ENCTYPE_DES_CBC_MD5: case ENCTYPE_DES3_CBC_SHA1: statuscode = krb5_c_block_size(kcmd_context, final_enctype, &blocksize); if (statuscode) { /* XXX what do I do? */ abort(); } encivec_i[0].length = encivec_i[1].length = encivec_o[0].length = encivec_o[1].length = blocksize; if ((encivec_i[0].data = malloc(encivec_i[0].length * 4)) == NULL) { /* XXX what do I do? */ abort(); } encivec_i[1].data = encivec_i[0].data + encivec_i[0].length; encivec_o[0].data = encivec_i[1].data + encivec_i[0].length; encivec_o[1].data = encivec_o[0].data + encivec_i[0].length; /* is there a better way to initialize this? */ (void) memset(encivec_i[0].data, amclient, blocksize); (void) memset(encivec_o[0].data, 1 - amclient, blocksize); (void) memset(encivec_i[1].data, 2 | amclient, blocksize); (void) memset(encivec_o[1].data, 2 | (1 - amclient), blocksize); break; default: if (amclient) { enc_keyusage_i[0] = 1028; enc_keyusage_i[1] = 1030; enc_keyusage_o[0] = 1032; enc_keyusage_o[1] = 1034; } else { /* amclient */ enc_keyusage_i[0] = 1032; enc_keyusage_i[1] = 1034; enc_keyusage_o[0] = 1028; enc_keyusage_o[1] = 1030; } for (i = 0; i < 2; i++) { ret = krb5_c_init_state(ctxt, skey, enc_keyusage_i[i], &encivec_i[i]); if (ret) goto fail; ret = krb5_c_init_state(ctxt, skey, enc_keyusage_o[i], &encivec_o[i]); if (ret) goto fail; } break; } return; fail: abort(); } int desread(int fd, char *buf, int len, int secondary) { int nreturned = 0; long net_len, rd_len; int cc; size_t ret = 0; unsigned char len_buf[4]; krb5_enc_data inputd; krb5_data outputd; if (!encrypt_flag) return (read(fd, buf, len)); /* * If there is stored data from a previous read, * put it into the output buffer and return it now. */ if (nstored >= len) { (void) memcpy(buf, store_ptr, len); store_ptr += len; nstored -= len; return (len); } else if (nstored) { (void) memcpy(buf, store_ptr, nstored); nreturned += nstored; buf += nstored; len -= nstored; nstored = 0; } if ((cc = krb5_net_read(kcmd_context, fd, (char *)len_buf, 4)) != 4) { if ((cc < 0) && ((errno == EWOULDBLOCK) || (errno == EAGAIN))) return (cc); /* XXX can't read enough, pipe must have closed */ return (0); } rd_len = ((len_buf[0] << 24) | (len_buf[1] << 16) | (len_buf[2] << 8) | len_buf[3]); if (krb5_c_encrypt_length(kcmd_context, final_enctype, use_ivecs ? (size_t)rd_len + 4 : (size_t)rd_len, &ret)) net_len = ((size_t)-1); else net_len = ret; if ((net_len <= 0) || (net_len > desinbuf.length)) { /* * preposterous length; assume out-of-sync; only recourse * is to close connection, so return 0 */ (void) fprintf(stderr, gettext("Read size problem.\n")); return (0); } if ((cc = krb5_net_read(kcmd_context, fd, desinbuf.data, net_len)) != net_len) { /* pipe must have closed, return 0 */ (void) fprintf(stderr, gettext("Read error: length received %d " "!= expected %d.\n"), cc, net_len); return (0); } /* * Decrypt information */ inputd.enctype = ENCTYPE_UNKNOWN; inputd.ciphertext.length = net_len; inputd.ciphertext.data = (krb5_pointer)desinbuf.data; outputd.length = sizeof (storage); outputd.data = (krb5_pointer)storage; /* * data is decrypted into the "storage" buffer, which * had better be large enough! */ cc = krb5_c_decrypt(kcmd_context, skey, enc_keyusage_i[secondary], use_ivecs ? encivec_i + secondary : 0, &inputd, &outputd); if (cc) { (void) fprintf(stderr, gettext("Cannot decrypt data " "from network\n")); return (0); } store_ptr = storage; nstored = rd_len; if (use_ivecs == B_TRUE) { int rd_len2; rd_len2 = storage[0] & 0xff; rd_len2 <<= 8; rd_len2 |= storage[1] & 0xff; rd_len2 <<= 8; rd_len2 |= storage[2] & 0xff; rd_len2 <<= 8; rd_len2 |= storage[3] & 0xff; if (rd_len2 != rd_len) { /* cleartext length trashed? */ errno = EIO; return (-1); } store_ptr += 4; } /* * Copy only as much data as the input buffer will allow. * The rest is kept in the 'storage' pointer for the next * read. */ if (nstored > len) { (void) memcpy(buf, store_ptr, len); nreturned += len; store_ptr += len; nstored -= len; } else { (void) memcpy(buf, store_ptr, nstored); nreturned += nstored; nstored = 0; } return (nreturned); } int deswrite(int fd, char *buf, int len, int secondary) { int cc; size_t ret = 0; krb5_data inputd; krb5_enc_data outputd; char tmpbuf[KCMD_BUFSIZ + 8]; char encrbuf[KCMD_BUFSIZ + 8]; unsigned char *len_buf = (unsigned char *)tmpbuf; if (!encrypt_flag) return (write(fd, buf, len)); if (use_ivecs == B_TRUE) { unsigned char *lenbuf2 = (unsigned char *)tmpbuf; if (len + 4 > sizeof (tmpbuf)) abort(); lenbuf2[0] = (len & 0xff000000) >> 24; lenbuf2[1] = (len & 0xff0000) >> 16; lenbuf2[2] = (len & 0xff00) >> 8; lenbuf2[3] = (len & 0xff); (void) memcpy(tmpbuf + 4, buf, len); inputd.data = (krb5_pointer)tmpbuf; inputd.length = len + 4; } else { inputd.data = (krb5_pointer)buf; inputd.length = len; } desoutbuf.data = encrbuf; if (krb5_c_encrypt_length(kcmd_context, final_enctype, use_ivecs ? (size_t)len + 4 : (size_t)len, &ret)) { desoutbuf.length = ((size_t)-1); goto err; } else { desoutbuf.length = ret; } if (desoutbuf.length > MAXSIZE) { (void) fprintf(stderr, gettext("Write size problem.\n")); return (-1); } /* * Encrypt information */ outputd.ciphertext.length = desoutbuf.length; outputd.ciphertext.data = (krb5_pointer)desoutbuf.data; cc = krb5_c_encrypt(kcmd_context, skey, enc_keyusage_o[secondary], use_ivecs ? encivec_o + secondary : 0, &inputd, &outputd); if (cc) { err: (void) fprintf(stderr, gettext("Write encrypt problem.\n")); return (-1); } len_buf[0] = (len & 0xff000000) >> 24; len_buf[1] = (len & 0xff0000) >> 16; len_buf[2] = (len & 0xff00) >> 8; len_buf[3] = (len & 0xff); (void) write(fd, len_buf, 4); if (write(fd, desoutbuf.data, desoutbuf.length) != desoutbuf.length) { (void) fprintf(stderr, gettext("Could not write " "out all data.\n")); return (-1); } else { return (len); } }