Allow "configure" to explicitly disable OpenSSL and PAM support. Also, allow

OpenSSL and PAM libraries to be optionally linked as regular shared libraries
instead of being searched for and loaded at run-time.


git-svn-id: https://shellinabox.googlecode.com/svn/trunk@65 0da03de8-d603-11dd-86c2-0f8696b7b6f9
This commit is contained in:
zodiac 2009-02-17 04:13:47 +00:00
parent 530182d15e
commit 84dcc33650
9 changed files with 1134 additions and 254 deletions

View file

@ -1,3 +1,12 @@
2009-02-16 Markus Gutschke <markus@shellinabox.com>
* Fixed various bugs that prevents ShellInABox from running on
FreeBSD
* Allow "configure" to select whether OpenSSL and PAM libraries
should be used at all, dynamically searched-for at run-time, or
linked as a regular dynamic library.
2009-02-12 Markus Gutschke <markus@shellinabox.com>
* Released version 2.4

View file

@ -79,19 +79,28 @@ shellinaboxd_LDFLAGS = -static
libtool: $(LIBTOOL_DEPS)
$(SHELL) ./config.status --recheck
shellinaboxd.1: shellinabox/shellinaboxd.man.in
shellinaboxd.1: shellinabox/shellinaboxd.man.in $(top_srcdir)/config.h
@src="${top_srcdir}/shellinabox/shellinaboxd.man.in"; \
echo preprocess "$$src" '>'"$@"; \
if echo " $(DEFS)" | grep HAVE_OPENSSL_BIO_H | \
grep HAVE_OPENSSL_ERR_H | \
grep -q HAVE_OPENSSL_SSL_H; then \
if [ `sed -e 's/^#define \([^ ]*\).*/\1/' -e t -e d \
"${top_srcdir}/config.h" | \
egrep 'HAVE_OPENSSL_BIO_H|HAVE_OPENSSL_ERR_H|HAVE_OPENSSL_SSL_H'|\
wc -l` -eq 3 ]; then \
sed -e '/^#ifdef *HAVE_OPENSSL$$/d' \
-e '/^#endif$$/d' "$$src" >"$@"; \
else \
sed -e '/^#ifdef *HAVE_OPENSSL$$/,/^#endif$$/d' "$$src" >"$@"; \
fi; \
if sed -e 's/^#define \([^ ]*\).*/\1/' -e t -e d \
"${top_srcdir}/config.h" | \
grep 'HAVE_SECURITY_PAM_APPL_H' >/dev/null 2>&1; then \
sed -e '/^#ifdef *HAVE_PAM$$/d' \
-e '/^#endif$$/d' "$$src" >"$@"; \
else \
sed -e '/^#ifdef *HAVE_PAM$$/,/^#endif$$/d' "$$src" >"$@"; \
fi
@man -Tps "./$@" >`echo "$@" 2>/dev/null|sed -e 's/\.[^.]*$$/.ps/'` \
|| true
@out=`echo "$@" 2>/dev/null|sed -e 's/\.[^.]*$$/.ps/'`; \
man -Tps "./$@" >"$${out}" 2>/dev/null || rm -f "$${out}"
clean-local:
-rm -rf shellinaboxd.1 \

View file

