/*
 * LIB/SUBS.C
 *
 * (c)Copyright 1998, Matthew Dillon, All Rights Reserved.  Refer to
 *    the COPYRIGHT file in the base directory of this distribution
 *    for specific rights granted.
 *
 */

#include "defs.h"

Prototype const char *PatExpand(const char *pat);
Prototype void diablo_strlcpy(char *d, const char *s, int ssize, int dsize);
Prototype void diablo_strlcpynl(char *d, const char *s, int ssize, int dsize);
Prototype const char *PatExpand(const char *pat);
Prototype const char *PatLibExpand(const char *pat);
Prototype const char *PatDbExpand(const char *pat);
Prototype const char *PatLogExpand(const char *pat);
Prototype const char *PatSpoolExpand(const char *pat);
Prototype const char *PatRunExpand(const char *pat);
Prototype int CidrMatch(const char *cidr, const char *ip);
Prototype int IsIpAddr(char *a);
Prototype char *SanitiseAddr(char *addr);
Prototype void GetIPHost(struct sockaddr *sa, char *hname, int hnamelen, char *ipname, int iplen);
Prototype int TestForwardLookup(const char *hostName, const char *ipst, const struct sockaddr *res);
Prototype char *NetAddrToSt(int fd, struct sockaddr *sa, int ip4conv, int port, int v6spec);
Prototype int ValidGroupName(const char *name);
Prototype void SanitizeString(char *name);
Prototype void SanitizeDescString(char *name);
Prototype char *StrnCpyNull(char *dst, const char *src, size_t maxlen);
Prototype const char *ftos(double d);
Prototype long bsizetol(char *p);
Prototype double bsizektod(char *p);
Prototype long btimetol(char *p);
Prototype char *dtlenstr(time_t t);
Prototype int MakeGroupDirectory(char *path);
Prototype char *strdupfree(char **st, char *val, char *empty);
Prototype char *safestr(char *st, char *noval);
Prototype int enabled(char *st);
Prototype int HashFeedMatch(HashFeed *hashfeed, int article);
Prototype int MoveFile(char *from, char *to);
Prototype int TimeSpec(char *t, char *def);

u_long ascToAddr(const char *str);
u_long ascToMask(const char *str);

void 
diablo_strlcpy(char *d, const char *s, int ssize, int dsize)
{
    while (ssize && dsize > 1 && *s) {
	*d = *s;
	--ssize;
	--dsize;
	++s;
	++d;
    }
    *d = 0;
}

void 
diablo_strlcpynl(char *d, const char *s, int ssize, int dsize)
{
    char *dold = d;

    while (ssize && *s == ' ') {
	++s;
	--ssize;
    }

    while (ssize && dsize > 1 && *s) {
	*d = *s;
	--ssize;
	--dsize;
	++s;
	++d;
    }
    *d = 0;

    /*
     * remove newlines and trailing tabs and spaces
     */
    while (d != dold && (d[-1] == '\n' || d[-1] == '\r')) {
	--d;
	*d = 0;
    }
    while (d != dold && (d[-1] == '\t' || d[-1] == ' ')) {
	--d;
	*d = 0;
    }
}

static char PatPath1[128];
static char PatPath2[128];

const char *
PatExpand(const char *pat)
{
    snprintf(PatPath1, sizeof(PatPath1), pat, NewsHome);

    return(PatPath1);
}


const char *
PatLibExpand(const char *pat)
{
    snprintf(PatPath2, sizeof(PatPath2), pat, PatExpand(LibHomePat));
    return(PatPath2);
}


const char *
PatDbExpand(const char *pat)
{
    snprintf(PatPath2, sizeof(PatPath2), pat, PatExpand(DbHomePat));
    return(PatPath2);
}

const char *
PatLogExpand(const char *pat)
{
    snprintf(PatPath2, sizeof(PatPath2), pat, PatExpand(LogHomePat));
    return(PatPath2);
}

