mirror of
https://git.savannah.gnu.org/git/inetutils.git
synced 2026-01-12 00:19:39 +08:00
337 lines
8.8 KiB
C
337 lines
8.8 KiB
C
/*
|
|
Copyright (C) 2000-2025 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Inetutils.
|
|
|
|
GNU Inetutils is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or (at
|
|
your option) any later version.
|
|
|
|
GNU Inetutils is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see `http://www.gnu.org/licenses/'. */
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <syslog.h>
|
|
#include <attribute.h>
|
|
#include "extern.h"
|
|
|
|
#ifdef HAVE_SECURITY_PAM_APPL_H
|
|
# include <security/pam_appl.h>
|
|
#endif
|
|
|
|
/*
|
|
* Mechanisms and prerequisites.
|
|
*
|
|
* The original code was tailored to rely on the side-effects
|
|
* of the Linux-PAM module `pam_ftp.so', with its peculiarities:
|
|
*
|
|
* 1. If PAM_USER is `ftp' or `anonymous', fetch PAM_AUTHTOK
|
|
* and split it at `@' into PAM_RUSER and PAM_RHOST.
|
|
* Return with success and set PAM_USER to `ftp'.
|
|
*
|
|
* 2. For other values of PAM_USER, keep the gotten PAM_AUTHTOK
|
|
* unchanged, and fail.
|
|
*
|
|
* This module `pam_ftp.so' does not exist in OpenPAM, nor in
|
|
* Solaris-PAM. Thus portability requires some care.
|
|
*/
|
|
|
|
/* June 3rd, 2012:
|
|
* The draft of A.G Morgan on behalf of the the Open-PAM
|
|
* working group has clearly not been able to get the
|
|
* additions PAM_INCOMPLETE and PAM_CONV_AGAIN accepted
|
|
* sufficiently well in order that the present code should
|
|
* force them upon BSD and Solaris. They are thus protected
|
|
* by preprocessor conditionals for the time being.
|
|
*/
|
|
|
|
#ifdef WITH_PAM
|
|
|
|
/* 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);
|
|
|
|
static struct pam_conv PAM_conversation = { &PAM_conv, NULL };
|
|
|
|
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_BUF_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 == NULL)
|
|
{
|
|
savemsg = 1;
|
|
# ifdef PAM_CONV_AGAIN
|
|
retval = PAM_CONV_AGAIN; /* Linux-PAM */
|
|
# elif !defined WITH_LINUX_PAM
|
|
/*
|
|
* Simulate PAM_CONV_AGAIN.
|
|
* The alternate value PAM_TRY_AGAIN is not
|
|
* an expected return value here, so it will
|
|
* leave an audit trail. */
|
|
retval = PAM_CRED_INSUFFICIENT;
|
|
# else/* !PAM_CONV_AGAIN && !WITH_LINUX_PAM */
|
|
retval = PAM_CONV_ERR;
|
|
# endif
|
|
}
|
|
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 formatted 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. */
|
|
/* XXX: Attempted solution; collect all messages, appended
|
|
* one after the other, separated by "\n". Then print all
|
|
* of them in one single run. This will circumvent the hard
|
|
* coded protocol convention of not allowing continuation
|
|
* massage to carry a deviating reply code relative to the
|
|
* final message.
|
|
*/
|
|
if (pcred->message) /* XXX: make sure we split newlines correctly */
|
|
{
|
|
size_t len = strlen (pcred->message);
|
|
char *s = realloc (pcred->message, len
|
|
+ strlen (msg[count]->msg) + 2);
|
|
if (s == NULL)
|
|
{
|
|
free (pcred->message);
|
|
pcred->message = NULL;
|
|
}
|
|
else
|
|
{
|
|
pcred->message = s;
|
|
strcat (pcred->message, "\n");
|
|
strcat (pcred->message, msg[count]->msg);
|
|
}
|
|
}
|
|
else
|
|
pcred->message = sgetsave (msg[count]->msg);
|
|
|
|
if (pcred->message == NULL)
|
|
retval = PAM_CONV_ERR;
|
|
else
|
|
{
|
|
char *sp;
|
|
/* Remove trailing space only. */
|
|
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 return status means failure. */
|
|
static int
|
|
pam_doit (struct credentials *pcred)
|
|
{
|
|
char *username;
|
|
int error;
|
|
|
|
error = pam_authenticate (pamh, 0);
|
|
|
|
/* Probably being called with empty passwd. */
|
|
if (0
|
|
# ifdef PAM_CONV_AGAIN
|
|
|| error == PAM_CONV_AGAIN
|
|
# endif
|
|
# ifdef PAM_INCOMPLETE
|
|
|| error == PAM_INCOMPLETE
|
|
# endif
|
|
# ifndef WITH_LINUX_PAM
|
|
/*
|
|
* In absence of `pam_ftp.so', catch the simulated,
|
|
* incomplete state reported by our PAM_conv().
|
|
*/
|
|
|| (error == PAM_CRED_INSUFFICIENT && pcred->pass == NULL)
|
|
# endif/* !WITH_LINUX_PAM */
|
|
)
|
|
{
|
|
/* 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 PAM_SUCCESS;
|
|
}
|
|
|
|
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_open_session (pamh, 0);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
if (error != PAM_SUCCESS)
|
|
{
|
|
pam_end (pamh, error);
|
|
pamh = NULL;
|
|
}
|
|
|
|
return (error != PAM_SUCCESS);
|
|
}
|
|
|
|
# define TTY_FORMAT "/dev/ftp%d"
|
|
|
|
/* Non-zero return means failure. */
|
|
int
|
|
pam_user (const char *username, struct credentials *pcred)
|
|
{
|
|
int error;
|
|
char tty_name[strlen (TTY_FORMAT) + strlen ("9999999")];
|
|
|
|
if (pamh != NULL)
|
|
{
|
|
pam_end (pamh, PAM_ABORT);
|
|
pamh = NULL;
|
|
}
|
|
|
|
free (pcred->name);
|
|
pcred->name = strdup (username);
|
|
free (pcred->message);
|
|
pcred->message = NULL;
|
|
|
|
(void) snprintf (tty_name, sizeof (tty_name), TTY_FORMAT, (int) getpid ());
|
|
|
|
/* Arrange our creditive. */
|
|
PAM_conversation.appdata_ptr = (void *) pcred;
|
|
|
|
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)
|
|
error = pam_set_item (pamh, PAM_TTY, tty_name);
|
|
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 = (char *) passwd;
|
|
error = pam_doit (pcred);
|
|
pcred->pass = NULL;
|
|
}
|
|
return error != PAM_SUCCESS;
|
|
}
|
|
|
|
void
|
|
pam_end_login (struct credentials *pcred MAYBE_UNUSED)
|
|
{
|
|
int error;
|
|
|
|
if (pamh)
|
|
{
|
|
error = pam_close_session (pamh, PAM_SILENT);
|
|
if (logging && error != PAM_SUCCESS)
|
|
syslog (LOG_ERR, "pam_session: %s", pam_strerror (pamh, error));
|
|
|
|
error = pam_setcred (pamh, PAM_SILENT | PAM_DELETE_CRED);
|
|
if (logging && error != PAM_SUCCESS)
|
|
syslog (LOG_ERR, "pam_setcred: %s", pam_strerror (pamh, error));
|
|
|
|
error = pam_end (pamh, error);
|
|
if (logging && error != PAM_SUCCESS)
|
|
syslog (LOG_ERR, "pam_end: %s", pam_strerror (pamh, error));
|
|
|
|
pamh = NULL;
|
|
}
|
|
}
|
|
#endif /* WITH_PAM */
|