/* pam_misc.c -- This is random stuff */

/* $Id: pam_misc.c,v 1.9 1997/04/05 06:56:19 morgan Exp $
 *
 * $Log: pam_misc.c,v $
 * Revision 1.9  1997/04/05 06:56:19  morgan
 * enforce AUTHTOK restrictions
 *
 * Revision 1.8  1997/02/15 15:59:46  morgan
 * modified ..strCMP comment
 *
 * Revision 1.7  1996/12/01 03:14:13  morgan
 * use _pam_macros.h
 *
 * Revision 1.6  1996/11/10 20:05:52  morgan
 * name convention _pam_ enforced. Also modified _pam_strdup()
 *
 * Revision 1.5  1996/07/07 23:57:14  morgan
 * deleted debuggin function and replaced it with a static function
 * defined in pam_private.h
 *
 * Revision 1.4  1996/06/02 08:00:56  morgan
 * added StrTok function
 *
 * Revision 1.3  1996/05/21 04:36:58  morgan
 * added debugging information
 * replaced the _pam_log need for a local buffer with a call to vsyslog()
 * [Al Longyear had some segfaulting problems related to this]
 *
 * Revision 1.2  1996/03/16 21:55:13  morgan
 * changed pam_mkargv to _pam_mkargv
 *
 */

#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <ctype.h>

#include "pam_private.h"

/* caseless string comparison: POSIX does not define this.. */
int _pam_strCMP(const char *s, const char *t)
{
    int cf;

    do {
	cf = tolower(*s) - tolower(*t);
	++t;
    } while (!cf && *s++);

    return cf;
}

char *_pam_StrTok(char *from, const char *format, char **next)
/*
 * this function is a variant of the standard strtok, it differs in that
 * it takes an additional argument and doesn't nul terminate tokens until
 * they are actually reached.
 */
{
     char table[256], *end;
     int i;

     if (from == NULL && (from = *next) == NULL)
	  return from;

     /* initialize table */
     for (i=1; i<256; table[i++] = '\0');
     for (i=0; format[i] ; table[(int)format[i++]] = 'y');

     /* look for first non-blank char */
     while (*from && table[(int)*from]) {
	  ++from;
     }

     if (*from == '[') {
	 /*
	  * special case, "[...]" is considered to be a single
	  * object.  Note, however, if one of the format[] chars is
	  * '[' this single string will not be read correctly.
	  */
	 for (end=++from; *end && *end != ']'; ++end) {
	     if (*end == '\\' && end[1] == ']')
		 ++end;
	 }
	 /* note, this string is stripped of its edges: "..." is what
            remains */
     } else if (*from) {
	 /* simply look for next blank char */
	 for (end=from; *end && !table[(int)*end]; ++end);
     } else {
	 return (*next = NULL);                    /* no tokens left */
     }

     /* now terminate what we have */
     if (*end)
	 *end++ = '\0';

     /* indicate what it left */
     if (*end) {
	 *next = end;
     } else {
	 *next = NULL;                      /* have found last token */
     }

     /* return what we have */
     return from;
}

/*
 * Safe duplication of character strings. "Paranoid"; don't leave
 * evidence of old token around for later stack analysis.
 */

char *_pam_strdup(const char *x)
{
     register char *new=NULL;

     if (x != NULL) {
	  register int i;

	  for (i=0; x[i]; ++i);                       /* length of string */
	  if ((new = malloc(++i)) == NULL) {
	       i = 0;
	       _pam_log_error("_pam_strdup: failed to get memory");
	  } else {
	       while (i-- > 0) {
		    new[i] = x[i];
	       }
	  }
	  x = NULL;
     }

     return new;                 /* return the duplicate or NULL on error */
}

/* Generate argv, argc from s */
/* caller must free(argv)     */

int _pam_mkargv(char *s, char ***argv, int *argc)
{
    int l;
    int argvlen = 0;
    char *sbuf, *sbuf_start;
    char **our_argv = NULL;
    char **argvbuf;
    char *argvbufp;
#ifdef DEBUG
    int count=0;
#endif

    D(("_pam_mkargv called: %s",s));

    *argc = 0;

    l = strlen(s);
    if (l) {
	if ((sbuf = sbuf_start = _pam_strdup(s)) == NULL) {
	    _pam_log_error("pam_mkargv: null returned by _pam_strdup");
	    D(("arg NULL"));
	} else {
	    /* Overkill on the malloc, but not large */
	    argvlen = (l + 1) * ((sizeof(char)) + sizeof(char *));
	    if ((our_argv = argvbuf = malloc(argvlen)) == NULL) {
		_pam_log_error("pam_mkargv: null returned by malloc");
	    } else {
		char *tmp=NULL;

		argvbufp = (char *) argvbuf + (l * sizeof(char *));
		D(("[%s]",sbuf));
		while ((sbuf = _pam_StrTok(sbuf, " \n\t", &tmp))) {
		    D(("arg #%d",++count));
		    D(("->[%s]",sbuf));
		    strcpy(argvbufp, sbuf);
		    D(("copied token"));
		    *argvbuf = argvbufp;
		    argvbufp += strlen(argvbufp) + 1;
		    D(("stepped in argvbufp"));
		    (*argc)++;
		    argvbuf++;
		    sbuf = NULL;
		    D(("loop again?"));
		}
		_pam_drop(sbuf_start);
	    }
	}
    }
    
    *argv = our_argv;

    D(("_pam_mkargv returned"));

    return(argvlen);
}

