passdb-pam.c revision c483a995bd24a3d37146d180b9f85213d103b6c7
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose Based on auth_pam.c from popa3d by Solar Designer <solar@openwall.com>.
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose You're allowed to do whatever you like with this software (including
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose re-distribution in source and/or binary form, with or without
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose modification), provided that credit is given where it is due and any
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose modified versions are marked as such. There's absolutely no warranty.
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose#if !defined(_SECURITY_PAM_APPL_H) && !defined(LINUX_PAM) && !defined(_OPENPAM)
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose/* Sun's PAM doesn't use const. we use a bit dirty hack to check it.
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose Originally it was just __sun__ check, but HP/UX also uses Sun's PAM
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose so I thought this might work better. */
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose/* Linux-PAM prior to 0.74 */
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose const char *user;
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose const char *pass;
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bosestatic int pam_userpass_conv(int num_msg, linux_const struct pam_message **msg,
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose /* @UNSAFE */
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose struct pam_userpass *userpass = (struct pam_userpass *) appdata_ptr;
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose const char *input;
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose if (num_msg != 1 || msg[0]->msg_style != PAM_BINARY_PROMPT)
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose strncmp(input, USERPASS_AGENT_ID "/", USERPASS_AGENT_ID_LENGTH + 1))
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose if ((flags & USERPASS_USER_MASK) == USERPASS_USER_FIXED &&
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose if (!(*resp = malloc(sizeof(struct pam_response))))
cd5033e86bb4065d75188e2b6ef287a4421344c8Sumit Bose PAM_BP_RENEW(&prompt, PAM_BPC_DONE, userlen + 1 + passlen);
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose memcpy(output + userlen + 1, userpass->pass, passlen);
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose if (!(*resp = malloc(num_msg * sizeof(struct pam_response))))
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose for (i = 0; i < num_msg; i++) {
2ef62c64e7f07c8aced3f72850008ecb72860162Sumit Bose while (--i >= 0) {
fe2ab0d67fe8c66fb6352e9d8f845bb46d1848cbSumit Bose struct passdb_module *_module = request->passdb->passdb;
fe2ab0d67fe8c66fb6352e9d8f845bb46d1848cbSumit Bose struct pam_passdb_module *module = (struct pam_passdb_module *)_module;
fe2ab0d67fe8c66fb6352e9d8f845bb46d1848cbSumit Bose if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
fe2ab0d67fe8c66fb6352e9d8f845bb46d1848cbSumit Bose *error = t_strdup_printf("pam_authenticate() failed: %s",
fe2ab0d67fe8c66fb6352e9d8f845bb46d1848cbSumit Bose if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) !=
fe2ab0d67fe8c66fb6352e9d8f845bb46d1848cbSumit Bose *error = t_strdup_printf("pam_setcred() failed: %s",
fe2ab0d67fe8c66fb6352e9d8f845bb46d1848cbSumit Bose if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
fe2ab0d67fe8c66fb6352e9d8f845bb46d1848cbSumit Bose *error = t_strdup_printf("pam_acct_mgmt() failed: %s",
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose if ((status = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose "pam_open_session() failed: %s",
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose if ((status = pam_close_session(pamh, 0)) != PAM_SUCCESS) {
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose "pam_close_session() failed: %s",
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose /* FIXME: this doesn't actually work since we're in the child
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose process.. */
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose status = pam_get_item(pamh, PAM_USER, (linux_const void **)&item);
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose *error = t_strdup_printf("pam_get_item() failed: %s",
87f8bee53ee1b4ca87b602ff8536bc5fd5b5b595Lukas Slebodnik auth_request_set_field(request, "user", item, NULL);
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bosepam_verify_plain_child(struct auth_request *request, const char *service,
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose const char *str;
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose status = pam_start(service, request->user, &conv, &pamh);
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose const char *host = net_ip2addr(&request->remote_ip);
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose /* Set some PAM items. They shouldn't fail, and we don't really
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose care if they do. */
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose /* TTY is needed by eg. pam_access module */
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose if ((status2 = pam_end(pamh, status)) == PAM_SUCCESS) {
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose /* blocking=yes code path in auth worker */
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose buf = buffer_create_dynamic(pool_datastack_create(), 512);
04d138472cc086fb7961f0d378852b09961b1a33Lukas Slebodnik buffer_append(buf, &result, sizeof(result));
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose /* Don't send larger writes than what would block. truncated error
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose message isn't that bad.. */
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose if ((ret = write(fd, buf->data, size)) != (int)size) {
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bosestatic void pam_child_input(struct pam_auth_request *request)
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose struct auth_request *auth_request = request->request;
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose /* POSIX guarantees that writing PIPE_BUF bytes or less to pipes is
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose atomic. We rely on that. */
2fe140d3a41e1ac66400069d35adc9379348c1e5Sumit Bose "read() from child process failed: %m");
9da27cbc7532f775afc411d809735760dd5294a7Sumit Bose } else if (ret == 0) {
9da27cbc7532f775afc411d809735760dd5294a7Sumit Bose /* it died */
9da27cbc7532f775afc411d809735760dd5294a7Sumit Bose "Child process died");
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose "Child process returned only %d bytes", (int)ret);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose /* error message included */
9da27cbc7532f775afc411d809735760dd5294a7Sumit Bose "close(child input) failed: %m");
9da27cbc7532f775afc411d809735760dd5294a7Sumit Bosestatic void sigchld_handler(int signo __attr_unused__,
9da27cbc7532f775afc411d809735760dd5294a7Sumit Bose /* FIXME: if we ever do some other kind of forking, this needs fixing */
9da27cbc7532f775afc411d809735760dd5294a7Sumit Bose while ((pid = waitpid(-1, &status, WNOHANG)) != 0) {
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose request = hash_lookup(pam_requests, POINTER_CAST(pid));
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose i_error("PAM: Unknown child %s exited with status %d",
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose i_error("PAM: Child %s exited unexpectedly with "
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose hash_remove(pam_requests, POINTER_CAST(request->pid));
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bosepam_verify_plain(struct auth_request *request, const char *password,
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose struct passdb_module *_module = request->passdb->passdb;
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose struct pam_passdb_module *module = (struct pam_passdb_module *)_module;
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose /* blocking=yes code path in auth worker */
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose result = pam_verify_plain_child(request, service, password, -1);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose auth_request_log_error(request, "pam", "pipe() failed: %m");
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose auth_request_log_error(request, "pam", "fork() failed: %m");
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose callback(PASSDB_RESULT_INTERNAL_FAILURE, request);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose pam_verify_plain_child(request, service, password, fd[1]);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose "close(fd[1]) failed: %m");
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose pam_auth_request = i_new(struct pam_auth_request, 1);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose io_add(fd[0], IO_READ, pam_child_input, pam_auth_request);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose hash_insert(pam_requests, POINTER_CAST(pid), pam_auth_request);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bosestatic struct passdb_module *
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bosepam_preinit(struct auth_passdb *auth_passdb, const char *args)
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose const char *const *t_args;
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose module = p_new(auth_passdb->auth->pool, struct pam_passdb_module, 1);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose /* -session for backwards compatibility */
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose else if (strncmp(t_args[i], "cache_key=", 10) == 0) {
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose } else if (strcmp(t_args[i], "blocking=yes") == 0) {
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose i_fatal("Unexpected PAM parameter: %s", t_args[i]);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, NULL);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bosestatic void pam_child_timeout(void *context __attr_unused__)
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose time_t timeout = ioloop_time - PAM_CHILD_TIMEOUT;
b52b26176c92f3b06dba5598428c70c0cde13fd1Sumit Bose "PAM child process %s timed out, killing it",
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bosestatic void pam_init(struct passdb_module *_module __attr_unused__,
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose i_fatal("Can't support more than one PAM passdb");
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose /* we're caching the password by using directly the plaintext password
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose given by the auth mechanism */
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose pam_requests = hash_create(default_pool, default_pool, 0,
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bose lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, NULL);
ca49ae1eee321751681e99f3ebe2547211db3bf6Sumit Bosestatic void pam_deinit(struct passdb_module *_module __attr_unused__)