This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
yap-6.3/packages/clib/maildrop/rfc2045/makemime.c
Vítor Santos Costa 40febfdf9b clib package
2010-06-17 00:40:25 +01:00

1001 lines
20 KiB
C

/*
** Copyright 2000 Double Precision, Inc. See COPYING for
** distribution information.
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#if HAVE_STRINGS_H
#include <strings.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <ctype.h>
#include <pwd.h>
#include <fcntl.h>
#include <signal.h>
#include "rfc2045.h"
#include "rfc2045charset.h"
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include "numlib/numlib.h"
#if HAS_GETHOSTNAME
#else
int gethostname(const char *, size_t);
#endif
static const char rcsid[]="$Id$";
struct arg_list {
struct arg_list *next;
char *arg;
} ;
/******************************************************************************
Open some file or a pipe for reading and writing.
******************************************************************************/
static FILE *openfile_or_pipe(const char *filename, const char *mode)
{
int fd;
FILE *fp;
if (strcmp(filename, "-") == 0) /* stdin or stdout */
fd=dup( strcmp(mode, "r") ? 1:0);
else if (*filename == '&')
fd=dup( atoi(filename+1)); /* file descriptor */
else fd=open(filename, (strcmp(mode, "r") ? O_WRONLY|O_CREAT|O_TRUNC:
O_RDONLY), 0666); /* or a file */
if (fd < 0)
{
perror(filename);
exit(1);
}
fp=fdopen(fd, mode);
if (!fp)
{
perror("fdopen");
exit(1);
}
return (fp);
}
/******************************************************************************
Open some file. If we get a pipe, open a temporary file, and drain pipe's
contents into it.
******************************************************************************/
static FILE *openfile(const char *filename)
{
FILE *fp=openfile_or_pipe(filename, "r");
int fd=fileno(fp);
off_t orig_pos;
if ((orig_pos=lseek(fd, 0L, SEEK_CUR)) == -1 ||
lseek(fd, 0L, SEEK_END) == -1 ||
lseek(fd, 0L, SEEK_CUR) == -1 ||
lseek(fd, orig_pos, SEEK_SET) == -1) /* Must be a pipe */
{
FILE *t=tmpfile();
int c;
if (!t)
{
perror("tmpfile");
exit(1);
}
while ((c=getc(fp)) != EOF)
putc(c, t);
if (ferror(fp) || fflush(t)
|| ferror(t) || fseek(t, 0L, SEEK_SET) == -1)
{
perror("write");
exit(1);
}
fclose(fp);
fp=t;
}
return (fp);
}
/******************************************************************************
Build argv/argc from a file.
******************************************************************************/
static void read_args(int *argcp, char ***argvp, const char *file)
{
FILE *fp=openfile_or_pipe(file, "r");
struct arg_list *argfirst=0, *arglast=0, *argp;
char buffer[BUFSIZ];
char *p;
int c;
*argcp=0;
while (fgets(buffer, sizeof(buffer), fp) != 0)
{
const char *q;
if ((p=strchr(buffer, '\n')) != 0)
*p=0;
else while ((c=getc(fp)) != '\n' && c != EOF)
; /* Just dump the excess */
/* Skip the filler. */
q=buffer;
while (*q && isspace((int)(unsigned char)*q))
++q;
if (!*q) continue;
if (*q == '#') continue;
if (strcmp(buffer, "-") == 0) break;
argp=(struct arg_list *)malloc(sizeof(struct arg_list)+1+
strlen(q));
if (!argp)
{
perror("malloc");
exit(1);
}
if (arglast)
arglast->next=argp;
else
argfirst=argp;
arglast=argp;
++*argcp;
argp->next=0;
argp->arg=strcpy((char *)(argp+1), q);
}
if ((*argvp=malloc(sizeof (char *) * (*argcp+1))) == 0)
{
perror("malloc");
exit(1);
}
c=0;
for (argp=argfirst; argp; argp=argp->next)
{
(*argvp)[c]= argp->arg;
++c;
}
(*argvp)[c]=0;
}
static void usage()
{
fprintf(stderr,
"Usage:\n"
" makemime -c type [-o file] [-e encoding] [-a \"Header: Contents\"] file\n"
" -m [ type ] [-o file] [-e encoding] [-a \"Header: Contents\"] file\n"
" -j [-o file] file1 file2\n"
" @file\n"
"\n"
" file: filename - read or write from filename\n"
" - - read or write from stdin or stdout\n"
" &n - read or write from file descriptor n\n"
" \\( opts \\) - read from child process, that generates [ opts ]\n"
"\n"
"Options:\n"
"\n"
" -c type - create a new mime section from \"file\" with this\n"
" Content-Type: (default is application/octet-stream).\n"
" -m [ type ] - create a multipart mime section from \"file\" of this\n"
" Content-Type: (default is multipart/mixed).\n"
" -e encoding - use the given encoding (7bit, 8bit, quoted-printable,\n"
" or base64), instead of guessing.\n"
" -j file1 file2 - join mime section file2 to multipart section file1.\n"
" -o file - write ther result to file, instead of stdout (not\n"
" allowed in child processes).\n"
" -a header - prepend an additional header to the output.\n"
"\n"
" @file - read all of the above options from file, one option or\n"
" value on each line.\n"
);
exit (0);
}
/******************************************************************************
The arguments are parsed into the following structure, as a tree.
******************************************************************************/
struct mimestruct {
/*
** One or two input files. We initialize either file or child,
** depending on the source being a file, or a child process.
** Later, we open a file pointer in either case.
*/
const char *inputfile1, *inputfile2;
struct mimestruct *inputchild1, *inputchild2;
FILE *inputfp1, *inputfp2;
pid_t child1, child2;
/* Output file. Defaults to "-", stdout */
const char *outputfile;
FILE *outputfp;
/* The handler and open functions */
void (*handler_func)(struct mimestruct *);
void (*open_func)(struct mimestruct *);
/* The new mime type, and encoding (-e) */
const char *mimetype;
const char *mimeencoding;
/* A list of -a headers */
struct arg_list *a_first, *a_last;
} ;
static void createsimplemime(struct mimestruct *);
static void createmultipartmime(struct mimestruct *);
static void joinmultipart(struct mimestruct *);
static void opencreatesimplemime(struct mimestruct *);
static void opencreatemultipartmime(struct mimestruct *);
static void openjoinmultipart(struct mimestruct *);
/******************************************************************************
Recursively build the mimestruct tree.
******************************************************************************/
struct mimestruct *parseargs(int *argcp, char ***argvp)
{
struct mimestruct *m=malloc(sizeof(struct mimestruct));
int argc= *argcp;
char **argv= *argvp;
if (!m)
{
perror("malloc");
exit(1);
}
memset(m, 0, sizeof(*m));
if (argc == 0 || argv[0][0] != '-') usage();
if (strncmp(argv[0], "-c", 2) == 0)
{
m->handler_func= &createsimplemime;
m->open_func= &opencreatesimplemime;
if (argv[0][2])
{
m->mimetype=argv[0]+2;
--argc;
++argv;
}
else
{
--argc;
++argv;
if (argc && argv[0][0] != '-' && argv[0][0] != ')')
{
m->mimetype=argv[0];
--argc;
++argv;
}
else
m->mimetype="application/octet-stream";
}
while (isspace((int)(unsigned char)*m->mimetype))
++m->mimetype;
}
else if (strncmp(argv[0], "-m", 2) == 0)
{
m->handler_func= &createmultipartmime;
m->open_func= &opencreatemultipartmime;
if (argv[0][2])
{
m->mimetype=argv[0]+2;
--argc;
++argv;
}
else
{
--argc;
++argv;
if (argc && argv[0][0] != '-' && argv[0][0] != ')')
{
m->mimetype=argv[0];
--argc;
++argv;
}
else
m->mimetype="multipart/mixed";
}
while (isspace((int)(unsigned char)*m->mimetype))
++m->mimetype;
}
else if (strncmp(argv[0], "-j", 2) == 0)
{
const char *filename;
m->handler_func= &joinmultipart;
m->open_func= &openjoinmultipart;
if (argv[0][2])
{
filename=argv[0]+2;
--argc;
++argv;
}
else
{
--argc;
++argv;
if (argc == 0) usage();
filename=argv[0];
--argc;
++argv;
}
while (isspace((int)(unsigned char)*filename))
++filename;
if (strcmp(filename, "(") == 0)
{
m->inputchild2=parseargs(&argc, &argv);
if (argc == 0 || strcmp(argv[0], ")"))
usage();
--argc;
++argv;
}
else
m->inputfile2=filename;
}
/* Handle common options */
while (argc)
{
if (strncmp(argv[0], "-o", 2) == 0)
{
const char *f=argv[0]+2;
++argv;
--argc;
if (*f == 0)
{
if (!argc) usage();
f=argv[0];
++argv;
--argc;
}
while (isspace((int)(unsigned char)*f))
++f;
m->outputfile=f;
continue;
}
if (strncmp(argv[0], "-e", 2) == 0)
{
char *f=argv[0]+2, *q;
++argv;
--argc;
if (*f == 0)
{
if (!argc) usage();
f=argv[0];
++argv;
--argc;
}
for (q=f; *q; q++)
*q=tolower((int)(unsigned char)*q);
while (isspace((int)(unsigned char)*f))
++f;
if (strcmp(f, "7bit") && strcmp(f, "8bit") &&
strcmp(f, "quoted-printable") &&
strcmp(f, "base64"))
usage();
m->mimeencoding=f;
continue;
}
if (strncmp(argv[0], "-a", 2) == 0)
{
char *f=argv[0]+2;
struct arg_list *a;
++argv;
--argc;
if (*f == 0)
{
if (!argc) usage();
f=argv[0];
++argv;
--argc;
}
while (isspace((int)(unsigned char)*f))
++f;
a=malloc(sizeof(struct arg_list));
if (!a)
{
perror("malloc");
exit(1);
}
if (m->a_last)
m->a_last->next=a;
else m->a_first=a;
m->a_last=a;
a->arg=f;
a->next=0;
continue;
}
break;
}
/* We must now have the input file argument */
if (!argc) usage();
if (strcmp(argv[0], "(") == 0)
{
--argc;
++argv;
m->inputchild1=parseargs(&argc, &argv);
if (argc == 0 || strcmp(argv[0], ")"))
usage();
--argc;
++argv;
}
else
{
m->inputfile1=argv[0];
--argc;
++argv;
}
*argcp=argc;
*argvp=argv;
return (m);
}
/******************************************************************************
After we're done, terminate with a zero exit code if all child processes also
terminated with a zero exit code. Otherwise, terminate with a non-zero exit
code thus propagating any child's non-zero exit code to parent.
******************************************************************************/
static void goodexit(struct mimestruct *m, int exitcode)
{
if (m->outputfp && (fflush(m->outputfp) || ferror(m->outputfp)))
{
perror("makemime");
exit(1);
}
/*
** Drain any leftover input, so that the child doesn't get
** a SIGPIPE.
*/
while (m->inputfp1 && !feof(m->inputfp1) && !ferror(m->inputfp1))
getc(m->inputfp1);
while (m->inputfp2 && !feof(m->inputfp2) && !ferror(m->inputfp2))
getc(m->inputfp2);
if (m->inputfp1)
{
if (ferror(m->inputfp1))
{
perror("makemime");
exitcode=1;
}
fclose(m->inputfp1);
}
if (m->inputfp2)
{
if (ferror(m->inputfp2))
{
perror("makemime");
exitcode=1;
}
fclose(m->inputfp2);
}
while (m->child1 > 0 && m->child2 > 0)
{
int waitstat;
pid_t p=wait(&waitstat);
if (p <= 0 && errno == ECHILD) break;
if (p == m->child1)
m->child1=0;
else if (p == m->child2)
m->child2=0;
else continue;
if (waitstat) exitcode=1;
}
exit(exitcode);
}
int main(int argc, char **argv)
{
struct mimestruct *m;
signal(SIGCHLD, SIG_DFL);
if (argc > 1 && argv[1][0] == '@')
read_args(&argc, &argv, argv[1]+1);
else if (argc > 1)
{
--argc;
++argv;
}
m=parseargs(&argc, &argv);
if (argc) usage(); /* Some arguments left */
(*m->open_func)(m);
(*m->handler_func)(m);
goodexit(m, 0);
return (0);
}
static struct mimestruct *base64m;
static void putbase64(const char *p, size_t l)
{
fwrite(p, 1, l, base64m->outputfp);
}
static void createsimplemime(struct mimestruct *m)
{
int c;
struct arg_list *a;
/* Determine encoding by reading the file, as follows:
**
** Default to 7bit. Use 8bit if high-ascii bytes found. Use
** quoted printable if lines more than 200 characters found.
** Use base64 if a null byte is found.
*/
if (m->mimeencoding == 0)
{
int l=0;
int longline=0;
long orig_pos=ftell(m->inputfp1);
if (orig_pos == -1)
{
perror("ftell");
goodexit(m, 1);
}
m->mimeencoding="7bit";
while ((c=getc(m->inputfp1)) != EOF)
{
unsigned char ch= (unsigned char)c;
if (ch >= 0x80)
m->mimeencoding="8bit";
if (ch == 0)
{
m->mimeencoding="base64";
longline=0;
break;
}
if (ch == '\n') l=0;
else if (++l > 200)
longline=1;
}
if (ferror(m->inputfp1)
|| fseek(m->inputfp1, orig_pos, SEEK_SET)<0)
{
perror("fseek");
goodexit(m, 1);
}
if (longline)
m->mimeencoding="quoted-printable";
}
for (a=m->a_first; a; a=a->next)
fprintf(m->outputfp, "%s\n", a->arg);
fprintf(m->outputfp, "Content-Type: %s\n"
"Content-Transfer-Encoding: %s\n\n",
m->mimetype, m->mimeencoding);
if (strcmp(m->mimeencoding, "quoted-printable") == 0)
{
int l=0;
int c;
while ((c=getc(m->inputfp1)) != EOF)
{
if (l > 72)
{
fprintf(m->outputfp, "=\n");
l=0;
}
if (c == '\n')
l=0;
else if (c < ' ' || c == '=' || c > 0x7F)
{
fprintf(m->outputfp, "=%02X",
(int)(unsigned char)c);
l += 3;
continue;
}
else ++l;
putc(c, m->outputfp);
}
return;
}
if (strcmp(m->mimeencoding, "base64") == 0)
{
char buf[BUFSIZ];
int l;
base64m=m;
rfc2045_base64encode_start( &putbase64 );
while ((l=fread(buf, 1, sizeof(buf), m->inputfp1)) > 0)
rfc2045_base64encode(buf, l);
rfc2045_base64encode_end();
return;
}
/* 7bit or 8bit */
while ((c=getc(m->inputfp1)) != EOF)
putc(c, m->outputfp);
}
/******************************************************************************
Satisfy paranoia by making sure that the MIME boundary we picked does not
appear in the contents of the bounded section.
******************************************************************************/
static int tryboundary(struct mimestruct *m, FILE *f, const char *bbuf)
{
char buf[BUFSIZ];
char *p;
int l=strlen(bbuf);
int c;
long orig_pos=ftell(f);
if (orig_pos == -1)
{
perror("ftell");
goodexit(m, 1);
}
while ((p=fgets(buf, sizeof(buf), f)) != 0)
{
if (p[0] == '-' && p[1] == '-' &&
strncmp(p+2, bbuf, l) == 0)
break;
if ((p=strchr(buf, '\n')) != 0)
*p=0;
else while ((c=getc(f)) != EOF && c != '\n')
;
}
if (ferror(f) || fseek(f, orig_pos, SEEK_SET)<0)
{
perror("fseek");
goodexit(m, 1);
}
return (p ? 1:0);
}
/******************************************************************************
Create a MIME boundary for some content.
******************************************************************************/
static const char *mkboundary(struct mimestruct *m, FILE *f)
{
pid_t pid=getpid();
time_t t;
static unsigned n=0;
static char bbuf[NUMBUFSIZE*4];
char buf[NUMBUFSIZE];
time(&t);
do
{
strcpy(bbuf, "=_");
strcat(bbuf, str_size_t(++n, buf));
strcat(bbuf, "_");
strcat(bbuf, str_time_t(t, buf));
strcat(bbuf, "_");
strcat(bbuf, str_pid_t(pid, buf));
} while (tryboundary(m, f, bbuf));
return (bbuf);
}
static void createmultipartmime(struct mimestruct *m)
{
const char *b=mkboundary(m, m->inputfp1);
struct arg_list *a;
int c;
if (m->mimeencoding == 0)
m->mimeencoding="8bit";
for (a=m->a_first; a; a=a->next)
fprintf(m->outputfp, "%s\n", a->arg);
fprintf(m->outputfp, "Content-Type: %s; boundary=\"%s\"\n"
"Content-Transfer-Encoding: %s\n\n"
RFC2045MIMEMSG
"\n--%s\n",
m->mimetype, b,
m->mimeencoding,
b);
while ((c=getc(m->inputfp1)) != EOF)
putc(c, m->outputfp);
fprintf(m->outputfp, "\n--%s--\n", b);
}
static void joinmultipart(struct mimestruct *m)
{
const char *new_boundary;
char *old_boundary=0;
int old_boundary_len=0;
char buffer[BUFSIZ];
char *p;
int c;
do
{
new_boundary=mkboundary(m, m->inputfp1);
} while (tryboundary(m, m->inputfp2, new_boundary));
/* Copy the header */
for (;;)
{
if (fgets(buffer, sizeof(buffer), m->inputfp2) == 0)
{
buffer[0]=0;
break;
}
if (buffer[0] == '\n' || strncmp(buffer, "--", 2) == 0)
break;
if (strncasecmp(buffer, "content-type:", 13))
{
fprintf(m->outputfp, "%s", buffer);
if ((p=strchr(buffer, '\n')) != 0) continue;
while ((c=getc(m->inputfp2)) != EOF && c != '\n')
putc(c, m->outputfp);
continue;
}
if ((p=strchr(buffer, '\n')) == 0)
while ((c=getc(m->inputfp2)) != EOF && c != '\n')
;
p=strchr(buffer+13, ';');
if (p) *p=0;
fprintf(m->outputfp, "Content-Type:%s; boundary=\"%s\"\n",
buffer+13, new_boundary);
for (;;)
{
c=getc(m->inputfp2);
if (c != EOF) ungetc(c, m->inputfp2);
if (c == '\n' || !isspace((int)(unsigned char)c))
break;
while ((c=getc(m->inputfp2)) != EOF && c != '\n')
;
}
}
do
{
if (strncmp(buffer, "--", 2) == 0)
{
if (old_boundary == 0)
{
old_boundary=malloc(strlen(buffer)+1);
if (!old_boundary)
{
perror("malloc");
exit(1);
}
strcpy(old_boundary, buffer);
if ((p=strchr(old_boundary, '\n')) != 0)
*p=0;
p=old_boundary+strlen(old_boundary);
if (p >= old_boundary+4 &&
strcmp(p-2, "--") == 0)
p[-2]=0;
old_boundary_len=strlen(old_boundary);
}
if (strncasecmp(buffer, old_boundary,
old_boundary_len) == 0)
{
if ((p=strchr(buffer, '\n')) != 0)
*p=0;
else while ((c=getc(m->inputfp2)) != '\n'
&& c != EOF)
;
c=strlen(buffer);
if (c >= 4 && strcmp(buffer+(c-2), "--") == 0)
break;
fprintf(m->outputfp, "--%s\n",
new_boundary);
continue;
}
}
fprintf(m->outputfp, "%s", buffer);
if ((p=strchr(buffer, '\n')) == 0)
while ((c=getc(m->inputfp2)) != '\n' && c != EOF)
;
} while (fgets(buffer, sizeof(buffer), m->inputfp2) != 0);
fprintf(m->outputfp, "--%s\n", new_boundary);
while ((c=getc(m->inputfp1)) != EOF)
putc(c, m->outputfp);
fprintf(m->outputfp, "\n--%s--\n", new_boundary);
goodexit(m, 0);
}
/******************************************************************************
Open input from a child process
******************************************************************************/
static FILE *openchild(struct mimestruct *parent, struct mimestruct *child,
pid_t *pidptr,
int usescratch)
{
int pipefd[2];
char buf[NUMBUFSIZE];
char buf2[NUMBUFSIZE+1];
FILE *fp;
if (pipe(pipefd) < 0)
{
perror("pipe");
exit(1);
}
*pidptr=fork();
if (*pidptr < 0)
{
perror("fork");
exit(1);
}
if (*pidptr == 0)
{
/* Duplicate pipe on stdout */
close(pipefd[0]);
close(1);
dup(pipefd[1]);
close(pipefd[1]);
/* Close any input files opened by parent */
if (parent->inputfp1) fclose(parent->inputfp1);
if (parent->inputfp2) fclose(parent->inputfp2);
/* Open, then execute the child process */
(*child->open_func)(child);
(*child->handler_func)(child);
goodexit(child, 0);
}
close(pipefd[1]);
/*
** Open the pipe by calling openfile(), automatically creating
** the scratch file, if necessary.
*/
buf[0]='&';
strcpy(buf+1, str_size_t(pipefd[0], buf2));
fp= usescratch ? openfile(buf):openfile_or_pipe(buf, "r");
close(pipefd[0]); /* fd was duped by openfile */
return (fp);
}
static void openoutput(struct mimestruct *m)
{
if (!m->outputfile)
m->outputfile="-";
m->outputfp= openfile_or_pipe(m->outputfile, "w");
}
static void openjoinmultipart(struct mimestruct *m)
{
/* number two is the multipart section */
if (m->inputchild2)
m->inputfp2=openchild(m, m->inputchild2, &m->child2, 1);
else
m->inputfp2=openfile(m->inputfile2);
if (m->inputchild1)
m->inputfp1=openchild(m, m->inputchild1, &m->child1, 1);
else
m->inputfp1=openfile(m->inputfile1);
openoutput(m);
}
static void opencreatesimplemime(struct mimestruct *m)
{
if (m->inputchild1)
m->inputfp1=openchild(m, m->inputchild1, &m->child1,
m->mimeencoding ? 0:1);
else
m->inputfp1= m->mimeencoding
? openfile_or_pipe(m->inputfile1, "r")
: openfile(m->inputfile1);
openoutput(m);
}
static void opencreatemultipartmime(struct mimestruct *m)
{
if (m->inputchild1)
m->inputfp1=openchild(m, m->inputchild1, &m->child1, 1);
else
m->inputfp1=openfile_or_pipe(m->inputfile1, "r");
openoutput(m);
}