const char *
PatSpoolExpand(const char *pat)
{
    snprintf(PatPath2, sizeof(PatPath2), pat, PatExpand(SpoolHomePat));
    return(PatPath2);
}

const char *
PatRunExpand(const char *pat)
{
    snprintf(PatPath2, sizeof(PatPath2), pat, PatExpand(RunHomePat));
    return(PatPath2);
}

int 
IsIpAddr(char *a)
{
    const char *spanstr;

#ifdef INET6
    if (strchr(a, ':') != NULL)
	spanstr = "0123456789abcdefgh.:/*?[]";
    else
#endif
	spanstr = "0123456789./*?";
    if (strspn(a, spanstr) == strlen(a))
	return(1);
    else
	return(0);
}

/*
 * Sanitise an IP[v6] address by removing the '[]'
 */
char *
SanitiseAddr(char *addr)
{
    static char st[64];
    char *p;
    int len;

    if (addr == NULL)
	return(NULL);
    if (*addr == '[')
	addr++;
    len = strlen(addr);
    if ((p = strchr(addr, ']')) != NULL)
	len = p - addr;
    if (len >= sizeof(st))
	len = sizeof(st) - 1;
    strncpy(st, addr, len);
    st[len] = '\0';
    return(st);
}

u_long
ascToAddr(const char *str)
{
    char string[32];
    char *ptr;
    u_long i, a, b, c, d;

    snprintf(string, sizeof(string), "%s", str);
    if ((ptr = strchr(string, '/')))
	*ptr = '\0';

    /* Count the dots. */
    ptr = string;
    i = 0;
    while (*ptr)
	if (*ptr++ == '.')
	    i++;

    if (i == 0) {
	sscanf(string, "%ld", &a);
	return(a << 24);
    } else if (i == 1) {
	sscanf(string, "%ld.%ld", &a, &b);
	return((a << 24) + (b << 16));
    } else if (i == 2) {
	sscanf(string, "%ld.%ld.%ld", &a, &b, &c);
	return((a << 24) + (b << 16) + (c << 8));
    } else {
	sscanf(string, "%ld.%ld.%ld.%ld", &a, &b, &c, &d);
	return((a << 24) + (b << 16) + (c << 8) + d);
    }
}

u_long
ascToMask(const char *str)
{
    char *ptr;
    u_long i;

    if ((ptr = strchr(str, '/')))
	ptr++;
    else
	return(0xffffffff);

    /* Accept either CIDR /nn or /xxx.xxx.xxx.xxx */
    if (strstr(ptr, ".")) {
	return(ntohl(inet_addr(ptr)));
    } else {
	i = atoi(ptr);
	if (!i)
	    return(0);
	else
	    return((u_long) 0xffffffff << (32 - i));
    }
}

/*
 * Match CIDR address - return 1 for sucess else 0
 *
 * This is really messy
 */
int
CidrMatch(const char *cidr, const char *ip)
{
#ifdef INET6
    char addr[256];
    char mask[256];
    uint8 maskbits = 128;
    char maskst[NI_MAXHOST];
    char *p;
    int i;

    if (strchr(ip, ':') != NULL) {
	uint8 b8;
	uint8 b;

	if (strchr(cidr, ':') == NULL)	/* Don't compare against non v6 CIDR */
	    return(0);
	strcpy(maskst, cidr);
	if ((p = strchr(maskst, '/')) != NULL) {
	    *p = '\0';
	    maskbits = atoi(++p);
	}
	if (inet_pton(AF_INET6, ip, addr) != 1) {
	    logit(LOG_ERR, "Invalid IPv6 address: %s", ip);
	    return(0);
	}
	if (inet_pton(AF_INET6, maskst, mask) != 1) {
	    logit(LOG_ERR, "Invalid IPv6 address: %s", cidr);
	    return(0);
	}
	
	for (i = 0; i * 8 < maskbits; i++) {
	    if (i < maskbits / 8)
		b8 = 255;
	    else
		for (b8 = 0, b = 0; b < maskbits % 8; b++)
		    b8 |= (1 << (7 - b));
	    if ((addr[i] & b8) != (mask[i] & b8))
		return(0);
	}
	return(1);
    } else
#endif
    {
	u_long net;
	u_long mask;
	u_long addr;

	if (strchr(cidr, ':') != NULL)	/* Don't compare against v6 CIDR */
	    return(0);
	addr = ascToAddr(ip);
	net = ascToAddr(cidr);
	mask = ascToMask(cidr);
	if ((addr & mask) != (net & mask))
	    return(0);
	return(1);
    }
}

