Пример #1
0
void
mgt_sandbox(void)
{

#ifdef HAVE_SETPPRIV
	mgt_sandbox_solaris_init();
#endif

	if (geteuid() == 0) {
		XXXAZ(setgid(params->gid));
		XXXAZ(setuid(params->uid));
	} else {
		REPORT0(LOG_INFO, "Not running as root, no priv-sep");
	}

	/* On Linux >= 2.4, you need to set the dumpable flag
	   to get core dumps after you have done a setuid. */

#ifdef __linux__
	if (prctl(PR_SET_DUMPABLE, 1) != 0)
		REPORT0(LOG_INFO,
		    "Could not set dumpable bit.  Core dumps turned off\n");
#endif

#ifdef HAVE_SETPPRIV
	mgt_sandbox_solaris_fini();
#endif

}
Пример #2
0
mgt_sandbox_unix(enum sandbox_e who)
{
	(void)who;
	if (geteuid() == 0) {
		XXXAZ(setgid(mgt_param.gid));
		XXXAZ(initgroups(mgt_param.user, mgt_param.gid));
		XXXAZ(setuid(mgt_param.uid));
	} else {
		REPORT0(LOG_INFO, "Not running as root, no priv-sep");
	}
}
void
mgt_sandbox_solaris_privsep(void)
{
	if (priv_ineffect(PRIV_PROC_SETID)) {
                if (getgid() != params->gid)
                        XXXAZ(setgid(params->gid));
                if (getuid() != params->uid)
                        XXXAZ(setuid(params->uid));
        } else {
                REPORT(LOG_INFO, "Privilege %s missing, will not change uid/gid",
		    PRIV_PROC_SETID);
        }
}
Пример #4
0
static void
vjs_privsep(enum jail_gen_e jge)
{
	(void)jge;

	if (priv_ineffect(PRIV_PROC_SETID)) {
		if (getgid() != mgt_param.gid)
			XXXAZ(setgid(mgt_param.gid));
		if (getuid() != mgt_param.uid)
			XXXAZ(setuid(mgt_param.uid));
	} else {
		MGT_complain(C_SECURITY,
		    "Privilege %s missing, will not change uid/gid",
		    PRIV_PROC_SETID);
	}
}
static void
mgt_sandbox_solaris_privsep(enum sandbox_e who)
{
	(void)who;

	if (priv_ineffect(PRIV_PROC_SETID)) {
                if (getgid() != mgt_param.gid)
                        XXXAZ(setgid(mgt_param.gid));
                if (getuid() != mgt_param.uid)
                        XXXAZ(setuid(mgt_param.uid));
        } else {
                REPORT(LOG_INFO,
		    "Privilege %s missing, will not change uid/gid",
		    PRIV_PROC_SETID);
        }
}
vfp_esi_end(struct sess *sp)
{
	struct vsb *vsb;
	struct vef_priv *vef;
	ssize_t l;

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
	AN(sp->wrk->vep);

	vsb = VEP_Finish(sp);

	if (vsb != NULL) {
		l = VSB_len(vsb);
		assert(l > 0);
		/* XXX: This is a huge waste of storage... */
		sp->obj->esidata = STV_alloc(sp, l);
		XXXAN(sp->obj->esidata);
		memcpy(sp->obj->esidata->ptr, VSB_data(vsb), l);
		sp->obj->esidata->len = l;
		VSB_delete(vsb);
	}
	if (sp->wrk->vgz_rx != NULL)
		VGZ_Destroy(&sp->wrk->vgz_rx);

	if (sp->wrk->vef_priv != NULL) {
		vef = sp->wrk->vef_priv;
		CHECK_OBJ_NOTNULL(vef, VEF_MAGIC);
		sp->wrk->vef_priv = NULL;
		VGZ_UpdateObj(vef->vgz, sp->obj);
		VGZ_Destroy(&vef->vgz);
		XXXAZ(vef->error);
		FREE_OBJ(vef);
	}
	return (0);
}
Пример #7
0
vfp_testgzip_begin(struct busyobj *bo, size_t estimate)
{
	CHECK_OBJ_NOTNULL(bo, BUSYOBJ_MAGIC);
	(void)estimate;
	bo->vgz_rx = VGZ_NewUngzip(bo->vsl, "u F -");
	CHECK_OBJ_NOTNULL(bo->vgz_rx, VGZ_MAGIC);
	XXXAZ(vgz_getmbuf(bo->vgz_rx));
}
Пример #8
0
vfp_gzip_begin(struct busyobj *bo, size_t estimate)
{
	(void)estimate;

	CHECK_OBJ_NOTNULL(bo, BUSYOBJ_MAGIC);
	AZ(bo->vgz_rx);
	bo->vgz_rx = VGZ_NewGzip(bo->vsl, "G F -");
	XXXAZ(vgz_getmbuf(bo->vgz_rx));
}
Пример #9
0
static void
dyn_dir_init(VRT_CTX, struct xyzzy_debug_dyn *dyn,
     VCL_STRING addr, VCL_STRING port, VCL_PROBE probe)
{
	struct addrinfo hints, *res = NULL;
	struct suckaddr *sa;
	VCL_BACKEND dir, dir2;
	struct vrt_backend vrt;

	CHECK_OBJ_NOTNULL(dyn, VMOD_DEBUG_DYN_MAGIC);
	XXXAN(addr);
	XXXAN(port);

	INIT_OBJ(&vrt, VRT_BACKEND_MAGIC);
	vrt.port = port;
	vrt.vcl_name = dyn->vcl_name;
	vrt.hosthdr = addr;
	vrt.probe = probe;

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	AZ(getaddrinfo(addr, port, &hints, &res));
	XXXAZ(res->ai_next);

	sa = VSA_Malloc(res->ai_addr, res->ai_addrlen);
	AN(sa);
	if (VSA_Get_Proto(sa) == AF_INET) {
		vrt.ipv4_addr = addr;
		vrt.ipv4_suckaddr = sa;
	} else if (VSA_Get_Proto(sa) == AF_INET6) {
		vrt.ipv6_addr = addr;
		vrt.ipv6_suckaddr = sa;
	} else
		WRONG("Wrong proto family");

	freeaddrinfo(res);

	dir = VRT_new_backend(ctx, &vrt);
	AN(dir);

	/*
	 * NB: A real dynamic backend should not replace the previous
	 * instance if the new one is identical.  We do it here because
	 * the d* tests requires a replacement.
	 */
	AZ(pthread_mutex_lock(&dyn->mtx));
	dir2 = dyn->dir;
	dyn->dir = dir;
	AZ(pthread_mutex_unlock(&dyn->mtx));

	if (dir2 != NULL)
		VRT_delete_backend(ctx, &dir2);

	free(sa);
}
Пример #10
0
mgt_sandbox_unix(enum sandbox_e who)
{
#define NGID 2000
	int i;
	gid_t gid_list[NGID];

	if (geteuid() != 0) {
		REPORT0(LOG_INFO, "Not running as root, no priv-sep");
		return;
	}

	XXXAZ(setgid(mgt_param.gid));
	XXXAZ(initgroups(mgt_param.user, mgt_param.gid));

	if (who == SANDBOX_CC && strlen(mgt_param.group_cc) > 0) {
		/* Add the optional extra group for the C-compiler access */
		i = getgroups(NGID, gid_list);
		assert(i >= 0);
		gid_list[i++] = mgt_param.gid_cc;
		XXXAZ(setgroups(i, gid_list));
	}

	XXXAZ(setuid(mgt_param.uid));
}
/*--------------------------------------------------------------------
 * Append data to a signature
 */
