freebsd-src/usr.sbin/rpc.yppasswdd/yppasswd_comm.c
Bill Paul 8256fad9b7 Import new rpc.yppasswdd. (Note: accompanying changes to passwd(1) and
chpass(1) are on the way too.) This version supports all the features
of the old one and adds several new ones:

- Supports real multi-domain operation (optional, can be turned
  on with a command-line flag). This means you can actually have
  several different domains all served from one NIS server and
  allow users in any of the supported domains to change their passwords.
  The old yppasswdd only allowed changing passwords in the domain
  that was set as the system default domain name on the NIS master
  server. The new one can change passwords in any domain by trying
  to match the user information passed to it against all the passwd
  maps it can find. This is something of a hack, but the yppasswd.x
  protocol definiton does not allow for a domain to be passwd as an
  argument to rpc.yppasswdd, so the server has no choice but to
  grope around for a likely match. Since this method can fail if
  the same user exists in two domains, this feature is off by default.
  If the feature is turned on and the server becomes confused by
  duplicate entries, it will abort the update.

- Does not require NIS client services to be available. NIS servers do
  _NOT_ necessarily have to be configured as NIS clients in order to
  function: the ypserv, ypxfr and yppush programs I've written recently
  will operate fine even if the system domain name isn't set, ypbind isn't
  running and there are no magic '+' entries in any of the /etc files.
  Now rpc.yppasswdd is the same way. The old yppasswdd would not work
  like this because it depended on getpwent(3) and friends to look up
  users: this will obviously only work if the system where yppasswdd is
  running is configured as an NIS client. The new rpc.yppasswdd doesn't
  use getpwent(3) at all: instead it searches through the master.passwd
  map databases directly. This also makes it easier for it to handle
  multiple domains.

- Allows the superuser on the NIS master server to change any user's
  password without requiring password authentication. rpc.yppasswdd
  creates a UNIX domain socket (/var/run/ypsock) which it monitors
  using the same svc_run() loop used to handle incoming RPC requests.
  It also clears all the permission bits for /var/run/ypsock; since
  this socket is owned by root, this prevents anyone except root from
  successfully connect()ing to it. (Using a UNIX domain socket also
  prevents IP spoofing attacks.) By building code into passwd(1) and
  chpass(1) to take advantage of this 'trusted' channel, the superuser
  can use them to send private requests to rpc.yppasswdd.

- Allows the superuser on the NIS master to use chpass(1) to update _all_
  of a user's master.passwd information. The UNIX domain access point
  accepts a full master.passwd style structure (along with a domain
  name and other information), which allows the superuser to update all
  of a user's master.passwd information in the NIS master.passwd maps.
  Normal users on NIS clients are still only allowed to change their full
  name and shell information with chpass.

- Allows the superuser on the NIS master to _add_ records to the NIS
  master.passwd maps using chpass(1). This feature is also switchable
  with a command-line flag and is off by default.
1996-02-12 15:09:01 +00:00

301 lines
8 KiB
C

