/************************************************************************************************** $Header: /pub/cvsroot/yencode/src/ypost/ypost.c,v 1.5 2002/03/21 21:46:47 bboy Exp $ Copyright (C) 2002 Don Moore 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: */