# $Id: stampproto.m,v 1.9 2012/11/02 21:56:36 ksb Exp $

%i
static const char acProtoVer[] = "1\n";

/* Default timestamp top-level (no run-time override).
 */
#if !defined(STAMP_PATHFMT)
#define STAMP_PATHFMT		"/var/op/%s"
#endif

/* Params to credential a given escalation are encode in this variable.
 * For example under op we might ask for a wheel stamp for the real
 * login name with the same tty as we have now:
 *	$STAMP_SPEC=wheel/$l:TTY=$y
 */
#if !defined(STAMP_SPEC)
#define STAMP_SPEC		"STAMP_SPEC"
#endif

/* Copy tableau values to the environment of the escalated process:
 * Note that this only works with op versions > 2.144.
 *	$STAMP_SET=TERM:TERMCAP:EDITOR
 * No default for this.
 */
#if !defined(STAMP_SET)
#define STAMP_SET		"STAMP_SET"
#endif

/* Default credential search is to look for STAMP_PATHFMT applied
 * to our real uid as a decimal number (/var/op/7890) and look for
 * no special name-value-pair.
 */
#if !defined(STAMP_DEFREQ)
#define STAMP_DEFREQ		":"
#endif

/* We build a socket, so we need a (small) listen queue length
 */
#if !defined(STAMP_LISTENQ)
#define STAMP_LISTENQ	13
#endif

/* Add these variable when set in the environment, for example to force
 * the name of the login that authenticated the session:
 *	$AUTH_LOGIN=$l
 */
#if !defined(STAMP_REVEAL)
#define STAMP_REVEAL		"STAMP_REVEAL"
#endif


/* We can separate authentications into subdirs to allow more than
 * one policy for each login.  Add a directory to the path with
 * this specification.  The default is none.  For example "blotto"
 * makes the path me /var/op/stamp/blotto/200 for an owner with
 * uid 200.
 */
#if !defined(STAMP_FACILITY)
#define STAMP_FACILITY		"STAMP_FACILITY"
#endif

#if !defined(STAMP_DEFAUTH_FACILITY)
#define STAMP_DEFAUTH_FACILITY	"stamp"
#endif

/* When a remote stamp wants to pass params to the roap service they
 * are sent via this environment variable.
 */
#if !defined(STAMP_QUERY)
#define STAMP_QUERY		"STAMP_QUERY"
#endif

/* What to tell the customer that failed to credential, if not the
 * default "authentication failed".  (I like "Sorry.")
 */
#if !defined(STAMP_WARN)
#define STAMP_WARN		"STAMP_WARN"
#endif

/* Stamp status options TBS is "Is is ok To Build Stamp?"
 */
#define TBS_NOSYS	'S'	/* no system support installed	*/
#define TBS_AVAIL	'A'	/* no socket for login/uid/gid	*/
#define TBS_SEEN	'H'	/* socket exists		*/
#define TBS_OK		'O'	/* socket is up and authentic	*/
#define TBS_PENALTY	'P'	/* socket denys access, penalty	*/
#define TBS_MESSAGE	'M'	/* system message denys access	*/
#define TBS_ERROR	'E'	/* misconfigured directory	*/
#define TBS_DIR		'D'	/* is a directory, not a socket	*/

/* Some older hosts do not have PF_LOCAL or AF_LOCAL, use UNIX
 */
#if !defined(PF_LOCAL)
#if !defined(AF_LOCAL)
#define AF_LOCAL	AF_UNIX
#endif
#define PF_LOCAL	AF_LOCAL
#endif

#if !defined(HAVE_SETPROCTITLE)
#define HAVE_SETPROCTITLE (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
#endif
%%


%i
static const char rcsAPI[] =
	"$Id: stampproto.m,v 1.9 2012/11/02 21:56:36 ksb Exp $";

typedef struct SInode {
	uid_t uOwner;
	gid_t gOwner;
	mode_t mOwner;
} StampInfo;
static int Climbing(register char *);
static const char *SetCache();
static int SysStatus(char *, char **, char **, StampInfo *);
static int Client(char *pcEndPt);
static char *SockSpec(char *, uid_t);
static char *StampPath(const char *, char *);

static const char
	acStampFmt[] = STAMP_PATHFMT,
	*pcStampFacility = STAMP_FACILITY;
%%

%c
/* locate the cache directory from the environment			(ksb)
 * pcStampFacility ($STAMP_FACILITY) is set to an absolute path we use it
 * as the prefix, else we use it as a faclility.
 * When not set we use a default faclility ("stamp") which could also be
 * an absolute path (like /var/non-existing-thing).
 */
static const char *
SetCache()
{
	register const char *pcFacility;
	auto char acBuild[MAXPATHLEN+4];

	if ((char *)0 == (pcFacility = getenv(pcStampFacility))) {
		pcFacility = acDefAuthFacility;
	}
	if ('/' == pcFacility[0]) {
		return pcFacility;
	}
	snprintf(acBuild, sizeof(acBuild), acStampFmt, pcFacility);
	return strdup(acBuild);
}

/* Return the status of the timestamp structure				(ksb)
 * We write on pcTop to process the path, but we put it back.
 */
