/**************************************************************************************************
	$Header: /pub/cvsroot/yencode/src/ypost/ypost.c,v 1.5 2002/03/21 21:46:47 bboy Exp $

	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"


int		opt_verbose = 1;							/* Should the program be verbose in its operation? */
int		opt_debug = 0;								/* Debug output? */

char		*opt_author = NULL;						/* Author name for posting */
int		opt_prompt_author = 0;					/* Prompt for author? */
char		*opt_auth_user = NULL;					/* Username for authentication */
char		*opt_auth_pass = NULL;					/* Password for authentication */
int		opt_prompt_pass = 0;						/* Prompt for password? */
char		*opt_nntp_server = NULL;				/* nntp server to use */
char		*opt_subject = NULL;						/* Subject prefix */
int		opt_prompt_subject = 0;					/* Prompt for subject? */
char		*opt_comment = NULL;						/* Subject suffix */
int		opt_prompt_comment = 0;					/* Prompt for comment? */
int		opt_timeout = -1;							/* Timeout for socket ops */
char		*opt_newsgroup = NULL;					/* Newsgroup to post to */
int		opt_force = 0;								/* Post messages without confirming? */
int		opt_nosort = 0;							/* Do not sort input files */
int		opt_multipart_lines = -1;				/* Number of lines for multipart messages */
int		opt_info = 0;								/* Hidden option for debugging, just outputs file info */
int		opt_retry_limit = -1;					/* Max number of retries on failed post */
int		opt_message_id = 0;						/* Generate Message-ID: header when posting? */
int		opt_stdout = 0;							/* Output to stdout instead of a file? */

char		*opt_sender = NULL;						/* For header generation, not set directly */

int		opt_line_length = -1;					/* Line length for posting */
int		opt_keep_paths = 0;						/* Keep relative path names? */
int		opt_overwrite = 0;						/* (not actually used by this program..) */

int		opt_resume_msg = 0;						/* Message number at which to restart */
crc32_t	opt_resume_crc = 0;						/* CRC value for posts, to ensure they are the same */

YENCFILE	**input_files = (YENCFILE **)NULL;	/* List of files to process */
int		num_input_files = 0;						/* Number of items in files */

/* Optional common support files */
int		opt_sfv = 0;								/* Create .SFV file? */
char		*opt_sfv_filename = (char *)NULL;	/* SFV filename */
int		opt_crc = 0;								/* Create .CRC file? */
char		*opt_crc_filename = (char *)NULL;	/* CRC filename */

char		*opt_sort_first = NULL;					/* Sort these extensions first, comma separated list */

size_t	total_input_bytes = 0;					/* Total number of input bytes overall */

int		part_current = 0,							/* Current part number of this part */
			part_total = 0;							/* Total number of parts for this part */
int		file_current = 0,							/* Current file number overall */
			file_total = 0;							/* Total number of files being posted */
int		total_messages = 0,						/* Total number of messages to post */
			total_posted = 0;							/* Total number of messages posted so far */
crc32_t	total_crc = 0;								/* Total CRC of all messages to post */


static int	posting_started = 0;					/* Has the posting started yet? */



/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	USAGE
	Display program usage information.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
