#!mkcmd # $Id: stampctl.m,v 1.20 2012/11/02 21:57:58 ksb Exp $ #@Version $Revision: 1.20 $@# # # This is the interface to stop/stop an op stamp process. # See the few examples in the HTML document. # # $Compile(*): ${mkcmd:-mkcmd} -n %F %f && gcc %s -Wall %F.c -o %F -lpam # $Compile: ${mkcmd:-mkcmd} -n %F %f && gcc -Wall %F.c -o %F -lpam # $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 # # Run via op this could be an anyone rule, mostly. # Allow anyone to make a stamp with their tty in the creds: # stamp /usr/local/libexec/jacket/stampctl -M$1/$l -E1h -Dstamp TTY=$y ; # $1=^-M$,^make$ # users=^.*$ uid=stamp euid=root gid=stamp # This only means we can start a stamp process with our tty (TTY=$y) as # the only credential. (We must be on a tty to run this, since we used $y). # So run "op stamp wheel" to give yourself an hour to run escalataion. # # client /something/we/run/lots ... ; # groups=^wheel$ # helmet=/usr/local/libexec/jackets/stamp # STAMP_CREDS=wheel/$l:TTY=$y # uid=source gid=... # This allows anyone in group wheel with an active stamp _on_the_same_tty_ # to run the "client" escalation. If you don't have a stamp it fails, if # you are not in group wheel you can't even try, if you are on the wrong # tty you loose too. The tty restriction could be removed or expanded to # include other attributes about the session (even $Z) which really limits # you to the same shell (PPID=$Z is the common idiom). # Note that the directory "wheel" could have any spelling, it is common to # factor the stamps into purpose-built directories to limit name collisions. # # This replaces sudo's timestamp with a more secure and versitile facility. # from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' require "util_errno.m" require "std_help.m" "std_version.m" require "mode.m" "stampproto.m" augment action 'V' { user "Version();" } # Since the non-root can't _build_ the socket, let them reauthorize if there # is one, or remove the stamp if there be need (a logout for example). exclude 'Mvk' char* 'm' { local named "pcMode" param "mode" help "permissions for the new stamp" } char* 'u' { local named "pcUser" param "user" help "owner of the new stamp" } char* 'g' { local named "pcGroup" param "group" help "group ownership for the new stamp" } list { named "InitAllDirs" update "exit(%n(%#, %@, pcStampDir, pcMode, pcGroup, pcUser));" param "facilities" help "list of additional facility directories to create" } char* 'M' { local named "pcSocket" param "name" help "create an authorization stamp at name" number { param "max" track 'fGaveAllowed' named 'iAllowed' help "number of authorizations allowed by this stamp" } boolean 'C' { named "fChRoot" hidden help "try to be rediculously secure with chroot" } boolean 'n' { named "fSoMuchHate" help "start in penalty mode (never allow any escalations)" } char* 'R' { local named "pcRemote" param "roap" help "fetch tableau from an authorization service by name" boolean 'X' { named "fExchange" help "reverse the precedence of remote and rule tableaus" } type "seconds" 'T' { named "iRemConn" "pcRemConn" init 'DEF_MUX_TIMEOUT' param "timeout" help "allowed delay for the roap tcpmux connection" } type "service" 'p' { named "pSEConn" "pcRoapSvc" init '"tcpmux"' help "specify the tcpmux service port on remote" } } char* 'D' { local named "pcDrop" param "drop" help "setuid to user (or user:group) to process requests" } type "seconds" 'I' { named "iIdleSpan" init '"1h"' # 1 hour is liberal, tune it keep "pcIdleSpec" param "idle" help "specify an idle timeout in scaled seconds (default %qi)" } type "seconds" 'E' { named "iEndSpan" init '"0"' # no set termination date -blade runner keep "pcEndSpec" param "end" help "specify a maximum time limit in scaled seconds (default %qi)" } list { named "AuthForce" param "name=values" update "%n(%#, %@, %rMn, %run, %rgn, %rmn, %rDn, %rRn);/*NOTREACHED*/" help "set qualifiers in the attribute tableau" } } char* 'Q' { help "output requested tableau elements" param "stamp" boolean 'F' { local named "fExtLike" help "output can be sent to op as external input from a jacket" } list { named "Query" update "%n(%#, %@, %rQn, %rFn);/*NOTREACHED*/" param "tableaus" help "sessions to terminate, by name" } exclude "mgu" } boolean 'v' { help "reset the timeout on an existing authorization" list { hidden named "AuthRenew" param "stamps" help "the stamp to refresh" } exclude "mgu" } boolean 'kKN' { help "terminate an existing authorization" update "%n = %w;" list { hidden named "AuthTerminate" update "%n(%#, %@, %rkn);/*NOTREACHED*/" param "stamps" help "sessions to terminate, by name" } exclude "mgu" } action 'H' { named "Explain" update "%n(stdout);" aborts "exit(EX_OK);" help "explain the usage of this helmet" } exit { update "exit(EX_UNAVAILABLE);" } %i static const char *pcStampDir = (char *)0; extern char **environ; %% before "pcStampDir = SetCache();" %i static const char rcsid[] = "$Id: stampctl.m,v 1.20 2012/11/02 21:57:58 ksb Exp $"; #if !defined(DEF_MUX_TIMEOUT) #define DEF_MUX_TIMEOUT "19" #endif %% #@Explode NVPReply@ %hi static void NVPReply(int sClient, char *pcCmd, char *pcEntry); %% %c /* When the network doesn't take the whole write, push again (ksb) */ static int FlushWrite(int fd, char *pcOut, int iLen) { register int iCc; while (iLen > 0) { if (-1 == (iCc = write(fd, pcOut, iLen))) return iLen; pcOut += iCc; iLen -= iCc; } return 0; } /* Convert the input NPV into "quoted" form for op, if needed. (ksb) * If you add encoding here (\x) add to stamp's RecoverEntry function. */ static void /*ARGSUSED*/ NVPReply(int sClient, char *pcCmd, char *pcEntry) { register size_t wNeed; register char *pcCursor; auto struct iovec aIOV[4]; static char *pcQuote = (char *)0; static size_t wMax; if ((char *)0 == strchr(pcEntry, '\n') && (char *)0 == strchr(pcEntry, '\"')) { wNeed = 0; aIOV[wNeed ].iov_base = "$"; aIOV[wNeed++].iov_len = 1; aIOV[wNeed ].iov_base = pcCmd; aIOV[wNeed++].iov_len = strlen(pcCmd); aIOV[wNeed ].iov_base = pcEntry; aIOV[wNeed++].iov_len = strlen(pcEntry); aIOV[wNeed ].iov_base = "\n"; aIOV[wNeed++].iov_len = 1; (void)writev(sClient, aIOV, wNeed); return; } /* We have to quote it, sigh. Allocate enough to backslash * every character, which is worst-case. Pad so we might not * have to realloc in future calls. */ wNeed = (2*strlen(pcEntry)|63)+65; if ((char *)0 == pcQuote) { wMax = wNeed; pcQuote = malloc(wMax); } else if (wMax < wNeed) { wMax = wNeed; pcQuote = realloc((void *)pcQuote, wMax); } if ((char *)0 == pcQuote) { FlushWrite(sClient, "71\n", 3); return; } for (pcCursor = pcQuote; '\000' != (*pcCursor = *pcEntry); ++pcEntry) { switch (*pcCursor) { case '\\': *pcCursor++ = '\\'; *pcCursor++ = '\\'; break; case '\n': *pcCursor++ = '\\'; *pcCursor++ = 'n'; break; case '\t': *pcCursor++ = '\\'; *pcCursor++ = 't'; break; case '\"': *pcCursor++ = '\\'; *pcCursor++ = 'd'; /* op uses this for " */ break; case '\'': *pcCursor++ = '\\'; *pcCursor++ = 'q'; /* op uses this for ' */ break; case '`': *pcCursor++ = '\\'; /* op uses this for ` */ *pcCursor++ = 'o'; break; default: ++pcCursor; break; } } wNeed = 0; aIOV[wNeed ].iov_base = "$\""; aIOV[wNeed++].iov_len = 2; aIOV[wNeed ].iov_base = pcCmd; aIOV[wNeed++].iov_len = strlen(pcCmd); aIOV[wNeed ].iov_base = pcQuote; aIOV[wNeed++].iov_len = strlen(pcQuote); aIOV[wNeed ].iov_base = "\"\n"; aIOV[wNeed++].iov_len = 2; (void)writev(sClient, aIOV, wNeed); return; } %% #@Remove@ %c static const char acStampSpec[] = STAMP_SPEC, acStampReveal[] = STAMP_REVEAL, acStampFacility[] = STAMP_FACILITY, acStampQuery[] = STAMP_QUERY, acDefAuthFacility[] = STAMP_DEFAUTH_FACILITY, acStampWarn[] = STAMP_WARN; /* Also take -H like any stamp would (ksb) */ static void Explain(FILE *fp) { fprintf(fp, "%s: build a new authorization stamp\n", progname); fprintf(fp, "%s\tauthentication location (default %s)\n", acStampFacility, acDefAuthFacility); fprintf(fp, "%s\tstamp specification\n", acStampSpec); fprintf(fp, "%s\tstandard reveal logic\n", acStampReveal); fprintf(fp, "%s\tescalation denied message\n", acStampWarn); } /* output our version information (ksb) */ static void Version() { auto const char *pcStat; auto struct stat stCheck; auto char acState[MAXPATHLEN+4]; if (0 != stat(pcStampDir, &stCheck)) pcStat = strerror(errno); else if (S_ISSOCK(stCheck.st_mode)) pcStat = "global access socket"; else if (!S_ISDIR(stCheck.st_mode)) pcStat = "not a directory"; else if (0 != (022 & stCheck.st_mode) && 0 == (01000 & stCheck.st_mode)) pcStat = "writable and not sticky"; else if (0711 != (0711 & stCheck.st_mode)) pcStat = "should be at least mode 0711"; else if (0 != (0004 & stCheck.st_mode)) pcStat = "directory permissive"; else pcStat = "directory ok"; printf("%s: cache directory: %s [%s]\n", progname, pcStampDir, pcStat); snprintf(acState, sizeof(acState), "%s/%ld", pcStampDir, (long)getuid()); if (0 != stat(acState, &stCheck) && S_ISSOCK(stCheck.st_mode)) printf("%s: existing socket for %ld\n", progname, (long)getuid()); printf("%s: timeout under -R defaults to \"%s\"\n", progname, DEF_MUX_TIMEOUT); if ('/' != *pcStampDir) { fprintf(stderr, "%s: the compiled top-level directory is not an absolute path!\n", progname); exit(EX_NOPERM); } } /* Build (or update the modes on) a stamp directory (ksb) */ static int InitSDir(const char *pcDir, const char *pcUid, const char *pcGid, const char *pcMode, int iModeAbove) { auto char *pcEnd, *pc; register struct group *grp; register struct passwd *pwd; auto uid_t wUid; auto gid_t wGid; auto int wMode; auto u_MODE_CVT MCMode; if ((char *)0 != pcUid && (char *)0 != (pc = strchr(pcUid, ':'))) { *pc++ = '\000'; if ('\000' != *pc && (char *)0 == pcGid) pcGid = pc; } mode_cvt((char *)pcMode, &MCMode, "mode"); #if defined(DEBUG) fprintf(stderr, "%s: FYI build -m%04o/%04o -u%s -g%s %s\n", progname, MCMode.iset, MCMode.ioptional, pcUid, pcGid, pcDir); #endif if ((char *)0 == pcDir || '\000' == *pcDir) return EX_OK; wMode = MCMode.iset|(iModeAbove&MCMode.ioptional); if (-1 == mkdir(pcDir, wMode) && EEXIST != errno) { fprintf(stderr, "%s: mkdir: %s: %s\n", progname, pcDir, strerror(errno)); return EX_OSERR; } /* mkdir will not set sticky, setuid, setgid, use chmod */ if ((EEXIST == errno || 0 != (07000&wMode)) && -1 == chmod(pcDir, wMode)) { fprintf(stderr, "%s: chmod: %s, %04o: %s\n", progname, pcDir, wMode, strerror(errno)); return EX_OSERR; } if (isdigit(pcUid[0])) { wUid = strtoul(pcUid, &pcEnd, 0); if ('\000' != *pcEnd) { fprintf(stderr, "%s: -u: trailing junk on uid (%s)\n", progname, pcEnd); exit(EX_DATAERR); } #if defined(DEBUG) if ((struct passwd *)0 == (pwd = getpwuid(wUid))) fprintf(stderr, "%s: FYI -u uid %ld unmapped for %s\n", progname, (long)wUid, pcDir); #endif } else if ((struct passwd *)0 == (pwd = getpwnam(pcUid))) { fprintf(stderr, "%s: -u: unmapped login (%s)\n", progname, pcUid); exit(EX_DATAERR); } else { wUid = pwd->pw_uid; } if (isdigit(pcGid[0])) { wGid = strtoul(pcGid, &pcEnd, 0); if ('\000' != *pcEnd) { fprintf(stderr, "%s: -g: trailing junk on gid (%s)\n", progname, pcEnd); exit(EX_DATAERR); } #if defined(DEBUG) if ((struct group *)0 == (grp = getgrgid(wGid))) fprintf(stderr, "%s: FYI -g gid %ld unmapped for %s\n", progname, (long)wGid, pcDir); #endif } else if ((struct group *)0 == (grp = getgrnam(pcGid))) { fprintf(stderr, "%s: -g: unmapped group (%s)\n", progname, pcGid); exit(EX_DATAERR); } else { wGid = grp->gr_gid; } if (-1 == chown(pcDir, wUid, wGid)) { fprintf(stderr, "%s: chown: %s: %s\n", progname, pcDir, strerror(errno)); return EX_OSERR; } return EX_OK; } /* Allow for explicit facilities specified by to create/update (ksb) * the cache directory structure. * We overload -m mac (-m mode), -g group, -u user to specify the * permissions on each directory. We allow mixing options and arguments. * We are a Bad Dog. * Remove junk, line any socket w/o a password entry? Let the admin * use "find ... \( -nouser -or -nogroup \) -delete" */ static int InitAllDirs(int argc, char **argv, const char *pcTopDir, char *pcMode, char *pcGroup, char *pcLogin) { register char *pcArg; auto int iRet; auto int fSawDashDash, iMyMode; auto char acCvt[64]; /* %ld */ auto char acFullPath[MAXPATHLEN+4]; auto struct stat stTop; setpwent(); setgrent(); if ('/' != *pcTopDir) { fprintf(stderr, "%s: the compiled top-level directory is not an absolute path (%s)\n", progname, pcTopDir); exit(EX_NOPERM); } /* Set any unspecified mode options, and check for no dirs. */ if (0 == stat(pcTopDir, &stTop)) { if ((char *)0 == pcLogin) { snprintf(acCvt, sizeof(acCvt), "%ld", (long)stTop.st_uid); pcLogin = strdup(acCvt); } if ((char *)0 == pcGroup) { snprintf(acCvt, sizeof(acCvt), "%ld", (long)stTop.st_gid); pcGroup = strdup(acCvt); } iMyMode = 07777&stTop.st_mode; if ((char *)0 == pcMode) { snprintf(acCvt, sizeof(acCvt), "%04lo", (long)iMyMode); pcMode = strdup(acCvt); } iMyMode |= 01000; /* always allow sticky if opt'd */ } else { iMyMode = 03775; /* allow sticky, grp write+iherrit */ if ((char *)0 == pcLogin) pcLogin = "0"; if ((char *)0 == pcGroup) pcGroup = "0"; if ((char *)0 == pcMode) pcMode = "0700"; } iRet = EX_OK; fSawDashDash = 0; while (EX_OK == iRet && (char *)0 != (pcArg = *argv++)) { register char *pcSlash; if (fSawDashDash || '-' != pcArg[0]) { while ((char *)0 != (pcSlash = strrchr(pcArg, '/')) && '\000' == pcSlash[1] && pcSlash > pcArg) *pcSlash = '\000'; while ('.' == pcArg[0] && '/' == pcArg[1]) while ('/' == *++pcArg) /*nada*/; if (('.' == pcArg[0] && '\000' == pcArg[1]) || '\000' == *pcArg) pcArg = (char *)pcTopDir; if (Climbing(pcArg)) { fprintf(stderr, "%s: climbing never allowed (%s)\n", progname, pcArg); break; } if ('/' != *pcArg) { snprintf(acFullPath, sizeof(acFullPath), "%s/%s", pcTopDir, pcArg); pcArg = acFullPath; } iRet = InitSDir(pcArg, pcLogin, pcGroup, pcMode, iMyMode); continue; } /* so arg must be a mixed option or -- */ switch (pcArg[1]) { case '-': fSawDashDash = 1; continue; case 'm': if ('\000' != pcArg[2]) { pcMode = pcArg+2; } else if ((char *)0 == (pcMode = *argv++)) { fprintf(stderr, "%s: -m missing mode\n", progname); break; } continue; case 'g': if ('\000' != pcArg[2]) { pcGroup = pcArg+2; } else if ((char *)0 == (pcGroup = *argv++)) { fprintf(stderr, "%s: -g missing group\n", progname); break; } continue; case 'u': if ('\000' != pcArg[2]) { pcLogin = pcArg+2; } else if ((char *)0 == (pcLogin = *argv++)) { fprintf(stderr, "%s: -u missing uid\n", progname); break; } continue; default: fprintf(stderr, "%s: options are limited to [-m mode] [-g gid] [-u uid]\n", progname); break; } exit(EX_USAGE); } endgrent(); endpwent(); return iRet; } /* We maintain a list of groups for, we know the max number possible (ksb) * in the caller, which is (NGROUPS_MAX + 2) actually. */ static void AddGroup(gid_t wAdd, int *piCur, gid_t *pwList) { register int i; for (i = 0; i < *piCur; ++i) { if (wAdd == pwList[i]) break; } if (i == *piCur) { pwList[(*piCur)++] = wAdd; } } %% %i #if !defined(NGROUPS_MAX) #define NGROUPS_MAX 120 /* usually 8 or 16 */ #endif #if !defined(MAX_ID_WIDTH) #define MAX_ID_WIDTH 48 /* %ld */ #endif %% #@Explode CredList@ %hi static char *CredList(char *const pcOut, int iSize, const char *); %% %c /* Build the login:groups:netgroups:domain cred list (ksb) */ static char * CredList(char *const pcOut, int iSize, const char *pcQuery) { register int i; register char *pcUser; register struct passwd *pw; register struct group *gr; register FILE *fpNg; auto int iGrps; auto gid_t awGids[NGROUPS_MAX+8]; auto gid_t awGList[NGROUPS_MAX+8]; auto char acSep[4]; /* like ":" or " " or "," */ auto char acCvt[MAX_ID_WIDTH]; /* Make cred list-- nobody said it would be easy. */ iGrps = 0; setgrent(); setpwent(); if ((char *)0 == (pcUser = getenv("USER"))) { pcUser = getenv("LOGNAME"); } if ((char *)0 != pcUser && (struct passwd *)0 != (pw = getpwnam(pcUser)) && getuid() == pw->pw_uid) { AddGroup(pw->pw_gid, &iGrps, awGids); } else if ((struct passwd *)0 != (pw = getpwuid(getuid()))) { pcUser = pw->pw_name; AddGroup(pw->pw_gid, &iGrps, awGids); } else { snprintf(acCvt, MAX_ID_WIDTH, "%ld", (long)getuid()); pcUser = acCvt; } snprintf(pcOut, iSize, "%s", pcUser); endpwent(); /* Mapping gid->group, no common way to disambiguate between dup * groups we could try $GROUP, but that's never going to work. */ AddGroup(getgid(), &iGrps, awGids); if (0 < (i = getgroups(sizeof(awGList)/sizeof(awGList[0]), awGList))) { register int j; for (j = 0; j < i; ++j) { AddGroup(awGList[j], &iGrps, awGids); } } /* Convert the unique gids to group name if we can. */ acSep[0] = ':'; acSep[1] = '\000'; for (i = 0; i < iGrps; ++i) { strncat(pcOut, acSep, iSize); acSep[0] = ' '; if ((struct group *)0 == (gr = getgrgid(awGids[i]))) { snprintf(acCvt, MAX_ID_WIDTH, "%ld", (long)awGids[i]); strncat(pcOut, acCvt, sizeof(acCvt)); } else { strncat(pcOut, gr->gr_name, iSize); } } endgrent(); /* Netgroups. A little harder. And we don't look a ypcat without * a "+" in the netgroups file, which might be a bug. */ strncat(pcOut, ":", iSize); if ((FILE *)0 != (fpNg = fopen("/etc/netgroup", "r"))) { register int c, fSkip, iLen, fPlus; register char *pcBack; auto char acMyself[ #if defined(HOST_NAME_MAX) (HOST_NAME_MAX|7)+1 #else #if defined(MAXHOSTNAMELEN) MAXHOSTNAMELEN #else 512 /* traditionally 256 */ #endif #endif ]; static const char acLocalhost[] = "localhost"; if (-1 == gethostname(acMyself, sizeof(acMyself))) { acMyself[0] = '\000'; } pcBack = pcOut+strlen(pcOut); iLen = fSkip = fPlus = 0; /* a netgroup must start in col1 and have an isspace, '\\', * or '(' after the name, AFAICT --ksb */ while (EOF != (c = getc(fpNg))) { if (fSkip) { if ('\n' == c) { fSkip = 0; iLen = 0; } continue; } /* groupname1\ or groupname2(rockopera,ab,) */ if ('('/*)*/ == c || '\\' == c) { c = ' '; } if ('#' == c) { iLen = 0; fSkip = 1; continue; } if (!isspace(c)) { pcBack[iLen++] = c; continue; } if (0 == iLen) { fSkip = 1; continue; } pcBack[iLen] = '\000'; if (0 == strcmp("+", pcBack)) { fPlus = 1; } else if (('\000' != acMyself[0] && innetgr(pcBack, acMyself, pcUser, (char *)0)) || innetgr(pcBack, acLocalhost, pcUser, (char *)0)) { pcBack += strlen(pcBack); *pcBack++ = ' '; } iLen = 0; if ('\n' != c) { fSkip = 1; } } /* When fPlus is set, we should parse "ypcat -k netgroup" * output as above. Or send "+" and our NIS domain, which * is kinda a cop-out? OK for now. */ if (fPlus) { *pcBack++ = '+'; *pcBack++ = ' '; } /* Remove trailing space, if we found any netgroups. */ if (' ' == *--pcBack) { *pcBack = '\000'; } fclose(fpNg); } /* append YP/NIS domain name, just for grins, if we have one. */ strncat(pcOut, ":", iSize); i = strlen(pcOut); if (-1 == getdomainname(pcOut+i, iSize-i)) { pcOut[i] = '\000'; } strncat(pcOut, ":", iSize); if ((char *)0 != pcQuery) { strncat(pcOut, pcQuery, iSize); } return pcOut; } /* There is a tcpmux peer that is sending us a stream which represents (ksb) * tableau entries. They can send 2 forms: * NAME=value[\r]?\n * and * NAME="value"[\r]?\n * you cannot include a NUL (\000), it is never a valid tableau entry. */ static char ** ReadEnv(FILE *fpPeer) { register char **ppcCur, *pcRaw, *pcResize, *pcEnd, *pcCursor; register int c, i, iCount, iMax; iMax = 5906; while (iMax > 2 && (char *)0 == (pcRaw = calloc(iMax, sizeof(char)))) { iMax /= 2; } if (iMax < 3) { return (char **)0; } i = iCount = 0; while (EOF != (c = fgetc(fpPeer))) { /* Out of space -> realloc if we can, like I would on a PDP11 * Seq. 9557, 15465, 25026, 40497, 65532... */ if (i+1 >= iMax) { iMax *= 89; iMax /= 55; ++iMax; while (iMax > i && (char *)0 == (pcResize = realloc((void *)pcRaw, iMax))) { iMax -= 1024; } if ((char *)0 == pcResize || i+1 >= iMax) return (char **)0; pcRaw = pcResize; } pcRaw[i++] = c; iCount += '\n' == c; } pcEnd = pcRaw+i; if ((char **)0 == (ppcCur = calloc(iCount+1, sizeof(char *)))) { return (char **)0; } pcCursor = pcRaw; for (i = 0; pcRaw < pcEnd && i < iCount; ++i) { register int fQuote; if (isspace(*pcRaw)) { ++pcRaw; continue; } ppcCur[i] = pcCursor; fQuote = 0; while ('\n' != (c = *pcRaw++) || fQuote) { if (!fQuote) { if ('\"' == c) { fQuote = 1; continue; } if ('\r' == c && pcRaw != pcEnd && '\n' == pcRaw[1]) { ++pcRaw; break; } if ('\n' == c) { break; } *pcCursor++ = c; continue; } if ('\"' == c) { fQuote = 0; } else if ('\\' != c) { *pcCursor++ = c; } else if (pcRaw == pcEnd) { *pcCursor++ = '\\'; } else switch (c = *pcRaw++) { case 'e': break; /* empty */ case 's': *pcCursor++ = ' '; break; case 'n': *pcCursor++ = '\n'; break; case 't': *pcCursor++ = '\t'; break; case 'b': *pcCursor++ = '\b'; break; case 'r': *pcCursor++ = '\r'; break; case 'f': *pcCursor++ = '\f'; break; case 'v': *pcCursor++ = '\013'; break; case '\"': *pcCursor++ = '\"'; break; case '\\': *pcCursor++ = '\\'; break; default: *pcCursor++ = c; break; /* we do not do octal, you really need that? */ } } if (ppcCur[i] != pcCursor) { *pcCursor++ = '\000'; } } *pcCursor = '\000'; ppcCur[i] = (char *)0; return ppcCur; } /* Just to keep lint happier (ksb) */ static int StrSort(const void *pvL, const void *pvR) { return strcmp((const char *)pvL, (const char *)pvR); } /* Return '=' when the left env is the same as the right (ksb) * return '<' when left strcmp's less then right, else '>'. * the first check is just SEGV or snark avoidance, mayhap. */ static int MergeOrder(register char *pcLeft, register char *pcRight) { register char *pcEq; register int iL, iR; if ((char *)0 == pcLeft || (char *)0 == pcRight) return (char *)0 == pcLeft ? '>' : '<'; iL = ((char *)0 == (pcEq = strchr(pcLeft, '='))) ? strlen(pcLeft) : pcEq-pcLeft; iR = ((char *)0 == (pcEq = strchr(pcRight, '='))) ? strlen(pcRight) : pcEq-pcRight; if (iL == iR && 0 == strncmp(pcLeft, pcRight, iL)) return '='; if (iL <= iR && 0 < strncmp(pcLeft, pcRight, iL)) return '>'; if (iL > iR && 0 < strncmp(pcLeft, pcRight, iR)) return '>'; return '<'; } /* The tableau entries on the left are more profound than the ones (ksb) * on the right. But an empty left allows the right through. */ static char ** EnvMerge(char **ppcLeft, char **ppcRight) { register char **ppcRet, *pcLeft, *pcRight; register int iLeft, iRight, iMerge; static char *pcEmpty = (char *)0; if ((char **)0 == ppcRight || (char *)0 == *ppcRight) { return (char **)0 == ppcLeft ? &pcEmpty : ppcLeft; } if ((char **)0 == ppcLeft || (char *)0 == *ppcLeft) { return ppcRight; } for (iLeft = 0; (char *)0 != ppcLeft[iLeft]; ++iLeft) /* count'm */; for (iRight = 0; (char *)0 != ppcRight[iRight]; ++iRight) /* count'm */; iMerge = ((iLeft+iRight)|1)+1; if ((char **)0 == (ppcRet = calloc(iMerge, sizeof(char *)))) { fprintf(stderr, "%s: calloc: %ld,%ld: %s\n", progname, (long)iMerge, (long)sizeof(char *), strerror(errno)); exit(EX_OSERR); } qsort((void *)ppcLeft, iLeft, sizeof(char *), StrSort); qsort((void *)ppcRight, iRight, sizeof(char *), StrSort); iRight = iLeft = 0; for (iMerge = 0; (char *)0 != (pcLeft = ppcLeft[iLeft]) && (char *)0 != (pcRight = ppcRight[iRight]); ++iMerge) { switch (MergeOrder(pcLeft, pcRight)) { case '=': ++iRight; /*FALLTHROUGH*/ case '<': ++iLeft, ppcRet[iMerge] = pcLeft; break; case '>': ++iRight, ppcRet[iMerge] = pcRight; break; } } if ((char *)0 == ppcLeft[iLeft]) { ppcLeft = ppcRight, iLeft = iRight; } while ((char *)0 != (pcLeft = ppcLeft[iLeft++])) { ppcRet[iMerge++] = pcLeft; } ppcRet[iMerge] = (char *)0; return ppcRet; } /* To support ksb's @ extension to RCS1918 we need loop detection code. * A reply "@host:port\r\n" retrys the same service as the new host:port. */ typedef struct RCnode { struct sockaddr_in sain; struct RCnode *pRClast; } ReCurTrap; /* Connect to roapmux running under TCPMUX on Remote to fetch a list (ksb) * of authorization tokens. These overwrite any from argv with the * same name. Remote may be host:muxname as well, default "roap:roapmux" */ static char ** FetchRoap(const char *pcRemote, int wPort, char **ppcArgv, struct RCnode *pRCUp) { register int s; register char *pcRoap; register FILE *fpMux; register struct hostent *pHE; struct RCnode RCLevel; auto char **ppcRemote = (char **)0; auto char acReply[8192]; /* max hostname + extra */ static char acDefHost[] = "roap", acDefMux[] = "roapmux"; if ((char *)0 == (pcRoap = strchr(pcRemote, ':'))) { pcRoap = acDefMux; } else { *pcRoap++ = '\000'; if ('\000' == *pcRoap) pcRoap = acDefMux; } if ('\000' == pcRoap[1]) { pcRoap = acDefHost; } /* connect to pcRoap on our tcpmux port */ RCLevel.pRClast = pRCUp; memset((void *)&RCLevel.sain, '\000', sizeof(RCLevel.sain)); if ((struct hostent *)0 == (pHE = gethostbyname(pcRemote))) { fprintf(stderr, "%s: gethostbyname: %s: %s\n", progname, pcRemote, strerror(errno)); exit(EX_NOHOST); } memcpy((char *)&RCLevel.sain.sin_addr, (char *)pHE->h_addr, pHE->h_length); RCLevel.sain.sin_port = htons(wPort); RCLevel.sain.sin_family = AF_INET; for (; (struct RCnode *)0 != pRCUp; pRCUp = pRCUp->pRClast) { if (0 != memcmp((void *)&pRCUp->sain, &RCLevel.sain, sizeof(RCLevel.sain))) continue; fprintf(stderr, "%s: %s:%d detected tcmpux service %s loops to itself\n", progname, pcRemote, wPort, pcRoap); exit(EX_CONFIG); } if (-1 == (s = socket(AF_INET, SOCK_STREAM, 0))) { fprintf(stderr, "%s: socket: %s\n", progname, strerror(errno)); exit(EX_OSERR); } /* Go to the trouble to set a timeout for the connection, because * we don't want a stampctl setup to take too long. (ksb) */ { auto int iFlags; auto socklen_t wRead; auto fd_set fdsAccept; auto struct timeval tvSlow; if (0 != iRemConn && (-1 == (iFlags = fcntl(s, F_GETFL, 0)) || -1 == fcntl(s, F_SETFL, iFlags|O_NDELAY))) { fprintf(stderr, "%s: fcntl: %s\n", progname, strerror(errno)); exit(EX_OSERR); } iFlags = 0; if (-1 != connect(s, (struct sockaddr *)&RCLevel.sain, sizeof(RCLevel.sain))) { /* OK */ } else if (EINPROGRESS != errno) { iFlags = 1; } else { FD_ZERO(&fdsAccept); FD_SET(s, &fdsAccept); tvSlow.tv_sec = iRemConn; tvSlow.tv_usec = 121393; /* fib25 about 0.12s grace */ if (1 != select(s+1, (fd_set *)0, &fdsAccept, (fd_set *)0, &tvSlow)) { fprintf(stderr, "%s: select: %s\n", progname, strerror(errno)); exit(EX_UNAVAILABLE); } wRead = sizeof(iFlags); if (-1 == getsockopt(s, SOL_SOCKET, SO_ERROR, &iFlags, &wRead)) { fprintf(stderr, "%s: getsockopt: %d: %s\n", progname, s, strerror(errno)); exit(EX_OSERR); } errno = ECONNREFUSED; } if (0 != iFlags) { fprintf(stderr, "%s: connect: %s:%d: %s\n", progname, pcRemote, ntohs(RCLevel.sain.sin_port), strerror(errno)); exit(EX_UNAVAILABLE); } if (0 != iRemConn && -1 == fcntl(s, F_SETFL, iFlags)) { fprintf(stderr, "%s: fcntl: %s\n", progname, strerror(errno)); exit(EX_OSERR); } if ((FILE *)0 == (fpMux = fdopen(s, "a+"))) { fprintf(stderr, "%s: fdopen: %s\n", progname, strerror(errno)); exit(EX_OSERR); } } /* send roapmux\r\n or get to our service, I so love tcpmux. */ if (3 > fprintf(fpMux, "%s\r\n", pcRoap)) { fprintf(stderr, "%s: write: %s\n", progname, strerror(errno)); exit(EX_OSERR); } /* expect "+$hostname\r\n", "+Go\r\n", or "-failure message\r\n" * shortest valid reply "+\r\n" which returns "+" to us. */ if ((char *)0 == fgets(acReply, sizeof(acReply), fpMux)) { fprintf(stderr, "%s: %s: %s: short reply\n", progname, pcRemote, pcRoap); exit(EX_CONFIG); } /* ksb's redirection extension to RFC1918, retry at new address. * Like "@nexthost.domain:port\r\n", where :port is optional. */ if ('@' == acReply[0]) { register char *pcClean; register int iNew; fclose(fpMux); if ((char *)0 != (pcClean = strchr(acReply, '\n'))) { *pcClean-- = '\000'; if (pcClean > acReply && '\r' == *pcClean) *pcClean-- = '\000'; } if ((char *)0 == (pcClean = strchr(acReply, ':'))) { iNew = 1; } else { *pcClean++ = '\000'; iNew = strtol(pcClean, (char **)0, 10); } wPort = (0 == iNew) ? 1 : iNew; return FetchRoap(acReply+1, wPort, ppcArgv, &RCLevel); } if ('+' != acReply[0]) { fprintf(stderr, "%s: %s: %s: %s\n", progname, pcRemote, pcRoap, &acReply['-' == acReply[0]]); exit(EX_NOINPUT); } if ((char *)0 == CredList(acReply, sizeof(acReply), getenv(acStampQuery))) { exit(EX_OSFILE); } /* Tell the roapmux who we think we are (login:groups:netgroups:domain). */ if (5 > fprintf(fpMux, "%s\r\n", acReply)) { fprintf(stderr, "%s: fprintf: %s\n", progname, strerror(errno)); exit(EX_OSERR); } /* Read reply header */ if ((char *)0 == fgets(acReply, sizeof(acReply), fpMux)) { fprintf(stderr, "%s: fgets: %s\n", progname, strerror(errno)); exit(EX_OSERR); } if ('+' != acReply[0]) { fprintf(stderr, "%s: %s\n", progname, ('-' == acReply[0])+acReply); exit(EX_NOPERM); } { /* When roapmux sent max,timeout,idle set those to be our min. */ auto int iNewMax, iNewIdle, iNewEnd; if (3 == sscanf(acReply, "+%d,%d,%d", &iNewMax, &iNewIdle, &iNewEnd)) { if (!fGaveAllowed) { iAllowed = iNewMax; fGaveAllowed = 1; } else if (0 != iNewMax && iNewMax < iAllowed) { iAllowed = iNewMax; } if (0 != iNewIdle && iNewIdle < iIdleSpan) { iIdleSpan = iNewIdle; pcIdleSpec = "from network"; } if (0 != iNewEnd && iNewEnd < iEndSpan) { iEndSpan = iNewEnd; pcEndSpec = "from network"; } if (!fSoMuchHate && -1 == iAllowed) { fprintf(stderr, "%s: %s: penalty mode activated (for %ld seconds)\n", progname, pcRemote, (long)iEndSpan); } fSoMuchHate = fSoMuchHate || -1 == iAllowed; } } /* read tableau list, merge with argv */ ppcRemote = ReadEnv(fpMux); fclose(fpMux); return fExchange ? EnvMerge(ppcArgv, ppcRemote) : EnvMerge(ppcRemote, ppcArgv); } /* The customer needs to reset the session, we don't care why. (ksb) * Mocks sudo -k and sudo -K mostly. */ static void AuthTerminate(int argc, char **argv, int cWhich) { register char *pcArg, *pcSocket; auto char acState[MAXPATHLEN+4]; auto int i, s, iCc = 0; auto char *apcFake[2]; auto int iRet; if ((char *)0 == argv[0]) { apcFake[0] = ""; apcFake[1] = (char *)0; argv = apcFake; } iRet = EX_OK; for (i = 0; (char *)0 != (pcArg = argv[i]); ++i) { if ('\000' == pcArg[0]) { pcArg = SockSpec(pcArg, getuid()); } pcSocket = StampPath(pcStampDir, pcArg); if (-1 == (s = Client(pcSocket))) { fprintf(stderr, "%s: no session at %s\n", progname, pcArg); iRet = EX_NOUSER; continue; } switch (cWhich) { case 'k': write(s, "Q\n", 2); /* expect reply 'q' or 'p' or 'error text' */ if (2 != read(s, acState, sizeof(acState)) || 'q' != acState[0]) { fprintf(stderr, "%s: %s: termination failed (%.*s)\n", progname, pcArg, iCc-1, acState); iRet = 1; } break; case 'K': write(s, "J\n", 2); /* expect reply 'pid\n', so "9\n" is shortest reply */ if (2 > read(s, acState, sizeof(acState)) || !isdigit(acState[0])) { fprintf(stderr, "%s: %s: cannot recover pid (%.*s)\n", progname, pcArg, iCc-1, acState); iRet = 1; } if (0 != kill(atoi(acState), SIGTERM)) { fprintf(stderr, "%s: kill: %ld: %s\n", progname, (long)atoi(acState), strerror(errno)); iRet = EX_NOPERM; } break; case 'N': write(s, "n\n", 2); if (2 != (iCc = read(s, acState, sizeof(acState))) || 'p' != acState[0]) { fprintf(stderr, "%s: %s: failed to set penalty mode\n", progname, pcArg); iRet = EX_NOPERM; } break; default: fprintf(stderr, "%s: %c: unknown termination protocol\n", progname, cWhich); iRet = EX_SOFTWARE; } close(s); } exit(iRet); } /* Like sudo -v: ping the stamp to keep idle from killing it (ksb) */ static void AuthRenew(int argc, char **argv) { register char *pcArg, *pcSocket; auto char acState[MAXPATHLEN+4]; auto int i, s, iCc; auto char *apcFake[2]; if ((char *)0 == argv[0]) { apcFake[0] = ""; apcFake[1] = (char *)0; argv = apcFake; } for (i = 0; (char *)0 != (pcArg = argv[i]); ++i) { if ('\000' == pcArg[0]) { pcArg = SockSpec(pcArg, getuid()); } pcSocket = StampPath(pcStampDir, pcArg); if (-1 == (s = Client(pcSocket))) { fprintf(stderr, "%s: no session at %s\n", progname, pcArg); exit(EX_NOUSER); } write(s, "P\n", 2); while (0 != (iCc = read(s, acState, sizeof(acState)))) { if ('y' != acState[0]) fprintf(stderr, "%s: %s: %.*s\n", progname, pcArg, iCc-1, acState); } close(s); } exit(EX_OK); } /* allow sig alarm to cleanly jump to cleanup code (ksb) */ static jmp_buf jbQuit; void /*ARGSUSED*/ Destruct(int iSig) { (void)longjmp(jbQuit, 1); } static const char acNotThere[] = "#no "; /* Run the authorization service for a session (ksb) * We are fork'd into the bg, hide from the world. Never exit from here. * mode, q=quit, y=authorized, n|p=penalty */ static void Service(int s, int iMode, int *piRemain, char **ppcCred) { register int c, i; register char **ppcScan, *pcNVP; auto char *pcBuffer; auto size_t wMaxNVP, w; auto fd_set fdsTemp, fdsReading; auto struct timeval tvLease, tvCmd; auto char acCmd[8], acReply[8]; /* 2 characters e.g ?\n or =" */ auto struct sigaction SAAlarm; for (wMaxNVP = 2, ppcScan = ppcCred; (char *)0 != (pcNVP = *ppcScan); ++ppcScan) { if (wMaxNVP < (w = strlen(pcNVP))) wMaxNVP = w; } wMaxNVP = (wMaxNVP|15)+65; if ((char *)0 == (pcBuffer = calloc(wMaxNVP, sizeof(char)))) { fprintf(stderr, "%s: calloc: %ld,%ld: %s\n", progname, (long)wMaxNVP, (long)sizeof(char), strerror(errno)); exit(EX_OSERR); } FD_ZERO(&fdsReading); FD_SET(s, &fdsReading); /* Arange to jump out after iEndSpan second, if EndSpan doesn't * allow at least 1 idle timeout, move it up to 1 idle period. */ if (0 != iEndSpan) { SAAlarm.sa_handler = Destruct; SAAlarm.sa_sigaction = (void *)0; SAAlarm.sa_flags = 0; memset((void *)&SAAlarm.sa_mask, 0, sizeof(SAAlarm.sa_mask)); sigaction(SIGALRM, &SAAlarm, (struct sigaction *)0); alarm((unsigned)iEndSpan); } if (iEndSpan < iIdleSpan) { iEndSpan = iIdleSpan; } tvLease.tv_sec = iIdleSpan; tvLease.tv_usec = 317811; /* fib27 about 0.3s grace */ /* Process commands for clients */ acReply[0] = 'E'; acReply[1] = '\n'; while ('q' != iMode) { fdsTemp = fdsReading; if (-1 == (i = select(s+1, &fdsTemp, (fd_set *)0, (fd_set *)0, &tvLease))) { if (errno == EINTR) { tvLease.tv_sec = iIdleSpan; continue; } break; } if (i == 0 || !FD_ISSET(s, &fdsTemp)) { /* session timeout */ break; } if (-1 == (c = accept(s, (struct sockaddr *)0, (socklen_t *)0))) { /* client went away? */ continue; } /* We have client, they better be quick, the whole * session with them is limited to the 2.2s. * 2 character cmds: Q\n, P\n, N\n, V\n ?\n, =\n \! */ tvCmd.tv_sec = 2; tvCmd.tv_usec = 196418; FD_ZERO(&fdsTemp); FD_SET(c, &fdsTemp); while (1 != (i = select(c+1, &fdsTemp, (fd_set *)0, (fd_set *)0, &tvLease))) { if (-1 == i && errno == EINTR) { continue; } iMode = 'q'; break; } if (0 == i) { write(c, "q\n", 2); break; } while (1 == read(c, acCmd, 1)) { if ('\n' == acCmd[0]) continue; if (1 != read(c, acCmd+1, 1)) { write(c, "q\n", 2); break; } switch (acCmd[0]) { case 'Q': case 'q': /* quit is stopped by penalty */ if ('y' == iMode) { acReply[0] = iMode = 'q'; } else { acReply[0] = 'p'; } write(c, acReply, 2); break; case 'P': case 'p': /* reset timer with a ping */ write(c, "y\n", 2); break; case 'N': case 'n': /* drop to deny mode */ acReply[0] = iMode = 'p'; write(c, acReply, 2); break; case 'V': case 'v': /* output protocol version */ write(c, acProtoVer, sizeof(acProtoVer)-1); break; case '?': /* are we allowed? */ acReply[0] = iMode; write(c, acReply, 2); break; case 'X': case 'x': /* silent exit (no reply) */ break; /* commands that allow follow-up requests */ case 'I': case 'i': /* disaplay idle timeout */ snprintf(pcBuffer, 64, "%ld\n", (long)iIdleSpan); write(c, pcBuffer, strlen(pcBuffer)); continue; case 'J': case 'j': /* display process-id */ snprintf(pcBuffer, 64, "%ld\n", (long)getpid()); write(c, pcBuffer, strlen(pcBuffer)); continue; case '=': /* "NAME"value" NAME==value */ case '!': /* "NAME"value" check for NAME!=value */ case '+': /* "NAME"ignore" check NAME exists */ case '$': /* "NAME"prefix" output "prefixNAME=value" */ /* the double quote can be any character * not in NAME or value (like \000). * * = $name == value iMode in list & have * ! $name != value iMode in list & not value * + $name check for NAME at all * * 'E' for missing separator * 'y' is yes and not in penalty * 'p' for penalty active * 'n' otherwise * * These commands do NOT drop the client * connection as you may want to check * another tableau entry (unless E sent). */ { register char *pcQuest, *pcIn; register size_t wL, wScan, iCc; register int cSep; auto char acNull[2]; /* After "=_" in "=_name_value_" (make cSep '_') * pcBuffer will get "name" and pcQuest gets * the "value". */ cSep = acCmd[1]; /* usually " or \n */ pcQuest = (char *)0; wScan = 0; for (pcIn = pcBuffer; 1 == (iCc = read(c, pcIn, 1)); ++pcIn) { if (++wScan > wMaxNVP) { acNull[0] = *pcIn; pcIn = acNull; } if (cSep != *pcIn) { continue; } *pcIn = '\000'; if ((char *)0 == pcQuest) pcQuest = pcIn+1; else break; } /* If you asked for a missing entry say no */ if (pcQuest == acNull+1) { acReply[0] = 'n'; write(c, acReply, 2); continue; } /* Command not finished is an error, close */ if (1 != iCc) { acReply[0] = 'E'; write(c, acReply, 2); break; } wL = strlen(pcBuffer); for (ppcScan = ppcCred; (char *)0 != (pcNVP = *ppcScan); ++ppcScan) { if ('=' == MergeOrder(pcNVP, pcBuffer)) break; } /* When we got the whole name, we may ignore * the too long value for '+' command. */ if (wScan > wMaxNVP) { acReply[0] = 'p' == iMode ? 'p' : (char *)0 == pcNVP || acCmd[0] != '+' ? 'n' : iMode; write(c, acReply, 2); continue; } acReply[0] = iMode; /* Not found or penalty, else try to compare */ if ('y' != iMode) { acReply[0] = 'p'; /* not active */ } else if ((char *)0 == pcNVP) { if ('$' == acCmd[0]) { auto struct iovec aIOV[4]; aIOV[0].iov_base = (char *)acNotThere; aIOV[0].iov_len = sizeof(acNotThere)-1; aIOV[1].iov_base = pcBuffer; aIOV[1].iov_len = wL; aIOV[2].iov_base = "\n78\n"; aIOV[2].iov_len = 4; (void)writev(c, aIOV, 3); continue; } acReply[0] = 'n'; } else switch (acCmd[0]) { case '=': if ('\000' == pcNVP[wL] || 0 != strcmp(pcQuest, pcNVP+wL+1)) acReply[0] = 'n'; break; case '!': /* Humm no value is not any value? */ if ('\000' == pcNVP[wL] || 0 == strcmp(pcQuest, pcNVP+wL+1)) acReply[0] = 'n'; break; case '+': /* found it, ignore value */ break; case '$': /* "prefixNAME=value" */ NVPReply(c, pcQuest, pcNVP); continue; default: acReply[0] = 'E'; /* snark! */ break; } write(c, acReply, 2); } continue; /* any unknown command reports "U\n" */ default: write(c, "U\n", 2); break; } break; } close(c); if ((int *)0 != piRemain && 0 >= --*piRemain) { break; } } /* We are done, one way or the other */ close(s); } /* Allow the stamp owner (or sentinel login) to create any stamp by (ksb) * name (maybe more than one, in fact). This is how we "login" a Customer. */ static void AuthForce(int argc, char **argv, char *pcName, char *pcUid, char *pcGid, char *pcMode, char *pcDrop, char *pcRemote) { register char *pc; register int s, c, i; auto char *pcDropGid, *pcSocket; auto int aiPipe[2]; auto int wMode; auto int wOwner, wRunUid; auto int wGroup, wRunGid; auto u_MODE_CVT MCPerms; auto struct sockaddr_un run; auto struct stat stSockDir; auto char acReply[8192]; /* may contain an error message */ /* Try to hide the argv NVP list from ps. --ksb */ #if HAVE_SETPROCTITLE (void)setproctitle("stamp"); #endif if ((char *)0 != pcUid && (char *)0 != (pc = strchr(pcUid, ':'))) { *pc++ = '\000'; if ('\000' != *pc && (char *)0 == pcGid) pcGid = pc; } /* If passed NAME=value we keep it, just NAME looks for NAME=value in * the environment which we'll take, else we delete it. */ for (s = i = 0; (char *)0 != (pc = argv[i]); ++i) { register char *pcEnv; register size_t w; if ((char *)0 != strchr(pc, '=')) { argv[s++] = pc; continue; } w = strlen(pc); for (c = 0; (char *)0 != (pcEnv = environ[c]); ++c) { if (0 != strncmp(pcEnv, pc, w) || '=' != pcEnv[w]) continue; break; } if ((char *)0 != pcEnv) { argv[s++] = pcEnv; } } argv[s] = (char *)0; argc = s; /* dead assignment for sanity */ if ('\000' == pcName[0] || (('-' == pcName[0] || '.' == pcName[0]) && '\000' == pcName[1])) { pcName = SockSpec(getenv(acStampFacility), getuid()); } if ((char *)0 == pcName || '\000' == *pcName) { fprintf(stderr, "%s: missing stamp name\n", progname); exit(EX_USAGE); } pcSocket = StampPath(pcStampDir, pcName); /* When stat fails, set group access when we specified a group * (I think this never happens.) */ stSockDir.st_mode = (char *)0 != pcGid ? 0660 : 0600; if ((char *)0 != (pc = strrchr(pcSocket, '/'))) { *pc = '\000'; (void)stat(pcSocket, &stSockDir); *pc = '/'; } wMode = stSockDir.st_mode & 0666; if ((char *)0 != pcMode) { mode_cvt(pcMode, &MCPerms, "-m"); wMode = MCPerms.iset|((0777&stSockDir.st_mode)&MCPerms.ioptional); } /* -D ksb:staff or -D :madness -g madness -m 0060 * When you set a Drop uid then that login may kill stamp process. */ wRunUid = -1; wRunGid = -1; if ((char *)0 == pcDrop) { pcDropGid = (char *)0; } else if ((char *)0 != (pcDropGid = strchr(pcDrop, ':'))) { *pcDropGid++ = '\000'; } /* Find the owner of the socket (real uid), effective just allows * the creation of the socket. If the effective it root we * can drop to anyone specifed in pcDrop. */ wOwner = getuid(); wGroup = getgid(); setpwent(); if ((char *)0 != (pc = pcUid) && '\000' != *pc) { register struct passwd *pw; if ((struct passwd *)0 != (pw = getpwnam(pc))) { wOwner = pw->pw_uid; } else if ((struct passwd *)0 != (pw = getpwuid(atoi(pc)))) { wOwner = pw->pw_uid; } else { fprintf(stderr, "%s: getpwent: %s: not found\n", progname, pc); exit(EX_DATAERR); } } if ((char *)0 != (pc = pcDrop) && '\000' != *pc) { register struct passwd *pw; if ((struct passwd *)0 != (pw = getpwnam(pc))) { wRunUid = pw->pw_uid; } else if ((struct passwd *)0 != (pw = getpwuid(atoi(pc)))) { wRunUid = pw->pw_uid; } else { fprintf(stderr, "%s: getpwent: %s: not found\n", progname, pc); exit(EX_DATAERR); } } endpwent(); getgrent(); if ((char *)0 != (pc = pcGid) && '\000' != *pc) { register struct group *grp; if ((struct group *)0 != (grp = getgrnam(pc))) { wGroup = grp->gr_gid; } else if ((struct group *)0 != (grp = getgrgid(atoi(pc)))) { wGroup = grp->gr_gid; } else { fprintf(stderr, "%s: getgrent: %s: not found\n", progname, pc); exit(EX_DATAERR); } } if ((char *)0 != (pc = pcDropGid) && '\000' != *pc) { register struct group *grp; if ((struct group *)0 != (grp = getgrnam(pc))) { wRunGid = grp->gr_gid; } else if ((struct group *)0 != (grp = getgrgid(atoi(pc)))) { wRunGid = grp->gr_gid; } else { fprintf(stderr, "%s: getgrent: %s: not found\n", progname, pc); exit(EX_DATAERR); } } endgrent(); if ((char *)0 != pcRemote && (char **)0 == (argv = FetchRoap(pcRemote, (struct servent *)0 == pSEConn ? 1 : htons(pSEConn->s_port), argv, (void *)0))) { fprintf(stderr, "%s: %s: %s\n", progname, pcRemote, strerror(errno)); exit(EX_PROTOCOL); } /* Is there a service up? */ if (-1 != (c = Client(pcSocket))) { if (0 != FlushWrite(c, "Q\n", 2)) { (void)unlink(pcSocket); /* when this fails bind might still work */ } else /* service exists get back "q\n" or "p\n" */ if (1 >= (i = read(c, acReply, sizeof(acReply)))) { fprintf(stderr, "%s: %s: dead socket\n", progname, pcSocket); } else if ('q' == acReply[0] || 'Q' == acReply[0]) { (void)unlink(pcSocket); } else { fprintf(stderr, "%s: %s: existing service refused to quit (%.*s)\n", progname, pcSocket, i-1, acReply); exit(EX_NOPERM); } close(c); } /* Make a clean service because we can't trust any old bits. */ if (-1 == (s = socket(PF_LOCAL, SOCK_STREAM, 0))) { fprintf(stderr, "%s: socket: %s\n", progname, strerror(errno)); exit(EX_TEMPFAIL); } run.sun_family = AF_UNIX; (void)strcpy(run.sun_path, pcSocket); if (0 == bind(s, (struct sockaddr *)&run, sizeof(run.sun_family)+strlen(run.sun_path)+2)) { /* happy */ } else if (-1 == unlink(pcSocket)) { fprintf(stderr, "%s: unlink: %s: %s\n", progname, pcSocket, strerror(errno)); exit(EX_OSERR); } else if (-1 == bind(s, (struct sockaddr *)&run, sizeof(run.sun_family)+strlen(run.sun_path)+2)) { fprintf(stderr, "%s: bind: %s\n", progname, strerror(errno)); exit(EX_OSERR); } if (-1 == listen(s, STAMP_LISTENQ)) { fprintf(stderr, "%s: listen: %s\n", progname, strerror(errno)); exit(EX_OSERR); } if (-1 == chown(pcSocket, wOwner, wGroup)) { fprintf(stderr, "%s: chown: %s: %s\n", progname, pcSocket, strerror(errno)); exit(EX_DATAERR); } if (-1 == chmod(pcSocket, wMode)) { fprintf(stderr, "%s: chmod: %s,%04o: %s\n", progname, pcSocket, wMode, strerror(errno)); exit(EX_DATAERR); } if (-1 == pipe(aiPipe)) { aiPipe[0] = aiPipe[1] = -1; } fflush(stdout); /* make the manager process */ switch (fork()) { case -1: fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); exit(EX_OSERR); /*NOTREACHED*/ case 0: close(aiPipe[0]); if (-1 == chdir(pcStampDir) && -1 == chdir("/")) { write(aiPipe[1], "chdir failed!\n", 14); exit(EX_UNAVAILABLE); } if (fChRoot) (void)chroot("."); setsid(); if (s != 0) close(0); if (s != 1) close(1); if (-1 != wRunGid) setgid(wRunGid); if (-1 != wRunUid) setuid(wRunUid); if (s != 2) close(2); close(aiPipe[1]); /* When max spec -0 or --100 we just deny 1, not any more; * if -n10, then we deny 10, so pain intensify set to max. * (The idea: a negative max might be an error, -n is not.) */ if (fGaveAllowed && iAllowed < 1) { fSoMuchHate = 1; iAllowed = -1; } if (0 == setjmp(jbQuit)) { Service(s, fSoMuchHate ? 'n' : 'y', fGaveAllowed ? &iAllowed : (int *)0, argv); } unlink(pcSocket); exit(EX_OK); /*NOTREACHED*/ default: close(aiPipe[1]); if (0 < read(aiPipe[0], acReply, sizeof(acReply))) { fprintf(stderr, "%s: %s\n", progname, acReply); fflush(stderr); exit(EX_UNAVAILABLE); } break; } exit(EX_OK); /*NOTEACHED*/ } /* We really want to buffer the whole tableau output, so we output (ksb) * nothing if any of the entries do not exist. * start *piMaxOut=0, we'll do the rest. Reset with (int *)0 == piMaxOut. */ static void AddCh(char **ppcOut, int *piOut, int *piMaxOut, int c, int fQuote) { register int iMaxOut, iOut; register char *pcOut; static int fBackSlash = 0; if ((int *)0 == piMaxOut) { /* reset logic */ fBackSlash = 0; if ((char **)0 != ppcOut) { free((void *)*ppcOut); *ppcOut = (char *)0; } if ((int *)0 != piOut) { *piOut = 0; } return; } if ((char **)0 == ppcOut || (int *)0 == piOut) { fprintf(stderr, "%s: pointer snark\n", progname); exit(EX_SOFTWARE); } if (0 >= (iMaxOut = *piMaxOut)) { iMaxOut = 2584; while (iMaxOut > 2 && (char *)0 == (*ppcOut = calloc(iMaxOut, sizeof(char)))) { iMaxOut /= 2; } if (iMaxOut < 3) { fprintf(stderr, "%s: calloc: %ld,%ld: %s\n", progname, (long)iMaxOut, (long)sizeof(char), strerror(errno)); exit(EX_OSERR); } *piMaxOut = iMaxOut; *piOut = 0; } iOut = *piOut; if (iMaxOut <= iOut) { register char *pcResize; iMaxOut *= 89; iMaxOut /= 55; ++iMaxOut; while (iMaxOut > iOut && (char *)0 == (pcResize = realloc((void *)*ppcOut, iMaxOut))) { iMaxOut -= 1024; } if ((char *)0 == pcResize || iOut+1 >= iMaxOut) { fprintf(stderr, "%s: realloc: %ld: %s\n", progname, (long int)iMaxOut, strerror(ENOMEM)); exit(EX_OSERR); } *ppcOut = pcResize; } pcOut = *ppcOut; if (!fQuote) { pcOut[iOut++] = c; } else /* in quotes, look for blackslashes, cribbed from op.m ExtInput */ if (fBackSlash) { switch (c) { /* \n, \", \^M \o \\ */ case '\n': /* \^M is the empty string in sh and C */ break; case 'a': pcOut[iOut++] = '\007'; break; case 'b': pcOut[iOut++] = '\b'; break; case 'd': pcOut[iOut++] = '"'; break; /* op special */ case 'f': pcOut[iOut++] = '\f'; break; case 'n': pcOut[iOut++] = '\n'; break; case 'o': pcOut[iOut++] = '`'; break; /* op special */ case 'q': pcOut[iOut++] = '\''; break; /* op special */ case 'r': pcOut[iOut++] = '\r'; break; case 's': pcOut[iOut++] = ' '; break; case 't': pcOut[iOut++] = '\t'; break; case 'v': pcOut[iOut++] = '\v'; break; default: /* includes \\ */ pcOut[iOut++] = c; break; } fBackSlash = 0; } else if ('\\' == c) { fBackSlash = 1; } else { pcOut[iOut++] = c; } *piOut = iOut; } /* Find the requested tableau entries, output them as shell "words" (ksb) * so Dude="`stampctl -Q /tmp/s0 ROAP_LOGIN`" * does something useful. */ static void /*ARGSUSED*/ Query(int argc, char **argv, char *pcStamp, int fExtLike) { register int s, i, iCc; register char *pc; auto char cOK, *pcOut; auto int fdOut, fQuote, fSep, iOut, iMaxOut; auto char *pcSocket; auto struct iovec aIOV[4]; auto char acBlock[8192]; if ((char *)0 == pcStamp || '\000' == pcStamp[0]) { pcStamp = SockSpec(pcStamp, getuid()); } pcSocket = StampPath(pcStampDir, pcStamp); if (-1 == (s = Client(pcSocket))) { fprintf(stderr, "%s: no session at %s\n", progname, pcStamp); exit(EX_NOUSER); } pcOut = (char *)0; iMaxOut = iOut = fSep = 0; while ((char *)0 != (pc = *argv++)) { aIOV[0].iov_base = "$\000"; aIOV[0].iov_len = 2; aIOV[1].iov_base = pc; aIOV[1].iov_len = strlen(pc); aIOV[2].iov_base = "\000\000"; aIOV[2].iov_len = 2; if (aIOV[0].iov_len+aIOV[1].iov_len +aIOV[2].iov_len != writev(s, aIOV, 3) || 1 != read(s, &cOK, 1)) { fprintf(stderr, "%s: %s: lost connection\n", progname, pcSocket); exit(EX_OSERR); } /* 3 cases * failed "#no NVP X\n78\n" look for /acNotThere/ above * or $"VAR=value"\n (NVPReply encoded) * or $VAR=value\n (NVPReply) */ fQuote = 0, fdOut = 1; if (acNotThere[0] == cOK) { fdOut = 2; fprintf(stderr, "%s: %s: %s: no such tableau entry\n", progname, pcStamp, pc); exit(78); } if (fExtLike) { AddCh(&pcOut, &iOut, &iMaxOut, cOK, 0); } if (1 != read(s, &cOK, 1)) { read_fail: fprintf(stderr, "%s: read: %s: %s\n", progname, pcStamp, strerror(errno)); exit(EX_OSERR); } switch (cOK) { case '\"': if (fExtLike) AddCh(&pcOut, &iOut, &iMaxOut, cOK, 0); fQuote = !fExtLike; /*FALLTHROUGH*/ do { if (1 != read(s, &cOK, 1)) { goto read_fail; } default: if (cOK != *pc) { fprintf(stderr, "%s: %s: %s: reply garbled\n", progname, pcStamp, pc); exit(EX_SOFTWARE); } if (fExtLike) { AddCh(&pcOut, &iOut, &iMaxOut, cOK, fQuote); } } while ('\000' != *++pc); break; } if (!fExtLike) { if (1 != read(s, &cOK, 1) || '=' != cOK) goto read_fail; if (fSep) AddCh(&pcOut, &iOut, &iMaxOut, ' ', fQuote); fSep = 1; } while (0 < (iCc = read(s, acBlock, sizeof(acBlock)))) { register int fQuit; /* And of message, if we are in quotes we should * also remove the ending quote, it may have been * in the last read */ if (0 != (fQuit = '\n' == acBlock[iCc-1])) { --iCc; if (!fQuote || fExtLike) /*none to remove */; else if (0 == iCc) --iOut; else --iCc; } for (i = 0; i < iCc; ++i) { AddCh(&pcOut, &iOut, &iMaxOut, acBlock[i], fQuote); } if (fQuit) break; } if (fExtLike) { AddCh(&pcOut, &iOut, &iMaxOut, '\n', 0); } } close(s); if (!fExtLike) { AddCh(&pcOut, &iOut, &iMaxOut, '\n', 0); } (void)FlushWrite(1, pcOut, iOut); exit(EX_OK); /*NOTREACHED*/ }