#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "httpd.h" #define LS_ALLOC_SIZE (4 * 4096) #define HOMEPAGE "http://bytesex.org/webfs.html" #ifdef USE_THREADS static pthread_mutex_t lock_dircache = PTHREAD_MUTEX_INITIALIZER; #endif /* --------------------------------------------------------- */ #define CACHE_SIZE 32 static char* xgetpwuid(uid_t uid) { static char *cache[CACHE_SIZE]; static uid_t uids[CACHE_SIZE]; static unsigned int used,next; struct passwd *pw; int i; if (do_chroot) return NULL; /* would'nt work anyway .. */ for (i = 0; i < used; i++) { if (uids[i] == uid) return cache[i]; } /* 404 */ pw = getpwuid(uid); if (NULL != cache[next]) { free(cache[next]); cache[next] = NULL; } if (NULL != pw) cache[next] = strdup(pw->pw_name); uids[next] = uid; if (debug) fprintf(stderr,"uid: %3d n=%2d, name=%s\n", (int)uid, next, cache[next] ? cache[next] : "?"); next++; if (CACHE_SIZE == next) next = 0; if (used < CACHE_SIZE) used++; return pw ? pw->pw_name : NULL; } static char* xgetgrgid(gid_t gid) { static char *cache[CACHE_SIZE]; static gid_t gids[CACHE_SIZE]; static unsigned int used,next; struct group *gr; int i; if (do_chroot) return NULL; /* would'nt work anyway .. */ for (i = 0; i < used; i++) { if (gids[i] == gid) return cache[i]; } /* 404 */ gr = getgrgid(gid); if (NULL != cache[next]) { free(cache[next]); cache[next] = NULL; } if (NULL != gr) cache[next] = strdup(gr->gr_name); gids[next] = gid; if (debug) fprintf(stderr,"gid: %3d n=%2d, name=%s\n", (int)gid,next,cache[next] ? cache[next] : "?"); next++; if (CACHE_SIZE == next) next = 0; if (used < CACHE_SIZE) used++; return gr ? gr->gr_name : NULL; } /* --------------------------------------------------------- */ struct myfile { int r; struct stat s; char n[1]; }; static int compare_files(const void *a, const void *b) { const struct myfile *aa = *(struct myfile**)a; const struct myfile *bb = *(struct myfile**)b; if (S_ISDIR(aa->s.st_mode) != S_ISDIR(bb->s.st_mode)) return S_ISDIR(aa->s.st_mode) ? -1 : 1; return strcmp(aa->n,bb->n); } static char do_quote[256]; void init_quote(void) { int i; for (i = 0; i < 256; i++) do_quote[i] = (isalnum(i) || ispunct(i)) ? 0 : 1; do_quote['+'] = 1; do_quote['#'] = 1; do_quote['%'] = 1; do_quote['"'] = 1; do_quote['?'] = 1; } char* quote(unsigned char *path, int maxlength) { static unsigned char buf[2048]; /* FIXME: threads break this... */ int i,j,n=strlen(path); if (n > maxlength) n = maxlength; for (i=0, j=0; i> 6) & 0x7], rwx[(mode >> 3) & 0x7], rwx[mode & 0x7]); } #endif static char* ls(time_t now, char *hostname, char *filename, char *path, int *length) { DIR *dir; struct dirent *file; struct myfile **files = NULL; struct myfile **re1; char *h1,*h2,*re2,*buf = NULL; int count,len,size,i,uid,gid; char line[1024]; char *pw = NULL, *gr = NULL; if (debug) fprintf(stderr,"dir: reading %s\n",filename); if (NULL == (dir = opendir(filename))) return NULL; /* read dir */ uid = getuid(); gid = getgid(); for (count = 0;; count++) { if (NULL == (file = readdir(dir))) break; if (0 == strcmp(file->d_name,".")) { /* skip the the "." directory */ count--; continue; } if (0 == strcmp(path,"/") && 0 == strcmp(file->d_name,"..")) { /* skip the ".." directory in root dir */ count--; continue; } if (0 == (count % 64)) { re1 = realloc(files,(count+64)*sizeof(struct myfile*)); if (NULL == re1) goto oom; files = re1; } files[count] = malloc(strlen(file->d_name)+sizeof(struct myfile)); if (NULL == files[count]) goto oom; strcpy(files[count]->n,file->d_name); sprintf(line,"%s/%s",filename,file->d_name); if (-1 == stat(line,&files[count]->s)) { free(files[count]); count--; continue; } files[count]->r = 0; if (S_ISDIR(files[count]->s.st_mode) || S_ISREG(files[count]->s.st_mode)) { if (files[count]->s.st_uid == uid && files[count]->s.st_mode & 0400) files[count]->r = 1; else if (files[count]->s.st_uid == gid && files[count]->s.st_mode & 0040) files[count]->r = 1; /* FIXME: check additional groups */ else if (files[count]->s.st_mode & 0004) files[count]->r = 1; } } closedir(dir); /* sort */ if (count) qsort(files,count,sizeof(struct myfile*),compare_files); /* output */ size = LS_ALLOC_SIZE; buf = malloc(size); if (NULL == buf) goto oom; len = 0; len += sprintf(buf+len, "%s:%d%s\n" "\n" "

