mp_sint32 LoaderXM::load(XMFileBase& f, XModule* module)
{
	mp_ubyte insData[230];
	mp_sint32 smpReloc[MP_MAXINSSAMPS];
	mp_ubyte nbu[MP_MAXINSSAMPS];
	mp_uint32 fileSize = 0;

	module->cleanUp();

	// this will make code much easier to read
	TXMHeader*		header = &module->header;
	TXMInstrument*	instr  = module->instr;
	TXMSample*		smp	   = module->smp;
	TXMPattern*		phead  = module->phead;

	// we're already out of memory here
	if (!phead || !instr || !smp)
		return MP_OUT_OF_MEMORY;

	fileSize = f.sizeWithBaseOffset();

	f.read(&header->sig,1,17);
	f.read(&header->name,1,20);
	f.read(&header->whythis1a,1,1);
	header->whythis1a=0;
	f.read(&header->tracker,1,20);
	f.readWords(&header->ver,1);

	if (header->ver != 0x102 &&
		header->ver != 0x103 && // untested
		header->ver != 0x104)
		return MP_LOADER_FAILED;

	f.readDwords(&header->hdrsize,1);

	header->hdrsize-=4;

	mp_uint32 hdrSize = 0x110;
	if (header->hdrsize > hdrSize)
		hdrSize = header->hdrsize;

	mp_ubyte* hdrBuff = new mp_ubyte[hdrSize];
	memset(hdrBuff, 0, hdrSize);

	f.read(hdrBuff, 1, header->hdrsize);

	header->ordnum = LittleEndian::GET_WORD(hdrBuff);
	header->restart = LittleEndian::GET_WORD(hdrBuff+2);
	header->channum = LittleEndian::GET_WORD(hdrBuff+4);
	header->patnum = LittleEndian::GET_WORD(hdrBuff+6);
	header->insnum = LittleEndian::GET_WORD(hdrBuff+8);
	header->freqtab = LittleEndian::GET_WORD(hdrBuff+10);
	header->tempo = LittleEndian::GET_WORD(hdrBuff+12);
	header->speed = LittleEndian::GET_WORD(hdrBuff+14);
	memcpy(header->ord, hdrBuff+16, 256);
	if(header->ordnum > MP_MAXORDERS)
		header->ordnum = MP_MAXORDERS;
	if(header->insnum > MP_MAXINS)
		return MP_LOADER_FAILED;

	delete[] hdrBuff;

	header->mainvol=255;
	header->flags = XModule::MODULE_XMNOTECLIPPING |
		XModule::MODULE_XMARPEGGIO |
		XModule::MODULE_XMPORTANOTEBUFFER |
		XModule::MODULE_XMVOLCOLUMNVIBRATO;

	header->uppernotebound = 119;

	mp_sint32 i,y,sc;
	for (i=0;i<32;i++) header->pan[i]=0x80;

	// old version?
	if (header->ver == 0x102 || header->ver == 0x103)
	{
		mp_sint32 s = 0;
		mp_sint32 e = 0;
		for (y=0;y<header->insnum;y++) {

			f.readDwords(&instr[y].size,1);
			f.read(&instr[y].name,1,22);
			f.read(&instr[y].type,1,1);
			mp_uword numSamples = 0;
			f.readWords(&numSamples,1);
			if(numSamples > MP_MAXINSSAMPS)
				return MP_LOADER_FAILED;
			instr[y].samp = numSamples;

			if (instr[y].size == 29)
			{
#ifdef MILKYTRACKER
				s+=16;
#endif
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
				continue;
			}

			f.readDwords(&instr[y].shsize,1);

			memset(insData, 0, 230);

			if (instr[y].size - 33 > 230)
				return MP_OUT_OF_MEMORY;

			f.read(insData, 1, instr[y].size - 33);

			if (instr[y].samp) {
				mp_ubyte* insDataPtr = insData;

				memcpy(nbu, insDataPtr, MP_MAXINSSAMPS);
				insDataPtr+=MP_MAXINSSAMPS;

				TEnvelope venv;
				TEnvelope penv;
				memset(&venv,0,sizeof(venv));
				memset(&penv,0,sizeof(penv));

				mp_sint32 k;
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					venv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					venv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					penv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					penv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}

				venv.num = *insDataPtr++;
				if (venv.num > XM_ENVELOPENUMPOINTS) venv.num = XM_ENVELOPENUMPOINTS;
				penv.num = *insDataPtr++;
				if (penv.num > XM_ENVELOPENUMPOINTS) penv.num = XM_ENVELOPENUMPOINTS;
				venv.sustain = *insDataPtr++;
				venv.loops = *insDataPtr++;
				venv.loope = *insDataPtr++;
				penv.sustain = *insDataPtr++;
				penv.loops = *insDataPtr++;
				penv.loope = *insDataPtr++;
				venv.type = *insDataPtr++;
				penv.type = *insDataPtr++;

				mp_ubyte vibtype, vibsweep, vibdepth, vibrate;
				mp_uword volfade;

				vibtype = *insDataPtr++;
				vibsweep = *insDataPtr++;
				vibdepth = *insDataPtr++;
				vibrate = *insDataPtr++;

				vibdepth<<=1;

				volfade = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;
				volfade<<=1;

				//instr[y].res = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;

				for (mp_sint32 l=0;l<XM_ENVELOPENUMPOINTS;l++) {
					venv.env[l][1]<<=2;
					penv.env[l][1]<<=2;
				}

				if (!module->addVolumeEnvelope(venv))
					return MP_OUT_OF_MEMORY;
				if (!module->addPanningEnvelope(penv))
					return MP_OUT_OF_MEMORY;

				mp_sint32 g=0, sc;
				for (sc=0;sc<instr[y].samp;sc++) {

					smp[g+s].flags=3;
					smp[g+s].venvnum=e+1;
					smp[g+s].penvnum=e+1;

					smp[g+s].vibtype=vibtype;
					smp[g+s].vibsweep=vibsweep;
					smp[g+s].vibdepth=vibdepth;
					smp[g+s].vibrate=vibrate;
					smp[g+s].volfade=volfade;

					// not sure why I did that, actually doesn't make sense
					//if (!(venv.type&1)) smp[g+s].volfade=0;

					f.readDwords(&smp[g+s].samplen,1);
					f.readDwords(&smp[g+s].loopstart,1);
					f.readDwords(&smp[g+s].looplen,1);
					smp[g+s].vol=XModule::vol64to255(f.readByte());
					//f.read(&smp[g+s].vol,1,1);
					f.read(&smp[g+s].finetune,1,1);
					f.read(&smp[g+s].type,1,1);
#ifdef VERBOSE
					printf("Before: %i, After: %i\n", smp[g+s].type, smp[g+s].type & (3+16));
#endif
					f.read(&smp[g+s].pan,1,1);
					f.read(&smp[g+s].relnote,1,1);
					f.read(&smp[g+s].res,1,1);
					f.read(&smp[g+s].name,1,22);

					char line[30];
					memset(line, 0, sizeof(line));
					XModule::convertStr(line, smp[g+s].name, 23, false);
					if (line[0])
						module->addSongMessageLine(line);

					// ignore empty samples
#ifndef MILKYTRACKER
					// ignore empty samples when not being a tracker
					if (smp[g+s].samplen) {
						smpReloc[sc] = g;
						g++;
					}
					else
						smpReloc[sc] = -1;
#else
					smpReloc[sc] = g;
					g++;
#endif
				}

				instr[y].samp = g;

				for (sc = 0; sc < MP_MAXINSSAMPS; sc++) {
					if (smpReloc[nbu[sc]] == -1)
						instr[y].snum[sc] = -1;
					else
						instr[y].snum[sc] = smpReloc[nbu[sc]]+s;
				}

				e++;

			}
			else
			{
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
			}

#ifdef MILKYTRACKER
			s+=16;
#else
			s+=instr[y].samp;
#endif

		}

		header->smpnum=s;
		header->volenvnum=e;
		header->panenvnum=e;
	}

	for (y=0;y<header->patnum;y++) {

		if (header->ver == 0x104 || header->ver == 0x103)
		{
			f.readDwords(&phead[y].len,1);
			f.read(&phead[y].ptype,1,1);
			f.readWords(&phead[y].rows,1);
			f.readWords(&phead[y].patdata,1);
		}
		else
		{
			f.readDwords(&phead[y].len,1);
			f.read(&phead[y].ptype,1,1);
			phead[y].rows = (mp_uword)f.readByte()+1;
			f.readWords(&phead[y].patdata,1);
		}

		phead[y].effnum=2;
		phead[y].channum=(mp_ubyte)header->channum;

		phead[y].patternData = new mp_ubyte[phead[y].rows*header->channum*6];

		// out of memory?
		if (phead[y].patternData == NULL)
		{
			return MP_OUT_OF_MEMORY;
		}

		memset(phead[y].patternData,0,phead[y].rows*header->channum*6);

		if (phead[y].patdata) {
			mp_ubyte *buffer = new mp_ubyte[phead[y].patdata];

			// out of memory?
			if (buffer == NULL)
			{
				return MP_OUT_OF_MEMORY;
			}

			f.read(buffer,1,phead[y].patdata);

			//printf("%i\n", phead[y].patdata);

			mp_sint32 pc = 0, bc = 0;
			for (mp_sint32 r=0;r<phead[y].rows;r++) {
				for (mp_sint32 c=0;c<header->channum;c++) {

					mp_ubyte slot[5];
					memset(slot,0,5);

					if ((buffer[pc]&128)) {

						mp_ubyte pb = buffer[pc];
						pc++;

						if ((pb&1)) {
							//phead[y].patternData[bc]=buffer[pc];
							slot[0]=buffer[pc];
							pc++;
						}
						if ((pb&2)) {
							//phead[y].patternData[bc+1]=buffer[pc];
							slot[1]=buffer[pc];
							pc++;
						}
						if ((pb&4)) {
							//phead[y].patternData[bc+2]=buffer[pc];
							slot[2]=buffer[pc];
							pc++;
						}
						if ((pb&8)) {
							//phead[y].patternData[bc+3]=buffer[pc];
							slot[3]=buffer[pc];
							pc++;
						}
						if ((pb&16)) {
							//phead[y].patternData[bc+4]=buffer[pc];
							slot[4]=buffer[pc];
							pc++;
						}

					}
					else {
						//memcpy(phead[y].patternData+bc,buffer+pc,5);
						memcpy(slot,buffer+pc,5);
						pc+=5;
					}

					char gl=0;
					for (mp_sint32 i=0;i<XModule::numValidXMEffects;i++)
						if (slot[3]==XModule::validXMEffects[i]) gl=1;

					if (!gl) slot[3]=slot[4]=0;

					if ((slot[3]==0xC)||(slot[3]==0x10)) {
						slot[4] = XModule::vol64to255(slot[4]);
						/*mp_sint32 bl = slot[4];
						if (bl>64) bl=64;
						slot[4]=(bl*261120)>>16;*/
					}

					if ((!slot[3])&&(slot[4])) slot[3]=0x20;

					if (slot[3]==0xE) {
						slot[3]=(slot[4]>>4)+0x30;
						slot[4]=slot[4]&0xf;
					}

					if (slot[3]==0x21) {
						slot[3]=(slot[4]>>4)+0x40;
						slot[4]=slot[4]&0xf;
					}

					if (slot[0]==97) slot[0]=XModule::NOTE_OFF;

					phead[y].patternData[bc]=slot[0];
					phead[y].patternData[bc+1]=slot[1];

					XModule::convertXMVolumeEffects(slot[2], phead[y].patternData[bc+2], phead[y].patternData[bc+3]);

					phead[y].patternData[bc+4]=slot[3];
					phead[y].patternData[bc+5]=slot[4];

					/*if ((y==3)&&(c==2)) {
						for (mp_sint32 bl=0;bl<6;bl++) cprintf("%x ",phead[y].patternData[bc+bl]);
					cprintf("\r\n");
					getch();
					};*/

					/*printf("Note : %i\r\n",phead[y].patternData[bc]);
					printf("Ins  : %i\r\n",phead[y].patternData[bc+1]);
					printf("Vol  : %i\r\n",phead[y].patternData[bc+2]);
					printf("Eff  : %i\r\n",phead[y].patternData[bc+3]);
					printf("Effop: %i\r\n",phead[y].patternData[bc+4]);
					getch();*/

					bc+=6;
				} // for c

			} // for r

			delete[] buffer;
		}

	}

	if (header->ver == 0x104)
	{
		mp_sint32 s = 0;
		mp_sint32 e = 0;
		for (y=0;y<header->insnum;y++) {

			// fixes MOOH.XM loading problems
			// seems to store more instruments in the header than in the actual file
			if (f.posWithBaseOffset() >= fileSize)
				break;

			//TXMInstrument* ins = &instr[y];

			f.readDwords(&instr[y].size,1);

			if (instr[y].size >= 4 && instr[y].size < 29)
			{
				mp_ubyte buffer[29];
				memset(buffer, 0, sizeof(buffer));
				f.read(buffer, 1, instr[y].size - 4);
				memcpy(instr[y].name, buffer, 22);
				instr[y].type = buffer[22];
				instr[y].samp = LittleEndian::GET_WORD(buffer + 23);
			}
			else
			{
				f.read(&instr[y].name,1,22);
				f.read(&instr[y].type,1,1);
				f.readWords(&instr[y].samp,1);
			}
			if (instr[y].samp > MP_MAXINSSAMPS)
				return MP_LOADER_FAILED;

			//printf("%i, %i\n", instr[y].size, instr[y].samp);

			if (instr[y].size <= 29)
			{
#ifdef MILKYTRACKER
				s+=16;
#endif
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
				continue;
			}

			f.readDwords(&instr[y].shsize,1);
#ifdef VERBOSE
			printf("%i/%i: %i, %i, %i, %s\n",y,header->insnum-1,instr[y].size,instr[y].shsize,instr[y].samp,instr[y].name);
#endif
			memset(insData, 0, 230);

			if (instr[y].size - 33 > 230)
			{
				//return -7;
				break;
			}

			f.read(insData, 1, instr[y].size - 33);

			/*printf("%i\r\n",instr[y].size);
			printf("%s\r\n",instr[y].name);
			printf("%i\r\n",instr[y].type);
			printf("%i\r\n",instr[y].samp);
			printf("%i\r\n",instr[y].shsize);*/
			//getch();

			memset(smpReloc, 0, sizeof(smpReloc));

			if (instr[y].samp) {
				mp_ubyte* insDataPtr = insData;

				//f.read(&nbu,1,96);

				memcpy(nbu, insDataPtr, MP_MAXINSSAMPS);
				insDataPtr+=MP_MAXINSSAMPS;

				TEnvelope venv;
				TEnvelope penv;
				memset(&venv,0,sizeof(venv));
				memset(&penv,0,sizeof(penv));

				mp_sint32 k;
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					venv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					venv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					penv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					penv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}

				venv.num = *insDataPtr++;
				if (venv.num > XM_ENVELOPENUMPOINTS) venv.num = XM_ENVELOPENUMPOINTS;
				penv.num = *insDataPtr++;
				if (penv.num > XM_ENVELOPENUMPOINTS) penv.num = XM_ENVELOPENUMPOINTS;
				venv.sustain = *insDataPtr++;
				venv.loops = *insDataPtr++;
				venv.loope = *insDataPtr++;
				penv.sustain = *insDataPtr++;
				penv.loops = *insDataPtr++;
				penv.loope = *insDataPtr++;
				venv.type = *insDataPtr++;
				penv.type = *insDataPtr++;

				mp_ubyte vibtype, vibsweep, vibdepth, vibrate;
				mp_uword volfade;

				vibtype = *insDataPtr++;
				vibsweep = *insDataPtr++;
				vibdepth = *insDataPtr++;
				vibrate = *insDataPtr++;

				vibdepth<<=1;

				//f.readWords(&volfade,1);
				volfade = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;
				volfade<<=1;

				//instr[y].res = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;

				for (mp_sint32 l=0;l<XM_ENVELOPENUMPOINTS;l++) {
					venv.env[l][1]<<=2;
					penv.env[l][1]<<=2;
				}

				if (!module->addVolumeEnvelope(venv))
					return MP_OUT_OF_MEMORY;
				if (!module->addPanningEnvelope(penv))
					return MP_OUT_OF_MEMORY;

				mp_sint32 g=0, sc;
				for (sc=0;sc<instr[y].samp;sc++) {
					//TXMSample* smpl = &smp[g+s];

					smp[g+s].flags=3;
					smp[g+s].venvnum=e+1;
					smp[g+s].penvnum=e+1;

					smp[g+s].vibtype=vibtype;
					smp[g+s].vibsweep=vibsweep;
					smp[g+s].vibdepth=vibdepth;
					smp[g+s].vibrate=vibrate;
					smp[g+s].volfade=volfade;

					// not sure why I did that, actually doesn't make sense
					//if (!(venv.type&1)) smp[g+s].volfade=0;

					f.readDwords(&smp[g+s].samplen,1);

					f.readDwords(&smp[g+s].loopstart,1);
					f.readDwords(&smp[g+s].looplen,1);
					smp[g+s].vol=XModule::vol64to255(f.readByte());
					//f.read(&smp[g+s].vol,1,1);
					f.read(&smp[g+s].finetune,1,1);
					f.read(&smp[g+s].type,1,1);
#ifdef VERBOSE
					printf("Before: %i, After: %i\n", smp[g+s].type, smp[g+s].type & (3+16));
#endif
					f.read(&smp[g+s].pan,1,1);
					f.read(&smp[g+s].relnote,1,1);
					f.read(&smp[g+s].res,1,1);
					f.read(&smp[g+s].name,1,22);

					char line[30];
					memset(line, 0, sizeof(line));
					XModule::convertStr(line, smp[g+s].name, 23, false);
					if (line[0])
						module->addSongMessageLine(line);

#ifndef MILKYTRACKER
					// ignore empty samples when not being a tracker
					if (smp[g+s].samplen) {
						smpReloc[sc] = g;
						g++;
					}
					else
						smpReloc[sc] = -1;
#else
					smpReloc[sc] = g;
					g++;
#endif
				}

				instr[y].samp = g;

				for (sc = 0; sc < MP_MAXINSSAMPS; sc++) {
					if (smpReloc[nbu[sc]] == -1)
						instr[y].snum[sc] = -1;
					else
						instr[y].snum[sc] = smpReloc[nbu[sc]]+s;
				}

				for (sc=0;sc<instr[y].samp;sc++) {

					if (smp[s].samplen)
					{
						bool adpcm = (smp[s].res == 0xAD);

						mp_uint32 oldSize = smp[s].samplen;
						if (smp[s].type&16)
						{
							smp[s].samplen>>=1;
							smp[s].loopstart>>=1;
							smp[s].looplen>>=1;
						}

						mp_sint32 result = module->loadModuleSample(f, s,
													 adpcm ? XModule::ST_PACKING_ADPCM : XModule::ST_DELTA,
													 adpcm ? (XModule::ST_PACKING_ADPCM | XModule::ST_16BIT) : (XModule::ST_DELTA | XModule::ST_16BIT),
													 oldSize);
						if (result != MP_OK)
							return result;

						if (adpcm)
							smp[s].res = 0;
					}

					s++;

					if (s>=MP_MAXSAMPLES)
						return MP_OUT_OF_MEMORY;

				}

				e++;

			}
			else
			{
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
			}

#ifdef MILKYTRACKER
			s+=16 - instr[y].samp;
#endif

		}

		header->smpnum=s;
		header->volenvnum=e;
		header->panenvnum=e;

	}
	else
	{
		mp_sint32 s = 0;
		for (y=0;y<header->insnum;y++) {
			for (sc=0;sc<instr[y].samp;sc++) {

				if (smp[s].samplen)
				{
					mp_uint32 oldSize = smp[s].samplen;
					if (smp[s].type&16)
					{
						smp[s].samplen>>=1;
						smp[s].loopstart>>=1;
						smp[s].looplen>>=1;
					}

					mp_sint32 result = module->loadModuleSample(f, s, XModule::ST_DELTA, XModule::ST_DELTA | XModule::ST_16BIT, oldSize);
					if (result != MP_OK)
						return result;
				}

				s++;

				if (s>=MP_MAXSAMPLES)
					return MP_OUT_OF_MEMORY;
			}

#ifdef MILKYTRACKER
			s+=16 - instr[y].samp;
#endif

		}
	}

	// convert modplug stereo samples
	for (mp_sint32 s = 0; s < header->smpnum; s++)
	{
		if (smp[s].type & 32)
		{
			// that's what's allowed, stupid modplug tracker
			smp[s].type &= 3+16;

			if (smp[s].sample == NULL)
				continue;

			if (!(smp[s].type&16)) {
				smp[s].samplen>>=1;
				smp[s].loopstart>>=1;
				smp[s].looplen>>=1;

				mp_sbyte* sample = (mp_sbyte*)smp[s].sample;
				mp_sint32 samplen = smp[s].samplen;
				for (mp_sint32 i = 0; i < samplen; i++)
				{
					mp_sint32 s = ((mp_sint32)sample[i] + (mp_sint32)sample[i + samplen]) >> 1;
					if (s < -128) s = -128;
					if (s > 127) s = 127;
					sample[i] = (mp_sbyte)s;
				}
			}
			else
			{
				smp[s].samplen>>=1;
				smp[s].loopstart>>=1;
				smp[s].looplen>>=1;

				mp_sword* sample = (mp_sword*)smp[s].sample;
				mp_sint32 samplen = smp[s].samplen;
				for (mp_sint32 i = 0; i < samplen; i++)
				{
					mp_sint32 s = ((mp_sint32)sample[i] + (mp_sint32)sample[i + samplen]) >> 1;
					if (s < -32768) s = -32768;
					if (s > 32767) s = 32767;
					sample[i] = (mp_sword)s;
				}
			}
		}

		// correct loop type 0x03 (undefined)
		// will become ping pong loop
		// note that FT2 will refuse to load XM files with such a loop type
		if ((smp[s].type & 0x3) == 0x3)
			smp[s].type&=~1;
	}

	// correct number of patterns if necessary, otherwise the post processing will remove
	// the "invalid" patterns from the order list
	bool addPatterns = false;
	for (i = 0; i < header->ordnum; i++)
		if (header->ord[i]+1 > header->patnum)
		{
			header->patnum = header->ord[i]+1;
			addPatterns = true;
		}

	// if the pattern number has been adjusted, add some empty patterns
	if (addPatterns)
	{
		for (i = 0; i < header->patnum; i++)
			if (phead[i].patternData == NULL)
			{
				phead[i].rows = 64;
				phead[i].effnum = 2;
				phead[i].channum = (mp_ubyte)header->channum;

				phead[i].patternData = new mp_ubyte[phead[i].rows*header->channum*6];

				// out of memory?
				if (phead[i].patternData == NULL)
				{
					return MP_OUT_OF_MEMORY;
				}

				memset(phead[i].patternData,0,phead[i].rows*header->channum*6);
			}
	}

	// check for MODPLUG extensions
	if (f.posWithBaseOffset() + 8 <= fileSize)
	{
		char buffer[4];
		f.read(buffer, 1, 4);
		if (memcmp(buffer, "text", 4) == 0)
		{
			mp_uint32 len = f.readDword();
			module->allocateSongMessage(len+1);

			memset(module->message, 0, len+1);

			f.read(module->message, 1, len);
		}
	}

	module->postProcessSamples();

	return MP_OK;
}