#ifdef __hpux
# include <stdio.h>
# include <syslog.h>
# ifdef __STDC__
#  ifndef __P
#   define __P(p)  p
#  endif /* __P */
#  include <stdarg.h>
#  define VA_LOCAL_DECL	va_list ap;
#  define VA_START(f)	va_start(ap, f)
#  define VA_END	va_end(ap)
# else /* __STDC__ */
#  ifndef __P
#   define __P(p)  ()
#  endif /* __P */
#  include <varargs.h>
#  define VA_LOCAL_DECL	va_list ap;
#  define VA_START(f)	va_start(ap)
#  define VA_END	va_end(ap)
# endif /* __STDC__ */
/**************************************************************
 * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
 * A bombproof version of doprnt (dopr) included.
 * Sigh.  This sort of thing is always nasty do deal with.  Note that
 * the version here does not include floating point...
 *
 * snprintf() is used instead of sprintf() as it does limit checks
 * for string length.  This covers a nasty loophole.
 *
 * The other functions are there to prevent NULL pointers from
 * causing nast effects.
 **************************************************************/

static void dopr();
static char *end;
# ifndef _SCO_DS
/* VARARGS3 */
int
#  ifdef __STDC__
snprintf(char *str, size_t count, const char *fmt, ...)
#  else /* __STDC__ */
snprintf(str, count, fmt, va_alist)
	char *str;
	size_t count;
	const char *fmt;
	va_dcl
#  endif /* __STDC__ */
{
	int len;
	VA_LOCAL_DECL

	VA_START(fmt);
	len = vsnprintf(str, count, fmt, ap);
	VA_END;
	return len;
}
# endif /* _SCO_DS */

int
# ifdef __STDC__
vsnprintf(char *str, size_t count, const char *fmt, va_list args)
# else /* __STDC__ */
vsnprintf(str, count, fmt, args)
	char *str;
	int count;
	char *fmt;
	va_list args;
# endif /* __STDC__ */
{
	str[0] = 0;
	end = str + count - 1;
	dopr( str, fmt, args );
	if (count > 0)
		end[0] = 0;
	return strlen(str);
}

/*
 * dopr(): poor man's version of doprintf
 */

static void fmtstr __P((char *value, int ljust, int len, int zpad,
			int maxwidth));
static void fmtnum __P((long value, int base, int dosign, int ljust, int len,
			int zpad));
static void dostr __P(( char * , int ));
static char *output;
static void dopr_outch __P(( int c ));

static void
# ifdef __STDC__
dopr(char  * buffer, const char * format, va_list args )
# else /* __STDC__ */
dopr( buffer, format, args )
       char *buffer;
       char *format;
       va_list args;