usage(int status)
{
	if (status != EXIT_SUCCESS)
	{
		fprintf(stderr, _("Try `%s --help' for more information."), progname);
		fputs("\n", stderr);
	}
	else
	{
		printf(_("Usage: %s [OPTION]... FILE..."), progname);
		puts("");
      puts(_("Post file(s) to Usenet."));
		puts("");
//		puts("----------------------------------------------------------------------------78");
		puts(_("  -a, --author=NAME      use NAME as author when posting (or prompt)"));
		puts(_("  -c, --comment=COMMENT  suffix all subjects with COMMENT (or prompt)"));
		puts(_("  -d, --debug            output extra debugging information while running"));
		puts(_("  -f, --force            post messages without confirming"));
		puts(_("  -g, --group=NEWSGROUP  post messages to the newsgroup called GROUP"));
		puts(_("  -l, --line=LEN         output lines that are LEN bytes in length"));
		puts(_("  -m, --multipart=NUM    multipart posts contain NUM lines (default: 5000)"));
		puts(_("  -M, --message-id       generate Message-ID header field when posting"));
		puts(_("  -n, --nosort           post articles in the order specified; do not sort"));
		puts(_("  -p, --paths            maintain paths in input filenames"));
		puts(_("  -P, --pass=PASS        password for nntp authentication (or prompt)"));
		puts(_("  -q, --quiet            inhibit all messages written to the standard output"));
		puts(_("  -r, --retry=NUM        if a post fails, retry NUM times (default: 3)"));
		puts(_("  -R, --resume=MSG,CHK   resume failed post at MSG, optionally verify CHK"));
		puts(_("  -s, --subject=SUBJECT  prefix all subjects with SUBJECT (or prompt)"));
		puts(_("  -S, --server=ADDR      connect to nntp server at ADDR (hostname or IP)"));
		puts(_("  -t, --timeout=SECS     socket operations timeout after SECS (default: 60)"));
		puts(_("  -U, --user=USER        username for nntp authentication"));
		puts(_("      --crc=NAME         post CRC checksum file for all input files"));
		puts(_("      --sfv=NAME         post SFV checksum file for all input files"));
		puts(_("      --stdout           output messages to standard output instead of posting"));
		puts(_("      --help             display this help and exit"));
		puts(_("      --version          output version information and exit"));
		puts("");
		puts(_("Command line options always override options found in the configuration file."));
		puts("");
      puts(_("Report bugs to bugs@yencode.org."));
	}
	exit(status);
}
/*--- usage() -----------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	CMDLINE
	Process command line options.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
cmdline(int argc, char **argv)
{
	int  optc, optindex;
	char *optstr;
	struct option const longopts[] =
	{
		{"author",			optional_argument,	NULL,	'a'},
		{"comment",			optional_argument,	NULL,	'c'},
		{"debug",			no_argument,			NULL,	'd'},
		{"force",			no_argument,			NULL,	'f'},
		{"group",			required_argument,	NULL,	'g'},
		{"line",				required_argument,	NULL,	'l'},
		{"multipart",		required_argument,	NULL,	'm'},
		{"pass",				optional_argument,	NULL,	'P'},
		{"paths",			no_argument,			NULL,	'p'},
		{"quiet",			no_argument,			NULL,	'q'},
		{"retry",			required_argument,	NULL,	'r'},
		{"server",			required_argument,	NULL,	'S'},
		{"subject",			optional_argument,	NULL,	's'},
		{"timeout",			required_argument,	NULL,	't'},
		{"user",				required_argument,	NULL,	'U'},
		{"sfv",				optional_argument,	NULL,	0},
		{"stdout",			no_argument,			NULL,	0},
		{"crc",				optional_argument,	NULL,	0},
		{"resume",			required_argument,	NULL,	'R'},
		{"help",				no_argument,			NULL,	0},
		{"version",			no_argument,			NULL,	0},
		{"info",				no_argument,			NULL,	0},

		{NULL, 0, NULL, 0}
	};

	optstr = getoptstr(longopts);
	while ((optc = getopt_long(argc, argv, optstr, longopts, &optindex)) != -1)
	{
		switch (optc)
		{
			case 0:
				{
					const char *opt = longopts[optindex].name;

					if (!strcmp(opt, "version"))									// --version
					{
						printf("%s - " PACKAGE " " VERSION "\n", short_progname);
						exit(EXIT_SUCCESS);
					}
					else if (!strcmp(opt, "help"))								// --help
						usage(EXIT_SUCCESS);
					else if (!strcmp(opt, "sfv"))									// --sfv
					{
						opt_sfv = 1;
						opt_sfv_filename = optarg;
					}
					else if (!strcmp(opt, "crc"))									// --crc
					{
						opt_crc = 1;
						opt_crc_filename = optarg;
					}
					else if (!strcmp(opt, "info"))								// --info
						opt_info = 1;
					else if (!strcmp(opt, "stdout"))								// --stdout
						opt_stdout = 1;
				}
				break;

			case 'a':																	// -a, --author=AUTHOR
				if (optarg)
					opt_author = xstrdup(optarg);
				else
					opt_prompt_author = 1;
				break;

			case 'c':																	// -c, --comment=COMMENT
				if (optarg)
					opt_comment = xstrdup(optarg);
				else
					opt_prompt_comment = 1;
				break;

			case 'd':																	// -d, --debug
				opt_debug = opt_verbose = 1;
				break;

			case 'f':																	// -f, --force
				opt_force = 1;
				break;

			case 'g':																	// -g, --group=GROUP
				opt_newsgroup = xstrdup(optarg);
				break;

			case 'l':																	// -l, --line
				opt_line_length = atoi(optarg);
				if (opt_line_length > 254)
					Err(_("line lengths greater than 254 are not allowed by the yEnc specification"));
				break;

			case 'm':																	// -m, --multipart=LINES
				opt_multipart_lines = atoi(optarg);
				break;

			case 'M':																	// -M, --message-id
				opt_message_id = 1;
				break;

			case 'p':																	// -p, --paths
				opt_keep_paths = 1;
				break;

			case 'P':																	// -P, --pass=PASS
				if (optarg)
					opt_auth_pass = xstrdup(optarg);
				else
					opt_prompt_pass = 1;
				break;

			case 'q':																	// -q, --quiet
				opt_debug = opt_verbose = 0;
				break;

			case 'r':																	// -r, --retry=NUM
				opt_retry_limit = atoi(optarg);
				break;

			case 'R':																	// -R, --resume
				{
					char *c = strchr(optarg, ',');

					if (c)
					{
						*(c++) = '\0';
						opt_resume_crc = (crc32_t)strtoul(c, (char **)NULL, 16);
					}
					opt_resume_msg = atoi(optarg);
				}
				break;

			case 's':																	// -s, --subject=SUBJ
				if (optarg)
					opt_subject = xstrdup(optarg);
				else
					opt_prompt_subject = 1;
				break;

			case 'S':																	// -S, --server=SERV
				opt_nntp_server = xstrdup(optarg);
				break;

			case 't':																	// -t, --timeout=SECS
				opt_timeout = atoi(optarg);
				break;

			case 'U':																	// -U, --user=USER
				opt_auth_user = xstrdup(optarg);
				break;

			default:
				usage(EXIT_FAILURE);
		}
	}

	/* Set these options for the routines in "error.c" in the library */
	err_debug = opt_debug;
	err_verbose = opt_verbose;

	while (optind < argc)
	{
		YENCFILE *y;

		if ((y = yencfile_create(argv[optind++], NULL, YSUPPORT_NOT_SPECIAL, 0)))
		{
			input_files = (YENCFILE **)xrealloc(input_files, (num_input_files + 1) * sizeof(YENCFILE *));
			input_files[num_input_files++] = y;
			total_input_bytes += y->filesize;

			/* The xxx_construct() functions want this set */
			y->ok = 1;
		}
	}

	/* Some input files must be specified.. */
	if (!num_input_files)
	{
		Warn(_("no input files"));
		usage(EXIT_FAILURE);
	}
}
/*--- cmdline() ---------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	LOAD_YPOSTRC
	Load the file ~/.ypostrc.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
load_ypostrc(void)
{
	FILE	*fp;
	char	buf[BUFSIZ];
	char	*name, *value;
	char	rcfile[PATH_MAX];
	struct passwd	*pwd;
	int	line = 0;

	if (!(pwd = getpwuid(getuid())) || !pwd->pw_dir)
		return;
	snprintf(rcfile, sizeof(rcfile), "%s/.ypostrc", pwd->pw_dir);
	memset(pwd, 0, sizeof(struct passwd));					// Get this out of memory
	if (!(fp = fopen(rcfile, "r")))
	{
		if (errno == ENOENT)
			return;
		ErrERR("%s", rcfile);
	}
	while (fgets(buf, sizeof(buf), fp))
	{
		line++;
		conftrim(buf);
		value = buf;
		name = strsep(&value, "=");
		if (!name || !value)
			continue;
		strtrim(name);
		strtrim(value);

		/* Remove quotation marks around value if any */
		while ((value[0] == '\'' && value[strlen(value)-1] == '\'')
				 || (value[0] == '"' && value[strlen(value)-1] == '"'))
		{
			value++;
			value[strlen(value)-1] = '\0';
		}