@ -991,19 +991,28 @@ uninstall-man: uninstall-man1
libtool: $(LIBTOOL_DEPS)
$(SHELL) ./config.status --recheck
shellinaboxd.1: shellinabox/shellinaboxd.man.in
shellinaboxd.1: shellinabox/shellinaboxd.man.in $(top_srcdir)/config.h
@src="${top_srcdir}/shellinabox/shellinaboxd.man.in"; \
echo preprocess "$$src" '>'"$@"; \
if echo " $(DEFS)" | grep HAVE_OPENSSL_BIO_H | \
grep HAVE_OPENSSL_ERR_H | \
grep -q HAVE_OPENSSL_SSL_H; then \
if [ `sed -e 's/^#define \([^ ]*\).*/\1/' -e t -e d \
"${top_srcdir}/config.h" | \
egrep 'HAVE_OPENSSL_BIO_H|HAVE_OPENSSL_ERR_H|HAVE_OPENSSL_SSL_H'|\
wc -l` -eq 3 ]; then \
sed -e '/^#ifdef *HAVE_OPENSSL$$/d' \
-e '/^#endif$$/d' "$$src" >"$@"; \
else \
sed -e '/^#ifdef *HAVE_OPENSSL$$/,/^#endif$$/d' "$$src" >"$@"; \
fi; \
if sed -e 's/^#define \([^ ]*\).*/\1/' -e t -e d \
"${top_srcdir}/config.h" | \
grep 'HAVE_SECURITY_PAM_APPL_H' >/dev/null 2>&1; then \
sed -e '/^#ifdef *HAVE_PAM$$/d' \
-e '/^#endif$$/d' "$$src" >"$@"; \
else \
sed -e '/^#ifdef *HAVE_PAM$$/,/^#endif$$/d' "$$src" >"$@"; \
fi
@man -Tps "./$@" >`echo "$@" 2>/dev/null|sed -e 's/\.[^.]*$$/.ps/'` \
|| true
@out=`echo "$@" 2>/dev/null|sed -e 's/\.[^.]*$$/.ps/'`; \
man -Tps "./$@" >"$${out}" 2>/dev/null || rm -f "$${out}"
clean-local:
-rm -rf shellinaboxd.1 \