# endif /* __STDC__ */
{
       int ch;
       long value;
       int longflag  = 0;
       int pointflag = 0;
       int maxwidth  = 0;
       char *strvalue;
       int ljust;
       int len;
       int zpad;

       output = buffer;
       while( (ch = *format++) ){
               switch( ch ){
               case '%':
                       ljust = len = zpad = maxwidth = 0;
                       longflag = pointflag = 0;
               nextch:
                       ch = *format++;
                       switch( ch ){
                       case 0:
                               dostr( "**end of format**" , 0);
                               return;
                       case '-': ljust = 1; goto nextch;
                       case '0': /* set zero padding if len not set */
                               if(len==0 && !pointflag) zpad = '0';
                       case '1': case '2': case '3':
                       case '4': case '5': case '6':
                       case '7': case '8': case '9':
			       if (pointflag)
				 maxwidth = maxwidth*10 + ch - '0';
			       else
				 len = len*10 + ch - '0';
                               goto nextch;
		       case '*': 
			       if (pointflag)
				 maxwidth = va_arg( args, int );
			       else
				 len = va_arg( args, int );
			       goto nextch;
		       case '.': pointflag = 1; goto nextch;
                       case 'l': longflag = 1; goto nextch;
                       case 'u': case 'U':
                               /*fmtnum(value,base,dosign,ljust,len,zpad) */
                               if( longflag ){
                                       value = va_arg( args, long );
                               } else {
                                       value = va_arg( args, int );
                               }
                               fmtnum( value, 10,0, ljust, len, zpad ); break;
                       case 'o': case 'O':
                               /*fmtnum(value,base,dosign,ljust,len,zpad) */
                               if( longflag ){
                                       value = va_arg( args, long );
                               } else {
                                       value = va_arg( args, int );
                               }
                               fmtnum( value, 8,0, ljust, len, zpad ); break;
                       case 'd': case 'D':
                               if( longflag ){
                                       value = va_arg( args, long );
                               } else {
                                       value = va_arg( args, int );
                               }
                               fmtnum( value, 10,1, ljust, len, zpad ); break;
                       case 'x':
                               if( longflag ){
                                       value = va_arg( args, long );
                               } else {
                                       value = va_arg( args, int );
                               }
                               fmtnum( value, 16,0, ljust, len, zpad ); break;
                       case 'X':
                               if( longflag ){
                                       value = va_arg( args, long );
                               } else {
                                       value = va_arg( args, int );
                               }
                               fmtnum( value,-16,0, ljust, len, zpad ); break;
                       case 's':
                               strvalue = va_arg( args, char *);
			       if (maxwidth > 0 || !pointflag) {
				 if (pointflag && len > maxwidth)
				   len = maxwidth; /* Adjust padding */
				 fmtstr( strvalue,ljust,len,zpad, maxwidth);
			       }
			       break;
                       case 'c':
                               ch = va_arg( args, int );
                               dopr_outch( ch ); break;
                       case '%': dopr_outch( ch ); continue;
                       default:
                               dostr(  "???????" , 0);
                       }
                       break;
               default:
                       dopr_outch( ch );
                       break;
               }
       }
       *output = 0;
}

static void
fmtstr(  value, ljust, len, zpad, maxwidth )
       char *value;
       int ljust, len, zpad, maxwidth;
{
       int padlen, strlen;     /* amount to pad */

       if( value == 0 ){
               value = "<NULL>";
       }
       for( strlen = 0; value[strlen]; ++ strlen ); /* strlen */
       if (strlen > maxwidth && maxwidth)
	 strlen = maxwidth;
       padlen = len - strlen;
       if( padlen < 0 ) padlen = 0;
       if( ljust ) padlen = -padlen;
       while( padlen > 0 ) {
               dopr_outch( ' ' );
               --padlen;
       }
       dostr( value, maxwidth );
       while( padlen < 0 ) {
               dopr_outch( ' ' );
               ++padlen;
       }
}

static void
fmtnum(  value, base, dosign, ljust, len, zpad )
       long value;
       int base, dosign, ljust, len, zpad;
{
       int signvalue = 0;
       unsigned long uvalue;
       char convert[20];
       int place = 0;
       int padlen = 0; /* amount to pad */
       int caps = 0;

       /* DEBUGP(("value 0x%x, base %d, dosign %d, ljust %d, len %d, zpad %d\n",
               value, base, dosign, ljust, len, zpad )); */
       uvalue = value;
       if( dosign ){
               if( value < 0 ) {
                       signvalue = '-';
                       uvalue = -value;
               }
       }
       if( base < 0 ){
               caps = 1;
               base = -base;
       }
       do{
               convert[place++] =
                       (caps? "0123456789ABCDEF":"0123456789abcdef")
                        [uvalue % (unsigned)base  ];
               uvalue = (uvalue / (unsigned)base );
       }while(uvalue);
       convert[place] = 0;
       padlen = len - place;
       if( padlen < 0 ) padlen = 0;
       if( ljust ) padlen = -padlen;
       /* DEBUGP(( "str '%s', place %d, sign %c, padlen %d\n",
               convert,place,signvalue,padlen)); */
       if( zpad && padlen > 0 ){
               if( signvalue ){
                       dopr_outch( signvalue );
                       --padlen;
                       signvalue = 0;
               }
               while( padlen > 0 ){
                       dopr_outch( zpad );
                       --padlen;
               }
       }
       while( padlen > 0 ) {
               dopr_outch( ' ' );
               --padlen;
       }
       if( signvalue ) dopr_outch( signvalue );
       while( place > 0 ) dopr_outch( convert[--place] );
       while( padlen < 0 ){
               dopr_outch( ' ' );
               ++padlen;
       }
}

static void
dostr( str , cut)
     char *str;
     int cut;
{
  if (cut) {
    while(*str && cut-- > 0) dopr_outch(*str++);
  } else {
    while(*str) dopr_outch(*str++);
  }
}

static void
dopr_outch( c )
       int c;
{
       if( end == 0 || output < end )
               *output++ = c;
}

