/**************************************************************************************************
	$Header: /pub/cvsroot/yencode/src/ypost/usenet.c,v 1.1 2002/03/15 15:10:49 bboy Exp $
	Routines used by `ypost' to validate and construct Usenet (RFC 1036) data.

	Copyright (C) 2002  Don Moore <bboy@bboy.net>

	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 2 of the License, 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, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "ypost.h"


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	VALIDATE_EMAIL
	Is this a valid email address?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static char *
validate_email(char *email)
{
	/* XXX: This is VERY non-robust */
	if (strchr(email, '@'))
		return (NULL);
	else
		return (_("invalid email address"));
}
/*--- validate_email() --------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	USENET_VALID_FROM
	Checks the supplied address to make sure it's a conformant From: value.  Will make minor
	adjustments to the string if possible.  Returns NULL if conformant, or a string describing
	the problem if not.  This function is not that strict.

	RFC 1036 gives the following examples as valid addresses:

		From: mark@cbosgd.ATT.COM
		From: mark@cbosgd.ATT.COM (Mark Horton)
		From: Mark Horton <mark@cbosgd.ATT.COM>
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char *
usenet_valid_From(char *from, size_t len)
{
	char	*paren, *angle, *email, *comment, *reason;
	char	fromcopy[BUFSIZ];

	strtrim(from);
	strncpy(fromcopy, from, sizeof(fromcopy)-1);
	paren = strchr(fromcopy, '(');
	angle = strchr(fromcopy, '<');

	// First form: No parens, no angle brackets
	if (!paren && !angle)
	{
		email = fromcopy;
		opt_sender = xstrdup(email);
		strtrim(opt_sender);
		return (validate_email(email));
	}

	// Second form: Comment in parentheses
	if (paren)
	{
		email = fromcopy;
		comment = paren;
		*(comment++) = '\0';
		if (!(paren = strchr(comment, ')')))
			return (_("unterminated comment in author name"));
		*paren = '\0';
		opt_sender = xstrdup(email);
		strtrim(opt_sender);
		if ((reason = validate_email(email)))
			return (reason);
		return (NULL);
	}

	// Third form: Email in angle brackets
	if (angle)
	{
		comment = fromcopy;
		email = angle;
		*(email++) = '\0';
		if (!(angle = strchr(email, '>')))
			return (_("unterminated email address in author name"));
		*angle = '\0';
		opt_sender = xstrdup(email);
		strtrim(opt_sender);
		if ((reason = validate_email(email)))
			return (reason);
		return (NULL);
	}
	return (_("unknown or invalid format for author (see ypost(1))"));
}
/*--- usenet_valid_From() -----------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	USENET_VALID_SUBJECT
	Checks the supplied subject data to make sure it's a conformant Subject: value.
	Returns NULL if conformant, or a string describing the problem if not.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char *
usenet_valid_Subject(char *str, size_t len)
{
	strtrim(str);
	/* XXX: This isn't much of a check! */
	return (NULL);
}
/*--- usenet_valid_Subject() --------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	USENET_DATE
	Returns a static string containing the date in the format specified by RFC 822 / RFC 1036
	Ex: Wdy, DD Mon YY HH:MM:SS TIMEZONE
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char *
usenet_date(void)
{
	static char datebuf[80];									/* Return value */
	time_t now;														/* Current time */
	const struct tm *tm;											/* Broken down time */

	time(&now);
	tm = gmtime(&now);
	strftime(datebuf, sizeof(datebuf)-1, "%a, %d %b %Y %H:%M:%S %Z", tm);
	return (datebuf);
}
/*--- usenet_date() -----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	USENET_MESSAGE_ID
	Returns an unique message ID.  The basic format is:
		<time><pid><idcount>@<hostname>
	The <pid> ensures that two copies of ypost running simultaneously will not use the same
	Message-ID if they post in the same second, and the <idcount> ensures that if this instance
	of ypost posts many messages in the same second they will still be unique, as it increments
	with each Message-ID generated.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char *
usenet_message_id(void)
{
	struct utsname uts;
	static int idcount = 0;										/* Number of IDs generated so far */
	static char id[1024];										/* The ID generated */

	if (uname(&uts))
		ErrERR(_("unable to determine hostname"));
	snprintf(id, sizeof(id), "<%lx%x%x@%s>", time(NULL), getpid(), idcount++, uts.nodename);
	return (id);
}
/*--- usenet_message_id() -----------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	PAD_NUMBER
	Returns a pointer to a static string containing a number formatted to the width of the 
	`total' number.  0 indicates 'x' if use_x is nonzero.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char *
pad_number(int number_to_pad, int total, int use_x)
{
	int width = numeric_width(total);
	static char numbuf[16];

	if (number_to_pad <= 0 && use_x)
		snprintf(numbuf, sizeof(numbuf), "%*.*s", width, width, "xxxxxxxxxxxxxxxxx");
	else
		snprintf(numbuf, sizeof(numbuf), "%0*d", width, number_to_pad);
	return (numbuf);
}
/*--- pad_number() ------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	FORMAT_SUBJECT
	Replaces variables with values in the subject string specified.  The string assumed to be
	dynamically allocated, and will be realloc()'d to accomodate the new string.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
format_subject(char *subject)
{
	char *c;
	char result[BUFSIZ], *r;
	int  len = 0;

	memset(result, 0, sizeof(result));
	for (c = subject, r = result; *c && len < sizeof(result)-1; c++)
	{
		if (*c == '%')
			switch (*(c+1))
			{
				case 'p':		/* $p: Current part number */
					len += snprintf(r, sizeof(result) - strlen(result), "%s", pad_number(part_current, part_total, 1));
					r = result + len, c++;
					break;
				case 'P':		/* $P: Total number of parts */
					len += snprintf(r, sizeof(result) - strlen(result), "%d", part_total);
					r = result + len, c++;
					break;
				case 'f':		/* $f: Current file number */
					len += snprintf(r, sizeof(result) - strlen(result), "%s", pad_number(file_current, file_total, 0));
					r = result + len, c++;
					break;
				case 'F':		/* $F: Total number of files */
					len += snprintf(r, sizeof(result) - strlen(result), "%d", file_total);
					r = result + len, c++;
					break;
				default:
					result[len++] = *c;
					break;
			}
		else
			*(r++) = *c, len++;
	}
	subject = realloc(subject, len);
	subject[len] = '\0';
	memcpy(subject, result, len);
}
/*--- format_subject() --------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	USENET_MAKE_SUBJECT
	Constructs the subject line for the current file.  `part' is the current part number (relevant
	only if the file is multipart).  If `part' is -1, "XX" will be inserted for the confirmation
	output.

	Suggested format, single part binary:

		[Comment1] "filename" 12345 yEnc bytes [Comment2]

	[Comment1] and [Comment2] are optional. The filename should always be enclosed in quotes;
	this allows for easy detection, even when the filename includes spaces or other special
	characters. The word "yEnc" should be placed in between the file size and the word "bytes".


	Suggested format, multipart binary:

		[Comment1] "filename" yEnc (partnum/numparts) [size] [Comment2]

	Again, [Comment1] and [Comment2] are optional. The [size] value is also optional here.
	The filename must be included, in quotes. The keyword "yEnc" is mandatory, and must
	appear between the filename and the size (or Comment2, if size is omitted). Future
	revisions of the draft may specify additional information may be inserted between the
	"yEnc" keyword and the opening parenthesis of the part number. 
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
unsigned char *
usenet_make_subject(YENCFILE *y, int part)
{
	unsigned char *subj = NULL;

	if (opt_subject)
		sdprintf(&subj, "[%s] ", opt_subject);
	sdprintf(&subj, "\"%s\" ", STRIP_PATH(y->input_filename));

	if (y->totalparts < 2)										/* Single part subject */
		sdprintf(&subj, "%u yEnc bytes", y->filesize);
	else																/* Multi part subject */
	{
		/* Get part number as a string */
		sdprintf(&subj, "yEnc (%s/%d) %u bytes", pad_number(part, y->totalparts, 1), y->totalparts, y->filesize);
	}
	if (opt_comment)
		sdprintf(&subj, " [%s]", opt_comment);
	format_subject(subj);

	return (subj);
}
/*--- usenet_make_subject() ---------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	USENET_MAKE_HEADERS
	Creates the full set of headers for a single message.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
unsigned char *
usenet_make_headers(YENCFILE *y, int part)
{
	unsigned char *hdr = NULL, **h = &hdr;					/* The header data */
	unsigned char *subject = usenet_make_subject(y, part);

	sdprintf(h, "From: %s" CRLF, opt_author);
	if (opt_sender)
		sdprintf(h, "Sender: %s" CRLF, opt_sender);
	sdprintf(h, "User-Agent: %s/" VERSION " (http://www.yencode.org/)" CRLF, short_progname);
	sdprintf(h, "Date: %s" CRLF, usenet_date());
	sdprintf(h, "Newsgroups: %s" CRLF, opt_newsgroup);
	sdprintf(h, "Subject: %s" CRLF, subject);

	if (opt_message_id)
		sdprintf(h, "Message-ID: %s" CRLF, usenet_message_id());

	/* Calculate number of lines in this post */
	if (y->totalparts == 1)
		sdprintf(h, "Lines: %u" CRLF, y->enclines + 2);
	else
	{
		if (part < y->totalparts)
			sdprintf(h, "Lines: %u" CRLF, opt_multipart_lines + 3);
		else
			sdprintf(h, "Lines: %u" CRLF, (y->enclines % opt_multipart_lines) + 3);
	}
	sdprintf(h, CRLF);

	free(subject);
	return (hdr);
}
/*--- usenet_make_headers() ---------------------------------------------------------------------*/

/* vi:set ts=3: */


syntax highlighted by Code2HTML, v. 0.9.1