1139
configure vendored

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,9 @@
AC_PREREQ(2.57)
dnl This is the one location where the authoritative version number is stored
AC_INIT(shellinabox, 2.4, markus@shellinabox.com)
dnl Set up autoconf/automake for building C libraries and binaries with GCC
AM_INIT_AUTOMAKE
AM_CONFIG_HEADER(config.h)
AC_PROG_CC
@ -8,17 +12,17 @@ AC_PROG_LIBTOOL
AC_SUBST(LIBTOOL_DEPS)
AC_C_CONST
AC_PROG_GCC_TRADITIONAL
AC_CHECK_HEADERS([libutil.h openssl/bio.h openssl/err.h openssl/ssl.h \
pthread.h security/pam_appl.h security/pam_client.h \
security/pam_misc.h sys/prctrl.h utmp.h utmpx.h])
AC_CHECK_FUNCS(dlopen, [],
[AC_CHECK_LIB(dl, dlopen,
[LIBS="-ldl $LIBS"
AC_DEFINE(HAVE_DLOPEN)])])
dnl Check for header files that do not exist on all platforms
AC_CHECK_HEADERS([libutil.h pthread.h sys/prctrl.h utmp.h utmpx.h])
dnl Most systems require linking against libutil.so in order to get login_tty()
AC_CHECK_FUNCS(login_tty, [],
[AC_CHECK_LIB(util, login_tty,
[LIBS="-lutil $LIBS"
AC_DEFINE(HAVE_LOGIN_TTY)])])
dnl We prefer ptsname_r(), but will settle for ptsname() if necessary
AC_TRY_LINK([#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE
#endif
@ -29,5 +33,72 @@ AC_TRY_LINK([#ifndef _XOPEN_SOURCE
[ptsname_r(0, 0, 0);],
[AC_DEFINE(HAVE_PTSNAME_R, 1,
Define to 1 if you have a re-entrant version of ptsname)])
dnl We automatically detect SSL support, but allow users to disable it
AC_ARG_ENABLE(ssl,
[ --disable-ssl if available at built-time, support for SSL
connections will be enabled. It can still be
disabled at run-time, either on the daemon's
command line or if the operating system does not
have the OpenSSL libraries available.])
dnl We automatically detect PAM support, but allow users to disable it
AC_ARG_ENABLE(pam,
[ --disable-pam PAM support is necessary in order to authenticate
users for running programs other than their default
login shell.])
dnl We try to always use dlopen() instead of linking libraries dynamically, as
dnl this reduces the hard run-time dependencies that our binary has. But we
dnl allow users to disable this feature.
AC_ARG_ENABLE(runtime-loading,
[ --disable-runtime-loading ShellInABox will try to load the OpenSSL, and PAM
libraries at run-time, if it has been compiled with
support for these libraries, and if the operating
system supports dynamic loading of libraries. This
allows you to install the same binary on different
systems independent of whether they have OpenSSL
and PAM enabled. If you would rather directly link
these libraries into the binary, thus making them a
hard dependency, then disable runtime-loading.])
dnl Only test for OpenSSL headers, if not explicitly disabled
if test "x$enable_ssl" != xno; then
AC_CHECK_HEADERS([openssl/bio.h openssl/err.h openssl/ssl.h])
fi
dnl Only test for PAM headers, if not explicitly disabled
if test "x$enable_pam" != xno; then
AC_CHECK_HEADERS([security/pam_appl.h security/pam_client.h \
security/pam_misc.h ])
fi
dnl Only test for dlopen(), if not explicitly disabled. Add required libdl.so
dnl library if necessary. Also, if dlopen() is not available on this system,
dnl explicitly disable runtime loading.
if test "x$enable_runtime_loading" != xno; then
AC_CHECK_FUNCS(dlopen, [],
[AC_CHECK_LIB(dl, dlopen,
[LIBS="-ldl $LIBS"
AC_DEFINE(HAVE_DLOPEN)],
[enable_runtime_loading=no])])
fi
dnl If runtime loading has been disabled, add OpenSSL and PAM as hard
dnl dependencies.
if test "x$enable_runtime_loading" == xno; then
dnl Link against OpenSSL libraries, unless SSL support has been disabled
if test "x$enable_ssl" != xno; then
AC_CHECK_HEADER(openssl/ssl.h, [LIBS="-lssl $LIBS"])
fi
dnl Link against PAM libraries, unless PAM support has been disabled
if test "x$enable_pam" != xno; then
AC_CHECK_HEADER(security/pam_appl.h, [LIBS="-lpam $LIBS"])
AC_CHECK_HEADER(security/pam_misc.h, [LIBS="-lpam_misc $LIBS"])
fi
fi
dnl Generate output files
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

View file

@ -67,12 +67,15 @@
// Pthread support is optional. Only enable it, if the library has been
// linked into the program
#include <pthread.h>
#if defined(__linux__)
extern int pthread_once(pthread_once_t *, void (*)(void))__attribute__((weak));
#endif
extern int pthread_sigmask(int, const sigset_t *, sigset_t *)
__attribute__((weak));
#endif
#if defined(HAVE_DLOPEN)
// SSL support is optional. Only enable it, if the library can be loaded.
long (*BIO_ctrl)(BIO *, int, long, void *);
BIO_METHOD * (*BIO_f_buffer)(void);
@ -113,7 +116,7 @@ int (*SSL_set_ex_data)(SSL *, int, void *);
int (*SSL_shutdown)(SSL *);
int (*SSL_write)(SSL *, const void *, int);
SSL_METHOD * (*SSLv23_server_method)(void);
#endif
static void sslDestroyCachedContext(void *ssl_, char *context_) {
struct SSLSupport *ssl = (struct SSLSupport *)ssl_;
@ -163,7 +166,7 @@ void deleteSSL(struct SSLSupport *ssl) {
free(ssl);
}
#if defined(HAVE_OPENSSL)
#if defined(HAVE_OPENSSL) && defined(HAVE_DLOPEN)
static void *loadSymbol(const char *lib, const char *fn) {
void *dl = RTLD_DEFAULT;
void *rc = dlsym(dl, fn);
@ -251,6 +254,9 @@ static void loadSSL(void) {
#endif
int serverSupportsSSL(void) {
#if defined(HAVE_OPENSSL) && !defined(HAVE_DLOPEN)
return SSL_library_init();
#else
#if defined(HAVE_OPENSSL)
// We want to call loadSSL() exactly once. For single-threaded applications,
// this is straight-forward. For threaded applications, we need to call
@ -274,6 +280,7 @@ int serverSupportsSSL(void) {
#else
return 0;
#endif
#endif
}
#if defined(HAVE_OPENSSL)

View file

@ -68,6 +68,7 @@ typedef struct SSL_METHOD SSL_METHOD;
#define SSL_ERROR_WANT_WRITE 3
#endif
#if defined(HAVE_DLOPEN)
extern long (*x_BIO_ctrl)(BIO *, int, long, void *);
extern BIO_METHOD *(*x_BIO_f_buffer)(void);
extern void (*x_BIO_free_all)(BIO *);
@ -166,6 +167,7 @@ extern SSL_METHOD *(*x_SSLv23_server_method)(void);
#define SSL_get_app_data(s) (x_SSL_get_ex_data(s, 0))
#define SSL_set_app_data(s, arg) (x_SSL_set_ex_data(s, 0, (char *)arg))
#define SSL_set_mode(ssl, op) (x_SSL_ctrl((ssl), SSL_CTRL_MODE, (op), NULL))
#endif
struct SSLSupport {
int enabled;

View file

@ -50,6 +50,7 @@
#include <dlfcn.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
@ -90,7 +91,7 @@ struct pam_conv;
typedef struct pam_handle pam_handle_t;
#endif
#if defined(HAVE_PTHREAD_H)
#if defined(HAVE_PTHREAD_H) && defined(__linux__)
#include <pthread.h>
extern int pthread_once(pthread_once_t *, void (*)(void))__attribute__((weak));
#endif
@ -103,7 +104,7 @@ extern int pthread_once(pthread_once_t *, void (*)(void))__attribute__((weak));
// If PAM support is available, take advantage of it. Otherwise, silently fall
// back on legacy operations for session management.
#if defined(HAVE_SECURITY_PAM_APPL_H)
#if defined(HAVE_SECURITY_PAM_APPL_H) && defined(HAVE_DLOPEN)
static int (*x_pam_acct_mgmt)(pam_handle_t *, int);
static int (*x_pam_authenticate)(pam_handle_t *, int);
#if defined(HAVE_SECURITY_PAM_CLIENT_H)
@ -118,6 +119,17 @@ static int (*x_pam_start)(const char *, const char *, const struct pam_conv *,
pam_handle_t **);
static int (*x_misc_conv)(int, const struct pam_message **,
struct pam_response **, void *);
#define pam_acct_mgmt x_pam_acct_mgmt
#define pam_authenticate x_pam_authenticate
#define pam_binary_handler_fn x_pam_binary_handler_fn
#define pam_close_session x_pam_close_session
#define pam_end x_pam_end
#define pam_get_item x_pam_get_item
#define pam_open_session x_pam_open_session
#define pam_set_item x_pam_set_item
#define pam_start x_pam_start
#define misc_conv x_misc_conv
#endif
// Older versions of glibc might not support fdopendir(). That's OK, we can
@ -128,7 +140,7 @@ static int launcher = -1;
static uid_t restricted;
#if defined(HAVE_SECURITY_PAM_APPL_H)
#if defined(HAVE_SECURITY_PAM_APPL_H) && defined(HAVE_DLOPEN)
// If the PAM misc library cannot be found, we have to provide our own basic
// conversation function. As we know that this code is only ever called from
@ -221,14 +233,14 @@ static int my_misc_conv(int num_msg, const struct pam_message **msgm,
#if defined(HAVE_SECURITY_PAM_CLIENT_H)
case PAM_BINARY_PROMPT: {
pamc_bp_t binary_prompt = NULL;
if (!msgm[count]->msg || !*x_pam_binary_handler_fn) {
if (!msgm[count]->msg || !*pam_binary_handler_fn) {
goto failed_conversation;
}
PAM_BP_RENEW(p(&binary_prompt), PAM_BP_RCONTROL(msgm[count]->msg),
PAM_BP_LENGTH(msgm[count]->msg));
PAM_BP_FILL(binary_prompt, 0, PAM_BP_LENGTH(msgm[count]->msg),
PAM_BP_RDATA(msgm[count]->msg));
if ((*x_pam_binary_handler_fn)(appdata_ptr, &binary_prompt) !=
if ((*pam_binary_handler_fn)(appdata_ptr, &binary_prompt) !=
PAM_SUCCESS || !binary_prompt) {
goto failed_conversation;
}
@ -268,8 +280,8 @@ static void *loadSymbol(const char *lib, const char *fn) {
}
static void loadPAM(void) {
check(!x_pam_start);
check(!x_misc_conv);
check(!pam_start);
check(!misc_conv);
struct {
union {
void *avoid_gcc_warning_about_type_punning;
@ -278,18 +290,18 @@ static void loadPAM(void) {
const char *lib;
const char *fn;
} symbols[] = {
{ { &x_pam_acct_mgmt }, "libpam.so", "pam_acct_mgmt" },
{ { &x_pam_authenticate }, "libpam.so", "pam_authenticate" },
{ { &pam_acct_mgmt }, "libpam.so", "pam_acct_mgmt" },
{ { &pam_authenticate }, "libpam.so", "pam_authenticate" },
#if defined(HAVE_SECURITY_PAM_CLIENT_H)
{ { &x_pam_binary_handler_fn }, "libpam_misc.so", "pam_binary_handler_fn"},
{ { &pam_binary_handler_fn }, "libpam_misc.so", "pam_binary_handler_fn" },
#endif
{ { &x_pam_close_session }, "libpam.so", "pam_close_session" },
{ { &x_pam_end }, "libpam.so", "pam_end" },
{ { &x_pam_get_item }, "libpam.so", "pam_get_item" },
{ { &x_pam_open_session }, "libpam.so", "pam_open_session" },
{ { &x_pam_set_item }, "libpam.so", "pam_set_item" },
{ { &x_pam_start }, "libpam.so", "pam_start" },
{ { &x_misc_conv }, "libpam_misc.so", "misc_conv" }
{ { &pam_close_session }, "libpam.so", "pam_close_session" },
{ { &pam_end }, "libpam.so", "pam_end" },
{ { &pam_get_item }, "libpam.so", "pam_get_item" },
{ { &pam_open_session }, "libpam.so", "pam_open_session" },
{ { &pam_set_item }, "libpam.so", "pam_set_item" },
{ { &pam_start }, "libpam.so", "pam_start" },
{ { &misc_conv }, "libpam_misc.so", "misc_conv" }
};
for (int i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
if (!(*symbols[i].var = loadSymbol(symbols[i].lib, symbols[i].fn))) {
@ -317,6 +329,9 @@ static void loadPAM(void) {
#endif
int supportsPAM(void) {
#if defined(HAVE_SECURITY_PAM_APPL_H) && !defined(HAVE_DLOPEN)
return 1;
#else
#if defined(HAVE_SECURITY_PAM_APPL_H)
// We want to call loadPAM() exactly once. For single-threaded applications,
@ -337,10 +352,11 @@ int supportsPAM(void) {
loadPAM();
}
}
return x_misc_conv && x_pam_start;
return misc_conv && pam_start;
#else
return 0;
#endif
#endif
}
int launchChild(int service, struct Session *session) {
@ -540,7 +556,7 @@ static int ptsname_r(int fd, char *buf, size_t buflen) {
}
strcpy(buf, p);
return 0;
}
}
#endif
static int forkPty(int *pty, int useLogin, struct Utmp **utmp,
@ -666,48 +682,48 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
const struct passwd *pw;
pam_handle_t *pam = NULL;
#if defined(HAVE_SECURITY_PAM_APPL_H)
struct pam_conv conv = { .conv = x_misc_conv };
struct pam_conv conv = { .conv = misc_conv };
if (service->authUser) {
check(supportsPAM());
check(x_pam_start("shellinabox", NULL, &conv, &pam) == PAM_SUCCESS);
check(pam_start("shellinabox", NULL, &conv, &pam) == PAM_SUCCESS);
// Change the prompt to include the host name
struct utsname uts;
check(!uname(&uts));
const char *origPrompt;
check(x_pam_get_item(pam, PAM_USER_PROMPT, (void *)&origPrompt) ==
check(pam_get_item(pam, PAM_USER_PROMPT, (void *)&origPrompt) ==
PAM_SUCCESS);
char *prompt;
check(prompt = stringPrintf(NULL, "%s %s", uts.nodename,
origPrompt ? origPrompt : "login: "));
check(x_pam_set_item(pam, PAM_USER_PROMPT, prompt) == PAM_SUCCESS);
check(pam_set_item(pam, PAM_USER_PROMPT, prompt) == PAM_SUCCESS);
// Up to three attempts to enter the user id and password
for (int i = 0;;) {
check(x_pam_set_item(pam, PAM_USER, NULL) == PAM_SUCCESS);
check(pam_set_item(pam, PAM_USER, NULL) == PAM_SUCCESS);
int rc;
if ((rc = x_pam_authenticate(pam, PAM_SILENT)) ==
if ((rc = pam_authenticate(pam, PAM_SILENT)) ==
PAM_SUCCESS &&
(geteuid() ||
(rc = x_pam_acct_mgmt(pam, PAM_SILENT)) ==
(rc = pam_acct_mgmt(pam, PAM_SILENT)) ==
PAM_SUCCESS)) {
break;
}
if (++i == 3) {
// Quit if login failed.
puts("\nMaximum number of tries exceeded (3)");
x_pam_end(pam, rc);
pam_end(pam, rc);
_exit(1);
} else {
puts("\nLogin incorrect");
}
}
check(x_pam_set_item(pam, PAM_USER_PROMPT, "login: ") == PAM_SUCCESS);
check(pam_set_item(pam, PAM_USER_PROMPT, "login: ") == PAM_SUCCESS);
free(prompt);
// Retrieve user id, and group id.
const char *name;
check(x_pam_get_item(pam, PAM_USER, (void *)&name) == PAM_SUCCESS);
check(pam_get_item(pam, PAM_USER, (void *)&name) == PAM_SUCCESS);
pw = getPWEnt(getUserId(name));
check(service->uid < 0);
check(service->gid < 0);
@ -723,16 +739,16 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
check(service->user);
check(service->group);
if (supportsPAM()) {
check(x_pam_start("shellinabox", service->user, &conv, &pam) ==
check(pam_start("shellinabox", service->user, &conv, &pam) ==
PAM_SUCCESS);
int rc;
// PAM account management requires root access. Just skip it, if we
// are running with lower privileges.
if (geteuid() &&
(rc = x_pam_acct_mgmt(pam, PAM_SILENT)) !=
(rc = pam_acct_mgmt(pam, PAM_SILENT)) !=
PAM_SUCCESS) {
x_pam_end(pam, rc);
pam_end(pam, rc);
_exit(1);
}
}
@ -747,7 +763,7 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
(service->uid != restricted || service->gid != pw->pw_gid)) {
puts("\nAccess denied!");
#if defined(HAVE_SECURITY_PAM_APPL_H)
x_pam_end(pam, PAM_SUCCESS);
pam_end(pam, PAM_SUCCESS);
#endif
_exit(1);
}
@ -755,7 +771,7 @@ static pam_handle_t *internalLogin(struct Service *service, struct Utmp *utmp,
#if defined(HAVE_SECURITY_PAM_APPL_H)
if (pam) {
#ifdef HAVE_UTMPX_H
check(x_pam_set_item(pam, PAM_TTY, (const void **)utmp->utmpx.ut_line) ==
check(pam_set_item(pam, PAM_TTY, (const void **)utmp->utmpx.ut_line) ==
PAM_SUCCESS);
#endif
}
@ -1080,7 +1096,7 @@ static void childProcess(struct Service *service, int width, int height,
pam_handle_t *pam = internalLogin(service, utmp, &environment);
#if defined(HAVE_SECURITY_PAM_APPL_H)
if (pam && !geteuid()) {
check(x_pam_open_session(pam, PAM_SILENT) == PAM_SUCCESS);
check(pam_open_session(pam, PAM_SILENT) == PAM_SUCCESS);
pid_t pid = fork();
switch (pid) {
case -1:
@ -1091,9 +1107,9 @@ static void childProcess(struct Service *service, int width, int height,
// Finish all pending PAM operations.
int status, rc;
check(NOINTR(waitpid(pid, &status, 0)) == pid);
check((rc = x_pam_close_session(pam, PAM_SILENT)) ==
check((rc = pam_close_session(pam, PAM_SILENT)) ==
PAM_SUCCESS);
check(x_pam_end(pam, rc) == PAM_SUCCESS);
check(pam_end(pam, rc) == PAM_SUCCESS);
_exit(WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status));
}
}

View file

@ -300,13 +300,19 @@ Alternatively, an \fIapplication\fP can be specified by providing a
\fIuser\fP description, a working directory, and a command line:
.in +4
\fIAPPLICATION\fP := 'LOGIN' | \fIUSER\fP ':' \fICWD\fP ':' <cmdline>
.in
#ifdef HAVE_PAM
.in
The keyword 'AUTH' indicates that the \fIuser\fP information should
be requested interactively, instead of being provided as part of the
\fIservice\fP description:
.in +4
\fIUSER\fP := 'AUTH' | <username> ':' <groupname>
#endif
\fIUSER\fP :=
#ifdef HAVE_PAM
'AUTH' |
#endif
<username> ':' <groupname>
.in
The working directory can either be given as an absolute path, or it