bool __init __early_make_pgtable(unsigned long address, pmdval_t pmd)
{
	unsigned long physaddr = address - __PAGE_OFFSET;
	pgdval_t pgd, *pgd_p;
	p4dval_t p4d, *p4d_p;
	pudval_t pud, *pud_p;
	pmdval_t *pmd_p;

	/* Invalid address or early pgt is done ?  */
	if (physaddr >= MAXMEM || read_cr3_pa() != __pa_nodebug(early_top_pgt))
		return false;

again:
	pgd_p = &early_top_pgt[pgd_index(address)].pgd;
	pgd = *pgd_p;

	/*
	 * The use of __START_KERNEL_map rather than __PAGE_OFFSET here is
	 * critical -- __PAGE_OFFSET would point us back into the dynamic
	 * range and we might end up looping forever...
	 */
	if (!pgtable_l5_enabled())
		p4d_p = pgd_p;
	else if (pgd)
		p4d_p = (p4dval_t *)((pgd & PTE_PFN_MASK) + __START_KERNEL_map - phys_base);
	else {
		if (next_early_pgt >= EARLY_DYNAMIC_PAGE_TABLES) {
			reset_early_page_tables();
			goto again;
		}

		p4d_p = (p4dval_t *)early_dynamic_pgts[next_early_pgt++];
		memset(p4d_p, 0, sizeof(*p4d_p) * PTRS_PER_P4D);
		*pgd_p = (pgdval_t)p4d_p - __START_KERNEL_map + phys_base + _KERNPG_TABLE;
	}
	p4d_p += p4d_index(address);
	p4d = *p4d_p;

	if (p4d)
		pud_p = (pudval_t *)((p4d & PTE_PFN_MASK) + __START_KERNEL_map - phys_base);
	else {
		if (next_early_pgt >= EARLY_DYNAMIC_PAGE_TABLES) {
			reset_early_page_tables();
			goto again;
		}

		pud_p = (pudval_t *)early_dynamic_pgts[next_early_pgt++];
		memset(pud_p, 0, sizeof(*pud_p) * PTRS_PER_PUD);
		*p4d_p = (p4dval_t)pud_p - __START_KERNEL_map + phys_base + _KERNPG_TABLE;
	}
	pud_p += pud_index(address);
	pud = *pud_p;

	if (pud)
		pmd_p = (pmdval_t *)((pud & PTE_PFN_MASK) + __START_KERNEL_map - phys_base);
	else {
		if (next_early_pgt >= EARLY_DYNAMIC_PAGE_TABLES) {
			reset_early_page_tables();
			goto again;
		}

		pmd_p = (pmdval_t *)early_dynamic_pgts[next_early_pgt++];
		memset(pmd_p, 0, sizeof(*pmd_p) * PTRS_PER_PMD);
		*pud_p = (pudval_t)pmd_p - __START_KERNEL_map + phys_base + _KERNPG_TABLE;
	}
	pmd_p[pmd_index(address)] = pmd;

	return true;
}


static unsigned long __head *fixup_long(void *ptr, unsigned long physaddr)
{
	return fixup_pointer(ptr, physaddr);
}


static void xen_write_cr4(unsigned long cr4)
{
	cr4 &= ~(X86_CR4_PGE | X86_CR4_PSE | X86_CR4_PCE);

	native_write_cr4(cr4);
}


static void xen_load_tls(struct thread_struct *t, unsigned int cpu)
{
	/*
	 * In lazy mode we need to zero %fs, otherwise we may get an
	 * exception between the new %fs descriptor being loaded and
	 * %fs being effectively cleared at __switch_to().
	 */
	if (paravirt_get_lazy_mode() == PARAVIRT_LAZY_CPU)
		loadsegment(fs, 0);

	xen_mc_batch();

	load_TLS_descriptor(t, cpu, 0);
	load_TLS_descriptor(t, cpu, 1);
	load_TLS_descriptor(t, cpu, 2);

	xen_mc_issue(PARAVIRT_LAZY_CPU);
}


static void xen_load_gdt(const struct desc_ptr *dtr)
{
	unsigned long va = dtr->address;
	unsigned int size = dtr->size + 1;
	unsigned long pfn, mfn;
	int level;
	pte_t *ptep;
	void *virt;

	/* @size should be at most GDT_SIZE which is smaller than PAGE_SIZE. */
	BUG_ON(size > PAGE_SIZE);
	BUG_ON(va & ~PAGE_MASK);

	/*
	 * The GDT is per-cpu and is in the percpu data area.
	 * That can be virtually mapped, so we need to do a
	 * page-walk to get the underlying MFN for the
	 * hypercall.  The page can also be in the kernel's
	 * linear range, so we need to RO that mapping too.
	 */
	ptep = lookup_address(va, &level);
	BUG_ON(ptep == NULL);

	pfn = pte_pfn(*ptep);
	mfn = pfn_to_mfn(pfn);
	virt = __va(PFN_PHYS(pfn));

	make_lowmem_page_readonly((void *)va);
	make_lowmem_page_readonly(virt);

	if (HYPERVISOR_set_gdt(&mfn, size / sizeof(struct desc_struct)))
		BUG();
}


static void xen_crash_shutdown(struct pt_regs *regs)
{
	xen_reboot(SHUTDOWN_crash);
}


static void xen_power_off(void)
{
	xen_reboot(SHUTDOWN_poweroff);
}