#define WARNOVER(OverrideCondition) \
		if (OverrideCondition) \
		{ \
			Warn(_("%s:%d: `%s' option in config file overridden by command-line"), rcfile, line, name); \
			continue; \
		}

		if (!strcmp(name, "author"))
		{
			WARNOVER(opt_author || opt_prompt_author);
			opt_author = xstrdup(value);
		}

		else if (!strcmp(name, "comment"))
		{
			WARNOVER(opt_comment || opt_prompt_comment);
			opt_comment = xstrdup(value);
		}

		else if (!strcmp(name, "debug"))
		{
			WARNOVER(opt_debug);
			opt_debug = err_debug = opt_verbose = err_verbose = getbool(value);
		}

		else if (!strcmp(name, "force"))
		{
			WARNOVER(opt_force);
			opt_force = getbool(value);
		}

		else if ((!strcmp(name, "group") || !strcmp(name, "newsgroup")))
		{
			WARNOVER(opt_newsgroup);
			opt_newsgroup = xstrdup(value);
		}

		else if (!strcmp(name, "line"))
		{
			WARNOVER(opt_line_length != -1);
			opt_line_length = atoi(value);
		}

		else if (!strcmp(name, "multipart"))
		{
			WARNOVER(opt_multipart_lines != -1);
			opt_multipart_lines = atoi(value);
		}

		else if ((!strcmp(name, "messageid") || !strcmp(name, "message-id")))
		{
			WARNOVER(opt_message_id);
			opt_message_id = getbool(value);
		}

		else if (!strcmp(name, "nosort"))
		{
			WARNOVER(opt_nosort);
			opt_nosort = getbool(value);
		}

		else if (!strcmp(name, "paths"))
		{
			WARNOVER(opt_keep_paths);
			opt_keep_paths = getbool(value);
		}

		else if ((!strcmp(name, "pass") || !strcmp(name, "password")))
		{
			WARNOVER(opt_auth_pass || opt_prompt_pass);
			opt_auth_pass = xstrdup(value);
		}

		else if (!strcmp(name, "quiet"))
		{
			WARNOVER(!opt_verbose);
			opt_debug = opt_verbose = 0;
		}

		else if (!strcmp(name, "retry"))
		{
			WARNOVER(opt_retry_limit != -1);
			opt_retry_limit = atoi(value);
		}

		else if (!strcmp(name, "stdout"))
		{
			WARNOVER(opt_stdout);
			opt_stdout = getbool(value);
		}

		else if (!strcmp(name, "subject"))
		{
			WARNOVER(opt_subject || opt_prompt_subject);
			opt_subject = xstrdup(value);
		}

		else if ((!strcmp(name, "serv") || !strcmp(name, "server")))
		{
			WARNOVER(opt_nntp_server);
			opt_nntp_server = xstrdup(value);
		}

		else if (!strcmp(name, "timeout"))
		{
			WARNOVER(opt_timeout != -1);
			opt_timeout = atoi(value);
		}

		else if ((!strcmp(name, "user") || !strcmp(name, "username")))
		{
			WARNOVER(opt_auth_user);
			opt_auth_user = xstrdup(value);
		}

		else if ((!strcmp(name, "sortfirst") || !strcmp(name, "sort_first")))
		{
			WARNOVER(opt_sort_first);
			opt_sort_first = xstrdup(value);
		}

		else if (!strcmp(name, "sfv"))
		{
			WARNOVER(opt_sfv);
			opt_sfv = getbool(value);
		}

		else if (!strcmp(name, "crc"))
		{
			WARNOVER(opt_crc);
			opt_crc = getbool(value);
		}

		else
			Warn(_("%s:%d: unknown option `%s' in config file (ignored)"), rcfile, line, name);
	}
	fclose(fp);
}
/*--- load_ypostrc() ----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	CALCULATE_ENCODED_INFO
	Calculates CRC values, encoded data length, and number of lines for all files matching `type'.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
calculate_encoded_info(ysupportfile_t file_type)
{
	register int ct, x, linect;
	FILE *fp;
	unsigned char buf[BUFSIZ];										/* Input data buffer */
	register size_t rb;												/* Bytes read this read() */
	register size_t total_bytes_read;							/* Total number of bytes read */
	size_t total_bytes;												/* Total bytes in all files this scan */
	int do_meter = 0;													/* Display a progress meter? */
	int lines_this_part = 0;										/* Number of lines found for current part */
	register size_t bytes_this_part = 0;						/* Number of bytes this part */
	off_t	offset;														/* Current byte offset in current part */
	YENCPART *part;													/* Current part info */

	total_bytes_read = total_bytes = (size_t)0;

	/* Get total number of bytes */
	for (ct = 0; ct < num_input_files; ct++)
		if (input_files[ct]->file_type == file_type)
			total_bytes += input_files[ct]->filesize;
	if (total_bytes > 1000000)
		do_meter = 1;

	/* Scan each file matching `file_type' */
	for (ct = 0; ct < num_input_files; ct++)
	{
		YENCFILE *y = input_files[ct];

		if (y->file_type != file_type)
			continue;

		y->totalparts = 1;
		lines_this_part = 0;
		bytes_this_part = 0;
		offset = 0;
		linect = 0;
		CRC_START(y->crc);

		/* Initialize our `part' list -- all files have at least one part */
		y->part = (YENCPART **)xrealloc(y->part, (y->totalparts) * sizeof(YENCPART *));
		part = yencpart_create();
		y->part[y->totalparts - 1] = part;
		part->begin = 1;

		if (y->support_data)
		{
			for (x = 0; x < y->filesize; x++)
			{
				CRC_UPDATE(y->crc, y->support_data[x]);

				/* Update total CRC */
				CRC_UPDATE(total_crc, y->support_data[x]);

				if (YSHOULD_ESCAPE(YENCODE(y->support_data[x]),linect,opt_line_length))
					linect++, y->encsize++;
				linect++, y->encsize++, offset++, bytes_this_part++;
				if (linect >= opt_line_length)
				{
					y->enclines++, lines_this_part++, linect = 0;

					/* Add a CR to total_crc to indicate line length */
					CRC_UPDATE(total_crc, '\r');

					if (lines_this_part >= opt_multipart_lines)
					{
						y->totalparts++;
						part->end = offset;
						part->size = bytes_this_part;
						y->part = (YENCPART **)xrealloc(y->part, (y->totalparts) * sizeof(YENCPART *));
						part = yencpart_create();
						y->part[y->totalparts - 1] = part;
						part->begin = offset + 1;
						lines_this_part = 0;
						bytes_this_part = 0;

						/* Add a LF to total_crc to indicate file length */
						CRC_UPDATE(total_crc, '\n');
					}
				}
			}
		}
		else
		{
			if (!(fp = fopen(y->input_filename, "r")))
				ErrERR("%s", y->input_filename);
			while ((rb = fread(buf, sizeof(unsigned char), sizeof(buf), fp)) > 0)
			{
				/* Update CRC and encoded byte count */
				for (x = 0; x < rb; x++)
				{
					CRC_UPDATE(y->crc, buf[x]);

					/* Update total CRC */
					CRC_UPDATE(total_crc, buf[x]);

					if (YSHOULD_ESCAPE(YENCODE(buf[x]),linect,opt_line_length))
						linect++, y->encsize++;
					linect++, y->encsize++, offset++, bytes_this_part++;
					if (linect >= opt_line_length)
					{
						y->enclines++, lines_this_part++, linect = 0;

						/* Add a CR to total_crc to indicate line length */
						CRC_UPDATE(total_crc, '\r');

						if (lines_this_part >= opt_multipart_lines)
						{
							y->totalparts++;
							part->end = offset;
							part->size = bytes_this_part;
							y->part = (YENCPART **)xrealloc(y->part, (y->totalparts) * sizeof(YENCPART *));
							part = yencpart_create();
							y->part[y->totalparts - 1] = part;
							part->begin = offset + 1;
							part->size = 0;
							lines_this_part = 0;
							bytes_this_part = 0;

							/* Add a LF to total_crc to indicate file length */
							CRC_UPDATE(total_crc, '\n');
						}
					}
				}
				if (do_meter)
				{
					total_bytes_read += rb;
					meter(total_bytes_read, total_bytes, _("Scanning files"));
				}
			}
			fclose(fp);
		}
		if (linect != 0)
			y->enclines++;
		part->end = offset;
		part->size = bytes_this_part;

		CRC_FINISH(y->crc);
	}
	if (do_meter)
		meter_clear();
}
/*--- calculate_encoded_info() ------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	CREATE_SUPPORT_FILES
	Precalculates CRC values for all regular input files and generates any requested support files.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
create_support_files(void)
{
	int ct;

	CRC_START(total_crc);

	/* Calculate CRC values, etc. for all regular input files */
	calculate_encoded_info(YSUPPORT_NOT_SPECIAL);

	/* Generate special file data */
	for (ct = 0; ct < num_input_files; ct++)
	{
		YENCFILE *y = input_files[ct];
		switch (y->file_type)
		{
			case YSUPPORT_IS_SFV:
				y->support_data = sfv_construct(input_files, num_input_files);
				y->filesize = strlen(y->support_data);
				Debug("%s: file generated, %s bytes", y->input_filename, comma(y->filesize));
				break;

			case YSUPPORT_IS_CRC:
				y->support_data = crc_construct(input_files, num_input_files);
				y->filesize = strlen(y->support_data);
				Debug("%s: file generated, %s bytes", y->input_filename, comma(y->filesize));
				break;

			case YSUPPORT_NOT_SPECIAL:
			default:
				break;
		}
	}

	/* Now calculate CRC, etc. for special files */
	calculate_encoded_info(YSUPPORT_IS_SFV);
	calculate_encoded_info(YSUPPORT_IS_CRC);
}
/*--- create_support_files() --------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	OUTPUT_FILE_INFO
	This is called if you pass `--info' on the command line.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
output_file_info(void)
{
	int ct, part;

	for (ct = 0; ct < num_input_files; ct++)
	{
		YENCFILE *y = input_files[ct];

		charline('-');
		printf("     input filename: %s\n", y->input_filename);
		printf("                crc: %08X\n", y->crc);
		printf("          file size: %s\n", comma(y->filesize));
		printf("  encoded data size: %s\n", comma(y->encsize));
		printf("      encoded lines: %s\n", comma(y->enclines));
		printf("        total parts: %s\n", comma(y->totalparts));
		printf("          file type: ");
		switch (y->file_type)
		{
			case YSUPPORT_IS_SFV: printf("support file - .sfv\n"); break;
			case YSUPPORT_IS_CRC: printf("support file - .crc\n"); break;
			case YSUPPORT_NOT_SPECIAL: default: printf("regular file\n"); break;
		}
		printf("       support data: %s\n", y->support_data ? "yes" : "no");
		for (part = 0; part < y->totalparts; part++)
			printf("           part %03d: begin=%lu end=%lu size=%u\n", part + 1, y->part[part]->begin,
					 y->part[part]->end, y->part[part]->size);
	}
	charline('-');
	exit(EXIT_SUCCESS);
}
/*--- output_file_info() ------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	POST_FILE
	Posts a single part file to the news server.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
post_file(YENCFILE *y)
{
	unsigned char *subject = NULL, *headers = NULL;		/* Subject and headers, current part */
	FILE *fp = NULL;												/* Input file (if reading from a file) */
	unsigned char inbuf[BUFSIZ], outbuf[BUFSIZ];			/* Input and output buffers */
	register unsigned char c;									/* Current character being encoded */
	register int ct, linect, lines, total_lines;
	register size_t written;									/* Bytes written this part */
	size_t rb;														/* Number of bytes read */
	unsigned char *reply = NULL;								/* Reply from server */
	register off_t offset;										/* Current offset into entire buffer/file */
	int orig_debug = opt_debug;								/* Original debug value (hide encdata) */
	register crc32_t pcrc;										/* CRC for this part (if multipart) */
	int	replynum = 0;											/* Numeric value of server reply */
	off_t start_offset;											/* Starting offset for this part (for retrying) */
	int	retry = 0;												/* Current retry number */
	float elapsed;													/* Elapsed time for this part */
	char	desc[80];												/* Description of current action */

	opt_debug = err_debug = 0;

	/* Open the input file (if there is one) */
	if (!y->support_data && !(fp = fopen(y->input_filename, "r")))
		ErrERR("%s", y->input_filename);

	/*
	**  Loop through each part of this file
	*/
	for (offset = 0, part_current = 1; part_current <= y->totalparts; )
	{
		/*
		**  If we are resuming, we might need to skip this file.
		**  We do not want to start posting until total_posted >= opt_resume_msg.
		*/
		if (opt_resume_msg && (total_posted+1 < opt_resume_msg))
		{
			offset += y->part[part_current-1]->size;
			subject = usenet_make_subject(y, part_current);
			Verbose("%s", subject);
			free(subject);
			Verbose("* Message skipped (resuming, already posted)");
			Verbose(" ");
			retry = 0;
			part_current++;
			total_posted++;
			continue;
		}

		/*
		**  Initialize data; generate subject and headers; send NNTP POST command and headers
		*/
		start_offset = offset;
		subject = usenet_make_subject(y, part_current);
		headers = usenet_make_headers(y, part_current);
		if (retry)
			snprintf(desc, sizeof(desc), "%s %d of %d", _("Retry"), retry, opt_retry_limit);
		else
		{
			snprintf(desc, sizeof(desc), "%s of %s", comma1(total_posted+1), comma2(total_messages));
			Verbose("%s", subject);
		}
		if (!opt_stdout)
			nntptransx("POST\n");								/* Send NNTP POST */
		sock_write(headers, strlen(headers));				/* Output headers */
		free(subject);
		free(headers);

		/*
		**  Write the yEnc "=ybegin" line
		*/
		if (y->totalparts == 1)
			sock_printf("=ybegin line=%d size=%u name=%s\n",
							opt_line_length, y->filesize, STRIP_PATH(y->input_filename));
		else
			sock_printf("=ybegin part=%d total=%d line=%d size=%u name=%s\n",
							part_current, y->totalparts, opt_line_length, y->filesize, STRIP_PATH(y->input_filename));

		/*
		**  Determine the total number of lines for the current part
		*/
		if (y->totalparts == 1)
			total_lines = y->enclines;
		else
			total_lines = (part_current < y->totalparts) ? opt_multipart_lines : (y->enclines % opt_multipart_lines);
		linect = lines = 0;

		/*
		**  Write the yEnc "=ypart" line (if this is a multipart archive)
		*/
		if (y->totalparts > 1)
			sock_printf("=ypart begin=%lu end=%lu\n", y->part[part_current-1]->begin, y->part[part_current-1]->end);

		/*
		**  Output encoded data for this part
		*/
		CRC_START(pcrc);
		written = 0;
		timer_reset(NULL);
		if (y->support_data)
		{
      	for (ct = offset; ct < y->filesize && lines < opt_multipart_lines; ct++)
			{
				offset++;
				CRC_UPDATE(pcrc, y->support_data[ct]);
				c = YENCODE(y->support_data[ct]);

	         if (YSHOULD_ESCAPE(c,linect,opt_line_length))
				{
					outbuf[linect++] = '=';
					c = YESCAPE(c);
				}
				outbuf[linect++] = c;
				if (linect >= opt_line_length)
				{
					outbuf[linect++] = '\0';
					if (!opt_stdout || (opt_stdout && !isatty(STDOUT_FILENO)))
						written += sock_puts_meter(outbuf, written, y->part[part_current-1]->size, desc);
					linect = 0;
					lines++;
				}
			}
		}
		else		/* Reading from a file */
		{
			fseek(fp, offset, SEEK_SET);
			while ((lines < opt_multipart_lines) && ((rb = fread(inbuf, sizeof(unsigned char), sizeof(inbuf), fp)) > 0))
			{
				for (ct = 0; ct < rb && lines < opt_multipart_lines; ct++)
				{
					offset++;
					CRC_UPDATE(pcrc, inbuf[ct]);
					c = YENCODE(inbuf[ct]);

					if (YSHOULD_ESCAPE(c,linect,opt_line_length))
					{
						outbuf[linect++] = '=';
						c = YESCAPE(c);
					}
					outbuf[linect++] = c;
					if (linect >= opt_line_length)
					{
						outbuf[linect++] = '\0';
						if (!opt_stdout || (opt_stdout && !isatty(STDOUT_FILENO)))
							written += sock_puts_meter(outbuf, written, y->part[part_current-1]->size, desc);
						linect = 0;
						lines++;
					}
				}
			}
		}

		/*
		**  Finished with this part - close the current line if we've output any data
		*/
		if (linect)
		{
			outbuf[linect++] = '\0';
			if (!opt_stdout || (opt_stdout && !isatty(STDOUT_FILENO)))
				sock_puts(outbuf);
		}
		CRC_FINISH(pcrc);
		elapsed = timer_elapsed(NULL);

		/*
		**  If output is going to stdout and stdout is a terminal, we didn't output any of the
		**  encoded data (to remain screen-friendly).  Output a short message explaining this.
		*/
		if (opt_stdout && isatty(STDOUT_FILENO))
		{
			puts(_("<< ENCODED DATA OMITTED SINCE OUTPUT IS TO A TERMINAL >>"));
			puts(_("<< TO ENABLE ENCODED DATA,  REDIRECT STDOUT TO A FILE >>"));
		}

		/*
		**  Write the yEnc "=yend" line
		*/
		if (y->totalparts == 1)
			sock_printf("=yend size=%u crc32=%08X\n", y->filesize, y->crc);
		else
		{
			if (part_current == y->totalparts)
				sock_printf("=yend size=%u part=%d pcrc32=%08X crc32=%08X\n", y->part[part_current-1]->size, part_current, pcrc, y->crc);
			else
				sock_printf("=yend size=%u part=%d pcrc32=%08X\n", y->part[part_current-1]->size, part_current, pcrc);
		}

		/*
		**  Finish the NNTP POST command and check the server's result
		*/
		if (!opt_stdout)
			meter_clear();
		sock_puts(".");		/* Do this even if opt_stdout, as a separator there */
		if (!opt_stdout)
		{
			if (!(reply = sock_gets()))
				Err(_("server did not reply to our command"));
			replynum = nntp_get_reply_num(reply);
			if (STATUS_ERR(replynum))
				Verbose("* %s", strtrim(reply));
			else
				Verbose("* %s (%.2fk/s)", strtrim(reply),
						  (float)((float)((float)y->part[part_current-1]->size / 1024.0) / elapsed));

			// Sleep for a couple of seconds, so that we make sure our files are posted in order
			sleep(2);
		}
		else
			Verbose("* Done");

		/*
		**  If this part was posted successfully, move on to the next part
		*/
		if (opt_stdout || !STATUS_ERR(replynum))
		{
			Verbose(" ");
			retry = 0;
			part_current++;
			total_posted++;
		}
   	else		/* An error occurred - incremement retry count, reconnect, and retry */
		{
			offset = start_offset;
			retry++;
			if (retry > opt_retry_limit)
				Err(_("posting failed; retry limit reached"));

			/* Disconnect from server and reconnect */
			nntp_disconnect();
			sleep(1);
			nntp_connect();
		}
   }

	/* Close the input file (if there was one) */
	if (!y->support_data)
		fclose(fp);
	opt_debug = err_debug = orig_debug;
}
/*--- post_file() -------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	FINISH_TOTAL_CRC
	Completes the calculation of the `total_crc' global variable, which is a checksum that may
	be optionally used when resuming a post.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
finish_total_crc(void)
{
	register unsigned char *c, *subject;
	YENCFILE *y;

	/* Finish total_crc by calculating subject lines for each message, and adding their crc to the end */
	for (file_current = 1; file_current <= num_input_files; file_current++)
	{
		y = input_files[file_current-1];
		for (part_current = 1, part_total = y->totalparts; part_current <= part_total; part_current++)
		{
			subject = usenet_make_subject(y, part_current);
			for (c = subject; *c; c++)
				CRC_UPDATE(total_crc, *c);
			free(subject);
		}
	}
	CRC_FINISH(total_crc);

	if (opt_resume_crc && (opt_resume_crc != total_crc))
		Err(_("resume checksum (%08X) does not match posting checksum (%08X)"), opt_resume_crc, total_crc);
}
/*--- finish_total_crc() ------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	CLEANUP
	If the posting has begun, this will let the user know how they can restart the post.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
cleanup(int code)
{
	int ct, special;

	if (!posting_started || !total_posted)
		exit(EXIT_FAILURE);

	for (ct = special = 0; ct < num_input_files && !special; ct++)
		if (input_files[ct]->file_type != YSUPPORT_NOT_SPECIAL)
			special++;

	fputs((isatty(STDERR_FILENO)) ? "\a\n\n" : "\n\n", stderr);

	fprintf(stderr, "%s\n\n", _("NOTICE: The program ended while messages were being posted!"));

	fprintf(stderr, _("To resume the post that was just interrupted, run %s\n"), short_progname);
	fprintf(stderr, _("again with the same arguments as before, plus the following:\n\n"));

	/* We can't list the total_crc if there were special files present, since some of them
		(like .sfv) contain data that will change on each run (the date/time), thus changing
		the crc */
	if (special)
		fprintf(stderr, "\t--resume=%d\n\n", total_posted+1);
	else
		fprintf(stderr, "\t--resume=%d,%08X\n\n", total_posted+1, total_crc);

	exit(EXIT_FAILURE);
}
/*--- cleanup() ---------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	ADD_SUPPORT_FILE
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
add_support_file(const char *filename, const char *ext, ysupportfile_t type)
{
	char	*fn;													/* Filename to use */
	char	*def = NULL;
	char	namebuf[PATH_MAX];
	int	ct;
	char	*c;
	YENCFILE *y;												/* New file */

	/* If no filename was specified, try to find a .nfo or .m3u file and take the name from that */
	if (!filename)
		for (ct = 0; ct < num_input_files; ct++)
			if ((c = strrchr(input_files[ct]->input_filename, '.')))
				if (!strcasecmp(c, ".m3u") || !strcasecmp(c, ".nfo"))
				{
					if ((c = strrchr(input_files[ct]->input_filename, '/')))
						strncpy(namebuf, c+1, sizeof(namebuf)-1);
					else
						strncpy(namebuf, input_files[ct]->input_filename, sizeof(namebuf)-1);
					if ((c = strrchr(namebuf, '.')))
						*c = '\0';
					def = namebuf;
				}
	fn = yencfile_default_filename(filename, def ? def : input_files[0]->input_filename, ext);
	if ((y = yencfile_create(fn, NULL, type, 0)))
	{
		input_files = (YENCFILE **)xrealloc(input_files, (num_input_files + 1) * sizeof(YENCFILE *));
		input_files[num_input_files++] = y;
	}
}
/*--- add_support_file() ------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	MAIN
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int
main(int argc, char **argv)
{
	register int ct;

	set_progname(argv[0]);
	err_set_exit(cleanup);

	signal(SIGWINCH, set_screen_width);
	signal(SIGHUP, cleanup);
	signal(SIGINT, cleanup);
	signal(SIGQUIT, cleanup);
	signal(SIGABRT, cleanup);
	signal(SIGTERM, cleanup);

	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	cmdline(argc, argv);
	load_ypostrc();

	/* Set default options if values were not specified */
	if (opt_timeout == -1)
		opt_timeout = DEFAULT_SOCK_TIMEOUT;
	if (opt_line_length == -1)
		opt_line_length = Y_LL;
	if (opt_multipart_lines == -1)
		opt_multipart_lines = DEFAULT_MULTIPART_LINES;
	if (opt_retry_limit == -1)
		opt_retry_limit = DEFAULT_RETRY_LIMIT;
	opt_multipart_lines -= 3;									/* Subtract 3 lines for the =y lines */

	/* Add support files if requested */
	if (opt_sfv) add_support_file(opt_sfv_filename, ".sfv", YSUPPORT_IS_SFV);
	if (opt_crc) add_support_file(opt_crc_filename, ".crc", YSUPPORT_IS_CRC);

	/* Sort the input file list */
	if (!opt_nosort)
	{
		yencfile_seed_sort_first_extensions(opt_sort_first);
		qsort(input_files, num_input_files, sizeof(YENCFILE *), yencfile_cmp);
	}

	create_support_files();										/* Create support files if requested */
	if (opt_info)
		output_file_info();

	/* Set total number of parts and files */
	part_total = file_total = total_messages = total_posted = 0;
	for (ct = 0; ct < num_input_files; ct++)
	{
		part_total += input_files[ct]->totalparts;
		file_total++;
		total_messages += input_files[ct]->totalparts;
	}
	prompt_for_missing();									/* Prompt user for any missing information */

	finish_total_crc();										/* Finish calculating total CRC */

	if (!opt_force && !opt_stdout)
		prompt_confirm_post();								/* Confirm posting */

	/* Post all files */
	nntp_connect();
	posting_started = 1;
	for (ct = 0; ct < num_input_files; ct++, file_current++)
	{
		part_total = input_files[ct]->totalparts;
		post_file(input_files[ct]);
	}
	posting_started = 0;

	return (EXIT_SUCCESS);
}
/*--- main() ------------------------------------------------------------------------------------*/

/* vi:set ts=3: */


syntax highlighted by Code2HTML, v. 0.9.1