Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 11 Nov 2003 15:53:23 +1100 (EST)
From:      Edwin Groothuis <edwin@mavetju.org>
To:        FreeBSD-gnats-submit@FreeBSD.org
Subject:   ports/59163: [patch] www/mod_dav - fix symlink problem
Message-ID:  <20031111045323.B13CF6A7101@k7.mavetju>
Resent-Message-ID: <200311110500.hAB50mgP031749@freefall.freebsd.org>

next in thread | raw e-mail | index | archive | help

>Number:         59163
>Category:       ports
>Synopsis:       [patch] www/mod_dav - fix symlink problem
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-ports-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Mon Nov 10 21:00:48 PST 2003
>Closed-Date:
>Last-Modified:
>Originator:     Edwin Groothuis
>Release:        FreeBSD 4.8-RELEASE i386
>Organization:
-
>Environment:
System: FreeBSD k7.mavetju 4.8-RELEASE FreeBSD 4.8-RELEASE #0: Sun Aug 17 16:04:25 EST 2003 edwin@k7.mavetju:/usr/src/sys/compile/k7 i386

>Description:

While playing with mod_dav, I found out that it didn't follow
symlinks properly. Looking through various mailinglists I found
this patch by Mark Montague (markmont@umich.edu)

>How-To-Repeat:
>Fix:

I do not say we should include this patch by default, but if a knob
called WITH_SYMLINK_PATCH could be made which adds these patches
it would help people already.


patch-mod_dav.h:
--- mod_dav.h.orig	Tue Nov 11 14:16:48 2003
+++ mod_dav.h	Tue Nov 11 14:17:18 2003
@@ -1431,6 +1431,7 @@
 
     dav_text *propstat_404;	/* (cached) propstat giving a 404 error */
 
+    int is_dir;                 /* state for dav_fs_walker(): should we recurse? */
     /* for COPY and MOVE operations */
     int is_move;
     dav_buffer work_buf;


patch-dav_fs_repos.c:
--- dav_fs_repos.c.orig	Mon Nov  5 16:20:32 2001
+++ dav_fs_repos.c	Tue Nov 11 14:14:11 2003
@@ -154,6 +154,30 @@
 **
 ** PRIVATE REPOSITORY FUNCTIONS
 */
+
+static int symlink_aware_rmdir(const char *pathname)
+{
+
+    struct stat finfo;
+
+    /* Is this a symbolic link? */
+    /* If not, just call rmdir() and return. */
+
+    if (lstat(pathname, &finfo) != 0) {
+        return -1; /* and errno set by lstat() */
+    }
+
+    if (!S_ISLNK(finfo.st_mode)) {
+        return rmdir(pathname); /* and errno set by rmdir() */
+    }
+
+    /* It's a symlink. */
+    /* Remove the link itself, leaving the link's target untouched. */
+    return unlink(pathname); /* and errno set by unlink() */
+
+}
+
+
 pool *dav_fs_pool(const dav_resource *resource)
 {
     return resource->info->pool;
@@ -258,13 +282,25 @@
     /* chmod() the destination if the source is executable, and the
      * destination already exists. */
     if ((mode & DAV_FS_MODE_XUSR) && (dst_finfo != NULL) && 
-	(dst_finfo->st_mode != 0)) {
+	(dst_finfo->st_mode != 0) && !S_ISLNK(dst_finfo->st_mode)) {
 	if (chmod(dst, mode) == -1) {
 	    return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 				 "Could not set permissions on destination");
 	}
     }
 
+    /* If the destination is a symlink, break it so that we don't overwrite
+     * its target. */
+    if ((dst_finfo != NULL) && S_ISLNK(dst_finfo->st_mode)) {
+        if (unlink(dst) != 0) {
+           return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
+                                "Could not break destination symlink");
+        }
+    }
+
+
+      dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
+
     dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
 
     if ((fdi = open(src, O_RDONLY | O_BINARY)) == -1) {
@@ -565,8 +601,6 @@
     ctx->pool = r->pool;
     ctx->finfo = r->finfo;
 
-    (void) ap_update_mtime(r, r->finfo.st_mtime);
-
     /* Preserve case on OSes which fold canonical filenames */
 #if MODULE_MAGIC_NUMBER_MAJOR > 19990320 || (MODULE_MAGIC_NUMBER_MAJOR == 19990320 && MODULE_MAGIC_NUMBER_MINOR >= 8)
     filename = r->case_preserved_filename;
@@ -606,9 +640,18 @@
 	resource->uri = r->uri;
     }
 
-    if (r->finfo.st_mode != 0) {
+    if (r->finfo.st_mode == 0) {
+        /* Apache says it does not exists.  Is it maybe a dangling symlink? */
+        if (lstat(ctx->pathname, &(ctx->finfo)) != 0) {
+            ctx->finfo.st_mode = 0; /* does not exist */
+        }
+    }
+
+    (void) ap_update_mtime(r, ctx->finfo.st_mtime);
+
+    if (ctx->finfo.st_mode != 0) {
         resource->exists = 1;
-        resource->collection = S_ISDIR(r->finfo.st_mode);
+        resource->collection = S_ISDIR(ctx->finfo.st_mode);
 
 	/* unused info in the URL will indicate a null resource */
 
@@ -979,7 +1022,7 @@
 	     * Note: when copying, we do not enable the postfix-traversal.
 	     */
 	    /* ### we are ignoring any error here; what should we do? */
-	    (void) rmdir(srcinfo->pathname);
+	    (void) symlink_aware_rmdir(srcinfo->pathname);
 	}
         else {
 	    /* copy/move of a collection. Create the new, target collection */
@@ -1157,6 +1200,15 @@
 	}
     }
 