void
GetIPHost(struct sockaddr *sa, char *hname, int hnamelen, char *ipname, int iplen)
{
    strncpy(ipname, NetAddrToSt(0, sa, 1, 0, 0), iplen - 1);
    ipname[iplen - 1] = '\0';

    *hname = '\0';
    {
#ifdef INET6
	int error;

	if ((error = getnameinfo(sa, SA_LEN(sa), hname, hnamelen,
					NULL, 0, NI_NAMEREQD)) != 0)
		*hname = '\0';
#else
	struct hostent *he;
	struct sockaddr_in *sin = (struct sockaddr_in *)sa;

	he = gethostbyaddr(
	    (char *)&sin->sin_addr,
	    sizeof(sin->sin_addr),
	    AF_INET
	);

	if (he != NULL) {
	    strncpy(hname, he->h_name, hnamelen - 1);
	    hname[hnamelen - 1] = '\0';
	}
#endif /* INET6 */
    }

}

/*
 * TestForwardLookup(). 
 *
 * Check that Fwd and Rev DNS entries match up
 */

int
TestForwardLookup(const char *hostName, const char *ipst, const struct sockaddr *sa)
{
#ifdef INET6
    struct addrinfo *ai0;
    int r = -1;

    stprintf("forward auth %s", hostName);
    if (getaddrinfo(hostName, NULL, NULL, &ai0) == 0) {
	struct addrinfo *ai;

	for (ai = ai0; ai; ai = ai->ai_next) {
	    char *st = NetAddrToSt(0, ai->ai_addr, 1, 0, 0);
	    if (st != NULL && strcmp(ipst, st) == 0) {
		r = 0;
		break;
	    }
	}
	if (ai == NULL)
	    logit(LOG_NOTICE, "DNS Fwd/Rev mismatch: %s/%s", hostName, ipst);
	freeaddrinfo(ai0);
    } else {
	logit(LOG_NOTICE, "DNS Fwd/Rev mismatch: lookup of %s failed", hostName);
    }
    return(r);
#else
    struct hostent *he;
    struct sockaddr_in *sin = (struct sockaddr_in *)sa;
    int r = -1;

    if ((he = gethostbyname(hostName)) != NULL) {
	int i;

	for (i = 0; he->h_addr_list && he->h_addr_list[i]; ++i) {
	    const struct in_addr *haddr = (const void *)he->h_addr_list[i];
	    if (sin->sin_addr.s_addr == haddr->s_addr) {
		r = 0;
		break;
	    }
	}
	if (he->h_addr_list[i] == NULL)
	    logit(LOG_NOTICE, "DNS Fwd/Rev mismatch: %s/%s", hostName, ipst);
    } else {
	logit(LOG_NOTICE, "DNS Fwd/Rev mismatch: lookup of %s failed", hostName);
    }
    return(r);
#endif
}

int 
ValidGroupName(const char *name)
{
    int r = 0;

    if (name[0] == 0 || name[0] == '.') {
	r = -1;
    } else {
	for (; *name; ++name) {
	    if (*name == ' ' || ! isprint((int)*name)) {
		r = -1;
	    }
	    if (*name >= 'a' && *name <= 'z')
		continue;
	    if (*name >= 'A' && *name <= 'Z')
		continue;
	    if (*name >= '0' && *name <= '9')
		continue;
	    if (*name == '+')
		continue;
	    if (*name == '-')
		continue;
	    if (*name == '.') {
		if (name[-1] == '.' || name[1] == '.' || name[1] == 0)
		    r = -1;
	    }
	}
    }
    return(r);
}