/*
* Copyright (c) 1995, 1996
* Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Bill Paul.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: yppasswd_comm.c,v 1.10 1996/02/03 04:41:59 wpaul Exp $
*/
/*
* This file contains a UNIX domain socket communications package
* that lets a client process send pseudo-RPCs to rpc.yppasswdd
* without using IP. This 'local-only' communications channel is
* only used when the superuser runs passwd(1) or chpass(1) on
* the NIS master server. The idea is that we want to grant the
* superuser permission to perfom certain special operations, but
* we need an iron-clad way to tell when we're receiving a request
* from the superuser and when we aren't. To connect to a UNIX
* domain socket, one needs to be able to access a file in the
* filesystem. The socket created by rpc.yppasswdd is owned by
* root and has all its permission bits cleared, so the only
* user who can sucessfully connect() to it is root.
*
* It is the server's responsibility to initialize the listening
* socket with the makeservsock() function and to add the socket to
* the set of file descriptors monitored by the svc_run() loop.
* Once this is done, calls made through the UNIX domain socket
* can be handled almost exactly like a normal RPC. We even use
* the XDR functions for serializing data between the client and
* server to simplify the passing of complex data structures.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/fcntl.h>
#include <rpc/rpc.h>
#include <rpcsvc/yp.h>
#include "yppasswd_comm.h"
#include "yppasswd_private.h"
#include "ypxfr_extern.h"
#ifndef lint
static const char rcsid[] = "$Id: yppasswd_comm.c,v 1.10 1996/02/03 04:41:59 wpaul Exp $";
#endif
char *sockname = "/var/run/ypsock";
FILE *serv_fp;
FILE *clnt_fp;
int serv_sock;
int clnt_sock;
/*
* serialize_data() and serialize_resp() are what really do most of
* the work. These functions (ab)use xdrstdio_create() as the interface
* to the XDR library. The RPC library uses xdrrec_create() and friends
* for TCP based connections. I suppose we could use that here, but
* the interface is a bit too complicated to justify using in an
* applicatuion such as this. With xdrstdio_create(), the only catch
* is that we need to provide a buffered file stream rather than
* a simple socket descriptor, but we can easily turn the latter into
* the former using fdopen(2).
*
* Doing things this way buys us the ability to change the form of
* the data being exchanged without having to modify any of the
* routines in this package.
*/
static int serialize_data(data, fp, op)
struct master_yppasswd *data;
FILE *fp;
int op;
{
XDR xdrs;
xdrstdio_create(&xdrs, fp, op);
if (!xdr_master_yppasswd(&xdrs, data)) {
xdr_destroy(&xdrs);
return(1);
}
return(0);
}
static int serialize_resp(resp, fp, op)
int *resp;
FILE *fp;
int op;
{
XDR xdrs;
xdrstdio_create(&xdrs, fp, op);
if (!xdr_int(&xdrs, resp)) {
xdr_destroy(&xdrs);
return(1);
}
return(0);
}
/*
* Build the server's listening socket. The descriptor generated
* here will be monitored for new connections by the svc_run() loop.
*/
int makeservsock()
{
static int ypsock;
struct sockaddr_un us;
int len;
unlink(sockname);
if ((ypsock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
err(1, "failed to create UNIX domain socket");
bzero((char *)&us, sizeof(us));
us.sun_family = AF_UNIX;
strcpy((char *)&us.sun_path, sockname);
len = strlen(us.sun_path) + sizeof(us.sun_family) + 1;
if (bind(ypsock, (struct sockaddr *)&us, len) == -1)
err(1,"failed to bind UNIX domain socket");
listen (ypsock, 1);
return(ypsock);
}
/*
* Create a socket for a client and try to connect() it to the
* server.
*/
static int makeclntsock()
{
static int ypsock;
struct sockaddr_un us;
int len;
if ((ypsock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
warn("failed to create UNIX domain socket");
return(-1);
}
bzero((char *)&us, sizeof(us));
us.sun_family = AF_UNIX;
strcpy((char *)&us.sun_path, sockname);
len = strlen(us.sun_path) + sizeof(us.sun_family) + 1;
if (connect(ypsock, (struct sockaddr *)&us, len) == -1) {
warn("failed to connect to server");
return(-1);
}
return(ypsock);
}
/*
* This function is used by the server to accept a new connection
* from a client and read its request data into a master_yppasswd
* stucture.
*/
struct master_yppasswd *getdat(sock)
int sock;
{
int len;
struct sockaddr_un us;
static struct master_yppasswd pw;
struct timeval tv;
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
tv.tv_sec = CONNECTION_TIMEOUT;
tv.tv_usec = 0;
switch(select(FD_SETSIZE, &fds, NULL, NULL, &tv)) {
case 0:
yp_error("select timed out");
return(NULL);
break;
case -1:
yp_error("select() failed: %s", strerror(errno));
return(NULL);
break;
default:
break;
}
if ((serv_sock = accept(sock, (struct sockaddr *)&us, &len)) == -1) {
yp_error("accept failed: %s", strerror(errno));
return(NULL);
}
if ((serv_fp = fdopen(serv_sock, "r+")) == NULL) {
yp_error("fdopen failed: %s",strerror(errno));
return(NULL);
}
if (serialize_data(&pw, serv_fp, XDR_DECODE)) {
yp_error("failed to receive data");
return(NULL);
}
return(&pw);
}
/*
* Client uses this to read back a response code (a single
* integer) from the server. Note that we don't need to implement
* any special XDR function for this since an int is a base data
* type which the XDR library can handle directly.
*/
int *getresp()
{
static int resp;
if (serialize_resp(&resp, clnt_fp, XDR_DECODE)) {
warn("failed to receive response");
return(NULL);
}
fclose(clnt_fp);
close(clnt_sock);
return(&resp);
}
/*
* Create a connection to the server and send a reqest
* to be processed.
*/
int senddat(pw)
struct master_yppasswd *pw;
{
if ((clnt_sock = makeclntsock()) == -1) {
warn("failed to create socket");
return(1);
}
if ((clnt_fp = fdopen(clnt_sock, "r+")) == NULL) {
warn("fdopen failed");
return(1);
}
if (serialize_data(pw, clnt_fp, XDR_ENCODE)) {
warn("failed to send data");
return(1);
}
return(0);
}
/*
* This sends a response code back to the client.
*/
int sendresp(resp)
int resp;
{
if (serialize_resp(&resp, serv_fp, XDR_ENCODE)) {
yp_error("failed to send response");
return(-1);
}
fclose(serv_fp);
close(serv_sock);
return(0);
}