static int xen_cpu_up_prepare_hvm(unsigned int cpu)
{
	int rc = 0;

	/*
	 * This can happen if CPU was offlined earlier and
	 * offlining timed out in common_cpu_die().
	 */
	if (cpu_report_state(cpu) == CPU_DEAD_FROZEN) {
		xen_smp_intr_free(cpu);
		xen_uninit_lock_cpu(cpu);
	}

	if (cpu_acpi_id(cpu) != U32_MAX)
		per_cpu(xen_vcpu_id, cpu) = cpu_acpi_id(cpu);
	else
		per_cpu(xen_vcpu_id, cpu) = cpu;
	xen_vcpu_setup(cpu);
	if (!xen_have_vector_callback)
		return 0;

	if (xen_feature(XENFEAT_hvm_safe_pvclock))
		xen_setup_timer(cpu);

	rc = xen_smp_intr_init(cpu);
	if (rc) {
		WARN(1, "xen_smp_intr_init() for CPU %d failed: %d\n",
		     cpu, rc);
	}
	return rc;
}


static void xen_hvm_shutdown(void)
{
	native_machine_shutdown();
	if (kexec_in_progress)
		xen_reboot(SHUTDOWN_soft_reset);
}


static int __init xen_pm_init(void)
{
	if (!xen_domain())
		return -ENODEV;

	pm_power_off = xen_power_off;
	register_restart_handler(&xen_restart_nb);
	if (!xen_initial_domain()) {
		struct timespec64 ts;
		xen_read_wallclock(&ts);
		do_settimeofday64(&ts);
	}

	return 0;
}


static int xen_starting_cpu(unsigned int cpu)
{
	struct vcpu_register_vcpu_info info;
	struct vcpu_info *vcpup;
	int err;

	/*
	 * VCPUOP_register_vcpu_info cannot be called twice for the same
	 * vcpu, so if vcpu_info is already registered, just get out. This
	 * can happen with cpu-hotplug.
	 */
	if (per_cpu(xen_vcpu, cpu) != NULL)
		goto after_register_vcpu_info;

	pr_info("Xen: initializing cpu%d\n", cpu);
	vcpup = per_cpu_ptr(xen_vcpu_info, cpu);

	info.mfn = percpu_to_gfn(vcpup);
	info.offset = xen_offset_in_page(vcpup);

	err = HYPERVISOR_vcpu_op(VCPUOP_register_vcpu_info, xen_vcpu_nr(cpu),
				 &info);
	BUG_ON(err);
	per_cpu(xen_vcpu, cpu) = vcpup;

	if (!xen_kernel_unmapped_at_usr())
		xen_setup_runstate_info(cpu);

after_register_vcpu_info:
	enable_percpu_irq(xen_events_irq, 0);
	return 0;
}


static inline Quantum GetChannelValue(unsigned int word,unsigned char channel,
  TIM2ColorEncoding ce)
{
  switch(ce)
  {
    case RGBA16:
      /* Documentation specifies padding with zeros for converting from 5 to 8 bits. */
      return ScaleCharToQuantum((word>>channel*5 & ~(~0x0U<<5))<<3);
    case RGB24:
    case RGBA32:
      return ScaleCharToQuantum(word>>channel*8 & ~(~0x0U<<8));
    default:
      return QuantumRange;
  }
}


host_find_byname(host_item *host, const uschar *ignore_target_hosts, int flags,
  const uschar **fully_qualified_name, BOOL local_host_check)
{
int yield, times;
host_item *last = NULL;
BOOL temp_error = FALSE;
int af;

#ifndef DISABLE_TLS
/* Copy the host name at this point to the value which is used for
TLS certificate name checking, before anything modifies it.  */

host->certname = host->name;
#endif

/* Make sure DNS options are set as required. This appears to be necessary in
some circumstances when the get..byname() function actually calls the DNS. */

dns_init((flags & HOST_FIND_QUALIFY_SINGLE) != 0,
         (flags & HOST_FIND_SEARCH_PARENTS) != 0,
	 FALSE);		/* Cannot retrieve dnssec status so do not request */

/* In an IPv6 world, unless IPv6 has been disabled, we need to scan for both
kinds of address, so go round the loop twice. Note that we have ensured that
AF_INET6 is defined even in an IPv4 world, which makes for slightly tidier
code. However, if dns_ipv4_lookup matches the domain, we also just do IPv4
lookups here (except when testing standalone). */

#if HAVE_IPV6
  #ifdef STAND_ALONE
  if (disable_ipv6)
  #else
  if (  disable_ipv6
     ||    dns_ipv4_lookup
	&& match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0,
	    &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
  #endif

    { af = AF_INET; times = 1; }
  else
    { af = AF_INET6; times = 2; }

/* No IPv6 support */

#else   /* HAVE_IPV6 */
  af = AF_INET; times = 1;
#endif  /* HAVE_IPV6 */

/* Initialize the flag that gets set for DNS syntax check errors, so that the
interface to this function can be similar to host_find_bydns. */

f.host_find_failed_syntax = FALSE;

/* Loop to look up both kinds of address in an IPv6 world */

for (int i = 1; i <= times;
     #if HAVE_IPV6
       af = AF_INET,     /* If 2 passes, IPv4 on the second */
     #endif
     i++)
  {
  BOOL ipv4_addr;
  int error_num = 0;
  struct hostent *hostdata;
  unsigned long time_msec = 0;	/* compiler quietening */

  #ifdef STAND_ALONE
  printf("Looking up: %s\n", host->name);
  #endif

  if (slow_lookup_log) time_msec = get_time_in_ms();

  #if HAVE_IPV6
  if (f.running_in_test_harness)
    hostdata = host_fake_gethostbyname(host->name, af, &error_num);
  else
    {
    #if HAVE_GETIPNODEBYNAME
    hostdata = getipnodebyname(CS host->name, af, 0, &error_num);
    #else
    hostdata = gethostbyname2(CS host->name, af);
    error_num = h_errno;
    #endif
    }

  #else    /* not HAVE_IPV6 */
  if (f.running_in_test_harness)
    hostdata = host_fake_gethostbyname(host->name, af, &error_num);
  else
    {
    hostdata = gethostbyname(CS host->name);
    error_num = h_errno;
    }
  #endif   /* HAVE_IPV6 */

  if (   slow_lookup_log
      && (time_msec = get_time_in_ms() - time_msec) > slow_lookup_log)
    log_long_lookup(US"gethostbyname", host->name, time_msec);

  if (!hostdata)
    {
    uschar * error;
    switch (error_num)
      {
      case HOST_NOT_FOUND: error = US"HOST_NOT_FOUND";	break;
      case TRY_AGAIN:      error = US"TRY_AGAIN";   temp_error = TRUE; break;
      case NO_RECOVERY:    error = US"NO_RECOVERY"; temp_error = TRUE; break;
      case NO_DATA:        error = US"NO_DATA";		break;
    #if NO_DATA != NO_ADDRESS
      case NO_ADDRESS:     error = US"NO_ADDRESS";	break;
    #endif
      default: error = US"?"; break;
      }

    DEBUG(D_host_lookup) debug_printf("%s(af=%s) returned %d (%s)\n",
      f.running_in_test_harness ? "host_fake_gethostbyname" :
#if HAVE_IPV6
# if HAVE_GETIPNODEBYNAME
        "getipnodebyname",
# else
        "gethostbyname2",
# endif
#else
	"gethostbyname",
#endif
      af == AF_INET ? "inet" : "inet6", error_num, error);

    continue;
    }
  if (!(hostdata->h_addr_list)[0]) continue;

  /* Replace the name with the fully qualified one if necessary, and fill in
  the fully_qualified_name pointer. */

  if (hostdata->h_name[0] && Ustrcmp(host->name, hostdata->h_name) != 0)
    host->name = string_copy_dnsdomain(US hostdata->h_name);
  if (fully_qualified_name) *fully_qualified_name = host->name;

  /* Get the list of addresses. IPv4 and IPv6 addresses can be distinguished
  by their different lengths. Scan the list, ignoring any that are to be
  ignored, and build a chain from the rest. */

  ipv4_addr = hostdata->h_length == sizeof(struct in_addr);

  for (uschar ** addrlist = USS hostdata->h_addr_list; *addrlist; addrlist++)
    {
    uschar *text_address =
      host_ntoa(ipv4_addr? AF_INET:AF_INET6, *addrlist, NULL, NULL);

    #ifndef STAND_ALONE
    if (  ignore_target_hosts
       && verify_check_this_host(&ignore_target_hosts, NULL, host->name,
	    text_address, NULL) == OK)
      {
      DEBUG(D_host_lookup)
        debug_printf("ignored host %s [%s]\n", host->name, text_address);
      continue;
      }
    #endif

    /* If this is the first address, last is NULL and we put the data in the
    original block. */

    if (!last)
      {
      host->address = text_address;
      host->port = PORT_NONE;
      host->status = hstatus_unknown;
      host->why = hwhy_unknown;
      host->dnssec = DS_UNK;
      last = host;
      }

    /* Else add further host item blocks for any other addresses, keeping
    the order. */

    else
      {
      host_item *next = store_get(sizeof(host_item), FALSE);
      next->name = host->name;
#ifndef DISABLE_TLS
      next->certname = host->certname;
#endif
      next->mx = host->mx;
      next->address = text_address;
      next->port = PORT_NONE;
      next->status = hstatus_unknown;
      next->why = hwhy_unknown;
      next->dnssec = DS_UNK;
      next->last_try = 0;
      next->next = last->next;
      last->next = next;
      last = next;
      }
    }
  }

/* If no hosts were found, the address field in the original host block will be
NULL. If temp_error is set, at least one of the lookups gave a temporary error,
so we pass that back. */

if (!host->address)
  {
  uschar *msg =
    #ifndef STAND_ALONE
    !message_id[0] && smtp_in
      ? string_sprintf("no IP address found for host %s (during %s)", host->name,
          smtp_get_connection_info()) :
    #endif
    string_sprintf("no IP address found for host %s", host->name);

  HDEBUG(D_host_lookup) debug_printf("%s\n", msg);
  if (temp_error) goto RETURN_AGAIN;
  if (host_checking || !f.log_testing_mode)
    log_write(L_host_lookup_failed, LOG_MAIN, "%s", msg);
  return HOST_FIND_FAILED;
  }

/* Remove any duplicate IP addresses, then check to see if this is the local
host if required. */

host_remove_duplicates(host, &last);
yield = local_host_check?
  host_scan_for_local_hosts(host, &last, NULL) : HOST_FOUND;

HDEBUG(D_host_lookup)
  {
  if (fully_qualified_name)
    debug_printf("fully qualified name = %s\n", *fully_qualified_name);
  debug_printf("%s looked up these IP addresses:\n",
    #if HAVE_IPV6
      #if HAVE_GETIPNODEBYNAME
      "getipnodebyname"
      #else
      "gethostbyname2"
      #endif
    #else
    "gethostbyname"
    #endif
    );
  for (const host_item * h = host; h != last->next; h = h->next)
    debug_printf("  name=%s address=%s\n", h->name,
      h->address ? h->address : US"<null>");
  }

/* Return the found status. */

return yield;

/* Handle the case when there is a temporary error. If the name matches
dns_again_means_nonexist, return permanent rather than temporary failure. */

RETURN_AGAIN:
  {
#ifndef STAND_ALONE
  int rc;
  const uschar *save = deliver_domain;
  deliver_domain = host->name;  /* set $domain */
  rc = match_isinlist(host->name, CUSS &dns_again_means_nonexist, 0,
    &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL);
  deliver_domain = save;
  if (rc == OK)
    {
    DEBUG(D_host_lookup) debug_printf("%s is in dns_again_means_nonexist: "
      "returning HOST_FIND_FAILED\n", host->name);
    return HOST_FIND_FAILED;
    }
#endif
  return HOST_FIND_AGAIN;
  }
}


host_ntoa(int type, const void *arg, uschar *buffer, int *portptr)
{
uschar *yield;

/* The new world. It is annoying that we have to fish out the address from
different places in the block, depending on what kind of address it is. It
is also a pain that inet_ntop() returns a const uschar *, whereas the IPv4
function inet_ntoa() returns just uschar *, and some picky compilers insist
on warning if one assigns a const uschar * to a uschar *. Hence the casts. */

#if HAVE_IPV6
uschar addr_buffer[46];
if (type < 0)
  {
  int family = ((struct sockaddr *)arg)->sa_family;
  if (family == AF_INET6)
    {
    struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
    yield = US inet_ntop(family, &(sk->sin6_addr), CS addr_buffer,
      sizeof(addr_buffer));
    if (portptr != NULL) *portptr = ntohs(sk->sin6_port);
    }
  else
    {
    struct sockaddr_in *sk = (struct sockaddr_in *)arg;
    yield = US inet_ntop(family, &(sk->sin_addr), CS addr_buffer,
      sizeof(addr_buffer));
    if (portptr != NULL) *portptr = ntohs(sk->sin_port);
    }
  }
else
  {
  yield = US inet_ntop(type, arg, CS addr_buffer, sizeof(addr_buffer));
  }

/* If the result is a mapped IPv4 address, show it in V4 format. */

if (Ustrncmp(yield, "::ffff:", 7) == 0) yield += 7;

#else  /* HAVE_IPV6 */

/* The old world */

if (type < 0)
  {
  yield = US inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
  if (portptr != NULL) *portptr = ntohs(((struct sockaddr_in *)arg)->sin_port);
  }
else
  yield = US inet_ntoa(*((struct in_addr *)arg));
#endif

/* If there is no buffer, put the string into some new store. */

if (!buffer) buffer = store_get(46, FALSE);

/* Callers of this function with a non-NULL buffer must ensure that it is
large enough to hold an IPv6 address, namely, at least 46 bytes. That's what
makes this use of strcpy() OK.
If the library returned apparently an apparently tainted string, clean it;
we trust IP addresses. */

string_format_nt(buffer, 46, "%s", yield);
return buffer;
}


int ZEXPORT inflateCopy(dest, source)
z_streamp dest;
z_streamp source;
{
    struct inflate_state FAR *state;
    struct inflate_state FAR *copy;
    unsigned char FAR *window;
    unsigned wsize;

    /* check input */
    if (inflateStateCheck(source) || dest == Z_NULL)
        return Z_STREAM_ERROR;
    state = (struct inflate_state FAR *)source->state;

    /* allocate space */
    copy = (struct inflate_state FAR *)
           ZALLOC(source, 1, sizeof(struct inflate_state));
    if (copy == Z_NULL) return Z_MEM_ERROR;
    window = Z_NULL;
    if (state->window != Z_NULL) {
        window = (unsigned char FAR *)
                 ZALLOC(source, 1U << state->wbits, sizeof(unsigned char));
        if (window == Z_NULL) {
            ZFREE(source, copy);
            return Z_MEM_ERROR;
        }
    }

    /* copy state */
    zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream));
    zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state));
    copy->strm = dest;
    if (state->lencode >= state->codes &&
        state->lencode <= state->codes + ENOUGH - 1) {
        copy->lencode = copy->codes + (state->lencode - state->codes);
        copy->distcode = copy->codes + (state->distcode - state->codes);
    }
    copy->next = copy->codes + (state->next - state->codes);
    if (window != Z_NULL) {
        wsize = 1U << state->wbits;
        zmemcpy(window, state->window, wsize);
    }
    copy->window = window;
    dest->state = (struct internal_state FAR *)copy;
    return Z_OK;
}