void
SanitizeString(char *name)
{
    for (; *name; ++name) {
	if (*name >= 'a' && *name <= 'z')
	    continue;
	if (*name >= 'A' && *name <= 'Z')
	    continue;
	if (*name >= '0' && *name <= '9')
	    continue;
	if (*name == '\t')
	    *name = ' ';
	if (*name == '@' ||
	    *name == '-' ||
	    *name == '+' ||
	    *name == '_' ||
	    *name == '.' ||
	    *name == ',' ||
	    *name == ' '
	) {
	    continue;
	}
	*name = '?';
    }
}

/*
 * Sanitise a group description
 */
void
SanitizeDescString(char *name)
{
    for (; *name; ++name) {
	if (*name == '\t')
	    *name = ' ';
	if (!iscntrl((int)*name))  
	    continue;
	*name = '?';
    }
}

/*
 * StrnCpyNull()
 *
 * Like strcpy, but gracefully handles null pointers and limits the length
 * of the string copied.
 */
char *
StrnCpyNull(char *dst, const char *src, size_t maxlen)
{
    if (dst) {
    	if (src) {
	    if (strlen(src) >= maxlen) {
		strncpy(dst, src, maxlen - 1);
		dst[maxlen - 1] = '\0';
	    }
	    else {
		strcpy(dst, src);
	    }
    	}
    	else {
	    *dst = '\0';
    	}
    }
    return dst;
}

#define ONE_K	1024.0	
#define ONE_M	(1024.0*1024.0)
#define ONE_G	(1024.0*1024.0*1024.0)
#define ONE_T	(1024.0*1024.0*1024.0*1024.0)

const char *
ftos(double d)
{
    static char FBuf[8][32];
    static int FCnt;
    char *p = FBuf[FCnt];

    if (d < 1024.0) {
	sprintf(p, "%d", (int)d);
    } else if (d < ONE_M) {
	sprintf(p, "%d.%03dK", (int)(d / ONE_K), ((int)d % (int)ONE_K) * 1000 / (int)ONE_K);
    } else if (d < ONE_G) {
	sprintf(p, "%d.%03dM", (int)(d / ONE_M), ((int)(d / ONE_K) % (int)ONE_K) * 1000 / (int)ONE_K);
    } else if (d < ONE_T) {
	sprintf(p, "%d.%03dG", (int)(d / ONE_G), ((int)(d / ONE_M) % (int)ONE_K) * 1000 / (int)ONE_K);
    } else {
	sprintf(p, "%d.%03dT", (int)(d / ONE_T), ((int)(d / ONE_G) % (int)(d / ONE_M) % (int)ONE_K) * 1000 / (int)ONE_K);
    }

    FCnt = (FCnt + 1) & 7;
    return(p);
}

/*
 * Convert a string value to a number of bytes. The string can be
 * specified in kb, mb or gb
 */
long
bsizetol(char *p)
{
    long n;

    n = strtol(p, &p, 0);
    switch(*p) {
    case 'g':
    case 'G':
	n *= 1024;
	/* fall through */
    case 'm':
    case 'M':
	n *= 1024;
	/* fall through */
    case 'k':
    case 'K':
	n *= 1024;
	break;
    }
    return(n);
}

/*
 * Convert a string value to a number of kilobytes
 */
double
bsizektod(char *p)
{
    double n;

    n = strtol(p, &p, 0) * 1024.0;
    switch(*p) {
    case 'g':
    case 'G':
	n *= 1024.0;
	/* fall through */
    case 'm':
    case 'M':
	n *= 1024.0;
	break;
	/* fall through */
    case 't':
    case 'T':
	n *= 1024.0;
	break;
    }
    return(n);
}

