1 #ifndef lint 2 static char sccsid[] = "@(#)uuxqt.c 5.5 (Berkeley) 04/10/85"; 3 #endif 4 5 #include "uucp.h" 6 #include <sys/stat.h> 7 #ifdef NDIR 8 #include "ndir.h" 9 #else 10 #include <sys/dir.h> 11 #endif 12 #include <signal.h> 13 14 #define BADCHARS "&^|(`\\<>;\"{}\n'" 15 #define RECHECKTIME 60*10 /* 10 minutes */ 16 17 #define APPCMD(d) {\ 18 char *p;\ 19 for (p = d; *p != '\0';) *cmdp++ = *p++; *cmdp++ = ' '; *cmdp = '\0';} 20 21 /* 22 * uuxqt will execute commands set up by a uux command, 23 * usually from a remote machine - set by uucp. 24 */ 25 26 #define NCMDS 50 27 char *Cmds[NCMDS+1]; 28 int Notify[NCMDS+1]; 29 #define NT_YES 0 /* if should notify on execution */ 30 #define NT_ERR 1 /* if should notify if non-zero exit status (-z equivalent) */ 31 #define NT_NO 2 /* if should not notify ever (-n equivalent) */ 32 33 extern int Nfiles; 34 char *strpbrk(); 35 36 int TransferSucceeded = 1; 37 int notiok = 1; 38 int nonzero = 0; 39 40 char PATH[MAXFULLNAME] = "PATH=/bin:/usr/bin:/usr/ucb"; 41 char Shell[MAXFULLNAME]; 42 char HOME[MAXFULLNAME]; 43 44 extern char **environ; 45 char *nenv[] = { 46 PATH, 47 Shell, 48 HOME, 49 0 50 }; 51 52 /* to remove restrictions from uuxqt 53 * define ALLOK 1 54 * 55 * to add allowable commands, add to the file CMDFILE 56 * A line of form "PATH=..." changes the search path 57 */ 58 main(argc, argv) 59 char *argv[]; 60 { 61 char xcmd[MAXFULLNAME]; 62 int argnok; 63 int notiflg; 64 char xfile[MAXFULLNAME], user[MAXFULLNAME], buf[BUFSIZ]; 65 char lbuf[MAXFULLNAME]; 66 char cfile[NAMESIZE], dfile[MAXFULLNAME]; 67 char file[NAMESIZE]; 68 char fin[MAXFULLNAME], sysout[NAMESIZE], fout[MAXFULLNAME]; 69 register FILE *xfp, *fp; 70 FILE *dfp; 71 char path[MAXFULLNAME]; 72 char cmd[BUFSIZ]; 73 char *cmdp, prm[1000], *ptr; 74 char *getprm(), *lastpart(); 75 int uid, ret, ret2, badfiles; 76 register int i; 77 int stcico = 0; 78 time_t xstart, xnow; 79 char retstat[30]; 80 char **ep; 81 82 strcpy(Progname, "uuxqt"); 83 uucpname(Myname); 84 85 umask(WFMASK); 86 Ofn = 1; 87 Ifn = 0; 88 while (argc>1 && argv[1][0] == '-') { 89 switch(argv[1][1]){ 90 case 'x': 91 chkdebug(); 92 Debug = atoi(&argv[1][2]); 93 if (Debug <= 0) 94 Debug = 1; 95 break; 96 default: 97 fprintf(stderr, "unknown flag %s\n", argv[1]); 98 break; 99 } 100 --argc; argv++; 101 } 102 103 DEBUG(4, "\n\n** START **\n", CNULL); 104 ret = subchdir(Spool); 105 ASSERT(ret >= 0, "CHDIR FAILED", Spool, ret); 106 strcpy(Wrkdir, Spool); 107 uid = getuid(); 108 guinfo(uid, User, path); 109 setgid(getegid()); 110 setuid(geteuid()); 111 112 DEBUG(4, "User - %s\n", User); 113 if (ulockf(X_LOCK, (time_t) X_LOCKTIME) != 0) 114 exit(0); 115 116 fp = fopen(CMDFILE, "r"); 117 if (fp == NULL) { 118 logent(CANTOPEN, CMDFILE); 119 Cmds[0] = "rmail"; 120 Cmds[1] = "rnews"; 121 Cmds[2] = "ruusend"; 122 Cmds[3] = NULL; 123 goto doprocess; 124 } 125 DEBUG(5, "%s opened\n", CMDFILE); 126 for (i=0; i<NCMDS && cfgets(xcmd, sizeof(xcmd), fp) != NULL; i++) { 127 int j; 128 /* strip trailing whitespace */ 129 for (j = strlen(xcmd)-1; j >= 0; --j) 130 if (xcmd[j] == '\n' || xcmd[j] == ' ' || xcmd[j] == '\t') 131 xcmd[j] = '\0'; 132 else 133 break; 134 /* look for imbedded whitespace */ 135 for (; j >= 0; --j) 136 if (xcmd[j] == '\n' || xcmd[j] == ' ' || xcmd[j] == '\t') 137 break; 138 /* skip this entry if it has embedded whitespace */ 139 /* This defends against a bad PATH=, for example */ 140 if (j >= 0) { 141 logent(xcmd, "BAD WHITESPACE"); 142 continue; 143 } 144 if (strncmp(xcmd, "PATH=", 5) == 0) { 145 strcpy(PATH, xcmd); 146 i--; /*kludge */ 147 continue; 148 } 149 DEBUG(5, "xcmd = %s\n", xcmd); 150 151 if ((ptr = index(xcmd, ',')) != NULL) { 152 *ptr++ = '\0'; 153 if (strncmp(ptr, "Err", 3) == SAME) 154 Notify[i] = NT_ERR; 155 else if (strcmp(ptr, "No") == SAME) 156 Notify[i] = NT_NO; 157 else 158 Notify[i] = NT_YES; 159 } else 160 Notify[i] = NT_YES; 161 if ((Cmds[i] = malloc((unsigned)(strlen(xcmd)+1))) == NULL) { 162 DEBUG(1, "MALLOC FAILED", CNULL); 163 break; 164 } 165 strcpy(Cmds[i], xcmd); 166 } 167 Cmds[i] = CNULL; 168 fclose(fp); 169 170 doprocess: 171 172 (void) sprintf(HOME, "HOME=%s", Spool); 173 (void) sprintf(Shell, "SHELL=%s", SHELL); 174 environ = nenv; /* force use if our environment */ 175 176 DEBUG(11,"path = %s\n", getenv("PATH")); 177 178 DEBUG(4, "process %s\n", CNULL); 179 time(&xstart); 180 while (gtxfile(xfile) > 0) { 181 ultouch(); 182 /* if /etc/nologin exists, exit cleanly */ 183 if (nologinflag) { 184 logent(NOLOGIN, "UUXQT SHUTDOWN"); 185 if (Debug) 186 logent("debugging", "continuing anyway"); 187 else 188 break; 189 } 190 DEBUG(4, "xfile - %s\n", xfile); 191 192 xfp = fopen(subfile(xfile), "r"); 193 ASSERT(xfp != NULL, CANTOPEN, xfile, 0); 194 195 /* initialize to default */ 196 strcpy(user, User); 197 strcpy(fin, DEVNULL); 198 strcpy(fout, DEVNULL); 199 sprintf(sysout, "%.7s", Myname); 200 badfiles = 0; 201 while (fgets(buf, BUFSIZ, xfp) != NULL) { 202 switch (buf[0]) { 203 case X_USER: 204 sscanf(&buf[1], "%s %s", user, Rmtname); 205 break; 206 case X_RETURNTO: 207 sscanf(&buf[1], "%s", user); 208 break; 209 case X_STDIN: 210 sscanf(&buf[1], "%s", fin); 211 i = expfile(fin); 212 /* rti!trt: do not check permissions of 213 * vanilla spool file */ 214 if (i != 0 215 && (chkpth("", "", fin) || anyread(fin) != 0)) 216 badfiles = 1; 217 break; 218 case X_STDOUT: 219 sscanf(&buf[1], "%s%s", fout, sysout); 220 sysout[7] = '\0'; 221 /* rti!trt: do not check permissions of 222 * vanilla spool file. DO check permissions 223 * of writing on a non-vanilla file */ 224 i = 1; 225 if (fout[0] != '~' || prefix(sysout, Myname)) 226 i = expfile(fout); 227 if (i != 0 228 && (chkpth("", "", fout) 229 || chkperm(fout, (char *)1))) 230 badfiles = 1; 231 break; 232 case X_CMD: 233 strcpy(cmd, &buf[2]); 234 if (*(cmd + strlen(cmd) - 1) == '\n') 235 *(cmd + strlen(cmd) - 1) = '\0'; 236 break; 237 case X_NONOTI: 238 notiok = 0; 239 break; 240 case X_NONZERO: 241 nonzero = 1; 242 break; 243 default: 244 break; 245 } 246 } 247 248 fclose(xfp); 249 DEBUG(4, "fin - %s, ", fin); 250 DEBUG(4, "fout - %s, ", fout); 251 DEBUG(4, "sysout - %s, ", sysout); 252 DEBUG(4, "user - %s\n", user); 253 DEBUG(4, "cmd - %s\n", cmd); 254 255 /* command execution */ 256 if (strcmp(fout, DEVNULL) == SAME) 257 strcpy(dfile,DEVNULL); 258 else 259 gename(DATAPRE, sysout, 'O', dfile); 260 261 /* expand file names where necessary */ 262 expfile(dfile); 263 cmdp = buf; 264 ptr = cmd; 265 xcmd[0] = '\0'; 266 argnok = 0; 267 while ((ptr = getprm(ptr, prm)) != NULL) { 268 if (prm[0] == ';' || prm[0] == '^' 269 || prm[0] == '&' || prm[0] == '|') { 270 xcmd[0] = '\0'; 271 APPCMD(prm); 272 continue; 273 } 274 275 if ((argnok = argok(xcmd, prm)) != SUCCESS) 276 /* command not valid */ 277 break; 278 279 if (prm[0] == '~') 280 expfile(prm); 281 APPCMD(prm); 282 } 283 /* 284 * clean up trailing ' ' in command. 285 */ 286 if (cmdp > buf && cmdp[0] == '\0' && cmdp[-1] == ' ') 287 *--cmdp = '\0'; 288 if (strpbrk(user, BADCHARS) != NULL) { 289 sprintf(lbuf, "%s INVALID CHARACTER IN USERNAME", user); 290 logent(cmd, lbuf); 291 strcpy(user, "postmaster"); 292 } 293 if (argnok || badfiles) { 294 sprintf(lbuf, "%s XQT DENIED", user); 295 logent(cmd, lbuf); 296 DEBUG(4, "bad command %s\n", prm); 297 notify(user, Rmtname, cmd, "DENIED"); 298 goto rmfiles; 299 } 300 sprintf(lbuf, "%s XQT", user); 301 logent(buf, lbuf); 302 DEBUG(4, "cmd %s\n", buf); 303 304 mvxfiles(xfile); 305 ret = subchdir(XQTDIR); 306 ASSERT(ret >= 0, "CHDIR FAILED", XQTDIR, ret); 307 ret = shio(buf, fin, dfile); 308 sprintf(retstat, "signal %d, exit %d", ret & 0377, 309 (ret>>8) & 0377); 310 if (strcmp(xcmd, "rmail") == SAME) 311 notiok = 0; 312 if (strcmp(xcmd, "rnews") == SAME) 313 nonzero = 1; 314 notiflg = chknotify(xcmd); 315 if (notiok && notiflg != NT_NO && 316 (ret != 0 || (!nonzero && notiflg == NT_YES))) 317 notify(user, Rmtname, cmd, retstat); 318 else if (ret != 0 && strcmp(xcmd, "rmail") == SAME) { 319 /* mail failed - return letter to sender */ 320 #ifdef DANGEROUS 321 /* NOT GUARANTEED SAFE!!! */ 322 if (!nonzero) 323 retosndr(user, Rmtname, fin); 324 #else 325 notify(user, Rmtname, cmd, retstat); 326 #endif 327 sprintf(buf, "%s (%s) from %s!%s", buf, retstat, Rmtname, user); 328 logent("MAIL FAIL", buf); 329 } 330 DEBUG(4, "exit cmd - %d\n", ret); 331 ret2 = subchdir(Spool); 332 ASSERT(ret2 >= 0, "CHDIR FAILED", Spool, ret); 333 rmxfiles(xfile); 334 if (ret != 0) { 335 /* exit status not zero */ 336 dfp = fopen(subfile(dfile), "a"); 337 ASSERT(dfp != NULL, CANTOPEN, dfile, 0); 338 fprintf(dfp, "exit status %d", ret); 339 fclose(dfp); 340 } 341 if (strcmp(fout, DEVNULL) != SAME) { 342 if (prefix(sysout, Myname)) { 343 xmv(dfile, fout); 344 chmod(fout, BASEMODE); 345 } else { 346 char *cp = rindex(user, '!'); 347 gename(CMDPRE, sysout, 'O', cfile); 348 fp = fopen(subfile(cfile), "w"); 349 ASSERT(fp != NULL, "OPEN", cfile, 0); 350 fprintf(fp, "S %s %s %s - %s 0666\n", dfile, 351 fout, cp ? cp : user, lastpart(dfile)); 352 fclose(fp); 353 } 354 } 355 rmfiles: 356 xfp = fopen(subfile(xfile), "r"); 357 ASSERT(xfp != NULL, CANTOPEN, xfile, 0); 358 while (fgets(buf, BUFSIZ, xfp) != NULL) { 359 if (buf[0] != X_RQDFILE) 360 continue; 361 sscanf(&buf[1], "%s", file); 362 unlink(subfile(file)); 363 } 364 unlink(subfile(xfile)); 365 fclose(xfp); 366 367 /* rescan X. for new work every RECHECKTIME seconds */ 368 time(&xnow); 369 if (xnow > (xstart + RECHECKTIME)) { 370 extern int Nfiles; 371 Nfiles = 0; /*force rescan for new work */ 372 } 373 xstart = xnow; 374 } 375 376 if (stcico) 377 xuucico(""); 378 cleanup(0); 379 } 380 381 382 cleanup(code) 383 int code; 384 { 385 logcls(); 386 rmlock(CNULL); 387 #ifdef VMS 388 /* 389 * Since we run as a BATCH job we must wait for all processes to 390 * to finish 391 */ 392 while(wait(0) != -1) 393 ; 394 #endif VMS 395 exit(code); 396 } 397 398 399 /* 400 * get a file to execute 401 * 402 * return codes: 0 - no file | 1 - file to execute 403 */ 404 405 gtxfile(file) 406 register char *file; 407 { 408 char pre[3]; 409 int rechecked; 410 time_t ystrdy; /* yesterday */ 411 extern time_t time(); 412 struct stat stbuf; /* for X file age */ 413 414 pre[0] = XQTPRE; 415 pre[1] = '.'; 416 pre[2] = '\0'; 417 rechecked = 0; 418 retry: 419 if (!gtwrkf(Spool, file)) { 420 if (rechecked) 421 return 0; 422 rechecked = 1; 423 DEBUG(4, "iswrk\n", CNULL); 424 if (!iswrk(file, "get", Spool, pre)) 425 return 0; 426 } 427 DEBUG(4, "file - %s\n", file); 428 /* skip spurious subdirectories */ 429 if (strcmp(pre, file) == SAME) 430 goto retry; 431 if (gotfiles(file)) 432 return 1; 433 /* check for old X. file with no work files and remove them. */ 434 if (Nfiles > LLEN/2) { 435 time(&ystrdy); 436 ystrdy -= (4 * 3600L); /* 4 hours ago */ 437 DEBUG(4, "gtxfile: Nfiles > LLEN/2\n", CNULL); 438 while (gtwrkf(Spool, file) && !gotfiles(file)) { 439 if (stat(subfile(file), &stbuf) == 0) 440 if (stbuf.st_mtime <= ystrdy) { 441 char *bnp, cfilename[NAMESIZE]; 442 DEBUG(4, "gtxfile: move %s to CORRUPT \n", file); 443 unlink(subfile(file)); 444 bnp = rindex(subfile(file), '/'); 445 sprintf(cfilename, "%s/%s", CORRUPT, 446 bnp ? bnp + 1 : subfile(file)); 447 xmv(subfile(file), cfilename); 448 logent(file, "X. FILE CORRUPTED"); 449 } 450 } 451 DEBUG(4, "iswrk\n", CNULL); 452 if (!iswrk(file, "get", Spool, pre)) 453 return 0; 454 } 455 goto retry; 456 } 457 458 /* 459 * check for needed files 460 * 461 * return codes: 0 - not ready | 1 - all files ready 462 */ 463 464 gotfiles(file) 465 register char *file; 466 { 467 struct stat stbuf; 468 register FILE *fp; 469 char buf[BUFSIZ], rqfile[MAXFULLNAME]; 470 471 fp = fopen(subfile(file), "r"); 472 if (fp == NULL) 473 return 0; 474 475 while (fgets(buf, BUFSIZ, fp) != NULL) { 476 DEBUG(4, "%s\n", buf); 477 if (buf[0] != X_RQDFILE) 478 continue; 479 sscanf(&buf[1], "%s", rqfile); 480 expfile(rqfile); 481 if (stat(subfile(rqfile), &stbuf) == -1) { 482 fclose(fp); 483 return 0; 484 } 485 } 486 487 fclose(fp); 488 return 1; 489 } 490 491 492 /* 493 * remove execute files to x-directory 494 */ 495 496 rmxfiles(xfile) 497 register char *xfile; 498 { 499 register FILE *fp; 500 char buf[BUFSIZ], file[NAMESIZE], tfile[NAMESIZE]; 501 char tfull[MAXFULLNAME]; 502 503 if((fp = fopen(subfile(xfile), "r")) == NULL) 504 return; 505 506 while (fgets(buf, BUFSIZ, fp) != NULL) { 507 if (buf[0] != X_RQDFILE) 508 continue; 509 if (sscanf(&buf[1], "%s%s", file, tfile) < 2) 510 continue; 511 sprintf(tfull, "%s/%s", XQTDIR, tfile); 512 unlink(subfile(tfull)); 513 } 514 fclose(fp); 515 return; 516 } 517 518 519 /* 520 * move execute files to x-directory 521 */ 522 523 mvxfiles(xfile) 524 char *xfile; 525 { 526 register FILE *fp; 527 char buf[BUFSIZ], ffile[MAXFULLNAME], tfile[NAMESIZE]; 528 char tfull[MAXFULLNAME]; 529 int ret; 530 531 if((fp = fopen(subfile(xfile), "r")) == NULL) 532 return; 533 534 while (fgets(buf, BUFSIZ, fp) != NULL) { 535 if (buf[0] != X_RQDFILE) 536 continue; 537 if (sscanf(&buf[1], "%s%s", ffile, tfile) < 2) 538 continue; 539 expfile(ffile); 540 sprintf(tfull, "%s/%s", XQTDIR, tfile); 541 unlink(subfile(tfull)); 542 ret = xmv(ffile, tfull); 543 ASSERT(ret == 0, "XQTDIR ERROR", CNULL, ret); 544 } 545 fclose(fp); 546 } 547 548 /* 549 * check for valid command/argument 550 * *NOTE - side effect is to set xc to the command to be executed. 551 * 552 * return 0 - ok | 1 nok 553 */ 554 555 argok(xc, cmd) 556 register char *xc, *cmd; 557 { 558 register char **ptr; 559 560 #ifndef ALLOK 561 if (strpbrk(cmd, BADCHARS) != NULL) { 562 DEBUG(1,"MAGIC CHARACTER FOUND\n", CNULL); 563 logent(cmd, "NASTY MAGIC CHARACTER FOUND"); 564 return FAIL; 565 } 566 #endif !ALLOK 567 568 if (xc[0] != '\0') 569 return SUCCESS; 570 571 #ifndef ALLOK 572 ptr = Cmds; 573 DEBUG(9, "Compare %s and\n", cmd); 574 while(*ptr != NULL) { 575 DEBUG(9, "\t%s\n", *ptr); 576 if (strcmp(cmd, *ptr) == SAME) 577 break; 578 ptr++; 579 } 580 if (*ptr == NULL) { 581 DEBUG(1,"COMMAND NOT FOUND\n", CNULL); 582 return FAIL; 583 } 584 #endif 585 strcpy(xc, cmd); 586 DEBUG(9, "MATCHED %s\n", xc); 587 return SUCCESS; 588 } 589 590 591 /* 592 * if notification should be sent for successful execution of cmd 593 * 594 * return NT_YES - do notification 595 * NT_ERR - do notification if exit status != 0 596 * NT_NO - don't do notification ever 597 */ 598 599 chknotify(cmd) 600 char *cmd; 601 { 602 register char **ptr; 603 register int *nptr; 604 605 ptr = Cmds; 606 nptr = Notify; 607 while (*ptr != NULL) { 608 if (strcmp(cmd, *ptr) == SAME) 609 return *nptr; 610 ptr++; 611 nptr++; 612 } 613 return NT_YES; /* "shouldn't happen" */ 614 } 615 616 617 618 /* 619 * send mail to user giving execution results 620 */ 621 622 notify(user, rmt, cmd, str) 623 char *user, *rmt, *cmd, *str; 624 { 625 char text[MAXFULLNAME]; 626 char ruser[MAXFULLNAME]; 627 628 sprintf(text, "uuxqt cmd (%s) status (%s)", cmd, str); 629 if (prefix(rmt, Myname)) 630 strcpy(ruser, user); 631 else 632 sprintf(ruser, "%s!%s", rmt, user); 633 mailst(ruser, text, CNULL); 634 return; 635 } 636 637 /* 638 * return mail to sender 639 * 640 */ 641 642 retosndr(user, rmt, file) 643 char *user, *rmt, *file; 644 { 645 char ruser[MAXFULLNAME]; 646 647 if (strcmp(rmt, Myname) == SAME) 648 strcpy(ruser, user); 649 else 650 sprintf(ruser, "%s!%s", rmt, user); 651 652 if (anyread(file) == 0) 653 mailst(ruser, "Mail failed. Letter returned to sender.\n", file); 654 else 655 mailst(ruser, "Mail failed. Letter returned to sender.\n", CNULL); 656 return; 657 } 658 659 /* 660 * this is like index, but takes a string as the second argument 661 */ 662 char * 663 strpbrk(str, chars) 664 register char *str, *chars; 665 { 666 register char *cp; 667 668 do { 669 cp = chars - 1; 670 while (*++cp) { 671 if (*str == *cp) 672 return str; 673 } 674 } while (*str++); 675 return NULL; 676 } 677 678 /* 679 * execute shell of command with fi and fo as standard input/output 680 */ 681 682 shio(cmd, fi, fo) 683 char *cmd, *fi, *fo; 684 { 685 int status, f; 686 int uid, pid, ret; 687 char path[MAXFULLNAME]; 688 char *args[20]; 689 extern int errno; 690 691 if (fi == NULL) 692 fi = DEVNULL; 693 if (fo == NULL) 694 fo = DEVNULL; 695 696 getargs(cmd, args, 20); 697 DEBUG(3, "shio - %s\n", cmd); 698 #ifdef SIGCHLD 699 signal(SIGCHLD, SIG_IGN); 700 #endif SIGCHLD 701 if ((pid = fork()) == 0) { 702 signal(SIGINT, SIG_IGN); 703 signal(SIGHUP, SIG_IGN); 704 signal(SIGQUIT, SIG_IGN); 705 signal(SIGKILL, SIG_IGN); 706 close(Ifn); 707 close(Ofn); 708 close(0); 709 setuid(getuid()); 710 f = open(subfile(fi), 0); 711 if (f != 0) { 712 logent(fi, "CAN'T READ"); 713 exit(-errno); 714 } 715 close(1); 716 f = creat(subfile(fo), 0666); 717 if (f != 1) { 718 logent(fo, "CAN'T WRITE"); 719 exit(-errno); 720 } 721 execvp(args[0], args); 722 exit(100+errno); 723 } 724 while ((ret = wait(&status)) != pid && ret != -1) 725 ; 726 DEBUG(3, "status %d\n", status); 727 return status; 728 } 729