getspnam_r.c raw

   1  #include <fcntl.h>
   2  #include <unistd.h>
   3  #include <sys/stat.h>
   4  #include <ctype.h>
   5  #include <pthread.h>
   6  #include "pwf.h"
   7  
   8  /* This implementation support Openwall-style TCB passwords in place of
   9   * traditional shadow, if the appropriate directories and files exist.
  10   * Thus, it is careful to avoid following symlinks or blocking on fifos
  11   * which a malicious user might create in place of his or her TCB shadow
  12   * file. It also avoids any allocation to prevent memory-exhaustion
  13   * attacks via huge TCB shadow files. */
  14  
  15  static long xatol(char **s)
  16  {
  17  	long x;
  18  	if (**s == ':' || **s == '\n') return -1;
  19  	for (x=0; **s-'0'<10U; ++*s) x=10*x+(**s-'0');
  20  	return x;
  21  }
  22  
  23  int __parsespent(char *s, struct spwd *sp)
  24  {
  25  	sp->sp_namp = s;
  26  	if (!(s = strchr(s, ':'))) return -1;
  27  	*s = 0;
  28  
  29  	sp->sp_pwdp = ++s;
  30  	if (!(s = strchr(s, ':'))) return -1;
  31  	*s = 0;
  32  
  33  	s++; sp->sp_lstchg = xatol(&s);
  34  	if (*s != ':') return -1;
  35  
  36  	s++; sp->sp_min = xatol(&s);
  37  	if (*s != ':') return -1;
  38  
  39  	s++; sp->sp_max = xatol(&s);
  40  	if (*s != ':') return -1;
  41  
  42  	s++; sp->sp_warn = xatol(&s);
  43  	if (*s != ':') return -1;
  44  
  45  	s++; sp->sp_inact = xatol(&s);
  46  	if (*s != ':') return -1;
  47  
  48  	s++; sp->sp_expire = xatol(&s);
  49  	if (*s != ':') return -1;
  50  
  51  	s++; sp->sp_flag = xatol(&s);
  52  	if (*s != '\n') return -1;
  53  	return 0;
  54  }
  55  
  56  static void cleanup(void *p)
  57  {
  58  	fclose(p);
  59  }
  60  
  61  int getspnam_r(const char *name, struct spwd *sp, char *buf, size_t size, struct spwd **res)
  62  {
  63  	char path[20+NAME_MAX];
  64  	FILE *f = 0;
  65  	int rv = 0;
  66  	int fd;
  67  	size_t k, l = strlen(name);
  68  	int skip = 0;
  69  	int cs;
  70  	int orig_errno = errno;
  71  
  72  	*res = 0;
  73  
  74  	/* Disallow potentially-malicious user names */
  75  	if (*name=='.' || strchr(name, '/') || !l)
  76  		return errno = EINVAL;
  77  
  78  	/* Buffer size must at least be able to hold name, plus some.. */
  79  	if (size < l+100)
  80  		return errno = ERANGE;
  81  
  82  	/* Protect against truncation */
  83  	if (snprintf(path, sizeof path, "/etc/tcb/%s/shadow", name) >= sizeof path)
  84  		return errno = EINVAL;
  85  
  86  	fd = open(path, O_RDONLY|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC);
  87  	if (fd >= 0) {
  88  		struct stat st = { 0 };
  89  		errno = EINVAL;
  90  		if (fstat(fd, &st) || !S_ISREG(st.st_mode) || !(f = fdopen(fd, "rb"))) {
  91  			pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
  92  			close(fd);
  93  			pthread_setcancelstate(cs, 0);
  94  			return errno;
  95  		}
  96  	} else {
  97  		if (errno != ENOENT && errno != ENOTDIR)
  98  			return errno;
  99  		f = fopen("/etc/shadow", "rbe");
 100  		if (!f) {
 101  			if (errno != ENOENT && errno != ENOTDIR)
 102  				return errno;
 103  			return 0;
 104  		}
 105  	}
 106  
 107  	pthread_cleanup_push(cleanup, f);
 108  	while (fgets(buf, size, f) && (k=strlen(buf))>0) {
 109  		if (skip || strncmp(name, buf, l) || buf[l]!=':') {
 110  			skip = buf[k-1] != '\n';
 111  			continue;
 112  		}
 113  		if (buf[k-1] != '\n') {
 114  			rv = ERANGE;
 115  			break;
 116  		}
 117  
 118  		if (__parsespent(buf, sp) < 0) continue;
 119  		*res = sp;
 120  		break;
 121  	}
 122  	pthread_cleanup_pop(1);
 123  	errno = rv ? rv : orig_errno;
 124  	return rv;
 125  }
 126