static int
SysStatus(char *pcTop, char **ppcText, char **ppcMessage, StampInfo *pSI)
{
	auto struct stat stTop;
	auto char *pc_1, *pc_2;

	/* avoid bad params by replacing NULLs with diversions to noplace
	 */
	if ((char **)0 == ppcText)
		ppcText = &pc_1;
	if ((char **)0 == ppcMessage)
		ppcMessage = &pc_2;
	*ppcMessage = *ppcText = (char *)0;

	/* Set default perms
	 */
	pSI->uOwner = -1;
	pSI->gOwner = -1;
	pSI->mOwner = 0666;
	if ((const char *)0 == pcTop) {
		*ppcText = "no stamp name";
		return TBS_NOSYS;
	}

	/* If the tail doesn't exist see if the parent does
	 */
	if (-1 == stat(pcTop, &stTop)) {
		register char *pcSlash;
		register int iCall;

		if (errno != ENOENT) {
			*ppcText = strerror(errno);
			return TBS_NOSYS;
		}
		if ((char *)0 != (pcSlash = strrchr(pcTop, '/'))) {
			while (pcSlash > pcTop && '/' == pcSlash[-1])
				--pcSlash;
		}
		if ((char *)0 == pcSlash || pcSlash == pcTop) {
			*ppcText = "no such directory";
			return TBS_NOSYS;
		}
		*pcSlash = '\000';
		iCall = SysStatus(pcTop, ppcText, ppcMessage, pSI);
		*pcSlash = '/';
		if (TBS_DIR == iCall)
			return TBS_AVAIL;
		return iCall;
	}
	pSI->uOwner = stTop.st_uid;
	pSI->gOwner = stTop.st_gid ;
	pSI->mOwner = stTop.st_mode & (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
	if (S_ISSOCK(stTop.st_mode)) {
		return TBS_SEEN;
	}
	if (S_ISDIR(stTop.st_mode)) {
		return TBS_DIR;
	}
	if (S_ISREG(stTop.st_mode)) {
		*ppcText = "timestamp support disabled by message file";
		*ppcMessage = pcTop;
		return TBS_MESSAGE;
	}
	if (0 == (stTop.st_mode & (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH))) {
		*ppcText = "timestamp support disabled by permissions";
		/* no message, sadly */
		return TBS_MESSAGE;
	}

	*ppcText = "timestamp support disabled non-directory";
	return TBS_ERROR;
}

/* Open a connection to the named domain socket				(ksb)
 */
static int
Client(char *pcEndPt)
{
	auto struct sockaddr_un run;
	register int sRet, e;

	memset((void *)& run, '\000', sizeof(run));
	run.sun_family = AF_UNIX;
	if (strlen(pcEndPt) > sizeof(run.sun_path)-1) {
		errno = ENAMETOOLONG;
		return -1;
	}
	(void)strcpy(run.sun_path, pcEndPt);
	if (-1 == (sRet = socket(AF_UNIX, SOCK_STREAM, 0))) {
		return -1;
	}
	if (-1 != connect(sRet, (struct sockaddr *)&run, strlen(pcEndPt)+2)) {
		(void)fcntl(sRet, F_SETFD, 1);
		return sRet;
	}
	e = errno;
	close(sRet);
	errno = e;
	return -1;
}

/* Return 1 when the path tries to climb ".."				(ksb)
 */
static int
Climbing(register char *pcArg)
{
	register size_t w = strlen(pcArg);
	return 0 == strcmp("..", pcArg) ||
		0 == strncmp(pcArg, "../", 3) ||
		(char *)0 != strstr(pcArg, "/../") ||
		(w >= 3 && 0 == strcmp(pcArg+w-3, "/.."));
}

/* Take a name for the socket in the facility directory, or make one	(ksb)
 * We won't accept ../foo, change the facility to an absolute path.
 */
static char *
SockSpec(char *pc, uid_t wDefOwner)
{
	auto char acCvt[64];

	while ((char *)0 != pc && '.' == pc[0] && '/' == pc[1]) {
		while ('/' == *++pc)
			/* skip */;
	}
	if ((char *)0 == pc || '\000' == *pc) {
		snprintf(acCvt, sizeof(acCvt), "%ld", (long)wDefOwner);
		return strdup(acCvt);
	}

	if (Climbing(pc)) {
		fprintf(stderr, "%s: %s: climbing not allowed\n", progname, pc);
		printf("%d\n", EX_CONFIG);
		exit(EX_CONFIG);
	}
	return pc;
}

/* Given a socket specification find the stamp for it			(ksb)
 * If the specificaion it the empty string, call SockSpec to get the
 * default before you call this function.
 */
static char *
StampPath(const char *pcSpec, char *pcName)
{
	auto char acPath[MAXPATHLEN+4];

	if ('/' == pcName[0])
		return pcName;
	if ((char *)0 == pcSpec && (char *)0 == (pcSpec = SetCache()))
		return (char *)0;
	snprintf(acPath, sizeof(acPath), "%s/%s", pcSpec, pcName);
	return strdup(acPath);
}
%%