void
smp_append_sign(struct smp_signctx *ctx, const void *ptr, uint32_t len)
{
	struct SHA256Context cx;
	unsigned char sign[SHA256_LEN];

	if (len != 0) {
		SHA256_Update(&ctx->ctx, ptr, len);
		ctx->ss->length += len;
	}
	cx = ctx->ctx;
	SHA256_Update(&cx, &ctx->ss->length, sizeof(ctx->ss->length));
	SHA256_Final(sign, &cx);
	memcpy(SIGN_END(ctx), sign, sizeof sign);
XXXAZ(smp_chk_sign(ctx));
}
vep_do_include(struct vep_state *vep, enum dowhat what)
{
	char *p, *q, *h;
	ssize_t l;
	txt url;

	Debug("DO_INCLUDE(%d)\n", what);
	if (what == DO_ATTR) {
		Debug("ATTR (%s) (%s)\n", vep->match_hit->match,
			VSB_data(vep->attr_vsb));
		if (vep->include_src != NULL) {
			vep_error(vep,
			    "ESI 1.0 <esi:include> "
			    "has multiple src= attributes");
			vep->state = VEP_TAGERROR;
			VSB_delete(vep->attr_vsb);
			VSB_delete(vep->include_src);
			vep->attr_vsb = NULL;
			vep->include_src = NULL;
			return;
		}
		XXXAZ(vep->include_src);	/* multiple src= */
		vep->include_src = vep->attr_vsb;
		return;
	}
	assert(what == DO_TAG);
	if (!vep->emptytag)
		vep_warn(vep,
		    "ESI 1.0 <esi:include> lacks final '/'");
	if (vep->include_src == NULL) {
		vep_error(vep,
		    "ESI 1.0 <esi:include> lacks src attr");
		return;
	}

	/*
	 * Strictly speaking, we ought to spit out any piled up skip before
	 * emitting the VEC for the include, but objectively that makes no
	 * difference and robs us of a chance to collapse another skip into
	 * this on so we don't do that.
	 * However, we cannot tolerate any verbatim stuff piling up.
	 * The mark_skip() before calling dostuff should have taken
	 * care of that.  Make sure.
	 */
	assert(vep->o_wait == 0 || vep->last_mark == SKIP);
	/* XXX: what if it contains NUL bytes ?? */
	p = VSB_data(vep->include_src);
	l = VSB_len(vep->include_src);
	h = 0;

	VSB_printf(vep->vsb, "%c", VEC_INCL);
	if (l > 7 && !memcmp(p, "http://", 7)) {
		h = p + 7;
		p = strchr(h, '/');
		AN(p);
		Debug("HOST <%.*s> PATH <%s>\n", (int)(p-h),h, p);
		VSB_printf(vep->vsb, "Host: %.*s%c",
		    (int)(p-h), h, 0);
	} else if (*p == '/') {
		VSB_printf(vep->vsb, "%c", 0);
	} else {
		VSB_printf(vep->vsb, "%c", 0);
		url = vep->sp->wrk->bereq->hd[HTTP_HDR_URL];
		/* Look for the last / before a '?' */
		h = NULL;
		for (q = url.b; q < url.e && *q != '?'; q++)
			if (*q == '/')
				h = q;
		if (h == NULL)
			h = q + 1;

		Debug("INCL:: [%.*s]/[%s]\n",
		    (int)(h - url.b), url.b, p);
		VSB_printf(vep->vsb, "%.*s/", (int)(h - url.b), url.b);
	}
	l -= (p - VSB_data(vep->include_src));
	for (q = p; *q != '\0'; ) {
		if (*q == '&') {
#define R(w,f,r)							\
			if (q + w <= p + l && !memcmp(q, f, w)) { \
				VSB_printf(vep->vsb, "%c", r);	\
				q += w;				\
				continue;			\
			}
			R(6, "&apos;", '\'');
			R(6, "&quot;", '"');
			R(4, "&lt;", '<');
			R(4, "&gt;", '>');
			R(5, "&amp;", '&');
		}
		VSB_printf(vep->vsb, "%c", *q++);
	}
#undef R
	VSB_printf(vep->vsb, "%c", 0);

	VSB_delete(vep->include_src);
	vep->include_src = NULL;
}
Пример #13
0
static int
cnt_lookup(struct worker *wrk, struct req *req)
{
	struct objcore *oc;
	struct object *o;
	struct objhead *oh;
	struct busyobj *bo;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
	AZ(req->objcore);

	CHECK_OBJ_NOTNULL(req->vcl, VCL_CONF_MAGIC);
	AZ(req->busyobj);

	VRY_Prep(req);

	AZ(req->objcore);
	oc = HSH_Lookup(req);
	if (oc == NULL) {
		/*
		 * We lost the session to a busy object, disembark the
		 * worker thread.   We return to STP_LOOKUP when the busy
		 * object has been unbusied, and still have the objhead
		 * around to restart the lookup with.
		 */
		return (2);
	}
	AZ(req->objcore);

	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
	oh = oc->objhead;
	CHECK_OBJ_NOTNULL(oh, OBJHEAD_MAGIC);

	/* If we inserted a new object it's a miss */
	if (oc->flags & OC_F_BUSY) {
		AZ(req->busyobj);
		bo = VBO_GetBusyObj(wrk, req);
		req->busyobj = bo;
		/* One ref for req, one for FetchBody */
		bo->refcount = 2;
		VRY_Finish(req, bo);

		oc->busyobj = bo;
		wrk->stats.cache_miss++;

		req->objcore = oc;
		req->req_step = R_STP_MISS;
		return (0);
	}

	/* We are not prepared to do streaming yet */
	XXXAZ(req->busyobj);

	o = oc_getobj(&wrk->stats, oc);
	CHECK_OBJ_NOTNULL(o, OBJECT_MAGIC);
	req->obj = o;

	VRY_Finish(req, NULL);

	if (oc->flags & OC_F_PASS) {
		wrk->stats.cache_hitpass++;
		VSLb(req->vsl, SLT_HitPass, "%u", req->obj->vxid);
		(void)HSH_Deref(&wrk->stats, NULL, &req->obj);
		AZ(req->objcore);
		req->req_step = R_STP_PASS;
		return (0);
	}

	wrk->stats.cache_hit++;
	VSLb(req->vsl, SLT_Hit, "%u", req->obj->vxid);
	req->req_step = R_STP_HIT;
	return (0);
}
Пример #14
0
static void
parse_new(struct vcc *tl)
{
	struct symbol *sy1, *sy2, *sy3;
	const char *p, *s_obj, *s_init, *s_struct, *s_fini;
	char buf1[128];
	char buf2[128];

	vcc_NextToken(tl);
	ExpectErr(tl, ID);
	sy1 = VCC_FindSymbol(tl, tl->t, SYM_NONE);
	XXXAZ(sy1);

	sy1 = VCC_AddSymbolTok(tl, tl->t, SYM_NONE);	// XXX: NONE ?
	XXXAN(sy1);
	vcc_NextToken(tl);

	ExpectErr(tl, '=');
	vcc_NextToken(tl);

	ExpectErr(tl, ID);
	sy2 = VCC_FindSymbol(tl, tl->t, SYM_OBJECT);
	XXXAN(sy2);

	/*lint -save -e448 */
	/* Split the first three args */
	p = sy2->args;
	s_obj = p;
	p += strlen(p) + 1;
	s_init = p;
	while (p[0] != '\0' || p[1] != '\0')
		p++;
	p += 2;
	s_struct = p;
	p += strlen(p) + 1;
	s_fini = p + strlen(p) + 1;
	while (p[0] != '\0' || p[1] != '\0')
		p++;
	p += 2;

	Fh(tl, 0, "static %s *%s;\n\n", s_struct, sy1->name);

	vcc_NextToken(tl);

	bprintf(buf1, ", &%s, \"%s\"", sy1->name, sy1->name);
	vcc_Eval_Func(tl, s_init, buf1, "ASDF", s_init + strlen(s_init) + 1);
	Fd(tl, 0, "\t%s(&%s);\n", s_fini, sy1->name);
	ExpectErr(tl, ';');

	bprintf(buf1, ", %s", sy1->name);
	/* Split the methods from the args */
	while (*p != '\0') {
		p += strlen(s_obj);
		bprintf(buf2, "%s%s", sy1->name, p);
		sy3 = VCC_AddSymbolStr(tl, buf2, SYM_FUNC);
		AN(sy3);
		sy3->eval = vcc_Eval_SymFunc;
		p += strlen(p) + 1;
		sy3->cfunc = p;
		p += strlen(p) + 1;

		/* Functions which return VOID are procedures */
		if (!memcmp(p, "VOID\0", 5))
			sy3->kind = SYM_PROC;

		sy3->args = p;
		sy3->extra = TlDup(tl, buf1);
		while (p[0] != '\0' || p[1] != '\0')
			p++;
		p += 2;
	}
	/*lint -restore */
}
Пример #15
0
void
HTTP1_Session(struct worker *wrk, struct req *req)
{
	enum htc_status_e hs;
	struct sess *sp;
	const char *st;
	int i;

	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
	sp = req->sp;
	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);

	/*
	 * Whenever we come in from the acceptor or waiter, we need to set
	 * blocking mode.  It would be simpler to do this in the acceptor
	 * or waiter, but we'd rather do the syscall in the worker thread.
	 * On systems which return errors for ioctl, we close early
	 */
	if (http1_getstate(sp) == H1NEWREQ && VTCP_blocking(sp->fd)) {
		if (errno == ECONNRESET)
			SES_Close(sp, SC_REM_CLOSE);
		else
			SES_Close(sp, SC_TX_ERROR);
		AN(Req_Cleanup(sp, wrk, req));
		return;
	}

	while (1) {
		st = http1_getstate(sp);
		if (st == H1NEWREQ) {
			assert(isnan(req->t_prev));
			assert(isnan(req->t_req));
			AZ(req->vcl);
			AZ(req->esi_level);

			hs = HTC_RxStuff(req->htc, HTTP1_Complete,
			    &req->t_first, &req->t_req,
			    sp->t_idle + cache_param->timeout_linger,
			    sp->t_idle + cache_param->timeout_idle,
			    cache_param->http_req_size);
			XXXAZ(req->htc->ws->r);
			if (hs < HTC_S_EMPTY) {
				req->acct.req_hdrbytes +=
				    req->htc->rxbuf_e - req->htc->rxbuf_b;
				CNT_AcctLogCharge(wrk->stats, req);
				Req_Release(req);
				switch(hs) {
				case HTC_S_CLOSE:
					SES_Delete(sp, SC_REM_CLOSE, NAN);
					return;
				case HTC_S_TIMEOUT:
					SES_Delete(sp, SC_RX_TIMEOUT, NAN);
					return;
				case HTC_S_OVERFLOW:
					SES_Delete(sp, SC_RX_OVERFLOW, NAN);
					return;
				case HTC_S_EOF:
					SES_Delete(sp, SC_REM_CLOSE, NAN);
					return;
				default:
					WRONG("htc_status (bad)");
				}
			}
			if (hs == HTC_S_IDLE) {
				wrk->stats->sess_herd++;
				Req_Release(req);
				SES_Wait(sp, &HTTP1_transport);
				return;
			}
			if (hs != HTC_S_COMPLETE)
				WRONG("htc_status (nonbad)");

			i = http1_dissect(wrk, req);
			req->acct.req_hdrbytes +=
			    req->htc->rxbuf_e - req->htc->rxbuf_b;
			if (i) {
				SES_Close(req->sp, req->doclose);
				http1_setstate(sp, H1CLEANUP);
			} else {
				req->req_step = R_STP_RECV;
				http1_setstate(sp, H1PROC);
			}
		} else if (st == H1BUSY) {
			/*
			 * Return from waitinglist.
			 * Check to see if the remote has left.
			 */
			if (VTCP_check_hup(sp->fd)) {
				AN(req->hash_objhead);
				(void)HSH_DerefObjHead(wrk, &req->hash_objhead);
				AZ(req->hash_objhead);
				SES_Close(sp, SC_REM_CLOSE);
				AN(Req_Cleanup(sp, wrk, req));
				return;
			}
			http1_setstate(sp, H1PROC);
		} else if (st == H1PROC) {
			req->transport = &HTTP1_transport;
			if (CNT_Request(wrk, req) == REQ_FSM_DISEMBARK) {
				req->task.func = http1_req;
				req->task.priv = req;
				http1_setstate(sp, H1BUSY);
				return;
			}
			req->transport = NULL;
			http1_setstate(sp, H1CLEANUP);
		} else if (st == H1CLEANUP) {
			if (Req_Cleanup(sp, wrk, req))
				return;
			HTC_RxInit(req->htc, req->ws);
			if (req->htc->rxbuf_e != req->htc->rxbuf_b)
				wrk->stats->sess_readahead++;
			http1_setstate(sp, H1NEWREQ);
		} else {
			WRONG("Wrong H1 session state");
		}
	}
}
Пример #16
0
static void
parse_new(struct vcc *tl)
{
	struct symbol *sy1, *sy2, *sy3;
	struct inifin *ifp;
	const char *p, *s_obj, *s_init, *s_struct, *s_fini;
	char buf1[128];
	char buf2[128];

	vcc_NextToken(tl);
	ExpectErr(tl, ID);
	if (!vcc_isCid(tl->t)) {
		VSB_printf(tl->sb,
		    "Names of VCL objects cannot contain '-'\n");
		vcc_ErrWhere(tl, tl->t);
		return;
	}
	sy1 = VCC_FindSymbol(tl, tl->t, SYM_NONE);
	XXXAZ(sy1);

	sy1 = VCC_AddSymbolTok(tl, tl->t, SYM_NONE);	// XXX: NONE ?
	XXXAN(sy1);
	vcc_NextToken(tl);

	ExpectErr(tl, '=');
	vcc_NextToken(tl);

	ExpectErr(tl, ID);
	sy2 = VCC_FindSymbol(tl, tl->t, SYM_OBJECT);
	if (sy2 == NULL) {
		VSB_printf(tl->sb, "Object not found: ");
		vcc_ErrToken(tl, tl->t);
		VSB_printf(tl->sb, " at ");
		vcc_ErrWhere(tl, tl->t);
		return;
	}
	XXXAN(sy2);

	/*lint -save -e448 */
	/* Split the first three args */
	p = sy2->args;
	s_obj = p;
	p += strlen(p) + 1;
	s_init = p;
	while (p[0] != '\0' || p[1] != '\0')
		p++;
	p += 2;
	s_struct = p;
	p += strlen(p) + 1;
	s_fini = p + strlen(p) + 1;
	while (p[0] != '\0' || p[1] != '\0')
		p++;
	p += 2;

	Fh(tl, 0, "static %s *%s;\n\n", s_struct, sy1->name);

	vcc_NextToken(tl);

	bprintf(buf1, ", &%s, \"%s\"", sy1->name, sy1->name);
	vcc_Eval_Func(tl, s_init, buf1, "ASDF", s_init + strlen(s_init) + 1);
	ifp = New_IniFin(tl);
	VSB_printf(ifp->fin, "\t%s(&%s);", s_fini, sy1->name);
	ExpectErr(tl, ';');

	bprintf(buf1, ", %s", sy1->name);
	/* Split the methods from the args */
	while (*p != '\0') {
		p += strlen(s_obj);
		bprintf(buf2, "%s%s", sy1->name, p);
		sy3 = VCC_AddSymbolStr(tl, buf2, SYM_FUNC);
		AN(sy3);
		sy3->eval = vcc_Eval_SymFunc;
		p += strlen(p) + 1;
		sy3->cfunc = p;
		p += strlen(p) + 1;

		/* Functions which return VOID are procedures */
		if (!memcmp(p, "VOID\0", 5))
			sy3->kind = SYM_PROC;

		sy3->args = p;
		sy3->extra = TlDup(tl, buf1);
		while (p[0] != '\0' || p[1] != '\0') {
			if (!memcmp(p, "ENUM\0", 5)) {
				/* XXX: Special case for ENUM that has
				   it's own \0\0 end marker. Not exactly
				   elegant, we should consider
				   alternatives here. Maybe runlength
				   encode the entire block? */
				p += strlen(p) + 1;
				while (p[0] != '\0' || p[1] != '\0')
					p++;
			}
			p++;
		}
		p += 2;
	}
	/*lint -restore */
}
Пример #17
0
int
FetchBody(struct sess *sp)
{
	int cls;
	struct storage *st;
	struct worker *w;
	int mklen;

	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
	CHECK_OBJ_NOTNULL(sp->wrk, WORKER_MAGIC);
	w = sp->wrk;
	CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);
	CHECK_OBJ_NOTNULL(sp->obj->http, HTTP_MAGIC);

	if (w->vfp == NULL)
		w->vfp = &vfp_nop;

	AN(sp->director);
	AssertObjCorePassOrBusy(sp->obj->objcore);

	AZ(w->vgz_rx);
	AZ(VTAILQ_FIRST(&sp->obj->store));
	switch (w->body_status) {
	case BS_NONE:
		cls = 0;
		mklen = 0;
		break;
	case BS_ZERO:
		cls = 0;
		mklen = 1;
		break;
	case BS_LENGTH:
		cls = fetch_straight(sp, w->htc,
		    w->h_content_length);
		mklen = 1;
		XXXAZ(w->vfp->end(sp));
		break;
	case BS_CHUNKED:
		cls = fetch_chunked(sp, w->htc);
		mklen = 1;
		XXXAZ(w->vfp->end(sp));
		break;
	case BS_EOF:
		cls = fetch_eof(sp, w->htc);
		mklen = 1;
		XXXAZ(w->vfp->end(sp));
		break;
	case BS_ERROR:
		cls = 1;
		mklen = 0;
		break;
	default:
		cls = 0;
		mklen = 0;
		INCOMPL();
	}
	AZ(w->vgz_rx);

	/*
	 * It is OK for ->end to just leave the last storage segment
	 * sitting on w->storage, we will always call vfp_nop_end()
	 * to get it trimmed or thrown out if empty.
	 */
	AZ(vfp_nop_end(sp));

	WSL(w, SLT_Fetch_Body, sp->vbc->vsl_id, "%u(%s) cls %d mklen %u",
	    w->body_status, body_status(w->body_status),
	    cls, mklen);

	if (w->body_status == BS_ERROR) {
		VDI_CloseFd(sp);
		return (__LINE__);
	}

	if (cls < 0) {
		w->stats.fetch_failed++;
		/* XXX: Wouldn't this store automatically be released ? */
		while (!VTAILQ_EMPTY(&sp->obj->store)) {
			st = VTAILQ_FIRST(&sp->obj->store);
			VTAILQ_REMOVE(&sp->obj->store, st, list);
			STV_free(st);
		}
		VDI_CloseFd(sp);
		sp->obj->len = 0;
		return (__LINE__);
	}

	if (cls == 0 && w->do_close)
		cls = 1;

	WSL(w, SLT_Length, sp->vbc->vsl_id, "%u", sp->obj->len);

	{
	/* Sanity check fetch methods accounting */
		ssize_t uu;

		uu = 0;
		VTAILQ_FOREACH(st, &sp->obj->store, list)
			uu += st->len;
		if (sp->objcore == NULL || (sp->objcore->flags & OC_F_PASS))
			/* Streaming might have started freeing stuff */
			assert (uu <= sp->obj->len);
		else
			assert(uu == sp->obj->len);
	}

	if (mklen > 0) {
		http_Unset(sp->obj->http, H_Content_Length);
		http_PrintfHeader(w, sp->vsl_id, sp->obj->http,
		    "Content-Length: %jd", (intmax_t)sp->obj->len);
	}

	if (cls)
		VDI_CloseFd(sp);
	else
		VDI_RecycleFd(sp);

	return (0);
}