#!mkcmd # $Id: stamp.m,v 1.37 2012/10/30 23:04:51 ksb Exp $ #@Version $Revision: 1.37 $@# # # Some rule (op or not) creates a session process, we allow or forbid # access via a helmet/jacket call and keep track of a time limit to # allow new escalations w/o a re-credential operation. --ksb # # Suggested use in op's rule-base (all these are removed from user envronment): # $STAMP_SPEC=$l:tab=value:tab=.. # name of socket + tableau checks # $STAMP_SET=NAME1:NAME2:NAME3 # recovery of tableau values -> env # $STAMP_WARN=Message # tell the user he failed # $STAMP_REVEAL=envname # standard reveal logic # helmet=/usr/local/libexec/jacket/timestamp # See the few examples in the HTML document. # $Compile(*): ${mkcmd:-mkcmd} -n %F %f && gcc %s -Wall %F.c -o %F # $Compile: ${mkcmd:-mkcmd} -n %F %f && gcc -Wall %F.c -o %F # $InstallDir: ${install:-install} -dr ${DESTDIR}/usr/local/libexec/jacket # $Install: ( [ -f %F ] || %b -mCompile %f ) && ${install:-install} -c -m 0711 %F ${DESTDIR}/usr/local/libexec/jacket/%F # # We take a slightly different view than sudo (of course), we build # an active process for each escalated uid. The socket for each uid # lives in a directory that we hard code here. # # The modes on the directory may allow already credentialed Customers to # end the session or renew the timeout _without_ running an escalated # command. This is a feature as it doesn't clog the log with no-op # commands just to hold the session open. I contend that: # while sleep 55; do /usr/local/libexec/jacket/timestamp -v; done # is better than # while sleep 55; do sudo date; done # because we don't fill the auth log with junk. And in a .logout: # /usr/local/libexec/jacket/timestamp -k 2>/dev/null # is a fine idea. # # Note that the spell below about the same as the -v option: # echo P | nc -U /var/op/timestamp/$UID # # Easter egg: STAMP_PENALTY='2y?' /usr/local/libexec/jacket/timestamp -V # # Run via op this could be an anyone rule, mostly. # stamp /usr/local/libexec/jacket/stampctl $1 ; # $1=^-k|-K|-v$ uid=... # $STAMP_EXPIRY=10m # That allows "op stamp -k" to kill your auth token, and "op stamp -v" to do # something like "sudo -v". Note that -k won't remove a penalty token. # from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' require "util_errno.m" require "api_jacket.m" require "std_help.m" "std_version.m" require "mode.m" "stampproto.m" augment action 'V' { user "Version();" } action 'H' { named "Explain" update "%n(stdout);" aborts "exit(EX_OK);" help "explain the usage of this helmet" } # Since the non-root can't _build_ the socket, let them re-auth if there # is one, or remove auth if there be need (a logout for example). list { hidden named "Stamp" help "silent running" param "" } exit { update "exit(EX_UNAVAILABLE);" } %c static const char rcsid[] = "$Id: stamp.m,v 1.37 2012/10/30 23:04:51 ksb Exp $"; static char acDefCred[] = #if defined(LOCAL_STAMP_DEFREQ) /* allow local site policy */ LOCAL_STAMP_DEFREQ #else STAMP_DEFREQ #endif ; /* must be +write */ static const char acDumpCode[] = "%d\n", acStampSpec[] = STAMP_SPEC, acStampSet[] = STAMP_SET, acStampReveal[] = STAMP_REVEAL, acDefAuthFacility[] = STAMP_DEFAUTH_FACILITY; static const char *pcStampDir = (char *)0; static const char acStampWarn[] = STAMP_WARN; %% before 7 "pcStampDir = SetCache();" #@Explode ExtLine@ %i static char *ExtLine(int fdPeer, size_t *pwLen); %% %c /* grab a complete line from the peer ExtInput stream (ksb) */ static char * ExtLine(int fdPeer, size_t *pwLen) { register int fQuoted; register size_t wLen; auto char acReply[8192]; /* part of the value */ static char *pcMem = (char *)0; static size_t wMaxMem = 0; if ((size_t *)0 != pwLen) { *pwLen = -1; } if (-1 == fdPeer) { /* std reset logic */ if ((char *)0 != pcMem) { free((void *)pcMem); pcMem = (char *)0; } /* Do not vetch: API spec says with is not a failure. */ return (char *)0; } acReply[0] = '\000'; if (0 == wMaxMem) { wMaxMem = 16*1024; while (wMaxMem > 0 && (char *)0 == (pcMem = malloc(wMaxMem))) wMaxMem /= 2; if (0 == wMaxMem) { return (char *)0; } } /* Reading "FOO=C-string"\n or FOO=literal\n or &1/dev/null\n * or any other op jacket external command. */ wLen = 0; if (1 > read(fdPeer, pcMem, 1)) { return (char *)0; } fQuoted = '\"' == acReply[wLen++]; /* Yeah, we beat the machine to death with 1 character reads. Sorry. * we know we need 2 chacters ("\n) to end a string and 1 for a * non-encoded command (\n) -- but that's error-prone code. We * don't send too many commands this way in the Real World(tm). */ for (/*nada*/; 1 == read(fdPeer, pcMem+wLen, 1); ++wLen) { if (fQuoted) { if (wLen > 1 && '\n' == pcMem[wLen] && '\"' == pcMem[wLen-1]) { break; } } else if ('\n' == pcMem[wLen]) { break; } if (wLen+2 >= wMaxMem) { wMaxMem += 1024; if ((char *)0 != (pcMem = realloc((void *)pcMem, wMaxMem))) { return (char *)0; } } } pcMem[wLen] = '\000'; if ((size_t *)0 != pwLen) *pwLen = wLen; return pcMem; } %% #@Remove@ #@Explode ExtDecode@ %i static size_t ExtDecode(char *pcLine); %% %c /* Given a "$VAR=value" (or really "anything legit") decode it (ksb) * in-place returning the new length. Before you call this please * strip off any terminating '\n' (replace it with a '\000'). * The inverse of the this is NVPReply in stampctl.m */ static size_t ExtDecode(char *pcLine) { register char *pcSrc, *pcCursor; pcSrc = pcCursor = pcLine; while (isspace(*pcSrc)) { ++pcSrc; } if ('\"' != *pcSrc++) { /* not really quoted */ return strlen(pcLine); } for (/*above*/; '\"' != *pcSrc && '\000' != *pcSrc; ++pcSrc) { if ('\\' != *pcSrc) { *pcCursor++ = *pcSrc; continue; } switch (*++pcSrc) { case '\\': *pcCursor++ = '\\'; break; case 'n': *pcCursor++ = '\n'; break; case 't': *pcCursor++ = '\t'; break; case 'd': *pcCursor++ = '\"'; break; case 'q': *pcCursor++ = '\''; break; case 'o': *pcCursor++ = '`'; break; default: *pcCursor++ = pcSrc[-1]; break; } } *pcCursor = '\000'; return pcCursor-pcLine; } %% #@Remove@ %c /* How do we use this mess? (ksb) */ static void Explain(FILE *fp) { register int iWidth, i; static int aiLen[] = { sizeof(acStampSpec), sizeof(acStampSet), sizeof(acStampReveal), sizeof(acStampWarn) }; iWidth = strlen(pcStampFacility); for (i = 0; i < sizeof(aiLen)/sizeof(aiLen[0]); ++i) if (aiLen[i] > iWidth) iWidth = aiLen[i]; iWidth *= -1; fprintf(fp, "%s: check stamp authorization criteria\n", progname); fprintf(fp, "%*s the default facility\n", iWidth, pcStampFacility); fprintf(fp, "%*s stamp-path:npv-checks\n%*sfor string compares use name{!,=}value\n%*sfor RE matches use name{=~,!~}RE\n%*sfor integer compares use name{<,<=,>,>=,==,!=}value\n%*s(string suffixes are also compared after the integers)\n", iWidth, acStampSpec, iWidth-1, "", iWidth-1, "", iWidth-1, "", iWidth-2, ""); fprintf(fp, "%*s copy listed tableau entries to the environment\n", iWidth, acStampSet); fprintf(fp, "%*s standard reveal logic\n", iWidth, acStampReveal); fprintf(fp, "%*s authorization failure message\n", iWidth, acStampWarn); exit(EX_OK); } /* output our version information (ksb) */ static void Version() { register char *pcCheck; pcCheck = strstr(pcStampDir, acDefAuthFacility); if ((char *)0 != pcCheck && '\000' != pcCheck[sizeof(acDefAuthFacility)-1]) pcCheck = (char *)0; printf("%s: cache directory: %s\n", progname, pcStampDir); printf("%s: default facility: %s [%sactive]\n", progname, acDefAuthFacility, (char *)0 == pcCheck ? "not " : ""); printf("%s: environment: $%s, $%s, $%s, $%s \n", progname, pcStampFacility, acStampSpec, acStampReveal, acStampWarn); } /* The op admin can leave a kill file in place of the top level (ksb) * directory, a login or a login/group to kill access for that combination * (all, user, user requesting group). We copy the message to stderr. */ static void ErrCat(const char *pcFile) { register FILE *fpShow; register char *pc; auto char acCopy[8192]; if ((char *)0 == pcFile) { fprintf(stderr, "No reason given.\n"); } else if ((FILE *)0 != (fpShow = fopen(pcFile, "r"))) { while ((char *)0 != (pc = fgets(acCopy, sizeof(acCopy), fpShow))) fputs(acCopy, stderr); fclose(fpShow); } } /* We asked for a name=value, and the stamp is sending it back (ksb) * convert it into a string again (might be quoted). This allows * us to emulate op version 3 checks (=~, !~, <=, etc.) below. * If you want to save the value, copy it. We use the same memory * next time. We don't use this to just copy data back to op. * When we fail we must output a suggested exit code from sysexits.h. */ char * RecoverEntry(int iStamp) { register char *pcRet; auto size_t sNull; if (-1 == iStamp) { /* std reset logic */ return ExtLine(-1, (size_t *)0); } if ((char *)0 == (pcRet = ExtLine(iStamp, &sNull))) { printf(acDumpCode, EX_PROTOCOL); return (char *)0; } ExtDecode(pcRet); return pcRet; } static char acChatEnd[] = "\000\000\n"; /* As the stamp at Stamp about the value to $Name (ksb) * wire format is namevalue, where * cmd is "=" for is equal to, and "!" for is not equal to * We emulate RE "=~", "!~" and numeric in ops "<", ">", * "<=", ">=", "==", "!=", in versions 2.x. * q any character not in name or value (\000 a winner) and * name the tableau name * value the value we want to match (or exclude) * Return Boolean success for matched (or not). * * When the value might start with a = or ~ you could be hosed, match it * with an RE, since it must be a string and not a number. If we return * failure we must output a suggested exit from sysexits.h. */ static int CheckEnv(int iStamp, char *pcName) { register int c, iOK, iSend, iRet; register char *pcOp, *pcEq, *pcBang; auto struct iovec aIOV[8]; auto char acReply[64]; /* %c\n */ iOK = iSend = 0; pcEq = pcName; while ('\000' != (c = *pcEq) && '=' != c && '!' != c && '<' != c && '>' != c && '~' != c) ++pcEq; /* We found a 2 character op (RE or number), act like V3 op */ if ('<' == *pcEq || '>' == *pcEq || '=' == pcEq[1] || '~' == pcEq[1]) { auto regex_t reg; auto char *pcEndLeft, *pcEndRight, *pcValue; auto int fLt, fGt, fEq; auto regmatch_t aRM[1]; auto long int lLeft, lRight; /* Check exists, sending "$\000name\000\000\n" */ aIOV[iSend ].iov_base = "$\000";; aIOV[iSend++].iov_len = 2; /* yes we need the \000 */ aIOV[iSend ].iov_base = pcName; aIOV[iSend++].iov_len = pcEq-pcName; aIOV[iSend ].iov_base = acChatEnd; aIOV[iSend++].iov_len = 3; /* \000\000\n */ if (-1 == writev(iStamp, aIOV, iSend)) { printf(acDumpCode, EX_OSERR); return 0; } if ((char *)0 == (pcValue = RecoverEntry(iStamp))) { /* function set error code */ return 0; } ++pcValue; /* skip the '$' prefix */ if (0 != strncmp(pcValue, pcName, pcEq-pcName) || '=' != pcValue[pcEq-pcName]) { printf("# stamp returned a different macro?\n"); printf(acDumpCode, EX_PROTOCOL); return 0; } pcValue += pcEq-pcName + 1; /* handle the RE matches first */ if ('~' == pcEq[1]) { /* !~ or =~ */ if (0 != regcomp(®, pcEq+2, REG_EXTENDED|REG_NOSUB)) { /* If this has a \n in it we should * not print it, as op may read the part * after the \n as an ExtCmd. */ if ((char *)0 == strchr(pcEq, '\n')) printf("# RE failed to compile: %s\n", pcEq+2); else printf("# RE with a newline in it failed to compile for %.*s\n", (int)(pcEq-pcName), pcName); printf(acDumpCode, EX_CONFIG); return 0; } aRM[0].rm_so = 0; aRM[0].rm_eo = strlen(pcValue); switch (regexec(®, pcValue, 1, aRM, 0)) { case 0: iRet = ('=' == pcEq[0]); break; case REG_NOMATCH: iRet = ('!' == pcEq[0]); break; default: printf("# regexec failed for %s\n", pcEq+2); printf(acDumpCode, EX_CONFIG); return 0; } /* match operation failed */ if (0 == iRet) printf(acDumpCode, EX_DATAERR); return iRet; } /* Convert the value to a number+suffix */ lLeft = strtol(pcValue, &pcEndLeft, 0); lRight = strtol(pcEq+2, &pcEndRight, 0); fEq = lLeft == lRight && 0 == strcmp(pcEndLeft, pcEndRight); fGt = lLeft > lRight || (lLeft == lRight && 0 > strcmp(pcEndLeft, pcEndRight)); fLt= lLeft < lRight || (lLeft == lRight && 0 < strcmp(pcEndLeft, pcEndRight)); if ('<' == pcEq[0] && '=' == pcEq[1]) { /*<=*/ iRet = fLt || fEq; } else if ('<' == pcEq[0]) { /*< */ iRet = fLt; } else if ('>' == pcEq[0] && '=' == pcEq[1]) { /*>=*/ iRet = fGt || fEq; } else if ('>' == pcEq[0]) { /*> */ iRet = fGt; } else if ('=' == pcEq[0] && '=' == pcEq[1]) { /*==*/ iRet = fEq; } else if ('!' == pcEq[0] && '=' == pcEq[1]) { /*!=*/ iRet = !fEq; } else { /* wow! >~ or <~ or ~= or ~~ */ printf("# unknown operator %.2s\n", pcEq); printf(acDumpCode, EX_CONFIG); return 0; } if (0 == iRet) printf(acDumpCode, EX_DATAERR); return iRet; } /* Must be a string compare, let the stamp do it so we * don't have to parse it. */ pcEq = strchr(pcName, '='), pcBang = strchr(pcName, '!'); if ((char *)0 == pcEq && (char *)0 == pcBang) { /* Check exists, sending "+\000name\000\000\n" */ aIOV[iSend ].iov_base = "+\000";; aIOV[iSend++].iov_len = 2; /* yes we need the \000 */ aIOV[iSend ].iov_base = pcName; aIOV[iSend++].iov_len = strlen(pcName); aIOV[iSend ].iov_base = acChatEnd; aIOV[iSend++].iov_len = 3; /* \000\000\n */ } else { /* Check equal, sending "=\000name\000value\000\n" */ if (((char *)0 == pcBang) || ((char *)0 != pcEq && pcEq < pcBang)) pcOp = pcEq; else pcOp = pcBang; aIOV[iSend ].iov_base = (pcOp == pcEq) ? "=\000" : "!\000"; aIOV[iSend++].iov_len = 2; /* =\000 or !\000 */ aIOV[iSend ].iov_base = pcName; aIOV[iSend++].iov_len = pcOp-pcName; /* name */ aIOV[iSend ].iov_base = acChatEnd; aIOV[iSend++].iov_len = 1; /* \000 */ aIOV[iSend ].iov_base = ++pcOp; aIOV[iSend++].iov_len = strlen(pcOp); /* value */ aIOV[iSend ].iov_base = acChatEnd+1; aIOV[iSend++].iov_len = 2; /* \000\n */ } if (-1 == writev(iStamp, aIOV, iSend) || 2 != read(iStamp, acReply, 2)) { printf(acDumpCode, EX_OSERR); return 0; } /* The only OK reply is exactly "y\n" 'p' is penalty, and * 'n' is not, others are errors we don't care which, like a * dropped connection is just as bad. */ iRet = 'y' == acReply[0] && '\n' == acReply[1]; if (0 == iRet) printf(acDumpCode, EX_DATAERR); return iRet; } /* Ask the stamp for $\000\000$\000\n, send what ever it (ksb) * sends back to use (that starts with a '$') to iOut. * Return Boolean success, when we return failure we must output a * suggested exit code from sysexits.h. */ static int PushEnv(char *pcName, int iStamp, int iOut) { register int iCc, iSend, iRet; auto struct iovec aIOV[8]; auto char acReply[8192]; /* "$ENV=value" */ iSend = 0; aIOV[iSend ].iov_base = "$\000";; aIOV[iSend++].iov_len = 2; /* yes we need the \000 */ aIOV[iSend ].iov_base = pcName; aIOV[iSend++].iov_len = strlen(pcName); aIOV[iSend ].iov_base = acChatEnd; aIOV[iSend++].iov_len = 3; /* \000\000\n */ if (-1 == writev(iStamp, aIOV, iSend)) { printf(acDumpCode, EX_OSERR); return 0; } /* Expect "$.*"\n or $.*\n, which we can just sent to op * text like "#no name\n78\n" is the alternative */ iRet = EX_OK; acReply[0] = '\000'; if (2 != (iCc = read(iStamp, acReply, 2)) || ('$' != acReply[0] && '\"' != acReply[0])) { printf(acDumpCode, EX_CANTCREAT); return 0; } if (iCc > 0 && iCc != write(iOut, acReply, iCc)) { printf(acDumpCode, EX_OSERR); return 0; } while (1) { if (0 >= (iCc = read(iStamp, acReply, sizeof(acReply)))) break; if (iCc != write(iOut, acReply, iCc)) { printf(acDumpCode, EX_OSERR); return 0; } if ('\n' == acReply[iCc-1]) break; } return 1; } /* Check the stamp expression for authorization (ksb) * return 1 for failure, and dump a non-zero code on stdout as well. */ static int CheckSpec(char *pcStamp, char *pcSet, char **ppcConnect, unsigned int *puTimeout) { register char *pcColon, *pcScan; register int s; auto char *pcSocket, *pcText, *pcMessage; auto StampInfo SI; auto char acReady[64]; /* %c\n */ if ((char **)0 != ppcConnect) { *ppcConnect = (char *)0; } if ((char *)0 != (pcColon = strchr(pcStamp, ':'))) { *pcColon++ = '\000'; } /* Convert shorthand name into a socket path (viz. "" -> uid) */ pcStamp = SockSpec(pcStamp, getuid()); pcSocket = StampPath(pcStampDir, pcStamp); switch (SysStatus(pcSocket, &pcText, &pcMessage, &SI)) { case TBS_SEEN: if (-1 == (s = Client(pcSocket))) { case TBS_AVAIL: #if defined(DEBUG) printf("# %s didn't exist, or failed to connect\n", pcSocket); #endif printf(acDumpCode, EX_NOUSER); return 1; } break; case TBS_DIR: fprintf(stderr, "%s: misconfigured stamp: \"%s\" is a directory\n", progname, pcSocket); printf(acDumpCode, EX_CANTCREAT); return 1; default: if ((char *)0 != pcText) fprintf(stderr, "%s\n", '\000' == *pcText ? "stamp unaliailabel" : pcText); if ((char *)0 != pcMessage) ErrCat(pcMessage); fflush(stderr); printf(acDumpCode, EX_UNAVAILABLE); return 1; } /* Chat with stamp, verify tableau values, cannot check for * a value with a colon in it, you'll live (since you put the * values in the tableau just don't put a colon in any). */ if ((char *)0 != (pcScan = pcColon)) { while (-1 != s && '\000' != *pcScan) { if ((char *)0 != (pcColon = strchr(pcScan, ':'))) { *pcColon = '\000'; if (!CheckEnv(s, pcScan)) { close(s); s = -1; /* CheckEnv dumped a code */ break; } *pcColon++ = ':'; pcScan = pcColon; continue; } if (!CheckEnv(s, pcScan)) { close(s); s = -1; /* CheckEnv dumped a code */ } break; } } /* Fetch and set requested $ENVs, which also can't have a colon in * the name. That shouldn't be an issue. If any of these fail * the running stamp outputs "\n78\n" after an error comment, so * as long as we don't output any other exit codes the escalation * fails with EX_CONFIG. */ if (-1 != s && (char *)0 != (pcScan = getenv(pcSet))) { printf("-%s\n", pcSet); while (-1 != s && '\000' != *pcScan) { if ((char *)0 != (pcColon = strchr(pcScan, ':'))) { *pcColon = '\000'; if (!PushEnv(pcScan, s, 1)) { close(s); s = -1; /* PushEnv dumped a code */ break; } *pcColon++ = ':'; pcScan = pcColon; continue; } if (!PushEnv(pcScan, s, 1)) { close(s); s = -1; /* PushEnv dumped a code */ } break; } } /* If we are a jacket we need to know the idle timeout. */ if (-1 != s && fJacket) { auto char acTimeout[64]; /* "%ld\n" */ auto char *pcEnd = acChatEnd; if (2 != write(s, "I\n", 2) || 0 >= read(s, acTimeout, sizeof(acTimeout))) { s = -1; printf(acDumpCode, EX_OSERR); } else { *puTimeout = (unsigned int)strtoul(acTimeout, &pcEnd, 0); } if ('\000' != *pcEnd && '\n' != *pcEnd) { printf("# %s: timeout format error: %s\n", progname, acTimeout); *puTimeout = 0U; } } /* Last request to the stamp session, are we allowing any access? * If we are not any of the above MAY have already stopped us. */ if (-1 != s && (2 != write(s, "?\n", 2) || 2 != read(s, acReady, sizeof(acReady)) || 'y' != acReady[0] || '\n' != acReady[1])) { close(s); s = -1; printf(acDumpCode, EX_NOPERM); } if (-1 == s) { return 1; } close(s); if ((char **)0 != ppcConnect) { *ppcConnect = pcSocket; } else { free((void *)pcSocket); } return 0; } /* Allow sig alarm to cleanly jump to cleanup code (ksb) */ static unsigned int uTimeout = 0L; void ChildReady(int iSig) { /* we do not set SA_RESTART which is all we want */ uTimeout = 0U; } /* The helmet/jacket entry point (ksb) * you knew I'd get around to this someday. * LLL a timeout here would be nifty (alarm + signal handler) */ int Stamp(int argc, char **argv) { extern char **environ; register char **ppc, *pcScan, *pcEq; register pid_t wReap; register int s; auto char *pcWarn, *pcSocket, *pcWorked, *pcLast, *pcLastEq, *pcSet; auto int wStatus, iSetLen; auto struct sigaction SAChild; iSetLen = 1024; pcSet = malloc(iSetLen); /* Fetch $STAMP_WARN just to be nice */ if ((char *)0 == (pcWarn = getenv(acStampWarn))) { pcWarn = "Sorry"; /* so common, no radiation here */ } else { printf("-%s\n", acStampWarn); } /* Remove radiation of stamp configuration details */ if ((char *)0 != getenv(pcStampFacility)) { printf("-%s\n", pcStampFacility); unsetenv(pcStampFacility); } /* $STAMP_SPEC.*=[stamp-name][:tableau[=!]value]* * yes, you can check multiple stamps, always an "and" operation. * The socket a jacket is going to refresh is the last one in * sorted order base on the SPEC_tag, so put the one you want * last in the list (helmets do not refresh, of course). * $STAMP_SET.* is the parallel tableau request, and * $STAMP_WARN.* is specific the parallel rejection notification */ pcLastEq = pcLast = (char *)0; for (ppc = environ; (char *)0 != (pcScan = *ppc); ++ppc) { register char *pcThisWarn; auto unsigned int uSaw; if ((char *)0 == (pcEq = strchr(pcScan, '='))) continue; if (0 != strncmp(pcScan, acStampSpec, sizeof(acStampSpec)-1)) continue; if (iSetLen < pcEq-pcScan) { /* almost never */ iSetLen = (63|(pcEq-pcScan))+65; pcSet = realloc((void *)pcSet, iSetLen); if ((char *)0 == pcSet) exit(EX_OSERR); } snprintf(pcSet, iSetLen, "%s%.*s", acStampSet, (int)(pcEq-pcScan-(sizeof(acStampSpec)-1)), pcScan+(sizeof(acStampSpec)-1)); if ('\000' == pcEq[1]) { s = CheckSpec(acDefCred, pcSet, &pcWorked, &uSaw); } else { s = CheckSpec(pcEq+1, pcSet, &pcWorked, &uSaw); } if (s) { snprintf(pcSet, iSetLen, "%s%.*s", acStampWarn, (int)(pcEq-pcScan-(sizeof(acStampSpec)-1)), pcScan+(sizeof(acStampSpec)-1)); if ((char *)0 != (pcThisWarn = getenv(pcSet))) pcWarn = pcThisWarn; fprintf(stderr, "%s\n", pcWarn); exit(EX_OK); /* CheckSpec set exit code */ } if ((char *)0 != pcLast) { *pcEq = *pcLastEq = '\000'; s = 0 < strcmp(pcLast, pcScan); *pcEq = *pcLastEq = '='; if (!s) continue; free((void *)pcSocket); /* pcSocket = (char *)0; */ } pcLast = pcScan; pcLastEq = pcEq; pcSocket = pcWorked; uTimeout = uSaw; } /* Allow a reveal spec. */ if ((char *)0 != (pcScan = getenv(acStampReveal))) { printf("-%s\n~%s\n", acStampReveal, pcScan); } /* Checking for helmet case here */ if (!fJacket) { exit(EX_OK); } /* We are a jacket, we need to release the process. Then we * want to keep the stamp open by pinging it before it * times out until the process finishes. After all the stamp * is not idle as long as the process is running, right? * So we set child handler to wake up our sleep call */ memset((void *)&SAChild, 0, sizeof(SAChild)); SAChild.sa_sigaction = (void *)0; SAChild.sa_flags = 0; /* note: not SA_RESTART */ SAChild.sa_handler = ChildReady; if (0U != uTimeout && -1 != sigaction(SIGCHLD, &SAChild, (struct sigaction *)0) && -1 != sigaction(SIGHUP, &SAChild, (struct sigaction *)0)) { auto char acPing[8]; /* "y\n" or "p\n" or "n\n" */ /* We don't want to sleep for the whole idle time, * because we might race with the timer. So we back off * by 39% or so. Either HUP or CHLD stop this loop. */ uTimeout = (unsigned int)(((unsigned long)uTimeout * 286UL)/463UL)+1; /* go, go go! */ close(1); /* We'll catch a signal for the child, which will * make sleep return non-zero, we hope */ do { if (-1 == (s = Client(pcSocket))) break; if (2 != write(s, "?\n", 2) || 2 != read(s, acPing, sizeof(acPing))) { close(s); break; } close(s); if ('y' != acPing[0] || '\n' != acPing[1]) break; } while (0 != uTimeout && 0 == sleep(uTimeout)); } else { /* Just go on, stamp doesn't have a timeout. */ close(1); } /* Wait for the deepest ply in the stack, send that status * to the shim above and repeat until no more plys. Note that * a jacket might interpret an exit code (viz. invert it). */ while (iPid != (wReap = wait(& wStatus))) { if (WIFSTOPPED(wStatus)) { (void)kill(wReap, SIGCONT); continue; } if (-1 == wReap) { break; } } exit(WIFEXITED(wStatus) ? WEXITSTATUS(wStatus) : 255); }