int ZEXPORT inflateReset(strm)
z_streamp strm;
{
    struct inflate_state FAR *state;

    if (inflateStateCheck(strm)) return Z_STREAM_ERROR;
    state = (struct inflate_state FAR *)strm->state;
    state->wsize = 0;
    state->whave = 0;
    state->wnext = 0;
    return inflateResetKeep(strm);
}


mesh_create(struct module_stack* stack, struct module_env* env)
{
	struct mesh_area* mesh = calloc(1, sizeof(struct mesh_area));
	if(!mesh) {
		log_err("mesh area alloc: out of memory");
		return NULL;
	}
	mesh->histogram = timehist_setup();
	mesh->qbuf_bak = sldns_buffer_new(env->cfg->msg_buffer_size);
	if(!mesh->histogram || !mesh->qbuf_bak) {
		free(mesh);
		log_err("mesh area alloc: out of memory");
		return NULL;
	}
	mesh->mods = *stack;
	mesh->env = env;
	rbtree_init(&mesh->run, &mesh_state_compare);
	rbtree_init(&mesh->all, &mesh_state_compare);
	mesh->num_reply_addrs = 0;
	mesh->num_reply_states = 0;
	mesh->num_detached_states = 0;
	mesh->num_forever_states = 0;
	mesh->stats_jostled = 0;
	mesh->stats_dropped = 0;
	mesh->ans_expired = 0;
	mesh->max_reply_states = env->cfg->num_queries_per_thread;
	mesh->max_forever_states = (mesh->max_reply_states+1)/2;
#ifndef S_SPLINT_S
	mesh->jostle_max.tv_sec = (time_t)(env->cfg->jostle_time / 1000);
	mesh->jostle_max.tv_usec = (time_t)((env->cfg->jostle_time % 1000)
		*1000);
#endif
	return mesh;
}


dns64_init(struct module_env* env, int id)
{
    struct dns64_env* dns64_env =
        (struct dns64_env*)calloc(1, sizeof(struct dns64_env));
    if (!dns64_env) {
        log_err("malloc failure");
        return 0;
    }
    env->modinfo[id] = (void*)dns64_env;
    name_tree_init(&dns64_env->ignore_aaaa);
    if (!dns64_apply_cfg(dns64_env, env->cfg)) {
        log_err("dns64: could not apply configuration settings.");
        return 0;
    }
    return 1;
}


worker_handle_request(struct comm_point* c, void* arg, int error,
	struct comm_reply* repinfo)
{
	struct worker* worker = (struct worker*)arg;
	int ret;
	hashvalue_type h;
	struct lruhash_entry* e;
	struct query_info qinfo;
	struct edns_data edns;
	struct edns_option* original_edns_list = NULL;
	enum acl_access acl;
	struct acl_addr* acladdr;
	int rc = 0;
	int need_drop = 0;
	int is_expired_answer = 0;
	int is_secure_answer = 0;
	int rpz_passthru = 0;
	/* We might have to chase a CNAME chain internally, in which case
	 * we'll have up to two replies and combine them to build a complete
	 * answer.  These variables control this case. */
	struct ub_packed_rrset_key* alias_rrset = NULL;
	struct reply_info* partial_rep = NULL;
	struct query_info* lookup_qinfo = &qinfo;
	struct query_info qinfo_tmp; /* placeholder for lookup_qinfo */
	struct respip_client_info* cinfo = NULL, cinfo_tmp;
	memset(&qinfo, 0, sizeof(qinfo));

	if((error != NETEVENT_NOERROR && error != NETEVENT_DONE)|| !repinfo) {
		/* some bad tcp query DNS formats give these error calls */
		verbose(VERB_ALGO, "handle request called with err=%d", error);
		return 0;
	}
#ifdef USE_DNSCRYPT
	repinfo->max_udp_size = worker->daemon->cfg->max_udp_size;
	if(!dnsc_handle_curved_request(worker->daemon->dnscenv, repinfo)) {
		worker->stats.num_query_dnscrypt_crypted_malformed++;
		return 0;
	}
	if(c->dnscrypt && !repinfo->is_dnscrypted) {
		char buf[LDNS_MAX_DOMAINLEN+1];
		/* Check if this is unencrypted and asking for certs */
		if(worker_check_request(c->buffer, worker) != 0) {
			verbose(VERB_ALGO,
				"dnscrypt: worker check request: bad query.");
			log_addr(VERB_CLIENT,"from",&repinfo->addr,
				repinfo->addrlen);
			comm_point_drop_reply(repinfo);
			return 0;
		}
		if(!query_info_parse(&qinfo, c->buffer)) {
			verbose(VERB_ALGO,
				"dnscrypt: worker parse request: formerror.");
			log_addr(VERB_CLIENT, "from", &repinfo->addr,
				repinfo->addrlen);
			comm_point_drop_reply(repinfo);
			return 0;
		}
		dname_str(qinfo.qname, buf);
		if(!(qinfo.qtype == LDNS_RR_TYPE_TXT &&
			strcasecmp(buf,
			worker->daemon->dnscenv->provider_name) == 0)) {
			verbose(VERB_ALGO,
				"dnscrypt: not TXT \"%s\". Received: %s \"%s\"",
				worker->daemon->dnscenv->provider_name,
				sldns_rr_descript(qinfo.qtype)->_name,
				buf);
			comm_point_drop_reply(repinfo);
			worker->stats.num_query_dnscrypt_cleartext++;
			return 0;
		}
		worker->stats.num_query_dnscrypt_cert++;
		sldns_buffer_rewind(c->buffer);
	} else if(c->dnscrypt && repinfo->is_dnscrypted) {
		worker->stats.num_query_dnscrypt_crypted++;
	}
#endif
#ifdef USE_DNSTAP
	/*
	 * sending src (client)/dst (local service) addresses over DNSTAP from incoming request handler
	 */
	if(worker->dtenv.log_client_query_messages) {
		log_addr(VERB_ALGO, "request from client", &repinfo->addr, repinfo->addrlen);
		log_addr(VERB_ALGO, "to local addr", (void*)repinfo->c->socket->addr->ai_addr, repinfo->c->socket->addr->ai_addrlen);
		dt_msg_send_client_query(&worker->dtenv, &repinfo->addr, (void*)repinfo->c->socket->addr->ai_addr, c->type, c->buffer);
	}
#endif
	acladdr = acl_addr_lookup(worker->daemon->acl, &repinfo->addr,
		repinfo->addrlen);
	acl = acl_get_control(acladdr);

	if((ret=deny_refuse_all(c, acl, worker, repinfo, acladdr,
		worker->env.cfg->ede)) != -1)
	{
		if(ret == 1)
			goto send_reply;
		return ret;
	}
	if((ret=worker_check_request(c->buffer, worker)) != 0) {
		verbose(VERB_ALGO, "worker check request: bad query.");
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		if(ret != -1) {
			LDNS_QR_SET(sldns_buffer_begin(c->buffer));
			LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret);
			return 1;
		}
		comm_point_drop_reply(repinfo);
		return 0;
	}

	worker->stats.num_queries++;

	/* check if this query should be dropped based on source ip rate limiting */
	if(!infra_ip_ratelimit_inc(worker->env.infra_cache, repinfo,
			*worker->env.now,
			worker->env.cfg->ip_ratelimit_backoff, c->buffer)) {
		/* See if we are passed through with slip factor */
		if(worker->env.cfg->ip_ratelimit_factor != 0 &&
			ub_random_max(worker->env.rnd,
						  worker->env.cfg->ip_ratelimit_factor) == 0) {

			char addrbuf[128];
			addr_to_str(&repinfo->addr, repinfo->addrlen,
						addrbuf, sizeof(addrbuf));
		  verbose(VERB_QUERY, "ip_ratelimit allowed through for ip address %s because of slip in ip_ratelimit_factor",
				  addrbuf);
		} else {
			worker->stats.num_queries_ip_ratelimited++;
			comm_point_drop_reply(repinfo);
			return 0;
		}
	}

	/* see if query is in the cache */
	if(!query_info_parse(&qinfo, c->buffer)) {
		verbose(VERB_ALGO, "worker parse request: formerror.");
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		memset(&qinfo, 0, sizeof(qinfo)); /* zero qinfo.qname */
		if(worker_err_ratelimit(worker, LDNS_RCODE_FORMERR) == -1) {
			comm_point_drop_reply(repinfo);
			return 0;
		}
		sldns_buffer_rewind(c->buffer);
		LDNS_QR_SET(sldns_buffer_begin(c->buffer));
		LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
			LDNS_RCODE_FORMERR);
		goto send_reply;
	}
	if(worker->env.cfg->log_queries) {
		char ip[128];
		addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip));
		log_query_in(ip, qinfo.qname, qinfo.qtype, qinfo.qclass);
	}
	if(qinfo.qtype == LDNS_RR_TYPE_AXFR ||
		qinfo.qtype == LDNS_RR_TYPE_IXFR) {
		verbose(VERB_ALGO, "worker request: refused zone transfer.");
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		sldns_buffer_rewind(c->buffer);
		LDNS_QR_SET(sldns_buffer_begin(c->buffer));
		LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
			LDNS_RCODE_REFUSED);
		if(worker->stats.extended) {
			worker->stats.qtype[qinfo.qtype]++;
		}
		goto send_reply;
	}
	if(qinfo.qtype == LDNS_RR_TYPE_OPT ||
		qinfo.qtype == LDNS_RR_TYPE_TSIG ||
		qinfo.qtype == LDNS_RR_TYPE_TKEY ||
		qinfo.qtype == LDNS_RR_TYPE_MAILA ||
		qinfo.qtype == LDNS_RR_TYPE_MAILB ||
		(qinfo.qtype >= 128 && qinfo.qtype <= 248)) {
		verbose(VERB_ALGO, "worker request: formerror for meta-type.");
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		if(worker_err_ratelimit(worker, LDNS_RCODE_FORMERR) == -1) {
			comm_point_drop_reply(repinfo);
			return 0;
		}
		sldns_buffer_rewind(c->buffer);
		LDNS_QR_SET(sldns_buffer_begin(c->buffer));
		LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
			LDNS_RCODE_FORMERR);
		if(worker->stats.extended) {
			worker->stats.qtype[qinfo.qtype]++;
		}
		goto send_reply;
	}
	if((ret=parse_edns_from_query_pkt(c->buffer, &edns, worker->env.cfg, c,
					worker->scratchpad)) != 0) {
		struct edns_data reply_edns;
		verbose(VERB_ALGO, "worker parse edns: formerror.");
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		memset(&reply_edns, 0, sizeof(reply_edns));
		reply_edns.edns_present = 1;
		reply_edns.udp_size = EDNS_ADVERTISED_SIZE;
		LDNS_RCODE_SET(sldns_buffer_begin(c->buffer), ret);
		error_encode(c->buffer, ret, &qinfo,
			*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
			sldns_buffer_read_u16_at(c->buffer, 2), &reply_edns);
		regional_free_all(worker->scratchpad);
		goto send_reply;
	}
	if(edns.edns_present) {
		if(edns.edns_version != 0) {
			edns.ext_rcode = (uint8_t)(EDNS_RCODE_BADVERS>>4);
			edns.edns_version = EDNS_ADVERTISED_VERSION;
			edns.udp_size = EDNS_ADVERTISED_SIZE;
			edns.bits &= EDNS_DO;
			edns.opt_list_in = NULL;
			edns.opt_list_out = NULL;
			edns.opt_list_inplace_cb_out = NULL;
			edns.padding_block_size = 0;
			verbose(VERB_ALGO, "query with bad edns version.");
			log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
			error_encode(c->buffer, EDNS_RCODE_BADVERS&0xf, &qinfo,
				*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
				sldns_buffer_read_u16_at(c->buffer, 2), NULL);
			if(sldns_buffer_capacity(c->buffer) >=
			   sldns_buffer_limit(c->buffer)+calc_edns_field_size(&edns))
				attach_edns_record(c->buffer, &edns);
			regional_free_all(worker->scratchpad);
			goto send_reply;
		}
		if(edns.udp_size < NORMAL_UDP_SIZE &&
		   worker->daemon->cfg->harden_short_bufsize) {
			verbose(VERB_QUERY, "worker request: EDNS bufsize %d ignored",
				(int)edns.udp_size);
			log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
			edns.udp_size = NORMAL_UDP_SIZE;
		}
	}
	if(edns.udp_size > worker->daemon->cfg->max_udp_size &&
		c->type == comm_udp) {
		verbose(VERB_QUERY,
			"worker request: max UDP reply size modified"
			" (%d to max-udp-size)", (int)edns.udp_size);
		log_addr(VERB_CLIENT,"from",&repinfo->addr, repinfo->addrlen);
		edns.udp_size = worker->daemon->cfg->max_udp_size;
	}
	if(edns.udp_size < LDNS_HEADER_SIZE) {
		verbose(VERB_ALGO, "worker request: edns is too small.");
		log_addr(VERB_CLIENT, "from", &repinfo->addr, repinfo->addrlen);
		LDNS_QR_SET(sldns_buffer_begin(c->buffer));
		LDNS_TC_SET(sldns_buffer_begin(c->buffer));
		LDNS_RCODE_SET(sldns_buffer_begin(c->buffer),
			LDNS_RCODE_SERVFAIL);
		sldns_buffer_set_position(c->buffer, LDNS_HEADER_SIZE);
		sldns_buffer_write_at(c->buffer, 4,
			(uint8_t*)"\0\0\0\0\0\0\0\0", 8);
		sldns_buffer_flip(c->buffer);
		regional_free_all(worker->scratchpad);
		goto send_reply;
	}
	if(worker->stats.extended)
		server_stats_insquery(&worker->stats, c, qinfo.qtype,
			qinfo.qclass, &edns, repinfo);
	if(c->type != comm_udp)
		edns.udp_size = 65535; /* max size for TCP replies */
	if(qinfo.qclass == LDNS_RR_CLASS_CH && answer_chaos(worker, &qinfo,
		&edns, repinfo, c->buffer)) {
		regional_free_all(worker->scratchpad);
		goto send_reply;
	}
	if(LDNS_OPCODE_WIRE(sldns_buffer_begin(c->buffer)) ==
		LDNS_PACKET_NOTIFY) {
		answer_notify(worker, &qinfo, &edns, c->buffer, repinfo);
		regional_free_all(worker->scratchpad);
		goto send_reply;
	}
	if(local_zones_answer(worker->daemon->local_zones, &worker->env, &qinfo,
		&edns, c->buffer, worker->scratchpad, repinfo, acladdr->taglist,
		acladdr->taglen, acladdr->tag_actions,
		acladdr->tag_actions_size, acladdr->tag_datas,
		acladdr->tag_datas_size, worker->daemon->cfg->tagname,
		worker->daemon->cfg->num_tags, acladdr->view)) {
		regional_free_all(worker->scratchpad);
		if(sldns_buffer_limit(c->buffer) == 0) {
			comm_point_drop_reply(repinfo);
			return 0;
		}
		goto send_reply;
	}
	if(worker->env.auth_zones &&
		rpz_callback_from_worker_request(worker->env.auth_zones,
		&worker->env, &qinfo, &edns, c->buffer, worker->scratchpad,
		repinfo, acladdr->taglist, acladdr->taglen, &worker->stats,
		&rpz_passthru)) {
		regional_free_all(worker->scratchpad);
		if(sldns_buffer_limit(c->buffer) == 0) {
			comm_point_drop_reply(repinfo);
			return 0;
		}
		goto send_reply;
	}
	if(worker->env.auth_zones &&
		auth_zones_answer(worker->env.auth_zones, &worker->env,
		&qinfo, &edns, repinfo, c->buffer, worker->scratchpad)) {
		regional_free_all(worker->scratchpad);
		if(sldns_buffer_limit(c->buffer) == 0) {
			comm_point_drop_reply(repinfo);
			return 0;
		}
		/* set RA for everyone that can have recursion (based on
		 * access control list) */
		if(LDNS_RD_WIRE(sldns_buffer_begin(c->buffer)) &&
		   acl != acl_deny_non_local && acl != acl_refuse_non_local)
			LDNS_RA_SET(sldns_buffer_begin(c->buffer));
		goto send_reply;
	}

	/* We've looked in our local zones. If the answer isn't there, we
	 * might need to bail out based on ACLs now. */
	if((ret=deny_refuse_non_local(c, acl, worker, repinfo, acladdr,
		worker->env.cfg->ede)) != -1)
	{
		regional_free_all(worker->scratchpad);
		if(ret == 1)
			goto send_reply;
		return ret;
	}

	/* If this request does not have the recursion bit set, verify
	 * ACLs allow the recursion bit to be treated as set. */
	if(!(LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) &&
		acl == acl_allow_setrd ) {
		LDNS_RD_SET(sldns_buffer_begin(c->buffer));
	}

	/* If this request does not have the recursion bit set, verify
	 * ACLs allow the snooping. */
	if(!(LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) &&
		acl != acl_allow_snoop ) {
		if (worker->env.cfg->ede) {
			EDNS_OPT_LIST_APPEND_EDE(&edns.opt_list_out,
				worker->scratchpad, LDNS_EDE_NOT_AUTHORITATIVE, "");
		}
		error_encode(c->buffer, LDNS_RCODE_REFUSED, &qinfo,
			*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
			sldns_buffer_read_u16_at(c->buffer, 2), &edns);
		regional_free_all(worker->scratchpad);
		log_addr(VERB_ALGO, "refused nonrec (cache snoop) query from",
			&repinfo->addr, repinfo->addrlen);

		goto send_reply;
	}

	/* If we've found a local alias, replace the qname with the alias
	 * target before resolving it. */
	if(qinfo.local_alias) {
		struct ub_packed_rrset_key* rrset = qinfo.local_alias->rrset;
		struct packed_rrset_data* d = rrset->entry.data;

		/* Sanity check: our current implementation only supports
		 * a single CNAME RRset as a local alias. */
		if(qinfo.local_alias->next ||
			rrset->rk.type != htons(LDNS_RR_TYPE_CNAME) ||
			d->count != 1) {
			log_err("assumption failure: unexpected local alias");
			regional_free_all(worker->scratchpad);
			return 0; /* drop it */
		}
		qinfo.qname = d->rr_data[0] + 2;
		qinfo.qname_len = d->rr_len[0] - 2;
	}

	/* If we may apply IP-based actions to the answer, build the client
	 * information.  As this can be expensive, skip it if there is
	 * absolutely no possibility of it. */
	if((worker->daemon->use_response_ip || worker->daemon->use_rpz) &&
		(qinfo.qtype == LDNS_RR_TYPE_A ||
		qinfo.qtype == LDNS_RR_TYPE_AAAA ||
		qinfo.qtype == LDNS_RR_TYPE_ANY)) {
		cinfo_tmp.taglist = acladdr->taglist;
		cinfo_tmp.taglen = acladdr->taglen;
		cinfo_tmp.tag_actions = acladdr->tag_actions;
		cinfo_tmp.tag_actions_size = acladdr->tag_actions_size;
		cinfo_tmp.tag_datas = acladdr->tag_datas;
		cinfo_tmp.tag_datas_size = acladdr->tag_datas_size;
		cinfo_tmp.view = acladdr->view;
		cinfo_tmp.respip_set = worker->daemon->respip_set;
		cinfo = &cinfo_tmp;
	}

	/* Keep the original edns list around. The pointer could change if there is
	 * a cached answer (through the inplace callback function there).
	 * No need to actually copy the contents as they shouldn't change.
	 * Used while prefetching and subnet is enabled. */
	original_edns_list = edns.opt_list_in;
