int tac_add_server(struct tac_handle *h, const char *host, int port, const char *secret, int timeout, int flags) { struct tac_server *srvp; if (h->num_servers >= MAXSERVERS) { generr(h, "Too many TACACS+ servers specified"); return -1; } srvp = &h->servers[h->num_servers]; memset(&srvp->addr, 0, sizeof srvp->addr); srvp->addr.sin_len = sizeof srvp->addr; srvp->addr.sin_family = AF_INET; if (!inet_aton(host, &srvp->addr.sin_addr)) { struct hostent *hent; if ((hent = gethostbyname(host)) == NULL) { generr(h, "%s: host not found", host); return -1; } memcpy(&srvp->addr.sin_addr, hent->h_addr, sizeof srvp->addr.sin_addr); } srvp->addr.sin_port = htons(port != 0 ? port : TACPLUS_PORT); if ((srvp->secret = xstrdup(h, secret)) == NULL) return -1; srvp->timeout = timeout; srvp->flags = flags; h->num_servers++; return 0; }
/* * Append some optional data to the current request, and store its * length into the 16-bit field (network byte order) referenced by * "fld". Returns 0 on success, or -1 on failure. * * This function also frees the "cs" string data and initializes it * for the next time. */ static int add_str_16(struct tac_handle *h, u_int16_t *fld, struct clnt_str *cs) { size_t len; len = cs->len; if (cs->data == NULL) len = 0; if (len != 0) { int offset; if (len > 0xffff) { generr(h, "Field too long"); return -1; } offset = ntohl(h->request.length); if (offset + len > BODYSIZE) { generr(h, "Message too long"); return -1; } memcpy(h->request.u.body + offset, cs->data, len); h->request.length = htonl(offset + len); } *fld = htons(len); free_str(cs); return 0; }
int rad_send_response(struct rad_handle *h) { int n; if (h->type != RADIUS_SERVER) { generr(h, "denied function call"); return (-1); } /* Fill in the length field in the message */ h->out[POS_LENGTH] = h->out_len >> 8; h->out[POS_LENGTH+1] = h->out_len; insert_message_authenticator(h, (h->in[POS_CODE] == RAD_ACCESS_REQUEST) ? 1 : 0); insert_request_authenticator(h, 1); /* Send the request */ n = sendto(h->fd, h->out, h->out_len, 0, (const struct sockaddr *)&h->servers[h->srv].addr, sizeof h->servers[h->srv].addr); if (n != h->out_len) { if (n == -1) generr(h, "sendto: %s", strerror(errno)); else generr(h, "sendto: short write"); return -1; } return 0; }
int rad_add_server_ex(struct rad_handle *h, const char *host, int port, const char *secret, int timeout, int tries, int dead_time, struct in_addr *bindto) { struct rad_server *srvp; if (h->num_servers >= MAXSERVERS) { generr(h, "Too many RADIUS servers specified"); return -1; } srvp = &h->servers[h->num_servers]; memset(&srvp->addr, 0, sizeof srvp->addr); srvp->addr.sin_len = sizeof srvp->addr; srvp->addr.sin_family = AF_INET; if (!inet_aton(host, &srvp->addr.sin_addr)) { struct hostent *hent; if ((hent = gethostbyname(host)) == NULL) { generr(h, "%s: host not found", host); return -1; } memcpy(&srvp->addr.sin_addr, hent->h_addr, sizeof srvp->addr.sin_addr); } if (port != 0) srvp->addr.sin_port = htons((u_short)port); else { struct servent *sent; if (h->type == RADIUS_AUTH) srvp->addr.sin_port = (sent = getservbyname("radius", "udp")) != NULL ? sent->s_port : htons(RADIUS_PORT); else srvp->addr.sin_port = (sent = getservbyname("radacct", "udp")) != NULL ? sent->s_port : htons(RADACCT_PORT); } if ((srvp->secret = strdup(secret)) == NULL) { generr(h, "Out of memory"); return -1; } srvp->timeout = timeout; srvp->max_tries = tries; srvp->num_tries = 0; srvp->is_dead = 0; srvp->dead_time = dead_time; srvp->next_probe = 0; srvp->bindto = bindto->s_addr; h->num_servers++; return 0; }
/* * Receive a response from the server and decrypt it. Returns 0 on * success, or -1 on failure. */ static int recv_msg(struct tac_handle *h) { struct timeval deadline; struct tac_msg *msg; u_int32_t len; msg = &h->response; gettimeofday(&deadline, NULL); deadline.tv_sec += h->servers[h->cur_server].timeout; /* Read the message header and make sure it is reasonable. */ if (read_timed(h, msg, HDRSIZE, &deadline) == -1) return -1; if (memcmp(msg->session_id, h->request.session_id, sizeof msg->session_id) != 0) { generr(h, "Invalid session ID in received message"); return -1; } if (msg->type != h->request.type) { generr(h, "Invalid type in received message" " (got %u, expected %u)", msg->type, h->request.type); return -1; } len = ntohl(msg->length); if (len > BODYSIZE) { generr(h, "Received message too large (%u > %u)", len, BODYSIZE); return -1; } if (msg->seq_no != ++h->last_seq_no) { generr(h, "Invalid sequence number in received message" " (got %u, expected %u)", msg->seq_no, h->last_seq_no); return -1; } /* Read the message body. */ if (read_timed(h, msg->u.body, len, &deadline) == -1) return -1; /* Decrypt it. */ crypt_msg(h, msg); /* * Turn off single-connection mode if the server isn't amenable * to it. */ if (!(msg->flags & TAC_SINGLE_CONNECT)) h->single_connect = 0; return 0; }
static int read_timed(struct tac_handle *h, void *buf, size_t len, const struct timeval *deadline) { char *ptr; ptr = (char *)buf; while (len > 0) { int n; n = read(h->fd, ptr, len); if (n == -1) { struct timeval tv; int nfds; if (errno != EAGAIN) { generr(h, "Network read error: %s", strerror(errno)); return -1; } /* Wait until we can read more data. */ gettimeofday(&tv, NULL); timersub(deadline, &tv, &tv); if (tv.tv_sec >= 0) { fd_set rfds; FD_ZERO(&rfds); FD_SET(h->fd, &rfds); nfds = select(h->fd + 1, &rfds, NULL, NULL, &tv); if (nfds == -1) { generr(h, "select: %s", strerror(errno)); return -1; } } else nfds = 0; if (nfds == 0) { generr(h, "Network read timed out"); return -1; } } else if (n == 0) { generr(h, "unexpected EOF from server"); return -1; } else { ptr += n; len -= n; } } return 0; }
static int establish_connection(struct tac_handle *h) { int i; if (h->fd >= 0) /* Already connected. */ return 0; if (h->num_servers == 0) { generr(h, "No TACACS+ servers specified"); return -1; } /* * Try the servers round-robin. We begin with the one that * worked for us the last time. That way, once we find a good * server, we won't waste any more time trying the bad ones. */ for (i = 0; i < h->num_servers; i++) { if (conn_server(h) == 0) { h->single_connect = (h->servers[h->cur_server].flags & TAC_SRVR_SINGLE_CONNECT) != 0; return 0; } if (++h->cur_server >= h->num_servers) /* Wrap around */ h->cur_server = 0; } /* Just return whatever error was last reported by conn_server(). */ return -1; }
static int put_password_attr(struct rad_handle *h, int type, const void *value, size_t len) { int padded_len; int pad_len; if (h->pass_pos != 0) { generr(h, "Multiple User-Password attributes specified"); return -1; } if (len > PASSSIZE) len = PASSSIZE; padded_len = len == 0 ? 16 : (len+15) & ~0xf; pad_len = padded_len - len; /* * Put in a place-holder attribute containing all zeros, and * remember where it is so we can fill it in later. */ clear_password(h); put_raw_attr(h, type, h->pass, padded_len); h->pass_pos = h->out_len - padded_len; /* Save the cleartext password, padded as necessary */ memcpy(h->pass, value, len); h->pass_len = len; memset(h->pass + len, 0, pad_len); return 0; }
static int put_raw_attr(struct rad_handle *h, int type, const void *value, size_t len) { if (len > 253) { generr(h, "Attribute too long"); return -1; } if (h->out_len + 2 + len > MSGSIZE) { generr(h, "Maximum message length exceeded"); return -1; } h->out[h->out_len++] = type; h->out[h->out_len++] = len + 2; memcpy(&h->out[h->out_len], value, len); h->out_len += len; return 0; }
static int get_srvr_str(struct tac_handle *h, const char *field, struct srvr_str *ss, size_t len) { if (h->srvr_pos + len > ntohl(h->response.length)) { generr(h, "Invalid length field in %s response from server " "(%lu > %lu)", field, (u_long)(h->srvr_pos + len), (u_long)ntohl(h->response.length)); return -1; } ss->data = len != 0 ? h->response.u.body + h->srvr_pos : NULL; ss->len = len; h->srvr_pos += len; return 0; }
/* * Verify that we are exactly at the end of the response message. * Returns 0 on success, -1 on failure. */ static int get_srvr_end(struct tac_handle *h) { int len; len = ntohl(h->response.length); if (h->srvr_pos != len) { generr(h, "Invalid length field in response " "from server: end expected at %u, response length %u", h->srvr_pos, len); return -1; } return 0; }
int rad_create_request(struct rad_handle *h, int code) { int i; if (h->type == RADIUS_SERVER) { generr(h, "denied function call"); return (-1); } if (h->num_servers == 0) { generr(h, "No RADIUS servers specified"); return (-1); } h->out[POS_CODE] = code; h->out[POS_IDENT] = ++h->ident; if (code == RAD_ACCESS_REQUEST) { /* Create a random authenticator */ for (i = 0; i < LEN_AUTH; i += 2) { long r; r = random(); h->out[POS_AUTH+i] = (u_char)r; h->out[POS_AUTH+i+1] = (u_char)(r >> 8); } } else
int rad_receive_request(struct rad_handle *h) { struct sockaddr_in from; socklen_t fromlen; int n; if (h->type != RADIUS_SERVER) { generr(h, "denied function call"); return (-1); } h->srv = -1; fromlen = sizeof(from); h->in_len = recvfrom(h->fd, h->in, MSGSIZE, MSG_WAITALL, (struct sockaddr *)&from, &fromlen); if (h->in_len == -1) { generr(h, "recvfrom: %s", strerror(errno)); return (-1); } for (n = 0; n < h->num_servers; n++) { if (h->servers[n].addr.sin_addr.s_addr == from.sin_addr.s_addr) { h->servers[n].addr.sin_port = from.sin_port; h->srv = n; break; } } if (h->srv == -1) return (-2); if (is_valid_request(h)) { h->in_len = h->in[POS_LENGTH] << 8 | h->in[POS_LENGTH+1]; h->in_pos = POS_ATTRS; return (h->in[POS_CODE]); } return (-3); }
/* * Append some optional data to the current request, and store its * length into the 8-bit field referenced by "fld". Returns 0 on * success, or -1 on failure. * * This function also frees the "cs" string data and initializes it * for the next time. */ static int add_str_8(struct tac_handle *h, u_int8_t *fld, struct clnt_str *cs) { u_int16_t len; if (add_str_16(h, &len, cs) == -1) return -1; len = ntohs(len); if (len > 0xff) { generr(h, "Field too long"); return -1; } *fld = len; return 0; }
static int conn_server(struct tac_handle *h) { const struct tac_server *srvp = &h->servers[h->cur_server]; int flags; if ((h->fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { generr(h, "Cannot create socket: %s", strerror(errno)); return -1; } if ((flags = fcntl(h->fd, F_GETFL, 0)) == -1 || fcntl(h->fd, F_SETFL, flags | O_NONBLOCK) == -1) { generr(h, "Cannot set non-blocking mode on socket: %s", strerror(errno)); close(h->fd); h->fd = -1; return -1; } if (connect(h->fd, (struct sockaddr *)&srvp->addr, sizeof srvp->addr) == 0) return 0; if (errno == EINPROGRESS) { fd_set wfds; struct timeval tv; int nfds; struct sockaddr peer; int peerlen; int err; int errlen; /* Wait for the connection to complete. */ FD_ZERO(&wfds); FD_SET(h->fd, &wfds); tv.tv_sec = srvp->timeout; tv.tv_usec = 0; nfds = select(h->fd + 1, NULL, &wfds, NULL, &tv); if (nfds == -1) { generr(h, "select: %s", strerror(errno)); close(h->fd); h->fd = -1; return -1; } if (nfds == 0) { generr(h, "connect: timed out"); close(h->fd); h->fd = -1; return -1; } /* See whether we are connected now. */ peerlen = sizeof peer; if (getpeername(h->fd, &peer, &peerlen) == 0) return 0; if (errno != ENOTCONN) { generr(h, "getpeername: %s", strerror(errno)); close(h->fd); h->fd = -1; return -1; } /* Find out why the connect failed. */ errlen = sizeof err; getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &err, &errlen); errno = err; } generr(h, "connect: %s", strerror(errno)); close(h->fd); h->fd = -1; return -1; }
int tac_config(struct tac_handle *h, const char *path) { FILE *fp; char buf[MAXCONFLINE]; int linenum; int retval; if (path == NULL) path = PATH_TACPLUS_CONF; if ((fp = fopen(path, "r")) == NULL) { generr(h, "Cannot open \"%s\": %s", path, strerror(errno)); return -1; } retval = 0; linenum = 0; while (fgets(buf, sizeof buf, fp) != NULL) { int len; char *fields[4]; int nfields; char msg[ERRSIZE]; char *host, *res; char *port_str; char *secret; char *timeout_str; char *options_str; char *end; unsigned long timeout; int port; int options; linenum++; len = strlen(buf); /* We know len > 0, else fgets would have returned NULL. */ if (buf[len - 1] != '\n') { if (len >= sizeof buf - 1) generr(h, "%s:%d: line too long", path, linenum); else generr(h, "%s:%d: missing newline", path, linenum); retval = -1; break; } buf[len - 1] = '\0'; /* Extract the fields from the line. */ nfields = split(buf, fields, 4, msg, sizeof msg); if (nfields == -1) { generr(h, "%s:%d: %s", path, linenum, msg); retval = -1; break; } if (nfields == 0) continue; if (nfields < 2) { generr(h, "%s:%d: missing shared secret", path, linenum); retval = -1; break; } host = fields[0]; secret = fields[1]; timeout_str = fields[2]; options_str = fields[3]; /* Parse and validate the fields. */ res = host; host = strsep(&res, ":"); port_str = strsep(&res, ":"); if (port_str != NULL) { port = strtoul(port_str, &end, 10); if (port_str[0] == '\0' || *end != '\0') { generr(h, "%s:%d: invalid port", path, linenum); retval = -1; break; } } else port = 0; if (timeout_str != NULL) { timeout = strtoul(timeout_str, &end, 10); if (timeout_str[0] == '\0' || *end != '\0') { generr(h, "%s:%d: invalid timeout", path, linenum); retval = -1; break; } } else timeout = TIMEOUT; options = 0; if (options_str != NULL) { if (strcmp(options_str, "single-connection") == 0) options |= TAC_SRVR_SINGLE_CONNECT; else { generr(h, "%s:%d: invalid option \"%s\"", path, linenum, options_str); retval = -1; break; } } if (tac_add_server(h, host, port, secret, timeout, options) == -1) { char msg[ERRSIZE]; strcpy(msg, h->errmsg); generr(h, "%s:%d: %s", path, linenum, msg); retval = -1; break; } } /* Clear out the buffer to wipe a possible copy of a shared secret */ memset(buf, 0, sizeof buf); fclose(fp); return retval; }
/* * Send the current request, after encrypting it. Returns 0 on success, * or -1 on failure. */ static int send_msg(struct tac_handle *h) { struct timeval deadline; struct tac_msg *msg; char *ptr; int len; if (h->last_seq_no & 1) { generr(h, "Attempt to send message out of sequence"); return -1; } if (establish_connection(h) == -1) return -1; msg = &h->request; msg->seq_no = ++h->last_seq_no; if (msg->seq_no == 1) gen_session_id(msg); crypt_msg(h, msg); if (h->single_connect) msg->flags |= TAC_SINGLE_CONNECT; else msg->flags &= ~TAC_SINGLE_CONNECT; gettimeofday(&deadline, NULL); deadline.tv_sec += h->servers[h->cur_server].timeout; len = HDRSIZE + ntohl(msg->length); ptr = (char *)msg; while (len > 0) { int n; n = write(h->fd, ptr, len); if (n == -1) { struct timeval tv; int nfds; if (errno != EAGAIN) { generr(h, "Network write error: %s", strerror(errno)); return -1; } /* Wait until we can write more data. */ gettimeofday(&tv, NULL); timersub(&deadline, &tv, &tv); if (tv.tv_sec >= 0) { fd_set wfds; FD_ZERO(&wfds); FD_SET(h->fd, &wfds); nfds = select(h->fd + 1, NULL, &wfds, NULL, &tv); if (nfds == -1) { generr(h, "select: %s", strerror(errno)); return -1; } } else nfds = 0; if (nfds == 0) { generr(h, "Network write timed out"); return -1; } } else { ptr += n; len -= n; } } return 0; }
int rad_config(struct rad_handle *h, const char *path) { FILE *fp; char buf[MAXCONFLINE]; int linenum; int retval; if (path == NULL) path = PATH_RADIUS_CONF; if ((fp = fopen(path, "r")) == NULL) { generr(h, "Cannot open \"%s\": %s", path, strerror(errno)); return -1; } retval = 0; linenum = 0; while (fgets(buf, sizeof buf, fp) != NULL) { int len; char *fields[MAX_FIELDS]; int nfields; char msg[ERRSIZE]; char *type; char *host, *res; char *port_str; char *secret; char *timeout_str; char *maxtries_str; char *dead_time_str; char *bindto_str; char *end; char *wanttype; unsigned long timeout; unsigned long maxtries; unsigned long dead_time; int port; struct in_addr bindto; int i; linenum++; len = strlen(buf); /* We know len > 0, else fgets would have returned NULL. */ if (buf[len - 1] != '\n') { if (len == sizeof buf - 1) generr(h, "%s:%d: line too long", path, linenum); else generr(h, "%s:%d: missing newline", path, linenum); retval = -1; break; } buf[len - 1] = '\0'; /* Extract the fields from the line. */ nfields = split(buf, fields, MAX_FIELDS, msg, sizeof msg); if (nfields == -1) { generr(h, "%s:%d: %s", path, linenum, msg); retval = -1; break; } if (nfields == 0) continue; /* * The first field should contain "auth" or "acct" for * authentication or accounting, respectively. But older * versions of the file didn't have that field. Default * it to "auth" for backward compatibility. */ if (strcmp(fields[0], "auth") != 0 && strcmp(fields[0], "acct") != 0) { if (nfields >= MAX_FIELDS) { generr(h, "%s:%d: invalid service type", path, linenum); retval = -1; break; } nfields++; for (i = nfields; --i > 0; ) fields[i] = fields[i - 1]; fields[0] = "auth"; } if (nfields < 3) { generr(h, "%s:%d: missing shared secret", path, linenum); retval = -1; break; } type = fields[0]; host = fields[1]; secret = fields[2]; timeout_str = fields[3]; maxtries_str = fields[4]; dead_time_str = fields[5]; bindto_str = fields[6]; /* Ignore the line if it is for the wrong service type. */ wanttype = h->type == RADIUS_AUTH ? "auth" : "acct"; if (strcmp(type, wanttype) != 0) continue; /* Parse and validate the fields. */ res = host; host = strsep(&res, ":"); port_str = strsep(&res, ":"); if (port_str != NULL) { port = strtoul(port_str, &end, 10); if (*end != '\0') { generr(h, "%s:%d: invalid port", path, linenum); retval = -1; break; } } else port = 0; if (timeout_str != NULL) { timeout = strtoul(timeout_str, &end, 10); if (*end != '\0') { generr(h, "%s:%d: invalid timeout", path, linenum); retval = -1; break; } } else timeout = TIMEOUT; if (maxtries_str != NULL) { maxtries = strtoul(maxtries_str, &end, 10); if (*end != '\0') { generr(h, "%s:%d: invalid maxtries", path, linenum); retval = -1; break; } } else maxtries = MAXTRIES; if (dead_time_str != NULL) { dead_time = strtoul(dead_time_str, &end, 10); if (*end != '\0') { generr(h, "%s:%d: invalid dead_time", path, linenum); retval = -1; break; } } else dead_time = DEAD_TIME; if (bindto_str != NULL) { bindto.s_addr = inet_addr(bindto_str); if (bindto.s_addr == INADDR_NONE) { generr(h, "%s:%d: invalid bindto", path, linenum); retval = -1; break; } } else bindto.s_addr = INADDR_ANY; if (rad_add_server_ex(h, host, port, secret, timeout, maxtries, dead_time, &bindto) == -1) { strcpy(msg, h->errmsg); generr(h, "%s:%d: %s", path, linenum, msg); retval = -1; break; } } /* Clear out the buffer to wipe a possible copy of a shared secret */ memset(buf, 0, sizeof buf); fclose(fp); return retval; }
/* * rad_init_send_request() must have previously been called. * Returns: * 0 The application should select on *fd with a timeout of tv before * calling rad_continue_send_request again. * < 0 Failure * > 0 Success */ int rad_continue_send_request(struct rad_handle *h, int selected, int *fd, struct timeval *tv) { int n, cur_srv; time_t now; struct sockaddr_in sin; if (h->type == RADIUS_SERVER) { generr(h, "denied function call"); return (-1); } if (selected) { struct sockaddr_in from; socklen_t fromlen; fromlen = sizeof from; h->in_len = recvfrom(h->fd, h->in, MSGSIZE, MSG_WAITALL, (struct sockaddr *)&from, &fromlen); if (h->in_len == -1) { generr(h, "recvfrom: %s", strerror(errno)); return -1; } if (is_valid_response(h, h->srv, &from)) { h->in_len = h->in[POS_LENGTH] << 8 | h->in[POS_LENGTH+1]; h->in_pos = POS_ATTRS; return h->in[POS_CODE]; } } /* * Scan round-robin to the next server that has some * tries left. There is guaranteed to be one, or we * would have exited this loop by now. */ cur_srv = h->srv; now = time(NULL); if (h->servers[h->srv].num_tries >= h->servers[h->srv].max_tries) { /* Set next probe time for this server */ if (h->servers[h->srv].dead_time) { h->servers[h->srv].is_dead = 1; h->servers[h->srv].next_probe = now + h->servers[h->srv].dead_time; } do { h->srv++; if (h->srv >= h->num_servers) h->srv = 0; if (h->servers[h->srv].is_dead == 0) break; if (h->servers[h->srv].dead_time && h->servers[h->srv].next_probe <= now) { h->servers[h->srv].is_dead = 0; h->servers[h->srv].num_tries = 0; break; } } while (h->srv != cur_srv); if (h->srv == cur_srv) { generr(h, "No valid RADIUS responses received"); return (-1); } } /* Rebind */ if (h->bindto != h->servers[h->srv].bindto) { h->bindto = h->servers[h->srv].bindto; close(h->fd); if ((h->fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) { generr(h, "Cannot create socket: %s", strerror(errno)); return -1; } memset(&sin, 0, sizeof sin); sin.sin_len = sizeof sin; sin.sin_family = AF_INET; sin.sin_addr.s_addr = h->bindto; sin.sin_port = 0; if (bind(h->fd, (const struct sockaddr *)&sin, sizeof sin) == -1) { generr(h, "bind: %s", strerror(errno)); close(h->fd); h->fd = -1; return (-1); } } if (h->out[POS_CODE] == RAD_ACCESS_REQUEST) { /* Insert the scrambled password into the request */ if (h->pass_pos != 0) insert_scrambled_password(h, h->srv); } insert_message_authenticator(h, 0); if (h->out[POS_CODE] != RAD_ACCESS_REQUEST) { /* Insert the request authenticator into the request */ memset(&h->out[POS_AUTH], 0, LEN_AUTH); insert_request_authenticator(h, 0); } /* Send the request */ n = sendto(h->fd, h->out, h->out_len, 0, (const struct sockaddr *)&h->servers[h->srv].addr, sizeof h->servers[h->srv].addr); if (n != h->out_len) tv->tv_sec = 1; /* Do not wait full timeout if send failed. */ else tv->tv_sec = h->servers[h->srv].timeout; h->servers[h->srv].num_tries++; tv->tv_usec = 0; *fd = h->fd; return 0; }