/******************************************************************** * 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 #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 #else #include #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 #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 * * Where is the address of the sender, being an ordinary * string with no white space imbedded in it, and 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 (bernd@bhpcpd.kembla.oz.au) * 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 "" 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 (MACRIDES@SCI.WFEB.EDU) -- 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 #include #include #include #include #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 (MACRIDES@SCI.WFEB.EDU) -- 21-Feb-1994 ** (based on code from H.Flowers ) ** ** 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 (MACRIDES@SCI.WFEB.EDU) -- 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: * Search the foo database. */ 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 .