#if !defined(lint) && defined(DOSCCS) static char sccsid[] = "@(#)uuxqt.c 5.8.1 (2.11BSD) 1997/10/2"; #endif #include "uucp.h" #include #ifdef NDIR #include "ndir.h" #else #include #endif #include #define BADCHARS "&^|(`\\<>;\"{}\n'" #define RECHECKTIME 60*10 /* 10 minutes */ #define APPCMD(d) {\ char *p;\ for (p = d; *p != '\0';) *cmdp++ = *p++; *cmdp++ = ' '; *cmdp = '\0';} /* * uuxqt will execute commands set up by a uux command, * usually from a remote machine - set by uucp. */ #define NCMDS 50 char *Cmds[NCMDS+1]; int Notify[NCMDS+1]; #define NT_YES 0 /* if should notify on execution */ #define NT_ERR 1 /* if should notify if non-zero exit status (-z equivalent) */ #define NT_NO 2 /* if should not notify ever (-n equivalent) */ extern int Nfiles; int TransferSucceeded = 1; int notiok = 1; int nonzero = 0; struct timeb Now; char PATH[MAXFULLNAME] = "PATH=/bin:/usr/bin:/usr/ucb"; char Shell[MAXFULLNAME]; char HOME[MAXFULLNAME]; extern char **environ; char *nenv[] = { PATH, Shell, HOME, 0 }; /* to remove restrictions from uuxqt * define ALLOK 1 * * to add allowable commands, add to the file CMDFILE * A line of form "PATH=..." changes the search path */ main(argc, argv) char *argv[]; { char xcmd[MAXFULLNAME]; int argnok; int notiflg; char xfile[MAXFULLNAME], user[MAXFULLNAME], buf[BUFSIZ]; char lbuf[MAXFULLNAME]; char cfile[NAMESIZE], dfile[MAXFULLNAME]; char file[NAMESIZE]; char fin[MAXFULLNAME], sysout[NAMESIZE], fout[MAXFULLNAME]; register FILE *xfp, *fp; FILE *dfp; char path[MAXFULLNAME]; char cmd[BUFSIZ]; char *cmdp, prm[1000], *ptr; char *getprm(), *lastpart(); int uid, ret, ret2, badfiles; register int i; int stcico = 0; time_t xstart, xnow; char retstat[30]; char **ep; strcpy(Progname, "uuxqt"); uucpname(Myname); umask(WFMASK); Ofn = 1; Ifn = 0; while (argc>1 && argv[1][0] == '-') { switch(argv[1][1]){ case 'x': chkdebug(); Debug = atoi(&argv[1][2]); if (Debug <= 0) Debug = 1; break; default: fprintf(stderr, "unknown flag %s\n", argv[1]); break; } --argc; argv++; } DEBUG(4, "\n\n** START **\n", CNULL); ret = subchdir(Spool); ASSERT(ret >= 0, "CHDIR FAILED", Spool, ret); strcpy(Wrkdir, Spool); uid = getuid(); guinfo(uid, User, path); setgid(getegid()); setuid(geteuid()); DEBUG(4, "User - %s\n", User); if (ulockf(X_LOCK, X_LOCKTIME) != 0) exit(0); fp = fopen(CMDFILE, "r"); if (fp == NULL) { logent(CANTOPEN, CMDFILE); Cmds[0] = "rmail"; Cmds[1] = "rnews"; Cmds[2] = "ruusend"; Cmds[3] = NULL; goto doprocess; } DEBUG(5, "%s opened\n", CMDFILE); for (i=0; i= 0; --j) if (xcmd[j] == '\n' || xcmd[j] == ' ' || xcmd[j] == '\t') xcmd[j] = '\0'; else break; /* look for imbedded whitespace */ for (; j >= 0; --j) if (xcmd[j] == '\n' || xcmd[j] == ' ' || xcmd[j] == '\t') break; /* skip this entry if it has embedded whitespace */ /* This defends against a bad PATH=, for example */ if (j >= 0) { logent(xcmd, "BAD WHITESPACE"); continue; } if (strncmp(xcmd, "PATH=", 5) == 0) { strcpy(PATH, xcmd); i--; /*kludge */ continue; } DEBUG(5, "xcmd = %s\n", xcmd); if ((ptr = index(xcmd, ',')) != NULL) { *ptr++ = '\0'; if (strncmp(ptr, "Err", 3) == SAME) Notify[i] = NT_ERR; else if (strcmp(ptr, "No") == SAME) Notify[i] = NT_NO; else Notify[i] = NT_YES; } else Notify[i] = NT_YES; if ((Cmds[i] = (char *)malloc((unsigned)(strlen(xcmd)+1))) == NULL) { DEBUG(1, "MALLOC FAILED", CNULL); break; } strcpy(Cmds[i], xcmd); } Cmds[i] = CNULL; fclose(fp); doprocess: (void) sprintf(HOME, "HOME=%s", Spool); (void) sprintf(Shell, "SHELL=%s", SHELL); environ = nenv; /* force use if our environment */ DEBUG(11,"path = %s\n", getenv("PATH")); DEBUG(4, "process %s\n", CNULL); time(&xstart); while (gtxfile(xfile) > 0) { /* if /etc/nologin exists, exit cleanly */ #if defined(BSD4_2) || defined(USG) if (access(NOLOGIN) == 0) { #else !BSD4_2 && ! USG ultouch(); if (nologinflag) { #endif !BSD4_2 && !USG logent(NOLOGIN, "UUXQT SHUTDOWN"); if (Debug) logent("debugging", "continuing anyway"); else break; } DEBUG(4, "xfile - %s\n", xfile); xfp = fopen(subfile(xfile), "r"); ASSERT(xfp != NULL, CANTOPEN, xfile, 0); /* initialize to default */ strcpy(user, User); strcpy(fin, DEVNULL); strcpy(fout, DEVNULL); strcpy(sysout, Myname); badfiles = 0; while (fgets(buf, BUFSIZ, xfp) != NULL) { switch (buf[0]) { case X_USER: sscanf(&buf[1], "%s %s", user, Rmtname); break; case X_RETURNTO: sscanf(&buf[1], "%s", user); break; case X_STDIN: sscanf(&buf[1], "%s", fin); i = expfile(fin); /* rti!trt: do not check permissions of * vanilla spool file */ if (i != 0 && (chkpth("", "", fin) || anyread(fin) != 0)) badfiles = 1; break; case X_STDOUT: sscanf(&buf[1], "%s%s", fout, sysout); sysout[MAXBASENAME] = '\0'; /* rti!trt: do not check permissions of * vanilla spool file. DO check permissions * of writing on a non-vanilla file */ i = 1; if (fout[0] != '~' || prefix(sysout, Myname)) i = expfile(fout); if (i != 0 && (chkpth("", "", fout) || chkperm(fout, (char *)1))) badfiles = 1; break; case X_CMD: strcpy(cmd, &buf[2]); if (*(cmd + strlen(cmd) - 1) == '\n') *(cmd + strlen(cmd) - 1) = '\0'; break; case X_NONOTI: notiok = 0; break; case X_NONZERO: nonzero = 1; break; default: break; } } fclose(xfp); DEBUG(4, "fin - %s, ", fin); DEBUG(4, "fout - %s, ", fout); DEBUG(4, "sysout - %s, ", sysout); DEBUG(4, "user - %s\n", user); DEBUG(4, "cmd - %s\n", cmd); /* command execution */ if (strcmp(fout, DEVNULL) == SAME) strcpy(dfile,DEVNULL); else gename(DATAPRE, sysout, 'O', dfile); /* expand file names where necessary */ expfile(dfile); cmdp = buf; ptr = cmd; xcmd[0] = '\0'; argnok = 0; while ((ptr = getprm(ptr, prm)) != NULL) { if (prm[0] == ';' || prm[0] == '^' || prm[0] == '&' || prm[0] == '|') { xcmd[0] = '\0'; APPCMD(prm); continue; } if ((argnok = argok(xcmd, prm)) != SUCCESS) /* command not valid */ break; if (prm[0] == '~') expfile(prm); APPCMD(prm); } /* * clean up trailing ' ' in command. */ if (cmdp > buf && cmdp[0] == '\0' && cmdp[-1] == ' ') *--cmdp = '\0'; if (argnok || badfiles) { sprintf(lbuf, "%s XQT DENIED", user); logent(cmd, lbuf); DEBUG(4, "bad command %s\n", prm); notify(user, Rmtname, cmd, "DENIED"); goto rmfiles; } sprintf(lbuf, "%s XQT", user); logent(buf, lbuf); DEBUG(4, "cmd %s\n", buf); mvxfiles(xfile); ret = subchdir(XQTDIR); ASSERT(ret >= 0, "CHDIR FAILED", XQTDIR, ret); ret = shio(buf, fin, dfile); sprintf(retstat, "signal %d, exit %d", ret & 0377, (ret>>8) & 0377); if (strcmp(xcmd, "rmail") == SAME) notiok = 0; if (strcmp(xcmd, "rnews") == SAME) nonzero = 1; notiflg = chknotify(xcmd); if (notiok && notiflg != NT_NO && (ret != 0 || (!nonzero && notiflg == NT_YES))) notify(user, Rmtname, cmd, retstat); else if (ret != 0 && strcmp(xcmd, "rmail") == SAME) { /* mail failed - return letter to sender */ #ifdef DANGEROUS /* NOT GUARANTEED SAFE!!! */ if (!nonzero) retosndr(user, Rmtname, fin); #else notify(user, Rmtname, cmd, retstat); #endif sprintf(buf, "%s (%s) from %s!%s", buf, retstat, Rmtname, user); logent("MAIL FAIL", buf); } DEBUG(4, "exit cmd - %d\n", ret); ret2 = subchdir(Spool); ASSERT(ret2 >= 0, "CHDIR FAILED", Spool, ret); rmxfiles(xfile); if (ret != 0) { /* exit status not zero */ dfp = fopen(subfile(dfile), "a"); ASSERT(dfp != NULL, CANTOPEN, dfile, 0); fprintf(dfp, "exit status %d", ret); fclose(dfp); } if (strcmp(fout, DEVNULL) != SAME) { if (prefix(sysout, Myname)) { xmv(dfile, fout); chmod(fout, BASEMODE); } else { char *cp = rindex(user, '!'); gename(CMDPRE, sysout, 'O', cfile); fp = fopen(subfile(cfile), "w"); ASSERT(fp != NULL, "OPEN", cfile, 0); fprintf(fp, "S %s %s %s - %s 0666\n", dfile, fout, cp ? cp : user, lastpart(dfile)); fclose(fp); } } rmfiles: xfp = fopen(subfile(xfile), "r"); ASSERT(xfp != NULL, CANTOPEN, xfile, 0); while (fgets(buf, BUFSIZ, xfp) != NULL) { if (buf[0] != X_RQDFILE) continue; sscanf(&buf[1], "%s", file); unlink(subfile(file)); } unlink(subfile(xfile)); fclose(xfp); /* rescan X. for new work every RECHECKTIME seconds */ time(&xnow); if (xnow > (xstart + RECHECKTIME)) { extern int Nfiles; Nfiles = 0; /*force rescan for new work */ } xstart = xnow; } if (stcico) xuucico(""); cleanup(0); } cleanup(code) int code; { logcls(); rmlock(CNULL); #ifdef VMS /* * Since we run as a BATCH job we must wait for all processes to * to finish */ while(wait(0) != -1) ; #endif VMS exit(code); } /* * get a file to execute * * return codes: 0 - no file | 1 - file to execute */ gtxfile(file) register char *file; { char pre[3]; int rechecked; time_t ystrdy; /* yesterday */ struct stat stbuf; /* for X file age */ pre[0] = XQTPRE; pre[1] = '.'; pre[2] = '\0'; rechecked = 0; retry: if (!gtwrkf(Spool, file)) { if (rechecked) return 0; rechecked = 1; DEBUG(4, "iswrk\n", CNULL); if (!iswrk(file, "get", Spool, pre)) return 0; } DEBUG(4, "file - %s\n", file); /* skip spurious subdirectories */ if (strcmp(pre, file) == SAME) goto retry; if (gotfiles(file)) return 1; /* check for old X. file with no work files and remove them. */ if (Nfiles > LLEN/2) { time(&ystrdy); ystrdy -= (4 * 3600L); /* 4 hours ago */ DEBUG(4, "gtxfile: Nfiles > LLEN/2\n", CNULL); while (gtwrkf(Spool, file) && !gotfiles(file)) { if (stat(subfile(file), &stbuf) == 0) if (stbuf.st_mtime <= ystrdy) { char *bnp, cfilename[NAMESIZE]; DEBUG(4, "gtxfile: move %s to CORRUPT \n", file); unlink(subfile(file)); bnp = rindex(subfile(file), '/'); sprintf(cfilename, "%s/%s", CORRUPT, bnp ? bnp + 1 : subfile(file)); xmv(subfile(file), cfilename); logent(file, "X. FILE CORRUPTED"); } } DEBUG(4, "iswrk\n", CNULL); if (!iswrk(file, "get", Spool, pre)) return 0; } goto retry; } /* * check for needed files * * return codes: 0 - not ready | 1 - all files ready */ gotfiles(file) register char *file; { struct stat stbuf; register FILE *fp; char buf[BUFSIZ], rqfile[MAXFULLNAME]; fp = fopen(subfile(file), "r"); if (fp == NULL) return 0; while (fgets(buf, BUFSIZ, fp) != NULL) { DEBUG(4, "%s\n", buf); if (buf[0] != X_RQDFILE) continue; sscanf(&buf[1], "%s", rqfile); expfile(rqfile); if (stat(subfile(rqfile), &stbuf) == -1) { fclose(fp); return 0; } } fclose(fp); return 1; } /* * remove execute files to x-directory */ rmxfiles(xfile) register char *xfile; { register FILE *fp; char buf[BUFSIZ], file[NAMESIZE], tfile[NAMESIZE]; char tfull[MAXFULLNAME]; if((fp = fopen(subfile(xfile), "r")) == NULL) return; while (fgets(buf, BUFSIZ, fp) != NULL) { if (buf[0] != X_RQDFILE) continue; if (sscanf(&buf[1], "%s%s", file, tfile) < 2) continue; sprintf(tfull, "%s/%s", XQTDIR, tfile); unlink(subfile(tfull)); } fclose(fp); return; } /* * move execute files to x-directory */ mvxfiles(xfile) char *xfile; { register FILE *fp; char buf[BUFSIZ], ffile[MAXFULLNAME], tfile[NAMESIZE]; char tfull[MAXFULLNAME]; int ret; if((fp = fopen(subfile(xfile), "r")) == NULL) return; while (fgets(buf, BUFSIZ, fp) != NULL) { if (buf[0] != X_RQDFILE) continue; if (sscanf(&buf[1], "%s%s", ffile, tfile) < 2) continue; expfile(ffile); sprintf(tfull, "%s/%s", XQTDIR, tfile); unlink(subfile(tfull)); ret = xmv(ffile, tfull); ASSERT(ret == 0, "XQTDIR ERROR", CNULL, ret); } fclose(fp); } /* * check for valid command/argument * *NOTE - side effect is to set xc to the command to be executed. * * return 0 - ok | 1 nok */ argok(xc, cmd) register char *xc, *cmd; { register char **ptr; #ifndef ALLOK if (strpbrk(cmd, BADCHARS) != NULL) { DEBUG(1,"MAGIC CHARACTER FOUND\n", CNULL); logent(cmd, "NASTY MAGIC CHARACTER FOUND"); return FAIL; } #endif !ALLOK if (xc[0] != '\0') return SUCCESS; #ifndef ALLOK ptr = Cmds; DEBUG(9, "Compare %s and\n", cmd); while(*ptr != NULL) { DEBUG(9, "\t%s\n", *ptr); if (strcmp(cmd, *ptr) == SAME) break; ptr++; } if (*ptr == NULL) { DEBUG(1,"COMMAND NOT FOUND\n", CNULL); return FAIL; } #endif strcpy(xc, cmd); DEBUG(9, "MATCHED %s\n", xc); return SUCCESS; } /* * if notification should be sent for successful execution of cmd * * return NT_YES - do notification * NT_ERR - do notification if exit status != 0 * NT_NO - don't do notification ever */ chknotify(cmd) char *cmd; { register char **ptr; register int *nptr; ptr = Cmds; nptr = Notify; while (*ptr != NULL) { if (strcmp(cmd, *ptr) == SAME) return *nptr; ptr++; nptr++; } return NT_YES; /* "shouldn't happen" */ } /* * send mail to user giving execution results */ notify(user, rmt, cmd, str) char *user, *rmt, *cmd, *str; { char text[MAXFULLNAME]; char ruser[MAXFULLNAME]; if (strpbrk(user, BADCHARS) != NULL) { char lbuf[MAXFULLNAME]; sprintf(lbuf, "%s INVALID CHARACTER IN USERNAME", user); logent(cmd, lbuf); strcpy(user, "postmaster"); } sprintf(text, "uuxqt cmd (%s) status (%s)", cmd, str); if (prefix(rmt, Myname)) strcpy(ruser, user); else sprintf(ruser, "%s!%s", rmt, user); mailst(ruser, text, CNULL); } /* * return mail to sender * */ retosndr(user, rmt, file) char *user, *rmt, *file; { char ruser[MAXFULLNAME]; if (strpbrk(user, BADCHARS) != NULL) { char lbuf[MAXFULLNAME]; sprintf(lbuf, "%s INVALID CHARACTER IN USERNAME", user); logent(file, lbuf); strcpy(user, "postmaster"); } if (strcmp(rmt, Myname) == SAME) strcpy(ruser, user); else sprintf(ruser, "%s!%s", rmt, user); if (anyread(file) == 0) mailst(ruser, "Mail failed. Letter returned to sender.\n", file); else mailst(ruser, "Mail failed. Letter returned to sender.\n", CNULL); return; } /* * execute shell of command with fi and fo as standard input/output */ shio(cmd, fi, fo) char *cmd, *fi, *fo; { int status, f; int uid, pid, ret; char path[MAXFULLNAME]; char *args[20]; extern int errno; if (fi == NULL) fi = DEVNULL; if (fo == NULL) fo = DEVNULL; getargs(cmd, args, 20); DEBUG(3, "shio - %s\n", cmd); #ifdef SIGCHLD signal(SIGCHLD, SIG_IGN); #endif SIGCHLD if ((pid = fork()) == 0) { signal(SIGINT, SIG_IGN); signal(SIGHUP, SIG_IGN); signal(SIGQUIT, SIG_IGN); close(Ifn); close(Ofn); close(0); setuid(getuid()); f = open(subfile(fi), 0); if (f != 0) { logent(fi, "CAN'T READ"); exit(-errno); } close(1); f = creat(subfile(fo), 0666); if (f != 1) { logent(fo, "CAN'T WRITE"); exit(-errno); } execvp(args[0], args); exit(100+errno); } while ((ret = wait(&status)) != pid && ret != -1) ; DEBUG(3, "status %d\n", status); return status; }