/*
 * Convert a time specification in days, hours, mins and/or secs to a long
 * representing the number of secs. The specification can include
 * multiple time specifications.
 */
long
btimetol(char *p)
{
    long n;
    long res = 0;

    while (*p) {
	while (*p && !(int)isdigit(*p))
	    p++;
	n = strtol(p, &p, 0);
	while (*p && (int)isdigit(*p))
	    p++;
	switch(*p) {
	    case 'd':
	    case 'D':
		n *= 24;
		/* fall through */
	    case 'h':
	    case 'H':
		n *= 60;
		/* fall through */
	    case 'm':
	    case 'M':
		n *= 60;
		break;
	    case 's':
	    case 'S':
	    case ' ':
		break;
	    default:
		p++;
	}
	p++;
	res += n;
    }
    return(res);
}

char *
dtlenstr(time_t t)
{
    int d = 0;
    int h = 0;
    int m = 0;
    static char tb[64];

    if (t > 24 * 60 * 60) {
	d = t / (24 * 60 * 60);
	t = t % (24 * 60 * 60);
    }
    if (t > 60 * 60) {
	h = t / (60 * 60);
	t = t % (60 * 60);
    }
    if (t > 60) {
	m = t / 60;
	t = t % 60;
    }
    sprintf(tb, "%d days %d hrs %d min %d sec", d, h, m, (int)t);
    return(tb);
}

#ifdef INET6
/*
 * A protocol independent conversion of network address to string
 *
 * Strips the initial portion of a IPv6 encoded IPv4 address if
 * requested
 *
 * If sa == NULL then the address is obtained from the fd passed
 * otherwise the fd is ignored.
 */
char *
NetAddrToSt(int fd, struct sockaddr *sa, int ip4conv, int port, int v6spec)
{
    static char st[NI_MAXHOST + 8];
    char addrst[NI_MAXHOST];
    char serv[16];
    int error;

    if (sa == NULL) {
	static struct sockaddr_storage res;
	int salen = sizeof(res);

	sa = (struct sockaddr *)&res;
	if (getpeername(fd, sa, &salen) != 0) {
	    logit(LOG_ERR, "%s", strerror(errno));
	    return(NULL);
	}
    }
    if (SA_LEN(sa) == 0)
	return(NULL);
    if ((error = getnameinfo(sa, SA_LEN(sa), addrst, sizeof(addrst),
					serv, sizeof(serv),
					NI_NUMERICHOST|NI_NUMERICSERV)) == 0) {
	if (v6spec && strchr(addrst, ':')) {
	    snprintf(st, sizeof(st), "[%s]", addrst);
	} else {
	    strncpy(st, addrst, sizeof(st) - 1);
	    st[sizeof(st) - 1] = '\0';
	}
	if (port) {
	    strncat(st, ":", sizeof(st) - 1);
	    strncat(st, serv, sizeof(st) - 1);
	}
	if (ip4conv && strncmp(st, "::ffff:", 7) == 0)
            return(st + 7);
	else
            return(st);
    } else {
            logit(LOG_ERR, "getnameinfo: %s\n", gai_strerror(error));
    }
    return(NULL);
}
#else
char *
NetAddrToSt(int fd, struct sockaddr *sa, int ip4conv, int port, int v6spec)
{
    static char st[128];
    struct sockaddr_in *sin = (struct sockaddr_in *)sa;

    strncpy(st, inet_ntoa(sin->sin_addr), sizeof(st) - 1);
    st[sizeof(st) - 1] = '\0';
    if (port) {
	char port[16];
	snprintf(port, sizeof(port), ":%d", ntohs(sin->sin_port));
	strncat(st, port, sizeof(st) - 1);
    }
    return(st);
}

#endif

/*
 * Create the directories required for the file pathname specified in path
 *   path = the full path/filename which gets clobbered.
 */