+    /* Renaming a symlink will only work in some circumstances (e.g., if
+     * old and new names are in the same directory).  Be safe and break
+     * the link.
+     */
+    if (S_ISLNK(srcinfo->finfo.st_mode)) {
+        can_rename = 0;
+    }
+
+
     /* if we can't simply renamed, then do it the hard way... */
     if (!can_rename) {
         if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY, response)) == NULL) {
@@ -1226,8 +1278,31 @@
 
 static dav_error * dav_fs_delete_walker(dav_walker_ctx *ctx, int calltype)
 {
+    struct stat finfo;
     dav_resource_private *info = ctx->resource->info;
 
+    /* If this is a symlink to a directory, immediately remove the link
+     * during any "prefix traversal", then signal our caller, dav_fs_walker(),
+     * that we're done and not to follow the link to the target's children.
+     */
+    if (ctx->resource->exists && ctx->resource->collection) {
+        if (lstat(info->pathname, &finfo) != 0) {
+                dav_add_response(ctx, ctx->resource->uri, HTTP_FORBIDDEN, NULL);
+                return NULL; /* failure */
+        }
+        if (S_ISLNK(finfo.st_mode)) {
+           if (unlink(info->pathname) != 0) {
+                /* ### assume there is a permissions problem */
+                /* ### use errno to generate DAV:responsedescription? */
+                dav_add_response(ctx, ctx->resource->uri, HTTP_FORBIDDEN, NULL);
+                return NULL; /* failure */
+            }
+            ctx->is_dir = 0; /* signal dav_fs_walker() that we're done */
+            return NULL; /* success */
+        }
+    }
+
+
     /* do not attempt to remove a null resource,
      * or a collection with children
      */
@@ -1237,7 +1312,7 @@
 	int result;
 
 	result = ctx->resource->collection
-	    ? rmdir(info->pathname)
+	    ? symlink_aware_rmdir(info->pathname)
 	    : remove(info->pathname);
 
 	/*
@@ -1320,20 +1395,25 @@
 {
     dav_error *err = NULL;
     dav_walker_ctx *wctx = fsctx->wctx;
-    int isdir = wctx->resource->collection;
     DIR *dirp;
     struct dirent *ep;
 
-    /* ensure the context is prepared properly, then call the func */
+    /* ensure the context is prepared properly, then call the func.
+     * Note that if the resource is a symbolic link to a directory,
+     * and we should not recurse (for example, because we're deleting)
+     * func() will change wctx->is_dir to 0 to signal this.
+     */
+    wctx->is_dir = wctx->resource->collection;
     err = (*wctx->func)(wctx,
-			isdir
+			wctx->is_dir
 			? DAV_CALLTYPE_COLLECTION
 			: DAV_CALLTYPE_MEMBER);
     if (err != NULL) {
 	return err;
     }
 
-    if (!isdir) {
+    /* At this point, wctx->is_dir actually means "should we recurse?" */
+    if (!wctx->is_dir) {
 	return NULL;
     }
 
@@ -1397,11 +1477,15 @@
 	dav_buffer_place_mem(wctx->pool,
 			     &fsctx->path1, ep->d_name, len + 1, 0);
 
-	if (lstat(fsctx->path1.buf, &fsctx->info1.finfo) != 0) {
-	    /* woah! where'd it go? */
-	    /* ### should have a better error here */
-	    err = dav_new_error(wctx->pool, HTTP_NOT_FOUND, 0, NULL);
-	    break;
+        /* stat() not lstat() because we want to follow symlinks */
+        /* BUT, ignore dangling symlinks (symlinks whose targets don't exist) */
+	if (stat(fsctx->path1.buf, &fsctx->info1.finfo) != 0) {
+	    if (lstat(fsctx->path1.buf, &fsctx->info1.finfo) != 0) {
+	        /* woah! where'd it go? */
+	        /* ### should have a better error here */
+	        err = dav_new_error(wctx->pool, HTTP_NOT_FOUND, 0, NULL);
+	        break;
+            }
 	}
 
 	/* copy the file to the URI, too. NOTE: we will pad an extra byte
@@ -1421,8 +1505,8 @@
 	/* set up the URI for the current resource */
 	fsctx->res1.uri = wctx->uri.buf;
 
-	/* ### for now, only process regular files (e.g. skip symlinks) */
-	if (S_ISREG(fsctx->info1.finfo.st_mode)) {
+	if (S_ISREG(fsctx->info1.finfo.st_mode) ||
+            S_ISLNK(fsctx->info1.finfo.st_mode)) {
 	    /* call the function for the specified dir + file */
 	    if ((err = (*wctx->func)(wctx, DAV_CALLTYPE_MEMBER)) != NULL) {
 		/* ### maybe add a higher-level description? */
>Release-Note:
>Audit-Trail:
>Unformatted:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20031111045323.B13CF6A7101>