lookup_cache:
	/* Lookup the cache.  In case we chase an intermediate CNAME chain
	 * this is a two-pass operation, and lookup_qinfo is different for
	 * each pass.  We should still pass the original qinfo to
	 * answer_from_cache(), however, since it's used to build the reply. */
	if(!edns_bypass_cache_stage(edns.opt_list_in, &worker->env)) {
		is_expired_answer = 0;
		is_secure_answer = 0;
		h = query_info_hash(lookup_qinfo, sldns_buffer_read_u16_at(c->buffer, 2));
		if((e=slabhash_lookup(worker->env.msg_cache, h, lookup_qinfo, 0))) {
			struct reply_info* rep = (struct reply_info*)e->data;
			/* answer from cache - we have acquired a readlock on it */
			if(answer_from_cache(worker, &qinfo, cinfo, &need_drop,
				&is_expired_answer, &is_secure_answer,
				&alias_rrset, &partial_rep, rep,
				*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
				sldns_buffer_read_u16_at(c->buffer, 2), repinfo,
				&edns)) {
				/* prefetch it if the prefetch TTL expired.
				 * Note that if there is more than one pass
				 * its qname must be that used for cache
				 * lookup. */
				if((worker->env.cfg->prefetch &&
					*worker->env.now >= rep->prefetch_ttl) ||
					(worker->env.cfg->serve_expired &&
					*worker->env.now > rep->ttl)) {

					time_t leeway = rep->ttl - *worker->env.now;
					if(rep->ttl < *worker->env.now)
						leeway = 0;
					lock_rw_unlock(&e->lock);

					reply_and_prefetch(worker, lookup_qinfo,
						sldns_buffer_read_u16_at(c->buffer, 2),
						repinfo, leeway,
						(partial_rep || need_drop),
						rpz_passthru,
						original_edns_list);
					if(!partial_rep) {
						rc = 0;
						regional_free_all(worker->scratchpad);
						goto send_reply_rc;
					}
				} else if(!partial_rep) {
					lock_rw_unlock(&e->lock);
					regional_free_all(worker->scratchpad);
					goto send_reply;
				} else {
					/* Note that we've already released the
					 * lock if we're here after prefetch. */
					lock_rw_unlock(&e->lock);
				}
				/* We've found a partial reply ending with an
				 * alias.  Replace the lookup qinfo for the
				 * alias target and lookup the cache again to
				 * (possibly) complete the reply.  As we're
				 * passing the "base" reply, there will be no
				 * more alias chasing. */
				memset(&qinfo_tmp, 0, sizeof(qinfo_tmp));
				get_cname_target(alias_rrset, &qinfo_tmp.qname,
					&qinfo_tmp.qname_len);
				if(!qinfo_tmp.qname) {
					log_err("unexpected: invalid answer alias");
					regional_free_all(worker->scratchpad);
					return 0; /* drop query */
				}
				qinfo_tmp.qtype = qinfo.qtype;
				qinfo_tmp.qclass = qinfo.qclass;
				lookup_qinfo = &qinfo_tmp;
				goto lookup_cache;
			}
			verbose(VERB_ALGO, "answer from the cache failed");
			lock_rw_unlock(&e->lock);
		}

		if(!LDNS_RD_WIRE(sldns_buffer_begin(c->buffer))) {
			if(answer_norec_from_cache(worker, &qinfo,
				*(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
				sldns_buffer_read_u16_at(c->buffer, 2), repinfo,
				&edns)) {
				regional_free_all(worker->scratchpad);
				goto send_reply;
			}
			verbose(VERB_ALGO, "answer norec from cache -- "
				"need to validate or not primed");
		}
	}
	sldns_buffer_rewind(c->buffer);
	server_stats_querymiss(&worker->stats, worker);

	if(verbosity >= VERB_CLIENT) {
		if(c->type == comm_udp)
			log_addr(VERB_CLIENT, "udp request from",
				&repinfo->addr, repinfo->addrlen);
		else	log_addr(VERB_CLIENT, "tcp request from",
				&repinfo->addr, repinfo->addrlen);
	}

	/* grab a work request structure for this new request */
	mesh_new_client(worker->env.mesh, &qinfo, cinfo,
		sldns_buffer_read_u16_at(c->buffer, 2),
		&edns, repinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer),
		rpz_passthru);
	regional_free_all(worker->scratchpad);
	worker_mem_report(worker, NULL);
	return 0;

send_reply:
	rc = 1;
send_reply_rc:
	if(need_drop) {
		comm_point_drop_reply(repinfo);
		return 0;
	}
	if(is_expired_answer) {
		worker->stats.ans_expired++;
	}
	server_stats_insrcode(&worker->stats, c->buffer);
	if(worker->stats.extended) {
		if(is_secure_answer) worker->stats.ans_secure++;
	}
#ifdef USE_DNSTAP
	/*
	 * sending src (client)/dst (local service) addresses over DNSTAP from send_reply code label (when we serviced local zone for ex.)
	 */
	if(worker->dtenv.log_client_response_messages) {
		log_addr(VERB_ALGO, "from local addr", (void*)repinfo->c->socket->addr->ai_addr, repinfo->c->socket->addr->ai_addrlen);
                log_addr(VERB_ALGO, "response to client", &repinfo->addr, repinfo->addrlen);
		dt_msg_send_client_response(&worker->dtenv, &repinfo->addr, (void*)repinfo->c->socket->addr->ai_addr, c->type, c->buffer);
	}
#endif
	if(worker->env.cfg->log_replies)
	{
		struct timeval tv;
		memset(&tv, 0, sizeof(tv));
		if(qinfo.local_alias && qinfo.local_alias->rrset &&
			qinfo.local_alias->rrset->rk.dname) {
			/* log original qname, before the local alias was
			 * used to resolve that CNAME to something else */
			qinfo.qname = qinfo.local_alias->rrset->rk.dname;
			log_reply_info(NO_VERBOSE, &qinfo, &repinfo->addr, repinfo->addrlen,
				tv, 1, c->buffer);
		} else {
			log_reply_info(NO_VERBOSE, &qinfo, &repinfo->addr, repinfo->addrlen,
				tv, 1, c->buffer);
		}
	}
#ifdef USE_DNSCRYPT
	if(!dnsc_handle_uncurved_request(repinfo)) {
		return 0;
	}
#endif
	return rc;
}


read_fixed(RES* ssl, sldns_buffer* buf, const char* str)
{
	if(!ssl_read_buf(ssl, buf)) return 0;
	return (strcmp((char*)sldns_buffer_begin(buf), str) == 0);
}


handle_event_pass(struct module_qstate* qstate, int id)
{
	struct dns64_qstate* iq = (struct dns64_qstate*)qstate->minfo[id];
	if (iq && iq->state == DNS64_NEW_QUERY
            && qstate->qinfo.qtype == LDNS_RR_TYPE_PTR
            && qstate->qinfo.qname_len == 74
            && !strcmp((char*)&qstate->qinfo.qname[64], "\03ip6\04arpa"))
        /* Handle PTR queries for IPv6 addresses. */
        return handle_ipv6_ptr(qstate, id);

	if (qstate->env->cfg->dns64_synthall &&
	    iq && iq->state == DNS64_NEW_QUERY
	    && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA)
		return generate_type_A_query(qstate, id);

	if(dns64_always_synth_for_qname(qstate, id) &&
	    iq && iq->state == DNS64_NEW_QUERY
	    && !(qstate->query_flags & BIT_CD)
	    && qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) {
		verbose(VERB_ALGO, "dns64: ignore-aaaa and synthesize anyway");
		return generate_type_A_query(qstate, id);
	}

	/* We are finished when our sub-query is finished. */
	if (iq && iq->state == DNS64_SUBQUERY_FINISHED)
		return module_finished;

	/* Otherwise, pass request to next module. */
	verbose(VERB_ALGO, "dns64: pass to next module");
	return module_wait_module;
}


processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
	struct iter_env* ie, int id)
{
	int dnsseclame = 0;
	enum response_type type;

	iq->num_current_queries--;

	if(!inplace_cb_query_response_call(qstate->env, qstate, iq->response))
		log_err("unable to call query_response callback");

