#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "extern.h" #ifdef HAVE_SECURITY_PAM_APPL_H #include #endif #ifndef PAM_CONV_AGAIN # define PAM_CONV_AGAIN PAM_TRY_AGAIN #endif #ifndef PAM_INCOMPLETE # define PAM_INCOMPLETE PAM_TRY_AGAIN #endif #ifdef WITH_PAM static int PAM_conv __P ((int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)); /* FIXME: We still have a side effect since we use the global variable cred. A better approach would be to use the pcred parameter in pam_user(). */ static struct pam_conv PAM_conversation = { &PAM_conv, &cred }; /* PAM authentication, now using the PAM's async feature. */ static pam_handle_t *pamh; static int PAM_conv (int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { struct pam_response *repl = NULL; int retval, count = 0, replies = 0; int size = sizeof(struct pam_response); struct credentials *pcred = (struct credentials *) appdata_ptr; #define GET_MEM \ if (!(repl = realloc (repl, size))) \ return PAM_CONV_ERR; \ size += sizeof (struct pam_response) retval = PAM_SUCCESS; for (count = 0; count < num_msg; count++) { int savemsg = 0; switch (msg[count]->msg_style) { case PAM_PROMPT_ECHO_ON: GET_MEM; repl[replies].resp_retcode = PAM_SUCCESS; repl[replies].resp = sgetsave (pcred->name); replies++; break; case PAM_PROMPT_ECHO_OFF: GET_MEM; if (pcred->pass == 0) { savemsg = 1; retval = PAM_CONV_AGAIN; } else { repl[replies].resp_retcode = PAM_SUCCESS; repl[replies].resp = sgetsave (pcred->pass); replies++; } break; case PAM_TEXT_INFO: savemsg = 1; break; case PAM_ERROR_MSG: default: /* Must be an error of some sort... */ savemsg = 1; retval = PAM_CONV_ERR; } if (savemsg) { /* FIXME: This is a serious problem. If the PAM message is multilines, the reply _must_ be formated correctly. The way to do this would be to consider \n as a boundary then in the ftpd.c:user() or ftpd.c:pass() check for it and send a lreply(). But I'm not sure the RFCs allow mutilines replies for a passwd challenge. Many clients will simply break. */ if (pcred->message) /* XXX: make sure we split newlines correctly */ { size_t len = strlen (pcred->message) + strlen (msg[count]->msg) + 1; char *s = realloc (pcred->message, len); if (s == NULL) { free (pcred->message); pcred->message = NULL; } else { pcred->message = s; snprintf(pcred->message + strlen(pcred->message), len - strlen(pcred->message), "%s", msg[count]->msg); } } else pcred->message = sgetsave (msg[count]->msg); if (pcred->message == NULL) retval = PAM_CONV_ERR; else { char *sp; /* FIXME: What's this for ? */ /* Remove trailing `: ' */ sp = pcred->message + strlen (pcred->message); while (sp > pcred->message && strchr (" \t\n:", *--sp)) *sp = '\0'; } } /* In case of error, drop responses and return */ if (retval) { /* FIXME: drop_reply is not standard, need to clean this. */ //_pam_drop_reply (repl, replies); free (repl); return retval; } } if (repl) *resp = repl; return PAM_SUCCESS; } /* Non-zero means failure. */ static int pam_doit (struct credentials *pcred) { char *username; int error; error = pam_authenticate (pamh, 0); /* Probably being call for the passwd. */ if (error == PAM_CONV_AGAIN || error == PAM_INCOMPLETE) { /* Avoid overly terse passwd messages and let the people upstairs do something sane. */ if (pcred->message && !strcasecmp (pcred->message, "password")) { free (pcred->message); pcred->message = NULL; } return 0; } if (error == PAM_SUCCESS) /* Alright, we got it */ { error = pam_acct_mgmt (pamh, 0); if (error == PAM_SUCCESS) error = pam_setcred (pamh, PAM_ESTABLISH_CRED); if (error == PAM_SUCCESS) error = pam_get_item (pamh, PAM_USER, (const void **) &username); if (error == PAM_SUCCESS) { if (sgetcred (username, pcred) != 0) error = PAM_AUTH_ERR; else { if (strcasecmp (username, "ftp") == 0) pcred->guest = 1; } } } pam_end(pamh, error); pamh = 0; return (error != PAM_SUCCESS); } /* Non-zero return means failure. */ int pam_user (const char *username, struct credentials *pcred) { int error; if (pamh != 0) { pam_end (pamh, PAM_ABORT); pamh = 0; } if (pcred->name) free (pcred->name); pcred->name = strdup (username); if (pcred->message) free (pcred->message); pcred->message = NULL; error = pam_start ("ftp", pcred->name, &PAM_conversation, &pamh); if (error == PAM_SUCCESS) error = pam_set_item (pamh, PAM_RHOST, pcred->remotehost); if (error != PAM_SUCCESS) { pam_end (pamh, error); pamh = 0; } if (pamh) error = pam_doit (pcred); return (error != PAM_SUCCESS); } /* Nonzero value return for error. */ int pam_pass (const char *passwd, struct credentials *pcred) { int error = PAM_AUTH_ERR; if (pamh) { pcred->pass = passwd; error = pam_doit (pcred); pcred->pass = NULL; } return error != PAM_SUCCESS; } #endif /* WITH_PAM */