listen.c revision 113f42324b1acf3e5144da27ddbda36e338fbd07
/*
* 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
* 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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Network Listener Process
*
* command line:
*
* listen [ -m minor_prefix ] netspec
*
*/
/* system include files */
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <memory.h>
#include <values.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <sac.h>
#include <utmpx.h>
/* listener include files */
#include "lsparam.h" /* listener parameters */
#include "lsfiles.h" /* listener files info */
#include "lserror.h" /* listener error codes */
#include "lsnlsmsg.h" /* NLPS listener protocol */
#include "lssmbmsg.h" /* MS_NET identifier */
#include "lsdbf.h" /* data base file stuff */
#include "listen.h"
/* defines */
#define GEN 1
#define LOGIN 0
/* global variables */
int NLPS_proc = 0; /* set if process is a listener child */
char *Progname; /* listener's basename (from argv[0]) */
char *Minor_prefix; /* prefix for minor device names */
int Dbf_entries; /* number of private addresses in dbf file*/
int Valid_addrs; /* number of addresses bound */
char *Server_cmd_lines; /* database space */
char *New_cmd_lines; /* database space (reread) */
long Ndesc; /* Number of per-process file descriptors */
int Readdb; /* set to TRUE by SAC_READDB message */
/* FILE DESCRIPTOR MANAGEMENT:
*
* The listener uses 6 (sometimes 7) file descriptors:
* fd 1: In the parent, a connection to _sacpipe. Closed in the child
* and dup'ed to 0.
* fd 2: In the parent, a connection to _pmpipe. Dup'ed in the child
* to 0.
* reserved to open the STREAMS pipe when passing the connection
* to a standing server.
* fd 4: Opened to the pid file. We have to keep it open to keep the
* lock active.
* fd 5: Opened to the log file.
* fd 6: Opened to the debug file ONLY when compiled with DEBUGMODE.
*
* The remaining file descriptors are available for binding private addresses.
*/
#ifndef DEBUGMODE
#define USEDFDS 6
#else
#define USEDFDS 7
#endif
int Acceptfd; /* to accept connections (fd 0) */
int Sacpipefd; /* pipe TO sac process (fd 1) */
int Pmpipefd; /* pipe FROM sac process (fd 2) */
int Passfd; /* pipe used to pass FD (fd 3) */
int Pidfd; /* locked pid file (fd 4) */
char Mytag[15];
int Splflag; /* logfile critical region flag */
static char *badnspmsg = "Bad netspec on command line ( Pathname too long )";
static char *badstart = "Listener failed to start properly";
static char *nologfile = "Unable to open listener log file during initialization";
static char *usage = "Usage: listen [ -m minor_prefix ] network_device";
static char *nopmtag = "Fatal error: Unable to get PMTAG from environment";
#define TZSTR "TZ="
void check_sac_mesg(); /* routine to process messages from sac */
void rpc_register(); /* routine to register rpc services */
void rpc_unregister(); /* routine to unregister rpc services */
extern struct netconfig *getnetconfigent();
extern char *t_alloc();
extern void logexit();
extern int t_errno;
extern int errno;
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
static void mod_prvaddr(void);
static void listen(void);
static void rst_signals(void);
static void catch_signals(void);
static void net_open(void);
static void init_files(void);
static void pid_open(void);
int
{
int ret;
char *mytag_p;
extern char *getenv();
char *parse();
int c;
extern char *optarg;
extern int optind;
int i;
/* Get my port monitor tag out of the environment */
/* no place to write */
exit(1);
}
/* open log file */
/* file exists, try and save it but if we can't don't worry */
}
/* as stated above, the log file should be file descriptor 5 */
/* Get my port monitor tag out of the environment */
}
(void) umask(022);
logmessage("Must be root to start listener");
}
switch (c) {
case 'm':
break;
default:
break;
}
}
}
if (!Minor_prefix)
}
/*
* SAC will start the listener in the correct directory, so we
* don't need to chdir there, as we did in older versions
*/
(void) umask(0);
init_files(); /* open Accept, Sac, Pm, Pass files */
pid_open(); /* create pid file */
#ifdef DEBUGMODE
#endif
#ifdef DEBUGMODE
#else
if (!Logfp)
#endif
/*
* In case we started with no environment, find out what timezone we're
* in. This will get passed to children, so only need to do once.
*/
if (fp) {
break;
}
}
}
else {
}
}
logmessage("@(#)listen:listen.c 1.19.9.1");
#ifdef DEBUGMODE
logmessage("Listener process with DEBUG capability");
#endif
/* fill in Pmmesg fields that always stay the same */
/* Find out what state to start in. If not in env, exit */
State = PM_ENABLED;
logmessage("Starting state: ENABLED");
}
else {
State = PM_DISABLED;
logmessage("Starting state: DISABLED");
}
/* try to get my "basename" */
++Progname;
else
/*
* Allocate memory for private address and file descriptor table
* Here we are assuming that no matter how many private addresses
* exist in the system if the system limit is 20 then we will only
* get 20 file descriptors
*/
net_open(); /* init, open, bind names */
}
logmessage("Initialization Complete");
listen();
return (0);
}
/*
* pid_open:
*
* open pidfile with specified oflags and modes and lock it
*
*/
static char *pidopenmsg ="Can't create process ID file in home directory";
static char *pidlockmsg ="Can't lock PID file: listener may already be running";
static void
pid_open(void)
{
int ret;
unsigned int i;
char pidstring[20];
}
}
continue;
if (ret < 0)
else
}
}
/*
* init_files: open initial files for the listener (see FILE DESC MGMT comment)
*/
static char *pmopenmsg = "Can't open pipe to read SAC messages";
static char *sacopenmsg = "Can't open pipe to respond to SAC messages";
static void
init_files(void)
{
close(0);
logmessage("Trouble opening /dev/null");
}
close(1);
}
close(2);
}
close(3);
logmessage("Trouble duping /dev/null");
}
}
/*
* net_open: open and bind communications channels
* The name generation code in net_open, open_bind and bind is,
* for the most part, specific to STARLAN NETWORK.
* This name generation code is included in the listener
* as a developer debugging aid.
*/
static void
net_open(void)
{
#ifdef CHARADDR
#endif /* CHARADDR */
int i;
/* set up free call list and pending connection lists */
/* Pending calls are linked in a structure, one per fild descriptor */
i = 0;
Valid_addrs = 0;
/* first do static addrs */
while ( (i < Dbf_entries) ) {
if (add_prvaddr(dp) == 0)
Valid_addrs++;
}
i++;
}
i = 0;
/* second pass for dynamic addrs */
while ( (i < Dbf_entries) ) {
if (add_prvaddr(dp) == 0)
Valid_addrs++;
}
i++;
}
}
/*
* Following are some general queueing routines. The call list head contains
* a pointer to the head of the queue and to the tail of the queue. Normally,
* calls are added to the tail and removed from the head to ensure they are
* processed in the order received, however, because of the possible interruption
* of an acceptance with the resulting requeueing, it is necessary to have a
* way to do a "priority queueing" which inserts at the head of the queue for
* immediate processing
*/
/*
* queue:
*
* add calls to tail of queue
*/
void
{
}
else {
}
}
/*
* pqueue:
*
* priority queuer, add calls to head of queue
*/
void
{
}
else {
}
}
/*
* dequeue:
*
* remove a call from the head of queue
*/
struct callsave *
{
#ifdef OLD
#endif
}
return(ret);
}
/*
* open_bind:
*
* open the network and bind the endpoint to 'name'
* this routine is also used by listen(), so it can't exit
* under all error conditions:
* if there are no minor devices avaliable in the network driver,
* open_bind returns -1. (error message will be logged).
* if the open fails because all file descriptors are in use,
* open_bind returns -2. (no message logged). This should
* only happen when too many private addresses are specified.
* if the bind fails, open_bind returns -3 (no message logged). This
* happens when a duplicate address is bound, and the message
* should be logged by the routine that calls open_bind.
* All other errors cause an exit.
*
* If clen is zero, transport provider picks the name and these
* routines (open_bind and bind) ignore name and qlen --
* this option is used when binding a name for accepting a connection
* (not for listening.) You MUST supply a name, qlen and clen when
*
* Assumptions: driver returns ENXIO when all devices are allocated.
*/
int
char *name;
int qlen;
int clen;
unsigned int *conp;
char **adrp;
{
int fd;
int ret;
switch (errno) {
case EINTR:
continue;
case EMFILE:
return(-2);
break;
case ENXIO:
case ENOSR:
case ENOSPC:
case EAGAIN:
logmessage("No network minor devices (ENXIO/ENOSR)");
return(-1);
break;
}
}
}
if (ret < 0) {
return(-3);
}
if (conp)
return(fd);
}
int
int fd;
char *name;
int qlen;
int clen;
char **ap;
{
char *p, *q;
unsigned int retval;
extern void nlsaddr2c();
extern int memcmp();
extern int errno;
#ifdef CHARADDR
#endif
if (clen) {
else
}
else
}
}
if (clen == -1) {
}
else {
}
#endif /* CHARADDR && DEBUGMODE */
#endif /* CHARADDR && DEBUGMODE */
}
if (qlen) /* starup only */
/* here during normal service */
/* our name space is all used up */
if (clen) {
}
return(-1);
}
/* otherwise, irrecoverable error */
}
if (clen) {
if (clen == -1) {
/* dynamic address */
if (*ap) {
(*ap)[0] = '\\';
}
}
if (p && q) {
free(p);
free(q);
}
return(-1);
}
return(retval);
}
return((unsigned int) 0);
}
/*
* catch_signals:
* Ignore some, catch the rest. Use SIGTERM to kill me.
*/
static void
catch_signals(void)
{
extern void sigterm();
(void) sigfillset(&sset);
}
/*
* rst_signals:
* After forking but before exec'ing a server,
* reset all signals to original setting.
*/
static void
rst_signals(void)
{
}
/*
* sigterm: Clean up and exit.
*/
void
sigterm()
{
extern char *shaddr;
extern char *sh2addr;
}
/*
* listen: listen for and process connection requests.
*/
static char *dbfnewdmsg = "Using new data base file";
static void
listen(void)
{
int i;
== NULL)
/* setup poll structures for sac messages and private addresses */
sp++;
sp++;
}
}
for (;;) {
/* +1 for Pmpipefd */
continue;
/* poll error */
}
else {
/* incoming request or message */
case POLLIN:
}
else {
if (State == PM_ENABLED)
else
}
break;
case 0:
break;
/* distinguish the various errors for the user */
case POLLERR:
logmessage("poll() returned POLLERR");
case POLLHUP:
logmessage("poll() returned POLLHUP");
case POLLNVAL:
logmessage("poll() returned POLLNVAL");
case POLLPRI:
logmessage("poll() returned POLLPRI");
case POLLOUT:
logmessage("poll() returned POLLOUT");
default:
logmessage("poll() returned unrecognized event");
}
}
}
if (Readdb) {
logmessage("Re-reading database");
/* have to close an fd because read_dbf needs it */
/* MUST re-open Acceptfd to insure it is free later */
mod_prvaddr();
}
else {
}
}
}
}
/*
* check_sac_mesg: check the pipe to see if SAC has sent a message
*/
void
{
int length;
/* read all messages out of pipe */
if (length < 0) {
continue;
return;
}
case SC_STATUS:
break;
case SC_ENABLE:
if (State != PM_ENABLED)
logmessage("New state: ENABLED");
State = PM_ENABLED;
break;
case SC_DISABLE:
if (State != PM_DISABLED)
logmessage("New state: DISABLED");
State = PM_DISABLED;
break;
case SC_READDB:
break;
default:
logmessage("Received unknown message from sac -- ignored");
break;
}
continue;
break;
}
}
}
/*
* doevent: handle an asynchronous event
*/
static void
{
case 0:
/* no return */
case T_LISTEN:
return;
}
break;
case T_DISCONNECT:
else
}
}
/* no return */
}
break;
default:
break;
}
}
/*
* send_dis: send a disconnect
* called when we are in state PM_DISABLED
*/
static void
{
return;
}
else
}
}
return;
}
/*
* trycon: try to accept a connection
*/
static void
{
int i;
continue;
}
continue;
}
SPLhi();
continue; /* let transport provider generate disconnect */
}
SPLlo();
SPLhi();
logmessage("Trouble duping fd 0");
SPLlo();
logmessage("Incoming call during t_accept -- queueing current call");
return;
}
else {
SPLhi();
logmessage("Trouble duping fd 0");
SPLlo();
continue;
}
}
goto cleanup;
}
/* doconfig needs a file descriptor, so use Passfd */
goto cleanup;
}
/* open pipe to pass fd through */
/* bad pipe? */
goto cleanup;
}
/* clean up call, log error */
}
/* clean up this call */
}
else {
else if (!pid) {
setpgrp();
/* so log files are correct */
logmessage("Can't expand server's environment");
}
#ifdef COREDUMP
abort();
#endif
/* no return */
}
/* only parent gets here */
SPLhi();
logmessage("Trouble duping fd 0");
SPLlo();
}
}
}
/*
* common code to start a server process (for any service)
* The first argument in argv is the full pathname of server.
* Before exec-ing the server, the caller's
* logical address, opt and udata are addded to the environment.
*/
int
int netfd;
{
char *path;
char **argvp;
extern char **environ;
extern char **mkdbfargv();
char msgbuf[256];
int i;
/* set up stdout and stderr before pushing optional modules */
/* this child doesn't need access to _sacpipe and _pmpipe */
char device[20];
/*
* create a utmpx entry --
* we do an extra fork here to make init this process's
* parent. this lets init clean up the utmpx entry when
* this proc dies.
*
* the utmpx routines need a file descriptor!
*/
logmessage("Can't fork to create utmpx entry");
exit(2);
}
if (tmp)
exit(0); /* kill parent */
/*
* child continues processing, creating utmp and exec'ing
* the service
*/
setpgrp();
logmessage("Stat failed on fd 0: no line field "
"available for utmpx entry");
*device = '\0';
}
else {
else
}
/*
* prepend a "." so this can be distinguished as a "funny"
* utmpx entry that may never get a DEAD_PROCESS entry in
* the wtmpx file.
*/
/* XXX - utmp - fix login name length */
}
logmessage("Dup of fd 0 failed");
}
logmessage("Can't push server's modules: exit");
}
rst_signals();
exit(2);
}
}
}
}
}
}
else
endpwent();
/* exec returns only on failure! */
logmessage("ERROR: could not exec server");
return(-1);
}
/*
* senviron: Update environment before exec-ing the server:
* The callers logical address is placed in the
*
* Note: no need to free the malloc'ed buffers since this process
* will either exec or exit.
*/
int
{
char *p;
extern void nlsaddr2c();
extern char *getenv();
/*
* The following code handles the case where the listener was started with
* no environment. If so, supply a reasonable default path. Parent already
* set TZ on startup if it wasn't, so don't need to do it here.
*/
return(-1);
strcat(p, "=");
putenv(p);
return(-1);
strcat(p, "=");
putenv(p);
p = provenv;
strcpy(p, NLSPROVIDER);
strcat(p, "=");
putenv(p);
/*
* MPREFIX is NEW for SVR4.0. It tells the nlps_server what to use
* as a minor device prefix. THIS SHOULD BE DOCUMENTED!
*/
p = prefenv;
strcpy(p, "MPREFIX");
strcat(p, "=");
strcat(p, Minor_prefix);
putenv(p);
return(-1);
strcat(p, "=");
putenv(p);
return (0);
}
/*
* parse: Parse TZ= string like init does for consistency
* Work on string in place since result will
* either be the same or shorter.
*/
char *
parse(s)
char *s;
{
char *p;
char *tp;
int delim;
if ((*p == '"') || (*p == '\'')) {
/* it is quoted */
delim = *p++;
for (;;) {
if (*p == '\0') {
strcpy(s, "TZ=");
return(s);
}
if (*p == delim) {
*tp = '\0';
return(s);
}
else {
*tp++ = *p++;
}
}
}
else { /* look for comment or trailing whitespace */
for ( ; *p && !isspace(*p) && *p != '#'; ++p)
;
/* if a comment or trailing whitespace, trash it */
if (*p) {
*p = '\0';
}
return(s);
}
}
/*
* clr_call: clear out a call structure
*/
static void
{
}
/*
* pitchcall: remove call from pending list
*/
static void
{
return;
}
while (p) {
}
}
}
else {
}
queue(Free_call_p, p);
return;
}
oldp = p;
p = p->c_np;
}
logmessage("received disconnect with no pending call");
return;
}
/*
* add_prvaddr: open and bind the private address specified in the database
* entry passed into the routine. Update the maxcon and fd
* entries in the database structure
*
* This routine is very sloppy with malloc'ed memory, but addresses
* shouldn't ever change enough for this to matter.
*/
int
{
extern char *t_alloc();
int j;
int bindfd;
int maxcon;
char *ap;
int clen;
dbp->dbf_svc_code));
/* call stoa - convert from rfs address to netbuf */
return(-1);
}
}
else {
clen = -1;
}
switch (bindfd) {
case -1:
return(-1);
break;
case -2:
return(-1);
break;
case -3:
return(-1);
break;
default:
}
}
if (clen == -1) {
}
else {
}
for (j=0; j < maxcon; ++j) {
}
}
}
return(0);
}
/*
* mod_prvaddr -- after re-reading the database, take appropriate action for
* new, deleted, or changed addresses.
*/
static void
mod_prvaddr(void)
{
dbf_t *svc_code_match();
int bound;
/*
* for each entry in the new table, check for a svc code match.
* if there is a svc code match and the address matches, all we
* need to do is update the new table. if the addresses are
* different, we need to remove the old one and replace it.
*/
/* matched svc code. see if address matches. */
if ((strcmp(oldentry_p->dbf_prv_adr, entry_p->dbf_prv_adr) == 0) || ((oldentry_p->dbf_sflags & DFLAG) && (entry_p->dbf_sflags & DFLAG))) {
/* update new table with fd, set old fd to -1 */
}
}
if ((oldentry_p->dbf_version != entry_p->dbf_version) || (oldentry_p->dbf_prognum != entry_p->dbf_prognum)) {
}
}
}
}
/* now unbind the remaining addresses in the old table (fd != -1) */
if (del_prvaddr(oldentry_p) == 0)
Valid_addrs--;
}
}
/* now bind all of the new addresses (fd == -1) */
/*
* this tries to bind any addresses that failed to bind successfully
* when the address changed. This means that if a service is moved to
* an address that is being deleted, the first attempt to bind it will
* fail, the old address will be removed, and this bind will succeed
*/
/* first the static addrs */
if (add_prvaddr(entry_p) == 0)
Valid_addrs++;
}
}
/* then the dynamic addrs */
if (add_prvaddr(entry_p) == 0)
Valid_addrs++;
}
}
/* free old database, set up new pollfd table, and we're done */
/* Pollfds[0] is for _pmpipe */
sp++;
}
}
}
/*
* unbind the address, close the file descriptor, and free call structs
*/
int
{
struct call_list *q;
int i;
return -1;
i = 0;
/* delete pending calls */
i++;
}
/* delete free call structs we don't need */
for ( ; i < dbp->dbf_maxcon; i++) {
}
return 0;
}
/*
* look through the old database file to see if this service code matches
* one already present
*/
dbf_t *
char *new_code;
{
return(dbp);
}
}
/*
* register an rpc service with rpcbind
*/
void
{
extern int errno;
/* not an rpc service */
return;
return;
}
sprintf(scratch," registered with rpcbind, prognum %d version %d", dbp->dbf_prognum, dbp->dbf_version);
}
else {
logmessage("rpcb_set failed, service not registered with rpcbind");
}
return;
}
/*
* unregister an rpc service with rpcbind
*/
void
{
/* not an rpc service */
return;
}