	if(iq->response == NULL) {
		/* Don't increment qname when QNAME minimisation is enabled */
		if(qstate->env->cfg->qname_minimisation) {
			iq->minimisation_state = SKIP_MINIMISE_STATE;
		}
		iq->timeout_count++;
		iq->chase_to_rd = 0;
		iq->dnssec_lame_query = 0;
		verbose(VERB_ALGO, "query response was timeout");
		return next_state(iq, QUERYTARGETS_STATE);
	}
	iq->timeout_count = 0;
	type = response_type_from_server(
		(int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd),
		iq->response, &iq->qinfo_out, iq->dp);
	iq->chase_to_rd = 0;
	if(type == RESPONSE_TYPE_REFERRAL && (iq->chase_flags&BIT_RD) &&
		!iq->auth_zone_response) {
		/* When forwarding (RD bit is set), we handle referrals
		 * differently. No queries should be sent elsewhere */
		type = RESPONSE_TYPE_ANSWER;
	}
	if(!qstate->env->cfg->disable_dnssec_lame_check && iq->dnssec_expected
                && !iq->dnssec_lame_query &&
		!(iq->chase_flags&BIT_RD)
		&& iq->sent_count < DNSSEC_LAME_DETECT_COUNT
		&& type != RESPONSE_TYPE_LAME
		&& type != RESPONSE_TYPE_REC_LAME
		&& type != RESPONSE_TYPE_THROWAWAY
		&& type != RESPONSE_TYPE_UNTYPED) {
		/* a possible answer, see if it is missing DNSSEC */
		/* but not when forwarding, so we dont mark fwder lame */
		if(!iter_msg_has_dnssec(iq->response)) {
			/* Mark this address as dnsseclame in this dp,
			 * because that will make serverselection disprefer
			 * it, but also, once it is the only final option,
			 * use dnssec-lame-bypass if it needs to query there.*/
			if(qstate->reply) {
				struct delegpt_addr* a = delegpt_find_addr(
					iq->dp, &qstate->reply->addr,
					qstate->reply->addrlen);
				if(a) a->dnsseclame = 1;
			}
			/* test the answer is from the zone we expected,
		 	 * otherwise, (due to parent,child on same server), we
		 	 * might mark the server,zone lame inappropriately */
			if(!iter_msg_from_zone(iq->response, iq->dp, type,
				iq->qchase.qclass))
				qstate->reply = NULL;
			type = RESPONSE_TYPE_LAME;
			dnsseclame = 1;
		}
	} else iq->dnssec_lame_query = 0;
	/* see if referral brings us close to the target */
	if(type == RESPONSE_TYPE_REFERRAL) {
		struct ub_packed_rrset_key* ns = find_NS(
			iq->response->rep, iq->response->rep->an_numrrsets,
			iq->response->rep->an_numrrsets
			+ iq->response->rep->ns_numrrsets);
		if(!ns) ns = find_NS(iq->response->rep, 0,
				iq->response->rep->an_numrrsets);
		if(!ns || !dname_strict_subdomain_c(ns->rk.dname, iq->dp->name)
			|| !dname_subdomain_c(iq->qchase.qname, ns->rk.dname)){
			verbose(VERB_ALGO, "bad referral, throwaway");
			type = RESPONSE_TYPE_THROWAWAY;
		} else
			iter_scrub_ds(iq->response, ns, iq->dp->name);
	} else iter_scrub_ds(iq->response, NULL, NULL);
	if(type == RESPONSE_TYPE_THROWAWAY &&
		FLAGS_GET_RCODE(iq->response->rep->flags) == LDNS_RCODE_YXDOMAIN) {
		/* YXDOMAIN is a permanent error, no need to retry */
		type = RESPONSE_TYPE_ANSWER;
	}
	if(type == RESPONSE_TYPE_CNAME && iq->response->rep->an_numrrsets >= 1
		&& ntohs(iq->response->rep->rrsets[0]->rk.type) == LDNS_RR_TYPE_DNAME) {
		uint8_t* sname = NULL;
		size_t snamelen = 0;
		get_cname_target(iq->response->rep->rrsets[0], &sname,
			&snamelen);
		if(snamelen && dname_subdomain_c(sname, iq->response->rep->rrsets[0]->rk.dname)) {
			/* DNAME to a subdomain loop; do not recurse */
			type = RESPONSE_TYPE_ANSWER;
		}
	} else if(type == RESPONSE_TYPE_CNAME &&
		iq->qchase.qtype == LDNS_RR_TYPE_CNAME &&
		iq->minimisation_state == MINIMISE_STATE &&
		query_dname_compare(iq->qchase.qname, iq->qinfo_out.qname) == 0) {
		/* The minimised query for full QTYPE and hidden QTYPE can be
		 * classified as CNAME response type, even when the original
		 * QTYPE=CNAME. This should be treated as answer response type.
		 */
		type = RESPONSE_TYPE_ANSWER;
	}

	/* handle each of the type cases */
	if(type == RESPONSE_TYPE_ANSWER) {
		/* ANSWER type responses terminate the query algorithm,
		 * so they sent on their */
		if(verbosity >= VERB_DETAIL) {
			verbose(VERB_DETAIL, "query response was %s",
				FLAGS_GET_RCODE(iq->response->rep->flags)
				==LDNS_RCODE_NXDOMAIN?"NXDOMAIN ANSWER":
				(iq->response->rep->an_numrrsets?"ANSWER":
				"nodata ANSWER"));
		}
		/* if qtype is DS, check we have the right level of answer,
		 * like grandchild answer but we need the middle, reject it */
		if(iq->qchase.qtype == LDNS_RR_TYPE_DS && !iq->dsns_point
			&& !(iq->chase_flags&BIT_RD)
			&& iter_ds_toolow(iq->response, iq->dp)
			&& iter_dp_cangodown(&iq->qchase, iq->dp)) {
			/* close down outstanding requests to be discarded */
			outbound_list_clear(&iq->outlist);
			iq->num_current_queries = 0;
			fptr_ok(fptr_whitelist_modenv_detach_subs(
				qstate->env->detach_subs));
			(*qstate->env->detach_subs)(qstate);
			iq->num_target_queries = 0;
			return processDSNSFind(qstate, iq, id);
		}
		if(!qstate->no_cache_store)
			iter_dns_store(qstate->env, &iq->response->qinfo,
				iq->response->rep,
				iq->qchase.qtype != iq->response->qinfo.qtype,
				qstate->prefetch_leeway,
				iq->dp&&iq->dp->has_parent_side_NS,
				qstate->region, qstate->query_flags,
				qstate->qstarttime);
		/* close down outstanding requests to be discarded */
		outbound_list_clear(&iq->outlist);
		iq->num_current_queries = 0;
		fptr_ok(fptr_whitelist_modenv_detach_subs(
			qstate->env->detach_subs));
		(*qstate->env->detach_subs)(qstate);
		iq->num_target_queries = 0;
		if(qstate->reply)
			sock_list_insert(&qstate->reply_origin,
				&qstate->reply->addr, qstate->reply->addrlen,
				qstate->region);
		if(iq->minimisation_state != DONOT_MINIMISE_STATE
			&& !(iq->chase_flags & BIT_RD)) {
			if(FLAGS_GET_RCODE(iq->response->rep->flags) !=
				LDNS_RCODE_NOERROR) {
				if(qstate->env->cfg->qname_minimisation_strict) {
					if(FLAGS_GET_RCODE(iq->response->rep->flags) ==
						LDNS_RCODE_NXDOMAIN) {
						iter_scrub_nxdomain(iq->response);
						return final_state(iq);
					}
					return error_response(qstate, id,
						LDNS_RCODE_SERVFAIL);
				}
				/* Best effort qname-minimisation.
				 * Stop minimising and send full query when
				 * RCODE is not NOERROR. */
				iq->minimisation_state = DONOT_MINIMISE_STATE;
			}
			if(FLAGS_GET_RCODE(iq->response->rep->flags) ==
				LDNS_RCODE_NXDOMAIN) {
				/* Stop resolving when NXDOMAIN is DNSSEC
				 * signed. Based on assumption that nameservers
				 * serving signed zones do not return NXDOMAIN
				 * for empty-non-terminals. */
				if(iq->dnssec_expected)
					return final_state(iq);
				/* Make subrequest to validate intermediate
				 * NXDOMAIN if harden-below-nxdomain is
				 * enabled. */
				if(qstate->env->cfg->harden_below_nxdomain &&
					qstate->env->need_to_validate) {
					struct module_qstate* subq = NULL;
					log_query_info(VERB_QUERY,
						"schedule NXDOMAIN validation:",
						&iq->response->qinfo);
					if(!generate_sub_request(
						iq->response->qinfo.qname,
						iq->response->qinfo.qname_len,
						iq->response->qinfo.qtype,
						iq->response->qinfo.qclass,
						qstate, id, iq,
						INIT_REQUEST_STATE,
						FINISHED_STATE, &subq, 1, 1))
						verbose(VERB_ALGO,
						"could not validate NXDOMAIN "
						"response");
				}
			}
			return next_state(iq, QUERYTARGETS_STATE);
		}
		return final_state(iq);
	} else if(type == RESPONSE_TYPE_REFERRAL) {
		/* REFERRAL type responses get a reset of the
		 * delegation point, and back to the QUERYTARGETS_STATE. */
		verbose(VERB_DETAIL, "query response was REFERRAL");

		/* if hardened, only store referral if we asked for it */
		if(!qstate->no_cache_store &&
		(!qstate->env->cfg->harden_referral_path ||
		    (  qstate->qinfo.qtype == LDNS_RR_TYPE_NS
			&& (qstate->query_flags&BIT_RD)
			&& !(qstate->query_flags&BIT_CD)
			   /* we know that all other NS rrsets are scrubbed
			    * away, thus on referral only one is left.
			    * see if that equals the query name... */
			&& ( /* auth section, but sometimes in answer section*/
			  reply_find_rrset_section_ns(iq->response->rep,
				iq->qchase.qname, iq->qchase.qname_len,
				LDNS_RR_TYPE_NS, iq->qchase.qclass)
			  || reply_find_rrset_section_an(iq->response->rep,
				iq->qchase.qname, iq->qchase.qname_len,
				LDNS_RR_TYPE_NS, iq->qchase.qclass)
			  )
		    ))) {
			/* Store the referral under the current query */
			/* no prefetch-leeway, since its not the answer */
			iter_dns_store(qstate->env, &iq->response->qinfo,
				iq->response->rep, 1, 0, 0, NULL, 0,
				qstate->qstarttime);
			if(iq->store_parent_NS)
				iter_store_parentside_NS(qstate->env,
					iq->response->rep);
			if(qstate->env->neg_cache)
				val_neg_addreferral(qstate->env->neg_cache,
					iq->response->rep, iq->dp->name);
		}
		/* store parent-side-in-zone-glue, if directly queried for */
		if(!qstate->no_cache_store && iq->query_for_pside_glue
			&& !iq->pside_glue) {
				iq->pside_glue = reply_find_rrset(iq->response->rep,
					iq->qchase.qname, iq->qchase.qname_len,
					iq->qchase.qtype, iq->qchase.qclass);
				if(iq->pside_glue) {
					log_rrset_key(VERB_ALGO, "found parent-side "
						"glue", iq->pside_glue);
					iter_store_parentside_rrset(qstate->env,
						iq->pside_glue);
				}
		}

		/* Reset the event state, setting the current delegation
		 * point to the referral. */
		iq->deleg_msg = iq->response;
		iq->dp = delegpt_from_message(iq->response, qstate->region);
		if (qstate->env->cfg->qname_minimisation)
			iq->minimisation_state = INIT_MINIMISE_STATE;
		if(!iq->dp) {
			errinf(qstate, "malloc failure, for delegation point");
			return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
		}
		if(!cache_fill_missing(qstate->env, iq->qchase.qclass,
			qstate->region, iq->dp)) {
			errinf(qstate, "malloc failure, copy extra info into delegation point");
			return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
		}
		if(iq->store_parent_NS && query_dname_compare(iq->dp->name,
			iq->store_parent_NS->name) == 0)
			iter_merge_retry_counts(iq->dp, iq->store_parent_NS,
				ie->outbound_msg_retry);
		delegpt_log(VERB_ALGO, iq->dp);
		/* Count this as a referral. */
		iq->referral_count++;
		iq->sent_count = 0;
		iq->dp_target_count = 0;
		/* see if the next dp is a trust anchor, or a DS was sent
		 * along, indicating dnssec is expected for next zone */
		iq->dnssec_expected = iter_indicates_dnssec(qstate->env,
			iq->dp, iq->response, iq->qchase.qclass);
		/* if dnssec, validating then also fetch the key for the DS */
		if(iq->dnssec_expected && qstate->env->cfg->prefetch_key &&
			!(qstate->query_flags&BIT_CD))
			generate_dnskey_prefetch(qstate, iq, id);

		/* spawn off NS and addr to auth servers for the NS we just
		 * got in the referral. This gets authoritative answer
		 * (answer section trust level) rrset.
		 * right after, we detach the subs, answer goes to cache. */
		if(qstate->env->cfg->harden_referral_path)
			generate_ns_check(qstate, iq, id);

		/* stop current outstanding queries.
		 * FIXME: should the outstanding queries be waited for and
		 * handled? Say by a subquery that inherits the outbound_entry.
		 */
		outbound_list_clear(&iq->outlist);
		iq->num_current_queries = 0;
		fptr_ok(fptr_whitelist_modenv_detach_subs(
			qstate->env->detach_subs));
		(*qstate->env->detach_subs)(qstate);
		iq->num_target_queries = 0;
		iq->response = NULL;
		iq->fail_reply = NULL;
		verbose(VERB_ALGO, "cleared outbound list for next round");
		return next_state(iq, QUERYTARGETS_STATE);
	} else if(type == RESPONSE_TYPE_CNAME) {
		uint8_t* sname = NULL;
		size_t snamelen = 0;
		/* CNAME type responses get a query restart (i.e., get a
		 * reset of the query state and go back to INIT_REQUEST_STATE).
		 */
		verbose(VERB_DETAIL, "query response was CNAME");
		if(verbosity >= VERB_ALGO)
			log_dns_msg("cname msg", &iq->response->qinfo,
				iq->response->rep);
		/* if qtype is DS, check we have the right level of answer,
		 * like grandchild answer but we need the middle, reject it */
		if(iq->qchase.qtype == LDNS_RR_TYPE_DS && !iq->dsns_point
			&& !(iq->chase_flags&BIT_RD)
			&& iter_ds_toolow(iq->response, iq->dp)
			&& iter_dp_cangodown(&iq->qchase, iq->dp)) {
			outbound_list_clear(&iq->outlist);
			iq->num_current_queries = 0;
			fptr_ok(fptr_whitelist_modenv_detach_subs(
				qstate->env->detach_subs));
			(*qstate->env->detach_subs)(qstate);
			iq->num_target_queries = 0;
			return processDSNSFind(qstate, iq, id);
		}
		/* Process the CNAME response. */
		if(!handle_cname_response(qstate, iq, iq->response,
			&sname, &snamelen)) {
			errinf(qstate, "malloc failure, CNAME info");
			return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
		}
		/* cache the CNAME response under the current query */
		/* NOTE : set referral=1, so that rrsets get stored but not
		 * the partial query answer (CNAME only). */
		/* prefetchleeway applied because this updates answer parts */
		if(!qstate->no_cache_store)
			iter_dns_store(qstate->env, &iq->response->qinfo,
				iq->response->rep, 1, qstate->prefetch_leeway,
				iq->dp&&iq->dp->has_parent_side_NS, NULL,
				qstate->query_flags, qstate->qstarttime);
		/* set the current request's qname to the new value. */
		iq->qchase.qname = sname;
		iq->qchase.qname_len = snamelen;
		if(qstate->env->auth_zones) {
			/* apply rpz qname triggers after cname */
			struct dns_msg* forged_response =
				rpz_callback_from_iterator_cname(qstate, iq);
			while(forged_response && reply_find_rrset_section_an(
				forged_response->rep, iq->qchase.qname,
				iq->qchase.qname_len, LDNS_RR_TYPE_CNAME,
				iq->qchase.qclass)) {
				/* another cname to follow */
				if(!handle_cname_response(qstate, iq, forged_response,
					&sname, &snamelen)) {
					errinf(qstate, "malloc failure, CNAME info");
					return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
				}
				iq->qchase.qname = sname;
				iq->qchase.qname_len = snamelen;
				forged_response =
					rpz_callback_from_iterator_cname(qstate, iq);
			}
			if(forged_response != NULL) {
				qstate->ext_state[id] = module_finished;
				qstate->return_rcode = LDNS_RCODE_NOERROR;
				qstate->return_msg = forged_response;
				iq->response = forged_response;
				next_state(iq, FINISHED_STATE);
				if(!iter_prepend(iq, qstate->return_msg, qstate->region)) {
					log_err("rpz: after cname, prepend rrsets: out of memory");
					return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
				}
				qstate->return_msg->qinfo = qstate->qinfo;
				return 0;
			}
		}
		/* Clear the query state, since this is a query restart. */
		iq->deleg_msg = NULL;
		iq->dp = NULL;
		iq->dsns_point = NULL;
		iq->auth_zone_response = 0;
		iq->sent_count = 0;
		iq->dp_target_count = 0;
		if(iq->minimisation_state != MINIMISE_STATE)
			/* Only count as query restart when it is not an extra
			 * query as result of qname minimisation. */
			iq->query_restart_count++;
		if(qstate->env->cfg->qname_minimisation)
			iq->minimisation_state = INIT_MINIMISE_STATE;

		/* stop current outstanding queries.
		 * FIXME: should the outstanding queries be waited for and
		 * handled? Say by a subquery that inherits the outbound_entry.
		 */
		outbound_list_clear(&iq->outlist);
		iq->num_current_queries = 0;
		fptr_ok(fptr_whitelist_modenv_detach_subs(
			qstate->env->detach_subs));
		(*qstate->env->detach_subs)(qstate);
		iq->num_target_queries = 0;
		if(qstate->reply)
			sock_list_insert(&qstate->reply_origin,
				&qstate->reply->addr, qstate->reply->addrlen,
				qstate->region);
		verbose(VERB_ALGO, "cleared outbound list for query restart");
		/* go to INIT_REQUEST_STATE for new qname. */
		return next_state(iq, INIT_REQUEST_STATE);
	} else if(type == RESPONSE_TYPE_LAME) {
		/* Cache the LAMEness. */
		verbose(VERB_DETAIL, "query response was %sLAME",
			dnsseclame?"DNSSEC ":"");
		if(!dname_subdomain_c(iq->qchase.qname, iq->dp->name)) {
			log_err("mark lame: mismatch in qname and dpname");
			/* throwaway this reply below */
		} else if(qstate->reply) {
			/* need addr for lameness cache, but we may have
			 * gotten this from cache, so test to be sure */
			if(!infra_set_lame(qstate->env->infra_cache,
				&qstate->reply->addr, qstate->reply->addrlen,
				iq->dp->name, iq->dp->namelen,
				*qstate->env->now, dnsseclame, 0,
				iq->qchase.qtype))
				log_err("mark host lame: out of memory");
		}
	} else if(type == RESPONSE_TYPE_REC_LAME) {
		/* Cache the LAMEness. */
		verbose(VERB_DETAIL, "query response REC_LAME: "
			"recursive but not authoritative server");
		if(!dname_subdomain_c(iq->qchase.qname, iq->dp->name)) {
			log_err("mark rec_lame: mismatch in qname and dpname");
			/* throwaway this reply below */
		} else if(qstate->reply) {
			/* need addr for lameness cache, but we may have
			 * gotten this from cache, so test to be sure */
			verbose(VERB_DETAIL, "mark as REC_LAME");
			if(!infra_set_lame(qstate->env->infra_cache,
				&qstate->reply->addr, qstate->reply->addrlen,
				iq->dp->name, iq->dp->namelen,
				*qstate->env->now, 0, 1, iq->qchase.qtype))
				log_err("mark host lame: out of memory");
		}
	} else if(type == RESPONSE_TYPE_THROWAWAY) {
		/* LAME and THROWAWAY responses are handled the same way.
		 * In this case, the event is just sent directly back to
		 * the QUERYTARGETS_STATE without resetting anything,
		 * because, clearly, the next target must be tried. */
		verbose(VERB_DETAIL, "query response was THROWAWAY");
	} else {
		log_warn("A query response came back with an unknown type: %d",
			(int)type);
	}

