/********************************************************************
* wilkinson
* 3.40VMS-1
* 1995/11/24 13:00
* gopher_root1:[gopher.g2.vms2_13.gopherd]serverutil.c,v
* Exp
*
* Paul Lindner, University of Minnesota CIS.
*
* Copyright 1991, 1992 by the Regents of the University of Minnesota
* see the file "Copyright" in the distribution for conditions of use.
*********************************************************************
* MODULE: serverutil.c
* utilities for the server.
*********************************************************************
* Revision History:
* serverutil.c,v
* Revision 3.40VMS-1 1995/11/24 13:00 wilkinson
* Tweaked security violation character scan and logging in Gpopen().
*
* Revision 3.40VMS 1995/09/25 14:40 wilkinson
* Consolodate VMS/Unix source code for server as well as client
*
* Revision 3.40 1995/02/25 20:49:05 lindner
* More timeouts..
*
* Revision 3.39 1995/02/11 06:22:49 lindner
* Fix for setvbuf parameters
*
* Revision 3.38 1995/02/07 19:34:08 lindner
* A little more readable
*
* Revision 3.37 1995/02/07 08:37:37 lindner
* Rewrite of mailfile/multifile parsing
*
* Revision 3.36 1995/02/07 07:03:45 lindner
* performance fixes
*
* Revision 3.35 1995/02/02 17:13:54 lindner
* Fix memory leaks
*
* Revision 3.34 1994/12/31 07:46:45 lindner
* Make shell script output line buffered for slow scripts
*
* Revision 3.33 1994/12/20 17:20:56 lindner
* Put environment variable setter here..
*
* Revision 3.32 1994/12/06 20:56:41 lindner
* Fix for multi-line bummer messages
*
* Revision 3.31 1994/11/29 05:00:09 lindner
* Fix for exec:
*
* Revision 3.30 1994/11/16 18:53:35 lindner
* Allow multi-line Bummer messages
*
* Revision 3.29 1994/10/13 05:17:50 lindner
* Compiler complaint fixes
*
* Revision 3.28 1994/09/29 19:57:09 lindner
* Make Grep Index queries work
*
* Revision 3.27 1994/07/21 15:46:49 lindner
* New multipart file code
*
* Revision 3.26 1994/07/03 21:18:04 lindner
* Add initgroup() call
*
* Revision 3.25 1994/06/29 05:36:54 lindner
* Add username logging
*
* Revision 3.24 1994/04/22 03:35:50 lindner
* for sunos 4.0.3
*
* Revision 3.23 1994/03/31 22:45:50 lindner
* Generate gopher- error responses for gopher- clients
*
* Revision 3.22 1994/03/31 21:10:30 lindner
* Don't need Setuid_username unless using UMNDES
*
* Revision 3.21 1994/03/08 15:56:09 lindner
* gcc -Wall fixes
*
* Revision 3.20 1994/03/04 23:25:38 lindner
* faster isadir()
*
* Revision 3.19 1994/01/21 04:01:10 lindner
* Add support for flock(), fix LOGGopher() function declaration
*
* Revision 3.18 1993/11/05 07:25:44 lindner
* futzing with stdarg lines
*
* Revision 3.17 1993/10/11 04:40:54 lindner
* Changes to allow logging via daemon.info syslogd facility
*
* Revision 3.16 1993/09/30 23:16:50 lindner
* Hack out SETPROCTITLE on systems that can't hack it
*
* Revision 3.15 1993/09/30 17:01:18 lindner
* Remove unnessesary logging
*
* Revision 3.14 1993/09/20 16:54:00 lindner
* Remove dead code, moved to Sockets.c
*
* Revision 3.13 1993/08/10 20:28:10 lindner
* return true for non-existant cache file
*
* Revision 3.12 1993/08/06 14:30:47 lindner
* Fixes for better security logging
*
* Revision 3.11 1993/08/05 20:46:36 lindner
* Fix for Gpopen for single quotes and !
*
* Revision 3.10 1993/08/04 22:14:51 lindner
* Mods to use Gpopen
*
* Revision 3.9 1993/08/04 22:12:48 lindner
* Mods to use Gpopen
*
* Revision 3.8 1993/07/27 05:27:56 lindner
* Mondo Debug overhaul from Mitra
*
* Revision 3.7 1993/07/20 23:57:29 lindner
* Added LOGGopher here, added routine to set proctitle
*
* Revision 3.6 1993/07/07 19:34:53 lindner
* fixed typo in GplusError
*
* Revision 3.5 1993/06/22 07:07:14 lindner
* prettyfication
*
* Revision 3.4 1993/04/10 06:07:40 lindner
* More debug msgs
*
* Revision 3.3 1993/04/09 15:12:09 lindner
* Better error checking on getpeername()
*
* Revision 3.2 1993/03/24 20:28:55 lindner
* Moved some code from gopherd.c and changed error message delivery
*
* Revision 3.1.1.1 1993/02/11 18:02:53 lindner
* Gopher+1.2beta release
*
* Revision 1.2 1993/02/09 22:16:24 lindner
* Mods for gopher+ error results.
*
* Revision 1.1 1992/12/10 23:13:27 lindner
* gopher 1.1 release
*
*
*********************************************************************/
#include "gopherd.h"
#include "serverutil.h"
#include "Debug.h"
#include "Dirent.h" /* For S_ISADIR */
#ifndef VMS_SERVER
#ifndef NOSYSLOG
#include <syslog.h>
#else
#define syslog(a,b) fprintf(stderr, "%s\n", (b))
#define openlog(a,b,c) fprintf(stderr, "%s\n", (a))
#endif
#else
#ifdef MULTINET
#include "syslog.h"
#else
#ifndef NOSYSLOG
#define NOSYSLOG
#endif
#endif
#endif
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#ifndef VMS_SERVER
/* Try using flock() if fcntl() won't let us lock. */
#if !defined(USE_FLOCK) && !defined(F_SETLKW)
#define USE_FLOCK
#endif
#ifdef USE_FLOCK
#include <sys/file.h>
#endif
#endif
/*
* This finds the current peer and the time and jams it into the
* logfile (if any) and adds the message at the end
*/
#ifdef __STDC__
void LOGGopher(int sockfd, const char *fmt, ...)
#else /* !__STDC__ */
void
LOGGopher(sockfd, fmt, va_alist)
int sockfd;
char *fmt;
va_dcl
#endif /* __STDC__ */
{
va_list args;
char message[512];
time_t Now;
char *cp;
/* cp + ' ' + host_name + ' : ' + MAXLINE + '\n' + '\0' */
char buf[286+MAXLINE];
char userandhost[256];
#ifndef VMS_SERVER
#ifndef USE_FLOCK
struct flock lock;
#endif
#else
char NowBuf[26];
char *c2;
char LogTag[60];
char LogBuf[100];
int i;
#endif
#ifdef __STDC__
va_start(args, fmt);
#else
va_start(args);
#endif
(void) vsprintf(message, fmt, args);
#ifdef VMS_SERVER
va_end(args);
userandhost[0] = '\0';
if (CurrentUser != NULL) {
strcpy(userandhost, CurrentUser);
strcat(userandhost, "@");
strcat(userandhost, CurrentPeerName);
} else
strcpy(userandhost, CurrentPeerName);
if (c2=GDCgetLogTag(Config)) {
c2 = strcpy(LogTag, c2);
if (cp=strchr(c2,'#')) {
if (strncasecmp(cp-2,"Cn",2)==0) {
cp -= 2;
*cp = '\0';
strcpy(LogBuf,LogTag);
cp += 3;
i = strlen(LogBuf);
LogBuf[i+=sprintf(LogBuf+i,"#%d",Connections)] = '\0';
strcat(LogBuf, cp);
c2 = LogBuf;
}
}
}
#else
if (LOGFileDesc == -1) {
return;
}
#endif
if ((int)LOGFileDesc == -2) {
/** Syslog case.. **/
#ifndef VMS_SERVER
syslog(LOG_INFO, "%s : %s", CurrentPeerName, message);
#else
syslog(LOG_INFO, "p%d%s %s : %s", Config?GDCgetPort(Config):0, c2,
userandhost, message);
#endif
return;
}
/** File case ***/
#ifndef VMS_SERVER
#ifdef USE_FLOCK
flock(LOGFileDesc, LOCK_EX);
#else
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0L;
lock.l_len = 0L;
fcntl(LOGFileDesc, F_SETLKW, &lock);
#endif
#endif
time(&Now); /* Include this in the lock to make sure */
cp = ctime(&Now); /* log entries are chronological */
ZapCRLF(cp);
#ifndef VMS_SERVER
/* someone else may have written to the file since we opened it */
lseek(LOGFileDesc, 0L, SEEK_END);
userandhost[0] = '\0';
if (CurrentUser != NULL) {
strcpy(userandhost, CurrentUser);
strcat(userandhost, "@");
strcat(userandhost, CurrentPeerName);
} else
strcpy(userandhost, CurrentPeerName);
sprintf(buf, "%s %d %s : %s\n", cp, getpid(), userandhost, message);
write(LOGFileDesc, buf, strlen(buf));
/* unlock the file */
#ifdef USE_FLOCK
flock(LOGFileDesc, LOCK_UN);
#else
lock.l_type = F_UNLCK;
fcntl(LOGFileDesc, F_SETLKW, &lock);
#endif
Debug("%s", buf);
#else
cp=strcpy(NowBuf,cp);
sprintf(buf, "%s p%d%s %s : %s\n", cp, Config?GDCgetPort(Config):0, c2,
userandhost, message);
if (!GDCgetLogfile(Config) && strlen(fmt) && !RunFromInetd) {
Debug("LOGGopher w/out LogFile: %s\n", buf);
}
else {
if ((LOGFileDesc = fopen_VMSopt(GDCgetLogfile(Config),"a",
log_alq,log_deq)) !=NULL) {
if (strlen(fmt)) {
fwrite(buf, strlen(buf), 1, LOGFileDesc);
Debug("LOGGopher: %s\n", buf);
fflush(LOGFileDesc);
}
fclose(LOGFileDesc);
}
else {
if (strlen(fmt) && !RunFromInetd) {
char *oops;
oops = (char *)malloc(strlen(buf)+256);
sprintf(oops, "Can't open the logfile %s - (%d/%d=%s/%s)\n%s",
GDCgetLogfile(Config),
vaxc$errno, vaxc$errno_stv,
STRerror(errno), STRerror_stv(),
buf);
fprintf(stderr, "LOGGopher: %s\n",oops);
Debug("LOGGopher w/ bad Logfile: %s\n", oops);
free(oops);
if (vaxc$errno == RMS$_ACC)
gopherd_exit(vaxc$errno);
}
}
}
if (sockfd == -99) {
DEBUG = TRUE;
LOGGopher(-1,"server shutdown!!!");
gopherd_exit(-1);
}
#endif
}
/*
* Gopher + error mechanism
*
* errclass is the type of error
* text is the first line of error text,
* moretext is a char array of yet more text to send
*/
/*
For Gopher0 requests
if they're expecting a directory, just pass back the
AbortMsg -- it's a single-item directory
reference pointing to the message file.
if they're expecting a document, we should read the
document to them.
For Gopher+ users
we should read the AbortMsg file to them.
*/
void
GplusError(sockfd, errclass, text, moretext)
int sockfd;
int errclass;
char *text;
char **moretext;
{
char outputline[256];
int i;
char *tempstr = NULL;
char *temptext[256];
#ifdef VMS_SERVER
char expected;
if (tempstr = CMDgetSelstr(cmd))
switch (*tempstr) {
case A_DIRECTORY:
case A_INDEX:
case A_FTP:
case A_MAILSPOOL:
expected = A_DIRECTORY;
break;
default: expected = A_FILE;
}
if (AbortString)
text = AbortString;
#endif
if (text == NULL)
text = "Unspecified error";
if ((moretext == NULL) && (strchr(text, '\n') != NULL)) {
char *nl;
int i = 0;
tempstr = strdup(text);
nl = tempstr;
while (nl = strchr(nl, '\n')) {
*nl = '\0';
nl++;
temptext[i++] = nl;
}
temptext[i] = NULL;
moretext = temptext;
text = tempstr;
}
if (!IsGplus)
#ifndef VMS_SERVER
sprintf(outputline, "0%s\t\terror.host\t1\r\n", text);
#else
{
if (expected == A_DIRECTORY)
sprintf(outputline, "0%s\t%s\t%s\t%d\r\n", text,
AbortMsg?AbortMsg:"",
AbortMsg?GDCgetHostname(Config):"error.host",
AbortMsg?GDCgetPort(Config):1);
else
sprintf(outputline, "%s\r\n", text);
}
#endif
else
sprintf(outputline, "--1\r\n%d %s <%s>\r\n%s\r\n",
errclass, GDCgetAdmin(Config),
GDCgetAdminEmail(Config), text);
(void) alarm(WRITETIMEOUT);
if (writestring(sockfd, outputline)<0) {
LOGGopher(sockfd, "Client went away!");
#ifndef VMS_SERVER
exit(-1);
#else
(void) alarm(0);
return;
#endif
}
(void) alarm(0);
if (moretext != NULL)
for (i=0; moretext[i] != NULL; i++) {
if (!IsGplus) {
#ifndef VMS_SERVER
sprintf(outputline, "0%s\t\terror.host\t1\r\n",
moretext[i]);
#else
if (expected==A_DIRECTORY)
sprintf(outputline, "0%s\t\terror.host\t1\r\n",
moretext[i]);
else
sprintf(outputline, "%s\r\n", text);
#endif
writestring(sockfd, outputline);
} else {
(void) alarm(WRITETIMEOUT);
writestring(sockfd, moretext[i]);
writestring(sockfd, "\n");
(void) alarm(0);
}
}
#ifdef VMS_SERVER
if (AbortMsg != NULL && (IsGplus || (expected == A_FILE))) {
FILE *AbortFile;
time_t now;
char *line;
if (AbortFile = fopen_VMSopt(AbortMsg+1,"r")) {
while (fgets(outputline, sizeof(outputline), AbortFile) != NULL) {
ZapCRLF(outputline);
if (*outputline == '.' && outputline[1] == '\0') {
outputline[1] = '.';
outputline[2] = '\0';
}
now = time(&now);
if (AbortGS)
line = VMS$FormatTokens(outputline, GSgetPath(AbortGS)+1,
NULL, &now,
GSgetPort(AbortGS)?GSgetPort(AbortGS):-1,
GSgetHost(AbortGS),
GSgetType(AbortGS),
GSgetAdmin(AbortGS),
cmd);
else
line = VMS$FormatTokens(outputline, NULL, NULL, &now,
-1, NULL, -1, NULL, cmd);
if (!IsGplus) {
writestring(sockfd, line);
writestring(sockfd, "\r\n");
} else {
(void) alarm(WRITETIMEOUT);
writestring(sockfd, line);
writestring(sockfd, "\n");
(void) alarm(0);
}
}
fclose(AbortFile);
}
}
#endif
(void) alarm(WRITETIMEOUT);
writestring(sockfd, ".\r\n");
(void) alarm(0);
close(sockfd);
return;
}
/*
* This routine cleans up an open file descriptor and sends out a bogus
* filename with the error message
*/
void
Abortoutput(sockfd, errmsg)
int sockfd;
char *errmsg;
{
GplusError(sockfd, 1, errmsg, NULL);
#ifdef VMS_SERVER
AbortMsg = NULL;
if (AbortGS)
if (discardAbortGS)
GSdestroy(AbortGS);
AbortGS = NULL;
AbortString = NULL;
discardAbortGS = FALSE;
#endif
}
#ifndef VMS_SERVER
/*
* only works if non-chroot really...
*/
boolean
Setuid_username(username)
char *username;
{
struct passwd *pw;
if (getuid() != 0)
return(FALSE);
pw = getpwnam(username);
if (!pw) {
Debug("Couldn't find user '%s'\n", username);
return(FALSE);
}
if (pw->pw_uid == 0) /* Don't allow this to be used for root access*/
return(FALSE);
if (initgroups(pw->pw_name, pw->pw_gid) != 0 )
return(FALSE);
if (setgid(pw->pw_gid) < 0)
return(FALSE);
if (setuid(pw->pw_uid) < 0)
return(FALSE);
Debug("Successfully changed user privs to '%s'\n", username);
return(TRUE);
}
/*
* is_mail_from_line - Is this a legal unix mail "From " line?
*
* Given a line of input will check to see if it matches the standard
* unix mail "from " header format. Returns 0 if it does and <0 if not.
*
* 2 - Very strict, also checks that each field contains a legal value.
*
* Assumptions: Not having the definitive unix mailbox reference I have
* assumed that unix mailbox headers follow this format:
*
* From <person> <date> <garbage>
*
* Where <person> is the address of the sender, being an ordinary
* string with no white space imbedded in it, and <date> is the date of
* posting, in ctime(3C) format.
*
* This would, on the face of it, seem valid. I (Bernd) have yet to find a
* unix mailbox header which doesn't follow this format.
*
* From: Bernd Wechner (
[email protected])
* Obfuscated by: KFS (as usual)
*/
#define MAX_FIELDS 10
static char legal_day[] = "SunMonTueWedThuFriSat";
static char legal_month[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
static int legal_numbers[] = { 1, 31, 0, 23, 0, 59, 0, 60, 1969, 2199 };
int is_mail_from_line(line)
char *line; /* Line of text to be checked */
{
char *fields[MAX_FIELDS];
char *sender_tail;
register char *lp, **fp;
register int n, i;
if (strncmp(line, "From ", 5)) return -100;
lp = line + 5;
/* sender day mon dd hh:mm:ss year */
for (n = 0, fp = fields; n < MAX_FIELDS; n++) {
while (*lp && *lp != '\n' && isascii(*lp) && isspace(*lp)) lp++;
if (*lp == '\0' || *lp == '\n') break;
*fp++ = lp;
while (*lp && isascii(*lp) && !isspace(*lp))
if (*lp++ == ':' && (n == 4 || n == 5)) break;
if (n == 0) sender_tail = lp;
}
if (n < 8) return -200-n;
fp = fields;
if (n > 8 && !isdigit(fp[7][0])) fp[7] = fp[8]; /* ... TZ year */
if (n > 9 && !isdigit(fp[7][0])) fp[7] = fp[9]; /* ... TZ DST year */
fp++;
for (i = 0; i < 21; i += 3)
if (strncmp(*fp, &legal_day[i], 3) == 0) break;
if (i == 21) return -1;
fp++;
for (i = 0; i < 36; i += 3)
if (strncmp(*fp, &legal_month[i], 3) == 0) break;
if (i == 36) return -2;
for (i = 0; i < 10; i += 2) {
lp = *++fp;
if (!isdigit(*lp)) return -20-i;
n = atoi(lp);
if (n < legal_numbers[i] || legal_numbers[i+1] < n) return -10-i;
}
return 0;
}
Splittype
is_multipartfile(line)
char *line;
{
int success, i;
int numitems = GDCgetNumFileSep(Config);
success = is_mail_from_line(line);
if (success == 0)
return(SPLIT_MAIL);
for (i=0; i< numitems; i++) {
re_comp(GDCgetFileSep(Config, i));
if (re_exec(line))
return(i);
}
return(SPLIT_UNKNOWN);
}
#define NO_SUBJECT "<no subject>"
void
process_mailfile(sockfd, Mailfname)
int sockfd;
char *Mailfname;
{
FILE *Mailfile;
char Zeline[MAXLINE];
char outputline[MAXLINE];
char Title[MAXLINE];
long Startbyte=0, Endbyte=0, Bytecount=0;
boolean foundtitle = 0;
char *p;
Splittype septype = SPLIT_UNKNOWN;
Debug("process_mailfile %s\n",Mailfname);
Mailfile = rfopen(Mailfname, "r");
if ((Mailfile == NULL) || (fgets(Zeline, MAXLINE, Mailfile) == NULL)) {
Abortoutput(sockfd, "Cannot access file");
return;
}
septype = is_multipartfile(Zeline);
Bytecount += strlen(Zeline);
/** Read through the file, finding sections **/
while (fgets(Zeline, MAXLINE, Mailfile) != NULL) {
if (!foundtitle) {
if (septype==SPLIT_MAIL) {
if (strncmp(Zeline,"Subject: ",9)==0) {
foundtitle = TRUE;
/* trim out the white space.. */
p = Zeline + 8;
while ((*p == ' ')||(*p == '\t'))
p++;
if (*p == '\n')
strcpy(Title, NO_SUBJECT);
else
strcpy(Title,p);
ZapCRLF(Title);
Debug("Found title %s\n", Title);
} else if (strcmp(Zeline, "\n")==0) {
foundtitle = TRUE;
strcpy(Title, NO_SUBJECT);
Debugmsg("No subject found - using default\n");
}
} else {
/** Not SPLIT_MAIL **/
strcpy(Title, Zeline);
ZapCRLF(Title);
foundtitle = TRUE;
}
}
else if (is_multipartfile(Zeline) == septype) {
Endbyte = Bytecount;
foundtitle = FALSE;
if (Endbyte != 0) {
sprintf(outputline, "0%s\tR%d-%d-%s\t%s\t%d\r\n",
Title, Startbyte, Bytecount, Mailfname,
Zehostname, GopherPort);
if (writestring(sockfd, outputline) < 0)
gopherd_exit(-1);
Startbyte=Bytecount;
*Title = '\0';
}
}
Bytecount += strlen(Zeline);
}
if (*Title != '\0') {
sprintf(outputline, "0%s\tR%d-%d-%s\t%s\t%d\r\n",
Title, Startbyte, Bytecount, Mailfname,
Zehostname, GopherPort);
if (writestring(sockfd, outputline)<0)
gopherd_exit(-1);
}
}
#endif
/*
* Return the basename of a filename string, i.e. everything
* after the last "/" character.
*/
char *mtm_basename(string)
char *string;
{
static char *buff;
#ifndef VMS_SERVER
buff = string + strlen(string); /* start at last char */
while (*buff != '/' && buff > string)
buff--;
return( (char *) (*buff == '/'? ++buff : buff));
#else
/*
* VMS users return the basename of a filename string, as everything
* after the last "]" character, or ":" if no "]", or as the input
* string if neither.
*/
if(buff = strchr(string,']'))
return(++buff);
else
if (buff = strchr(string,':'))
return(++buff);
else
return(string);
#endif
}
/*
* Cache timeout value.
* If cache is less than secs seconds old, it's ok.
* If cache is newest, it's ok, otherwise it must be rebuilt.
*
*/
boolean
Cachetimedout(cache, secs, dir)
char *cache;
int secs;
char *dir;
{
STATSTR buf;
int result;
time_t now;
result = rstat(cache, &buf);
if (result != 0)
return(TRUE);
time(&now);
Debug("Cache now: %d, ", now);
Debug("cache file: %d\n", buf.st_mtime);
if ( now < (buf.st_mtime + secs))
return(FALSE);
else
return(TRUE);
}
/*
* Returns true (1) for a directory
* false (0) for a file
* -1 for anything else
*/
boolean
isadir(path)
char *path;
{
static STATSTR buf;
int result;
result = rstat(path, &buf);
if (result != 0)
return(-1);
if (S_ISDIR(buf.st_mode)) {
#ifndef VMS_SERVER
char *cp = fixfile(path);
if (! access(cp, F_OK))
return(1);
else
return(-1);
#else
return(1);
#endif
}
else if (S_ISREG(buf.st_mode))
return(0);
else
return(-1);
}
/*
* This function sets the process title given by ps on a lot of BSDish
* systems
*/
#ifdef __STDC__
void
ServerSetArgv(const char *fmt, ...)
#else /* !__STDC__ */
void
ServerSetArgv(fmt, va_alist)
char *fmt;
va_dcl
#endif /* __STDC__ */
{
#ifdef VMS_SERVER
# define SETPROCTITLE
#endif
#if defined(SETPROCTITLE) && !defined(__sgi) && !defined(_AUX_SOURCE) && !defined(sgi) && !defined(USG)
va_list args;
register char *p;
register int i;
char buf[MAXLINE];
#ifdef VMS_SERVER
char var[60];
#endif
Argv[1] = NULL;
# ifdef __STDC__
va_start(args, fmt);
# else /* !__STDC__ */
va_start(args);
# endif /* __STDC__ */
(void) vsprintf(buf, fmt, args);
va_end(args);
#ifdef VMS_SERVER
Debug("ServerSetArgv: %s\n", buf);
#endif
/* make ps print "(gopherd)" */
p = Argv[0];
#ifndef VMS_SERVER
*p++ = '-';
#endif
i = strlen(buf);
if (i > LastArgv - p - 2)
{
i = LastArgv - p - 2;
buf[i] = '\0';
}
(void) strcpy(p, buf);
p += i;
#ifndef VMS_SERVER
while (p < LastArgv)
*p++ = ' ';
#else
*p = '\0';
sprintf(var,"GOPHERD_STATE_%x", getpid());
VMS$SetEnv("LNM$SYSTEM_TABLE",var, buf);
#endif
#endif /* SETPROCTITLE */
;
}
FILE*
Gpopen(sockfd, cmd, rw)
int sockfd;
char *cmd;
char *rw;
{
int inquote = 0;
int insquote = 0;
int i;
FILE *theproc;
/** Strip out the naughty bits.. **/
for (i=0; cmd[i] != '\0'; i++) {
switch (cmd[i]) {
case '"':
if (!insquote)
inquote = 1-inquote;
break;
case '\'':
if (!inquote)
insquote = 1-insquote;
break;
case '&':
case '|':
#ifndef VMS_SERVER
case ';':
case '=':
#endif
case '?':
case '>':
case '(':
case ')':
case '{':
case '}':
#ifndef VMS_SERVER
case '[':
case ']':
#endif
case '^':
/*** Stuff that's okay if quoted.. ***/
#ifdef VMS_SERVER
/*** Additional stuf that's ok if quoted under OpenVMS ***/
case '!':
case '\\':
case '\n':
#endif
if (!inquote && !insquote) {
#ifndef VMS_SERVER
LOGGopher(sockfd, "Possible Security Violation '%s'", cmd);
#else
LOGGopher(sockfd,
"Possible Security Violation @'%.4s<%c>%.4s'",
cmd+i-((i>=4)?4:i), cmd[i],
cmd+i+
(i==strlen(cmd)?0:1));
LOGGopher(sockfd, "unquoted in \"%s\"", cmd);
#endif
return(NULL);
}
break;
#ifndef VMS_SERVER
case '!':
case '\\':
case '`':
case '\n':
case '$':
#else
/*** Hmm... OpenVMS doesn't have so many dangerous escape codes, eh? ***/
#endif
/*** Stuff that shouldn't be in there at all! **/
#ifndef VMS_SERVER
LOGGopher(sockfd, "Possible Security Violation '%s'", cmd);
#else
LOGGopher(sockfd,
"Possible Security Violation @'%.4s<%c>%.4s'",
cmd+i-((i>=4)?4:i), cmd[i],
cmd+i+
(i==strlen(cmd)?0:1));
LOGGopher(sockfd, "in \"%s\"", cmd);
#endif
return(NULL);
break;
}
}
theproc = popen(cmd, rw);
#ifndef VMS_SERVER
if (theproc != NULL)
setvbuf(theproc, NULL, _IOLBF, 0);
#endif
return(theproc);
}
/*
* Set an environment variable
*/
#ifndef VMS_SERVER
void
SetEnvironmentVariable(variable, value)
#else
void
VMS$SetEnv(table, variable, value)
char *table;
#endif
char *variable, *value;
{
char *tmpputenv;
if (variable == NULL)
return;
if (value == NULL)
value = "";
#ifndef VMS_SERVER
tmpputenv = (char*) malloc(strlen(variable) + strlen(value) + 2);
sprintf(tmpputenv, "%s=%s", variable, value);
putenv(tmpputenv);
#else
if (table == NULL)
table = "";
tmpputenv = (char*) malloc(strlen(table)+ 1 + strlen(variable) +
strlen(value) + 2);
sprintf(tmpputenv, "%s%s%s=%s", table, strlen(table)>0?":":"",
variable, value);
putenv(tmpputenv);
free(tmpputenv);
#endif
}
#ifdef VMS_SERVER
/*
* Continuation on .LINKS, lookasides, and the like would really be nice
* so we'll add code to detect a trailing "-", and if the next record in
* the file starts with a space we'll concatenate the records up to a max.
*/
int
VMS$Continuation(char *buf, FILE *file, int max, char cont)
{
int i, posit;
char *cp;
while((buf[i=(strlen(buf)-1)]==cont) && (i>0)) {
posit = ftell(file);
cp=fgets(buf+i,max-i,file);
if (cp==NULL || buf[i]!=' ') {
buf[i++] = cont;
buf[i] = '\0';
fseek(file,posit,0);
return;
}
ZapCRLF(buf);
}
}
/*
* This routine validates a selector path as being a valid VMS file
* specification.
**/
char *
VMS$Validate_Filespec(char *path)
{
struct FAB fab;
struct NAM nam;
static
char expanded[256];
register status;
char *cp;
for(cp = path; *cp; cp++) if (*cp == ' ') break;
fab = cc$rms_fab;
nam = cc$rms_nam;
fab.fab$b_fac = FAB$M_GET;
fab.fab$l_fop = FAB$V_NAM;
fab.fab$l_nam = &nam;
fab.fab$l_dna = GDCgetDatadir(Config);
fab.fab$b_dns = strlen(fab.fab$l_dna);
fab.fab$l_fna = path;
fab.fab$b_fns = (cp - path);
nam.nam$l_esa = expanded;
nam.nam$b_ess = 255;
nam.nam$b_nop = NAM$V_SYNCHK;
expanded[0] = 0;
if ((status = SYS$PARSE(&fab)) != RMS$_NORMAL)
return(NULL);
expanded[nam.nam$b_esl] = '\0';
return(expanded);
}
/*
* Modification of Bruce Tanner's vms_system() function.
* F.Macrides (
[email protected]) -- 08-Jul-1993
*
* This routine permits a server started under Inetd/MULTINET_SERVER
* to spawn subprocesses with the DCL CLI.
*
* The subprocess is created with LOGINOUT.EXE as its image, so that
* it has a DCL CLI, but it has F$MODE() .eqs. "OTHER" and does not
* execute SYS$MANAGER:SYLOGIN.COM (furthermore, MULTINET recommends
* that you explicitly direct an exit at the top of SYLOGIN.COM for
* "OTHER" processes). To pass the subprocess logicals and foreign
* command definitions (most importantly, that for EGREP), you can
* define a command file to be executed before the execution of the
* gopher server's command, using any of these options:
* (1) Define the system logical "GOPHER_LOGIN" to point to the
* command file.
* (2) Set the "SpawnInit" field in the server's configuration
* file so that it points to the command file.
* (3) Define the program logical "LOGINCOM" in CONF.H so that
* it points to the command file instead of to the system
* logical.
*
* The subprocess has its privileges set to only TMPMBX and NETMBX,
* but it will be owned by SYSTEM, which grants it privileges you
* can't totally restrict (e.g., due to ACL settings and rights
* identifiers for SYSTEM). Therefore, if the server is not running
* from Inetd/MULTINET_SERVER, the function reroutes the call to the
* C library's system().
*
* The function talks to the subprocess via sys$qiow()'s to a mailbox,
* and can hang if the subprocess crashes. I therefore check that the
* subprocess is still alive, via a "throwaway" sys$getjpi() call, after
* the server's DCL command has been mailed, and before mailing a suicide
* command. I haven't had any problems since adding this simple trick,
* but someday the function should be rewritten to check event flags.
*/
#include <ssdef.h>
#include <iodef.h>
#include <dvidef.h>
#include <prvdef.h>
#include <jpidef.h>
#define check(status) if ((status & 1) != 1) return (status)
int
VMS$system(char *command)
{
char buf[256], mbx_name[20], *cp, username[12];
long name_len;
unsigned int pid;
short chan;
static int unit;
int status, iosb[2], privs[2] = {PRV$M_NETMBX|PRV$M_TMPMBX, 0};
struct itemlist3 {
short buflen;
short itmcode;
int *bufadr;
short *retadr;
} itmlst[2] = {
{4, DVI$_UNIT, (int *) &unit, 0},
{0, 0, 0, 0}
};
struct {
short buffer_length;
short item_code;
char *buffer_address;
long return_length_address;
long terminator[3];
} itmlstj;
$DESCRIPTOR(d_out, "NL:");
$DESCRIPTOR(d_err, "NL:");
$DESCRIPTOR(d_image, "sys$system:loginout.exe");
struct dsc$descriptor_s
d_input = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
/*
* If we're not under MULTINET_SERVER/Inetd, use system()
*/
if (GDCgetInetdActive(Config)==FALSE)
return(system(command));
/*
* Create a mailbox for passing DCL commands to the subprocess.
*/
status = sys$crembx(0, &chan, 0, 0, 0, 0, 0, 0);
check(status);
/*
* Identify the mailbox for the d_input descriptor.
*/
status = sys$getdviw(0, chan, 0, itmlst, 0, 0, 0, 0);
check(status);
sprintf(mbx_name, "_MBA%d:", unit);
d_input.dsc$w_length = (short) strlen(mbx_name);
d_input.dsc$a_pointer = mbx_name;
/*
* Create the subprocess with only TMPMBX and NETMBX privileges.
*/
status = sys$creprc(&pid, &d_image, &d_input, &d_out, &d_err,
&privs, 0, 0, 4, 0, 0, 0);
check(status);
/*
* The subprocess doesn't execute SYLOGIN.COM, and it's F$MODE()
* is "OTHER", so pass it the EGREP foreign command, and other
* symbols and logicals you want the subprocess to have, via a
* command file. But make sure the subprocess can execute the
* command file.
*/
if (access(GDCgetSpawnInit(Config), 1) == 0) {
sprintf(buf, "$ @%s", GDCgetSpawnInit(Config));
status = sys$qiow(0, chan, IO$_WRITEVBLK, &iosb, 0, 0, buf,
strlen(buf), 0, 0, 0, 0);
check(status);
}
/*
* Mail the server's DCL command to the subprocess.
*/
status = sys$qiow(0, chan, IO$_WRITEVBLK, &iosb, 0, 0, command,
strlen(command), 0, 0, 0, 0);
check(status);
/*
* Wait a second, and use a non-mailbox service to see if the
* command caused the subprocess to crash (so we don't send it
* a suicide note and end up hanging ourselves on the mailbox).
*/
sleep(1);
itmlstj.buffer_length = 12;
itmlstj.item_code = JPI$_USERNAME;
itmlstj.buffer_address = username;
itmlstj.return_length_address = (long) &name_len;
itmlstj.terminator[0] = 0;
itmlstj.terminator[1] = 0;
itmlstj.terminator[2] = 0;
name_len = 0;
status = sys$getjpiw(0, &pid, 0, &itmlstj.buffer_length, &iosb[0], 0, 0);
check(status);
/*
* If the subprocess is still alive, mail it instructions to
* commit suicide (when it's done executing the DCL command).
*/
sprintf(buf, "$ stop/id=%x", pid);
status = sys$qiow(0, chan, IO$_WRITEVBLK, &iosb, 0, 0, buf,
strlen(buf), 0, 0, 0, 0);
check(status);
/*
* Deassign the mailbox channel.
*/
status = sys$dassgn(chan);
check(status);
return SS$_NORMAL;
}
/*
* This routine validates a selector path as being a valid VMS file
* specification.
**/
boolean
ArbitraryDevice(char *type, char *path)
{
return(TRUE);
}
/* Emulate BSD UNIX's re_comp() and re_exec() functions (note: Emulated
right down to their inability to be multi-threaded!) */
static char *re_pattern = NULL;
/*
* re_comp() accepts a potential patter string are compiles it into an
* internal format suitable for pattern matching. It returns
* NULL if successful, a character string containing an error
* message if unsuccessful. Specifying NULL or a zero-length
* string for the pattern causes re_comp() to return without
* changing the precompiled pattern.
*/
char *
re_comp(char *pattern)
{
if (!pattern) return(NULL);
if (strlen(pattern)) return(NULL);
if (re_pattern)
free(re_pattern);
if (!(re_pattern = (char *)malloc(strlen(pattern)+1)))
return("Insufficient memory for pattern");
strcpy(re_pattern, pattern);
return(NULL);
}
/*
* re_exec() accepts a string and compares it to the most recently compiled
* pattern used by re_comp(). It returns 1 if the string
* matches the pattern, 0 if it does not match, and -1 if the
* pattern is not set up properly.
*/
int
re_exec(char *string)
{
int status;
struct dsc$descriptor_s
dsc$string = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0},
dsc$pattern = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
if (!re_pattern)
return(-1);
dsc$pattern.dsc$w_length = (short) strlen(re_pattern);
dsc$pattern.dsc$a_pointer = re_pattern;
dsc$string.dsc$w_length = (short) strlen(string);
dsc$string.dsc$a_pointer = string;
status = str$match_wild(dsc$string, dsc$pattern);
return (((status & 1) != 1) ? 0 : 1);
}
/* Disable all privileges except TMPMBX and NETMBX
** ------------------------------------------------
**
** F.Macrides (
[email protected]) -- 21-Feb-1994
** (based on code from H.Flowers <
[email protected]>)
**
** Note that if an INETD server process is running with a system UIC
** (like the SYSTEM account has), turning off SYSPRV will have little
** effect due to SYSTEM rights identifiers, but we should turn off all
** these other privs, and may as well turn off SYSPRV too.
*/
void
VMS$DisableAllPrivs(void)
{
union prvdef prvadr;
bzero((char *) &prvadr, sizeof(prvadr));
prvadr.prv$v_cmkrnl = 1;
prvadr.prv$v_cmexec = 1;
prvadr.prv$v_sysnam = 1;
prvadr.prv$v_grpnam = 1;
prvadr.prv$v_allspool = 1;
prvadr.prv$v_detach = 1;
prvadr.prv$v_diagnose = 1;
prvadr.prv$v_log_io = 1;
prvadr.prv$v_group = 1;
prvadr.prv$v_noacnt = 1;
prvadr.prv$v_prmceb = 1;
prvadr.prv$v_prmmbx = 1;
prvadr.prv$v_pswapm = 1;
prvadr.prv$v_setpri = 1;
prvadr.prv$v_setprv = 1;
prvadr.prv$v_world = 1;
prvadr.prv$v_mount = 1;
prvadr.prv$v_oper = 1;
prvadr.prv$v_exquota = 1;
prvadr.prv$v_volpro = 1;
prvadr.prv$v_phy_io = 1;
prvadr.prv$v_bugchk = 1;
prvadr.prv$v_prmgbl = 1;
prvadr.prv$v_sysgbl = 1;
prvadr.prv$v_pfnmap = 1;
prvadr.prv$v_shmem = 1;
prvadr.prv$v_sysprv = 1;
prvadr.prv$v_bypass = 1;
prvadr.prv$v_syslck = 1;
prvadr.prv$v_share = 1;
prvadr.prv$v_upgrade = 1;
prvadr.prv$v_downgrade = 1;
prvadr.prv$v_grpprv = 1;
prvadr.prv$v_readall = 1;
prvadr.prv$v_security = 1;
if (SS$_NORMAL != (vaxc$errno = SYS$SETPRV (DEBUG, &prvadr, 0, 0))) {
LOGGopher(-1,"Can't discard PRIVS, %s", STRerror(vaxc$errno));
}
}
/* ROUTINE WWW_to_VMS()
* Replace slash-separated pathspecs with VMS pathspecs.
* F.Macrides (
[email protected]) -- 08-Sep-1994
*
* This routine accepts pathspecs which begin with a slash, and replaces
* all slashes to create a VMS pathspec. If a GType for a directory is
* indicated (A_DIRECTORY or '1') and there is no terminal slash, it will
* append one before performing the conversion.
*
* The sole purpose of this routine is to allow slashes to be substituted
* for ':', ":[", '[' or ']' in the *pathspec* portions of selectors for
* gopher URL's, so that they do not need to be escaped to hex notation.
* It does *not* do a SHELL$ or POSIX conversion from Unix pathspecs, nor
* emulate the pathspec rules for http URL's. All VMSGopherServer rules
* with respect to DataDirectory defaulting, and uses of wildcards and/or
* ellipses still apply. In the pathspec fields of URL's, you simply
* replace the above three characters and/or ":[" string with slashes,
* and add a lead slash if not already present via the substitutions.
*
* You also can substituTe the dots between subdirectories with slashes,
* but the dots in ellipses are associated with the preceding directory
* string and should *not* be replaced. Also, you still must escape
* the pair or colons (':' == %3a) which serve as ARG delimiters in exec
* (A_EVENT, 'e') and search (A_INDEX, '7') selectors, and any spaces
* (' ' == %20) in the selector. E.g.,
* 7[foo...]*.txt can be replaced by:
* 7/foo.../*.txt
* and
* 7:nosort:[foo...]*.txt by:
* 7%3anosort%3a/foo.../*.txt
* and
* 1gopher_root4:[neat.stuff.for.you.to read] by:
* 1/gopher_root4/neat/stuff/for/you/to/read/ or:
* 1/gopher_root4/neat.stuff.for.you.to.read/
*
* Here's a full example of a URL for:
* Type=7
* Path=7[_shell]search.shell gopher_rooti:[foo]foodoc
*
* On the command line it would be:
*
gopher://host/77/_shell/search.shell%20/gopher_rooti/foo/foodoc
*
* In a foo.html would be:
* <A HREF="
gopher://host/77/_shell/search.shell%20/gopher_rooti/foo/foodoc"
* >Search the foo database</A>.
*/
char *
VMS$WWW_to_VMS(char *WWWname, char GType)
{
static char vmsname[256];
char *filename=NULL; /* Working copy of pathspec */
char *second; /* 2nd slash */
char *last; /* last slash */
if(!WWWname) /* Make sure we got a pathspec */
return(NULL);
filename = (char*)malloc(strlen(WWWname)+4);
strcpy(filename, WWWname); /* If a directory, ensure a terminal slash */
if(GType == A_DIRECTORY && filename[strlen(filename)-1] != '/')
strcat(filename, "/");
second = strchr(filename+1, '/'); /* 2nd slash */
last = strrchr(filename, '/'); /* last slash */
if (!second) { /* Only one slash */
sprintf(vmsname, "%s", filename+1);
} else if(second==last) { /* Exactly two slashes */
*second = 0;
sprintf(vmsname, "[%s]%s", filename+1, second+1);
*second = '/';
} else { /* More than two slashes */
char * p;
*second = 0; /* Split disk or dir from rest */
*last = 0; /* Split dir from filename */
if(strncasecmp(filename+1, GDCgetDatadir(Config),
strcspn(GDCgetDatadir(Config),":")))
sprintf(vmsname, "[%s.%s]%s", filename+1, second+1, last+1);
else
sprintf(vmsname, "%s:[%s]%s", filename+1, second+1, last+1);
*second = *last = '/'; /* restore filename */
for (p=strchr(vmsname, '['); *p!=']'; p++)
if (*p=='/') *p='.'; /* Convert dir sep. to dots */
}
free(filename);
return vmsname;
}
/*
Given a format string containing %xx tokens, substitute from the other
parameters the %xx tokens according to their definition. Return pointer
to the static buffer wher we've done this substitution.
*/
int getload();
char *
VMS$FormatTokens(char *fmt, char *Path, off_t *Size, time_t *TStamp, int Port,
char *Host, int Type, char *Admin, CMDobj *Cmd)
{
#define TKN_SZ 0 /* SiZe */
#define TKN_DT 1 /* DaTe only */
#define TKN_FN 2 /* FileName */
#define TKN_PA 3 /* directory PAth only */
#define TKN_DV 4 /* DeVice only */
#define TKN_FQ 5 /* Full-Qualified filespec */
#define TKN_TS 6 /* TimeStamp */
#define TKN_TI 7 /* TIme only */
#define TKN_PT 8 /* PorT */
#define TKN_HO 9 /* HOst name */
#define TKN_LD 10 /* system LoadD */
#define TKN_AM 11 /* effective AdMinistrator */
#define TKN_RQ 12 /* CMDgetData(Cmd) ReQuest */
#define TKN_AT 13 /* Abortoutput() Title */
static char *codes = "%sz%dt%fn%pa%dv%fq%ts%ti%pt%ho%ld%am%rq%at";
/* | | | | | | | | | | | | | |
| | | | | | | | | | | | | Abortoutput
| | | | | | | | | | | | | title
| | | | | | | | | | | | CMDgetData(Cmd)
| | | | | | | | | | | Administrator
| | | | | | | | | | systemload
| | | | | | | | | host
| | | | | | | | port
| | | | | | | time only
| | | | | | timestamp
| | | | | fully-qualified filespec
| | | | device only
| | | directory path only
| | filename only
| date only
size
*/
int l;
extern
double sysload;
static
String *buf = NULL;
char *ThisYear;
char work[60];
time_t now;
struct tm *local;
char code[4];
char *ft;
char *ftx;
char *cp;
int fd;
boolean stats_ok = TRUE;
char date[26];
#define MMM date+4
#define DD date+8
#define HHMMSS date+11
#define YYYY date+20
switch(Type) {
case A_DIRECTORY: /*** It's a directory ***/
case A_INDEX: /*** It's an index ***/
case A_FTP: /*** ftp link ***/
case A_EXEC: /*** exec link ***/
case A_HTML: /*** www link ***/
case A_WAIS: /*** wais or whois link ***/
stats_ok = FALSE;
default: stats_ok = TRUE;
}
if (!strchr(fmt,'%'))
return(fmt);
if (!stats_ok) {
Size = NULL;
TStamp = NULL;
}
if (TStamp) {
strcpy(date,ctime(TStamp));
date[3] = date[7]= date[10]= date[19] = date[24]= '\0';
if (date[8]==' ')
date[8] = '0';
date[0] = '\0';
}
if (!buf)
buf = STRnew();
else
STRset(buf,"");
for (l=0,code[3]='\0'; *fmt!='\0';) {
fd=strcspn(fmt,"%");
if (fd) {
STRncat(buf,fmt,fd);
fmt += fd;
l += fd;
}
if (*fmt=='%') {
strncpy(code,fmt,3);
if (NULL == (ftx=strstr(codes,code))) {
STRncat(buf,fmt++,1);
l++;
continue;
}
fmt += 3;
switch((ftx-codes)/3) {
/*%sz*/case TKN_SZ: if (!Size) break;
work[sprintf(work, (*Size >= 1000)?"%uKB":"%u Bytes",
(*Size >= 1000)?((*Size+1023)/1024)
:*Size)] = '\0';
STRcat(buf, work);
break;
/*%fn*/case TKN_FN: if (!Path) break;
if (cp = strchr(Path,']'))
STRcat(buf, cp+1);
break;
/*%pa*/case TKN_PA: if (!Path) break;
if (cp = strchr(Path,'['))
if (fd=strcspn(cp,"]"))
STRncat(buf, cp, fd+1);
break;
/*%dv*/case TKN_DV: if (!Path) break;
if (fd=strcspn(Path,":"))
STRncat(buf, Path, fd);
break;
/*%fq*/case TKN_FQ: if (!Path) break;
STRcat(buf, Path);
break;
/*%ts*/case TKN_TS: if (!TStamp) break;
now = time(&now);
local = localtime(&now);
ThisYear = asctime(local) + 20;
if (strncmp(YYYY,ThisYear,4)==0) {
work[sprintf(work,"%s-%s %s", DD, MMM, HHMMSS)]='\0';
STRcat(buf, work);
break;
}
/*%dt*/case TKN_DT: if (!TStamp) break;
work[sprintf(work,"%s-%s-%s", DD, MMM, YYYY)] = '\0';
STRcat(buf, work);
break;
/*%ti*/case TKN_TI: if (!TStamp) break;
STRcat(buf, HHMMSS);
break;
/*%pt*/case TKN_PT: if (Port==-1)
work[sprintf(work,"%d", GDCgetPort(Config))] = '\0';
else
work[sprintf(work,"%d",Port)] = '\0';
STRcat(buf, work);
break;
/*%ho*/case TKN_HO: if (!Host)
STRcat(buf,GDCgetHostname(Config));
else
STRcat(buf, Host);
break;
/*%ld*/case TKN_LD: getload();
work[sprintf(work, "%f",sysload)] = '\0';
STRcat(buf, work);
break;
/*%am*/case TKN_AM: if (!Admin)
STRcat(buf, GDCgetAdminEmail(Config));
else
STRcat(buf, Admin);
break;
/*%rq*/case TKN_RQ: if (!Cmd) break;
STRcat(buf, CurrentPeerName);
STRcat(buf, ": <");
STRcat(buf, CMDgetData(Cmd));
STRcat(buf, ">");
break;
/*%at*/case TKN_AT: if (!AbortString) break;
STRcat(buf, AbortString);
break;
}
}
}
return(STRget(buf));
}
void
str_tolower(char *string)
{
char *c2;
for(c2 = string; *c2; c2++)
*c2 = _tolower(*c2);
}
#endif