/* This file is part of gacopyz.
   Copyright (C) 2006-2025 Sergey Poznyakoff

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include <gacopyz_priv.h>
 
int
gacopyz_setpriv (SMFICTX *ctx, void *data)
{
	if (!ctx)
		return MI_FAILURE;
	ctx->privdata = data;
	return MI_SUCCESS;
}

void *
gacopyz_getpriv (SMFICTX *ctx)
{
	if (!ctx)
		return NULL;
	return ctx->privdata;
}

void *
gacopyz_getclosure(SMFICTX *ctx)
{
	if (!ctx)
		return NULL;
	return ctx->closure;
}

int
gacopyz_server_sockname(SMFICTX *ctx,
			milter_sockaddr_t *name, socklen_t *namelen)
{
	if (!ctx)
		return EINVAL;
	return getsockname(ctx->sd, (struct sockaddr*) name, namelen);
}

int
gacopyz_client_sockname(SMFICTX *ctx,
			milter_sockaddr_t *name, socklen_t *namelen)
{
	if (!ctx)
		return EINVAL;
	*name = ctx->addr;
	*namelen = ctx->addrlen;
	return 0;
}

const char *
gacopyz_getsymval(SMFICTX *ctx, const char *name)
{
	int i;
	int len;
	
        if (!ctx || !name)
		return NULL;
	
	if (name[0] == '{')
		name++;
	len = strlen(name);
	if (len == 0)
		return NULL;
	if (name[len-1] == '}')
		len--;
	
	for (i = gacopyz_stage_max - 1; i >= 0; i--) {
		if (ctx->macros[i].argv) {
			char **p;

			for (p = ctx->macros[i].argv; *p; p += 2) {
				if (strlen(*p) == len
				    && memcmp(*p, name, len) == 0)
					return *++p;
			}
		}
	}
	return NULL;
}

static int
enhanced_code_p(const char *p)
{
	int count = 0;

	if (!((*p == '2' || *p == '4' || *p == '5') && p[1] == '.'))
		return 0;
	
	while (*p) {
		int i;
		for (i = 0; isascii(*p) && isdigit(*p); i++, p++) {
			if (i == 3)
				return 0;
		}
		if (*p) {
			if (*p++ != '.')
				return 0;
		} 
		count++;
	}
	return count == 3;
}

static int
format_message(char **buf, const char *rcode, const char *xcode,
	       const char *message)
{
	size_t xcodelen = 0, pfxlen, len, numlines, i;
	const char *p;
	char *q;
	
	pfxlen = strlen(rcode) + 1;
        if (xcode != NULL) 
		pfxlen += (xcodelen = strlen(xcode)) + 1;

	numlines = 0;
	len = 0;
	for (p = message; *p; p++) {
		if (*p == '\r') {
			if (p[1] == '\n')
				p++;
			numlines++;
		} else if (*p == '\n')
			numlines++;
		else if (*p == '%')
			len += 2;
		else
			len++;
	}

	if (p == message || p[-1] != '\n')
		numlines++;
	
	len += numlines * (pfxlen + 2);

	*buf = malloc(len + 1);
	if (!*buf)
		return MI_FAILURE;

	q = *buf;
	p = message;
	for (i = 1; i <= numlines; i++) {
		strcpy(q, rcode);
		q += 3;
		if (xcode) {
			*q++ = (i < numlines) ? '-' : ' ';
			memcpy(q, xcode, xcodelen);
			q += xcodelen;
			*q++ = ' ';
		} else
			*q++ = (i < numlines) ? '-' : ' ';
		while (*p && !(*p == '\r' || *p == '\n')) {
			if ((*q++ = *p++) == '%')
				*q++ = '%';
		}
		if (numlines > 1 && i != numlines) {
			*q++ = '\r';
			*q++ = '\n';
		}
		if (*p == '\r')
			p++;
		if (*p == '\n')
			p++;
	}
	*q = 0;
	return MI_SUCCESS;
}

#define MLBUF_INIT_ALLOC 512
#define MLBUF_INCR_ALLOC 128
int
_gacopyz_setmlreply_va(SMFICTX *ctx, size_t max, const char *rcode,
			const char *xcode, va_list ap)
{
	size_t bufsize = MLBUF_INIT_ALLOC, i, lasti, numlines;
	char *buf, *p;
	size_t xcodelen, pfxlen;
	
	if (rcode == NULL || ctx == NULL)
		return MI_FAILURE;

        if ((rcode[0] != '4' && rcode[0] != '5') ||
	    !isascii(rcode[1]) || !isdigit(rcode[1]) ||
	    !isascii(rcode[2]) || !isdigit(rcode[2]))
		return MI_FAILURE;

	pfxlen = strlen(rcode);

        if (strlen(rcode) != 3)
		return MI_FAILURE;

        if (xcode != NULL) {
		if (!enhanced_code_p(xcode))
			return MI_FAILURE;
	} else {
		if (rcode[0] == '4')
			xcode = "4.0.0";
		else
			xcode = "5.0.0";
	}
        if (xcode != NULL) 
		pfxlen += (xcodelen = strlen(xcode)) + 1;
	
	free(ctx->reply);
	ctx->reply = NULL;
	buf = malloc(bufsize);
	if (!buf)
		return MI_FAILURE;
	i = 0;
	lasti = 0;
	numlines = 0;
	for (p = va_arg(ap, char*);
	     p && (max == 0 || numlines < max); numlines++) {
		size_t s = strlen(p) + pfxlen + 2;
		if (strpbrk(p, "\r\n") != NULL)
			break;
		if (i + s > bufsize) {
			char *newbuf;
			size_t delta = (i + s - bufsize +
					MLBUF_INCR_ALLOC) /
				        MLBUF_INCR_ALLOC;
			bufsize += delta * MLBUF_INCR_ALLOC;
			newbuf = realloc(buf, bufsize);
			if (!newbuf) {
				free(buf);
				return MI_FAILURE;
			}
			buf = newbuf;
		}

		strcpy(buf + i, rcode);
		i += 3;
		buf[i] = '-';
		lasti= i++;
		memcpy(buf + i, xcode, xcodelen);
		i += xcodelen;
		buf[i++] = ' ';
		strcpy(buf + i, p);
		i += strlen(p);

		p = va_arg(ap, char*);
		if (p) {
			buf[i++] = '\r';
			buf[i++] = '\n';
		}
	}
	buf[i] = 0;
	buf[lasti] = ' ';

	ctx->reply = buf;
	
	return MI_SUCCESS;
}

int
gacopyz_setreply(SMFICTX *ctx, const char *rcode, const char *xcode,
		 const char *message)
{
	if (rcode == NULL || ctx == NULL)
		return MI_FAILURE;

        if ((rcode[0] != '4' && rcode[0] != '5') ||
	    !isascii(rcode[1]) || !isdigit(rcode[1]) ||
	    !isascii(rcode[2]) || !isdigit(rcode[2]))
		return MI_FAILURE;

        if (strlen(rcode) != 3)
		return MI_FAILURE;

        if (xcode != NULL) {
		if (!enhanced_code_p(xcode))
			return MI_FAILURE;
	}

	free(ctx->reply);
	ctx->reply = NULL;
	return format_message(&ctx->reply, rcode, xcode, message);
}


int
gacopyz_setmlreply_va(SMFICTX *ctx, const char *rcode, const char *xcode,
		      va_list ap)
{
	return _gacopyz_setmlreply_va(ctx, 0, rcode, xcode, ap);
}
	
int
gacopyz_setmlreply_v(SMFICTX *ctx, const char *rcode, const char *xcode, ...)
{
	int rc;
	va_list ap;

	va_start(ap, xcode);
	rc = gacopyz_setmlreply_va(ctx, rcode, xcode, ap);
	va_end(ap);

	return rc;
}
	