int 
# ifdef __STDC__
vsyslog(int priority, const char *fmt, ...)
# else /* __STDC__ */
vsyslog(priority, fmt, va_alist)
  int priority;
  const char *fmt;
  va_dcl
# endif /* __STDC__ */
{
    VA_LOCAL_DECL
    char logbuf[BUFSIZ];
    
    VA_START(fmt);

    vsnprintf(logbuf, BUFSIZ, fmt, ap);
    syslog(priority, "%s", logbuf);

    VA_END;
}
#endif /* __hpux */

void _pam_log_error(const char *format, ...)
{
    va_list args;

#if defined(DEBUG) && !defined(__hpux)
# define ADDPERROR |LOG_PERROR
#else
# define ADDPERROR
#endif

    va_start(args, format);
    openlog("pam", LOG_CONS|LOG_PID ADDPERROR, LOG_AUTH);
    vsyslog(LOG_CRIT, format, args);
    va_end(args);
    closelog();
}

/*
 * this function is used to protect the modules from accidental or
 * semi-mallicious harm that an application may do to confuse the API.
 */

void _pam_sanitize(pam_handle_t *pamh)
{
    /*
     * this is for security. We reset the auth-tokens here.
     */
    pam_set_item(pamh,PAM_AUTHTOK,NULL);
    pam_set_item(pamh,PAM_OLDAUTHTOK,NULL);
}

/*
 * This function scans the array and replaces the _PAM_ACTION_UNDEF
 * entries with the default action.
 */

void _pam_set_default_control(int *control_array, int default_action)
{
    int i;

    for (i=0; i<_PAM_RETURN_VALUES; ++i) {
	if (control_array[i] == _PAM_ACTION_UNDEF) {
	    control_array[i] = default_action;
	}
    }
}

/*
 * This function is used to parse a control string.  This string is a
 * series of tokens of the following form:
 *
 *               "[ ]*return_code[ ]*=[ ]*action/[ ]".
 */

#include "pam_tokens.h"

void _pam_parse_control(int *control_array, char *tok)
{
    const char *error;
    int ret;

    while (*tok) {
	int act, len;

	/* skip leading space */
	while (isspace(*tok) && *++tok);
	if (!*tok)
	    break;

	/* identify return code */
	for (ret=0; ret<=_PAM_RETURN_VALUES; ++ret) {
	    len = strlen(_pam_token_returns[ret]);
	    if (!strncmp(_pam_token_returns[ret], tok, len)) {
		break;
	    }
	}
	if (ret > _PAM_RETURN_VALUES || !*(tok += len)) {
	    error = "expecting return value";
	    goto parse_error;
	}

	/* observe '=' */
	while (isspace(*tok) && *++tok);
	if (!*tok || *tok++ != '=') {
	    error = "expecting '='";
	    goto parse_error;
	}
	
	/* skip leading space */
	while (isspace(*tok) && *++tok);
	if (!*tok) {
	    error = "expecting action";
	    goto parse_error;
	}

	/* observe action type */
	for (act=0; act<=-_PAM_ACTION_UNDEF; ++act) {
	    len = strlen(_pam_token_actions[act]);
	    if (!strncmp(_pam_token_actions[act], tok, len)) {
		act *= -1;
		tok += len;
		break;
	    }
	}
 	if (act > 0) {
	    /*
	     * Either we have a number or we have hit an error.  In
	     * principle, there is nothing to stop us accepting
	     * negative offsets. (Although we would have to think of
	     * another way of encoding the tokens.)  However, I really
	     * think this would be both hard to administer and easily
	     * cause looping problems.  So, for now, we will just
	     * allow forward jumps.  (AGM 1998/1/7)
	     */
	    if (!isdigit(*tok)) {
		error = "expecting jump number";
		goto parse_error;
	    }
	    /* parse a number */
	    act = 0;
	    do {
		act *= 10;
		act += *tok - '0';      /* XXX - this assumes ascii behavior */
	    } while (*++tok && isdigit(*tok));
	    if (! act) {
		/* we do not allow 0 jumps.  There is a token ('ignore')
                   for that */
		error = "expecting non-zero";
		goto parse_error;
	    }
	}

	/* set control_array element */
	if (ret != _PAM_RETURN_VALUES) {
	    control_array[ret] = act;
	} else {
	    /* set the default to 'act' */
	    _pam_set_default_control(control_array, act);
	}
    }

    /* that was a success */
    return;

parse_error:
    /* treat everything as bad */
    _pam_log_error("pam_parse: %s; [...%s]", error, tok);
    for (ret=0; ret<_PAM_RETURN_VALUES; control_array[ret++]=_PAM_ACTION_BAD);

}
