yppasswdd.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <crypt.h>
#include <errno.h>
#include <tiuser.h>
#include <netdir.h>
#include <pwd.h>
#include <shadow.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/yppasswd.h>
#include <netconfig.h>
#include <deflt.h>
/* N2L includes */
#include <ndbm.h>
#include "shim.h"
#include "yptol.h"
/* must match sizes in passwd */
#define STRSIZE 100
#define DEFDIR "/etc/"
#define MYPASSWD "passwd"
#define MYSHADOW "shadow"
#define DEFAULT_YPPASSWDD "/etc/default/yppasswdd"
#define YPPASSWDD_STR "check_restricted_shell_name=1"
/* The guts are in there */
extern changepasswd(SVCXPRT *);
static void boilerplate(struct svc_req *rqstp, SVCXPRT *transp);
static void unlimit(int lim);
bool_t validloginshell(char *sh, char *arg, int);
int validstr(char *str, size_t size);
extern char *getusershell(void);
extern void setusershell(void);
extern void endusershell(void);
int Argc;
char **Argv;
int mflag; /* do a make */
int Mstart;
int single = 0;
int nogecos = 0;
int noshell = 0;
int nopw = 0;
int useadjunct = 0;
int useshadow = 0;
static char *defshell = "/bin/sh";
/* These are the various reasons we might exit. */
enum exitstat {
Esuccess,
EminusDandfiles,
Emissingdir,
Emissingadjunct,
Eaccesspasswd,
Eaccessshadow,
Echdir,
Egetnetconfigent,
Et_open,
Enetdir_rsvdport,
Et_sync,
Et_info,
Esvc_create,
Esvc_reg,
Esvcrun_ret,
ElockFail,
EparseFail
};
static char err_usage[] =
"Usage:\n"
" rpc.yppasswdd [-D directory | passwd [passwd.adjunct]]\n"
" [-nopw] [-nogecos]\n"
" [-noshell] [-m arg1 arg2 ...]\n"
"where\n"
" directory is the directory where the passwd, shadow and/or\n"
" passwd.adjunct files are found (/etc by default)\n"
" It should match the setting of PWDIR in /var/yp/Makefile\n\n"
" Alternatively, the old 4.1.x syntax is supported where\n"
" passwd is the path to the passwd file\n"
" passwd.adjunct is the patch to the passwd.adjunct file\n"
" NOTES:\n"
" 1. The -D option and the passwd/passwd.adjunct arguments are\n"
" mutually exclusive\n"
" 2. The old syntax deprecated and will be removed in a future\n"
" release\n"
" 3. A shadow file found in the same directory as the passwd\n"
" will be assumed to contain the password information\n\n"
" arguments after -m are passed to make(1S) after password changes\n"
" -nopw passwords may not be changed remotely using passwd\n"
" -nogecos full name may not be changed remotely using passwd or chfn\n"
" -noshell shell may not be changed remotely using passwd or chsh\n";
char passwd_file[FILENAME_MAX], shadow_file[FILENAME_MAX];
char lockfile[FILENAME_MAX], adjunct_file[FILENAME_MAX];
int
main(int argc, char **argv)
{
SVCXPRT *transp4, *transp6, *transpl;
struct netconfig *nconf4, *nconf6, *nconfl;
int i, tli4, tli6, stat;
int errorflag;
int dfexcl; /* -D or files, not both flag */
enum exitstat exitstatus = Esuccess;
int connmaxrec = RPC_MAXDATASIZE;
strcpy(passwd_file, DEFDIR MYPASSWD);
strcpy(shadow_file, DEFDIR MYSHADOW);
strcpy(lockfile, DEFDIR ".pwd.lock");
strcpy(adjunct_file, DEFDIR "security/passwd.adjunct");
Argc = argc;
Argv = argv;
for (i = 1, errorflag = 0, dfexcl = 0; i < argc; i++) {
if (argv[i][0] == '-' && argv[i][1] == 'm') {
if (access("/usr/ccs/bin/make", X_OK) < 0)
fprintf(stderr,
"%s: /usr/ccs/bin/make is not available, "
"ignoring -m option",
argv[0]);
else {
mflag++;
Mstart = i;
break;
}
} else if (argv[i][0] == '-' && argv[i][1] == 'D') {
switch (dfexcl) {
case 0:
if (++i < argc) {
strcpy(passwd_file, argv[i]);
strcpy(shadow_file, argv[i]);
strcpy(adjunct_file, argv[i]);
strcpy(lockfile, argv[i]);
if (argv[i][strlen(argv[i]) - 1] == '/') {
strcat(passwd_file, MYPASSWD);
strcat(shadow_file, MYSHADOW);
strcat(lockfile, ".pwd.lock");
strcat(adjunct_file, "security/passwd.adjunct");
} else {
strcat(passwd_file, "/" MYPASSWD);
strcat(shadow_file, "/" MYSHADOW);
strcat(lockfile, "/.pwd.lock");
strcat(adjunct_file,
"/security/passwd.adjunct");
}
dfexcl++;
} else {
fprintf(stderr,
"rpc.yppasswdd: -D option requires a "
"directory argument\n");
errorflag++;
exitstatus = Emissingdir;
}
break;
case 1:
fprintf(stderr,
"rpc.yppasswdd: cannot specify passwd/"
"passwd.adjunct pathnames AND use -D\n");
errorflag++;
dfexcl++;
exitstatus = EminusDandfiles;
break;
default:
break;
}
/* -single: Allow user to change only one of password, */
/* shell, or full name at a time. (WHY?) */
/* else if (strcmp(argv[i], "-single") == 0) */
/* single = 1; */
/* else if (strcmp(argv[i], "-nosingle") == 0) */
/* single = 0; */
} else if (strcmp(argv[i], "-nogecos") == 0)
nogecos = 1;
else if (strcmp(argv[i], "-nopw") == 0)
nopw = 1;
else if (strcmp(argv[i], "-noshell") == 0)
noshell = 1;
else if (argv[i][0] != '-') {
/*
* If we find a shadow file, we warn that we're
* using it in addition to warning that the user
* it using a deprecated syntax.
*/
errorflag++;
switch (dfexcl) {
case 0:
strcpy(passwd_file, argv[i]);
memset(shadow_file, 0, sizeof (shadow_file));
strncpy(shadow_file, argv[i],
strrchr(argv[i], '/') - argv[i] + 1);
strcat(shadow_file, MYSHADOW);
fprintf(stderr,
"rpc.yppasswdd: specifying the password file"
" on the command line is \n"
" obsolete, "
"consider using the -D option instead.\n");
if (access(shadow_file, F_OK) == 0) {
fprintf(stderr,
"rpc.yppasswdd: found a shadow file in "
"the same directory as %s\n"
" It will be used.\n",
passwd_file);
}
if (i + 1 < argc && argv[i+1][0] != '-') {
strcpy(adjunct_file, argv[++i]);
if (access(adjunct_file, F_OK) != 0) {
fprintf(stderr,
"rpc.yppasswdd: adjunct file %s "
"not found\n",
adjunct_file);
exitstatus = Emissingadjunct;
}
}
dfexcl++;
break;
case 1:
fprintf(stderr,
"rpc.yppasswdd: cannot specify passwd/"
"passwd.adjunct pathnames AND use -D\n");
dfexcl++;
exitstatus = EminusDandfiles;
break;
default:
break;
}
} else {
errorflag++;
fprintf(stderr,
"rpc.yppasswdd: unrecognized option %s ignored\n",
argv[i]);
}
}
if (errorflag)
fprintf(stderr, err_usage);
if (exitstatus)
exit(exitstatus);
if (access(passwd_file, W_OK) < 0) {
fprintf(stderr, "rpc.yppasswdd: can't access %s\n",
passwd_file);
exitstatus = Eaccesspasswd;
}
if (access(shadow_file, W_OK) == 0) {
useshadow = 1;
} else {
/* We don't demand a shadow file unless we're looking at /etc */
if (strcmp(DEFDIR MYSHADOW, shadow_file) == 0) {
fprintf(stderr, "rpc.yppasswdd: can't access %s\n",
shadow_file);
exitstatus = Eaccessshadow;
}
}
if (access(adjunct_file, W_OK) == 0) {
/* using an adjunct file */
useadjunct = 1;
}
if (chdir("/var/yp") < 0) {
fprintf(stderr, "rpc.yppasswdd: can't chdir to /var/yp\n");
exitstatus = Echdir;
}
if (exitstatus)
exit(exitstatus);
if (errorflag)
fprintf(stderr, "\nProceeding.\n");
/*
* Initialize locking system.
* This is required for N2L version which accesses the DBM files.
* For the non N2L version this sets up some locking which, since non
* N2L mode does not access the DBM files, will be unused.
*
* This also sets up yptol_mode.
*/
if (!init_lock_system(TRUE)) {
fprintf(stderr,
"rpc.yppasswdd: Cant initialize locking system\n");
exit(ElockFail);
}
#ifndef DEBUG
/* Close everything, but stdin/stdout/stderr */
closefrom(3);
#endif
if (yptol_mode) {
stat = parseConfig(NULL, NTOL_MAP_FILE);
if (stat == 1) {
fprintf(stderr, "yppasswdd : NIS to LDAP mapping"
" inactive.\n");
} else if (stat != 0) {
fprintf(stderr, "yppasswdd : Aborting after NIS to LDAP"
" mapping error.\n");
exit(EparseFail);
}
}
#ifndef DEBUG
/* Wack umask that we inherited from parent */
umask(0);
/* Be a midwife to ourselves */
if (fork())
exit(Esuccess);
/* Disassociation is hard to do, la la la */
setpgrp();
setsid();
/* Ignore stuff */
signal(SIGHUP, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGWINCH, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
/*
* Just in case that wasn't enough, let's fork
* again. (per Stevens).
*/
if (fork())
exit(Esuccess);
/*
* We need stdin, stdout, and stderr later when we
* fork a make(1).
*/
freopen("/dev/null", "r+", stdin);
freopen("/dev/null", "r+", stdout);
freopen("/dev/null", "r+", stderr);
#endif
openlog("yppasswdd", LOG_CONS | LOG_PID, LOG_AUTH);
unlimit(RLIMIT_CPU);
unlimit(RLIMIT_FSIZE);
/*
* Set non-blocking mode and maximum record size for
* connection oriented RPC transports.
*/
if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &connmaxrec)) {
syslog(LOG_INFO, "unable to set maximum RPC record size");
}
nconf4 = getnetconfigent("udp");
nconf6 = getnetconfigent("udp6");
if (nconf4 == 0 && nconf6 == 0) {
syslog(LOG_ERR, "udp/udp6 transport not supported\n");
exit(Egetnetconfigent);
}
tli4 = (nconf4 != 0) ? t_open(nconf4->nc_device, O_RDWR, NULL) : -1;
tli6 = (nconf6 != 0) ? t_open(nconf6->nc_device, O_RDWR, NULL) : -1;
if (tli4 == -1 && tli6 == -1) {
syslog(LOG_ERR, "can\'t open TLI endpoint(s)\n");
exit(Et_open);
}
if (tli4 != -1) {
if (netdir_options(nconf4, ND_SET_RESERVEDPORT, tli4, NULL)) {
syslog(LOG_ERR, "could not set reserved port: %s\n",
netdir_sperror());
exit(Enetdir_rsvdport);
}
}
if (tli6 != -1) {
if (netdir_options(nconf6, ND_SET_RESERVEDPORT, tli6, NULL)) {
syslog(LOG_ERR, "could not set reserved port: %s\n",
netdir_sperror());
exit(Enetdir_rsvdport);
}
}
#ifdef DEBUG
{
int i, tli[2];
char *label[2] = {"udp", "udp6"};
int state;
struct t_info tinfo;
tli[0] = tli4;
tli[1] = tli6;
for (i = 0; i < sizeof (tli)/sizeof (tli[0]); i++) {
fprintf(stderr, "transport %s, fd = %d\n",
tli[i], label[i]);
if ((state = t_sync(tli[i])) < 0) {
fprintf(stderr, "t_sync failed: %s\n",
t_errlist[t_errno]);
exit(Et_sync);
}
if (t_getinfo(tli[i], &tinfo) < 0) {
fprintf(stderr, "t_getinfo failed: %s\n",
t_errlist[t_errno]);
exit(Et_info);
}
switch (state) {
case T_UNBND:
fprintf(stderr, "TLI is unbound\n");
break;
case T_IDLE:
fprintf(stderr, "TLI is idle\n");
break;
case T_INREL:
fprintf(stderr,
"other side wants to release\n");
break;
case T_INCON:
fprintf(stderr, "T_INCON\n");
break;
case T_DATAXFER:
fprintf(stderr, "T_DATAXFER\n");
break;
default:
fprintf(stderr, "no state info, state = %d\n",
state);
}
}
}
#endif
if (tli4 != -1) {
rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
nconf4);
transp4 = svc_tli_create(tli4, nconf4, NULL, 0, 0);
} else {
transp4 = 0;
}
if (tli6 != -1) {
rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
nconf6);
transp6 = svc_tli_create(tli6, nconf6, NULL, 0, 0);
} else {
transp6 = 0;
}
if (transp4 == 0 && transp6 == 0) {
syslog(LOG_ERR, "yppasswdd: couldn't create an RPC server\n");
exit(Esvc_create);
}
if (transp4 && !svc_reg(transp4, (ulong_t)YPPASSWDPROG,
(ulong_t)YPPASSWDVERS, boilerplate, nconf4)) {
syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
exit(Esvc_reg);
}
if (transp6 && !svc_reg(transp6, (ulong_t)YPPASSWDPROG,
(ulong_t)YPPASSWDVERS, boilerplate, nconf6)) {
syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
exit(Esvc_reg);
}
/*
* Create a loopback RPC service for secure authentication of local
* principals -- we need this for accepting passwd updates from
* root on the master server.
*/
if ((nconfl = getnetconfigent("ticlts")) == NULL) {
syslog(LOG_ERR, "transport ticlts not supported\n");
exit(Egetnetconfigent);
}
rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS, nconfl);
transpl = svc_tli_create(RPC_ANYFD, nconfl, NULL, 0, 0);
if (transpl == NULL) {
syslog(LOG_ERR,
"yppasswdd: couldn't create an loopback RPC server\n");
exit(Esvc_create);
}
if (!svc_reg(transpl, (ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
boilerplate, nconfl)) {
syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
exit(Esvc_reg);
}
__rpc_negotiate_uid(transpl->xp_fd);
freenetconfigent(nconf4);
freenetconfigent(nconf6);
freenetconfigent(nconfl);
svc_run();
syslog(LOG_ERR, "yppasswdd: svc_run shouldn't have returned\n");
return (Esvcrun_ret);
/* NOTREACHED */
}
static void
boilerplate(struct svc_req *rqstp, SVCXPRT *transp)
{
switch (rqstp->rq_proc) {
case NULLPROC:
if (!svc_sendreply(transp, xdr_void, (char *)0))
syslog(LOG_WARNING,
"yppasswdd: couldn't reply to RPC call\n");
break;
case YPPASSWDPROC_UPDATE:
if (yptol_mode)
shim_changepasswd(transp);
else
changepasswd(transp);
break;
}
}
int
validstr(char *str, size_t size)
{
char c;
if (str == NULL || strlen(str) > size || strchr(str, ':'))
return (0);
while (c = *str++) {
if (iscntrl(c))
return (0);
}
return (1);
}
bool_t
validloginshell(char *pw_shell, char *arg, int privileged)
{
static char newshell[STRSIZE];
char *cp, *valid;
if (pw_shell == 0 || *pw_shell == '\0')
pw_shell = defshell;
if ((defopen(DEFAULT_YPPASSWDD)) == 0) {
if ((defread(YPPASSWDD_STR)) != NULL) {
cp = strrchr(pw_shell, '/');
if (cp)
cp++;
else
cp = pw_shell;
if (*cp == 'r') {
syslog(LOG_ERR,
"yppasswdd: cannot change "
"from restricted shell %s\n",
pw_shell);
return (0);
}
}
(void) defopen((char *)NULL);
}
for (valid = getusershell(); valid; valid = getusershell())
if (strcmp(pw_shell, valid) == 0)
break;
if (valid == NULL && !privileged) {
syslog(LOG_ERR, "yppasswdd: Current shell is not valid: %s\n",
pw_shell);
endusershell();
return (0);
}
if (arg != 0) {
strncpy(newshell, arg, sizeof (newshell) - 1);
newshell[sizeof (newshell) - 1] = 0;
} else {
endusershell();
return (0);
}
/*
* Allow user to give shell name w/o preceding pathname.
*/
setusershell();
for (valid = getusershell(); valid; valid = getusershell()) {
if (newshell[0] == '/') {
cp = valid;
} else {
cp = strrchr(valid, '/');
if (cp == 0)
cp = valid;
else
cp++;
}
if (strcmp(newshell, cp) == 0)
break;
}
if (valid == 0) {
if (!privileged || newshell[0] != '/') {
syslog(LOG_WARNING,
"%s is unacceptable as a new shell.\n",
newshell);
endusershell();
return (0);
}
valid = newshell;
}
if (access(valid, X_OK) < 0) {
syslog(LOG_WARNING, "%s is unavailable.\n", valid);
endusershell();
return (0);
}
strncpy(newshell, valid, sizeof (newshell));
pw_shell = newshell;
endusershell();
return (1);
}
static void
unlimit(int lim)
{
struct rlimit rlim;
rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
setrlimit(lim, &rlim);
}