/* ** Copyright 2000 Double Precision, Inc. See COPYING for ** distribution information. */ #if HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #if HAVE_STRINGS_H #include #endif #if HAVE_UNISTD_H #include #endif #include #include #include #include #include #include "rfc2045.h" #include "rfc2045charset.h" #if HAVE_UNISTD_H #include #endif #if HAVE_SYS_WAIT_H #include #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); }