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) { if (!quiet) printf("Error connecting to TACACS+ server: %m\n"); exit(EXIT_ERR); } /* start authentication */ if (tac_authen_send(tac_fd, 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); if (ret == TAC_PLUS_AUTHEN_STATUS_GETPASS) { if (tac_cont_send(tac_fd, pass) < 0) { if (!quiet) printf("Error sending query to TACACS+ server\n"); exit(EXIT_ERR); } ret = tac_authen_read(tac_fd, &arep); } if (ret != TAC_PLUS_AUTHEN_STATUS_PASS) { if (!quiet) printf("Authentication FAILED: %s\n", arep.msg); syslog(LOG_ERR, "authentication failed for %s: %s", user, arep.msg); exit(EXIT_FAIL); } if (!quiet) printf("Authentication OK\n"); syslog(LOG_INFO, "authentication OK for %s", user); close(tac_fd); }
/* no-op function for future use */ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, const char **argv) { int ctrl, retval; char *user; char *pass; char *tty; char *r_addr; const void *pam_pass = NULL; int srv_i; int tac_fd, status, msg, communicating; user = pass = tty = r_addr = NULL; ctrl = _pam_parse(argc, argv); if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)" , __FUNCTION__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); syslog(LOG_DEBUG, "%s(flags=%d, argc=%d)", __func__, flags, argc); // read config file _read_config(ctrl); if ( (pam_get_item(pamh, PAM_OLDAUTHTOK, &pam_pass) == PAM_SUCCESS) && (pam_pass != NULL) ) { if ((pass = strdup(pam_pass)) == NULL) return PAM_BUF_ERR; } else { pass = strdup(""); } if ((user = _pam_get_user(pamh)) == NULL) { if(pass) { xfree(pass); } return PAM_USER_UNKNOWN; } if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: user [%s] obtained", __FUNCTION__, user); tty = _pam_get_terminal(pamh); if (!strncmp(tty, "/dev/", 5)) tty += 5; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: tty [%s] obtained", __FUNCTION__, tty); r_addr = _pam_get_rhost(pamh); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: rhost [%s] obtained", __FUNCTION__, r_addr); if (PAM_SILENT == (flags & PAM_SILENT)) { status = PAM_AUTHTOK_ERR; goto finish; } 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) { _pam_log(LOG_ERR, "connection failed srv %d: %m", srv_i); continue; } if (PAM_PRELIM_CHECK == (flags & PAM_PRELIM_CHECK)) { if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "%s: finishing PAM_PRELIM_CHECK with srv %d", __FUNCTION__, srv_i); close(tac_fd); status = PAM_SUCCESS; goto finish; } if (tac_authen_send(tac_fd, user, "", tty, r_addr, TAC_PLUS_AUTHEN_CHPASS) < 0) { close(tac_fd); _pam_log(LOG_ERR, "error sending auth req to TACACS+ server"); continue; } communicating = 1; while (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); if (NULL != re.msg) { conv_msg.msg = re.msg; } /* talk the protocol */ switch (msg) { case TAC_PLUS_AUTHEN_STATUS_PASS: /* success */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_PASS"); if (NULL != conv_msg.msg) { int retval = -1; conv_msg.msg_style = PAM_TEXT_INFO; retval = converse(pamh, 1, &conv_msg, &resp); if (PAM_SUCCESS == retval) { if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "send msg=\"%s\"", conv_msg.msg); } else { _pam_log(LOG_WARNING, "%s: error sending msg=\"%s\", retval=%d", __FUNCTION__, conv_msg.msg, retval); } } status = PAM_SUCCESS; communicating = 0; if (active_server.addr != NULL) { xfree(active_server.addr); } active_server.addr = (struct addrinfo*)xcalloc(1, sizeof(struct addrinfo)); bcopy(tac_srv[srv_i].addr, active_server.addr, sizeof(struct addrinfo)); if (active_server.key != NULL) { xfree(active_server.key); } active_server.key = xstrdup(tac_srv[srv_i].key); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: active srv %d", __FUNCTION__, srv_i); break; case TAC_PLUS_AUTHEN_STATUS_FAIL: if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_FAIL"); if (NULL != conv_msg.msg) { int retval = -1; conv_msg.msg_style = PAM_ERROR_MSG; retval = converse(pamh, 1, &conv_msg, &resp); if (PAM_SUCCESS == retval) { if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "send msg=\"%s\"", conv_msg.msg); } else { _pam_log(LOG_WARNING, "%s: error sending msg=\"%s\", retval=%d", __FUNCTION__, conv_msg.msg, retval); } } status = PAM_AUTHTOK_ERR; communicating = 0; _pam_log(LOG_ERR, "chauthtok failed: %d", msg); break; case TAC_PLUS_AUTHEN_STATUS_GETDATA: if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETDATA"); if (NULL != conv_msg.msg) { int retval = -1; int echo_off = (0x1 == (re.flags & 0x1)); conv_msg.msg_style = echo_off ? PAM_PROMPT_ECHO_OFF : PAM_PROMPT_ECHO_ON; retval = converse(pamh, 1, &conv_msg, &resp); if (PAM_SUCCESS == retval) { if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "sent msg=\"%s\", resp=\"%s\"", conv_msg.msg, resp->resp); if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "%s: calling tac_cont_send", __FUNCTION__); if (0 > tac_cont_send_seq(tac_fd, resp->resp, re.seq_no + 1)) { _pam_log(LOG_ERR, "error sending continue req to TACACS+ server"); communicating = 0; } } else { _pam_log(LOG_WARNING, "%s: error sending msg=\"%s\", retval=%d", __FUNCTION__, conv_msg.msg, retval); communicating = 0; } } else { syslog(LOG_ERR, "GETDATA response with no message, returning PAM_TRY_AGAIN"); communicating = 0; } break; case TAC_PLUS_AUTHEN_STATUS_GETUSER: /* not implemented */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETUSER"); communicating = 0; break; case TAC_PLUS_AUTHEN_STATUS_GETPASS: if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETPASS"); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: calling tac_cont_send", __FUNCTION__); if (tac_cont_send(tac_fd, pass) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); communicating = 0; break; } /* continue the while loop; go read tac response */ break; case TAC_PLUS_AUTHEN_STATUS_RESTART: /* try it again */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_RESTART"); /* * not implemented * WdJ: I *think* you can just do tac_authen_send(user, pass) again * but I'm not sure */ communicating = 0; break; case TAC_PLUS_AUTHEN_STATUS_ERROR: /* server has problems */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_ERROR"); communicating = 0; break; case TAC_PLUS_AUTHEN_STATUS_FOLLOW: /* server tells to try a different server address */ /* not implemented */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_FOLLOW"); communicating = 0; break; default: if (msg < 0) { /* connection error */ communicating = 0; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "error communicating with tacacs server"); break; } /* unknown response code */ communicating = 0; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: unknown response 0x%02x", msg); } if (NULL != resp) { xfree(resp->resp); xfree(resp); } xfree(re.msg); } /* end while(communicating) */ close(tac_fd); if (status == PAM_SUCCESS || status == PAM_AUTHTOK_ERR) break; } finish: if (status != PAM_SUCCESS && status != PAM_AUTHTOK_ERR) _pam_log(LOG_ERR, "no more servers to connect"); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: exit with pam status: %d", __FUNCTION__, status); if (NULL != pass) { bzero(pass, strlen(pass)); xfree(pass); pass = NULL; } return status; } /* pam_sm_chauthtok */ #endif #ifdef PAM_STATIC struct pam_module _pam_tacplus_modstruct { "pam_tacplus", pam_sm_authenticate, pam_sm_setcred, pam_sm_acct_mgmt, pam_sm_open_session, pam_sm_close_session, #ifdef PAM_SM_PASSWORD pam_sm_chauthtok #else NULL #endif };
/* authenticates user on remote TACACS+ server * returns PAM_SUCCESS if the supplied username and password * pair is valid */ PAM_EXTERN int pam_sm_authenticate (pam_handle_t * pamh, int flags, int argc, const char **argv) { int ctrl, retval; char *user; char *pass; char *tty; char *r_addr; int srv_i; int tac_fd, status, msg, communicating; user = pass = tty = r_addr = NULL; ctrl = _pam_parse(argc, argv); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)", __FUNCTION__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); if ((user = _pam_get_user(pamh)) == NULL) return PAM_USER_UNKNOWN; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: user [%s] obtained", __FUNCTION__, user); // read config file _read_config(ctrl); retval = tacacs_get_password (pamh, flags, ctrl, &pass); if (retval != PAM_SUCCESS || pass == NULL || *pass == '\0') { _pam_log(LOG_ERR, "unable to obtain password"); xfree(pass); return PAM_CRED_INSUFFICIENT; } retval = pam_set_item (pamh, PAM_AUTHTOK, pass); if (retval != PAM_SUCCESS) { _pam_log(LOG_ERR, "unable to set password"); xfree(pass); return PAM_CRED_INSUFFICIENT; } if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: password obtained", __FUNCTION__); tty = _pam_get_terminal(pamh); if (!strncmp(tty, "/dev/", 5)) tty += 5; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: tty [%s] obtained", __FUNCTION__, tty); r_addr = _pam_get_rhost(pamh); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: rhost [%s] obtained", __FUNCTION__, r_addr); 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) { _pam_log(LOG_ERR, "connection failed srv %d: %m", srv_i); continue; } if (tac_authen_send(tac_fd, user, pass, tty, r_addr, TAC_PLUS_AUTHEN_LOGIN) < 0) { close(tac_fd); _pam_log(LOG_ERR, "error sending auth req to TACACS+ server"); continue; } communicating = 1; while (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); if (NULL != re.msg) { conv_msg.msg = re.msg; } /* talk the protocol */ switch (msg) { case TAC_PLUS_AUTHEN_STATUS_PASS: /* success */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_PASS"); if (NULL != conv_msg.msg) { int retval = -1; conv_msg.msg_style = PAM_TEXT_INFO; retval = converse(pamh, 1, &conv_msg, &resp); if (PAM_SUCCESS == retval) { if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "send msg=\"%s\"", conv_msg.msg); } else { _pam_log(LOG_WARNING, "%s: error sending msg=\"%s\", retval=%d", __FUNCTION__, conv_msg.msg, retval); } } status = PAM_SUCCESS; communicating = 0; if (active_server.addr != NULL) { xfree(active_server.addr); } active_server.addr = (struct addrinfo*)xcalloc(1, sizeof(struct addrinfo)); bcopy(tac_srv[srv_i].addr, active_server.addr, sizeof(struct addrinfo)); if (active_server.key != NULL) { xfree(active_server.key); } active_server.key = xstrdup(tac_srv[srv_i].key); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: active srv %d", __FUNCTION__, srv_i); break; case TAC_PLUS_AUTHEN_STATUS_FAIL: if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_FAIL"); if (NULL != conv_msg.msg) { int retval = -1; conv_msg.msg_style = PAM_ERROR_MSG; retval = converse(pamh, 1, &conv_msg, &resp); if (PAM_SUCCESS == retval) { if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "send msg=\"%s\"", conv_msg.msg); } else { _pam_log(LOG_WARNING, "%s: error sending msg=\"%s\", retval=%d", __FUNCTION__, conv_msg.msg, retval); } } status = PAM_AUTH_ERR; communicating = 0; _pam_log(LOG_ERR, "auth failed: %d", msg); break; case TAC_PLUS_AUTHEN_STATUS_GETDATA: if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETDATA"); if (NULL != conv_msg.msg) { int retval = -1; int echo_off = (0x1 == (re.flags & 0x1)); conv_msg.msg_style = echo_off ? PAM_PROMPT_ECHO_OFF : PAM_PROMPT_ECHO_ON; retval = converse(pamh, 1, &conv_msg, &resp); if (PAM_SUCCESS == retval) { if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "sent msg=\"%s\", resp=\"%s\"", conv_msg.msg, resp->resp); if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "%s: calling tac_cont_send", __FUNCTION__); if (0 > tac_cont_send_seq(tac_fd, resp->resp, re.seq_no + 1)) { _pam_log(LOG_ERR, "error sending continue req to TACACS+ server"); status = PAM_AUTH_ERR; communicating = 0; } } else { _pam_log(LOG_WARNING, "%s: error sending msg=\"%s\", retval=%d (%s)", __FUNCTION__, conv_msg.msg, retval, pam_strerror(pamh, retval)); status = PAM_AUTH_ERR; communicating = 0; } } else { syslog(LOG_ERR, "GETDATA response with no message, returning PAM_AUTH_ERR"); status = PAM_AUTH_ERR; communicating = 0; } break; case TAC_PLUS_AUTHEN_STATUS_GETUSER: /* not implemented */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETUSER"); communicating = 0; break; case TAC_PLUS_AUTHEN_STATUS_GETPASS: if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETPASS"); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: tac_cont_send called", __FUNCTION__); if (tac_cont_send(tac_fd, pass) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); communicating = 0; } /* continue the while loop; go read tac response */ break; case TAC_PLUS_AUTHEN_STATUS_RESTART: /* try it again */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_RESTART"); /* * not implemented * WdJ: I *think* you can just do tac_authen_send(user, pass) again * but I'm not sure */ communicating = 0; break; case TAC_PLUS_AUTHEN_STATUS_ERROR: /* server has problems */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_ERROR"); communicating = 0; break; case TAC_PLUS_AUTHEN_STATUS_FOLLOW: /* server tells to try a different server address */ /* not implemented */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_FOLLOW"); communicating = 0; break; default: if (msg < 0) { /* connection error */ communicating = 0; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "error communicating with tacacs server"); break; } /* unknown response code */ communicating = 0; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: unknown response 0x%02x", msg); } if (NULL != resp) { if (resp->resp != NULL) { xfree(resp->resp); } xfree(resp); } if (re.msg != NULL) { xfree(re.msg); } } /* end while(communicating) */ close(tac_fd); if (status == PAM_SUCCESS || status == PAM_AUTH_ERR) break; } if (status != PAM_SUCCESS && status != PAM_AUTH_ERR) _pam_log(LOG_ERR, "no more servers to connect"); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: exit with pam status: %d", __FUNCTION__, status); if (NULL != pass) { bzero(pass, strlen (pass)); xfree(pass); pass = NULL; } return status; } /* pam_sm_authenticate */ /* no-op function to satisfy PAM authentication module */ PAM_EXTERN int pam_sm_setcred (pam_handle_t * pamh, int flags, int argc, const char **argv) { int ctrl = _pam_parse (argc, argv); if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)" , __FUNCTION__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); return PAM_SUCCESS; } /* pam_sm_setcred */ /* authorizes user on remote TACACS+ server, i.e. checks * his permission to access requested service * returns PAM_SUCCESS if the service is allowed */ PAM_EXTERN int pam_sm_acct_mgmt (pam_handle_t * pamh, int flags, int argc, const char **argv) { int retval, ctrl, status=PAM_AUTH_ERR; char *user; char *tty; char *r_addr; struct areply arep; struct tac_attrib *attr = NULL; int tac_fd; user = tty = r_addr = NULL; memset(&arep, 0, sizeof(arep)); /* this also obtains service name for authorization this should be normally performed by pam_get_item(PAM_SERVICE) but since PAM service names are incompatible TACACS+ we have to pass it via command line argument until a better solution is found ;) */ ctrl = _pam_parse (argc, argv); if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)" , __FUNCTION__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); if ((user = _pam_get_user(pamh)) == NULL) return PAM_USER_UNKNOWN; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: username obtained [%s]", __FUNCTION__, user); // read config file _read_config(ctrl); tty = _pam_get_terminal(pamh); if(!strncmp(tty, "/dev/", 5)) tty += 5; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: tty obtained [%s]", __FUNCTION__, tty); r_addr = _pam_get_rhost(pamh); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: rhost obtained [%s]", __FUNCTION__, r_addr); /* checks if user has been successfully authenticated by TACACS+; we cannot solely authorize user if it hasn't been authenticated or has been authenticated by method other than TACACS+ */ if(active_server.addr == NULL) { _pam_log (LOG_ERR, "user not authenticated by TACACS+"); return PAM_AUTH_ERR; } if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: active server is [%s]", __FUNCTION__, tac_ntop(active_server.addr->ai_addr)); /* checks for specific data required by TACACS+, which should be supplied in command line */ if(!*tac_service) { _pam_log (LOG_ERR, "SM: TACACS+ service type not configured"); return PAM_AUTH_ERR; } if(!*tac_protocol) { _pam_log (LOG_ERR, "SM: TACACS+ protocol type not configured (IGNORED)"); } tac_add_attrib(&attr, "service", tac_service); 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) { _pam_log (LOG_ERR, "TACACS+ server unavailable"); if(arep.msg != NULL) xfree (arep.msg); return PAM_AUTH_ERR; } retval = tac_author_send(tac_fd, user, tty, r_addr, attr); tac_free_attrib(&attr); if(retval < 0) { _pam_log (LOG_ERR, "error getting authorization"); if(arep.msg != NULL) xfree (arep.msg); close(tac_fd); return PAM_AUTH_ERR; } if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: sent authorization request", __FUNCTION__); tac_author_read(tac_fd, &arep); if(arep.status != AUTHOR_STATUS_PASS_ADD && arep.status != AUTHOR_STATUS_PASS_REPL) { _pam_log (LOG_ERR, "TACACS+ authorisation failed for [%s]", user); if(arep.msg != NULL) xfree (arep.msg); close(tac_fd); return PAM_PERM_DENIED; } if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: user [%s] successfully authorized", __FUNCTION__, user); status = PAM_SUCCESS; attr = arep.attr; while (attr != NULL) { char attribute[attr->attr_len]; char value[attr->attr_len]; char *sep; sep = index(attr->attr, '='); if(sep == NULL) sep = index(attr->attr, '*'); if(sep != NULL) { bcopy(attr->attr, attribute, attr->attr_len-strlen(sep)); attribute[attr->attr_len-strlen(sep)] = '\0'; bcopy(sep, value, strlen(sep)); value[strlen(sep)] = '\0'; size_t i; for (i = 0; attribute[i] != '\0'; i++) { attribute[i] = toupper(attribute[i]); if (attribute[i] == '-') attribute[i] = '_'; } if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: returned attribute `%s%s' from server", __FUNCTION__, attribute, value); /* make returned attributes available for other PAM modules via PAM environment */ if (pam_putenv(pamh, strncat(attribute, value, strlen(value))) != PAM_SUCCESS) syslog(LOG_WARNING, "%s: unable to set PAM environment", __FUNCTION__); } else { syslog(LOG_WARNING, "%s: invalid attribute `%s', no separator", __FUNCTION__, attr->attr); } attr = attr->next; } /* free returned attributes */ if(arep.attr != NULL) tac_free_attrib(&arep.attr); if(arep.msg != NULL) xfree (arep.msg); close(tac_fd); return status; } /* pam_sm_acct_mgmt */
int _pam_account(pam_handle_t *pamh, int argc, const char **argv, int type, char *cmd) { int retval; static int ctrl; char *user = NULL; char *tty = NULL; char *r_addr = NULL; char *typemsg; int status = PAM_SESSION_ERR; int srv_i, tac_fd; typemsg = tac_acct_flag2str(type); ctrl = _pam_parse (argc, argv); if (ctrl & PAM_TAC_DEBUG) { syslog (LOG_DEBUG, "%s: [%s] called (pam_tacplus v%u.%u.%u)", __FUNCTION__, typemsg, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); syslog(LOG_DEBUG, "%s: tac_srv_no=%d", __FUNCTION__, tac_srv_no); } if ((user = _pam_get_user(pamh)) == NULL) return PAM_USER_UNKNOWN; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: username [%s] obtained", __FUNCTION__, user); // read config file _read_config(ctrl); tty = _pam_get_terminal(pamh); if(!strncmp(tty, "/dev/", 5)) tty += 5; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: tty [%s] obtained", __FUNCTION__, tty); r_addr = _pam_get_rhost(pamh); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: rhost [%s] obtained", __FUNCTION__, r_addr); /* checks for specific data required by TACACS+, which should be supplied in command line */ if(*tac_service == '\0') { _pam_log (LOG_ERR, "ACC: TACACS+ service type not configured"); return PAM_AUTH_ERR; } if(*tac_protocol == '\0') { _pam_log (LOG_ERR, "ACC: TACACS+ protocol type not configured (IGNORED)"); } /* when this module is called from within pppd or other application dealing with serial lines, it is likely that we will get hit with signal caused by modem hangup; this is important only for STOP packets, it's relatively rare that modem hangs up on accounting start */ if(type == TAC_PLUS_ACCT_FLAG_STOP) { signal(SIGALRM, SIG_IGN); signal(SIGCHLD, SIG_IGN); signal(SIGHUP, SIG_IGN); } 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) { _pam_log(LOG_WARNING, "%s: error sending %s (fd)", __FUNCTION__, typemsg); continue; } if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: connected with fd=%d (srv %d)", __FUNCTION__, tac_fd, srv_i); 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); } else { status = PAM_SUCCESS; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: [%s] for [%s] sent", __FUNCTION__, typemsg, user); } close(tac_fd); if ((status == PAM_SUCCESS) && !(ctrl & PAM_TAC_ACCT)) { /* do not send acct start/stop packets to _all_ servers */ break; } } if (type == TAC_PLUS_ACCT_FLAG_STOP) { signal(SIGALRM, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_DFL); } return status; }
/* * Talk to the server for authentication */ static int tac_auth_converse(int ctrl, int fd, int *sptr, char *pass, pam_handle_t * pamh) { int msg, status, flags; int ret = 1; 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(fd, &re); if (NULL != re.msg) { conv_msg.msg = re.msg; } /* talk the protocol */ switch (msg) { case TAC_PLUS_AUTHEN_STATUS_PASS: /* success */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_PASS"); if (NULL != conv_msg.msg) { int retval = -1; conv_msg.msg_style = PAM_TEXT_INFO; retval = converse(pamh, 1, &conv_msg, &resp); if (PAM_SUCCESS == retval) { if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "send msg=\"%s\"", conv_msg.msg); } else { _pam_log(LOG_WARNING, "%s: error sending msg=\"%s\", retval=%d", __func__, conv_msg.msg, retval); } } *sptr = PAM_SUCCESS; ret = 0; break; case TAC_PLUS_AUTHEN_STATUS_FAIL: /* * This can be a user unknown case, so we don't want to stop * trying other servers when we hit this case during authentication */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_FAIL"); if (NULL != conv_msg.msg) { int retval = -1; conv_msg.msg_style = PAM_ERROR_MSG; retval = converse(pamh, 1, &conv_msg, &resp); if (PAM_SUCCESS == retval) { if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "send msg=\"%s\"", conv_msg.msg); } else { _pam_log(LOG_WARNING, "%s: error sending msg=\"%s\", retval=%d", __func__, conv_msg.msg, retval); } } *sptr = PAM_AUTH_ERR; ret = 0; _pam_log(LOG_NOTICE, "auth failed %d", msg); break; case TAC_PLUS_AUTHEN_STATUS_GETDATA: /* not implemented */ if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETDATA"); if (NULL != conv_msg.msg) { int retval = -1; int echo_off = (0x1 == (re.flags & 0x1)); conv_msg.msg_style = echo_off ? PAM_PROMPT_ECHO_OFF : PAM_PROMPT_ECHO_ON; retval = converse(pamh, 1, &conv_msg, &resp); if (PAM_SUCCESS == retval) { if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "sent msg=\"%s\", resp=\"%s\"", conv_msg.msg, resp->resp); if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG)) syslog(LOG_DEBUG, "%s: calling tac_cont_send", __func__); if (0 > tac_cont_send_seq(fd, resp->resp, re.seq_no + 1)) { _pam_log(LOG_ERR, "error sending continue req to TACACS+ server"); status = PAM_AUTH_ERR; } } else { _pam_log(LOG_WARNING, "%s: error sending msg=\"%s\", retval=%d (%s)", __func__, conv_msg.msg, retval, pam_strerror(pamh, retval)); status = PAM_AUTH_ERR; } } else { syslog(LOG_ERR, "GETDATA response with no message, returning PAM_AUTH_ERR"); status = PAM_AUTH_ERR; } ret = 0; break; case TAC_PLUS_AUTHEN_STATUS_GETUSER: /* not implemented */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETUSER"); ret = 0; break; case TAC_PLUS_AUTHEN_STATUS_GETPASS: if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETPASS"); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: tac_cont_send called", __func__); if (tac_cont_send(fd, pass) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); ret = 0; break; } /* continue the while loop; go read tac response */ break; case TAC_PLUS_AUTHEN_STATUS_RESTART: /* try it again */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_RESTART (not impl)"); /* * not implemented * WdJ: I *think* you can just do tac_authen_send(user, pass) again * but I'm not sure */ ret = 0; break; case TAC_PLUS_AUTHEN_STATUS_ERROR: /* server has problems */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_ERROR"); ret = 0; break; case TAC_PLUS_AUTHEN_STATUS_FOLLOW: /* server tells to try a different server address */ /* not implemented */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_FOLLOW"); ret = 0; break; default: if (msg < 0) { /* connection error */ ret = 0; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "error communicating with tacacs server"); break; } /* unknown response code */ ret = 0; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: unknown response 0x%02x", msg); } if (NULL != resp) { free(resp->resp); free(resp); } if (NULL != re.msg); free(re.msg); return ret; } /* * Only acct and auth now; should handle all the cases here * Talk to the tacacs server for each type of transaction conversation */ static void talk_tac_server(int ctrl, int fd, char *user, char *pass, char *tty, char *r_addr, struct tac_attrib **attr, int *sptr, struct areply *reply, pam_handle_t * pamh) { if(!pass && attr) { /* acct, much simpler */ int retval; struct areply arep; retval = tac_author_send(fd, user, tty, r_addr, *attr); if(retval < 0) { _pam_log (LOG_ERR, "error getting authorization"); *sptr = PAM_AUTH_ERR; return; } if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: sent authorization request for [%s]", __func__, user); arep.msg = NULL; tac_author_read(fd, &arep); if (reply) *reply = arep; if(arep.status != AUTHOR_STATUS_PASS_ADD && arep.status != AUTHOR_STATUS_PASS_REPL) { /* * this is debug because we can get called for any user for * commands like sudo, not just tacacs users */ *sptr = PAM_PERM_DENIED; _pam_log (LOG_ERR, "TACACS+ authorization failed for [%s] (status=%d)", user, arep.status); if(arep.msg != NULL && !reply) free (arep.msg); /* if reply is set, caller will free */ } else { *sptr = PAM_SUCCESS; } } else if (pass) { /* auth */ if (tac_authen_send(fd, user, pass, tty, r_addr, TAC_PLUS_AUTHEN_LOGIN) < 0) { _pam_log(LOG_ERR, "error sending auth req to TACACS+ server"); } else { while ( tac_auth_converse(ctrl, fd, sptr, pass, pamh)) ; } } } /* * find a responding tacacs server, and converse with it. * See comments at do_tac_connect() below */ static void find_tac_server(int ctrl, int *tacfd, char *user, char *pass, char *tty, char *r_addr, struct tac_attrib **attr, int *sptr, struct areply *reply, pam_handle_t * pamh) { int fd = -1, srv_i; for (srv_i = 0; srv_i < tac_srv_no; srv_i++) { if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: trying srv[%d] %s", __func__, srv_i, tac_srv[srv_i].addr ? tac_ntop(tac_srv[srv_i].addr->ai_addr) : "not set"); fd = tac_connect_single(tac_srv[srv_i].addr, tac_srv[srv_i].key, NULL, __vrfname); if (fd < 0) { _pam_log(LOG_ERR, "connection to srv[%d] %s failed: %m", srv_i, tac_srv[srv_i].addr ? tac_ntop(tac_srv[srv_i].addr->ai_addr) : "not set"); active_server.addr = NULL; /* in case last in list */ continue; } talk_tac_server(ctrl, fd, user, pass, tty, r_addr, attr, sptr, reply, pamh); if (*sptr == PAM_SUCCESS || *sptr == PAM_AUTH_ERR || *sptr == PAM_PERM_DENIED) { if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: srv[%d] %s, pam_status=%d", __func__, srv_i, tac_ntop(tac_srv[srv_i].addr->ai_addr), *sptr); if (*sptr == PAM_SUCCESS) { if (active_server.addr == NULL) { active_server.addr = tac_srv[srv_i].addr; active_server.key = tac_srv[srv_i].key; } break; } /* else try other servers, if any. On errs, won't need fd */ } else /* in case end of list */ active_server.addr = NULL; close(fd); fd = -1; } *tacfd = fd; } /* * We have to make a new connection each time, because libtac is single * threaded (doesn't support multiple connects at the same time due to * use of globals), and doesn't have support for persistent connections. * That's fixable, but not worth the effort at this point. * * Trying to make this common code is ugly, but worth it to simplify * maintenance and debugging. * * The problem is that the definition allows for multiple tacacs * servers to be consulted, but a lot of the code was written such * that once a server is found that responds, it keeps using it. * That means when we are finding a server we need to do the full sequence. * The related issue is that the lower level code can't communicate * with multiple servers at the same time, and can't keep a connection * open. * * TODO: Really should have a structure to pass user, pass, tty, and r_addr * around everywhere. */ static int do_tac_connect(int ctrl, int *tacfd, char *user, char *pass, char *tty, char *r_addr, struct tac_attrib **attr, struct areply *reply, pam_handle_t * pamh) { int status = PAM_AUTHINFO_UNAVAIL, fd; if (active_server.addr == NULL) { /* find a server with the info we want */ find_tac_server(ctrl, &fd, user, pass, tty, r_addr, attr, &status, reply, pamh); } else { /* connect to the already chosen server, so we get * consistent results. */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: use previous server %s", __func__, tac_ntop(active_server.addr->ai_addr)); fd = tac_connect_single(active_server.addr, active_server.key, NULL, __vrfname); if (fd < 0) _pam_log(LOG_ERR, "reconnect failed: %m"); else talk_tac_server(ctrl, fd, user, pass, tty, r_addr, attr, &status, reply, pamh); } /* * this is debug because we can get called for any user for * commands like sudo, not just tacacs users, so it's not an * error to fail here. The caller can handle the logging. */ if ((ctrl & PAM_TAC_DEBUG) && status != PAM_SUCCESS && status != PAM_AUTH_ERR) _pam_log(LOG_ERR, "no more servers to connect"); if (tacfd) *tacfd = fd; /* auth caller needs fd */ else if (fd != -1) close(fd); /* acct caller doesn't need connection */ return status; }
/* * Send an accounting record to the TACACS+ server. * We send the start/stop accounting records even if the user is not known * to the TACACS+ server. This seems non-intuitive, but it's the way * this code is written to work. */ int _pam_account(pam_handle_t *pamh, int argc, const char **argv, int type, char *cmd) { int retval; int ctrl; char *user = NULL; char *tty = NULL; char *r_addr = NULL; char *typemsg; int status = PAM_SESSION_ERR; int srv_i, tac_fd; typemsg = tac_acct_flag2str(type); ctrl = _pam_parse (argc, argv); if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: [%s] called (pam_tacplus v%u.%u.%u)", __func__, typemsg, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); _pam_get_user(pamh, &user); if (user == NULL) return PAM_USER_UNKNOWN; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: username [%s] obtained", __func__, user); if (!task_id) #if defined(HAVE_OPENSSL_RAND_H) && defined(HAVE_LIBCRYPTO) RAND_pseudo_bytes((unsigned char *) &task_id, sizeof(task_id)); #else task_id = (short unsigned int) tac_magic(); #endif _pam_get_terminal(pamh, &tty); if(!strncmp(tty, "/dev/", 5)) tty += 5; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: tty [%s] obtained", __func__, tty); _pam_get_rhost(pamh, &r_addr); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: rhost [%s] obtained", __func__, r_addr); /* checks for specific data required by TACACS+, which should be supplied in command line */ if(tac_protocol[0] == '\0') { _pam_log (LOG_ERR, "ACC: TACACS+ protocol type not configured"); return PAM_AUTH_ERR; } /* when this module is called from within pppd or other application dealing with serial lines, it is likely that we will get hit with signal caused by modem hangup; this is important only for STOP packets, it's relatively rare that modem hangs up on accounting start */ if(type == TAC_PLUS_ACCT_FLAG_STOP) { signal(SIGALRM, SIG_IGN); signal(SIGCHLD, SIG_IGN); signal(SIGHUP, SIG_IGN); } /* * If PAM_SESSION_ERR is used, then the pam config can't * ignore server failures, so use PAM_AUTHINFO_UNAVAIL. * * We have to make a new connection each time, because libtac is single * threaded (doesn't support multiple connects at the same time due to * use of globals)), and doesn't have support for persistent connections. * That's fixable, but not worth the effort at this point. * * TODO: this should be converted to use do_tac_connect eventually. */ status = PAM_AUTHINFO_UNAVAIL; 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, __vrfname); if (tac_fd < 0) { _pam_log(LOG_WARNING, "%s: error sending %s (fd)", __func__, typemsg); continue; } if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: connected with fd=%d to srv[%d] %s", __func__, tac_fd, srv_i, tac_srv[srv_i].addr ? tac_ntop(tac_srv[srv_i].addr->ai_addr) : "not set"); retval = _pam_send_account(tac_fd, type, user, tty, r_addr, cmd); if (retval < 0) { _pam_log(LOG_WARNING, "%s: error sending %s (acct)", __func__, typemsg); } else { status = PAM_SUCCESS; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: [%s] for [%s] sent", __func__, typemsg, user); } if ((status == PAM_SUCCESS) && !(ctrl & PAM_TAC_ACCT)) { /* do not send acct start/stop packets to _all_ servers */ break; } } if (type == TAC_PLUS_ACCT_FLAG_STOP) { signal(SIGALRM, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_DFL); } return status; }
PAM_EXTERN int pam_sm_chauthtok (pam_handle_t * pamh, int flags, int argc, const char **argv) { int ctrl, retval; char *user; char *pass; char *tty; char *r_addr; int srv_i; int tac_fd; int status = PAM_TRY_AGAIN; int seq = 0; user = pass = tty = r_addr = NULL; ctrl = _pam_parse (argc, argv); if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)" , __FUNCTION__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); /* Preliminary call to check readiness of our module */ if (flags & PAM_PRELIM_CHECK) { /* Can we connect to TACACS? */ for (srv_i = 0; srv_i < tac_srv_no; srv_i++) { if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: trying srv %d", __FUNCTION__, srv_i ); tac_fd = tac_connect_single(tac_srv[srv_i], tac_srv_key[srv_i]); if (tac_fd < 0) { _pam_log (LOG_ERR, "connection failed srv %d: %m", srv_i); if (srv_i == tac_srv_no-1) { _pam_log (LOG_ERR, "no more servers to connect"); return PAM_TRY_AGAIN; } continue; } } close(tac_fd); return PAM_SUCCESS; } if ((user = _pam_get_user(pamh)) == NULL) return PAM_USER_UNKNOWN; if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: user [%s] obtained", __FUNCTION__, user); /* CHPASS does not send a password in data field, * user is prompted by a GETDATA reply packet */ pass = malloc(5); strcpy(pass,"null"); tty = _pam_get_terminal(pamh); if (!strncmp (tty, "/dev/", 5)) tty += 5; if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: tty [%s] obtained", __FUNCTION__, tty); r_addr = _pam_get_rhost(pamh); if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: rhost [%s] obtained", __FUNCTION__, r_addr); /* Attempt server connect */ for (srv_i = 0; srv_i < tac_srv_no; srv_i++) { status = TAC_PLUS_AUTHEN_STATUS_FAIL; if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: trying srv %d", __FUNCTION__, srv_i ); tac_fd = tac_connect_single(tac_srv[srv_i], tac_srv_key[srv_i]); if (tac_fd < 0) { _pam_log (LOG_ERR, "connection failed srv %d: %m", srv_i); if (srv_i == tac_srv_no-1) { _pam_log (LOG_ERR, "no more servers to connect"); return PAM_AUTHINFO_UNAVAIL; } continue; } /* Send AUTHEN/START */ if (tac_authen_send(tac_fd, user, pass, tty, r_addr, TAC_PLUS_AUTHEN_CHPASS, ctrl) < 0) { _pam_log (LOG_ERR, "error sending auth req to TACACS+ server"); status = PAM_AUTHINFO_UNAVAIL; } else { /* Read AUTHEN/REPLY and act on status */ struct msg_status *msgstatus = malloc(sizeof(msg_status)); do { tac_authen_read(msgstatus, tac_fd, ctrl, &seq); status = msgstatus->status; switch (status) { case TAC_PLUS_AUTHEN_STATUS_GETPASS: /* AUTHEN/CONT with password */ if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: tac_cont_send called", __FUNCTION__); if (tac_cont_send(tac_fd, pass, ctrl, seq+1) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); status = PAM_AUTHINFO_UNAVAIL; } break; case TAC_PLUS_AUTHEN_STATUS_GETDATA: { /* Get data from user with pam conversation */ struct pam_message msg; struct pam_response *resp = NULL; int retval; char *user_data = NULL; /* set up conversation call */ msg.msg_style = PAM_PROMPT_ECHO_OFF; msg.msg = malloc(100); strcpy((char *)msg.msg,msgstatus->server_msg); if ((retval = converse (pamh, 1, &msg, &resp)) != PAM_SUCCESS) { status = PAM_AUTHINFO_UNAVAIL; } else { if (resp != NULL) { if (resp->resp == NULL && (ctrl & PAM_TAC_DEBUG)) _pam_log (LOG_DEBUG, "pam_sm_authenticate: NULL given by user for GETDATA request"); user_data = resp->resp; resp->resp = NULL; } else { if (ctrl & PAM_TAC_DEBUG) { _pam_log (LOG_DEBUG, "pam_sm_authenticate: no error reported"); _pam_log (LOG_DEBUG, "getting data from user - NULL returned!?"); } return PAM_CONV_ERR; } /* AUTHEN/CONT with data */ if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: tac_cont_send called", __FUNCTION__); if (tac_cont_send(tac_fd, user_data, ctrl, seq+1) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); status = PAM_AUTHINFO_UNAVAIL; } } free(msg.msg); } break; case TAC_PLUS_AUTHEN_STATUS_GETUSER: /* AUTHEN/CONT with username */ if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: tac_cont_send called", __FUNCTION__); if (tac_cont_send(tac_fd, user, ctrl, seq+1) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); status = PAM_AUTHINFO_UNAVAIL; } break; } } while ( (status == TAC_PLUS_AUTHEN_STATUS_GETDATA) || (status == TAC_PLUS_AUTHEN_STATUS_GETPASS) || (status == TAC_PLUS_AUTHEN_STATUS_GETUSER) ); if (status != TAC_PLUS_AUTHEN_STATUS_PASS) { _pam_log (LOG_ERR, "auth failed: %d", status); status = PAM_AUTHTOK_ERR; } else { /* OK, we got authenticated; save the server that accepted us for pam_sm_acct_mgmt and exit the loop */ status = PAM_SUCCESS; active_server = tac_srv[srv_i]; active_key = tac_srv_key[srv_i]; close(tac_fd); break; } } close(tac_fd); } if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: exit with pam status: %i", __FUNCTION__, status); bzero (pass, strlen (pass)); free(pass); pass = NULL; return status; } /* pam_sm_chauthtok */
/* authorizes user on remote TACACS+ server, i.e. checks * his permission to access requested service * returns PAM_SUCCESS if the service is allowed */ PAM_EXTERN int pam_sm_acct_mgmt (pam_handle_t * pamh, int flags, int argc, const char **argv) { int retval, ctrl, status=PAM_AUTH_ERR; char *user; char *tty; char *r_addr; struct areply arep; struct tac_attrib *attr = NULL; int tac_fd; user = tty = r_addr = NULL; /* this also obtains service name for authorization this should be normally performed by pam_get_item(PAM_SERVICE) but since PAM service names are incompatible TACACS+ we have to pass it via command line argument until a better solution is found ;) */ ctrl = _pam_parse (argc, argv); if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)" , __FUNCTION__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); if ((user = _pam_get_user(pamh)) == NULL) return PAM_USER_UNKNOWN; if (ctrl & PAM_TAC_DEBUG) _pam_log(LOG_DEBUG, "%s: username obtained [%s]", __FUNCTION__, user); tty = _pam_get_terminal(pamh); if(!strncmp(tty, "/dev/", 5)) tty += 5; if (ctrl & PAM_TAC_DEBUG) _pam_log(LOG_DEBUG, "%s: tty obtained [%s]", __FUNCTION__, tty); r_addr = _pam_get_rhost(pamh); if (ctrl & PAM_TAC_DEBUG) _pam_log(LOG_DEBUG, "%s: rhost obtained [%s]", __FUNCTION__, r_addr); /* checks if user has been successfully authenticated by TACACS+; we cannot solely authorize user if it hasn't been authenticated or has been authenticated by method other than TACACS+ */ if(!active_server) { _pam_log (LOG_ERR, "user not authenticated by TACACS+"); return PAM_AUTH_ERR; } if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: active server is [%s]", __FUNCTION__, tac_ntop(active_server->ai_addr, active_server->ai_addrlen)); /* checks for specific data required by TACACS+, which should be supplied in command line */ if(tac_service == NULL || *tac_service == '\0') { _pam_log (LOG_ERR, "TACACS+ service type not configured"); return PAM_AUTH_ERR; } if(tac_protocol == NULL || *tac_protocol == '\0') { _pam_log (LOG_ERR, "TACACS+ protocol type not configured"); return PAM_AUTH_ERR; } tac_add_attrib(&attr, "service", tac_service); tac_add_attrib(&attr, "protocol", tac_protocol); tac_fd = tac_connect_single(active_server, active_key); if(tac_fd < 0) { _pam_log (LOG_ERR, "TACACS+ server unavailable"); if(arep.msg != NULL) free (arep.msg); close(tac_fd); return PAM_AUTH_ERR; } retval = tac_author_send(tac_fd, user, tty, r_addr, attr); tac_free_attrib(&attr); if(retval < 0) { _pam_log (LOG_ERR, "error getting authorization"); if(arep.msg != NULL) free (arep.msg); close(tac_fd); return PAM_AUTH_ERR; } if (ctrl & PAM_TAC_DEBUG) _pam_log(LOG_DEBUG, "%s: sent authorization request", __FUNCTION__); tac_author_read(tac_fd, &arep); if(arep.status != AUTHOR_STATUS_PASS_ADD && arep.status != AUTHOR_STATUS_PASS_REPL) { _pam_log (LOG_ERR, "TACACS+ authorisation failed for [%s]", user); if(arep.msg != NULL) free (arep.msg); close(tac_fd); return PAM_PERM_DENIED; } if (ctrl & PAM_TAC_DEBUG) _pam_log(LOG_DEBUG, "%s: user [%s] successfully authorized", __FUNCTION__, user); status = PAM_SUCCESS; attr = arep.attr; while (attr != NULL) { char attribute[attr->attr_len]; char value[attr->attr_len]; char *sep; sep = index(attr->attr, '='); if(sep == NULL) sep = index(attr->attr, '*'); if(sep != NULL) { bcopy(attr->attr, attribute, attr->attr_len-strlen(sep)); attribute[attr->attr_len-strlen(sep)] = '\0'; bcopy(sep, value, strlen(sep)); value[strlen(sep)] = '\0'; size_t i; for (i = 0; attribute[i] != '\0'; i++) { attribute[i] = toupper(attribute[i]); if (attribute[i] == '-') attribute[i] = '_'; } if (ctrl & PAM_TAC_DEBUG) _pam_log(LOG_DEBUG, "%s: returned attribute `%s%s' from server", __FUNCTION__, attribute, value); /* make returned attributes available for other PAM modules via PAM environment */ if (pam_putenv(pamh, strncat(attribute, value, strlen(value))) != PAM_SUCCESS) _pam_log(LOG_WARNING, "%s: unable to set PAM environment", __FUNCTION__); } else { _pam_log(LOG_WARNING, "%s: invalid attribute `%s', no separator", __FUNCTION__, attr->attr); } attr = attr->next; } /* free returned attributes */ if(arep.attr != NULL) tac_free_attrib(&arep.attr); if(arep.msg != NULL) free (arep.msg); close(tac_fd); return status; } /* pam_sm_acct_mgmt */
/* authenticates user on remote TACACS+ server * returns PAM_SUCCESS if the supplied username and password * pair is valid */ PAM_EXTERN int pam_sm_authenticate (pam_handle_t * pamh, int flags, int argc, const char **argv) { int ctrl, retval; char *user; char *pass; char *tty; char *r_addr; int srv_i; int tac_fd; int status = PAM_AUTH_ERR; int seq = 0; user = pass = tty = r_addr = NULL; ctrl = _pam_parse (argc, argv); if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)" , __FUNCTION__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); if ((user = _pam_get_user(pamh)) == NULL) return PAM_USER_UNKNOWN; if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: user [%s] obtained", __FUNCTION__, user); /* uwzgledniac PAM_DISALLOW_NULL_AUTHTOK */ retval = tacacs_get_password (pamh, flags, ctrl, &pass); if (retval != PAM_SUCCESS || pass == NULL || *pass == '\0') { _pam_log (LOG_ERR, "unable to obtain password"); return PAM_CRED_INSUFFICIENT; } retval = pam_set_item (pamh, PAM_AUTHTOK, pass); if (retval != PAM_SUCCESS) { _pam_log (LOG_ERR, "unable to set password"); return PAM_CRED_INSUFFICIENT; } if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: password obtained", __FUNCTION__); tty = _pam_get_terminal(pamh); if (!strncmp (tty, "/dev/", 5)) tty += 5; if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: tty [%s] obtained", __FUNCTION__, tty); r_addr = _pam_get_rhost(pamh); if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: rhost [%s] obtained", __FUNCTION__, r_addr); /* Attempt server connect */ for (srv_i = 0; srv_i < tac_srv_no; srv_i++) { status = TAC_PLUS_AUTHEN_STATUS_FAIL; if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: trying srv %d", __FUNCTION__, srv_i ); tac_fd = tac_connect_single(tac_srv[srv_i], tac_srv_key[srv_i]); if (tac_fd < 0) { _pam_log (LOG_ERR, "connection failed srv %d: %m", srv_i); if (srv_i == tac_srv_no-1) { _pam_log (LOG_ERR, "no more servers to connect"); return PAM_AUTHINFO_UNAVAIL; } continue; } /* Send AUTHEN/START */ if (tac_authen_send(tac_fd, user, pass, tty, r_addr, TAC_PLUS_AUTHEN_LOGIN, ctrl) < 0) { _pam_log (LOG_ERR, "error sending auth req to TACACS+ server"); status = PAM_AUTHINFO_UNAVAIL; } else { /* Read AUTHEN/REPLY and act on status */ struct msg_status *msgstatus = malloc(sizeof(msg_status)); do { tac_authen_read(msgstatus, tac_fd, ctrl, &seq); status = msgstatus->status; switch (status) { case TAC_PLUS_AUTHEN_STATUS_GETPASS: /* AUTHEN/CONT with password */ if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: tac_cont_send called", __FUNCTION__); if (tac_cont_send(tac_fd, pass, ctrl, seq+1) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); status = PAM_MAXTRIES; } break; case TAC_PLUS_AUTHEN_STATUS_GETDATA: { /* The only GETDATA request should be if the user's password * has expired and ACS is requesting a new password * * Check if a conversation function has been set and either * return a PAM_AUTHTOK_EXPIRED or start a conversation * with the user for to change the password */ if (!strcmp(tty,"http")) { status = PAM_NEW_AUTHTOK_REQD; if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: expired", __FUNCTION__); } else { /* Get data from user with pam conversation */ struct pam_message msg; struct pam_response *resp = NULL; int retval; char *user_data = NULL; /* set up conversation call */ msg.msg_style = PAM_PROMPT_ECHO_OFF; msg.msg = malloc(100); strcpy((char *)msg.msg,msgstatus->server_msg); if ((retval = converse (pamh, 1, &msg, &resp)) != PAM_SUCCESS) { status = PAM_AUTHINFO_UNAVAIL; } else { if (resp != NULL) { if (resp->resp == NULL && (ctrl & PAM_TAC_DEBUG)) _pam_log (LOG_DEBUG, "pam_sm_authenticate: NULL given by user for GETDATA request"); user_data = resp->resp; resp->resp = NULL; } else { if (ctrl & PAM_TAC_DEBUG) { _pam_log (LOG_DEBUG, "pam_sm_authenticate: no error reported"); _pam_log (LOG_DEBUG, "getting data from user - NULL returned!?"); } return PAM_CONV_ERR; } /* AUTHEN/CONT with data */ if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: tac_cont_send called", __FUNCTION__); if (tac_cont_send(tac_fd, user_data, ctrl, seq+1) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); status = PAM_AUTHINFO_UNAVAIL; } } free(msg.msg); } } break; case TAC_PLUS_AUTHEN_STATUS_GETUSER: /* AUTHEN/CONT with username */ if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: tac_cont_send called", __FUNCTION__); if (tac_cont_send(tac_fd, user, ctrl, seq+1) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); status = PAM_AUTHINFO_UNAVAIL; } break; } } while ( (status == TAC_PLUS_AUTHEN_STATUS_GETDATA) || (status == TAC_PLUS_AUTHEN_STATUS_GETPASS) || (status == TAC_PLUS_AUTHEN_STATUS_GETUSER) ); if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: out of while loop status=%d", __FUNCTION__,status); if (status == TAC_PLUS_AUTHEN_STATUS_PASS) { /* OK, we got authenticated; save the server that accepted us for pam_sm_acct_mgmt and exit the loop */ status = PAM_SUCCESS; active_server = tac_srv[srv_i]; active_key = tac_srv_key[srv_i]; } else if (status != PAM_NEW_AUTHTOK_REQD) { _pam_log (LOG_ERR, "auth failed: %d", status); status = PAM_AUTH_ERR; } } close(tac_fd); /* TODO: Allow time for tac server to reply * TODO: Check if reply received before connecting to next server */ } if (ctrl & PAM_TAC_DEBUG) _pam_log (LOG_DEBUG, "%s: exit with pam status: %i", __FUNCTION__, status); bzero (pass, strlen (pass)); free(pass); pass = NULL; return status; } /* pam_sm_authenticate */
/* authenticates user on remote TACACS+ server * returns PAM_SUCCESS if the supplied username and password * pair is valid */ PAM_EXTERN int pam_sm_authenticate (pam_handle_t * pamh, int flags, int argc, const char **argv) { int ctrl, retval; char *user; char *pass; char *tty; char *r_addr; int srv_i; int tac_fd, status, msg, communicating; user = pass = tty = r_addr = NULL; ctrl = _pam_parse(argc, argv); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)", __FUNCTION__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); if ((user = _pam_get_user(pamh)) == NULL) return PAM_USER_UNKNOWN; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: user [%s] obtained", __FUNCTION__, user); /* uwzgledniac PAM_DISALLOW_NULL_AUTHTOK */ retval = tacacs_get_password (pamh, flags, ctrl, &pass); if (retval != PAM_SUCCESS || pass == NULL || *pass == '\0') { _pam_log(LOG_ERR, "unable to obtain password"); return PAM_CRED_INSUFFICIENT; } retval = pam_set_item (pamh, PAM_AUTHTOK, pass); if (retval != PAM_SUCCESS) { _pam_log(LOG_ERR, "unable to set password"); return PAM_CRED_INSUFFICIENT; } if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: password obtained", __FUNCTION__); tty = _pam_get_terminal(pamh); if (!strncmp(tty, "/dev/", 5)) tty += 5; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: tty [%s] obtained", __FUNCTION__, tty); r_addr = _pam_get_rhost(pamh); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: rhost [%s] obtained", __FUNCTION__, r_addr); 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); if (tac_fd < 0) { _pam_log(LOG_ERR, "connection failed srv %d: %m", srv_i); continue; } if (tac_authen_send(tac_fd, user, pass, tty, r_addr) < 0) { close(tac_fd); _pam_log(LOG_ERR, "error sending auth req to TACACS+ server"); continue; } communicating = 1; while (communicating) { msg = tac_authen_read(tac_fd); /* talk the protocol */ switch (msg) { case TAC_PLUS_AUTHEN_STATUS_PASS: /* success */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_PASS"); status = PAM_SUCCESS; communicating = 0; active_server.addr = tac_srv[srv_i].addr; active_server.key = tac_srv[srv_i].key; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: active srv %d", __FUNCTION__, srv_i); break; case TAC_PLUS_AUTHEN_STATUS_FAIL: /* forget it */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_FAIL"); status = PAM_AUTH_ERR; communicating = 0; _pam_log(LOG_ERR, "auth failed: %d", msg); break; case TAC_PLUS_AUTHEN_STATUS_GETDATA: /* not implemented */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETDATA"); communicating = 0; break; case TAC_PLUS_AUTHEN_STATUS_GETUSER: /* not implemented */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETUSER"); communicating = 0; break; case TAC_PLUS_AUTHEN_STATUS_GETPASS: if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_GETPASS"); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: tac_cont_send called", __FUNCTION__); if (tac_cont_send(tac_fd, pass) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); communicating = 0; break; } /* continue the while loop; go read tac response */ break; case TAC_PLUS_AUTHEN_STATUS_RESTART: /* try it again */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_RESTART"); /* * not implemented * WdJ: I *think* you can just do tac_authen_send(user, pass) again * but I'm not sure */ communicating = 0; break; case TAC_PLUS_AUTHEN_STATUS_ERROR: /* server has problems */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_ERROR"); communicating = 0; break; case TAC_PLUS_AUTHEN_STATUS_FOLLOW: /* server tells to try a different server address */ /* not implemented */ if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: TAC_PLUS_AUTHEN_STATUS_FOLLOW"); communicating = 0; break; default: if (msg < 0) { /* connection error */ communicating = 0; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "error communicating with tacacs server"); break; } /* unknown response code */ communicating = 0; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "tacacs status: unknown response 0x%02x", msg); } } /* end while(communicating) */ close(tac_fd); if (status == PAM_SUCCESS || status == PAM_AUTH_ERR) break; } if (status != PAM_SUCCESS && status != PAM_AUTH_ERR) _pam_log(LOG_ERR, "no more servers to connect"); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: exit with pam status: %d", __FUNCTION__, status); bzero(pass, strlen (pass)); free(pass); pass = NULL; return status; } /* pam_sm_authenticate */
/* authenticates user on remote TACACS+ server * returns PAM_SUCCESS if the supplied username and password * pair is valid */ PAM_EXTERN int pam_sm_authenticate (pam_handle_t * pamh, int flags, int argc, const char **argv) { int ctrl, retval; char *user; char *pass; char *tty; char *r_addr; int srv_i; int tac_fd; int status = PAM_AUTH_ERR; user = pass = tty = r_addr = NULL; ctrl = _pam_parse (argc, argv); if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)" , __FUNCTION__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); if ((user = _pam_get_user(pamh)) == NULL) return PAM_USER_UNKNOWN; if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: user [%s] obtained", __FUNCTION__, user); /* uwzgledniac PAM_DISALLOW_NULL_AUTHTOK */ retval = tacacs_get_password (pamh, flags, ctrl, &pass); if (retval != PAM_SUCCESS || pass == NULL || *pass == '\0') { _pam_log (LOG_ERR, "unable to obtain password"); return PAM_CRED_INSUFFICIENT; } retval = pam_set_item (pamh, PAM_AUTHTOK, pass); if (retval != PAM_SUCCESS) { _pam_log (LOG_ERR, "unable to set password"); return PAM_CRED_INSUFFICIENT; } if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: password obtained", __FUNCTION__); tty = _pam_get_terminal(pamh); if (!strncmp (tty, "/dev/", 5)) tty += 5; if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: tty [%s] obtained", __FUNCTION__, tty); r_addr = _pam_get_rhost(pamh); if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: rhost [%s] obtained", __FUNCTION__, r_addr); for (srv_i = 0; srv_i < tac_srv_no; srv_i++) { int msg = TAC_PLUS_AUTHEN_STATUS_FAIL; if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: trying srv %d", __FUNCTION__, srv_i ); tac_fd = tac_connect_single(tac_srv[srv_i], tac_srv_key[srv_i]); if (tac_fd < 0) { _pam_log (LOG_ERR, "connection failed srv %d: %m", srv_i); if (srv_i == tac_srv_no-1) { _pam_log (LOG_ERR, "no more servers to connect"); return PAM_AUTHINFO_UNAVAIL; } continue; } if (tac_authen_send(tac_fd, user, pass, tty, r_addr) < 0) { _pam_log (LOG_ERR, "error sending auth req to TACACS+ server"); status = PAM_AUTHINFO_UNAVAIL; } else { msg = tac_authen_read(tac_fd); if (msg == TAC_PLUS_AUTHEN_STATUS_GETPASS) { if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: tac_cont_send called", __FUNCTION__); if (tac_cont_send(tac_fd, pass) < 0) { _pam_log (LOG_ERR, "error sending continue req to TACACS+ server"); status = PAM_AUTHINFO_UNAVAIL; } else { msg = tac_authen_read(tac_fd); if (msg != TAC_PLUS_AUTHEN_STATUS_PASS) { _pam_log (LOG_ERR, "auth failed: %d", msg); status = PAM_AUTH_ERR; } else { /* OK, we got authenticated; save the server that accepted us for pam_sm_acct_mgmt and exit the loop */ status = PAM_SUCCESS; active_server = tac_srv[srv_i]; active_key = tac_srv_key[srv_i]; close(tac_fd); break; } } } else if (msg != TAC_PLUS_AUTHEN_STATUS_PASS) { _pam_log (LOG_ERR, "auth failed: %d", msg); status = PAM_AUTH_ERR; } else { /* OK, we got authenticated; save the server that accepted us for pam_sm_acct_mgmt and exit the loop */ status = PAM_SUCCESS; active_server = tac_srv[srv_i]; active_key = tac_srv_key[srv_i]; close(tac_fd); break; } } close(tac_fd); } if (ctrl & PAM_TAC_DEBUG) syslog (LOG_DEBUG, "%s: exit with pam status: %i", __FUNCTION__, status); bzero (pass, strlen (pass)); free(pass); pass = NULL; return status; } /* pam_sm_authenticate */
/** * ... document me ... */ enum nss_status _nss_tacplus_getpwnam_r(const char *name, struct passwd *pw, char *buffer, size_t buflen, int *errnop) { enum nss_status status = NSS_STATUS_NOTFOUND; int tac_fd = -1; struct addrinfo *server = NULL; time_t now = -1; uint32_t cycle = 0; (void)pthread_once(&G_tacplus_initialized, &_initalize_tacplus); now = time(NULL); // check to see if we should re-read our configuration // once per 32 second cycle cycle = (now - G_tacplus_started) >> 5; if ( NSS_STATUS_SUCCESS != G_tacplus_conf.status || cycle > G_tacplus_cycles) { G_tacplus_cycles = cycle; _check_config(cycle); } else { G_tacplus_cycles = cycle; } if (NSS_STATUS_SUCCESS != G_tacplus_conf.status) { status = G_tacplus_conf.status; *errnop = G_tacplus_conf.errnum; assert(NULL == G_tacplus_conf.servers); } // Iterate through our servers linked list, stop when we get an answer // that isn't NOTFOUND. Since we're twisting TACACS+ authorization // functionality to provide this facility, we treat AUTHOR_STATUS_FAIL, // AUTHOR_STATUS_ERROR, AUTHOR_STATUS_FAIL as "soft" fails, and just // move on to the next server. Doing otherwise would result in potentially // unexpected behaviors if the user is provisioned on server->ai_next, but // not on server. for (server = G_tacplus_conf.servers; NSS_STATUS_NOTFOUND == status && NULL != server; server = server->ai_next) { void *sin_addr = NULL; // the first member of sockaddr_in and sockaddr_in6 are the same, so // this should always work. uint16_t port = ntohs(((struct sockaddr_in *)server->ai_addr)->sin_port); // this is ugly, but we need to differentiate IPv6 vs. IPv4 addresses // (in practice, this may not be necessary, as I'm not certain if the // remainder of this code is capable of handling IPv6, yet.) sin_addr = ( AF_INET6 == server->ai_family ? (void*)&((struct sockaddr_in6 *)server->ai_addr)->sin6_addr : (void*)&((struct sockaddr_in *)server->ai_addr)->sin_addr); inet_ntop(server->ai_family, sin_addr, buffer, buflen); syslog(LOG_INFO, "%s: begin lookup: user=`%s', server=`%s:%d'", __FILE__, name, buffer, port); // connect to the current server errno = 0; tac_fd = tac_connect_single(server, G_tacplus_conf.secret, NULL, 15); if (0 > tac_fd) { char errtext[256]; int errnum = errno; strerror_r(errnum, errtext, sizeof(errtext)); syslog(LOG_WARNING, "%s: Connection to TACACS+ server failed: server=`%s:%d', " "errno=%d, errtext=`%s'", __FILE__, buffer, port, errnum, errtext); // upon failure, simply move on to the next server in the list continue; } else { int rv = -1; struct tac_attrib *attr = NULL; tac_add_attrib(&attr, "service", G_tacplus_conf.service); tac_add_attrib(&attr, "protocol", G_tacplus_conf.protocol); rv = tac_author_send(tac_fd, name, G_tacplus_conf.protocol, "unknown", attr); tac_free_attrib(&attr); if (0 > rv) { status = NSS_STATUS_TRYAGAIN; } else { struct areply reply; memset(&reply, '\0', sizeof(reply)); tac_author_read(tac_fd, &reply); if ( (AUTHOR_STATUS_PASS_ADD == reply.status) || (AUTHOR_STATUS_PASS_REPL == reply.status)) { syslog(LOG_INFO, "%s: found match: user=`%s', server=`%s:%d', " "status=%d, attributes? %s", __FILE__, name, buffer, port, reply.status, (NULL == reply.attr ? "no" : "yes")); status = _passwd_from_reply(&reply, name, pw, buffer, buflen, errnop); } else { syslog(LOG_INFO, "%s: lookup failed: user=`%s', server=`%s:%d', " "status=%d, msg=%s", __FILE__, name, buffer, port, reply.status, reply.msg); } if (NULL != reply.attr) { tac_free_attrib(&reply.attr); } free(reply.msg); } } // XXX: there is a potential efficiency to be gained by not // reopening our sockets all the time, but I'm not // convinced it's worth it, especially since we require // nscd to operate, anyhow. close(tac_fd); } return status; }
int main(int argc, char **argv) { char *pass = NULL; char *tty = NULL; char *command = NULL; char *remote_addr = NULL; char *service = NULL; char *protocol = NULL; 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; #ifndef USE_SYSTEM pid_t pid; #endif struct areply arep; /* options */ flag log_wtmp = 1; flag do_author = 0; flag do_authen = 0; flag do_account = 0; flag login_mode = 0; /* check argc */ if (argc < 2) { showusage(argv[0]); exit(EXIT_ERR); } /* check for login mode */ if (argc == 2 && isalpha(*argv[1])) { g_user = argv[1]; do_author = do_authen = do_account = 1; command = DEFAULT_COMMAND; login_mode = 1; } else { int c; int opt_index; while ((c = getopt_long(argc, argv, opt_string, long_options, &opt_index)) != EOF) { switch (c) { case 'T': do_authen = 1; break; case 'R': do_author = 1; break; case 'A': do_account = 1; break; case 'V': showversion(argv[0]); /*NOTREACHED*/ break; case 'h': showusage(argv[0]); /*NOTREACHED*/ break; case 'u': g_user = optarg; break; case 'r': remote_addr = optarg; break; case 'L': // tac_login is a global variable initialized in libtac xstrcpy(tac_login, optarg, sizeof(tac_login)); break; case 'p': pass = optarg; break; case 's': tac_server_name = optarg; break; case 'k': tac_secret = optarg; break; case 'c': command = optarg; break; case 'S': service = optarg; break; case 'P': protocol = optarg; break; case 'q': quiet = 1; break; case 'w': log_wtmp = 0; break; case 'n': tac_encryption = 0; break; case 'y': tty = optarg; break; } } } /* check available information and set to defaults if needed */ if (do_authen + do_author + do_account == 0) { printf("error: one of -TRAVh options is required\n"); exit(EXIT_ERR); } if (g_user == NULL) { printf("error: username is required.\n"); exit(EXIT_ERR); } if (remote_addr == NULL) { printf("error: remote address is required.\n"); exit(EXIT_ERR); } if (service == NULL) { printf("error: service is required.\n"); exit(EXIT_ERR); } if (protocol == NULL) { printf("error: protocol is required.\n"); exit(EXIT_ERR); } if (tac_server_name == NULL) { printf("error: server name is required.\n"); exit(EXIT_ERR); } struct addrinfo hints; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; ret = getaddrinfo(tac_server_name, "tacacs", &hints, &tac_server); if (ret != 0) { printf("error: resolving name %s: %s", tac_server_name, gai_strerror(ret)); exit(EXIT_ERR); } if (tac_secret == NULL) { printf("error: server secret is required.\n"); exit(EXIT_ERR); } if (pass == NULL) { signal(SIGALRM, timeout_handler); alarm(GETPASS_TIMEOUT); pass = getpass(PASSWORD_PROMPT); alarm(0); signal(SIGALRM, SIG_DFL); if (!strlen(pass)) exit(EXIT_ERR); } if (tty == NULL) { printf("error: tty name is required.\n"); exit(EXIT_ERR); } /* open syslog before any TACACS+ calls */ openlog("tacc", LOG_CONS | LOG_PID, LOG_AUTHPRIV); if (do_authen) authenticate(tac_server, tac_secret, g_user, pass, tty, remote_addr); 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, g_user, tty, remote_addr, attr); tac_author_read(tac_fd, &arep); if (arep.status != AUTHOR_STATUS_PASS_ADD && arep.status != AUTHOR_STATUS_PASS_REPL) { if (!quiet) printf("Authorization FAILED: %s\n", arep.msg); exit(EXIT_FAIL); } else { if (!quiet) printf("Authorization OK: %s\n", arep.msg); } 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; sprintf(buf, "%lu", time(0)); tac_add_attrib(&attr, "start_time", buf); // this is not crypto but merely an identifier long rnd_id = random(); memcpy(&task_id, &rnd_id, sizeof(task_id)); sprintf(buf, "%hu", task_id); tac_add_attrib(&attr, "task_id", buf); 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, g_user, tty, remote_addr, attr); ret = tac_acct_read(tac_fd, &arep); if (ret == 0) { if (!quiet) printf("Accounting: START failed: %s\n", arep.msg); syslog(LOG_INFO, "TACACS+ accounting start failed: %s", arep.msg); } else if (!login_mode && !quiet) printf("Accounting: START OK\n"); close(tac_fd); tac_free_attrib(&attr); } /* log in local utmp */ if (log_wtmp) { #if defined(HAVE_PUTUTXLINE) struct timeval tv; gettimeofday(&tv, NULL); memset(&utmpx, 0, sizeof(utmpx)); utmpx.ut_type = USER_PROCESS; utmpx.ut_pid = getpid(); xstrcpy(utmpx.ut_line, tty, sizeof(utmpx.ut_line)); strncpy(utmpx.ut_id, tty + C_STRLEN("tty"), sizeof(utmpx.ut_id)); xstrcpy(utmpx.ut_host, "dialup", sizeof(utmpx.ut_host)); utmpx.ut_tv.tv_sec = tv.tv_sec; utmpx.ut_tv.tv_usec = tv.tv_usec; xstrcpy(utmpx.ut_user, g_user, sizeof(utmpx.ut_user)); /* ut_addr unused ... */ setutxent(); pututxline(&utmpx); #elif defined(HAVE_LOGWTMP) logwtmp(tty, g_user, "dialup"); #endif } if (command != NULL) { int ret; syslog(LOG_DEBUG, "starting %s for %s", command, g_user); signal(SIGHUP, SIG_IGN); signal(SIGTERM, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGCHLD, SIG_IGN); #ifdef COMMAND_MESSAGE printf(COMMAND_MESSAGE); #endif #if USE_SYSTEM ret = system(command); if (ret < 0) syslog(LOG_WARNING, "command failed: %m"); else syslog(LOG_NOTICE, "command exit code %u", ret); #else pid=fork(); if(pid == 0) { /* child */ execl(DEFAULT_COMMAND, DEFAULT_COMMAND, ARGS, NULL); syslog(LOG_ERR, "execl() failed: %m"); _exit(EXIT_FAIL); } if(pid < 0) { /* error */ syslog(LOG_ERR, "fork failed: %m"); exit(EXIT_FAIL); } if(pid > 0) { /* parent */ int st, r; r=wait(&st); } #endif } if (do_account) { /* stop accounting */ struct tac_attrib *attr = NULL; sprintf(buf, "%lu", time(0)); tac_add_attrib(&attr, "stop_time", buf); 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, g_user, tty, remote_addr, attr); ret = tac_acct_read(tac_fd, &arep); if (ret == 0) { if (!quiet) printf("Accounting: STOP failed: %s", arep.msg); syslog(LOG_INFO, "TACACS+ accounting stop failed: %s\n", arep.msg); } else if (!login_mode && !quiet) printf("Accounting: STOP OK\n"); close(tac_fd); tac_free_attrib(&attr); } /* logout from utmp */ if (log_wtmp) { #if defined(HAVE_PUTUTXLINE) utmpx.ut_type = DEAD_PROCESS; memset(utmpx.ut_line, 0, sizeof(utmpx.ut_line)); memset(utmpx.ut_user, 0, sizeof(utmpx.ut_user)); memset(utmpx.ut_host, 0, sizeof(utmpx.ut_host)); utmpx.ut_tv.tv_sec = utmpx.ut_tv.tv_usec = 0; setutxent(); pututxline(&utmpx); #elif defined(HAVE_LOGWTMP) logwtmp(tty, "", ""); #endif } exit(EXIT_OK); }