/* * $Id: packet.c,v 1.15 2006/12/13 01:11:37 heas Exp $ * * 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 /* 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 *); /* 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; bzero(pak, len); hdr->version = TAC_PLUS_VER_0; hdr->type = TAC_PLUS_ACCT; hdr->seq_no = ++session.seq_no; hdr->encryption = TAC_PLUS_CLEAR; 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; bcopy(msg, p, msg_len); p += msg_len; bcopy(data, p, 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[255]; sprintf(buf, "%s %s: %s", session.peer, session.port, msg); 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 = TAC_PLUS_HDR_SIZE + TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE + msg_len + data_len; pak = (u_char *) tac_malloc(len); bzero(pak, 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->encryption = TAC_PLUS_CLEAR; 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; bcopy(msg, p, msg_len); p += msg_len; bcopy(data, p, 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); bzero(pak, 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->encryption = TAC_PLUS_CLEAR; 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]); } bcopy(msg, p, msg_len); p += msg_len; bcopy(data, p, data_len); p += data_len; /* copy arg bodies into packet */ for (i = 0; i < arg_cnt; i++) { int arglen = strlen(args[i]); bcopy(args[i], p, 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; } /* read an authentication GETDATA packet from a NAS. Return 0 on failure */ u_char * get_authen_continue(void) { HDR *hdr; u_char *pak, *read_packet(); struct authen_cont *cont; char msg[255]; 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)) { sprintf(msg, "%s: Bad packet type=%d/seq no=%d when expecting authentication cont", session.peer, hdr->type, hdr->seq_no); 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); } void send_error_reply(int type, char *msg) { switch (type) { case TAC_PLUS_AUTHEN: send_authen_error(msg); return; case TAC_PLUS_AUTHOR: send_author_reply(AUTHOR_STATUS_ERROR, msg, NULL, 0, NULL); return; case TAC_PLUS_ACCT: send_acct_reply(TAC_PLUS_ACCT_STATUS_ERROR, msg, NULL); return; default: report(LOG_ERR, "Illegal type %d for send_error_reply", type); return; } /*NOTREACHED*/ 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; fd_set readfds, exceptfds; struct timeval tout; tout.tv_sec = timeout; tout.tv_usec = 0; FD_ZERO(&readfds); FD_SET(fd, &readfds); FD_ZERO(&exceptfds); FD_SET(fd, &exceptfds); nleft = nbytes; while (nleft > 0) { int status = select(fd + 1, &readfds, (fd_set *) NULL, &exceptfds, &tout); if (status == 0) { report(LOG_DEBUG, "%s: timeout reading fd %d", session.peer, fd); return(-1); } if (status < 0) { if (errno == EINTR) continue; report(LOG_DEBUG, "%s: error in select %s fd %d", session.peer, strerror(errno), fd); return(-1); } if (FD_ISSET(fd, &exceptfds)) { report(LOG_DEBUG, "%s: exception on fd %d", session.peer, fd); return(-1); } if (!FD_ISSET(fd, &readfds)) { report(LOG_DEBUG, "%s: spurious return from select", session.peer); continue; } again: nread = read(fd, ptr, nleft); if (nread < 0) { if (errno == EINTR) goto again; report(LOG_DEBUG, "%s %s: error reading fd %d nread=%d %s", session.peer, session.port, fd, nread, strerror(errno)); return(-1); /* error */ } else if (nread == 0) { report(LOG_DEBUG, "%s %s: fd %d eof (connection closed)", session.peer, session.port, fd); 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; fd_set writefds, exceptfds; struct timeval tout; sent = 0; tout.tv_sec = timeout; tout.tv_usec = 0; FD_ZERO(&writefds); FD_SET(fd, &writefds); FD_ZERO(&exceptfds); FD_SET(fd, &exceptfds); remaining = bytes; while (remaining > 0) { int status = select(fd + 1, (fd_set *) NULL, &writefds, &exceptfds, &tout); if (status == 0) { report(LOG_DEBUG, "%s: timeout writing to fd %d", session.peer, fd); return(-1); } if (status < 0) { report(LOG_DEBUG, "%s: error in select fd %d", session.peer, fd); return(-1); } if (FD_ISSET(fd, &exceptfds)) { report(LOG_DEBUG, "%s: exception on fd %d", session.peer, fd); return(sent); /* error */ } if (!FD_ISSET(fd, &writefds)) { report(LOG_DEBUG, "%s: spurious return from select", session.peer); continue; } sent = write(fd, ptr, remaining); if (sent <= 0) { report(LOG_DEBUG, "%s: error writing fd %d sent=%d", session.peer, fd, sent); return(sent); /* error */ } remaining -= sent; ptr += sent; } return(bytes - remaining); } /* * 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); } 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, ntohl(hdr.datalength)); return(NULL); } pkt = (u_char *) tac_malloc(len); /* initialise the packet */ bcopy(&hdr, pkt, 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); 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 # %d != packet seq # %d", session.peer, session.seq_no, hdr.seq_no); 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); 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); } /* 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); }