int
MakeGroupDirectory(char *path)
{
    char dir[PATH_MAX];
    char *p;
    struct stat st;

    strcpy(dir, path);
    if (strchr(dir, '/') == NULL)
	return(0);
    if ((p = strrchr(dir, '/')) != NULL)
	*p = '\0';
    if (stat(dir, &st) == 0)
	return(0);
    if (MakeGroupDirectory(dir) == -1)
	return(-1);
    return(mkdir(dir, 0755));
}

/*
 * If the string exists, free it
 * strdup the value
 * if value is "0", then set string to NULL
 */
char *
strdupfree(char **st, char *val, char *empty)
{
    if (*st != NULL)
	free(*st);
    if (val == NULL || strcmp(val, "0") == 0)
	*st = (empty == NULL) ? NULL : strdup(empty);
    else
	*st = strdup(val);
    return(*st);
}

/*
 * Return a string that isn't NULL
 */
char *
safestr(char *st, char *noval)
{
    if (st == NULL)
	return((noval != NULL) ? noval : "NONE");
    return(st);
}

int
enabled(char *st)
{
    if (st == NULL || !*st || *st == 'y' || *st == 'Y' ||
				strcasecmp(st, "on") == 0 || *st == '1')
	return(1);
    else
	return(0);
}

int
HashFeedMatch(HashFeed *hashfeed, int hash)
{
    if (DebugOpt > 1)
	printf("hash=%d  mod=%d  begin=%d  end=%d  res=%d  match=%d\n", hash,
				hashfeed->hf_Mod,
				hashfeed->hf_Begin,
				hashfeed->hf_End,
				hash % hashfeed->hf_Mod + 1,
		((hash % hashfeed->hf_Mod + 1) >= hashfeed->hf_Begin &&
                        (hash % hashfeed->hf_Mod + 1) <= hashfeed->hf_End)

    );
    return ((hash % hashfeed->hf_Mod + 1) >= hashfeed->hf_Begin &&
			(hash % hashfeed->hf_Mod + 1) <= hashfeed->hf_End);

}

int
MoveFile(char *from, char *to)
{
    int from_fd;
    int to_fd;
    static char buffer[32768];
    int rlen;

    if ((from_fd = open(from, O_RDONLY, 0)) < 0) {
	printf("ERROR: Unable to open input file: %s\n", from);
	return(-1);
    }
    if ((to_fd = open(to, O_CREAT | O_TRUNC | O_WRONLY, 0644)) < 0) {
	printf("ERROR: Unable to create output file: %s\n", to);
	return(-1);
    }
    while ((rlen = read(from_fd, buffer, 32768)) > 0)
	if (write(to_fd, buffer, rlen) != rlen) {
	    printf("ERROR: Unable to writing to file: %s\n", to);
	    close(from_fd);
	    close(to_fd);
	    unlink(to);
	    return(-1);
	}
    if (rlen < 0) {
	printf("ERROR: Unable to read from file: %s\n", from);
	close(from_fd);
	close(to_fd);
	unlink(to);
	return(-1);
    }
    close(from_fd);
    if (close(to_fd) != 0) {
	printf("ERROR: Unable to close file: %s\n", to);
	unlink(to);
	return(-1);
    }
    unlink(from);
    return(0);
}

/*
 * Convert a string time specification in days, hours, minutes or seconds
 * into a integer of seconds.
 */
int
TimeSpec(char *t, char *def)
{
    char *endp;
    int tt;

    tt = strtol(t, &endp, 0);
    if (endp == NULL || !*endp)
	endp = def;
    switch (*endp) {
	case 's':
	    break;
	case 'm':
	    tt *= 60;
	    break;
	case 'h':
	    tt *= 60 * 60;
	    break;
	case 'd':
	    tt *= 24 * 60 * 60;
	    break;
	default:
	    fprintf(stderr, "Invalid time specification: %s \n", t);
	    tt = -1;
    }
    return(tt);
}


syntax highlighted by Code2HTML, v. 0.9.1