	/* LAME, THROWAWAY and "unknown" all end up here.
	 * Recycle to the QUERYTARGETS state to hopefully try a
	 * different target. */
	if (qstate->env->cfg->qname_minimisation &&
		!qstate->env->cfg->qname_minimisation_strict)
		iq->minimisation_state = DONOT_MINIMISE_STATE;
	if(iq->auth_zone_response) {
		/* can we fallback? */
		iq->auth_zone_response = 0;
		if(!auth_zones_can_fallback(qstate->env->auth_zones,
			iq->dp->name, iq->dp->namelen, qstate->qinfo.qclass)) {
			verbose(VERB_ALGO, "auth zone response bad, and no"
				" fallback possible, servfail");
			errinf_dname(qstate, "response is bad, no fallback, "
				"for auth zone", iq->dp->name);
			return error_response(qstate, id, LDNS_RCODE_SERVFAIL);
		}
		verbose(VERB_ALGO, "auth zone response was bad, "
			"fallback enabled");
		iq->auth_zone_avoid = 1;
		if(iq->dp->auth_dp) {
			/* we are using a dp for the auth zone, with no
			 * nameservers, get one first */
			iq->dp = NULL;
			return next_state(iq, INIT_REQUEST_STATE);
		}
	}
	return next_state(iq, QUERYTARGETS_STATE);
}


tomsg(struct module_env* env, struct query_info* q, struct reply_info* r,
	struct regional* region, time_t now, int allow_expired,
	struct regional* scratch)
{
	struct dns_msg* msg;
	size_t i;
	int is_expired = 0;
	time_t now_control = now;
	if(now > r->ttl) {
		/* Check if we are allowed to serve expired */
		if(allow_expired) {
			if(env->cfg->serve_expired_ttl &&
				r->serve_expired_ttl < now) {
				return NULL;
			}
		} else {
			return NULL;
		}
		/* Change the current time so we can pass the below TTL checks when
		 * serving expired data. */
		now_control = r->ttl - env->cfg->serve_expired_reply_ttl;
		is_expired = 1;
	}

	msg = gen_dns_msg(region, q, r->rrset_count);
	if(!msg) return NULL;
	msg->rep->flags = r->flags;
	msg->rep->qdcount = r->qdcount;
	msg->rep->ttl = is_expired
		?SERVE_EXPIRED_REPLY_TTL
		:r->ttl - now;
	if(r->prefetch_ttl > now)
		msg->rep->prefetch_ttl = r->prefetch_ttl - now;
	else
		msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl);
	msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL;
	msg->rep->security = r->security;
	msg->rep->an_numrrsets = r->an_numrrsets;
	msg->rep->ns_numrrsets = r->ns_numrrsets;
	msg->rep->ar_numrrsets = r->ar_numrrsets;
	msg->rep->rrset_count = r->rrset_count;
	msg->rep->authoritative = r->authoritative;
	msg->rep->reason_bogus = r->reason_bogus;
	if(!rrset_array_lock(r->ref, r->rrset_count, now_control)) {
		return NULL;
	}
	if(r->an_numrrsets > 0 && (r->rrsets[0]->rk.type == htons(
		LDNS_RR_TYPE_CNAME) || r->rrsets[0]->rk.type == htons(
		LDNS_RR_TYPE_DNAME)) && !reply_check_cname_chain(q, r)) {
		/* cname chain is now invalid, reconstruct msg */
		rrset_array_unlock(r->ref, r->rrset_count);
		return NULL;
	}
	if(r->security == sec_status_secure && !reply_all_rrsets_secure(r)) {
		/* message rrsets have changed status, revalidate */
		rrset_array_unlock(r->ref, r->rrset_count);
		return NULL;
	}
	for(i=0; i<msg->rep->rrset_count; i++) {
		msg->rep->rrsets[i] = packed_rrset_copy_region(r->rrsets[i],
			region, now);
		if(!msg->rep->rrsets[i]) {
			rrset_array_unlock(r->ref, r->rrset_count);
			return NULL;
		}
	}
	if(env)
		rrset_array_unlock_touch(env->rrset_cache, scratch, r->ref,
		r->rrset_count);
	else
		rrset_array_unlock(r->ref, r->rrset_count);
	return msg;
}


mesh_state_make_unique(struct mesh_state* mstate)
{
	mstate->unique = mstate;
}


int mesh_make_new_space(struct mesh_area* mesh, sldns_buffer* qbuf)
{
	struct mesh_state* m = mesh->jostle_first;
	/* free space is available */
	if(mesh->num_reply_states < mesh->max_reply_states)
		return 1;
	/* try to kick out a jostle-list item */
	if(m && m->reply_list && m->list_select == mesh_jostle_list) {
		/* how old is it? */
		struct timeval age;
		timeval_subtract(&age, mesh->env->now_tv,
			&m->reply_list->start_time);
		if(timeval_smaller(&mesh->jostle_max, &age)) {
			/* its a goner */
			log_nametypeclass(VERB_ALGO, "query jostled out to "
				"make space for a new one",
				m->s.qinfo.qname, m->s.qinfo.qtype,
				m->s.qinfo.qclass);
			/* backup the query */
			if(qbuf) sldns_buffer_copy(mesh->qbuf_bak, qbuf);
			/* notify supers */
			if(m->super_set.count > 0) {
				verbose(VERB_ALGO, "notify supers of failure");
				m->s.return_msg = NULL;
				m->s.return_rcode = LDNS_RCODE_SERVFAIL;
				mesh_walk_supers(mesh, m);
			}
			mesh->stats_jostled ++;
			mesh_state_delete(&m->s);
			/* restore the query - note that the qinfo ptr to
			 * the querybuffer is then correct again. */
			if(qbuf) sldns_buffer_copy(qbuf, mesh->qbuf_bak);
			return 1;
		}
	}
	/* no space for new item */
	return 0;
}


val_state_to_string(enum val_state state)
{
	switch(state) {
		case VAL_INIT_STATE: return "VAL_INIT_STATE";
		case VAL_FINDKEY_STATE: return "VAL_FINDKEY_STATE";
		case VAL_VALIDATE_STATE: return "VAL_VALIDATE_STATE";
		case VAL_FINISHED_STATE: return "VAL_FINISHED_STATE";
	}
	return "UNKNOWN VALIDATOR STATE";
}


target_count_increase_nx(struct iter_qstate* iq, int num)
{
	target_count_create(iq);
	if(iq->target_count)
		iq->target_count[TARGET_COUNT_NX] += num;
}


mesh_state_compare(const void* ap, const void* bp)
{
	struct mesh_state* a = (struct mesh_state*)ap;
	struct mesh_state* b = (struct mesh_state*)bp;
	int cmp;

	if(a->unique < b->unique)
		return -1;
	if(a->unique > b->unique)
		return 1;

	if(a->s.is_priming && !b->s.is_priming)
		return -1;
	if(!a->s.is_priming && b->s.is_priming)
		return 1;

	if(a->s.is_valrec && !b->s.is_valrec)
		return -1;
	if(!a->s.is_valrec && b->s.is_valrec)
		return 1;

	if((a->s.query_flags&BIT_RD) && !(b->s.query_flags&BIT_RD))
		return -1;
	if(!(a->s.query_flags&BIT_RD) && (b->s.query_flags&BIT_RD))
		return 1;

	if((a->s.query_flags&BIT_CD) && !(b->s.query_flags&BIT_CD))
		return -1;
	if(!(a->s.query_flags&BIT_CD) && (b->s.query_flags&BIT_CD))
		return 1;

	cmp = query_info_compare(&a->s.qinfo, &b->s.qinfo);
	if(cmp != 0)
		return cmp;
	return client_info_compare(a->s.client_info, b->s.client_info);
}


dump_rrset_line(RES* ssl, struct ub_packed_rrset_key* k, time_t now, size_t i)
{
	char s[65535];
	if(!packed_rr_to_string(k, i, now, s, sizeof(s))) {
		return ssl_printf(ssl, "BADRR\n");
	}
	return ssl_printf(ssl, "%s", s);
}


timeval_subtract(struct timeval* d, const struct timeval* end, const struct timeval* start)
{
#ifndef S_SPLINT_S
	time_t end_usec = end->tv_usec;
	d->tv_sec = end->tv_sec - start->tv_sec;
	if(end_usec < start->tv_usec) {
		end_usec += 1000000;
		d->tv_sec--;
	}
	d->tv_usec = end_usec - start->tv_usec;
#endif
}


