/* $Id: kicker.mc,v 1.26 2008/12/13 20:59:28 ksb Exp $ * Expand local paths and meta stuff, then the code we need. */ %static char acShell[] = "%D/sh/pv"%; #if USE_BATCH %static char acBatch[] = "%D/batch/pv"%; #else %static char acBatch[] = "%D/at/pv"%; #endif %static char acInstall[] = "%D/install/pv"%; static char acLib[] = KICKER_LIB; static char acQClue[] = KICKER_QCLUE; static char *apcShell[] = { "sh", (char *)0 }; static char acTimeSpec[] = #if ADD_TIMESPEC " now" #else "" #endif ; #if USE_FTS #include #else #include #endif /* explain our default library path, which is not something we (ksb) * can (or should) change at run-time. */ static void Version() { register char *pcNote; auto struct stat stLib; pcNote = ""; if (-1 == stat(acLib, & stLib)) { pcNote = " [nonexistent]"; } else if (! S_ISDIR(stLib.st_mode)) { pcNote = " [not a directory!]"; } else if (0 == (0777 & stLib.st_mode)) { pcNote = " [mode zero]"; } else if (0 != (02 & stLib.st_mode) && 0 == (01000 & stLib.st_mode)) { pcNote = " [world write and NOT sticky]"; } else if (2 == stLib.st_nlink) { pcNote = " [missing all spool directories]"; } else if (2+24 > stLib.st_nlink) { pcNote = " [less than 24 hour spools available]"; } printf("%s: library at \"%s\"%s\n", progname, acLib, pcNote); printf("%s: shell as \"%s\"\n", progname, acShell); printf("%s: at as \"%s\"" #if ADD_TIMESPEC ", with added timespec" #endif "\n", progname, acBatch); printf("%s: install as \"%s\"\n", progname, acInstall); printf("%s: queue time in $%s\n", progname, acQClue); #if USE_FTS printf("%s: using fts\n", progname); #else printf("%s: using readdir\n", progname); #endif } /* Build a spool directory with the given group and mode (ksb) */ static void MkSpool(pcSpool, pcGroup, pcMode) char *pcSpool, *pcGroup, *pcMode; { auto char acCmd[MAXPATHLEN*2+512]; /* install+spool + opts */ register int iCode; #if HAVE_SNPRINTF snprintf(acCmd, sizeof(acCmd), "%s -d -m %s -o root -g %s %s", acInstall, pcMode, pcGroup, pcSpool); #else sprintf(acCmd, "%s -d -m %s -o root -g %s %s", acInstall, pcMode, pcGroup, pcSpool); #endif if (fVerbose) { printf("%s: %s\n", progname, acCmd); } if (fExec && EX_OK != (iCode = system(acCmd))) { exit(iCode); } } /* Build the spool directory structure on a new host (ksb) */ static void BuildSpool(pcGroup) char *pcGroup; { register int i; register struct group *pGR; auto char acPath[MAXPATHLEN+4]; static char *apcGroup[] = { "sysadmin", "adm", "sys", "wheel", "root", (char *)0 }; static char *apcTails[] = { "eod", "top", "boot", "shutdown", "manual", (char *)0 }; /* find a map-able group to own the spools */ for (i = 0; (char *)0 == pcGroup; ++i) { if ((char *)0 == apcGroup[i]) { pcGroup = "0"; break; } if ((struct group *)0 == (pGR = getgrnam(apcGroup[i]))) { continue; } pcGroup = apcGroup[i]; } #if HAVE_SNPRINTF snprintf(acPath, sizeof(acPath), "%s", acLib); #else sprintf(acPath, "%s", acLib); #endif MkSpool(acPath, pcGroup, "2755"); for (i = 0; i < 24; ++i) { #if HAVE_SNPRINTF snprintf(acPath, sizeof(acPath), "%s/%02d", acLib, i); #else sprintf(acPath, "%s/%02d", acLib, i); #endif MkSpool(acPath, pcGroup, "2775"); } for (i = 0; (char *)0 != apcTails[i]; ++i) { #if HAVE_SNPRINTF snprintf(acPath, sizeof(acPath), "%s/%s", acLib, apcTails[i]); #else sprintf(acPath, "%s/%s", acLib, apcTails[i]); #endif MkSpool(acPath, pcGroup, "2775"); } } /* Emit the command we need to put the tack in the batch queue (ksb) * Still Solaris batch doesn't have -f. Sigh. */ static void DumpCmd(FILE *fpShell, char *pcEntry, char *pcQ, time_t *ptNow) { register int bSuper; bSuper = 0 == getuid(); fprintf(fpShell, "%s=%ld; export %s\n", acQClue, (long)*ptNow, acQClue); if ((char *)0 != pcOpRule) fprintf(fpShell, "op -f %s %s %s\n", pcEntry, pcOpRule, pcQ); else fprintf(fpShell, "%s -q %s%s <<\\!\n%s\n!\n", acBatch, pcQ, acTimeSpec, pcEntry); fflush(fpShell); } /* Put the file in the appropriate batch queue (ksb) * also called at cleanup to flush the last entries out of the queue. */ static int Batch(char *pcEntry, struct stat *pstEntry, char *pcQ) { static FILE *fpShell = (FILE *)0; static uid_t wLastUid = 1; static gid_t wLastGid = 1; auto int afdPipe[2], iPid, iErr; auto time_t tNow; extern char **environ; register struct passwd *pwHome; register int bHaveRoot; if ((struct stat *)0 != pstEntry && 0 == (0100 & pstEntry->st_mode)) { if (fVerbose) { printf("# %s: not u+x (mode %04o)\n", pcEntry, 0777&pstEntry->st_mode); } return 0; } if ((char *)0 == pcEntry) { if ((FILE *)0 != fpShell) { fclose(fpShell); fpShell = (FILE *)0; } while (-1 != wait((void *)0)) { /* nada */ } return 0; } if ((struct stat *)0 == pstEntry) { if (fVerbose) { printf("# %s: no stat structure\n", pcEntry); } return 0; } if ((FILE *)0 != fpShell && (wLastUid != pstEntry->st_uid || wLastGid != pstEntry->st_gid)) { fclose(fpShell); fpShell = (FILE *)0; (void)wait((void *)0); } if ((struct passwd *)0 != (pwHome = getpwuid(pstEntry->st_uid)) && fVerbose && (char *)0 != pwHome->pw_dir) { printf("# HOME=\"%s\"\n", pwHome->pw_dir); } if (!fExec) { if (fVerbose) { printf("# su %d.%d\n", pstEntry->st_uid, pstEntry->st_gid); } } else if ((FILE *)0 == fpShell) { if (-1 == pipe(afdPipe)) { fprintf(stderr, "%s: pipe: %s\n", progname, strerror(errno)); exit(EX_OSERR); } for (;;) { switch (iPid = fork()) { case -1: if (EAGAIN == errno) { sleep(2); continue; } fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); exit(EX_OSERR); case 0: /* kid */ close(afdPipe[1]); if (afdPipe[0] != 0) { close(0); dup(afdPipe[0]); close(afdPipe[0]); } /* If we are root -> mortal, replace $HOME, $USER, * $LOGNAME and call setgroups to get init her groups. */ bHaveRoot = getuid() == 0; if (bHaveRoot) { setgroups(1, & pstEntry->st_gid); } if ((struct passwd *)0 != pwHome && (char *)0 != pwHome->pw_dir) { register char **ppc, *pcData, *pcLabel; static char acHome[] = "HOME="; static char acUser[] = "USER="; static char acLogname[] = "LOGNAME="; for (ppc = environ; (char **)0 != ppc && (char *)0 != *ppc; ++ppc) { if (0 == strncmp(*ppc, acHome, sizeof(acHome)-1)) { pcLabel = acHome; pcData = pwHome->pw_dir; } else if (0 == strncmp(*ppc, acLogname, sizeof(acLogname)-1)) { pcLabel = acUser; pcData = pwHome->pw_name; } else if (0 == strncmp(*ppc, acUser, sizeof(acUser)-1)) { pcLabel = acUser; pcData = pwHome->pw_name; } else { continue; } *ppc = malloc(((strlen(pcData)+sizeof(acHome))|7)+1); (void)strcpy(*ppc, pcLabel); (void)strcat(*ppc, pcData); } } if ((char *)0 == pcOpRule && getgid() != pstEntry->st_gid && -1 == setgid(pstEntry->st_gid)) { fprintf(stderr, "%s: setgid: %s\n", progname, strerror(errno)); exit(EX_NOPERM); } if ((char *)0 == pcOpRule && getuid() != pstEntry->st_uid && -1 == setuid(pstEntry->st_uid)) { fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno)); exit(EX_NOPERM); } afdPipe[0] = dup(2); (void)fcntl(afdPipe[0], F_SETFD, 1); if (fVerbose) { iErr = 2; } else { iErr = dup(2); (void)fcntl(iErr, F_SETFD, 1); close(2); (void)open("/dev/null", 1, 0666); } (void)execve(acShell, apcShell, environ); /* Ouch, put stderr back if we moved it to /dev/null */ if (2 != iErr) { close(2); (void)dup(iErr); } fprintf(stderr, "%s: execve: %s: %s\n", progname, acShell, strerror(errno)); exit(EX_UNAVAILABLE); } break; } close(afdPipe[0]); wLastUid = pstEntry->st_uid; wLastGid = pstEntry->st_gid; fpShell = fdopen(afdPipe[1], "wb"); if (!fVerbose) { /* nothing */ } else if ((char *)0 != pcOpRule) { printf("# op %s -f %d.%d\n", pcOpRule, pstEntry->st_uid, pstEntry->st_gid); } else { printf("# su %d.%d\n", pstEntry->st_uid, pstEntry->st_gid); } } (void)time(&tNow); if (fVerbose) { DumpCmd(stdout, pcEntry, pcQ, &tNow); } if (fExec) { DumpCmd(fpShell, pcEntry, pcQ, &tNow); } return 0; } /* Look for a queue name. That is a letter ([A-Za-z]) followed by (ksb) * a separator ([/.-]). */ static char * FindQ(pcLook, pcDefault) char *pcLook, *pcDefault; { static char acCurQ[4]; acCurQ[0] = (char *)0 == pcDefault ? 'Z' : *pcDefault; while ((char *)0 != pcLook && '\000' != *pcLook) { if (isalpha((int)*pcLook) && ('/' == pcLook[1] || '.' == pcLook[1] || '-' == pcLook[1])) { acCurQ[0] = *pcLook; } pcLook = strchr(pcLook, '/'); if ((char *)0 != pcLook) { ++pcLook; } } return acCurQ; } /* Spool a directory to the batch system (ksb) */ static int SpoolDir(pcSpool, pcQueue) char *pcSpool, *pcQueue; { auto int iRet = 0; static char acIgnoreOLD[] = "OLD"; #if USE_FTS register FTS *pFTScan; register FTSENT *pFTECursor; auto char *apcScan[2]; apcScan[0] = pcSpool; apcScan[1] = (char *)0; if ((FTS *)0 == (pFTScan = fts_open(apcScan, FTS_COMFOLLOW|FTS_NOCHDIR|FTS_LOGICAL, (int (*)())0))) { fprintf(stderr, "%s: ftp_open: %s: %s\n", progname, apcScan[0], strerror(errno)); return 1; } /* Scan the files under the directory for tasks * (but don't run the directories). * Skip /OLD dirs for install and all nodes in them, also * ignore the directories as we leave them. */ while ((FTSENT *)0 != (pFTECursor = fts_read(pFTScan))) { if (FTS_D == pFTECursor->fts_info) { if (0 == strcmp(acIgnoreOLD, pFTECursor->fts_name)) (void)fts_set(pFTScan, pFTECursor, FTS_SKIP); continue; } if (FTS_DP == pFTECursor->fts_info) { continue; } iRet |= Batch(pFTECursor->fts_accpath, pFTECursor->fts_statp, FindQ(pFTECursor->fts_accpath, pcQueue)); } fts_close(pFTScan); #else register DIR *pDIScan; register struct dirent *pDECursor; register char *pcTail; auto struct stat stCursor; if ((DIR *)0 == (pDIScan = opendir(pcSpool))) { fprintf(stderr, "%s: opendir: %s: %s\n", progname, pcSpool, strerror(errno)); return 1; } pcTail = strlen(pcSpool)+pcSpool; *pcTail++ = '/'; /* Scan the directory, batch files and scan dirs. ksb * Ignore "OLD", ".", and ".." dirs for install and the filesystem */ while ((struct dirent *)0 != (pDECursor = readdir(pDIScan))) { (void)strcpy(pcTail, pDECursor->d_name); if (-1 == stat(pcSpool, & stCursor)) { iRet |= 2; continue; } switch (stCursor.st_mode & S_IFMT) { case S_IFREG: iRet |= Batch(pcSpool, & stCursor, FindQ(pcSpool, pcQueue)); break; case S_IFDIR: if ('.' == pDECursor->d_name[0] && ('\000' == pDECursor->d_name[1] || ('.' == pDECursor->d_name[1] && '\000' == pDECursor->d_name[2]))) { continue; } if (0 != strcmp(acIgnoreOLD, pDECursor->d_name)) { iRet |= SpoolDir(pcSpool, pcQueue); } break; default: fprintf(stderr, "%s: %s: bad file type for batch job\n", progname, pcSpool); iRet |= 4; } } *--pcTail = '\000'; closedir(pDIScan); #endif return iRet; } /* Enqueue a task in the batch queue it runs in (ksb) * as the correct uid/gid. If the task is a directory then * enqueue every plain file in the directory that doesn't start * with a "." (viz. act like we ran $dir/<*>). * When the task is a plain file decode the name and enqueue it. * From TODO: pull the lib prefix off if given as absolute. */ static int Inject(pcParam) char *pcParam; { register char *pcQueue; register int iPref; auto struct stat stTask; auto char acTask[MAXPATHLEN+4]; if ((strlen(pcParam)+1+sizeof(acLib)) > MAXPATHLEN) { fprintf(stderr, "%s: %s: name too long\n", progname, pcParam); return 1; } iPref = strlen(acLib); if (0 == strncmp(pcParam, acLib, iPref) && '/' == pcParam[iPref]) { pcParam += iPref; do { ++pcParam; } while ('/' == pcParam[0]); } if ((char *)0 != strstr(pcParam, "/../") || 0 == strncmp(pcParam, "../", 3)) { fprintf(stderr, "%s: %s: task names cannot contain \"..\"\n", progname, pcParam); return 1; } sprintf(acTask, "%s/%s", acLib, pcParam); pcQueue = FindQ(pcParam, "Z"); if (-1 == stat(acTask, & stTask)) { fprintf(stderr, "%s: %s: stat: %s\n", progname, acTask, strerror(errno)); return 1; } switch (stTask.st_mode & S_IFMT) { case S_IFREG: return Batch(acTask, & stTask, pcQueue); case S_IFDIR: return SpoolDir(acTask, pcQueue); default: fprintf(stderr, "%s: %s: unrecognized file type\n", progname, acTask); break; } return 1; }