From 739da8f2ca86c424be68e2cb88d6d2ec205be8fd Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Wed, 28 May 2008 16:24:46 -0400 Subject: Make ipa_kpasswd listen on each single interface explicitly instead of 0.0.0.0, this allows us to reply to a UDP request by using the right source address to build the kerberos reply. --- ipa-server/ipa-kpasswd/ipa_kpasswd.c | 239 +++++++++++++++++++++++++---------- 1 file changed, 173 insertions(+), 66 deletions(-) diff --git a/ipa-server/ipa-kpasswd/ipa_kpasswd.c b/ipa-server/ipa-kpasswd/ipa_kpasswd.c index 8e83c515..c96d8a73 100644 --- a/ipa-server/ipa-kpasswd/ipa_kpasswd.c +++ b/ipa-server/ipa-kpasswd/ipa_kpasswd.c @@ -41,6 +41,7 @@ #include #include #include +#include #define DEFAULT_KEYTAB "FILE:/var/kerberos/krb5kdc/kpasswd.keytab" #define TMP_TEMPLATE "/var/cache/ipa/kpasswd/krb5_cc.XXXXXX" @@ -61,6 +62,14 @@ struct blacklist { static struct blacklist *global_blacklist = NULL; +struct socklist { + int fd; + int socktype; + int dest_addr_len; + struct sockaddr_storage dest_addr; + struct socklist *next; +}; + int check_blacklist(char *address) { struct blacklist *bl, *prev_bl; @@ -646,6 +655,7 @@ done: } void handle_krb_packets(uint8_t *buf, ssize_t buflen, + struct socklist *sd, struct sockaddr_storage *from, uint8_t **repbuf, ssize_t *replen) { @@ -654,7 +664,7 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen, krb5_keytab keytab; krb5_principal kprincpw; krb5_ticket *ticket; - krb5_address **lkaddr, rkaddr; + krb5_address lkaddr, rkaddr; krb5_data kreq, krep, kenc, kdec; krb5_replay_data replay; krb5_error krb5err; @@ -678,20 +688,31 @@ void handle_krb_packets(uint8_t *buf, ssize_t buflen, kprincpw = NULL; context = NULL; ticket = NULL; - lkaddr = NULL; switch(((struct sockaddr *)from)->sa_family) { case AF_INET: + lkaddr.addrtype = ADDRTYPE_INET; + lkaddr.length = sizeof(((struct sockaddr_in *)&sd->dest_addr)->sin_addr); + lkaddr.contents = (krb5_octet *) &(((struct sockaddr_in *)&sd->dest_addr)->sin_addr); + rkaddr.addrtype = ADDRTYPE_INET; rkaddr.length = sizeof(((struct sockaddr_in *)from)->sin_addr); rkaddr.contents = (krb5_octet *) &(((struct sockaddr_in *)from)->sin_addr); break; case AF_INET6: if (IN6_IS_ADDR_V4MAPPED (&((struct sockaddr_in6 *)from)->sin6_addr)) { + lkaddr.addrtype = ADDRTYPE_INET; + lkaddr.length = 4; + lkaddr.contents = 12 + (krb5_octet *) &(((struct sockaddr_in6 *)&sd->dest_addr)->sin6_addr); + rkaddr.addrtype = ADDRTYPE_INET; rkaddr.length = 4; rkaddr.contents = 12 + (krb5_octet *) &(((struct sockaddr_in6 *)from)->sin6_addr); } else { + lkaddr.addrtype = ADDRTYPE_INET6; + lkaddr.length = sizeof(((struct sockaddr_in6 *)&sd->dest_addr)->sin6_addr); + lkaddr.contents = (krb5_octet *) &(((struct sockaddr_in6 *)&sd->dest_addr)->sin6_addr); + rkaddr.addrtype = ADDRTYPE_INET6; rkaddr.length = sizeof(((struct sockaddr_in6 *)from)->sin6_addr); rkaddr.contents = (krb5_octet *) &(((struct sockaddr_in6 *)from)->sin6_addr); @@ -888,16 +909,7 @@ kpreply: kdec.data[1] = result_err & 0xff; memcpy(&kdec.data[2], result_string, strlen(result_string)); - /* we listen on ANYADDR, use this retrieve the right address */ - krberr = krb5_os_localaddr(context, &lkaddr); - if (krberr) { - result_string = strdup("Failed to retrieve local address"); - syslog(LOG_ERR, "%s: %s", result_string, - krb5_get_error_message(context, krberr)); - goto done; - } - - krberr = krb5_auth_con_setaddrs(context, auth_context, lkaddr[0], NULL); + krberr = krb5_auth_con_setaddrs(context, auth_context, &lkaddr, NULL); if (krberr) { result_string = strdup("Failed to set local address"); syslog(LOG_ERR, "%s: %s", result_string, @@ -971,11 +983,10 @@ done: if (krep.length) free(krep.data); if (ticket) krb5_free_ticket(context, ticket); if (kdec.length) free(kdec.data); - if (lkaddr) krb5_free_addresses(context, lkaddr); if (context) krb5_free_context(context); } -pid_t handle_conn(int fd, int type) +pid_t handle_conn(struct socklist *sd) { int mfd, tcp; pid_t pid; @@ -988,14 +999,15 @@ pid_t handle_conn(int fd, int type) struct sockaddr_storage from; socklen_t fromlen; ssize_t sendret; + int ret; fromlen = sizeof(from); tcp = 0; /* receive request */ - if (type == SOCK_STREAM) { + if (sd->socktype == SOCK_STREAM) { tcp = 1; - mfd = accept(fd, (struct sockaddr *)&from, &fromlen); + mfd = accept(sd->fd, (struct sockaddr *)&from, &fromlen); if (mfd == -1) { syslog(LOG_ERR, "Accept failed with error (%d) %s", errno, strerror(errno)); @@ -1003,7 +1015,7 @@ pid_t handle_conn(int fd, int type) } } else { /* read first to empty the buffer on udp connections */ - reqlen = recvfrom(fd, request, sizeof(request), 0, + reqlen = recvfrom(sd->fd, request, sizeof(request), 0, (struct sockaddr *)&from, &fromlen); if (reqlen <= 0) { syslog(LOG_ERR, "Error receiving request (%d) %s", @@ -1013,9 +1025,13 @@ pid_t handle_conn(int fd, int type) } - (void) getnameinfo((struct sockaddr *)&from, fromlen, + ret = getnameinfo((struct sockaddr *)&from, fromlen, addrto6, INET6_ADDRSTRLEN+1, NULL, 0, NI_NUMERICHOST); + if (ret) { + syslog(LOG_ERR, "Error retrieving host address\n"); + return -1; + } if (debug > 0) { syslog(LOG_ERR, "Connection from %s", addrto6); @@ -1082,16 +1098,16 @@ pid_t handle_conn(int fd, int type) * this information seem to be just redundant, so let's simply * skip it */ if (tcp) { - handle_krb_packets(request+4, reqlen-4, &from, &reply, &replen); + handle_krb_packets(request+4, reqlen-4, sd, &from, &reply, &replen); } else { - handle_krb_packets(request, reqlen, &from, &reply, &replen); + handle_krb_packets(request, reqlen, sd, &from, &reply, &replen); } if (replen) { /* we have something to reply */ if (tcp) { sendret = sendto(mfd, reply, replen, 0, NULL, 0); } else { - sendret = sendto(fd, reply, replen, 0, (struct sockaddr *)&from, fromlen); + sendret = sendto(sd->fd, reply, replen, 0, (struct sockaddr *)&from, fromlen); } if (sendret == -1) { syslog(LOG_ERR, "Error sending reply (%d)", errno); @@ -1101,15 +1117,99 @@ pid_t handle_conn(int fd, int type) exit(0); } -/* TODO: make this IPv6 aware */ +static int create_socket(struct addrinfo *ai, struct socklist **_sds, + struct pollfd **_pfds, int *_nfds) +{ + struct socklist *csd, *tsd; + struct pollfd *pfds; + int nfds; + int ret; + int tru = 1; + + pfds = *_pfds; + nfds = *_nfds; + + csd = calloc(1, sizeof(struct socklist)); + if (csd == NULL) { + syslog(LOG_ERR, "Out of memory, can't create socklist\n"); + return 1; + } + csd->socktype = ai->ai_socktype; + csd->dest_addr_len = ai->ai_addrlen; + memcpy(&csd->dest_addr, ai->ai_addr, ai->ai_addrlen); + + csd->fd = socket(csd->dest_addr.ss_family, csd->socktype, 0); + if (csd->fd == -1) { + syslog(LOG_ERR, "Unable to create socket (%s)", + strerror(errno)); + goto errout; + } + ret = setsockopt(csd->fd, SOL_SOCKET, SO_REUSEADDR, + (void *)&tru, sizeof(tru)); + + ret = bind(csd->fd, (struct sockaddr *)&csd->dest_addr, csd->dest_addr_len); + if (ret) { + if (errno != EADDRINUSE) { + syslog(LOG_ERR, "Unable to bind to socket"); + close(csd->fd); + goto errout; + } + /* if EADDRINUSE it means we are on a machine + * with a dual ipv4/ipv6 stack that does not + * allow to bind on both at the same time as the + * ipv6 bind already allows connections on ipv4 + * Just ignore */ + close(csd->fd); + free(csd); + return 0; + } + + if (csd->socktype == SOCK_STREAM) { + ret = listen(csd->fd, SOMAXCONN); + if (ret) { + syslog(LOG_ERR, "Unable to listen to TCP socket (%s)", + strerror(errno)); + close(csd->fd); + goto errout; + } + } + + pfds = realloc(pfds, sizeof(struct pollfd) * (nfds +1)); + if (pfds == NULL) { + syslog(LOG_ERR, "Out of memory, can't alloc pollfd array\n"); + close(csd->fd); + goto errout; + } + pfds[nfds].events = POLLIN; + pfds[nfds].fd = csd->fd; + nfds++; + + if (*_sds) { + for (tsd = *_sds; tsd->next; tsd = tsd->next) /* skip */ ; + tsd->next = csd; + } else { + *_sds = csd; + } + + *_pfds = pfds; + *_nfds = nfds; + + return 0; + +errout: + free(csd); + return 1; +} int main(int argc, char *argv[]) { pid_t pid; + struct ifaddrs *ifa, *tifa; struct addrinfo *ai, *tai; struct addrinfo hints; - struct pollfd pfds[4]; - int pfdtype[4]; + char host[NI_MAXHOST]; + struct socklist *sds, *csd; + struct pollfd *pfds; int nfds; int ret; char *env; @@ -1167,13 +1267,9 @@ int main(int argc, char *argv[]) debug = strtol(env, NULL, 0); } - /* set hints */ - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; - - ret = getaddrinfo (NULL, "kpasswd", &hints, &ai); + ret = getifaddrs(&ifa); if (ret) { - syslog(LOG_ERR, "getaddrinfo failed: %s", gai_strerror(ret)); + syslog(LOG_ERR, "getifaddrs failed: %s", gai_strerror(ret)); exit(1); } @@ -1187,54 +1283,61 @@ int main(int argc, char *argv[]) fail = 0; } if (fail) { - syslog(LOG_ERR,"Couldn't create pid file %s: %s", + syslog(LOG_ERR, "Couldn't create pid file %s: %s", pid_file, strerror(errno)); exit(1); } - tai = ai; nfds = 0; - /* we can have a maximum of 4 sockets (IPv4/IPv6(TCP/UDP)) */ - for (tai = ai; tai != NULL && nfds < 4; tai = tai->ai_next) { - int tru = 1; - - pfds[nfds].fd = socket( tai->ai_family, - tai->ai_socktype, - tai->ai_protocol); - if (pfds[nfds].fd == -1) { - syslog(LOG_ERR, "Unable to create socket (%d)", nfds); - exit(1); + pfds = NULL; + sds = NULL; + + for (tifa = ifa; tifa; tifa = tifa->ifa_next) { + + if (tifa->ifa_addr->sa_family != PF_INET && + tifa->ifa_addr->sa_family != AF_INET6) { + /* not interesting for us */ + continue; + } + + ret = getnameinfo(tifa->ifa_addr, sizeof(struct sockaddr_storage), + host, sizeof(host), NULL, 0, NI_NUMERICHOST); + if (ret) { + syslog(LOG_ERR, "Error converting address (%d)", + gai_strerror(ret)); + continue; + } else { + syslog(LOG_INFO, "Setting up socket for [%s]", host); } - pfds[nfds].events = POLLIN; - ret = setsockopt(pfds[nfds].fd, SOL_SOCKET, SO_REUSEADDR, - (void *)&tru, sizeof(tru)); + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = AF_UNSPEC; - ret = bind(pfds[nfds].fd, tai->ai_addr, tai->ai_addrlen); + /* this shoud return 2 entries, one for UDP and one for TCP */ + ret = getaddrinfo(host, "kpasswd", &hints, &ai); if (ret) { - if (errno != EADDRINUSE) { - syslog(LOG_ERR, "Unable to bind to socket"); - exit(1); + syslog(LOG_ERR, "Error getting address info (%d) for [%s]", + gai_strerror(ret), host); + continue; + } + + for (tai = ai; tai; tai = tai->ai_next) { + char *socktype = (tai->ai_socktype==SOCK_STREAM)?"TCP":"UDP"; + ret = create_socket(tai, &sds, &pfds, &nfds); + if (ret) { + syslog(LOG_ERR, + "Failed to set up %s socket for [%s]", + socktype, host); } - /* if EADDRINUSE it means we are on a machine - * with a dual ipv4/ipv6 stack that does not - * allow to bind on both at the same time as the - * ipv6 bind already allows connections on ipv4 - * Just ignore */ - close(pfds[nfds].fd); - } else { - if (tai->ai_socktype == SOCK_STREAM) { - ret = listen(pfds[nfds].fd, SOMAXCONN); - if (ret) { - syslog(LOG_ERR, "Unable to listen to socket"); - exit(1); - } - } - pfdtype[nfds] = tai->ai_socktype; - nfds++; } } + if (nfds == 0) { + syslog(LOG_ERR, "Failed to setup any socket. Aborting"); + exit(1); + } + /* now that sockets are set up, enter the poll loop */ while (1) { @@ -1256,7 +1359,11 @@ int main(int argc, char *argv[]) default: for (i = 0; i < nfds; i++) { if (pfds[i].revents & POLLIN) { - handle_conn(pfds[i].fd, pfdtype[i]); + for (csd = sds; csd; csd = csd->next) { + if (csd->fd == pfds[i].fd) { + handle_conn(csd); + } + } } } } -- cgit