diff --git a/.gitignore b/.gitignore index a7018dfb..0e8e97cf 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ config.status libtool pam_tacplus.spec stamp-h1 +libtac.pc +libtac-event.pc *.o *.lo *.la diff --git a/Makefile.am b/Makefile.am index 7e09e981..be16df61 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,6 +24,9 @@ libtac/include/libtac.h \ libtac/include/cdefs.h lib_LTLIBRARIES = libtac.la +if WITH_LIBEVENT +lib_LTLIBRARIES += libtac-event.la +endif libtac_la_SOURCES = \ libtac/lib/acct_r.c \ libtac/lib/acct_s.c \ @@ -45,6 +48,8 @@ libtac/lib/read_wait.c \ libtac/lib/version.c \ libtac/lib/xalloc.c \ libtac/lib/xalloc.h \ +libtac/lib/session.c \ +libtac/lib/parse.c \ $(libtac_include_HEADERS) if MY_MD5 libtac_la_SOURCES += \ @@ -54,6 +59,14 @@ endif libtac_la_CFLAGS = $(AM_CFLAGS) -I $(top_srcdir)/libtac/include @rt_debug_defines@ libtac_la_LDFLAGS = -version-info 2:0:0 -shared +if WITH_LIBEVENT +libtac_event_la_SOURCES = \ +libtac/lib/wrappers.c \ +$(libtac_include_HEADERS) +libtac_event_la_CFLAGS = $(AM_CFLAGS) -Ilibtac/include +libtac_event_la_LDFLAGS = -version-info 1:0:0 -shared -levent +endif + moduledir = @pamdir@ module_LTLIBRARIES = pam_tacplus.la pam_tacplus_la_SOURCES = pam_tacplus.h \ @@ -65,6 +78,9 @@ pam_tacplus_la_LDFLAGS = -module -avoid-version pam_tacplus_la_LIBADD = libtac.la EXTRA_DIST = pam_tacplus.spec libtac.pc.in +if WITH_LIBEVENT +EXTRA_DIST += libtac-event.pc.in +endif if DOC dist_doc_DATA = sample.pam README.md AUTHORS ChangeLog endif @@ -75,6 +91,9 @@ MAINTAINERCLEANFILES = Makefile.in config.h.in configure aclocal.m4 \ pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libtac.pc +if WITH_LIBEVENT +pkgconfig_DATA += libtac-event.pc +endif coverity: rm -rf cov-int diff --git a/configure.ac b/configure.ac index e34c7694..446dbdf5 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,7 @@ dnl -------------------------------------------------------------------- dnl Checks for header files. AC_HEADER_STDC AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h string.h strings.h sys/socket.h sys/time.h ]) +AC_CHECK_HEADERS([stdbool.h limits.h assert.h]) AC_CHECK_HEADERS([syslog.h unistd.h openssl/md5.h openssl/rand.h linux/random.h sys/random.h]) AC_CHECK_HEADER(security/pam_appl.h, [], [AC_MSG_ERROR([PAM libraries missing. Install with "yum install pam-devel" or "apt-get install libpam-dev".])] ) AM_CONDITIONAL(MY_MD5, [test "$ac_cv_header_openssl_md5_h" = "no" ]) @@ -102,8 +103,21 @@ AC_SUBST(rt_debug_defines) AM_SUBST_NOTMAKE(rt_debug_defines) dnl -------------------------------------------------------------------- +dnl Switch for libevent support +AC_ARG_WITH(libevent, + [AS_HELP_STRING([--with-libevent], [build libevent support])], + AC_DEFINE([WITH_LIBEVENT], 1, + [Define to 1 if building with libevent]) + [with_libevent=1] +) +AM_CONDITIONAL(WITH_LIBEVENT, [test -n "$with_libevent"]) + + +dnl -------------------------------------------------------------------- + dnl Generate made files AC_CONFIG_FILES([Makefile libtac.pc + libtac-event.pc pam_tacplus.spec]) AC_OUTPUT diff --git a/libtac-event.pc.in b/libtac-event.pc.in new file mode 100644 index 00000000..b907adcb --- /dev/null +++ b/libtac-event.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@/libtac + +Name: libtac-event +Description: A TACACS+ protocol implementation +URL: https://github.com/jeroennijhof/pam_tacplus +Version: @VERSION@ +Libs: -L${libdir} -ltac-event +Cflags: -I${includedir} diff --git a/libtac/include/libtac.h b/libtac/include/libtac.h index c872ff71..471e6a40 100644 --- a/libtac/include/libtac.h +++ b/libtac/include/libtac.h @@ -42,6 +42,9 @@ extern "C" { #else #include "cdefs.h" #endif +#include <assert.h> +#include <stdbool.h> +#include <limits.h> #include "tacplus.h" #if defined(DEBUGTAC) && !defined(TACDEBUG) @@ -87,6 +90,7 @@ struct tac_attrib { struct areply { struct tac_attrib *attr; char *msg; + char *data; int status :8; int flags :8; int seq_no :8; @@ -132,50 +136,172 @@ extern int tac_ver_major; extern int tac_ver_minor; extern int tac_ver_patch; -/* header.c */ -extern int session_id; -extern int tac_encryption; -extern const char *tac_secret; -extern char tac_login[64]; -extern int tac_priv_lvl; -extern int tac_authen_method; -extern int tac_authen_service; +/* session.c */ +struct bufferevent; +struct tac_session; + +struct cb_ctx { + struct tac_session *sess; + void *user_ctx; + const char *login; + const char *pass; +}; + +typedef void (*response_cb_t)(struct tac_session *, struct cb_ctx *, + int, uint8_t, struct areply *); + +typedef enum { UNINITIALIZED, CONNECTED, CLOSED, ERROR, TIMEOUT } session_event_t; + +typedef void (*oob_cb_t)(struct tac_session *, struct cb_ctx *, + session_event_t); + +struct tac_session { + unsigned tac_timeout; + const char *tac_secret; + uint32_t tac_session_id; + bool tac_encryption; + bool tac_multiplex; + bool tac_idle; /* not exposed via API */ + uint8_t tac_priv_lvl; + uint8_t tac_authen_method; + uint8_t tac_authen_service; + uint8_t tac_authen_type; + uint8_t seq_no; + int fd; + + struct bufferevent *bufev; + void *cookie; + + response_cb_t response_cb; + oob_cb_t oob_cb; + struct cb_ctx context; + /* user defined stuff */ + uint8_t user_data[0]; +}; + +struct tac_session *tac_session_alloc(void); +struct tac_session *tac_session_alloc_extra(unsigned); +void tac_session_set_authen_type(struct tac_session *, uint8_t); +void tac_session_set_secret(struct tac_session *, const char *); +void tac_session_set_timeout(struct tac_session *, unsigned); +void tac_session_set_multiplex(struct tac_session *, bool); +void tac_session_reset_timeouts(struct tac_session *, bool); +void tac_session_set_response(struct tac_session *, response_cb_t); +void tac_session_set_oob(struct tac_session *, oob_cb_t); +struct cb_ctx *tac_session_get_context(struct tac_session *); +void tac_session_new_session_id(struct tac_session *); +void tac_session_reset_seq(struct tac_session *); +void *tac_session_get_user_data(struct tac_session *); +void tac_session_free(struct tac_session *); + +/* header.c */ extern int tac_debug_enable; extern int tac_readtimeout_enable; +/* we return a void * because there are different types of bodies */ +static inline void *tac_hdr_to_body(HDR *th) +{ + return (void *)((u_char *)th + TAC_PLUS_HDR_SIZE); +} + +HDR *_tac_req_header(struct tac_session *, u_char, bool); + /* connect.c */ extern int tac_timeout; -int tac_connect(struct addrinfo **, char **, int); -int tac_connect_single(const struct addrinfo *, const char *, struct addrinfo *, - int); +int tac_connect(struct tac_session *, struct addrinfo **, unsigned); +int tac_connect_single(struct tac_session *, const struct addrinfo *, struct addrinfo *, int); +void tac_close(struct tac_session *); char *tac_ntop(const struct sockaddr *); -int tac_authen_send(int, const char *, const char *, const char *, const char *, - u_char); -int tac_authen_read(int, struct areply *); -int tac_cont_send_seq(int, const char *, int); -#define tac_cont_send(fd, pass) tac_cont_send_seq((fd), (pass), 3) -HDR *_tac_req_header(u_char, int); -void _tac_crypt(u_char *, const HDR *); +/* authen_s.c */ +u_char tac_get_authen_type(const char *); +const char *tag_get_authen_string(uint8_t); + +void tac_authen_send_pkt(struct tac_session *, + const char *, const char *, const char *, const char *, u_char, + u_char **, unsigned *); +int tac_authen_send(struct tac_session *, + const char *, const char *, const char *, const char *, u_char); + +/* authen_r.c */ +int tac_authen_parse(struct tac_session *, struct areply *, u_char *, unsigned); +int tac_authen_read(struct tac_session *, struct areply *); + +/* cont_s.c */ +void tac_cont_send_pkt(struct tac_session *, const char *, + u_char **, unsigned *); +int tac_cont_send(struct tac_session *, const char *); + +/* crypt.c */ +void _tac_crypt(const struct tac_session *, u_char *, const HDR *); + +/* author_r.c */ +int tac_author_parse(struct tac_session *, u_char *, unsigned, struct areply *); +int tac_author_read(struct tac_session *, struct areply *); + +/* author_s.c */ +void tac_author_send_pkt(struct tac_session *, const char *, const char *, + const char *, struct tac_attrib *, u_char **, unsigned *); +int tac_author_send(struct tac_session *, const char *, const char *, + const char *, struct tac_attrib *); + +/* attrib.c */ void tac_add_attrib(struct tac_attrib **, char *, char *); +void tac_add_attrib_pair(struct tac_attrib **, char *, char, char *); void tac_free_attrib(struct tac_attrib **); -char *tac_acct_flag2str(int); -int tac_acct_send(int, int, const char *, char *, char *, struct tac_attrib *); -int tac_acct_read(int, struct areply *); + +/* acct_s.c */ +char *tac_acct_flag2str(u_char); +void tac_acct_send_pkt(struct tac_session *, u_char, const char *, + const char *, const char *, struct tac_attrib *, u_char **, unsigned *); +int tac_acct_send(struct tac_session *, u_char, const char *, + const char *, const char *, struct tac_attrib *); + +/* acct_r.c */ +int tac_acct_parse(struct tac_session *, u_char *, unsigned, + struct areply *); +int tac_acct_read(struct tac_session *, struct areply *); + +/* xalloc.c */ void *xcalloc(size_t, size_t); void *xrealloc(void *, size_t); char *xstrcpy(char *, const char *, size_t); -char *_tac_check_header(HDR *, int); -int tac_author_send(int, const char *, char *, char *, struct tac_attrib *); -int tac_author_read(int, struct areply *); -void tac_add_attrib_pair(struct tac_attrib **, char *, char, char *); -int tac_read_wait(int, int, int, int *); + +/* hdr_check.c */ +char *_tac_check_header(struct tac_session *, HDR *, uint8_t); /* magic.c */ u_int32_t magic(void); +/* read_wait.c */ +int tac_read_wait(int, int, int, int *); + +/* parser.c */ +void tac_parse_pkt(struct tac_session *, struct cb_ctx *, u_char *, unsigned); + +/* wrappers.c */ +void *tac_event_loop_initialize(void); +int tac_event_loop(void *tac_event); +void tac_event_loop_end(void *tac_event); +void tac_event_loop_shutdown(void *tac_event); +void tac_event_loop_global_shutdown(void); + +bool tac_connect_single_ev(struct tac_session *, + void *, struct addrinfo *server, struct addrinfo *srcaddr, unsigned timeout); +bool tac_authen_send_ev(struct tac_session *sess, + const char *user, const char *pass, const char *tty, + const char *r_addr, u_char action); +bool tac_author_send_ev(struct tac_session *sess, + const char *user, const char *tty, const char *r_addr, + struct tac_attrib *attr); +bool tac_acct_send_ev(struct tac_session *sess, + u_char type, const char *user, const char *tty, + const char *r_addr, struct tac_attrib *attr); +bool tac_cont_send_ev(struct tac_session *sess, + const char *pass); + #ifdef __cplusplus } #endif diff --git a/libtac/include/tacplus.h b/libtac/include/tacplus.h index 90d7c8bf..c7f0c598 100644 --- a/libtac/include/tacplus.h +++ b/libtac/include/tacplus.h @@ -107,6 +107,7 @@ struct authen_cont { u_short user_msg_len; u_short user_data_len; u_char flags; + u_char msg[0]; #define TAC_PLUS_CONTINUE_FLAG_ABORT 0x01 @@ -133,6 +134,7 @@ struct authen_reply { u_short msg_len; u_short data_len; + u_char msg[0]; }; #define TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE 6 @@ -173,6 +175,7 @@ struct acct { u_char port_len; u_char r_addr_len; u_char arg_cnt; /* the number of cmd args */ + u_char arg_len[0]; }; #define TAC_ACCT_REQ_FIXED_FIELDS_SIZE 9 @@ -201,6 +204,7 @@ struct author { u_char port_len; u_char r_addr_len; u_char arg_cnt; /* the number of args */ + u_char arg_len[0]; }; #define TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE 8 @@ -211,6 +215,7 @@ struct author_reply { u_char arg_cnt; u_short msg_len; u_short data_len; + u_char arg_len[0]; #define TAC_PLUS_AUTHOR_STATUS_PASS_ADD 0x01 #define TAC_PLUS_AUTHOR_STATUS_PASS_REPL 0x02 diff --git a/libtac/lib/acct_r.c b/libtac/lib/acct_r.c index 6d0d04d9..2a25e197 100644 --- a/libtac/lib/acct_r.c +++ b/libtac/lib/acct_r.c @@ -32,118 +32,81 @@ * LIBTAC_STATUS_PROTOCOL_ERR * >= 0 : server response, see TAC_PLUS_AUTHEN_STATUS_... */ -int tac_acct_read(int fd, struct areply *re) { - HDR th; +int tac_acct_parse(struct tac_session *sess, u_char *pkt, unsigned pkt_total, + struct areply *re) { + HDR *th = (HDR *)pkt; struct acct_reply *tb = NULL; size_t ulen_from_header, len_from_body; - ssize_t spacket_read; char *msg = NULL; - int timeleft = 0; - re->attr = NULL; /* unused */ - re->msg = NULL; - - if (tac_readtimeout_enable && - tac_read_wait(fd,tac_timeout*1000, TAC_PLUS_HDR_SIZE,&timeleft) < 0 ) { - TACSYSLOG(LOG_ERR,\ - "%s: reply timeout after %u secs", __FUNCTION__, tac_timeout); - re->msg = xstrdup(acct_syserr_msg); - re->status = LIBTAC_STATUS_READ_TIMEOUT; - free(tb); - return re->status; - } - spacket_read = read(fd, &th, TAC_PLUS_HDR_SIZE); - if(spacket_read < TAC_PLUS_HDR_SIZE) { - TACSYSLOG(LOG_ERR,\ - "%s: short reply header, read %zd of %u expected: %m", __FUNCTION__,\ - spacket_read, TAC_PLUS_HDR_SIZE); - re->msg = xstrdup(acct_syserr_msg); - re->status = LIBTAC_STATUS_SHORT_HDR; - free(tb); - return re->status; - } + re->attr = NULL; /* unused */ + re->msg = re->data = NULL; /* check the reply fields in header */ - msg = _tac_check_header(&th, TAC_PLUS_ACCT); + msg = _tac_check_header(sess, th, TAC_PLUS_ACCT); if(msg != NULL) { re->msg = xstrdup(msg); re->status = LIBTAC_STATUS_PROTOCOL_ERR; - free(tb); TACDEBUG(LOG_DEBUG, "%s: exit status=%d, status message \"%s\"",\ __FUNCTION__, re->status, re->msg != NULL ? re->msg : ""); return re->status; } - ulen_from_header = ntohl(th.datalength); - if (ulen_from_header > TAC_PLUS_MAX_PACKET_SIZE) { - TACSYSLOG(LOG_ERR,\ - "%s: length declared in the packet %zu exceeds max allowed packet size %d",\ - __FUNCTION__,\ - ulen_from_header, TAC_PLUS_MAX_PACKET_SIZE); - re->status=LIBTAC_STATUS_SHORT_HDR; - free(tb); - return re->status; - } - tb=(struct acct_reply *) xcalloc(1, ulen_from_header); + ulen_from_header = ntohl(th->datalength); - /* read reply packet body */ - if (tac_readtimeout_enable && - tac_read_wait(fd,timeleft,ulen_from_header,NULL) < 0 ) { - TACSYSLOG(LOG_ERR,\ - "%s: reply timeout after %u secs", __FUNCTION__, tac_timeout); - re->msg = xstrdup(acct_syserr_msg); - re->status = LIBTAC_STATUS_READ_TIMEOUT; - free(tb); - return re->status; - } + tb = tac_hdr_to_body(th); - spacket_read = read(fd, tb, ulen_from_header); - if(spacket_read < (ssize_t) ulen_from_header) { + if (pkt_total != ulen_from_header) { TACSYSLOG(LOG_ERR,\ - "%s: short reply body, read %zd of %zu: %m",\ + "%s: short packet, got %u expected %zu: %m",\ __FUNCTION__,\ - spacket_read, ulen_from_header); + pkt_total, TAC_PLUS_HDR_SIZE + ulen_from_header); re->msg = xstrdup(acct_syserr_msg); re->status = LIBTAC_STATUS_SHORT_BODY; - free(tb); return re->status; } /* decrypt the body */ - _tac_crypt((u_char *) tb, &th); + _tac_crypt(sess, (u_char *) tb, th); /* Convert network byte order to host byte order */ tb->msg_len = ntohs(tb->msg_len); tb->data_len = ntohs(tb->data_len); /* check the length fields */ - len_from_body=sizeof(tb->msg_len) + sizeof(tb->data_len) + - sizeof(tb->status) + tb->msg_len + tb->data_len; + len_from_body = TAC_ACCT_REPLY_FIXED_FIELDS_SIZE + \ + tb->msg_len + tb->data_len; if(ulen_from_header != len_from_body) { TACSYSLOG(LOG_ERR,\ - "%s: inconsistent reply body, incorrect key?",\ - __FUNCTION__); + "%s: inconsistent reply body, header len %zu versus parsed len %zu",\ + __FUNCTION__, ulen_from_header, len_from_body); re->msg = xstrdup(acct_syserr_msg); re->status = LIBTAC_STATUS_PROTOCOL_ERR; - free(tb); return re->status; } /* save status and clean up */ if(tb->msg_len) { - msg=(char *) xcalloc(1, tb->msg_len+1); + msg = xcalloc(1, tb->msg_len+1); bcopy((u_char *) tb+TAC_ACCT_REPLY_FIXED_FIELDS_SIZE, msg, tb->msg_len); - msg[(int)tb->msg_len] = '\0'; + msg[tb->msg_len] = '\0'; re->msg = msg; /* Freed by caller */ } + if(tb->data_len) { + msg = xcalloc(1, tb->data_len+1); + bcopy((u_char *) tb+TAC_ACCT_REPLY_FIXED_FIELDS_SIZE+tb->data_len, + msg, tb->data_len); + msg[tb->data_len] = '\0'; + re->data = msg; /* Freed by caller */ + } + /* server logged our request successfully */ if (tb->status == TAC_PLUS_ACCT_STATUS_SUCCESS) { TACDEBUG(LOG_DEBUG, "%s: accounted ok", __FUNCTION__); if (!re->msg) re->msg = xstrdup(acct_ok_msg); re->status = tb->status; - free(tb); return re->status; } @@ -162,6 +125,95 @@ int tac_acct_read(int fd, struct areply *re) { break; } - free(tb); return re->status; -} +} /* tac_acct_parse */ + +/* + * return value: + * < 0 : error status code, see LIBTAC_STATUS_... + * LIBTAC_STATUS_READ_TIMEOUT + * LIBTAC_STATUS_SHORT_HDR + * LIBTAC_STATUS_SHORT_BODY + * LIBTAC_STATUS_PROTOCOL_ERR + * >= 0 : server response, see TAC_PLUS_AUTHEN_STATUS_... + */ +int tac_acct_read(struct tac_session *sess, struct areply *re) { + HDR *th; + struct acct_reply *tb = NULL; + size_t ulen_from_header; + ssize_t spacket_read; + int status, timeleft = 0; + + re->attr = NULL; /* unused */ + re->msg = re->data = NULL; + + if (tac_readtimeout_enable && + tac_read_wait(sess->fd, tac_timeout * 1000, TAC_PLUS_HDR_SIZE, &timeleft) < 0 ) { + TACSYSLOG(LOG_ERR,\ + "%s: reply timeout after %u secs", __FUNCTION__, tac_timeout); + re->msg = xstrdup(acct_syserr_msg); + re->status = LIBTAC_STATUS_READ_TIMEOUT; + return re->status; + } + + th = xcalloc(1, TAC_PLUS_HDR_SIZE); + + spacket_read = read(sess->fd, th, TAC_PLUS_HDR_SIZE); + if(spacket_read < TAC_PLUS_HDR_SIZE) { + TACSYSLOG(LOG_ERR,\ + "%s: short reply header, read %zd of %u expected: %m", __FUNCTION__,\ + ((spacket_read >= 0) ? spacket_read : 0), TAC_PLUS_HDR_SIZE); + re->msg = xstrdup(acct_syserr_msg); + re->status = LIBTAC_STATUS_SHORT_HDR; + free(th); + return re->status; + } + + ulen_from_header = ntohl(th->datalength); + if (ulen_from_header > TAC_PLUS_MAX_PACKET_SIZE) { + TACSYSLOG(LOG_ERR,\ + "%s: length declared in the packet %zu exceeds max allowed packet size %u", __FUNCTION__,\ + ulen_from_header, TAC_PLUS_MAX_PACKET_SIZE); + re->msg = xstrdup(acct_syserr_msg); + re->status = LIBTAC_STATUS_PROTOCOL_ERR; + free(th); + return re->status; + } + + /* now make room for entire contiguous packet */ + th = xrealloc(th, TAC_PLUS_HDR_SIZE + ulen_from_header); + tb = tac_hdr_to_body(th); + + /* read reply packet body */ + if (tac_readtimeout_enable && + tac_read_wait(sess->fd, timeleft, ulen_from_header, NULL) < 0 ) { + TACSYSLOG(LOG_ERR,\ + "%s: reply timeout after %u secs", __FUNCTION__, tac_timeout); + re->msg = xstrdup(acct_syserr_msg); + re->status = LIBTAC_STATUS_READ_TIMEOUT; + free(th); + return re->status; + } + + spacket_read = read(sess->fd, tb, ulen_from_header); + if(spacket_read < 0 || (size_t) spacket_read < ulen_from_header) { + TACSYSLOG(LOG_ERR,\ + "%s: short reply body, read %zd of %zu: %m",\ + __FUNCTION__,\ + ((spacket_read >= 0) ? spacket_read : 0), ulen_from_header); + re->msg = xstrdup(acct_syserr_msg); + re->status = LIBTAC_STATUS_SHORT_BODY; + free(th); + return re->status; + } + + /* now parse remaining packet fields */ + status = tac_acct_parse(sess, (u_char *)th, + TAC_PLUS_HDR_SIZE + ulen_from_header, re); + + /* all useful data has been copied out */ + free(th); + + return status; +} /* tac_acct_read */ + diff --git a/libtac/lib/acct_s.c b/libtac/lib/acct_s.c index db680672..5bc6e511 100644 --- a/libtac/lib/acct_s.c +++ b/libtac/lib/acct_s.c @@ -22,7 +22,7 @@ #include "libtac.h" #include "xalloc.h" -char *tac_acct_flag2str(int flag) { +char *tac_acct_flag2str(u_char flag) { switch(flag) { case TAC_PLUS_ACCT_FLAG_MORE: return "more"; @@ -37,143 +37,127 @@ char *tac_acct_flag2str(int flag) { } } -/* - * return value: - * 0 : success - * < 0 : error status code, see LIBTAC_STATUS_... - * LIBTAC_STATUS_WRITE_ERR - * LIBTAC_STATUS_WRITE_TIMEOUT (pending impl) - * LIBTAC_STATUS_ASSEMBLY_ERR (pending impl) - */ -int tac_acct_send(int fd, int type, const char *user, char *tty, - char *r_addr, struct tac_attrib *attr) { +/* allocate and format an Accounting Start packet */ +void tac_acct_send_pkt(struct tac_session *sess, u_char type, + const char *user, const char *tty, const char *r_addr, + struct tac_attrib *attr, u_char **_pkt, unsigned *_len) { HDR *th; - struct acct tb; - u_char user_len, port_len, r_addr_len; + struct acct *tb; + unsigned user_len, port_len, r_addr_len; struct tac_attrib *a; - int i = 0; /* arg count */ - int pkt_len = 0; - int pktl = 0; - int w; /* write count */ + unsigned i; /* arg count */ u_char *pkt=NULL; - /* u_char *pktp; */ /* obsolute */ - int ret = 0; - - th = _tac_req_header(TAC_PLUS_ACCT, 0); - - /* set header options */ - th->version=TAC_PLUS_VER_0; - th->encryption=tac_encryption ? TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG; + unsigned pkt_total, pkt_len = 0; TACDEBUG(LOG_DEBUG, "%s: user '%s', tty '%s', rem_addr '%s', encrypt: %s, type: %s", \ __FUNCTION__, user, tty, r_addr, \ - (tac_encryption) ? "yes" : "no", \ + (sess->tac_encryption) ? "yes" : "no", \ tac_acct_flag2str(type)); - - user_len=(u_char) strlen(user); - port_len=(u_char) strlen(tty); - r_addr_len=(u_char) strlen(r_addr); - - tb.flags=(u_char) type; - tb.authen_method=tac_authen_method; - tb.priv_lvl=tac_priv_lvl; - if (!*tac_login) { - /* default to PAP */ - tb.authen_type = TAC_PLUS_AUTHEN_TYPE_PAP; - } else { - if (strcmp(tac_login,"chap") == 0) { - tb.authen_type=TAC_PLUS_AUTHEN_TYPE_CHAP; - } else if(strcmp(tac_login,"login") == 0) { - tb.authen_type=TAC_PLUS_AUTHEN_TYPE_ASCII; - } else { - tb.authen_type=TAC_PLUS_AUTHEN_TYPE_PAP; - } - } - tb.authen_service=tac_authen_service; - tb.user_len=user_len; - tb.port_len=port_len; - tb.r_addr_len=r_addr_len; - - /* allocate packet */ - pkt=(u_char *) xcalloc(1, TAC_ACCT_REQ_FIXED_FIELDS_SIZE); - pkt_len=sizeof(tb); - - /* fill attribute length fields */ - a = attr; - while (a) { - pktl = pkt_len; - pkt_len += sizeof(a->attr_len); - pkt = (u_char*) xrealloc(pkt, pkt_len); - - /* see comments in author_s.c - pktp=pkt + pkt_len; - pkt_len += sizeof(a->attr_len); - pkt = xrealloc(pkt, pkt_len); - */ - - bcopy(&a->attr_len, pkt + pktl, sizeof(a->attr_len)); - i++; - - a = a->next; + + /* + * precompute the buffer size so we don't need to keep resizing/copying it + */ + user_len = strlen(user); + port_len = strlen(tty); + r_addr_len = strlen(r_addr); + + assert(user_len <= UCHAR_MAX); + assert(port_len <= UCHAR_MAX); + assert(r_addr_len <= UCHAR_MAX); + +#define TAC_ACCT_REQ_FIXED_TOTAL \ + (TAC_PLUS_HDR_SIZE + TAC_ACCT_REQ_FIXED_FIELDS_SIZE) + + pkt_total = TAC_ACCT_REQ_FIXED_TOTAL + user_len + port_len + r_addr_len; + + /* ... add in attributes */ + for (i = 0, a = attr; a; a = a->next, ++i) { + pkt_total += a->attr_len + 1; /* count length byte too */ } + pkt = xcalloc(1, pkt_total); + th = (HDR *)pkt; + + /* tacacs header */ + th->version = TAC_PLUS_VER_0; + th->type = TAC_PLUS_ACCT; + th->seq_no = ++sess->seq_no; + th->encryption = sess->tac_encryption ? TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG + | (sess->tac_multiplex ? TAC_PLUS_SINGLE_CONNECT_FLAG : 0); + th->session_id = htonl(sess->tac_session_id); + th->datalength = htonl(pkt_total - TAC_PLUS_HDR_SIZE); + + /* fixed part of tacacs body */ + tb = tac_hdr_to_body(th); + tb->flags = type; + tb->authen_method = sess->tac_authen_method; + tb->priv_lvl = sess->tac_priv_lvl; + tb->authen_type = sess->tac_authen_type; + tb->authen_service = sess->tac_authen_service; + tb->user_len = user_len; + tb->port_len = port_len; + tb->r_addr_len = r_addr_len; + /* fill the arg count field and add the fixed fields to packet */ - tb.arg_cnt = i; - bcopy(&tb, pkt, TAC_ACCT_REQ_FIXED_FIELDS_SIZE); + tb->arg_cnt = i; + + pkt_len = TAC_ACCT_REQ_FIXED_TOTAL + i; /* reserve room for lengths */ - /* -#define PUTATTR(data, len) \ - pktp = pkt + pkt_len; \ - pkt_len += len; \ - pkt = xrealloc(pkt, pkt_len); \ - bcopy(data, pktp, len); - */ #define PUTATTR(data, len) \ - pktl = pkt_len; \ - pkt_len += len; \ - pkt = (u_char*) xrealloc(pkt, pkt_len); \ - bcopy(data, pkt + pktl, len); + bcopy(data, pkt + pkt_len, len); \ + pkt_len += len /* fill user and port fields */ - PUTATTR(user, user_len) - PUTATTR(tty, port_len) - PUTATTR(r_addr, r_addr_len) + PUTATTR(user, user_len); + PUTATTR(tty, port_len); + PUTATTR(r_addr, r_addr_len); /* fill attributes */ - a = attr; - while(a) { - PUTATTR(a->attr, a->attr_len) - a = a->next; + for (i = 0, a = attr; a; a = a->next, ++i) { + tb->arg_len[i] = a->attr_len; + PUTATTR(a->attr, a->attr_len); } - /* finished building packet, fill len_from_header in header */ - th->datalength = htonl(pkt_len); - - /* write header */ - w = write(fd, th, TAC_PLUS_HDR_SIZE); + assert(pkt_len == pkt_total); - if(w < TAC_PLUS_HDR_SIZE) { - TACSYSLOG(LOG_ERR, "%s: short write on header, wrote %d of %d: %m",\ - __FUNCTION__, w, TAC_PLUS_HDR_SIZE); - free(pkt); - free(th); - return LIBTAC_STATUS_WRITE_ERR; - } - /* encrypt packet body */ - _tac_crypt(pkt, th); + _tac_crypt(sess, (u_char *)tb, th); + + *_pkt = pkt; + *_len = pkt_total; +} /* tac_acct_send_pkt */ - /* write body */ - w=write(fd, pkt, pkt_len); - if(w < pkt_len) { - TACSYSLOG(LOG_ERR, "%s: short write on body, wrote %d of %d: %m",\ - __FUNCTION__, w, pkt_len); +/* + * return value: + * 0 : success + * < 0 : error status code, see LIBTAC_STATUS_... + * LIBTAC_STATUS_WRITE_ERR + * LIBTAC_STATUS_WRITE_TIMEOUT (pending impl) + * LIBTAC_STATUS_ASSEMBLY_ERR (pending impl) + */ +int tac_acct_send(struct tac_session *sess, + u_char type, const char *user, + const char *tty, const char *r_addr, struct tac_attrib *attr) { + + u_char *pkt = NULL; + unsigned pkt_total = 0; + int w, ret = 0; + + /* generate the packet */ + tac_acct_send_pkt(sess, type, user, tty, r_addr, attr, &pkt, &pkt_total); + + /* write packet */ + w = write(sess->fd, pkt, pkt_total); + + if(w < 0 || (unsigned) w < pkt_total) { + TACSYSLOG(LOG_ERR, "%s: short write of packet, wrote %d of %d: %m",\ + __FUNCTION__, w, pkt_total); ret = LIBTAC_STATUS_WRITE_ERR; } free(pkt); - free(th); TACDEBUG(LOG_DEBUG, "%s: exit status=%d", __FUNCTION__, ret); return ret; -} +} /* tac_acct_send */ + diff --git a/libtac/lib/attrib.c b/libtac/lib/attrib.c index b8a7d822..80e1ad6e 100644 --- a/libtac/lib/attrib.c +++ b/libtac/lib/attrib.c @@ -29,14 +29,14 @@ void tac_add_attrib(struct tac_attrib **attr, char *name, char *value) { void tac_add_attrib_pair(struct tac_attrib **attr, char *name, char sep, char *value) { struct tac_attrib *a; - u_char l1 = (u_char) strlen(name); - u_char l2; - int total_len; + unsigned l1 = (u_char) strlen(name); + unsigned l2; + unsigned total_len; if (value == NULL) { l2 = 0; } else { - l2 = (u_char) strlen(value); + l2 = strlen(value); } total_len = l1 + l2 + 1; /* "name" + "=" + "value" */ @@ -49,7 +49,7 @@ void tac_add_attrib_pair(struct tac_attrib **attr, char *name, char sep, char *v /* initialize the list if application passed us a null pointer */ if(*attr == NULL) { - *attr = (struct tac_attrib *) xcalloc(1, sizeof(struct tac_attrib)); + *attr = xcalloc(1, sizeof(struct tac_attrib)); a = *attr; } else { /* find the last allocated block */ @@ -57,7 +57,7 @@ void tac_add_attrib_pair(struct tac_attrib **attr, char *name, char sep, char *v while(a->next != NULL) a = a->next; /* a holds last allocated block */ - a->next = (struct tac_attrib *) xcalloc(1, sizeof(struct tac_attrib)); + a->next = xcalloc(1, sizeof(struct tac_attrib)); a = a->next; /* set current block pointer to the new one */ } @@ -67,7 +67,7 @@ void tac_add_attrib_pair(struct tac_attrib **attr, char *name, char sep, char *v /* fill the block */ a->attr_len=total_len; - a->attr = (char *) xcalloc(1, total_len+1); + a->attr = xcalloc(1, total_len+1); bcopy(name, a->attr, l1); /* paste name */ *(a->attr+l1)=sep; /* insert seperator "[=*]" */ if (value != NULL) { diff --git a/libtac/lib/authen_r.c b/libtac/lib/authen_r.c index e89faa6f..7b773cb9 100644 --- a/libtac/lib/authen_r.c +++ b/libtac/lib/authen_r.c @@ -23,10 +23,7 @@ #include "libtac.h" #include "messages.h" -/* reads packet from TACACS+ server; returns: - * TAC_PLUS_AUTHEN_STATUS_PASS if the authentication succeded - * an other integer if failed. Check tacplus.h for all possible values - * +/* * return value: * < 0 : error status code, see LIBTAC_STATUS_... * LIBTAC_STATUS_READ_TIMEOUT @@ -35,93 +32,54 @@ * LIBTAC_STATUS_PROTOCOL_ERR * >= 0 : server response, see TAC_PLUS_AUTHEN_STATUS_... */ -int tac_authen_read(int fd, struct areply *re) { - HDR th; +int tac_authen_parse(struct tac_session *sess, struct areply *re, + u_char *pkt, unsigned pkt_total) { + + HDR *th = (HDR *)pkt; struct authen_reply *tb = NULL; size_t len_from_header, len_from_body; - ssize_t spacket_read; char *msg = NULL; - int timeleft = 0; - memset(re, 0, sizeof(struct areply)); - - /* read the reply header */ - if (tac_readtimeout_enable - && tac_read_wait(fd, tac_timeout * 1000, TAC_PLUS_HDR_SIZE, - &timeleft) < 0) { - TACSYSLOG( - LOG_ERR, "%s: reply timeout after %u secs", __FUNCTION__, tac_timeout); - re->status = LIBTAC_STATUS_READ_TIMEOUT; - free(tb); - return re->status; - } - spacket_read = read(fd, &th, TAC_PLUS_HDR_SIZE); - if (spacket_read < TAC_PLUS_HDR_SIZE) { - TACSYSLOG( - LOG_ERR, "%s: short reply header, read %zd of %d: %m", __FUNCTION__, spacket_read, TAC_PLUS_HDR_SIZE); - re->status = LIBTAC_STATUS_SHORT_HDR; - free(tb); - return re->status; - } + memset(re, 0, sizeof(*re)); /* check the reply fields in header */ - msg = _tac_check_header(&th, TAC_PLUS_AUTHEN); + msg = _tac_check_header(sess, th, TAC_PLUS_AUTHEN); if (msg != NULL) { re->msg = xstrdup(msg); re->status = LIBTAC_STATUS_PROTOCOL_ERR; - free(tb); return re->status; } - re->seq_no = th.seq_no; + len_from_header = ntohl(th->datalength); - len_from_header = ntohl(th.datalength); - if (len_from_header > TAC_PLUS_MAX_PACKET_SIZE) { - TACSYSLOG( - LOG_ERR, "%s: length declared in the packet %zu exceeds max packet size %d", __FUNCTION__, len_from_header, TAC_PLUS_MAX_PACKET_SIZE); - re->status = LIBTAC_STATUS_PROTOCOL_ERR; - free(tb); - return re->status; - } - tb = (struct authen_reply *) xcalloc(1, len_from_header); + tb = tac_hdr_to_body(th); - /* read reply packet body */ - if (tac_readtimeout_enable - && tac_read_wait(fd, timeleft, len_from_header, NULL) < 0) { - TACSYSLOG( - LOG_ERR, "%s: reply timeout after %u secs", __FUNCTION__, tac_timeout); - re->msg = xstrdup(authen_syserr_msg); - re->status = LIBTAC_STATUS_READ_TIMEOUT; - free(tb); - return re->status; - } - spacket_read = read(fd, tb, len_from_header); - if (spacket_read < (ssize_t) len_from_header) { + if (pkt_total != TAC_PLUS_HDR_SIZE + len_from_header) { TACSYSLOG( - LOG_ERR, "%s: short reply body, read %zd of %zu: %m", __FUNCTION__, spacket_read, len_from_header); + LOG_ERR, "%s: short packet, got %u expected %zu: %m", __FUNCTION__, + pkt_total, TAC_PLUS_HDR_SIZE + len_from_header); re->msg = xstrdup(authen_syserr_msg); re->status = LIBTAC_STATUS_SHORT_BODY; - free(tb); return re->status; } /* decrypt the body */ - _tac_crypt((u_char *) tb, &th); + _tac_crypt(sess, (u_char *) tb, th); /* Convert network byte order to host byte order */ tb->msg_len = ntohs(tb->msg_len); tb->data_len = ntohs(tb->data_len); /* check the length fields */ - len_from_body = sizeof(tb->status) + sizeof(tb->flags) + sizeof(tb->msg_len) - + sizeof(tb->data_len) + tb->msg_len + tb->data_len; + len_from_body = TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE + \ + tb->msg_len + tb->data_len; if (len_from_header != len_from_body) { TACSYSLOG( - LOG_ERR, "%s: inconsistent reply body, incorrect key?", __FUNCTION__); + LOG_ERR, "%s: inconsistent reply body, header len %zu versus parsed len %zu", + __FUNCTION__, len_from_header, len_from_body); re->msg = xstrdup(protocol_err_msg); re->status = LIBTAC_STATUS_PROTOCOL_ERR; - free(tb); return re->status; } @@ -130,23 +88,28 @@ int tac_authen_read(int fd, struct areply *re) { if (0 < tb->msg_len) { msg = xcalloc(tb->msg_len + 1, sizeof(char)); - memset(msg, 0, (tb->msg_len + 1)); - memcpy(msg, (char*) tb + sizeof(struct authen_reply), tb->msg_len); - + memcpy(msg, tb->msg, tb->msg_len); + msg[tb->msg_len] = '\0'; re->msg = msg; } + if (0 < tb->data_len) { + msg = xcalloc(tb->data_len + 1, sizeof(char)); + /* first byte beyond msg is data */ + memcpy(msg, &tb->msg[tb->msg_len], tb->data_len); + msg[tb->data_len] = '\0'; + re->data = msg; + } + /* server authenticated username and password successfully */ if (re->status == TAC_PLUS_AUTHEN_STATUS_PASS) { TACDEBUG(LOG_DEBUG, "%s: authentication ok", __FUNCTION__); - free(tb); return re->status; } /* server ask for continue packet with password */ if (re->status == TAC_PLUS_AUTHEN_STATUS_GETPASS) { TACDEBUG(LOG_DEBUG, "%s: continue packet with password needed", __FUNCTION__); - free(tb); return re->status; } @@ -155,15 +118,97 @@ int tac_authen_read(int fd, struct areply *re) { re->flags = tb->flags; TACDEBUG(LOG_DEBUG, "%s: continue packet with data request: msg=%.*s", - __func__, tb->msg_len, (char*)tb + sizeof(struct authen_reply)); - free(tb); + __func__, tb->msg_len, tb->msg); return re->status; } TACDEBUG(LOG_DEBUG, "%s: authentication failed, server reply status=%d", __FUNCTION__, re->status); - free(tb); return re->status; +} /* tac_authen_parse_pkt */ + +/* reads packet from TACACS+ server; returns: + * TAC_PLUS_AUTHEN_STATUS_PASS if the authentication succeded + * an other integer if failed. Check tacplus.h for all possible values + * + * return value: + * < 0 : error status code, see LIBTAC_STATUS_... + * LIBTAC_STATUS_READ_TIMEOUT + * LIBTAC_STATUS_SHORT_HDR + * LIBTAC_STATUS_SHORT_BODY + * LIBTAC_STATUS_PROTOCOL_ERR + * >= 0 : server response, see TAC_PLUS_AUTHEN_STATUS_... + */ +int tac_authen_read(struct tac_session *sess, struct areply *re) { + HDR *th; + struct authen_reply *tb = NULL; + size_t len_from_header; + int r, status, timeleft = 0; + + memset(re, 0, sizeof(*re)); + + /* read the reply header */ + if (tac_readtimeout_enable && + tac_read_wait(sess->fd, tac_timeout * 1000, TAC_PLUS_HDR_SIZE, &timeleft) < 0 ) { + TACSYSLOG(LOG_ERR, + "%s: reply timeout after %d secs", __FUNCTION__, tac_timeout); + re->status = LIBTAC_STATUS_READ_TIMEOUT; + return re->status; + } + + th = xcalloc(1, TAC_PLUS_HDR_SIZE); + + r = read(sess->fd, th, TAC_PLUS_HDR_SIZE); + if (r < TAC_PLUS_HDR_SIZE) { + TACSYSLOG(LOG_ERR, + "%s: short reply header, read %d of %u: %m", __FUNCTION__, + ((r >= 0) ? r : 0), TAC_PLUS_HDR_SIZE); + re->status = LIBTAC_STATUS_SHORT_HDR; + free(th); + return re->status; + } + + len_from_header = ntohl(th->datalength); + + if (len_from_header > TAC_PLUS_MAX_PACKET_SIZE) { + TACSYSLOG(LOG_ERR, + "%s: excessively long packet, got %zu bytes", __FUNCTION__, + TAC_PLUS_HDR_SIZE + len_from_header); + status = LIBTAC_STATUS_PROTOCOL_ERR; + free(th); + return status; + } + + /* now make room for entire contiguous packet */ + th = xrealloc(th, TAC_PLUS_HDR_SIZE + len_from_header); + tb = tac_hdr_to_body(th); + + /* read reply packet body */ + if (tac_readtimeout_enable && + tac_read_wait(sess->fd, timeleft, len_from_header, NULL) < 0 ) { + TACSYSLOG(LOG_ERR, + "%s: reply timeout after %d secs", __FUNCTION__, tac_timeout); + status = LIBTAC_STATUS_READ_TIMEOUT; + } + + r = read(sess->fd, tb, len_from_header); + if (r < 0 || (unsigned) r < len_from_header) { + TACSYSLOG(LOG_ERR, + "%s: short reply body, read %d of %zu: %m", + __FUNCTION__, + ((r >= 0) ? r : 0), len_from_header); + status = LIBTAC_STATUS_SHORT_BODY; + free(th); + return status; + } + + /* now parse remaining packet fields */ + status = tac_authen_parse(sess, re, (u_char *)th, TAC_PLUS_HDR_SIZE + len_from_header); + + /* all useful data has been copied out */ + free(th); + + return status; } /* tac_authen_read */ diff --git a/libtac/lib/authen_s.c b/libtac/lib/authen_s.c index 8c18b779..e4f9042a 100644 --- a/libtac/lib/authen_s.c +++ b/libtac/lib/authen_s.c @@ -51,6 +51,32 @@ digest_chap(u_char digest[MD5_LBLOCK], uint8_t id, MD5_Final(digest, &mdcontext); } +uint8_t tac_get_authen_type(const char *login) +{ + if (login && *login) { + if (!strcmp(login, "chap")) { + return TAC_PLUS_AUTHEN_TYPE_CHAP; + } else if (!strcmp(login, "login")) { + return TAC_PLUS_AUTHEN_TYPE_ASCII; + } + } + /* default to PAP */ + return TAC_PLUS_AUTHEN_TYPE_PAP; +} + +const char *tac_get_authen_string(uint8_t type) +{ + const char *authen_types[5] = { + "ascii", "pap", "chap", "arap", "mschap" + }; + + if (TAC_PLUS_AUTHEN_TYPE_ASCII <= type + && type <= TAC_PLUS_AUTHEN_TYPE_MSCHAP) + return authen_types[type - TAC_PLUS_AUTHEN_TYPE_ASCII]; + + return "???"; +} + /* this function sends a packet do TACACS+ server, asking * for validation of given username and password * @@ -61,35 +87,23 @@ digest_chap(u_char digest[MD5_LBLOCK], uint8_t id, * LIBTAC_STATUS_WRITE_TIMEOUT * LIBTAC_STATUS_ASSEMBLY_ERR */ -int tac_authen_send(int fd, const char *user, const char *pass, const char *tty, - const char *r_addr, u_char action) { +/* allocate and format an Authentication Start packet */ +void tac_authen_send_pkt(struct tac_session *sess, + const char *user, const char *pass, const char *tty, + const char *r_addr, u_char action, u_char **_pkt, unsigned *_len) { HDR *th; /* TACACS+ packet header */ - struct authen_start tb; /* message body */ - int user_len, pass_len, port_len, chal_len, token_len, bodylength, w; - int r_addr_len; - int pkt_len = 0; - int ret = 0; - char *chal = "1234123412341234"; + struct authen_start *tb; /* message body */ + unsigned user_len, pass_len, port_len, chal_len, r_addr_len, token_len; + const char *chal = "1234123412341234"; char *token = NULL; u_char *pkt = NULL; + unsigned pkt_total, pkt_len = 0; const uint8_t id = 5; - th = _tac_req_header(TAC_PLUS_AUTHEN, 0); - - /* set some header options */ - if (!strcmp(tac_login, "login")) { - th->version = TAC_PLUS_VER_0; - } else { - th->version = TAC_PLUS_VER_1; - } - th->encryption = - tac_encryption ? - TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG; - TACDEBUG(LOG_DEBUG, "%s: user '%s', tty '%s', rem_addr '%s', encrypt: %s", __FUNCTION__, user, tty, r_addr, - (tac_encryption) ? "yes" : "no"); + (sess->tac_encryption) ? "yes" : "no"); /* get size of submitted data */ user_len = strlen(user); @@ -98,7 +112,7 @@ int tac_authen_send(int fd, const char *user, const char *pass, const char *tty, port_len = strlen(tty); r_addr_len = strlen(r_addr); - if (!strcmp(tac_login, "chap")) { + if (sess->tac_authen_type == TAC_PLUS_AUTHEN_TYPE_CHAP) { u_char digest[MD5_LBLOCK]; digest_chap(digest, id, pass, pass_len, chal, chal_len); @@ -113,84 +127,108 @@ int tac_authen_send(int fd, const char *user, const char *pass, const char *tty, token_len = strlen(token); } - /* fill the body of message */ - tb.action = action; - tb.priv_lvl = tac_priv_lvl; - if (!*tac_login) { - /* default to PAP */ - tb.authen_type = + assert(user_len <= UCHAR_MAX); + assert(port_len <= UCHAR_MAX); + assert(r_addr_len <= UCHAR_MAX); + assert(token_len <= UCHAR_MAX); + +#define TAC_AUTHEN_START_FIXED_TOTAL \ + (TAC_PLUS_HDR_SIZE + TAC_AUTHEN_START_FIXED_FIELDS_SIZE) + + /* + * precompute the buffer size so we don't need to keep resizing/copying it + */ + pkt_total = TAC_AUTHEN_START_FIXED_TOTAL + + user_len + port_len + r_addr_len + token_len; + + pkt = xcalloc(1, pkt_total); + th = (HDR *)pkt; + + /* set some header options */ + if (sess->tac_authen_type == TAC_PLUS_AUTHEN_TYPE_ASCII) { + th->version = TAC_PLUS_VER_0; + } else { + th->version = TAC_PLUS_VER_1; + } + th->type = TAC_PLUS_AUTHEN; + th->seq_no = ++sess->seq_no; + th->encryption = (sess->tac_encryption ? TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG) + | (sess->tac_multiplex ? TAC_PLUS_SINGLE_CONNECT_FLAG : 0); + th->session_id = htonl(sess->tac_session_id); + th->datalength = htonl(pkt_total - TAC_PLUS_HDR_SIZE); + + /* fixed part of tacacs body */ + tb = tac_hdr_to_body(th); + tb->action = TAC_PLUS_AUTHEN_LOGIN; + tb->priv_lvl = sess->tac_priv_lvl; + if (sess->tac_authen_type == TAC_PLUS_AUTHEN_TYPE_PAP) { + tb->authen_type = TAC_PLUS_AUTHEN_CHPASS == action ? TAC_PLUS_AUTHEN_TYPE_ASCII : TAC_PLUS_AUTHEN_TYPE_PAP; } else { - if (!strcmp(tac_login, "chap")) { - tb.authen_type = TAC_PLUS_AUTHEN_TYPE_CHAP; - } else if (!strcmp(tac_login, "login")) { - tb.authen_type = TAC_PLUS_AUTHEN_TYPE_ASCII; - } else { - tb.authen_type = TAC_PLUS_AUTHEN_TYPE_PAP; - } + tb->authen_type = sess->tac_authen_type; } - tb.service = tac_authen_service; - tb.user_len = user_len; - tb.port_len = port_len; - tb.r_addr_len = r_addr_len; /* may be e.g Caller-ID in future */ - tb.data_len = token_len; + tb->service = sess->tac_authen_service; + tb->user_len = user_len; + tb->port_len = port_len; + tb->r_addr_len = r_addr_len; /* may be e.g Caller-ID in future */ + tb->data_len = token_len; - /* fill body length in header */ - bodylength = sizeof(tb) + user_len + port_len + r_addr_len + token_len; + pkt_len = TAC_AUTHEN_START_FIXED_TOTAL; - th->datalength = htonl(bodylength); +#define PUTATTR(data, len) \ + bcopy(data, pkt + pkt_len, len); \ + pkt_len += len - /* we can now write the header */ - w = write(fd, th, TAC_PLUS_HDR_SIZE); - if (w < 0 || w < TAC_PLUS_HDR_SIZE) { - TACSYSLOG( - LOG_ERR, "%s: short write on header, wrote %d of %d: %m", __FUNCTION__, w, TAC_PLUS_HDR_SIZE); - free(token); - free(pkt); - free(th); - return LIBTAC_STATUS_WRITE_ERR; - } + /* fill user, port, rem_addr, data fields */ + PUTATTR(user, user_len); + PUTATTR(tty, port_len); + PUTATTR(r_addr, r_addr_len); + PUTATTR(token, token_len); - /* build the packet */ - pkt = (u_char *) xcalloc(1, bodylength + 10); + /* no longer need token */ + free(token); - bcopy(&tb, pkt + pkt_len, sizeof(tb)); /* packet body beginning */ - pkt_len += sizeof(tb); - bcopy(user, pkt + pkt_len, user_len); /* user */ - pkt_len += user_len; - bcopy(tty, pkt + pkt_len, port_len); /* tty */ - pkt_len += port_len; - bcopy(r_addr, pkt + pkt_len, r_addr_len); /* rem addr */ - pkt_len += r_addr_len; + /* encrypt packet body */ + _tac_crypt(sess, (u_char *)tb, th); - bcopy(token, pkt + pkt_len, token_len); /* password */ - pkt_len += token_len; + *_pkt = pkt; + *_len = pkt_total; +} /* tac_authen_send_pkt */ - /* pkt_len == bodylength ? */ - if (pkt_len != bodylength) { - TACSYSLOG( - LOG_ERR, "%s: bodylength %d != pkt_len %d", __FUNCTION__, bodylength, pkt_len); - free(token); - free(pkt); - free(th); - return LIBTAC_STATUS_ASSEMBLY_ERR; - } +/* this function sends a packet do TACACS+ server, asking + * for validation of given username and password + * + * return value: + * 0 : success + * < 0 : error status code, see LIBTAC_STATUS_... + * LIBTAC_STATUS_WRITE_ERR + * LIBTAC_STATUS_WRITE_TIMEOUT + * LIBTAC_STATUS_ASSEMBLY_ERR + */ +int tac_authen_send(struct tac_session *sess, + const char *user, const char *pass, const char *tty, + const char *r_addr, u_char action) { - /* encrypt the body */ - _tac_crypt(pkt, th); + u_char *pkt = NULL; + unsigned pkt_total = 0; + int w, ret = 0; + + /* generate the packet */ + tac_authen_send_pkt(sess, user, pass, tty, r_addr, action, &pkt, &pkt_total); - w = write(fd, pkt, pkt_len); - if (w < 0 || w < pkt_len) { + /* we can now write the packet */ + w = write(sess->fd, pkt, pkt_total); + if (w < 0 || (unsigned) w < pkt_total) { TACSYSLOG( - LOG_ERR, "%s: short write on body, wrote %d of %d: %m", __FUNCTION__, w, pkt_len); + LOG_ERR, "%s: short write on packet, wrote %d of %u: %m", __FUNCTION__, w, pkt_total); ret = LIBTAC_STATUS_WRITE_ERR; } - free(token); free(pkt); - free(th); + TACDEBUG(LOG_DEBUG, "%s: exit status=%d", __FUNCTION__, ret); + return ret; } /* tac_authen_send */ diff --git a/libtac/lib/author_r.c b/libtac/lib/author_r.c index 148f7eaa..fe5f3b0a 100644 --- a/libtac/lib/author_r.c +++ b/libtac/lib/author_r.c @@ -23,13 +23,7 @@ #include "libtac.h" #include "messages.h" -/* This function returns structure containing - 1. status (granted/denied) - 2. message for the user - 3. list of attributes returned by server - The attributes should be applied to service authorization - is requested for. - * +/* * return value: * < 0 : error status code, see LIBTAC_STATUS_... * LIBTAC_STATUS_READ_TIMEOUT @@ -38,83 +32,40 @@ * LIBTAC_STATUS_PROTOCOL_ERR * >= 0 : server response, see TAC_PLUS_AUTHOR_STATUS_... */ -int tac_author_read(int fd, struct areply *re) { - HDR th; +int tac_author_parse(struct tac_session *sess, + u_char *pkt, unsigned pkt_total, struct areply *re) { + + HDR *th = (HDR *)pkt; struct author_reply *tb = NULL; size_t len_from_header, len_from_body; - ssize_t packet_read; - u_char *pktp = NULL; char *msg = NULL; - int timeleft = 0; - re->msg = NULL; unsigned int r = 0; - bzero(re, sizeof(struct areply)); - if (tac_readtimeout_enable - && tac_read_wait(fd, tac_timeout * 1000, TAC_PLUS_HDR_SIZE, - &timeleft) < 0) { - - TACSYSLOG( - LOG_ERR, "%s: reply timeout after %u secs", __FUNCTION__, tac_timeout); - re->msg = xstrdup(author_syserr_msg); - re->status = LIBTAC_STATUS_READ_TIMEOUT; - free(tb); - return re->status; - } - - packet_read = read(fd, &th, TAC_PLUS_HDR_SIZE); - if (packet_read < TAC_PLUS_HDR_SIZE) { - TACSYSLOG( - LOG_ERR, "%s: short reply header, read %zd of %d: %m", __FUNCTION__, packet_read, TAC_PLUS_HDR_SIZE); - re->msg = xstrdup(author_syserr_msg); - re->status = LIBTAC_STATUS_SHORT_HDR; - free(tb); - return re->status; - } + bzero(re, sizeof(*re)); /* check header consistency */ - msg = _tac_check_header(&th, TAC_PLUS_AUTHOR); + msg = _tac_check_header(sess, th, TAC_PLUS_AUTHOR); if (msg != NULL) { /* no need to process body if header is broken */ re->msg = xstrdup(msg); re->status = LIBTAC_STATUS_PROTOCOL_ERR; - free(tb); return re->status; } - len_from_header = ntohl(th.datalength); - if (len_from_header > TAC_PLUS_MAX_PACKET_SIZE) { - TACSYSLOG( - LOG_ERR, "%s: length declared in the packet %zu exceeds max packet size %d", __FUNCTION__, len_from_header, TAC_PLUS_MAX_PACKET_SIZE); - re->status = LIBTAC_STATUS_PROTOCOL_ERR; - free(tb); - return re->status; - } - tb = (struct author_reply *) xcalloc(1, len_from_header); + len_from_header = ntohl(th->datalength); - /* read reply packet body */ - if (tac_readtimeout_enable - && tac_read_wait(fd, timeleft, len_from_header, NULL) < 0) { + tb = tac_hdr_to_body(th); + if (pkt_total != TAC_PLUS_HDR_SIZE + len_from_header) { TACSYSLOG( - LOG_ERR, "%s: reply timeout after %u secs", __FUNCTION__, tac_timeout); - re->msg = xstrdup(author_syserr_msg); - re->status = LIBTAC_STATUS_READ_TIMEOUT; - free(tb); - return re->status; - } - packet_read = read(fd, tb, len_from_header); - if (packet_read < (ssize_t) len_from_header) { - TACSYSLOG( - LOG_ERR, "%s: short reply body, read %zd of %zu", __FUNCTION__, packet_read, len_from_header); + LOG_ERR, "%s: short packet, got %u of %zu", __FUNCTION__, pkt_total, len_from_header); re->msg = xstrdup(author_syserr_msg); re->status = LIBTAC_STATUS_SHORT_BODY; - free(tb); return re->status; } /* decrypt the body */ - _tac_crypt((u_char *) tb, &th); + _tac_crypt(sess, (u_char *) tb, th); /* Convert network byte order to host byte order */ tb->msg_len = ntohs(tb->msg_len); @@ -127,55 +78,52 @@ int tac_author_read(int fd, struct areply *re) { len_from_body = TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE + tb->msg_len + tb->data_len; - pktp = (u_char *) tb + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE; - /* cycle through the arguments supplied in the packet */ - for (r = 0; r < tb->arg_cnt && r < TAC_PLUS_MAX_ARGCOUNT; - r++) { - if ((ssize_t) len_from_body > packet_read - || ((void *) pktp - (void *) tb) > packet_read) { + for (r = 0; ; r++) { + if (len_from_body > pkt_total) { TACSYSLOG( LOG_ERR, "%s: arguments supplied in packet seem to exceed its size", __FUNCTION__); re->msg = xstrdup(protocol_err_msg); re->status = LIBTAC_STATUS_PROTOCOL_ERR; - free(tb); return re->status; } - len_from_body += sizeof(u_char); /* add arg length field's size*/ - len_from_body += *pktp; /* add arg length itself */ - pktp++; + + if (r == tb->arg_cnt) + break; + + len_from_body += sizeof(tb->arg_len[0]) + tb->arg_len[r]; } if (len_from_header != len_from_body) { TACSYSLOG( - LOG_ERR, "%s: inconsistent reply body, incorrect key?", __FUNCTION__); + LOG_ERR, "%s: inconsistent reply body, header len %zu versus parsed len %zu", __FUNCTION__, len_from_header, len_from_body); re->msg = xstrdup(protocol_err_msg); re->status = LIBTAC_STATUS_PROTOCOL_ERR; - free(tb); return re->status; } /* packet seems to be consistent, prepare return messages */ + /* server message for user */ if (tb->msg_len) { - char *msg = (char *) xcalloc(1, tb->msg_len + 1); + char *msg = xcalloc(1, tb->msg_len + 1); bcopy( (u_char *) tb + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE - + (tb->arg_cnt) * sizeof(u_char), msg, tb->msg_len); - msg[(int) tb->msg_len] = '\0'; + + (tb->arg_cnt) * sizeof(tb->arg_len[0]), msg, tb->msg_len); + msg[tb->msg_len] = '\0'; re->msg = msg; /* freed by caller */ } /* server message to syslog */ if (tb->data_len) { - char *smsg = (char *) xcalloc(1, tb->data_len + 1); + char *smsg = xcalloc(1, tb->data_len + 1); bcopy( (u_char *) tb + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE - + (tb->arg_cnt) * sizeof(u_char) + tb->msg_len, smsg, + + (tb->arg_cnt) * sizeof(tb->arg_len[0]) + tb->msg_len, smsg, tb->data_len); - smsg[(int) tb->data_len] = '\0'; + smsg[tb->data_len] = '\0'; + re->data = smsg; /* Freed by caller */ TACSYSLOG(LOG_ERR, "%s: reply message: %s", __FUNCTION__, smsg); - free(smsg); } TACDEBUG(LOG_DEBUG, "%s: authorization reply status=%d", @@ -186,6 +134,9 @@ int tac_author_read(int fd, struct areply *re) { /* success conditions */ /* XXX support optional vs mandatory arguments */ case TAC_PLUS_AUTHOR_STATUS_PASS_REPL: + /* @@@ we bzero'd the pointer at the top of this function, + * so there's nothing left to free here! + */ tac_free_attrib(&re->attr); case TAC_PLUS_AUTHOR_STATUS_PASS_ADD: { @@ -197,10 +148,11 @@ int tac_author_read(int fd, struct areply *re) { /* add attributes received to attribute list returned to the client */ - pktp = (u_char *) tb + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE; - argp = pktp + (tb->arg_cnt * sizeof(u_char)) + tb->msg_len - + tb->data_len; + argp = (u_char *) tb + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE + + (tb->arg_cnt * sizeof(tb->arg_len[0])) + tb->msg_len + + tb->data_len; TACSYSLOG(LOG_DEBUG, "Args cnt %d", tb->arg_cnt); + /* argp points to current argument string pktp points to current argument length */ for (r = 0; r < tb->arg_cnt && r < TAC_PLUS_MAX_ARGCOUNT; @@ -210,12 +162,15 @@ int tac_author_read(int fd, struct areply *re) { char *value; char sepchar = '='; - bcopy(argp, buff, *pktp); - buff[*pktp] = '\0'; - sep = strchr(buff, '='); + bcopy(argp, buff, tb->arg_len[r]); + buff[tb->arg_len[r]] = '\0'; + + sep = strchr(buff, sepchar); + if (sep == NULL) { sep = strchr(buff, '*'); } + if (sep == NULL) { TACSYSLOG( LOG_WARNING, "AUTHOR_STATUS_PASS_ADD/REPL: av pair does not contain a separator: %s", buff); @@ -227,20 +182,17 @@ int tac_author_read(int fd, struct areply *re) { *sep = '\0'; value = ++sep; /* now buff points to attribute name, - value to the attribute value */ + value to the attribute value (only + buff needs to be freed). */ } TACSYSLOG(LOG_DEBUG, "Adding buf/value pair (%s,%s)", buff, value); tac_add_attrib_pair(&re->attr, buff, sepchar, value); - argp += *pktp; - pktp++; + argp += tb->arg_len[r]; } - } - free(tb); - return re->status; + break; } - switch (tb->status) { /* authorization failure conditions */ /* failing to follow is allowed by RFC, page 23 */ case TAC_PLUS_AUTHOR_STATUS_FOLLOW: @@ -255,8 +207,101 @@ int tac_author_read(int fd, struct areply *re) { if (!re->msg) re->msg = xstrdup(author_err_msg); re->status = TAC_PLUS_AUTHOR_STATUS_ERROR; + break; } - free(tb); return re->status; } + +/* This function returns structure containing + 1. status (granted/denied) + 2. message for the user + 3. list of attributes returned by server + The attributes should be applied to service authorization + is requested for. + * + * return value: + * < 0 : error status code, see LIBTAC_STATUS_... + * LIBTAC_STATUS_READ_TIMEOUT + * LIBTAC_STATUS_SHORT_HDR + * LIBTAC_STATUS_SHORT_BODY + * LIBTAC_STATUS_PROTOCOL_ERR + * >= 0 : server response, see TAC_PLUS_AUTHOR_STATUS_... + */ +int tac_author_read(struct tac_session *sess, struct areply *re) { + HDR *th; + struct author_reply *tb = NULL; + size_t len_from_header; + ssize_t packet_read; + int timeleft = 0; + + bzero(re, sizeof(*re)); + + if (tac_readtimeout_enable + && tac_read_wait(sess->fd, tac_timeout * 1000, TAC_PLUS_HDR_SIZE, + &timeleft) < 0) { + + TACSYSLOG( + LOG_ERR, "%s: reply timeout after %d secs", __FUNCTION__, tac_timeout); + re->msg = xstrdup(author_syserr_msg); + re->status = LIBTAC_STATUS_READ_TIMEOUT; + return re->status; + } + + th = xcalloc(1, TAC_PLUS_HDR_SIZE); + + packet_read = read(sess->fd, th, TAC_PLUS_HDR_SIZE); + if (packet_read < TAC_PLUS_HDR_SIZE) { + TACSYSLOG( + LOG_ERR, "%s: short reply header, read %zd of %u: %m", __FUNCTION__, + ((packet_read >= 0) ? packet_read : 0), TAC_PLUS_HDR_SIZE); + re->msg = xstrdup(author_syserr_msg); + re->status = LIBTAC_STATUS_SHORT_HDR; + free(th); + return re->status; + } + + len_from_header = ntohl(th->datalength); + if (len_from_header > TAC_PLUS_MAX_PACKET_SIZE) { + TACSYSLOG( + LOG_ERR, "%s: length declared in the packet %zu exceeds max packet size %d", __FUNCTION__, len_from_header, TAC_PLUS_MAX_PACKET_SIZE); + re->msg = xstrdup(author_syserr_msg); + re->status = LIBTAC_STATUS_PROTOCOL_ERR; + free(th); + return re->status; + } + + /* now make room for entire contiguous packet */ + th = xrealloc(th, TAC_PLUS_HDR_SIZE + len_from_header); + tb = tac_hdr_to_body(th); + + /* read reply packet body */ + if (tac_readtimeout_enable + && tac_read_wait(sess->fd, timeleft, len_from_header, NULL) < 0) { + TACSYSLOG( + LOG_ERR, "%s: reply timeout after %u secs", __FUNCTION__, tac_timeout); + re->msg = xstrdup(author_syserr_msg); + re->status = LIBTAC_STATUS_READ_TIMEOUT; + free(th); + return re->status; + } + packet_read = read(sess->fd, tb, len_from_header); + if (packet_read < 0 || (size_t) packet_read < len_from_header) { + TACSYSLOG( + LOG_ERR, "%s: short reply body, read %zd of %zu: %m", __FUNCTION__, ((packet_read >= 0) ? packet_read : 0), len_from_header); + re->msg = xstrdup(author_syserr_msg); + re->status = LIBTAC_STATUS_SHORT_BODY; + free(th); + return re->status; + } + + /* now parse remaining packet fields */ + (void) tac_author_parse(sess, (u_char *)th, TAC_PLUS_HDR_SIZE + len_from_header, + re); + + /* all useful data has been copied out */ + free(th); + + return re->status; +} /* tac_author_read */ + diff --git a/libtac/lib/author_s.c b/libtac/lib/author_s.c index d067e2c0..4109a8ec 100644 --- a/libtac/lib/author_s.c +++ b/libtac/lib/author_s.c @@ -22,147 +22,126 @@ #include "libtac.h" #include "xalloc.h" -/* Send authorization request to the server, along with attributes - specified in attribute list prepared with tac_add_attrib. - * - * return value: - * 0 : success - * < 0 : error status code, see LIBTAC_STATUS_... - * LIBTAC_STATUS_WRITE_ERR - * LIBTAC_STATUS_WRITE_TIMEOUT (pending impl) - * LIBTAC_STATUS_ASSEMBLY_ERR (pending impl) - */ -int tac_author_send(int fd, const char *user, char *tty, char *r_addr, - struct tac_attrib *attr) { +/* allocate and format an Authorization Start packet */ +void tac_author_send_pkt(struct tac_session *sess, + const char *user, const char *tty, const char *r_addr, + struct tac_attrib *attr, u_char **_pkt, unsigned *_len) { HDR *th; - struct author tb; - u_char user_len, port_len, r_addr_len; + struct author *tb; + unsigned user_len, port_len, r_addr_len; struct tac_attrib *a; - int i = 0; /* attributes count */ - int pkt_len = 0; /* current packet length */ - int pktl = 0; /* temporary storage for previous pkt_len values */ - int w; /* write() return value */ + unsigned i; /* attributes count */ u_char *pkt = NULL; /* packet building pointer */ - /* u_char *pktp; *//* obsolete */ - int ret = 0; - - th = _tac_req_header(TAC_PLUS_AUTHOR, 0); - - /* set header options */ - th->version = TAC_PLUS_VER_0; - th->encryption = - tac_encryption ? - TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG; + unsigned pkt_total, pkt_len = 0; TACDEBUG(LOG_DEBUG, "%s: user '%s', tty '%s', rem_addr '%s', encrypt: %s", __FUNCTION__, user, - tty, r_addr, tac_encryption ? "yes" : "no"); - - user_len = (u_char) strlen(user); - port_len = (u_char) strlen(tty); - r_addr_len = (u_char) strlen(r_addr); - - tb.authen_method = tac_authen_method; - tb.priv_lvl = tac_priv_lvl; - if (!*tac_login) { - /* default to PAP */ - tb.authen_type = TAC_PLUS_AUTHEN_TYPE_PAP; - } else { - if (strcmp(tac_login, "chap") == 0) { - tb.authen_type = TAC_PLUS_AUTHEN_TYPE_CHAP; - } else if (strcmp(tac_login, "login") == 0) { - tb.authen_type = TAC_PLUS_AUTHEN_TYPE_ASCII; - } else { - tb.authen_type = TAC_PLUS_AUTHEN_TYPE_PAP; - } - } - tb.service = tac_authen_service; - tb.user_len = user_len; - tb.port_len = port_len; - tb.r_addr_len = r_addr_len; - - /* allocate packet */ - pkt = (u_char *) xcalloc(1, TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE); - pkt_len = sizeof(tb); - - /* fill attribute length fields */ - a = attr; - while (a) { - pktl = pkt_len; - pkt_len += sizeof(a->attr_len); - pkt = (u_char*) xrealloc(pkt, pkt_len); - - /* bad method: realloc() is allowed to return different pointer - with each call - pktp=pkt + pkt_len; - pkt_len += sizeof(a->attr_len); - pkt = xrealloc(pkt, pkt_len); - */ - - bcopy(&a->attr_len, pkt + pktl, sizeof(a->attr_len)); - i++; - - a = a->next; - } + tty, r_addr, sess->tac_encryption ? "yes" : "no"); - /* fill the arg count field and add the fixed fields to packet */ - tb.arg_cnt = i; - bcopy(&tb, pkt, TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE); /* - #define PUTATTR(data, len) \ - pktp = pkt + pkt_len; \ - pkt_len += len; \ - pkt = xrealloc(pkt, pkt_len); \ - bcopy(data, pktp, len); + * precompute the buffer size so we don't need to keep resizing/copying it */ + user_len = strlen(user); + port_len = strlen(tty); + r_addr_len = strlen(r_addr); -#define PUTATTR(data, len) \ - pktl = pkt_len; \ - pkt_len += len; \ - pkt = (u_char*) xrealloc(pkt, pkt_len); \ - bcopy(data, pkt + pktl, len); + assert(user_len <= UCHAR_MAX); + assert(port_len <= UCHAR_MAX); + assert(r_addr_len <= UCHAR_MAX); - /* fill user and port fields */ - PUTATTR(user, user_len) - PUTATTR(tty, port_len) - PUTATTR(r_addr, r_addr_len) +#define TAC_AUTHOR_REQ_FIXED_TOTAL \ + (TAC_PLUS_HDR_SIZE + TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE) - /* fill attributes */ - a = attr; - while (a) { - PUTATTR(a->attr, a->attr_len) + pkt_total = TAC_AUTHOR_REQ_FIXED_TOTAL + user_len + port_len + r_addr_len; - a = a->next; + /* ... add in attributes */ + for (i = 0, a = attr; a; a = a->next, ++i) { + pkt_total += a->attr_len + 1; /* count length byte too */ } - /* finished building packet, fill len_from_header in header */ - th->datalength = htonl(pkt_len); + pkt = xcalloc(1, pkt_total); + th = (HDR *)pkt; - /* write header */ - w = write(fd, th, TAC_PLUS_HDR_SIZE); + /* tacacs header */ + th->version = TAC_PLUS_VER_0; + th->type = TAC_PLUS_AUTHOR; + th->seq_no = ++sess->seq_no; + th->encryption = (sess->tac_encryption ? TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG) + | (sess->tac_multiplex ? TAC_PLUS_SINGLE_CONNECT_FLAG : 0); + th->session_id = htonl(sess->tac_session_id); + th->datalength = htonl(pkt_total - TAC_PLUS_HDR_SIZE); + + /* fixed part of tacacs body */ + tb = tac_hdr_to_body(th); + tb->authen_method = sess->tac_authen_method; + tb->priv_lvl = sess->tac_priv_lvl; + tb->authen_type = sess->tac_authen_type; + tb->service = sess->tac_authen_service; + tb->user_len = user_len; + tb->port_len = port_len; + tb->r_addr_len = r_addr_len; - if (w < TAC_PLUS_HDR_SIZE) { - TACSYSLOG( - LOG_ERR, "%s: short write on header, wrote %d of %d: %m", __FUNCTION__, w, TAC_PLUS_HDR_SIZE); - free(pkt); - free(th); - return LIBTAC_STATUS_WRITE_ERR; + /* fill the arg count field and add the fixed fields to packet */ + tb->arg_cnt = i; + + pkt_len = TAC_AUTHOR_REQ_FIXED_TOTAL + i; /* reserve room for lengths */ + +#define PUTATTR(data, len) \ + bcopy(data, pkt + pkt_len, len); \ + pkt_len += len + + /* fill user and port fields */ + PUTATTR(user, user_len); + PUTATTR(tty, port_len); + PUTATTR(r_addr, r_addr_len); + + /* fill attributes */ + for (i = 0, a = attr; a; a = a->next, i++) { + tb->arg_len[i] = a->attr_len; + PUTATTR(a->attr, a->attr_len); } + assert(pkt_len == pkt_total); + /* encrypt packet body */ - _tac_crypt(pkt, th); + _tac_crypt(sess, (u_char *)tb, th); - /* write body */ - w = write(fd, pkt, pkt_len); - if (w < pkt_len) { - TACSYSLOG( - LOG_ERR, "%s: short write on body, wrote %d of %d: %m", __FUNCTION__, w, pkt_len); + *_pkt = pkt; + *_len = pkt_total; +} /* tac_author_send_pkt */ + +/* Send authorization request to the server, along with attributes + specified in attribute list prepared with tac_add_attrib. + * + * return value: + * 0 : success + * < 0 : error status code, see LIBTAC_STATUS_... + * LIBTAC_STATUS_WRITE_ERR + * LIBTAC_STATUS_WRITE_TIMEOUT (pending impl) + * LIBTAC_STATUS_ASSEMBLY_ERR (pending impl) + */ +int tac_author_send(struct tac_session *sess, + const char *user, const char *tty, const char *r_addr, + struct tac_attrib *attr) { + + u_char *pkt = NULL; + unsigned pkt_total = 0; + int w, ret = 0; + + /* generate the packet */ + tac_author_send_pkt(sess, user, tty, r_addr, attr, &pkt, &pkt_total); + + /* write packet */ + w = write(sess->fd, pkt, pkt_total); + if (w < 0 || (unsigned) w < pkt_total) { + TACSYSLOG(LOG_ERR, "%s: short write on packet, wrote %d of %d: %m",\ + __FUNCTION__, w, pkt_total); ret = LIBTAC_STATUS_WRITE_ERR; } free(pkt); - free(th); TACDEBUG(LOG_DEBUG, "%s: exit status=%d", __FUNCTION__, ret); return ret; -} +} /* tac_author_send */ + diff --git a/libtac/lib/connect.c b/libtac/lib/connect.c index 314d60e1..bf0b515e 100644 --- a/libtac/lib/connect.c +++ b/libtac/lib/connect.c @@ -39,35 +39,38 @@ int tac_timeout = 5; in server table. * return value: - * >= 0 : valid fd + * == 0 : success * < 0 : error status code, see LIBTAC_STATUS_... */ -int tac_connect(struct addrinfo **server, char **key, int servers) { - int tries; - int fd=-1; +int tac_connect(struct tac_session *sess, + struct addrinfo **server, unsigned servers) { + unsigned tries; + int retval = -1; + + TACDEBUG(LOG_DEBUG, "session %p connect (%u addrs)", sess, servers); if(servers == 0 || server == NULL) { TACSYSLOG(LOG_ERR, "%s: no TACACS+ servers defined", __FUNCTION__); } else { for ( tries = 0; tries < servers; tries++ ) { - if((fd=tac_connect_single(server[tries], key[tries], NULL, tac_timeout)) >= 0 ) { - /* tac_secret was set in tac_connect_single on success */ + if ((retval = tac_connect_single(sess, server[tries], NULL, tac_timeout)) >= 0 ) { break; } } } /* all attempts failed if fd is still < 0 */ - TACDEBUG(LOG_DEBUG, "%s: exit status=%d",__FUNCTION__, fd); - return fd; + TACDEBUG(LOG_DEBUG, "%s: exit status=%d: %m", __FUNCTION__, retval); + return retval; } /* tac_connect */ /* return value: - * >= 0 : valid fd + * == 0 : success * < 0 : error status code, see LIBTAC_STATUS_... */ -int tac_connect_single(const struct addrinfo *server, const char *key, struct addrinfo *srcaddr, int timeout) { +int tac_connect_single(struct tac_session *sess, + const struct addrinfo *server, struct addrinfo *srcaddr, int timeout) { int retval = LIBTAC_STATUS_CONN_ERR; /* default retval */ int fd = -1; int flags, rc; @@ -77,6 +80,8 @@ int tac_connect_single(const struct addrinfo *server, const char *key, struct ad struct sockaddr_storage addr; char *ip; + TACDEBUG(LOG_DEBUG, "session %p connect", sess); + if(server == NULL) { TACSYSLOG(LOG_ERR, "%s: no TACACS+ server defined", __FUNCTION__); goto bomb; @@ -86,7 +91,7 @@ int tac_connect_single(const struct addrinfo *server, const char *key, struct ad ip = tac_ntop(server->ai_addr); if((fd=socket(server->ai_family, server->ai_socktype, server->ai_protocol)) < 0) { - TACSYSLOG(LOG_ERR,"%s: socket creation error: %s", __FUNCTION__, + TACSYSLOG(LOG_ERR,"%s: socket creation error: %s", __FUNCTION__,\ strerror(errno)); goto bomb; } @@ -104,7 +109,7 @@ int tac_connect_single(const struct addrinfo *server, const char *key, struct ad /* bind if source address got explicity defined */ if (srcaddr) { if (bind(fd, srcaddr->ai_addr, srcaddr->ai_addrlen) < 0) { - TACSYSLOG(LOG_ERR, "%s: Failed to bind source address: %s", + TACSYSLOG(LOG_ERR, "%s: Failed to bind source address: %s",\ __FUNCTION__, strerror(errno)); goto bomb; } @@ -133,6 +138,7 @@ int tac_connect_single(const struct addrinfo *server, const char *key, struct ad /* timeout */ if ( rc == 0 ) { + TACDEBUG(LOG_ERR, "%s: timeout", __FUNCTION__); retval = LIBTAC_STATUS_CONN_TIMEOUT; goto bomb; } @@ -161,24 +167,32 @@ int tac_connect_single(const struct addrinfo *server, const char *key, struct ad /* connected ok */ TACDEBUG(LOG_DEBUG, "%s: connected to %s", __FUNCTION__, ip); - retval = fd; + retval = 0; - /* set current tac_secret */ - tac_encryption = 0; - if (key != NULL && *key) { - tac_encryption = 1; - tac_secret = key; - } + /* now stuff the fd into the tac_session */ + if (sess->fd >= 0) + close(sess->fd); + sess->fd = fd; bomb: if (retval < 0 && fd != -1) close(fd); TACDEBUG(LOG_DEBUG, "%s: exit status=%d (fd=%d)",\ - __FUNCTION__, retval < 0 ? retval:0, fd); + __FUNCTION__, retval, fd); return retval; } /* tac_connect_single */ +void +tac_close(struct tac_session *sess) +{ + TACDEBUG(LOG_DEBUG, "closing %p", sess); + + if (sess->fd >= 0) { + close(sess->fd); + sess->fd = -1; + } +} /* return value: * ptr to char* with format IP address diff --git a/libtac/lib/cont_s.c b/libtac/lib/cont_s.c index e2815672..89e1f317 100644 --- a/libtac/lib/cont_s.c +++ b/libtac/lib/cont_s.c @@ -25,84 +25,95 @@ # include "md5.h" #endif -/* this function sends a continue packet do TACACS+ server, asking - * for validation of given password - * - * return value: - * 0 : success - * < 0 : error status code, see LIBTAC_STATUS_... - * LIBTAC_STATUS_WRITE_ERR - * LIBTAC_STATUS_WRITE_TIMEOUT (pending impl) - * LIBTAC_STATUS_ASSEMBLY_ERR - */ -int tac_cont_send_seq(int fd, const char *pass, int seq) { +/* allocate and format an continue packet */ +void tac_cont_send_pkt(struct tac_session *sess, const char *pass, + u_char **_pkt, unsigned *_len) { + HDR *th; /* TACACS+ packet header */ - struct authen_cont tb; /* continue body */ - int pass_len, bodylength, w; - int pkt_len = 0; - int ret = 0; + struct authen_cont *tb; /* continue body */ + unsigned pass_len; u_char *pkt = NULL; + unsigned pkt_total, pkt_len = 0; - th = _tac_req_header(TAC_PLUS_AUTHEN, 1); + /* get size of submitted data */ + pass_len = strlen(pass); + + assert(pass_len <= UCHAR_MAX); + +#define TAC_AUTHEN_CONT_FIXED_TOTAL \ + (TAC_PLUS_HDR_SIZE + TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE) + + /* + * precompute the buffer size so we don't need to keep resizing/copying it + */ + pkt_total = TAC_AUTHEN_CONT_FIXED_TOTAL + pass_len; + + /* build the packet */ + pkt = xcalloc(1, pkt_total); + th = (HDR *)pkt; /* set some header options */ th->version = TAC_PLUS_VER_0; - th->seq_no = seq; /* 1 = request, 2 = reply, 3 = continue, 4 = reply */ - th->encryption = - tac_encryption ? - TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG; + th->type = TAC_PLUS_AUTHEN; + th->seq_no = ++sess->seq_no; + th->encryption = (sess->tac_encryption ? TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG) + | (sess->tac_multiplex ? TAC_PLUS_SINGLE_CONNECT_FLAG : 0); + th->session_id = htonl(sess->tac_session_id); + th->datalength = htonl(pkt_total - TAC_PLUS_HDR_SIZE); - /* get size of submitted data */ - pass_len = strlen(pass); + /* fixed part of tacacs body */ + tb = tac_hdr_to_body(th); + tb->flags = 0; + tb->user_msg_len = htons(pass_len); + tb->user_data_len = 0; - /* fill the body of message */ - tb.user_msg_len = htons(pass_len); - tb.user_data_len = tb.flags = 0; + pkt_len = TAC_AUTHEN_CONT_FIXED_TOTAL; /* reserve room for lengths */ - /* fill body length in header */ - bodylength = TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE + 0 + pass_len; +#define PUTATTR(data, len) \ + bcopy(data, pkt + pkt_len, len); \ + pkt_len += len - th->datalength = htonl(bodylength); + PUTATTR(pass, pass_len); - /* we can now write the header */ - w = write(fd, th, TAC_PLUS_HDR_SIZE); - if (w < 0 || w < TAC_PLUS_HDR_SIZE) { - TACSYSLOG( - LOG_ERR, "%s: short write on header, wrote %d of %d: %m", __FUNCTION__, w, TAC_PLUS_HDR_SIZE); - free(pkt); - free(th); - return LIBTAC_STATUS_WRITE_ERR; - } + assert(pkt_len == pkt_total); - /* build the packet */ - pkt = (u_char *) xcalloc(1, bodylength); + /* encrypt the body */ + _tac_crypt(sess, (u_char *)tb, th); - bcopy(&tb, pkt + pkt_len, TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE); /* packet body beginning */ - pkt_len += TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE; - bcopy(pass, pkt + pkt_len, pass_len); /* password */ - pkt_len += pass_len; + *_pkt = pkt; + *_len = pkt_total; +} /* tac_cont_send */ - /* pkt_len == bodylength ? */ - if (pkt_len != bodylength) { - TACSYSLOG( - LOG_ERR, "%s: bodylength %d != pkt_len %d", __FUNCTION__, bodylength, pkt_len); - free(pkt); - free(th); - return LIBTAC_STATUS_ASSEMBLY_ERR; - } +/* this function sends a continue packet do TACACS+ server, asking + * for validation of given password + * + * return value: + * 0 : success + * < 0 : error status code, see LIBTAC_STATUS_... + * LIBTAC_STATUS_WRITE_ERR + * LIBTAC_STATUS_WRITE_TIMEOUT (pending impl) + * LIBTAC_STATUS_ASSEMBLY_ERR + */ +int tac_cont_send(struct tac_session *sess, const char *pass) { - /* encrypt the body */ - _tac_crypt(pkt, th); + u_char *pkt = NULL; + unsigned pkt_total = 0; + int w, ret = 0; + + /* generate the packet */ + tac_cont_send_pkt(sess, pass, &pkt, &pkt_total); - w = write(fd, pkt, pkt_len); - if (w < 0 || w < pkt_len) { + w = write(sess->fd, pkt, pkt_total); + if (w < 0 || (unsigned) w < pkt_total) { TACSYSLOG( - LOG_ERR, "%s: short write on body, wrote %d of %d: %m", __FUNCTION__, w, pkt_len); + LOG_ERR, "%s: short write on packet, wrote %d of %u: %m", __FUNCTION__, w, pkt_total); ret = LIBTAC_STATUS_WRITE_ERR; } free(pkt); - free(th); + TACDEBUG(LOG_DEBUG, "%s: exit status=%d", __FUNCTION__, ret); + return ret; } /* tac_cont_send */ + diff --git a/libtac/lib/crypt.c b/libtac/lib/crypt.c index b3e3158d..38c7b184 100644 --- a/libtac/lib/crypt.c +++ b/libtac/lib/crypt.c @@ -35,9 +35,9 @@ /* Produce MD5 pseudo-random pad for TACACS+ encryption. Use data from packet header and secret, which should be a global variable */ -static void _tac_md5_pad(const HDR *hdr, +static void _tac_md5_pad(const struct tac_session *sess, const HDR *hdr, u_char *new_digest, u_char *old_digest) { - unsigned tac_secret_len = strlen(tac_secret); + unsigned tac_secret_len = strlen(sess->tac_secret); MD5_CTX mdcontext; /* MD5_1 = MD5{session_id, secret, version, seq_no} @@ -46,7 +46,7 @@ static void _tac_md5_pad(const HDR *hdr, /* place session_id, key, version and seq_no in buffer */ MD5_Init(&mdcontext); MD5_Update(&mdcontext, (const u_char *) &hdr->session_id, sizeof(hdr->session_id)); - MD5_Update(&mdcontext, (const u_char *) tac_secret, tac_secret_len); + MD5_Update(&mdcontext, (const u_char *) sess->tac_secret, tac_secret_len); MD5_Update(&mdcontext, &hdr->version, sizeof(hdr->version)); MD5_Update(&mdcontext, &hdr->seq_no, sizeof(hdr->seq_no)); @@ -62,11 +62,11 @@ static void _tac_md5_pad(const HDR *hdr, /* Perform encryption/decryption on buffer. This means simply XORing each byte from buffer with according byte from pseudo-random pad. */ -void _tac_crypt(u_char *buf, const HDR *th) { +void _tac_crypt(const struct tac_session *sess, u_char *buf, const HDR *th) { unsigned i, j, length = ntohl(th->datalength); /* null operation if no encryption requested */ - if((tac_secret != NULL) && (th->encryption & TAC_PLUS_UNENCRYPTED_FLAG) != TAC_PLUS_UNENCRYPTED_FLAG) { + if ((sess->tac_secret != NULL) && (th->encryption & TAC_PLUS_UNENCRYPTED_FLAG) != TAC_PLUS_UNENCRYPTED_FLAG) { u_char digest[MD5_LBLOCK]; for (i=0; i<length; i++) { @@ -78,7 +78,7 @@ void _tac_crypt(u_char *buf, const HDR *th) { * the previous digest. */ if (j == 0) - _tac_md5_pad(th, digest, ((i > 0) ? digest : NULL)); + _tac_md5_pad(sess, th, digest, ((i > 0) ? digest : NULL)); buf[i] ^= digest[j]; } diff --git a/libtac/lib/hdr_check.c b/libtac/lib/hdr_check.c index 36866ff8..4804d9fc 100644 --- a/libtac/lib/hdr_check.c +++ b/libtac/lib/hdr_check.c @@ -29,22 +29,22 @@ * Returns pointer to error message * or NULL when the header seems to be correct */ -char *_tac_check_header(HDR *th, int type) { +char *_tac_check_header(struct tac_session *sess, HDR *th, uint8_t type) { if(th->type != type) { TACSYSLOG(LOG_ERR,\ "%s: unrelated reply, type %d, expected %d",\ __FUNCTION__, th->type, type); return protocol_err_msg; - } else if (1 == (th->seq_no % 2)) { - TACSYSLOG(LOG_ERR, "%s: not a reply - seq_no %d not even",\ - __FUNCTION__, th->seq_no); + } else if (++sess->seq_no != th->seq_no) { + TACSYSLOG(LOG_ERR, "%s: expected reply seq no %d but got %d",\ + __FUNCTION__, sess->seq_no, th->seq_no); return protocol_err_msg; - } /* else if(ntohl(th->session_id) != session_id) { + } else if(ntohl(th->session_id) != sess->tac_session_id) { TACSYSLOG(LOG_ERR,\ - "%s: unrelated reply, received session_id %d != sent %d",\ - __FUNCTION__, ntohl(th->session_id), session_id); + "%s: unrelated reply, received session_id %u != sent %u",\ + __FUNCTION__, ntohl(th->session_id), sess->tac_session_id); return protocol_err_msg; - } */ + } return NULL; /* header is ok */ } /* check header */ diff --git a/libtac/lib/header.c b/libtac/lib/header.c index 2156d2a4..89c71e79 100644 --- a/libtac/lib/header.c +++ b/libtac/lib/header.c @@ -28,33 +28,6 @@ #include "magic.h" -/* Miscellaneous variables that are global, because we need - * store their values between different functions and connections. - */ -/* Session identifier. */ -int session_id; - -/* Encryption flag. */ -int tac_encryption = 0; - -/* Pointer to TACACS+ shared secret string. */ -/* note: tac_secret will point to tacplus_server[i].key */ -const char *tac_secret = NULL; - -/* TACACS+ shared login string. */ -char tac_login[64]; /* default is PAP */ - -/* priv_lvl */ -int tac_priv_lvl = TAC_PLUS_PRIV_LVL_MIN; - -/* Authentication Method */ -int tac_authen_method = TAC_PLUS_AUTHEN_METH_TACACSPLUS; - -/* Service requesting authentication */ -int tac_authen_service = TAC_PLUS_AUTHEN_SVC_PPP; - -/* additional runtime flags */ - int tac_debug_enable = 0; int tac_readtimeout_enable = 0; @@ -66,21 +39,22 @@ int tac_readtimeout_enable = 0; * field depends on the TACACS+ request type and thus it * cannot be predefined. */ -HDR *_tac_req_header(u_char type, int cont_session) { +HDR *_tac_req_header(struct tac_session *sess, u_char type, bool cont_session) { HDR *th; th=(HDR *) xcalloc(1, TAC_PLUS_HDR_SIZE); /* preset some packet options in header */ th->type=type; - th->seq_no=1; /* always 1 for request */ - th->encryption=TAC_PLUS_ENCRYPTED_FLAG; + th->seq_no=++sess->seq_no; + th->encryption=(sess->tac_encryption ? TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG) + | (sess->tac_multiplex ? TAC_PLUS_SINGLE_CONNECT_FLAG : 0); /* make session_id from pseudo-random number */ if (!cont_session) { - session_id = magic(); + tac_session_new_session_id(sess); } - th->session_id = htonl(session_id); + th->session_id = htonl(sess->tac_session_id); return th; } diff --git a/libtac/lib/parse.c b/libtac/lib/parse.c new file mode 100644 index 00000000..67c1dd4c --- /dev/null +++ b/libtac/lib/parse.c @@ -0,0 +1,64 @@ +/* parse.c - Callback for dispatching received packets + * + * Copyright (C) 2016, Philip Prindeville <philipp@redfish-solutions.com> + * Copyright (C) 2016, Brocade Communications Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program - see the file COPYING. + * + * See `CHANGES' file for revision history. + */ + +#include <stdio.h> + +#include "libtac.h" + +void tac_parse_pkt(struct tac_session *sess, struct cb_ctx *ctx, u_char *pkt, unsigned len) +{ + HDR *th = (HDR *)pkt; + int status = -1; + struct areply re = { 0 }; + + switch (th->type) { + case TAC_PLUS_AUTHEN: + TACDEBUG(LOG_DEBUG, "session %p got authen %u bytes", sess, len); + status = tac_authen_parse(sess, &re, pkt, len); + break; + + case TAC_PLUS_AUTHOR: + TACDEBUG(LOG_DEBUG, "session %p got author %u bytes", sess, len); + status = tac_author_parse(sess, pkt, len, &re); + break; + + case TAC_PLUS_ACCT: + TACDEBUG(LOG_DEBUG, "session %p got account %u bytes", sess, len); + status = tac_acct_parse(sess, pkt, len, &re); + break; + + default: + TACDEBUG(LOG_INFO, "session %p got %u byte packet of %02x type; ignoring", \ + sess, len, th->type); + + break; + } + + if (sess->response_cb) { + TACDEBUG(LOG_DEBUG, "session %p user callback", sess); + sess->response_cb(sess, ctx, status, th->type, &re); + } + + tac_free_attrib(&re.attr); + free(re.msg); + free(re.data); +} + diff --git a/libtac/lib/session.c b/libtac/lib/session.c new file mode 100644 index 00000000..19292830 --- /dev/null +++ b/libtac/lib/session.c @@ -0,0 +1,136 @@ +/* session.c - Parameters for concurrent Tacacs+ sessions + * + * Copyright (C) 2016, Philip Prindeville <philipp@redfish-solutions.com> + * Copyright (C) 2016, Brocade Communications Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program - see the file COPYING. + * + * See `CHANGES' file for revision history. + */ + +#include "libtac.h" +#include "xalloc.h" + +/* + * like tac_session_alloc() but with n extra bytes of room at the bottom + */ + +struct tac_session * +tac_session_alloc_extra(unsigned n) +{ + struct tac_session *sess = (struct tac_session *)xcalloc(1, sizeof(*sess) + n); + + TACDEBUG(LOG_DEBUG, "session %p constructed (%u extra)", sess, n); + + sess->tac_timeout = 5; + sess->tac_secret = NULL; + sess->tac_session_id = magic(); + sess->tac_encryption = sess->tac_multiplex = false; + sess->tac_idle = true; + sess->tac_priv_lvl = TAC_PLUS_PRIV_LVL_MIN; + sess->tac_authen_service = TAC_PLUS_AUTHEN_SVC_PPP; + sess->tac_authen_method = TAC_PLUS_AUTHEN_METH_TACACSPLUS; + sess->tac_authen_type = TAC_PLUS_AUTHEN_TYPE_PAP; + sess->seq_no = 0; + sess->fd = -1; + sess->context.sess = sess; + + return sess; +} + +struct tac_session * +tac_session_alloc(void) +{ + return tac_session_alloc_extra(0); +} + +void +tac_session_set_secret(struct tac_session *sess, const char *secret) +{ + if (secret == NULL || !*secret) { + TACDEBUG(LOG_DEBUG, "session %p clearing session secret", sess); + sess->tac_encryption = false; + sess->tac_secret = NULL; + } else { + TACDEBUG(LOG_DEBUG, "session %p setting session secret", sess); + sess->tac_encryption = true; + sess->tac_secret = secret; + } +} + +void +tac_session_set_authen_type(struct tac_session *sess, uint8_t type) +{ + sess->tac_authen_type = type; +} + +void +tac_session_set_timeout(struct tac_session *sess, unsigned timeout) +{ + sess->tac_timeout = timeout; +} + +void +tac_session_set_multiplex(struct tac_session *sess, bool multiplex) +{ + sess->tac_multiplex = multiplex; +} + +void +tac_session_set_response(struct tac_session *sess, response_cb_t cb) +{ + sess->response_cb = cb; +} + +void +tac_session_set_oob(struct tac_session *sess, oob_cb_t cb) +{ + sess->oob_cb = cb; +} + +struct cb_ctx * +tac_session_get_context(struct tac_session *sess) +{ + return &sess->context; +} + +void +tac_session_new_session_id(struct tac_session *sess) +{ + uint32_t id = magic(); + + TACDEBUG(LOG_DEBUG, "session %p new id %#08x", sess, id); + sess->tac_session_id = id; +} + +void +tac_session_reset_seq(struct tac_session *sess) +{ + sess->seq_no = 0; +} + +void * +tac_session_get_user_data(struct tac_session *sess) +{ + return &sess->user_data; +} + +void +tac_session_free(struct tac_session *sess) +{ + TACDEBUG(LOG_DEBUG, "session %p destroy", sess); + tac_close(sess); + free(sess); +} + diff --git a/libtac/lib/wrappers.c b/libtac/lib/wrappers.c new file mode 100644 index 00000000..b4315bed --- /dev/null +++ b/libtac/lib/wrappers.c @@ -0,0 +1,470 @@ +/* wrappers.c - Add libevent2 wrappers + * + * Copyright (C) 2016, Philip Prindeville <philipp@redfish-solutions.com> + * Copyright (C) 2016, Brocade Communications Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program - see the file COPYING. + * + * See `CHANGES' file for revision history. + */ +#include <stdio.h> + +#include "libtac.h" +#include "xalloc.h" +#include "../../config.h" + +#include <event2/event.h> +#include <event2/buffer.h> +#include <event2/bufferevent.h> + +void * +tac_event_loop_initialize() +{ + struct event_base *ev_base = NULL; + + struct event_config *cfg = event_config_new(); + + TACDEBUG(LOG_DEBUG, "constructing event base"); + + /* can tailor methods later */ + ev_base = event_base_new_with_config(cfg); + + /* event_base_priority_init(ev_base, ev_priorities); */ + + /* no longer needed */ + event_config_free(cfg); + + TACDEBUG(LOG_DEBUG, "return event_base %p", ev_base); + + return ev_base; +} + +/* + * Loops until there are no more events. + * @return: 0 on normal exit, 1 if no more events are queued and -1 on error. + */ +int +tac_event_loop(void *tac_event) +{ + struct event_base *ev_base = (struct event_base *) tac_event; + int ret; + + TACDEBUG(LOG_DEBUG, "running event_base loop %p", ev_base); + + /* run until an event handler tells us to shut down via tac_end_loop() */ + /* EVLOOP_NO_EXIT_ON_EMPTY is not supported in older libevent versions. */ + ret = event_base_loop(ev_base, 0); + + TACDEBUG(LOG_DEBUG, "event_base loop returns %d", ret); + + return ret; +} + +void +tac_event_loop_end(void *tac_event) +{ + struct event_base *ev_base = (struct event_base *) tac_event; + + TACDEBUG(LOG_DEBUG, "ending event_base %p", ev_base); + + (void)event_base_loopexit(ev_base, NULL); +} + +void +tac_event_loop_free(void *tac_event) +{ + struct event_base *ev_base = (struct event_base *) tac_event; + + TACDEBUG(LOG_DEBUG, "destroying event_base %p", ev_base); + + event_base_free(ev_base); +} + +void +tac_event_loop_global_shutdown(void) +{ +#if LIBEVENT_VERSION_NUMBER >= 0x02010100 + libevent_global_shutdown(); +#endif +} + +void tac_session_reset_timeouts(struct tac_session *sess, bool on) +{ + struct timeval tv = { sess->tac_timeout, 0 }; + + TACDEBUG(LOG_DEBUG, "session %p reset_timeouts %u %s", sess, sess->tac_timeout, (on ? "on" : "off")); + + if (!sess->bufev) + return; + + /* nothing will be enabled if we haven't yet connected... */ + if (bufferevent_get_enabled(sess->bufev) == 0) + return; + + if (on) + bufferevent_set_timeouts(sess->bufev, &tv, &tv); + else + bufferevent_set_timeouts(sess->bufev, NULL, NULL); +} + +static void eventcb(struct bufferevent *bev, short events, void *ptr) +{ + struct cb_ctx *ctx = (struct cb_ctx *)ptr; + struct tac_session *sess = ctx->sess; + +#if 0 + fprintf(stderr, "eventcb: bev=%p, events=%#x, ptr=%p\n", bev, (unsigned)events, ptr); + fputs(" flags =", stderr); + if (events & BEV_EVENT_READING) + fputs(" reading", stderr); + if (events & BEV_EVENT_WRITING) + fputs(" writing", stderr); + if (events & BEV_EVENT_EOF) + fputs(" eof", stderr); + if (events & BEV_EVENT_ERROR) + fputs(" error", stderr); + if (events & BEV_EVENT_TIMEOUT) + fputs(" timeout", stderr); + if (events & BEV_EVENT_CONNECTED) + fputs(" connected", stderr); + fputc('\n', stderr); +#endif + + if (sess->oob_cb) { + if (events & BEV_EVENT_CONNECTED) { + TACDEBUG(LOG_DEBUG, "session %p connected", sess); + /* + * if we had enqueued a request before the connect + * completed, then the idle flag would be false + * and we would want to reset the timer; if we didn't + * have a request on-the-wire, then the timeout gets + * cleared once we're connected. + */ + tac_session_reset_timeouts(sess, !sess->tac_idle); + (sess->oob_cb)(sess, &sess->context, CONNECTED); + } + if (events & BEV_EVENT_ERROR) { + TACDEBUG(LOG_DEBUG, "session %p errored", sess); + (sess->oob_cb)(sess, &sess->context, ERROR); + } + if (events & BEV_EVENT_TIMEOUT) { + TACDEBUG(LOG_DEBUG, "session %p timeout", sess); + (sess->oob_cb)(sess, &sess->context, TIMEOUT); + } + if (events & BEV_EVENT_EOF) { + TACDEBUG(LOG_DEBUG, "session %p closing", sess); + (sess->oob_cb)(sess, &sess->context, CLOSED); + } + } + +#if 0 + /* we don't close on a timeout because it will keep trying; if you + * want to call tac_session_free() on the first TIMEOUT, you can + * or you can wait for the eventual ERROR. We might want to let + * the user do all the cleanup via tac_session_free()... + */ + if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { + bufferevent_free(bev); + sess->bufev = NULL; + } +#endif + + /* now notify us of inbound data, etc */ + if (events & BEV_EVENT_CONNECTED) { + TACDEBUG(LOG_DEBUG, "session %p enabling r/w callbacks", sess); + bufferevent_enable(bev, EV_READ|EV_WRITE); + } +} + +static void writecb(struct bufferevent *bev, void *ptr) +{ + struct cb_ctx *ctx = (struct cb_ctx *)ptr; + struct tac_session *sess = ctx->sess; + struct evbuffer *evbuf = bufferevent_get_output(bev); + size_t n = evbuffer_get_length(evbuf); + + sess = sess; /* unused */ + n = n; + + TACDEBUG(LOG_DEBUG, "session %p write cb %ld bytes", sess, n); +} + +static void readcb(struct bufferevent *bev, void *ptr) +{ + struct cb_ctx *ctx = (struct cb_ctx *)ptr; + struct tac_session *sess = ctx->sess; + unsigned length; + struct evbuffer *evbuf = bufferevent_get_input(bev); + size_t n = evbuffer_get_length(evbuf); + u_char *start; + HDR *th; + int i; + + sess = sess; /* unused */ + + TACDEBUG(LOG_DEBUG, "session %p read cb %ld bytes", sess, n); + + /* evbuffer_pullup() returns NULL if there aren't enough bytes in + * the buffer yet to pull-up as many as are requested. + */ + start = evbuffer_pullup(evbuf, TAC_PLUS_HDR_SIZE); + if (! start) { + TACDEBUG(LOG_DEBUG, "session %p not enough data to pullup", sess); + return; + } + + th = (HDR *)start; + + length = ntohl(th->datalength) + TAC_PLUS_HDR_SIZE; + + TACDEBUG(LOG_DEBUG, "session %p header shows %u bytes", sess, length); + + /* if we're short, we'll get called again when more data arrives */ + if (n < length) { + TACDEBUG(LOG_DEBUG, "session %p want %u bytes but have %ld", sess, length, n); + return; + } + + u_char *pkt = xcalloc(1, length); + + /* copy out... */ + i = evbuffer_remove(evbuf, pkt, length); + + if (i < 0 || (unsigned) i != length) + TACDEBUG(LOG_DEBUG, "%s: evbuffer_remove want %u got %d", __FUNCTION__, length, i); + + /* turn off timeouts */ + tac_session_reset_timeouts(sess, false); + + /* received response, so connection is idle again */ + sess->tac_idle = true; + + tac_parse_pkt(sess, ctx, pkt, ((i > 0) ? i : 0)); + + free(pkt); +} + +bool +tac_connect_single_ev(struct tac_session *sess, void *tac_event, + struct addrinfo *server, struct addrinfo *srcaddr, unsigned timeout) +{ + struct event_base *ev_base = (struct event_base *)tac_event; + struct bufferevent *bev; + + TACDEBUG(LOG_DEBUG, "sess %p starting connect", sess); + + bev = bufferevent_socket_new(ev_base, -1, BEV_OPT_CLOSE_ON_FREE); + + /* bind if source address got explicitly defined */ + if (srcaddr != NULL) { + if (bind(bufferevent_getfd(bev), srcaddr->ai_addr, srcaddr->ai_addrlen) < 0) { + TACDEBUG(LOG_DEBUG, "couldn't bind src address: %m"); + } + } + + if (timeout != 0) { + struct timeval tv = { timeout, 0 }; + + TACDEBUG(LOG_DEBUG, "session %p timeout %ld.%06ld", sess, tv.tv_sec, tv.tv_usec); + bufferevent_set_timeouts(bev, &tv, &tv); + } + + bufferevent_setwatermark(bev, EV_READ, TAC_PLUS_HDR_SIZE, 0); + bufferevent_setcb(bev, readcb, writecb, eventcb, &sess->context); + + if (bufferevent_socket_connect(bev, server->ai_addr, server->ai_addrlen) < 0) { + TACDEBUG(LOG_DEBUG, "session %p bufferevent connect fails: %m", sess); + bufferevent_free(bev); + return false; + } + + /* don't bind bufferevent to session until connect initiated */ + sess->bufev = bev; + + return true; +} + +static void cleanup_malloc(const void *data, size_t len, void *ptr) +{ + len = len; + ptr = ptr; /* unused */ + + free((void *)data); +} + +/* + * return value: + * true/false success + */ +bool +tac_authen_send_ev(struct tac_session *sess, + const char *user, const char *pass, const char *tty, + const char *r_addr, u_char action) { + + u_char *pkt = NULL; + unsigned pkt_total = 0; + struct evbuffer *evbuf = evbuffer_new(); + int ret; + + TACDEBUG(LOG_DEBUG, "session %p authen %s/%s/%s", sess, user, tty, r_addr); + + sess->context.login = user; + sess->context.pass = pass; + + /* generate the packet */ + tac_authen_send_pkt(sess, user, pass, tty, r_addr, action, &pkt, &pkt_total); + + /* if reusing connection, reset timeouts */ + tac_session_reset_timeouts(sess, true); + + /* + * make evbuffer wrap around our packet, and call cleanup (free) + * when done + */ + evbuffer_add_reference(evbuf, pkt, pkt_total, cleanup_malloc, NULL); + + ret = bufferevent_write_buffer(sess->bufev, evbuf); + evbuffer_free(evbuf); + + /* we have a request on-the-wire */ + sess->tac_idle = false; + + TACDEBUG(LOG_DEBUG, "session %p: write status=%d", sess, ret); + + return (ret == 0); +} + +/* + * return value: + * true/false + */ +bool +tac_author_send_ev(struct tac_session *sess, + const char *user, const char *tty, const char *r_addr, + struct tac_attrib *attr) { + + u_char *pkt = NULL; + unsigned pkt_total = 0; + struct evbuffer *evbuf = evbuffer_new(); + int ret; + + TACDEBUG(LOG_DEBUG, "session %p author %s/%s/%s", sess, user, tty, r_addr); + + sess->context.login = user; + sess->context.pass = NULL; + + /* generate the packet */ + tac_author_send_pkt(sess, user, tty, r_addr, attr, &pkt, &pkt_total); + + /* if reusing connection, reset timeouts */ + tac_session_reset_timeouts(sess, true); + + /* + * make evbuffer wrap around our packet, and call cleanup (free) + * when done + */ + evbuffer_add_reference(evbuf, pkt, pkt_total, cleanup_malloc, NULL); + + ret = bufferevent_write_buffer(sess->bufev, evbuf); + evbuffer_free(evbuf); + + /* we have a request on-the-wire */ + sess->tac_idle = false; + + TACDEBUG(LOG_DEBUG, "session %p write status=%d", sess, ret); + + return (ret == 0); +} + +/* + * return value: + * true/false + */ +bool +tac_acct_send_ev(struct tac_session *sess, + u_char type, const char *user, const char *tty, + const char *r_addr, struct tac_attrib *attr) { + + u_char *pkt = NULL; + unsigned pkt_total = 0; + struct evbuffer *evbuf = evbuffer_new(); + int ret; + + TACDEBUG(LOG_DEBUG, "session %p account %s/%s/%s", sess, user, tty, r_addr); + + sess->context.login = user; + sess->context.pass = NULL; + + /* generate the packet */ + tac_acct_send_pkt(sess, type, user, tty, r_addr, attr, &pkt, &pkt_total); + + /* if reusing connection, reset timeouts */ + tac_session_reset_timeouts(sess, true); + + /* + * make evbuffer wrap around our packet, and call cleanup (free) + * when done + */ + evbuffer_add_reference(evbuf, pkt, pkt_total, cleanup_malloc, NULL); + + ret = bufferevent_write_buffer(sess->bufev, evbuf); + evbuffer_free(evbuf); + + /* we have a request on-the-wire */ + sess->tac_idle = false; + + TACDEBUG(LOG_DEBUG, "session %p write status=%d", sess, ret); + + return (ret == 0); +} + +/* + * return value: + * true/false + */ +bool +tac_cont_send_ev(struct tac_session *sess, const char *pass) { + + u_char *pkt = NULL; + unsigned pkt_total = 0; + struct evbuffer *evbuf = evbuffer_new(); + int ret; + + TACDEBUG(LOG_DEBUG, "session %p authen-cont %s", sess, "********"); + + sess->context.pass = pass; + + /* generate the packet */ + tac_cont_send_pkt(sess, pass, &pkt, &pkt_total); + + /* if reusing connection, reset timeouts */ + tac_session_reset_timeouts(sess, true); + + /* + * make evbuffer wrap around our packet, and call cleanup (free) + * when done + */ + evbuffer_add_reference(evbuf, pkt, pkt_total, cleanup_malloc, NULL); + + ret = bufferevent_write_buffer(sess->bufev, evbuf); + evbuffer_free(evbuf); + + /* we have a request on-the-wire */ + sess->tac_idle = false; + + TACDEBUG(LOG_DEBUG, "session %p write status=%d", sess, ret); + + return (ret == 0); +} + diff --git a/libtac/lib/xalloc.c b/libtac/lib/xalloc.c index d077bf26..81db39f7 100644 --- a/libtac/lib/xalloc.c +++ b/libtac/lib/xalloc.c @@ -26,7 +26,7 @@ void *xcalloc(size_t nmemb, size_t size) { void *val = calloc(nmemb, size); if (val == 0) { TACSYSLOG( - LOG_ERR, "%s: calloc(%u,%u) failed", __FUNCTION__, (unsigned) nmemb, (unsigned) size); + LOG_ERR, "%s: calloc(%zd,%zd) failed", __FUNCTION__, nmemb, size); exit(1); } return val; @@ -36,7 +36,7 @@ void *xrealloc(void *ptr, size_t size) { void *val = realloc(ptr, size); if (val == 0) { TACSYSLOG( - LOG_ERR, "%s: realloc(%u) failed", __FUNCTION__, (unsigned) size); + LOG_ERR, "%s: realloc(%zd) failed", __FUNCTION__, size); exit(1); } return val; diff --git a/pam_tacplus.c b/pam_tacplus.c index 324cd5d4..b1e014f7 100644 --- a/pam_tacplus.c +++ b/pam_tacplus.c @@ -47,6 +47,8 @@ # include "magic.h" #endif +char tac_login[64]; + /* address of server discovered by pam_sm_authenticate */ static tacplus_server_t active_server; struct addrinfo active_addrinfo; @@ -66,9 +68,11 @@ static void set_active_server (const tacplus_server_t *tac_svr) active_server.key = active_key; } +struct tac_session *sess = NULL; + /* Helper functions */ -int _pam_send_account(int tac_fd, int type, const char *user, char *tty, - char *r_addr, char *cmd) { +int _pam_send_account(struct tac_session *sess, int type, + const char *user, char *tty, char *r_addr, char *cmd) { char buf[64]; struct tac_attrib *attr; @@ -92,7 +96,7 @@ int _pam_send_account(int tac_fd, int type, const char *user, char *tty, tac_add_attrib(&attr, "cmd", cmd); } - retval = tac_acct_send(tac_fd, type, user, tty, r_addr, attr); + retval = tac_acct_send(sess, type, user, tty, r_addr, attr); /* this is no longer needed */ tac_free_attrib(&attr); @@ -100,26 +104,23 @@ int _pam_send_account(int tac_fd, int type, const char *user, char *tty, if (retval < 0) { _pam_log(LOG_WARNING, "%s: send %s accounting failed (task %hu)", __FUNCTION__, tac_acct_flag2str(type), task_id); - close(tac_fd); return -1; } struct areply re; - if (tac_acct_read(tac_fd, &re) != TAC_PLUS_ACCT_STATUS_SUCCESS) { + if (tac_acct_read(sess, &re) != TAC_PLUS_ACCT_STATUS_SUCCESS) { _pam_log(LOG_WARNING, "%s: accounting %s failed (task %hu)", __FUNCTION__, tac_acct_flag2str(type), task_id); if (re.msg != NULL) free(re.msg); - close(tac_fd); return -1; } if (re.msg != NULL) free(re.msg); - close(tac_fd); return 0; } @@ -133,7 +134,7 @@ int _pam_account(pam_handle_t *pamh, int argc, const char **argv, int type, char *r_addr = NULL; char *typemsg; int status = PAM_SESSION_ERR; - int srv_i, tac_fd; + int srv_i; typemsg = tac_acct_flag2str(type); ctrl = _pam_parse(argc, argv); @@ -183,20 +184,27 @@ int _pam_account(pam_handle_t *pamh, int argc, const char **argv, int type, signal(SIGHUP, SIG_IGN); } + sess = tac_session_alloc(); + + tac_session_set_authen_type(sess, tac_get_authen_type(tac_login)); + status = PAM_SESSION_ERR; for (srv_i = 0; srv_i < tac_srv_no; srv_i++) { - tac_fd = tac_connect_single(tac_srv[srv_i].addr, tac_srv[srv_i].key, - NULL, tac_timeout); - if (tac_fd < 0) { + retval = tac_connect_single(sess, tac_srv[srv_i].addr, NULL, tac_timeout); + if (retval < 0) { _pam_log(LOG_WARNING, "%s: error sending %s (fd)", __FUNCTION__, typemsg); continue; } + + tac_session_set_secret(sess, tac_srv[srv_i].key); + if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: connected with fd=%d (srv %d)", __FUNCTION__, - tac_fd, srv_i); + sess->fd, srv_i); + + retval = _pam_send_account(sess, type, user, tty, r_addr, cmd); - retval = _pam_send_account(tac_fd, type, user, tty, r_addr, cmd); if (retval < 0) { _pam_log(LOG_WARNING, "%s: error sending %s (acct)", __FUNCTION__, typemsg); @@ -206,7 +214,7 @@ int _pam_account(pam_handle_t *pamh, int argc, const char **argv, int type, syslog(LOG_DEBUG, "%s: [%s] for [%s] sent", __FUNCTION__, typemsg, user); } - close(tac_fd); + tac_close(sess); if ((status == PAM_SUCCESS) && !(ctrl & PAM_TAC_ACCT)) { /* do not send acct start/stop packets to _all_ servers */ @@ -214,6 +222,8 @@ int _pam_account(pam_handle_t *pamh, int argc, const char **argv, int type, } } + tac_session_free(sess); + if (type == TAC_PLUS_ACCT_FLAG_STOP) { signal(SIGALRM, SIG_DFL); signal(SIGCHLD, SIG_DFL); @@ -238,7 +248,7 @@ int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, char *tty; char *r_addr; int srv_i; - int tac_fd, status, msg, communicating; + int status, msg, communicating; user = pass = tty = r_addr = NULL; @@ -281,33 +291,39 @@ int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: rhost [%s] obtained", __FUNCTION__, r_addr); + sess = tac_session_alloc(); + + tac_session_set_authen_type(sess, tac_get_authen_type(tac_login)); + status = PAM_AUTHINFO_UNAVAIL; for (srv_i = 0; srv_i < tac_srv_no; srv_i++) { if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: trying srv %d", __FUNCTION__, srv_i); - tac_fd = tac_connect_single(tac_srv[srv_i].addr, tac_srv[srv_i].key, - NULL, tac_timeout); - if (tac_fd < 0) { + retval = tac_connect_single(sess, tac_srv[srv_i].addr, NULL, tac_timeout); + if (retval < 0) { _pam_log(LOG_ERR, "connection failed srv %d: %m", srv_i); active_server.addr = NULL; continue; } - if (tac_authen_send(tac_fd, user, pass, tty, r_addr, + + tac_session_set_secret(sess, tac_srv[srv_i].key); + + if (tac_authen_send(sess, user, pass, tty, r_addr, TAC_PLUS_AUTHEN_LOGIN) < 0) { - close(tac_fd); + tac_close(sess); _pam_log(LOG_ERR, "error sending auth req to TACACS+ server"); active_server.addr = NULL; continue; } - communicating = 1; - while (communicating) { + + for (communicating = 1; communicating; ) { struct areply re = { .attr = NULL, .msg = NULL, .status = 0, .flags = 0 }; struct pam_message conv_msg = { .msg_style = 0, .msg = NULL }; struct pam_response *resp = NULL; - msg = tac_authen_read(tac_fd, &re); + msg = tac_authen_read(sess, &re); if (NULL != re.msg) { conv_msg.msg = re.msg; @@ -389,8 +405,7 @@ int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, __FUNCTION__); if (0 - > tac_cont_send_seq(tac_fd, resp->resp, - re.seq_no + 1)) { + > tac_cont_send(sess, resp->resp)) { _pam_log(LOG_ERR, "error sending continue req to TACACS+ server"); status = PAM_AUTH_ERR; @@ -431,7 +446,7 @@ int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: tac_cont_send called", __FUNCTION__); - if (tac_cont_send(tac_fd, pass) < 0) { + if (tac_cont_send(sess, pass) < 0) { _pam_log(LOG_ERR, "error sending continue req to TACACS+ server"); communicating = 0; @@ -497,11 +512,14 @@ int pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, free(re.msg); } /* end while(communicating) */ - close(tac_fd); + tac_close(sess); if (status == PAM_SUCCESS || status == PAM_AUTH_ERR) break; } + + tac_session_free(sess); + if (status != PAM_SUCCESS && status != PAM_AUTH_ERR) _pam_log(LOG_ERR, "no more servers to connect"); @@ -547,7 +565,6 @@ int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, char *r_addr; struct areply arep; struct tac_attrib *attr = NULL; - int tac_fd; flags = flags; /* unused */ @@ -607,16 +624,22 @@ int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, if (tac_protocol[0] != '\0') tac_add_attrib(&attr, "protocol", tac_protocol); - tac_fd = tac_connect_single(active_server.addr, active_server.key, NULL, - tac_timeout); - if (tac_fd < 0) { + sess = tac_session_alloc(); + + tac_session_set_authen_type(sess, tac_get_authen_type(tac_login)); + + retval = tac_connect_single(sess, active_server.addr, NULL, tac_timeout); + if (retval < 0) { _pam_log(LOG_ERR, "TACACS+ server unavailable"); if (arep.msg != NULL) free(arep.msg); + tac_session_free(sess); return PAM_AUTH_ERR; } - retval = tac_author_send(tac_fd, user, tty, r_addr, attr); + tac_session_set_secret(sess, active_server.key); + + retval = tac_author_send(sess, user, tty, r_addr, attr); tac_free_attrib(&attr); @@ -625,7 +648,7 @@ int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, if (arep.msg != NULL) free(arep.msg); - close(tac_fd); + tac_session_free(sess); active_server.addr = NULL; return PAM_AUTH_ERR; } @@ -633,7 +656,9 @@ int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: sent authorization request", __FUNCTION__); - tac_author_read(tac_fd, &arep); + tac_author_read(sess, &arep); + + tac_session_free(sess); if (arep.status != AUTHOR_STATUS_PASS_ADD && arep.status != AUTHOR_STATUS_PASS_REPL) { @@ -642,7 +667,6 @@ int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, if (arep.msg != NULL) free(arep.msg); - close(tac_fd); return PAM_PERM_DENIED; } @@ -698,8 +722,6 @@ int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, if (arep.msg != NULL) free(arep.msg); - close(tac_fd); - return status; } /* pam_sm_acct_mgmt */ @@ -750,7 +772,7 @@ int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, char *r_addr; const void *pam_pass = NULL; int srv_i; - int tac_fd, status, msg, communicating; + int status, msg, communicating; user = pass = tty = r_addr = NULL; @@ -795,14 +817,15 @@ int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, goto finish; } + sess = tac_session_alloc(); + status = PAM_TRY_AGAIN; for (srv_i = 0; srv_i < tac_srv_no; srv_i++) { if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: trying srv %d", __FUNCTION__, srv_i); - tac_fd = tac_connect_single(tac_srv[srv_i].addr, tac_srv[srv_i].key, - NULL, tac_timeout); - if (tac_fd < 0) { + retval = tac_connect_single(sess, tac_srv[srv_i].addr, NULL, tac_timeout); + if (retval < 0) { _pam_log(LOG_ERR, "connection failed srv %d: %m", srv_i); continue; } @@ -811,14 +834,16 @@ int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, syslog(LOG_DEBUG, "%s: finishing PAM_PRELIM_CHECK with srv %d", __FUNCTION__, srv_i); - close(tac_fd); + tac_close(sess); status = PAM_SUCCESS; goto finish; } - if (tac_authen_send(tac_fd, user, "", tty, r_addr, + tac_session_set_secret(sess, tac_srv[srv_i].key); + + if (tac_authen_send(sess, user, "", tty, r_addr, TAC_PLUS_AUTHEN_CHPASS) < 0) { - close(tac_fd); + tac_close(sess); _pam_log(LOG_ERR, "error sending auth req to TACACS+ server"); continue; } @@ -829,7 +854,7 @@ int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, struct pam_message conv_msg = { .msg_style = 0, .msg = NULL }; struct pam_response *resp = NULL; - msg = tac_authen_read(tac_fd, &re); + msg = tac_authen_read(sess, &re); if (NULL != re.msg) { conv_msg.msg = re.msg; @@ -911,8 +936,7 @@ int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, __FUNCTION__); if (0 - > tac_cont_send_seq(tac_fd, resp->resp, - re.seq_no + 1)) { + > tac_cont_send(sess, resp->resp)) { _pam_log(LOG_ERR, "error sending continue req to TACACS+ server"); communicating = 0; @@ -949,7 +973,7 @@ int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, syslog(LOG_DEBUG, "%s: calling tac_cont_send", __FUNCTION__); - if (tac_cont_send(tac_fd, pass) < 0) { + if (tac_cont_send(sess, pass) < 0) { _pam_log(LOG_ERR, "error sending continue req to TACACS+ server"); communicating = 0; @@ -1006,6 +1030,7 @@ int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: unknown response 0x%02x", msg); + break; } if (NULL != resp) { @@ -1016,12 +1041,14 @@ int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, free(re.msg); } /* end while(communicating) */ - close(tac_fd); + tac_close(sess); if (status == PAM_SUCCESS || status == PAM_AUTHTOK_ERR) break; } + tac_session_free(sess); + finish: if (status != PAM_SUCCESS && status != PAM_AUTHTOK_ERR) _pam_log(LOG_ERR, "no more servers to connect"); diff --git a/support.c b/support.c index 2406b321..a1867c00 100644 --- a/support.c +++ b/support.c @@ -56,7 +56,7 @@ char *_pam_get_user(pam_handle_t *pamh) { int retval; char *user; - retval = pam_get_user(pamh, (void *)&user, "Username: "); + retval = pam_get_user(pamh, (const char **)&user, "Username: "); if (retval != PAM_SUCCESS || user == NULL || *user == '\0') { _pam_log(LOG_ERR, "unable to obtain username"); user = NULL; @@ -68,7 +68,7 @@ char *_pam_get_terminal(pam_handle_t *pamh) { int retval; char *tty; - retval = pam_get_item(pamh, PAM_TTY, (void *)&tty); + retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty); if (retval != PAM_SUCCESS || tty == NULL || *tty == '\0') { tty = ttyname(STDIN_FILENO); if(tty == NULL || *tty == '\0') @@ -81,7 +81,7 @@ char *_pam_get_rhost(pam_handle_t *pamh) { int retval; char *rhost; - retval = pam_get_item(pamh, PAM_RHOST, (void *)&rhost); + retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost); if (retval != PAM_SUCCESS || rhost == NULL || *rhost == '\0') { rhost = "unknown"; } diff --git a/support.h b/support.h index 07c2f9dd..ff90644f 100644 --- a/support.h +++ b/support.h @@ -40,6 +40,7 @@ extern char tac_service[64]; extern char tac_protocol[64]; extern char tac_prompt[64]; void tac_copy_addr_info (struct addrinfo *p_dst, const struct addrinfo *p_src); +extern char tac_login[64]; int _pam_parse (int, const char **); unsigned long _resolve_name (char *); diff --git a/tacc.c b/tacc.c index 80693238..154be9b6 100644 --- a/tacc.c +++ b/tacc.c @@ -79,6 +79,10 @@ typedef unsigned char flag; flag quiet = 0; char *user = NULL; /* global, because of signal handler */ +char tac_login[64]; + +struct tac_session *sess = NULL; + /* command line options */ static struct option long_options[] = { @@ -117,7 +121,6 @@ int main(int argc, char **argv) { struct addrinfo *tac_server; char *tac_server_name = NULL; char *tac_secret = NULL; - int tac_fd; short int task_id = 0; char buf[40]; int ret; @@ -172,7 +175,6 @@ int main(int argc, char **argv) { remote_addr = optarg; break; case 'L': - // tac_login is a global variable initialized in libtac bzero(tac_login, sizeof(tac_login)); strncpy(tac_login, optarg, sizeof(tac_login) - 1); break; @@ -272,28 +274,41 @@ int main(int argc, char **argv) { exit(EXIT_ERR); } + bzero(&arep, sizeof(arep)); + /* open syslog before any TACACS+ calls */ openlog("tacc", LOG_CONS | LOG_PID, LOG_AUTHPRIV); + sess = tac_session_alloc(); + + ret = tac_connect_single(sess, tac_server, NULL, 60); + if (ret < 0) { + if (!quiet) + printf("Error connecting to TACACS+ server: %m\n"); + exit(EXIT_ERR); + } + + tac_session_set_secret(sess, tac_secret); + tac_session_set_authen_type(sess, tac_get_authen_type(tac_login)); + + tac_session_set_multiplex(sess, true); + if (do_authen) authenticate(tac_server, tac_secret, user, pass, tty, remote_addr); + /* we no longer need the password in our address space */ + bzero(pass, strlen(pass)); + pass = NULL; + if (do_author) { /* authorize user */ struct tac_attrib *attr = NULL; tac_add_attrib(&attr, "service", service); tac_add_attrib(&attr, "protocol", protocol); - tac_fd = tac_connect_single(tac_server, tac_secret, NULL, 60); - if (tac_fd < 0) { - if (!quiet) - printf("Error connecting to TACACS+ server: %m\n"); - exit(EXIT_ERR); - } - - tac_author_send(tac_fd, user, tty, remote_addr, attr); + tac_author_send(sess, user, tty, remote_addr, attr); - tac_author_read(tac_fd, &arep); + tac_author_read(sess, &arep); if (arep.status != AUTHOR_STATUS_PASS_ADD && arep.status != AUTHOR_STATUS_PASS_REPL) { if (!quiet) @@ -307,10 +322,6 @@ int main(int argc, char **argv) { tac_free_attrib(&attr); } - /* we no longer need the password in our address space */ - bzero(pass, strlen(pass)); - pass = NULL; - if (do_account) { /* start accounting */ struct tac_attrib *attr = NULL; @@ -322,17 +333,10 @@ int main(int argc, char **argv) { tac_add_attrib(&attr, "service", service); tac_add_attrib(&attr, "protocol", protocol); - tac_fd = tac_connect_single(tac_server, tac_secret, NULL, 60); - if (tac_fd < 0) { - if (!quiet) - printf("Error connecting to TACACS+ server: %m\n"); - exit(EXIT_ERR); - } - - tac_acct_send(tac_fd, TAC_PLUS_ACCT_FLAG_START, user, tty, remote_addr, + tac_acct_send(sess, TAC_PLUS_ACCT_FLAG_START, user, tty, remote_addr, attr); - ret = tac_acct_read(tac_fd, &arep); + ret = tac_acct_read(sess, &arep); if (ret == 0) { if (!quiet) printf("Accounting: START failed: %s\n", arep.msg); @@ -340,7 +344,7 @@ int main(int argc, char **argv) { } else if (!login_mode && !quiet) printf("Accounting: START OK\n"); - close(tac_fd); + tac_close(sess); tac_free_attrib(&attr); @@ -378,6 +382,8 @@ int main(int argc, char **argv) { if(pid == 0) { /* child */ + tac_session_free(sess); + execl(DEFAULT_COMMAND, DEFAULT_COMMAND, ARGS, NULL); syslog(LOG_ERR, "execl() failed: %m"); _exit(EXIT_FAIL); @@ -406,16 +412,9 @@ int main(int argc, char **argv) { sprintf(buf, "%hu", task_id); tac_add_attrib(&attr, "task_id", buf); - tac_fd = tac_connect_single(tac_server, tac_secret, NULL, 60); - if (tac_fd < 0) { - if (!quiet) - printf("Error connecting to TACACS+ server: %m\n"); - exit(EXIT_ERR); - } - - tac_acct_send(tac_fd, TAC_PLUS_ACCT_FLAG_STOP, user, tty, remote_addr, + tac_acct_send(sess, TAC_PLUS_ACCT_FLAG_STOP, user, tty, remote_addr, attr); - ret = tac_acct_read(tac_fd, &arep); + ret = tac_acct_read(sess, &arep); if (ret == 0) { if (!quiet) printf("Accounting: STOP failed: %s", arep.msg); @@ -423,8 +422,6 @@ int main(int argc, char **argv) { } else if (!login_mode && !quiet) printf("Accounting: STOP OK\n"); - close(tac_fd); - tac_free_attrib(&attr); } @@ -434,6 +431,8 @@ int main(int argc, char **argv) { logwtmp(tty, "", ""); #endif + tac_session_free(sess); + exit(EXIT_OK); } @@ -446,37 +445,41 @@ void sighandler(int sig) { void authenticate(const struct addrinfo *tac_server, const char *tac_secret, const char *user, const char *pass, const char *tty, const char *remote_addr) { - int tac_fd; int ret; struct areply arep; - tac_fd = tac_connect_single(tac_server, tac_secret, NULL, 60); - if (tac_fd < 0) { + tac_session_new_session_id(sess); + tac_session_reset_seq(sess); + + ret = tac_connect_single(sess, tac_server, NULL, 60); + if (ret < 0) { if (!quiet) printf("Error connecting to TACACS+ server: %m\n"); exit(EXIT_ERR); } + tac_session_set_secret(sess, tac_secret); + /* start authentication */ - if (tac_authen_send(tac_fd, user, pass, tty, remote_addr, + if (tac_authen_send(sess, user, pass, tty, remote_addr, TAC_PLUS_AUTHEN_LOGIN) < 0) { if (!quiet) printf("Error sending query to TACACS+ server\n"); exit(EXIT_ERR); } - ret = tac_authen_read(tac_fd, &arep); + ret = tac_authen_read(sess, &arep); if (ret == TAC_PLUS_AUTHEN_STATUS_GETPASS) { - if (tac_cont_send(tac_fd, pass) < 0) { + if (tac_cont_send(sess, pass) < 0) { if (!quiet) printf("Error sending query to TACACS+ server\n"); exit(EXIT_ERR); } - ret = tac_authen_read(tac_fd, &arep); + ret = tac_authen_read(sess, &arep); } if (ret != TAC_PLUS_AUTHEN_STATUS_PASS) { @@ -489,8 +492,6 @@ void authenticate(const struct addrinfo *tac_server, const char *tac_secret, if (!quiet) printf("Authentication OK\n"); syslog(LOG_INFO, "authentication OK for %s", user); - - close(tac_fd); } void showusage(char *progname) {