listing: \n", hostname,tcp_port,path); h1 = path, h2 = path+1; for (;;) { if (len > size) abort(); if (len+(LS_ALLOC_SIZE>>2) > size) { size += LS_ALLOC_SIZE; re2 = realloc(buf,size); if (NULL == re2) goto oom; buf = re2; } len += sprintf(buf+len,"%*.*s", quote(path,h2-path), (int)(h2-h1), (int)(h2-h1), h1); h1 = h2; h2 = strchr(h2,'/'); if (NULL == h2) break; h2++; } len += sprintf(buf+len, "


\n"
		   "access      user      group     date             "
		   "size  name\n\n");

    for (i = 0; i < count; i++) {
	if (len > size)
	    abort();
	if (len+(LS_ALLOC_SIZE>>2) > size) {
	    size += LS_ALLOC_SIZE;
	    re2 = realloc(buf,size);
	    if (NULL == re2)
		goto oom;
	    buf = re2;
	}

	/* mode */
	strmode(files[i]->s.st_mode, buf+len);
	len += 10;
	buf[len++] = ' ';
	buf[len++] = ' ';

	/* user */
	pw = xgetpwuid(files[i]->s.st_uid);
	if (NULL != pw)
	    len += sprintf(buf+len,"%-8.8s  ",pw);
	else
	    len += sprintf(buf+len,"%8d  ",(int)files[i]->s.st_uid);

	/* group */
	gr = xgetgrgid(files[i]->s.st_gid);
	if (NULL != gr)
	    len += sprintf(buf+len,"%-8.8s  ",gr);
	else
	    len += sprintf(buf+len,"%8d  ",(int)files[i]->s.st_gid);

	/* mtime */
	if (now - files[i]->s.st_mtime > 60*60*24*30*6)
	    len += strftime(buf+len,255,"%b %d  %Y  ",
			    gmtime(&files[i]->s.st_mtime));
	else
	    len += strftime(buf+len,255,"%b %d %H:%M  ",
			    gmtime(&files[i]->s.st_mtime));

	/* size */
	if (S_ISDIR(files[i]->s.st_mode)) {
	    len += sprintf(buf+len,"  <DIR>  ");
	} else if (!S_ISREG(files[i]->s.st_mode)) {
	    len += sprintf(buf+len,"     --  ");
	} else if (files[i]->s.st_size < 1024*9) {
	    len += sprintf(buf+len,"%4d  B  ",
			   (int)files[i]->s.st_size);
	} else if (files[i]->s.st_size < 1024*1024*9) {
	    len += sprintf(buf+len,"%4d kB  ",
			   (int)(files[i]->s.st_size>>10));
	} else if ((int64_t)(files[i]->s.st_size) < (int64_t)1024*1024*1024*9) {
	    len += sprintf(buf+len,"%4d MB  ",
			   (int)(files[i]->s.st_size>>20));
	} else if ((int64_t)(files[i]->s.st_size) < (int64_t)1024*1024*1024*1024*9) {
	    len += sprintf(buf+len,"%4d GB  ",
			   (int)(files[i]->s.st_size>>30));
	} else {
	    len += sprintf(buf+len,"%4d TB  ",
			   (int)(files[i]->s.st_size>>40));
	}

	/* filename */
	if (files[i]->r) {
	    len += sprintf(buf+len,"%s\n",
			   quote(files[i]->n,9999),
			   S_ISDIR(files[i]->s.st_mode) ? "/" : "",
			   files[i]->n);
	} else {
	    len += sprintf(buf+len,"%s\n",files[i]->n);
	}
    }
    strftime(line,32,"%d/%b/%Y %H:%M:%S GMT",gmtime(&now));
    len += sprintf(buf+len,
		   "

\n" "%s   %s\n" "\n", HOMEPAGE,server_name,line); for (i = 0; i < count; i++) free(files[i]); if (count) free(files); /* return results */ *length = len; return buf; oom: fprintf(stderr,"oom\n"); if (files) { for (i = 0; i < count; i++) if (files[i]) free(files[i]); free(files); } if (buf) free(buf); return NULL; } /* --------------------------------------------------------- */ #define MAX_CACHE_AGE 3600 /* seconds */ struct DIRCACHE *dirs = NULL; void free_dir(struct DIRCACHE *dir) { DO_LOCK(dir->lock_refcount); dir->refcount--; if (dir->refcount > 0) { DO_UNLOCK(dir->lock_refcount); return; } DO_UNLOCK(dir->lock_refcount); if (debug) fprintf(stderr,"dir: delete %s\n",dir->path); FREE_LOCK(dir->lock_refcount); FREE_LOCK(dir->lock_reading); FREE_COND(dir->wait_reading); if (NULL != dir->html) free(dir->html); free(dir); } struct DIRCACHE* get_dir(struct REQUEST *req, char *filename) { struct DIRCACHE *this,*prev; int i; DO_LOCK(lock_dircache); for (prev = NULL, this = dirs, i=0; this != NULL; prev = this, this = this->next, i++) { if (0 == strcmp(filename,this->path)) { /* remove from list */ if (NULL == prev) dirs = this->next; else prev->next = this->next; if (debug) fprintf(stderr,"dir: found %s\n",this->path); break; } if (i > max_dircache) { /* reached cache size limit -> free last element */ #if 0 if (this->next != NULL) { fprintf(stderr,"panic: this should'nt happen (%s:%d)\n", __FILE__, __LINE__); exit(1); } #endif free_dir(this); this = NULL; prev->next = NULL; break; } } if (this) { /* check mtime and cache entry age */ if (now - this->add > MAX_CACHE_AGE || 0 != strcmp(this->mtime, req->mtime)) { free_dir(this); this = NULL; } } if (!this) { /* add a new cache entry to the list */ this = malloc(sizeof(struct DIRCACHE)); this->refcount = 2; this->reading = 1; INIT_LOCK(this->lock_refcount); INIT_LOCK(this->lock_reading); INIT_COND(this->wait_reading); this->next = dirs; dirs = this; DO_UNLOCK(lock_dircache); strcpy(this->path, filename); strcpy(this->mtime, req->mtime); this->add = now; this->html = ls(now,req->hostname,filename,req->path,&(this->length)); DO_LOCK(this->lock_reading); this->reading = 0; BCAST_COND(this->wait_reading); DO_UNLOCK(this->lock_reading); } else { /* add back to the list */ this->next = dirs; dirs = this; this->refcount++; DO_UNLOCK(lock_dircache); DO_LOCK(this->lock_reading); if (this->reading) WAIT_COND(this->wait_reading,this->lock_reading); DO_UNLOCK(this->lock_reading); } req->body = this->html; req->lbody = this->length; return this; }