Example #1
0
static int parse(const char *boundary, char **argv)
{
	char *line, *s, *p;
	const char *type;
	int boundary_len = strlen(boundary);
	const char *delims = " ;\"\t\r\n";
	const char *uniq;
	int ntokens;
	const char *tokens[32]; // 32 is enough

	// prepare unique string pattern
	uniq = xasprintf("%%llu.%u.%s", (unsigned)getpid(), safe_gethostname());

//bb_info_msg("PARSE[%s]", terminator);

	while ((line = xmalloc_fgets_str(stdin, "\r\n\r\n")) != NULL) {

		// seek to start of MIME section
		// N.B. to avoid false positives let us seek to the _last_ occurance
		p = NULL;
		s = line;
		while ((s=strcasestr(s, "Content-Type:")) != NULL)
			p = s++;
		if (!p)
			goto next;
//bb_info_msg("L[%s]", p);

		// split to tokens
		// TODO: strip of comments which are of form: (comment-text)
		ntokens = 0;
		tokens[ntokens] = NULL;
		for (s = strtok(p, delims); s; s = strtok(NULL, delims)) {
			tokens[ntokens] = s;
			if (ntokens < ARRAY_SIZE(tokens) - 1)
				ntokens++;
//bb_info_msg("L[%d][%s]", ntokens, s);
		}
		tokens[ntokens] = NULL;
//bb_info_msg("N[%d]", ntokens);

		// analyse tokens
		type = find_token(tokens, "Content-Type:", "text/plain");
//bb_info_msg("T[%s]", type);
		if (0 == strncasecmp(type, "multipart/", 10)) {
			if (0 == strcasecmp(type+10, "mixed")) {
				parse(xfind_token(tokens, "boundary="), argv);
			} else
				bb_error_msg_and_die("no support of content type '%s'", type);
		} else {
			pid_t pid = pid;
			int rc;
			FILE *fp;
			// fetch charset
			const char *charset = find_token(tokens, "charset=", CONFIG_FEATURE_MIME_CHARSET);
			// fetch encoding
			const char *encoding = find_token(tokens, "Content-Transfer-Encoding:", "7bit");
			// compose target filename
			char *filename = (char *)find_token(tokens, "filename=", NULL);
			if (!filename)
				filename = xasprintf(uniq, monotonic_us());
			else
				filename = bb_get_last_path_component_strip(xstrdup(filename));

			// start external helper, if any
			if (opts & OPT_X) {
				int fd[2];
				xpipe(fd);
				pid = vfork();
				if (0 == pid) {
					// child reads from fd[0]
					xdup2(fd[0], STDIN_FILENO);
					close(fd[0]); close(fd[1]);
					xsetenv("CONTENT_TYPE", type);
					xsetenv("CHARSET", charset);
					xsetenv("ENCODING", encoding);
					xsetenv("FILENAME", filename);
					BB_EXECVP(*argv, argv);
					_exit(EXIT_FAILURE);
				}
				// parent dumps to fd[1]
				close(fd[0]);
				fp = fdopen(fd[1], "w");
				signal(SIGPIPE, SIG_IGN); // ignore EPIPE
			// or create a file for dump
			} else {
				char *fname = xasprintf("%s%s", *argv, filename);
				fp = xfopen_for_write(fname);
				free(fname);
			}

			// housekeeping
			free(filename);

			// dump to fp
			if (0 == strcasecmp(encoding, "base64")) {
				decode_base64(stdin, fp);
			} else if (0 != strcasecmp(encoding, "7bit")
				&& 0 != strcasecmp(encoding, "8bit")) {
				// quoted-printable, binary, user-defined are unsupported so far
				bb_error_msg_and_die("no support of encoding '%s'", encoding);
			} else {
				// N.B. we have written redundant \n. so truncate the file
				// The following weird 2-tacts reading technique is due to
				// we have to not write extra \n at the end of the file
				// In case of -x option we could truncate the resulting file as
				// fseek(fp, -1, SEEK_END);
				// if (ftruncate(fileno(fp), ftell(fp)))
				//	bb_perror_msg("ftruncate");
				// But in case of -X we have to be much more careful. There is
				// no means to truncate what we already have sent to the helper.
				p = xmalloc_fgets_str(stdin, "\r\n");
				while (p) {
					if ((s = xmalloc_fgets_str(stdin, "\r\n")) == NULL)
						break;
					if ('-' == s[0] && '-' == s[1]
						&& 0 == strncmp(s+2, boundary, boundary_len))
						break;
					fputs(p, fp);
					p = s;
				}

/*
				while ((s = xmalloc_fgetline_str(stdin, "\r\n")) != NULL) {
					if ('-' == s[0] && '-' == s[1]
						&& 0 == strncmp(s+2, boundary, boundary_len))
						break;
					fprintf(fp, "%s\n", s);
				}
				// N.B. we have written redundant \n. so truncate the file
				fseek(fp, -1, SEEK_END);
				if (ftruncate(fileno(fp), ftell(fp)))
					bb_perror_msg("ftruncate");
*/
			}
			fclose(fp);

			// finalize helper
			if (opts & OPT_X) {
				signal(SIGPIPE, SIG_DFL);
				// exit if helper exited >0
				rc = wait4pid(pid);
				if (rc)
					return rc+20;
			}

			// check multipart finalized
			if (s && '-' == s[2+boundary_len] && '-' == s[2+boundary_len+1]) {
				free(line);
				break;
			}
		}
 next:
 		free(line);
	}

//bb_info_msg("ENDPARSE[%s]", boundary);

	return EXIT_SUCCESS;
}
Example #2
0
int popmaildir_main(int argc UNUSED_PARAM, char **argv)
{
	char *buf;
	unsigned nmsg;
	char *hostname;
	pid_t pid;
	const char *retr;
#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
	const char *delivery;
#endif
	unsigned opt_nlines = 0;

	enum {
		OPT_b = 1 << 0,		// -b binary mode. Ignored
		OPT_d = 1 << 1,		// -d,-dd,-ddd debug. Ignored
		OPT_m = 1 << 2,		// -m show ugsed memory. Ignored
		OPT_V = 1 << 3,		// -V version. Ignored
		OPT_c = 1 << 4,		// -c use tcpclient. Ignored
		OPT_a = 1 << 5,		// -a use APOP protocol
		OPT_s = 1 << 6,		// -s skip authorization
		OPT_T = 1 << 7,		// -T get messages with TOP instead with RETR
		OPT_k = 1 << 8,		// -k keep retrieved messages on the server
		OPT_t = 1 << 9,		// -t90 set timeout to 90 sec
		OPT_R = 1 << 10,	// -R20000 remove old messages on the server >= 20000 bytes (requires -k). Ignored
		OPT_Z = 1 << 11,	// -Z11-23 remove messages from 11 to 23 (dangerous). Ignored
		OPT_L = 1 << 12,	// -L50000 not retrieve new messages >= 50000 bytes. Ignored
		OPT_H = 1 << 13,	// -H30 type first 30 lines of a message; (-L12000 -H30). Ignored
		OPT_M = 1 << 14,	// -M\"program arg1 arg2 ...\"; deliver by program. Treated like -F
		OPT_F = 1 << 15,	// -F\"program arg1 arg2 ...\"; filter by program. Treated like -M
	};

	// init global variables
	INIT_G();

	// parse options
	opt_complementary = "-1:dd:t+:R+:L+:H+";
	opts = getopt32(argv,
		"bdmVcasTkt:" "R:Z:L:H:" IF_FEATURE_POPMAILDIR_DELIVERY("M:F:"),
		&timeout, NULL, NULL, NULL, &opt_nlines
		IF_FEATURE_POPMAILDIR_DELIVERY(, &delivery, &delivery) // we treat -M and -F the same
	);
	//argc -= optind;
	argv += optind;

	// get auth info
	if (!(opts & OPT_s))
		get_cred_or_die(STDIN_FILENO);

	// goto maildir
	xchdir(*argv++);

	// launch connect helper, if any
	if (*argv)
		launch_helper((const char **)argv);

	// get server greeting
	pop3_checkr(NULL, NULL, &buf);

	// authenticate (if no -s given)
	if (!(opts & OPT_s)) {
		// server supports APOP and we want it?
		if ('<' == buf[0] && (opts & OPT_a)) {
			union { // save a bit of stack
				md5_ctx_t ctx;
				char hex[16 * 2 + 1];
			} md5;
			uint32_t res[16 / 4];

			char *s = strchr(buf, '>');
			if (s)
				s[1] = '\0';
			// get md5 sum of "<stamp>password" string
			md5_begin(&md5.ctx);
			md5_hash(&md5.ctx, buf, strlen(buf));
			md5_hash(&md5.ctx, G.pass, strlen(G.pass));
			md5_end(&md5.ctx, res);
			*bin2hex(md5.hex, (char*)res, 16) = '\0';
			// APOP
			s = xasprintf("%s %s", G.user, md5.hex);
			pop3_check("APOP %s", s);
			free(s);
			free(buf);
		// server ignores APOP -> use simple text authentication
		} else {
			// USER
			pop3_check("USER %s", G.user);
			// PASS
			pop3_check("PASS %s", G.pass);
		}
	}

	// get mailbox statistics
	pop3_checkr("STAT", NULL, &buf);

	// prepare message filename suffix
	hostname = safe_gethostname();
	pid = getpid();

	// get messages counter
	// NOTE: we don't use xatou(buf) since buf is "nmsg nbytes"
	// we only need nmsg and atoi is just exactly what we need
	// if atoi fails to convert buf into number it returns 0
	// in this case the following loop simply will not be executed
	nmsg = atoi(buf);
	free(buf);

	// loop through messages
	retr = (opts & OPT_T) ? xasprintf("TOP %%u %u", opt_nlines) : "RETR %u";
	for (; nmsg; nmsg--) {

		char *filename;
		char *target;
		char *answer;
		FILE *fp;
#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
		int rc;
#endif
		// generate unique filename
		filename  = xasprintf("tmp/%llu.%u.%s",
			monotonic_us(), (unsigned)pid, hostname);

		// retrieve message in ./tmp/ unless filter is specified
		pop3_check(retr, (const char *)(ptrdiff_t)nmsg);

#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
		// delivery helper ordered? -> setup pipe
		if (opts & (OPT_F|OPT_M)) {
			// helper will have $FILENAME set to filename
			xsetenv("FILENAME", filename);
			fp = popen(delivery, "w");
			unsetenv("FILENAME");
			if (!fp) {
				bb_perror_msg("delivery helper");
				break;
			}
		} else
#endif
		// create and open file filename
		fp = xfopen_for_write(filename);

		// copy stdin to fp (either filename or delivery helper)
		while ((answer = xmalloc_fgets_str(stdin, "\r\n")) != NULL) {
			char *s = answer;
			if ('.' == answer[0]) {
				if ('.' == answer[1])
					s++;
				else if ('\r' == answer[1] && '\n' == answer[2] && '\0' == answer[3])
					break;
			}
			//*strchrnul(s, '\r') = '\n';
			fputs(s, fp);
			free(answer);
		}

#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
		// analyse delivery status
		if (opts & (OPT_F|OPT_M)) {
			rc = pclose(fp);
			if (99 == rc) // 99 means bail out
				break;
//			if (rc) // !0 means skip to the next message
				goto skip;
//			// 0 means continue
		} else {
			// close filename
			fclose(fp);
		}
#endif

		// delete message from server
		if (!(opts & OPT_k))
			pop3_check("DELE %u", (const char*)(ptrdiff_t)nmsg);

		// atomically move message to ./new/
		target = xstrdup(filename);
		strncpy(target, "new", 3);
		// ... or just stop receiving on failure
		if (rename_or_warn(filename, target))
			break;
		free(target);

#if ENABLE_FEATURE_POPMAILDIR_DELIVERY
 skip:
#endif
		free(filename);
	}

	// Bye
	pop3_check("QUIT", NULL);

	if (ENABLE_FEATURE_CLEAN_UP) {
		free(G.user);
		free(G.pass);
	}

	return EXIT_SUCCESS;
}