/* 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 */
/* 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, *puser; char *pass; char *tty; char *r_addr; int status; priv_level = 0; 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)", __func__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT); /* reset static state in case we are re-entered */ _reset_saved_user(ctrl & PAM_TAC_DEBUG); /* * If a mapped user entry already exists, we are probably being * used for su or sudo, so we need to get the original user password, * rather than the mapped user. * Decided based on auid != uid and then do the lookup, similar to * find_pw_user() in nss_tacplusc */ _pam_get_user(pamh, &puser); user = get_user_to_auth(puser); if (user == NULL) return PAM_USER_UNKNOWN; if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: user [%s] obtained", __func__, user); retval = tacacs_get_password (pamh, flags, ctrl, &pass); if (retval != PAM_SUCCESS || pass == NULL || *pass == '\0') { _pam_log(LOG_ERR, "unable to obtain password"); status = PAM_CRED_INSUFFICIENT; goto err; } retval = pam_set_item (pamh, PAM_AUTHTOK, pass); if (retval != PAM_SUCCESS) { _pam_log(LOG_ERR, "unable to set password"); status = PAM_CRED_INSUFFICIENT; goto err; } if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: password obtained", __func__); _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); status = do_tac_connect(ctrl, NULL, user, pass, tty, r_addr, NULL, NULL, pamh); if (ctrl & PAM_TAC_DEBUG) syslog(LOG_DEBUG, "%s: exit with pam status: %d", __func__, status); err: if (user && user != puser) free(user); /* it was stdrup'ed */ if (NULL != pass) { bzero(pass, strlen (pass)); free(pass); } 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; 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; 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 */