tacacs-F4.0.4.28/packet.c (414 lines of code) (raw):

/* * $Id: packet.c,v 1.22 2009-03-18 21:09:26 heas Exp $ * * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved * Copyright (c) 1995-1998 by Cisco systems, Inc. * * Permission to use, copy, modify, and distribute this software for * any purpose and without fee is hereby granted, provided that this * copyright and permission notice appear on all copies of the * software and supporting documentation, the name of Cisco Systems, * Inc. not be used in advertising or publicity pertaining to * distribution of the program without specific prior permission, and * notice be given in supporting documentation that modification, * copying and distribution is by permission of Cisco Systems, Inc. * * Cisco Systems, Inc. makes no representations about the suitability * of this software for any purpose. THIS SOFTWARE IS PROVIDED ``AS * IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. */ #include "tac_plus.h" #include <poll.h> #include <netdb.h> #include <signal.h> #include <time.h> #pragma weak get_authen_continue #pragma weak read_packet #pragma weak send_acct_reply #pragma weak send_authen_error #pragma weak send_authen_reply #pragma weak send_author_reply #pragma weak send_error_reply /* Everything to do with reading and writing packets */ static int sockread(int, u_char *, int, int); static int sockwrite(int, u_char *, int, int); static int write_packet(u_char *); /* read an authentication GETDATA packet from a NAS. Return NULL on failure */ u_char * get_authen_continue(void) { HDR *hdr; u_char *pak; struct authen_cont *cont; char msg[NI_MAXHOST + 256]; pak = read_packet(); if (!pak) return(NULL); hdr = (HDR *)pak; cont = (struct authen_cont *)(pak + TAC_PLUS_HDR_SIZE); if ((hdr->type != TAC_PLUS_AUTHEN) || (hdr->seq_no <= 1)) { if (snprintf(msg, sizeof(msg), "%s: Bad packet type=%d/seq no=%d " "when expecting authentication cont", session.peer, hdr->type, hdr->seq_no) == -1) strcpy(msg, ""); report(LOG_ERR, msg); send_authen_error(msg); return(NULL); } cont->user_msg_len = ntohs(cont->user_msg_len); cont->user_data_len = ntohs(cont->user_data_len); if ((TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE + cont->user_msg_len + cont->user_data_len) != ntohl(hdr->datalength)) { char *m = "Illegally sized authentication cont packet"; report(LOG_ERR, "%s: %s", session.peer, m); send_authen_error(m); return(NULL); } if (debug & DEBUG_PACKET_FLAG) dump_nas_pak(pak); return(pak); } /* * read a packet from the wire, and decrypt it. Increment the global * seq_no return NULL on failure */ u_char * read_packet(void) { HDR hdr; u_char *pkt, *data; int len; char *tkey; if (debug & DEBUG_PACKET_FLAG) report(LOG_DEBUG, "Waiting for packet"); /* read a packet header */ len = sockread(session.sock, (u_char *)&hdr, TAC_PLUS_HDR_SIZE, TAC_PLUS_READ_TIMEOUT); if (len != TAC_PLUS_HDR_SIZE) { report(LOG_DEBUG, "Read %d bytes from %s %s, expecting %d", len, session.peer, session.port, TAC_PLUS_HDR_SIZE); return(NULL); } session.peerflags = hdr.flags; if ((hdr.version & TAC_PLUS_MAJOR_VER_MASK) != TAC_PLUS_MAJOR_VER) { report(LOG_ERR, "%s: Illegal major version specified: found %d wanted " "%d\n", session.peer, hdr.version, TAC_PLUS_MAJOR_VER); return(NULL); } /* get memory for the packet */ len = TAC_PLUS_HDR_SIZE + ntohl(hdr.datalength); if ((ntohl(hdr.datalength) & ~0xffffUL) || (len < TAC_PLUS_HDR_SIZE) || (len > 0x10000)) { report(LOG_ERR, "%s: Illegal data size: %lu\n", session.peer, (unsigned long)ntohl(hdr.datalength)); return(NULL); } pkt = (u_char *)tac_malloc(len); /* initialise the packet */ memcpy(pkt, &hdr, TAC_PLUS_HDR_SIZE); /* the data start here */ data = pkt + TAC_PLUS_HDR_SIZE; /* read the rest of the packet data */ if (sockread(session.sock, data, ntohl(hdr.datalength), TAC_PLUS_READ_TIMEOUT) != ntohl(hdr.datalength)) { report(LOG_ERR, "%s: start_session: bad socket read", session.peer); free(pkt); return(NULL); } session.seq_no++; /* should now equal that of incoming packet */ session.last_exch = time(NULL); if (session.seq_no != hdr.seq_no) { report(LOG_ERR, "%s: Illegal session seq #, expecting %d, received %d", session.peer, session.seq_no, hdr.seq_no); free(pkt); return(NULL); } /* decrypt the data portion */ tkey = cfg_get_host_key(session.peerip); if (tkey == NULL && !STREQ(session.peer, session.peerip)) { tkey = cfg_get_host_prompt(session.peer); } if (tkey == NULL) tkey = session.key; if (md5_xor((HDR *)pkt, data, tkey)) { report(LOG_ERR, "%s: start_session error decrypting data", session.peer); free(pkt); return(NULL); } if (debug & DEBUG_PACKET_FLAG) report(LOG_DEBUG, "Read %s size=%d", summarise_incoming_packet_type(pkt), len); session.version = hdr.version; return(pkt); } /* send an accounting response packet */ void send_acct_reply(u_char status, char *msg, char *data) { u_char *pak, *p; HDR *hdr; int len; struct acct_reply *reply; int msg_len, data_len; msg_len = msg ? strlen(msg) : 0; data_len = data ? strlen(data) : 0; len = TAC_PLUS_HDR_SIZE + TAC_ACCT_REPLY_FIXED_FIELDS_SIZE + msg_len + data_len; pak = (u_char *)tac_malloc(len); reply = (struct acct_reply *)(pak + TAC_PLUS_HDR_SIZE); hdr = (HDR *)pak; memset(pak, 0, len); hdr->version = TAC_PLUS_VER_0; hdr->type = TAC_PLUS_ACCT; hdr->seq_no = ++session.seq_no; hdr->flags = TAC_PLUS_UNENCRYPTED; if (!(session.flags & SESS_NO_SINGLECONN)) hdr->flags |= (session.peerflags & TAC_PLUS_SINGLE_CONNECT_FLAG); hdr->session_id = htonl(session.session_id); hdr->datalength = htonl(len - TAC_PLUS_HDR_SIZE); reply->status = status; reply->msg_len = msg_len; reply->data_len = data_len; p = pak + TAC_PLUS_HDR_SIZE + TAC_ACCT_REPLY_FIXED_FIELDS_SIZE; memcpy(p, msg, msg_len); p += msg_len; memcpy(p, data, data_len); if (debug & DEBUG_PACKET_FLAG) { report(LOG_DEBUG, "Writing %s size=%d", summarise_outgoing_packet_type(pak), len); dump_tacacs_pak(pak); } reply->msg_len = ntohs(reply->msg_len); reply->data_len = ntohs(reply->data_len); write_packet(pak); free(pak); return; } /* * Send an authentication reply packet indicating an error has occurred. * msg is a null terminated character string */ void send_authen_error(char *msg) { char buf[NI_MAXHOST + 256]; if (snprintf(buf, sizeof(buf), "%s %s: %s", session.peer, session.port, msg) == -1) strcpy(buf, ""); report(LOG_ERR, buf); send_authen_reply(TAC_PLUS_AUTHEN_STATUS_ERROR, buf, strlen(buf), NULL, 0, 0); } /* create and send an authentication reply packet from tacacs+ to a NAS */ void send_authen_reply(int status, char *msg, u_short msg_len, char *data, u_short data_len, u_char flags) { u_char *pak, *p; HDR *hdr; struct authen_reply *reply; int len; len = TAC_PLUS_HDR_SIZE + TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE + msg_len + data_len; pak = (u_char *)tac_malloc(len); memset(pak, 0, len); hdr = (HDR *)pak; reply = (struct authen_reply *)(pak + TAC_PLUS_HDR_SIZE); hdr->version = session.version; hdr->type = TAC_PLUS_AUTHEN; hdr->seq_no = ++session.seq_no; hdr->flags = TAC_PLUS_UNENCRYPTED; if (!(session.flags & SESS_NO_SINGLECONN)) hdr->flags |= (session.peerflags & TAC_PLUS_SINGLE_CONNECT_FLAG); hdr->session_id = htonl(session.session_id); hdr->datalength = htonl(TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE + msg_len + data_len); reply->status = status; reply->msg_len = msg_len; reply->data_len = data_len; reply->flags = flags; p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE; memcpy(p, msg, msg_len); p += msg_len; memcpy(p, data, data_len); if (debug & DEBUG_PACKET_FLAG) { report(LOG_DEBUG, "Writing %s size=%d", summarise_outgoing_packet_type(pak), len); dump_tacacs_pak(pak); } reply->msg_len = htons(reply->msg_len); reply->data_len = htons(reply->data_len); write_packet(pak); free(pak); return; } /* send an authorization reply packet */ void send_author_reply(u_char status, char *msg, char *data, int arg_cnt, char **args) { u_char *pak, *p; HDR *hdr; struct author_reply *reply; int msg_len; int len; int data_len; int i; data_len = (data ? strlen(data) : 0); msg_len = (msg ? strlen(msg) : 0); /* start calculating final packet size */ len = TAC_PLUS_HDR_SIZE + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE + msg_len + data_len; for (i = 0; i < arg_cnt; i++) { /* space for the arg and its length */ len += strlen(args[i]) + 1; } pak = (u_char *)tac_malloc(len); memset(pak, 0, len); hdr = (HDR *)pak; reply = (struct author_reply *) (pak + TAC_PLUS_HDR_SIZE); hdr->version = TAC_PLUS_VER_0; hdr->type = TAC_PLUS_AUTHOR; hdr->seq_no = ++session.seq_no; hdr->flags = TAC_PLUS_UNENCRYPTED; if (!(session.flags & SESS_NO_SINGLECONN)) hdr->flags |= (session.peerflags & TAC_PLUS_SINGLE_CONNECT_FLAG); hdr->session_id = htonl(session.session_id); hdr->datalength = htonl(len - TAC_PLUS_HDR_SIZE); reply->status = status; reply->msg_len = msg_len; reply->data_len = data_len; reply->arg_cnt = arg_cnt; p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE; /* place arg sizes into packet */ for (i = 0; i < arg_cnt; i++) { *p++ = strlen(args[i]); } memcpy(p, msg, msg_len); p += msg_len; memcpy(p, data, data_len); p += data_len; /* copy arg bodies into packet */ for (i = 0; i < arg_cnt; i++) { int arglen = strlen(args[i]); memcpy(p, args[i], arglen); p += arglen; } if (debug & DEBUG_PACKET_FLAG) { report(LOG_DEBUG, "Writing %s size=%d", summarise_outgoing_packet_type(pak), len); dump_tacacs_pak(pak); } reply->msg_len = htons(reply->msg_len); reply->data_len = htons(reply->data_len); write_packet(pak); free(pak); return; } void send_error_reply(int type, char *msg) { switch (type) { case TAC_PLUS_AUTHEN: send_authen_error(msg); break; case TAC_PLUS_AUTHOR: send_author_reply(AUTHOR_STATUS_ERROR, msg, NULL, 0, NULL); break; case TAC_PLUS_ACCT: send_acct_reply(TAC_PLUS_ACCT_STATUS_ERROR, msg, NULL); break; default: report(LOG_ERR, "Illegal type %d for send_error_reply", type); break; } return; } /* * Read n bytes from descriptor fd into array ptr with timeout t seconds. * Note the timeout is applied to each read, not for the overall operation. * * Return -1 on error, eof or timeout. Otherwise return number of bytes read. */ static int sockread(int fd, u_char *ptr, int nbytes, int timeout) { int nleft, nread; struct pollfd pfds; pfds.fd = fd; pfds.events = POLLIN | POLLERR | POLLHUP | POLLNVAL; nleft = nbytes; while (nleft > 0) { int status = poll(&pfds, 1, timeout * 1000); if (status == 0) { report(LOG_DEBUG, "%s: timeout reading fd %d", session.peer, fd); return(-1); } if (status < 0) { if (errno == EINTR) continue; status = errno; report(LOG_DEBUG, "%s: error in poll %s fd %d", session.peer, strerror(errno), fd); errno = status; return(-1); } if (pfds.revents & (POLLERR | POLLHUP | POLLNVAL)) { status = errno; report(LOG_DEBUG, "%s: exception on fd %d", session.peer, fd); errno = status; return(-1); } if (!(pfds.revents & POLLIN)) { status = errno; report(LOG_DEBUG, "%s: spurious return from poll", session.peer); errno = status; continue; } again: nread = read(fd, ptr, nleft); if (nread < 0) { if (errno == EINTR) goto again; status = errno; report(LOG_DEBUG, "%s %s: error reading fd %d nread=%d %s", session.peer, session.port, fd, nread, strerror(errno)); errno = status; return(-1); /* error */ } else if (nread == 0) { report(LOG_DEBUG, "%s %s: fd %d eof (connection closed)", session.peer, session.port, fd); errno = 0; return(-1); /* eof */ } nleft -= nread; if (nleft) ptr += nread; } return(nbytes - nleft); } /* * Write n bytes to descriptor fd from array ptr with timeout t seconds. * Note the timeout is applied to each write, not for the overall operation. * * Return -1 on error, eof or timeout. Otherwise return number of bytes * written. */ static int sockwrite(int fd, u_char *ptr, int bytes, int timeout) { int remaining, sent; struct pollfd pfds; pfds.fd = fd; pfds.events = POLLOUT | POLLERR | POLLHUP | POLLNVAL; sent = 0; remaining = bytes; while (remaining > 0) { int status = poll(&pfds, 1, timeout * 1000); if (status == 0) { status = errno; report(LOG_DEBUG, "%s: timeout writing to fd %d", session.peer, fd); errno = status; return(-1); } if (status < 0) { status = errno; report(LOG_DEBUG, "%s: error in poll fd %d", session.peer, fd); errno = status; return(-1); } if (pfds.revents & (POLLERR | POLLHUP | POLLNVAL)) { status = errno; report(LOG_DEBUG, "%s: exception on fd %d", session.peer, fd); errno = status; return(-1); /* error */ } if (!(pfds.revents & POLLOUT)) { report(LOG_DEBUG, "%s: spurious return from poll", session.peer); continue; } sent = write(fd, ptr, remaining); if (sent <= 0) { status = errno; report(LOG_DEBUG, "%s: error writing fd %d sent=%d", session.peer, fd, sent); errno = status; return(sent); /* error */ } remaining -= sent; ptr += sent; } return(bytes - remaining); } /* write a packet to the wire, encrypting it */ static int write_packet(u_char *pak) { HDR *hdr = (HDR *)pak; u_char *data; int len; char *tkey; len = TAC_PLUS_HDR_SIZE + ntohl(hdr->datalength); /* the data start here */ data = pak + TAC_PLUS_HDR_SIZE; /* encrypt the data portion */ tkey = cfg_get_host_key(session.peerip); if (tkey == NULL && !STREQ(session.peer, session.peerip)) { tkey = cfg_get_host_prompt(session.peer); } if (tkey == NULL) tkey = session.key; if (md5_xor((HDR *)pak, data, tkey)) { report(LOG_ERR, "%s: write_packet: error encrypting data", session.peer); return(-1); } if (sockwrite(session.sock, pak, len, TAC_PLUS_WRITE_TIMEOUT) != len) { return(-1); } session.last_exch = time(NULL); return(0); }