iter_clear(struct module_qstate* qstate, int id)
{
	struct iter_qstate* iq;
	if(!qstate)
		return;
	iq = (struct iter_qstate*)qstate->minfo[id];
	if(iq) {
		outbound_list_clear(&iq->outlist);
		if(iq->target_count && --iq->target_count[TARGET_COUNT_REF] == 0) {
			free(iq->target_count);
			if(*iq->nxns_dp) free(*iq->nxns_dp);
			free(iq->nxns_dp);
		}
		iq->num_current_queries = 0;
	}
	qstate->minfo[id] = NULL;
}


load_rr(RES* ssl, sldns_buffer* buf, struct regional* region,
	struct ub_packed_rrset_key* rk, struct packed_rrset_data* d,
	unsigned int i, int is_rrsig, int* go_on, time_t now)
{
	uint8_t rr[LDNS_RR_BUF_SIZE];
	size_t rr_len = sizeof(rr), dname_len = 0;
	int status;

	/* read the line */
	if(!ssl_read_buf(ssl, buf))
		return 0;
	if(strncmp((char*)sldns_buffer_begin(buf), "BADRR\n", 6) == 0) {
		*go_on = 0;
		return 1;
	}
	status = sldns_str2wire_rr_buf((char*)sldns_buffer_begin(buf), rr,
		&rr_len, &dname_len, 3600, NULL, 0, NULL, 0);
	if(status != 0) {
		log_warn("error cannot parse rr: %s: %s",
			sldns_get_errorstr_parse(status),
			(char*)sldns_buffer_begin(buf));
		return 0;
	}
	if(is_rrsig && sldns_wirerr_get_type(rr, rr_len, dname_len)
		!= LDNS_RR_TYPE_RRSIG) {
		log_warn("error expected rrsig but got %s",
			(char*)sldns_buffer_begin(buf));
		return 0;
	}

	/* convert ldns rr into packed_rr */
	d->rr_ttl[i] = (time_t)sldns_wirerr_get_ttl(rr, rr_len, dname_len) + now;
	sldns_buffer_clear(buf);
	d->rr_len[i] = sldns_wirerr_get_rdatalen(rr, rr_len, dname_len)+2;
	d->rr_data[i] = (uint8_t*)regional_alloc_init(region,
		sldns_wirerr_get_rdatawl(rr, rr_len, dname_len), d->rr_len[i]);
	if(!d->rr_data[i]) {
		log_warn("error out of memory");
		return 0;
	}

	/* if first entry, fill the key structure */
	if(i==0) {
		rk->rk.type = htons(sldns_wirerr_get_type(rr, rr_len, dname_len));
		rk->rk.rrset_class = htons(sldns_wirerr_get_class(rr, rr_len, dname_len));
		rk->rk.dname_len = dname_len;
		rk->rk.dname = regional_alloc_init(region, rr, dname_len);
		if(!rk->rk.dname) {
			log_warn("error out of memory");
			return 0;
		}
	}

	return 1;
}


worker_err_ratelimit(struct worker* worker, int err)
{
	if(worker->err_limit_time == *worker->env.now) {
		/* see if limit is exceeded for this second */
		if(worker->err_limit_count++ > ERROR_RATELIMIT)
			return -1;
	} else {
		/* new second, new limits */
		worker->err_limit_time = *worker->env.now;
		worker->err_limit_count = 1;
	}
	return err;
}


worker_mem_report(struct worker* ATTR_UNUSED(worker),
	struct serviced_query* ATTR_UNUSED(cur_serv))
{
#ifdef UNBOUND_ALLOC_STATS
	/* measure memory leakage */
	extern size_t unbound_mem_alloc, unbound_mem_freed;
	/* debug func in validator module */
	size_t total, front, back, mesh, msg, rrset, infra, ac, superac;
	size_t me, iter, val, anch;
	int i;
#ifdef CLIENT_SUBNET
	size_t subnet = 0;
#endif /* CLIENT_SUBNET */
	if(verbosity < VERB_ALGO)
		return;
	front = listen_get_mem(worker->front);
	back = outnet_get_mem(worker->back);
	msg = slabhash_get_mem(worker->env.msg_cache);
	rrset = slabhash_get_mem(&worker->env.rrset_cache->table);
	infra = infra_get_mem(worker->env.infra_cache);
	mesh = mesh_get_mem(worker->env.mesh);
	ac = alloc_get_mem(&worker->alloc);
	superac = alloc_get_mem(&worker->daemon->superalloc);
	anch = anchors_get_mem(worker->env.anchors);
	iter = 0;
	val = 0;
	for(i=0; i<worker->env.mesh->mods.num; i++) {
		fptr_ok(fptr_whitelist_mod_get_mem(worker->env.mesh->
			mods.mod[i]->get_mem));
		if(strcmp(worker->env.mesh->mods.mod[i]->name, "validator")==0)
			val += (*worker->env.mesh->mods.mod[i]->get_mem)
				(&worker->env, i);
#ifdef CLIENT_SUBNET
		else if(strcmp(worker->env.mesh->mods.mod[i]->name,
			"subnetcache")==0)
			subnet += (*worker->env.mesh->mods.mod[i]->get_mem)
				(&worker->env, i);
#endif /* CLIENT_SUBNET */
		else	iter += (*worker->env.mesh->mods.mod[i]->get_mem)
				(&worker->env, i);
	}
	me = sizeof(*worker) + sizeof(*worker->base) + sizeof(*worker->comsig)
		+ comm_point_get_mem(worker->cmd_com)
		+ sizeof(worker->rndstate)
		+ regional_get_mem(worker->scratchpad)
		+ sizeof(*worker->env.scratch_buffer)
		+ sldns_buffer_capacity(worker->env.scratch_buffer)
		+ forwards_get_mem(worker->env.fwds)
		+ hints_get_mem(worker->env.hints);
	if(worker->thread_num == 0)
		me += acl_list_get_mem(worker->daemon->acl);
	if(cur_serv) {
		me += serviced_get_mem(cur_serv);
	}
	total = front+back+mesh+msg+rrset+infra+iter+val+ac+superac+me;
#ifdef CLIENT_SUBNET
	total += subnet;
	log_info("Memory conditions: %u front=%u back=%u mesh=%u msg=%u "
		"rrset=%u infra=%u iter=%u val=%u subnet=%u anchors=%u "
		"alloccache=%u globalalloccache=%u me=%u",
		(unsigned)total, (unsigned)front, (unsigned)back,
		(unsigned)mesh, (unsigned)msg, (unsigned)rrset, (unsigned)infra,
		(unsigned)iter, (unsigned)val,
		(unsigned)subnet, (unsigned)anch, (unsigned)ac,
		(unsigned)superac, (unsigned)me);
#else /* no CLIENT_SUBNET */
	log_info("Memory conditions: %u front=%u back=%u mesh=%u msg=%u "
		"rrset=%u infra=%u iter=%u val=%u anchors=%u "
		"alloccache=%u globalalloccache=%u me=%u",
		(unsigned)total, (unsigned)front, (unsigned)back,
		(unsigned)mesh, (unsigned)msg, (unsigned)rrset,
		(unsigned)infra, (unsigned)iter, (unsigned)val, (unsigned)anch,
		(unsigned)ac, (unsigned)superac, (unsigned)me);
#endif /* CLIENT_SUBNET */
	log_info("Total heap memory estimate: %u  total-alloc: %u  "
		"total-free: %u", (unsigned)total,
		(unsigned)unbound_mem_alloc, (unsigned)unbound_mem_freed);
#else /* no UNBOUND_ALLOC_STATS */
	size_t val = 0;
#ifdef CLIENT_SUBNET
	size_t subnet = 0;
#endif /* CLIENT_SUBNET */
	int i;
	if(verbosity < VERB_QUERY)
		return;
	for(i=0; i<worker->env.mesh->mods.num; i++) {
		fptr_ok(fptr_whitelist_mod_get_mem(worker->env.mesh->
			mods.mod[i]->get_mem));
		if(strcmp(worker->env.mesh->mods.mod[i]->name, "validator")==0)
			val += (*worker->env.mesh->mods.mod[i]->get_mem)
				(&worker->env, i);
#ifdef CLIENT_SUBNET
		else if(strcmp(worker->env.mesh->mods.mod[i]->name,
			"subnetcache")==0)
			subnet += (*worker->env.mesh->mods.mod[i]->get_mem)
				(&worker->env, i);
#endif /* CLIENT_SUBNET */
	}
#ifdef CLIENT_SUBNET
	verbose(VERB_QUERY, "cache memory msg=%u rrset=%u infra=%u val=%u "
		"subnet=%u",
		(unsigned)slabhash_get_mem(worker->env.msg_cache),
		(unsigned)slabhash_get_mem(&worker->env.rrset_cache->table),
		(unsigned)infra_get_mem(worker->env.infra_cache),
		(unsigned)val, (unsigned)subnet);
#else /* no CLIENT_SUBNET */
	verbose(VERB_QUERY, "cache memory msg=%u rrset=%u infra=%u val=%u",
		(unsigned)slabhash_get_mem(worker->env.msg_cache),
		(unsigned)slabhash_get_mem(&worker->env.rrset_cache->table),
		(unsigned)infra_get_mem(worker->env.infra_cache),
		(unsigned)val);
#endif /* CLIENT_SUBNET */
#endif /* UNBOUND_ALLOC_STATS */
}


answer_norec_from_cache(struct worker* worker, struct query_info* qinfo,
	uint16_t id, uint16_t flags, struct comm_reply* repinfo,
	struct edns_data* edns)
{
	/* for a nonrecursive query return either:
	 * 	o an error (servfail; we try to avoid this)
	 * 	o a delegation (closest we have; this routine tries that)
	 * 	o the answer (checked by answer_from_cache)
	 *
	 * So, grab a delegation from the rrset cache.
	 * Then check if it needs validation, if so, this routine fails,
	 * so that iterator can prime and validator can verify rrsets.
	 */
	uint16_t udpsize = edns->udp_size;
	int secure = 0;
	time_t timenow = *worker->env.now;
	int must_validate = (!(flags&BIT_CD) || worker->env.cfg->ignore_cd)
		&& worker->env.need_to_validate;
	struct dns_msg *msg = NULL;
	struct delegpt *dp;

	dp = dns_cache_find_delegation(&worker->env, qinfo->qname,
		qinfo->qname_len, qinfo->qtype, qinfo->qclass,
		worker->scratchpad, &msg, timenow, 0, NULL, 0);
	if(!dp) { /* no delegation, need to reprime */
		return 0;
	}
	/* In case we have a local alias, copy it into the delegation message.
	 * Shallow copy should be fine, as we'll be done with msg in this
	 * function. */
	msg->qinfo.local_alias = qinfo->local_alias;
	if(must_validate) {
		switch(check_delegation_secure(msg->rep)) {
		case sec_status_unchecked:
			/* some rrsets have not been verified yet, go and
			 * let validator do that */
			return 0;
		case sec_status_bogus:
		case sec_status_secure_sentinel_fail:
			/* some rrsets are bogus, reply servfail */
			edns->edns_version = EDNS_ADVERTISED_VERSION;
			edns->udp_size = EDNS_ADVERTISED_SIZE;
			edns->ext_rcode = 0;
			edns->bits &= EDNS_DO;
			if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL,
				msg->rep, LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
				worker->env.now_tv))
					return 0;
			/* TODO store the reason for the bogus reply in cache
			 * and implement in here instead of the hardcoded EDE */
			if (worker->env.cfg->ede) {
				EDNS_OPT_LIST_APPEND_EDE(&edns->opt_list_out,
					worker->scratchpad, LDNS_EDE_DNSSEC_BOGUS, "");
			}
			error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
				&msg->qinfo, id, flags, edns);
			if(worker->stats.extended) {
				worker->stats.ans_bogus++;
				worker->stats.ans_rcode[LDNS_RCODE_SERVFAIL]++;
			}
			return 1;
		case sec_status_secure:
			/* all rrsets are secure */
			/* remove non-secure rrsets from the add. section*/
			if(worker->env.cfg->val_clean_additional)
				deleg_remove_nonsecure_additional(msg->rep);
			secure = 1;
			break;
		case sec_status_indeterminate:
		case sec_status_insecure:
		default:
			/* not secure */
			secure = 0;
			break;
		}
	}
	/* return this delegation from the cache */
	edns->edns_version = EDNS_ADVERTISED_VERSION;
	edns->udp_size = EDNS_ADVERTISED_SIZE;
	edns->ext_rcode = 0;
	edns->bits &= EDNS_DO;
	if(!inplace_cb_reply_cache_call(&worker->env, qinfo, NULL, msg->rep,
		(int)(flags&LDNS_RCODE_MASK), edns, repinfo, worker->scratchpad,
		worker->env.now_tv))
			return 0;
	msg->rep->flags |= BIT_QR|BIT_RA;
	if(!reply_info_answer_encode(&msg->qinfo, msg->rep, id, flags,
		repinfo->c->buffer, 0, 1, worker->scratchpad,
		udpsize, edns, (int)(edns->bits & EDNS_DO), secure)) {
		if(!inplace_cb_reply_servfail_call(&worker->env, qinfo, NULL, NULL,
			LDNS_RCODE_SERVFAIL, edns, repinfo, worker->scratchpad,
			worker->env.now_tv))
				edns->opt_list_inplace_cb_out = NULL;
		error_encode(repinfo->c->buffer, LDNS_RCODE_SERVFAIL,
			&msg->qinfo, id, flags, edns);
	}
	if(worker->stats.extended) {
		if(secure) worker->stats.ans_secure++;
		server_stats_insrcode(&worker->stats, repinfo->c->buffer);
	}
	return 1;
}


ipsecmod_new(struct module_qstate* qstate, int id)
{
	struct ipsecmod_qstate* iq = (struct ipsecmod_qstate*)regional_alloc(
		qstate->region, sizeof(struct ipsecmod_qstate));
	qstate->minfo[id] = iq;
	if(!iq)
		return 0;
	/* Initialise it. */
	memset(iq, 0, sizeof(*iq));
	iq->enabled = qstate->env->cfg->ipsecmod_enabled;
	iq->is_whitelisted = ipsecmod_domain_is_whitelisted(
		(struct ipsecmod_env*)qstate->env->modinfo[id], qstate->qinfo.qname,
		qstate->qinfo.qname_len, qstate->qinfo.qclass);
	return 1;
}