pam-modules/pam_umotd/pam_umotd.c
Sergey Poznyakoff b6c720096e Use wordsplit for keyword expansion.
* .gitmodules: Add wordsplit
* configure.ac: Likewise.
* lib/Makefile.am: Likewise.
* lib/graypam.h (gray_expand_argv): Remove.
(gray_expand_string): Change prototype.
* lib/vartab.c (gray_expand_argv): Remove.
(gray_expand_string): Rewrite using wordsplit.
* pam_ldaphome/pam_ldaphome.c (import_public_key): Assume
sshPublicKey as a default attribute.
* pam_log/pam_log.c (_pam_parse): Take two return arguments.
(echo): Use gray_expand_string.
* pam_sql/pam_mysql.c: Update gray_expand_string usage.
* pam_sql/pam_pgsql.c: Likewise.
* pam_sql/pam_sql.c: Likewise.
* pam_sql/pam_sql.h (gpam_sql_get_query): Change signature.
* pam_umotd/pam_umotd.c (pam_sm_open_session): Update
gray_expand_string usage.
2020-03-29 17:39:54 +03:00

408 lines
8.4 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This file is part of pam-modules.
Copyright (C) 2012-2020 Sergey Poznyakoff
This program 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.
This program 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/>. */
#ifdef HAVE__PAM_ACONF_H
# include <security/_pam_aconf.h>
#endif
#ifndef LINUX_PAM
# include <security/pam_appl.h>
#endif /* LINUX_PAM */
#include <security/pam_modules.h>
#include <security/pam_ext.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include "graypam.h"
#define PAM_SM_SESSION
static long debug_level;
static int cntl_flags;
static char *motd_file_name;
static int optindex = -1;
static long timeout_option = 10;
static const char *logfile_name;
static long max_output_size = 2000;
static char *la_str;
struct pam_opt pam_opt[] = {
{ PAM_OPTSTR(debug), pam_opt_long, &debug_level },
{ PAM_OPTSTR(debug), pam_opt_const, &debug_level, { 1 } },
{ PAM_OPTSTR(audit), pam_opt_const, &debug_level, { 100 } },
{ PAM_OPTSTR(waitdebug), pam_opt_null, NULL, { 0 },
gray_wait_debug_fun },
{ PAM_OPTSTR(file), pam_opt_string, &motd_file_name },
{ PAM_OPTSTR(log), pam_opt_string, &logfile_name },
{ PAM_OPTSTR(exec), pam_opt_rest, &optindex },
{ PAM_OPTSTR(timeout), pam_opt_long, &timeout_option },
{ PAM_OPTSTR(max-size),pam_opt_long, &max_output_size },
{ PAM_OPTSTR(max-la), pam_opt_string, &la_str },
{ NULL }
};
static int
fpread(const char *str, double *ret)
{
char *p;
*ret = strtod(str, &p);
if (*p) {
_pam_log(LOG_ERR,
"not a valid floating point number: %s",
str);
return 1;
}
return 0;
}
/* FIXME: Linux-specific */
static int
get_la(double *ret)
{
char buf[80];
int rc = -1;
FILE *fp = fopen("/proc/loadavg", "r");
if (!fp)
return -1;
if (!fgets(buf, sizeof(buf), fp))
_pam_log(LOG_ERR, "cannot read /proc/loadavg: %s",
strerror(errno));
else {
char *p = strchr(buf, ' ');
if (*p) {
*p = 0;
rc = fpread(buf, ret);
} else
rc = -1;
}
fclose(fp);
return rc;
}
static int
read_fd(pam_handle_t *pamh, const char *file, int fd)
{
char buf[1024], *p;
ssize_t rd = 0;
size_t total = 0;
size_t level = 0;
while (total < max_output_size) {
size_t rdsize = sizeof(buf) - level - 1;
if (total + rdsize >= max_output_size &&
(rdsize = max_output_size - total) == 0)
break;
rd = read(fd, buf + level, rdsize);
if (rd <= 0)
break;
total += rd;
level += rd;
buf[level] = 0;
p = strrchr(buf, '\n');
if (p)
*p++ = 0;
pam_info(pamh, "%s", buf);
if (p && *p) {
level = strlen(p);
memmove(buf, p, level);
} else
level = 0;
}
if (level) {
buf[level] = 0;
pam_info(pamh, "%s", buf);
}
if (rd < 0) {
_pam_log(LOG_ERR, "error reading file %s: %s",
file, strerror(errno));
return PAM_SYSTEM_ERR;
}
return PAM_SUCCESS;
}
static int
read_file(pam_handle_t *pamh, const char *file)
{
int fd;
int retval;
fd = open(file, O_RDONLY);
if (fd == -1) {
_pam_log(LOG_INFO, "cannot open file %s: %s",
file, strerror(errno));
return PAM_SYSTEM_ERR;
}
retval = read_fd(pamh, file, fd);
close(fd);
return retval;
}
static int
exec_file(pam_handle_t *pamh, char **argv, const char *logfile)
{
pid_t pid, rc;
int p[2];
char buf[1024];
long ttl;
time_t start;
int i, status, intr;
fd_set rd;
struct timeval tv;
size_t total = 0;
if (pipe(p)) {
_pam_log(LOG_ERR, "pipe: %s", strerror(errno));
return PAM_SYSTEM_ERR;
}
pid = fork();
if (pid == -1) {
close(p[0]);
close(p[1]);
_pam_log(LOG_ERR, "fork: %s", strerror(errno));
return PAM_SYSTEM_ERR;
}
if (pid == 0) {
/* child */
if (dup2(p[1], 1) == -1) {
_pam_log(LOG_ERR, "dup2: %s", strerror(errno));
_exit(127);
}
for (i = sysconf(_SC_OPEN_MAX); i >= 0; i--) {
if (i != 1)
close(i);
}
open("/dev/null", O_RDONLY);
if (logfile) {
if (open(logfile, O_CREAT|O_APPEND|O_WRONLY,
0644) == -1) {
_pam_log(LOG_ERR, "open(%s): %s",
logfile, strerror(errno));
_exit(127);
}
} else
dup2(1, 2);
execv(argv[0], argv);
_exit(127);
}
/* master */
close(p[1]);
start = time(NULL);
intr = 0;
rc = 0;
status = 0;
for (i = 0; total < max_output_size;) {
FD_ZERO(&rd);
FD_SET(p[0], &rd);
if (intr) {
rc = waitpid(pid, &status, WNOHANG);
if (rc == pid)
break;
if (rc == (pid_t)-1) {
_pam_log(LOG_ERR, "waitpid: %s",
strerror(errno));
break;
}
intr = 0;
}
ttl = timeout_option - (time(NULL) - start);
if (ttl <= 0) {
_pam_log(LOG_ERR, "timed out reading from %s",
argv[0]);
break;
}
tv.tv_sec = ttl;
tv.tv_usec = 0;
rc = select(p[0] + 1, &rd, NULL, NULL, &tv);
if (rc < 0) {
if (errno == EINTR || errno == EAGAIN) {
intr = 1;
continue;
}
_pam_log(LOG_ERR, "select: %s", strerror(errno));
}
if (i == sizeof(buf) - 1) {
char *p;
buf[i] = 0;
p = strrchr(buf, '\n');
if (p)
*p++ = 0;
pam_info(pamh, "%s", buf);
if (p && *p) {
i = strlen(p);
memmove(buf, p, i);
}
}
if (FD_ISSET(p[0], &rd)) {
char c;
rc = read(p[0], &c, 1);
if (rc == 1) {
buf[i++] = c;
total++;
} else if (rc == 0
|| errno == EINTR || errno == EAGAIN) {
intr = 1;
continue;
} else {
_pam_log(LOG_ERR, "read: %s", strerror(errno));
break;
}
}
}
if (i) {
buf[i] = 0;
pam_info(pamh, "%s", buf);
}
close(p[0]);
if (rc != pid) {
_pam_log(LOG_NOTICE, "killing %s (pid %lu)",
argv[0], (unsigned long) pid);
kill(pid, SIGKILL);
while ((rc = waitpid(pid, &status, 0)) == -1 &&
errno == EINTR);
if (rc == (pid_t)-1) {
_pam_log(LOG_ERR, "waitpid: %s", strerror(errno));
return PAM_SYSTEM_ERR;
}
} else if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
if (status) {
_pam_log(LOG_ERR, "%s exited with status %d",
argv[0], status);
return PAM_SYSTEM_ERR;
}
} else if (WIFSIGNALED(status)) {
status = WTERMSIG(status);
_pam_log(LOG_ERR, "%s got signal %d", argv[0], status);
return PAM_SYSTEM_ERR;
} else if (status) {
_pam_log(LOG_ERR, "%s failed: unknown status 0x%x",
argv[0], status);
return PAM_SYSTEM_ERR;
}
return PAM_SUCCESS;
}
static void
argcv_free(int wc, char **wv)
{
int i;
for (i = 0; i < wc; i++)
free(wv[i]);
free(wv);
}
PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
int retval = PAM_IGNORE;
cntl_flags = 0;
debug_level = 0;
gray_log_init(0, MODULE_NAME, LOG_AUTHPRIV);
gray_parseopt(pam_opt, argc, argv);
if (la_str) {
double max_la, la;
if (fpread(la_str, &max_la))
return PAM_SERVICE_ERR;
if (get_la(&la) == 0 && la >= max_la) {
_pam_log(LOG_ERR,
"load average too high: %.2g >= %.2g",
la, max_la);
return PAM_IGNORE;
}
}
if (motd_file_name) {
char *file;
retval = gray_expand_string(pamh, motd_file_name, &file);
if (retval == PAM_SUCCESS) {
retval = read_file(pamh, file);
free(file);
}
} else if (optindex >= 0) {
char **xargv;
int i;
argc -= optindex;
argv += optindex;
if (!argc) {
_pam_log(LOG_INFO, "empty command line");
return retval;
}
xargv = calloc((argc + 1), sizeof(xargv[0]));
if (!xargv)
return errno_to_pam(errno);
for (i = 0; i < argc; i++) {
retval = gray_expand_string(pamh, argv[i], &xargv[i]);
if (retval != PAM_SUCCESS) {
argc = i;
break;
}
}
if (retval == PAM_SUCCESS) {
xargv[i] = NULL;
retval = exec_file(pamh, xargv, logfile_name);
}
argcv_free(argc, xargv);
} else
_pam_log(LOG_ERR,
"invalid usage: either file or exec must be specified");
return retval;
}
PAM_EXTERN int
pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
return PAM_IGNORE;
}
#ifdef PAM_STATIC
/* static module data */
struct pam_module _pam_motd_modstruct = {
"pam_umotd",
NULL,
NULL,
NULL,
pam_sm_open_session,
pam_sm_close_session,
NULL,
};
#endif