
1) Removed the -B flag (it was stupid on my part) and added a short description indicating how to accomplish the same thing under the "Sending Mail" section of man mail(1). 2) Added a -H flag to dump the headers and exit. It takes optional flags to restrict to old, new, read, unread, and deleted messages (the later being kind of useless - it shares code with something that already had it). 3) Restored the 'Save' command which somehow got mistakenly removed in the last commit and add documentation for it! (My apologies to its author.) 4) Added a 'mkread' command to mark messages as read (the inverse of 'unread'). Should we also have a 'mknew' command? 5) Added a 'smopts' command to keep a database of addresses and sendmail options to be used when sending messages to those addresses. See man mail(1) for a fuller description. 6) Added 'indentpreamble' and 'indentpostscript' variables whose values are inserted before and after a quoted message (~m or ~M escapes). =20 7) Added string formatting abilities for the 'prompt', 'insertpreamble', 'insertpostscript', and header display strings. These strings support all the strftime() format parameters as well as many more specific to mail (see man mail(1)). 8) Fix the -a flag so that it only takes a single filename, unless "mime-attach-list" is defined. This is more conventional and avoids unexpected whitespace issues.
538 lines
10 KiB
C
538 lines
10 KiB
C
/* $NetBSD: cmd2.c,v 1.21 2006/10/31 20:07:32 christos Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 1980, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
#ifndef lint
|
|
#if 0
|
|
static char sccsid[] = "@(#)cmd2.c 8.1 (Berkeley) 6/6/93";
|
|
#else
|
|
__RCSID("$NetBSD: cmd2.c,v 1.21 2006/10/31 20:07:32 christos Exp $");
|
|
#endif
|
|
#endif /* not lint */
|
|
|
|
#include "rcv.h"
|
|
#include <util.h>
|
|
#include "extern.h"
|
|
|
|
/*
|
|
* Mail -- a mail program
|
|
*
|
|
* More user commands.
|
|
*/
|
|
static int igcomp(const void *, const void *);
|
|
|
|
/*
|
|
* If any arguments were given, go to the next applicable argument
|
|
* following dot, otherwise, go to the next applicable message.
|
|
* If given as first command with no arguments, print first message.
|
|
*/
|
|
int
|
|
next(void *v)
|
|
{
|
|
int *msgvec = v;
|
|
struct message *mp;
|
|
int *ip, *ip2;
|
|
int list[2], mdot;
|
|
|
|
if (*msgvec != 0) {
|
|
|
|
/*
|
|
* If some messages were supplied, find the
|
|
* first applicable one following dot using
|
|
* wrap around.
|
|
*/
|
|
|
|
mdot = dot - &message[0] + 1;
|
|
|
|
/*
|
|
* Find the first message in the supplied
|
|
* message list which follows dot.
|
|
*/
|
|
|
|
for (ip = msgvec; *ip != 0; ip++)
|
|
if (*ip > mdot)
|
|
break;
|
|
if (*ip == 0)
|
|
ip = msgvec;
|
|
ip2 = ip;
|
|
do {
|
|
mp = &message[*ip2 - 1];
|
|
if ((mp->m_flag & MDELETED) == 0) {
|
|
dot = mp;
|
|
goto hitit;
|
|
}
|
|
if (*ip2 != 0)
|
|
ip2++;
|
|
if (*ip2 == 0)
|
|
ip2 = msgvec;
|
|
} while (ip2 != ip);
|
|
(void)printf("No messages applicable\n");
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* If this is the first command, select message 1.
|
|
* Note that this must exist for us to get here at all.
|
|
*/
|
|
|
|
if (!sawcom)
|
|
goto hitit;
|
|
|
|
/*
|
|
* Just find the next good message after dot, no
|
|
* wraparound.
|
|
*/
|
|
|
|
for (mp = dot + 1; mp < &message[msgCount]; mp++)
|
|
if ((mp->m_flag & (MDELETED|MSAVED)) == 0)
|
|
break;
|
|
if (mp >= &message[msgCount]) {
|
|
(void)printf("At EOF\n");
|
|
return(0);
|
|
}
|
|
dot = mp;
|
|
hitit:
|
|
/*
|
|
* Print dot.
|
|
*/
|
|
|
|
list[0] = dot - &message[0] + 1;
|
|
list[1] = 0;
|
|
return(type(list));
|
|
}
|
|
|
|
/*
|
|
* Save a message in a file. Mark the message as saved
|
|
* so we can discard when the user quits.
|
|
*/
|
|
int
|
|
save(void *v)
|
|
{
|
|
char *str = v;
|
|
|
|
return save1(str, 1, "save", saveignore);
|
|
}
|
|
|
|
/*
|
|
* Save a message in a file. Mark the message as saved
|
|
* so we can discard when the user quits. Save all fields
|
|
* overriding saveignore and saveretain.
|
|
*/
|
|
int
|
|
Save(void *v)
|
|
{
|
|
char *str = v;
|
|
|
|
return save1(str, 1, "Save", NULL);
|
|
}
|
|
|
|
/*
|
|
* Copy a message to a file without affected its saved-ness
|
|
*/
|
|
int
|
|
copycmd(void *v)
|
|
{
|
|
char *str = v;
|
|
|
|
return save1(str, 0, "copy", saveignore);
|
|
}
|
|
|
|
/*
|
|
* Save/copy the indicated messages at the end of the passed file name.
|
|
* If markmsg is true, mark the message "saved."
|
|
*/
|
|
int
|
|
save1(char str[], int markmsg, const char *cmd, struct ignoretab *ignoretabs)
|
|
{
|
|
int *ip;
|
|
struct message *mp;
|
|
const char *fn;
|
|
const char *disp;
|
|
int f, *msgvec;
|
|
FILE *obuf;
|
|
|
|
msgvec = salloc((msgCount + 2) * sizeof *msgvec);
|
|
if ((fn = snarf(str, &f)) == NULL)
|
|
return(1);
|
|
if (!f) {
|
|
*msgvec = first(0, MMNORM);
|
|
if (*msgvec == 0) {
|
|
(void)printf("No messages to %s.\n", cmd);
|
|
return(1);
|
|
}
|
|
msgvec[1] = 0;
|
|
}
|
|
if (f && getmsglist(str, msgvec, 0) < 0)
|
|
return(1);
|
|
if ((fn = expand(fn)) == NULL)
|
|
return(1);
|
|
(void)printf("\"%s\" ", fn);
|
|
(void)fflush(stdout);
|
|
if (access(fn, 0) >= 0)
|
|
disp = "[Appended]";
|
|
else
|
|
disp = "[New file]";
|
|
if ((obuf = Fopen(fn, "a")) == NULL) {
|
|
warn(NULL);
|
|
return(1);
|
|
}
|
|
for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
|
|
mp = &message[*ip - 1];
|
|
touch(mp);
|
|
#ifdef MIME_SUPPORT
|
|
if (sendmessage(mp, obuf, ignoretabs, NULL, NULL) < 0) {
|
|
#else
|
|
if (sendmessage(mp, obuf, ignoretabs, NULL) < 0) {
|
|
#endif
|
|
warn("%s", fn);
|
|
(void)Fclose(obuf);
|
|
return(1);
|
|
}
|
|
if (markmsg)
|
|
mp->m_flag |= MSAVED;
|
|
}
|
|
(void)fflush(obuf);
|
|
if (ferror(obuf))
|
|
warn("%s", fn);
|
|
(void)Fclose(obuf);
|
|
(void)printf("%s\n", disp);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Write the indicated messages at the end of the passed
|
|
* file name, minus header and trailing blank line.
|
|
*/
|
|
int
|
|
swrite(void *v)
|
|
{
|
|
char *str = v;
|
|
|
|
return save1(str, 1, "write", ignoreall);
|
|
}
|
|
|
|
/*
|
|
* Snarf the file from the end of the command line and
|
|
* return a pointer to it. If there is no file attached,
|
|
* just return NULL. Put a null in front of the file
|
|
* name so that the message list processing won't see it,
|
|
* unless the file name is the only thing on the line, in
|
|
* which case, return 0 in the reference flag variable.
|
|
*/
|
|
|
|
char *
|
|
snarf(char linebuf[], int *flag)
|
|
{
|
|
char *cp;
|
|
|
|
*flag = 1;
|
|
cp = strlen(linebuf) + linebuf - 1;
|
|
|
|
/*
|
|
* Strip away trailing blanks.
|
|
*/
|
|
|
|
while (cp > linebuf && isspace((unsigned char)*cp))
|
|
cp--;
|
|
*++cp = 0;
|
|
|
|
/*
|
|
* Now search for the beginning of the file name.
|
|
*/
|
|
|
|
while (cp > linebuf && !isspace((unsigned char)*cp))
|
|
cp--;
|
|
if (*cp == '\0') {
|
|
(void)printf("No file specified.\n");
|
|
return(NULL);
|
|
}
|
|
if (isspace((unsigned char)*cp))
|
|
*cp++ = 0;
|
|
else
|
|
*flag = 0;
|
|
return(cp);
|
|
}
|
|
|
|
/*
|
|
* Delete messages.
|
|
*/
|
|
int
|
|
delete(void *v)
|
|
{
|
|
int *msgvec = v;
|
|
(void)delm(msgvec);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Delete messages, then type the new dot.
|
|
*/
|
|
int
|
|
deltype(void *v)
|
|
{
|
|
int *msgvec = v;
|
|
int list[2];
|
|
int lastdot;
|
|
|
|
lastdot = dot - &message[0] + 1;
|
|
if (delm(msgvec) >= 0) {
|
|
list[0] = dot - &message[0] + 1;
|
|
if (list[0] > lastdot) {
|
|
touch(dot);
|
|
list[1] = 0;
|
|
return(type(list));
|
|
}
|
|
(void)printf("At EOF\n");
|
|
} else
|
|
(void)printf("No more messages\n");
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Delete the indicated messages.
|
|
* Set dot to some nice place afterwards.
|
|
* Internal interface.
|
|
*/
|
|
int
|
|
delm(int *msgvec)
|
|
{
|
|
struct message *mp;
|
|
int *ip;
|
|
int last;
|
|
|
|
last = 0;
|
|
for (ip = msgvec; *ip != 0; ip++) {
|
|
mp = &message[*ip - 1];
|
|
touch(mp);
|
|
mp->m_flag |= MDELETED|MTOUCH;
|
|
mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
|
|
last = *ip;
|
|
}
|
|
if (last != 0) {
|
|
dot = &message[last - 1];
|
|
last = first(0, MDELETED);
|
|
if (last != 0) {
|
|
dot = &message[last - 1];
|
|
return(0);
|
|
}
|
|
else {
|
|
dot = &message[0];
|
|
return(-1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Following can't happen -- it keeps lint happy
|
|
*/
|
|
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* Undelete the indicated messages.
|
|
*/
|
|
int
|
|
undeletecmd(void *v)
|
|
{
|
|
int *msgvec = v;
|
|
struct message *mp;
|
|
int *ip;
|
|
|
|
for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
|
|
mp = &message[*ip - 1];
|
|
touch(mp);
|
|
dot = mp;
|
|
mp->m_flag &= ~MDELETED;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Interactively dump core on "core"
|
|
*/
|
|
int
|
|
/*ARGSUSED*/
|
|
core(void *v __unused)
|
|
{
|
|
int pid;
|
|
|
|
switch (pid = vfork()) {
|
|
case -1:
|
|
warn("fork");
|
|
return(1);
|
|
case 0:
|
|
abort();
|
|
_exit(1);
|
|
}
|
|
(void)printf("Okie dokie");
|
|
(void)fflush(stdout);
|
|
(void)wait_child(pid);
|
|
if (WCOREDUMP(wait_status))
|
|
(void)printf(" -- Core dumped.\n");
|
|
else
|
|
(void)printf(" -- Can't dump core.\n");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Clobber as many bytes of stack as the user requests.
|
|
*/
|
|
int
|
|
clobber(void *v)
|
|
{
|
|
char **argv = v;
|
|
int times;
|
|
|
|
if (argv[0] == 0)
|
|
times = 1;
|
|
else
|
|
times = (atoi(argv[0]) + 511) / 512;
|
|
clob1(times);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Clobber the stack.
|
|
*/
|
|
void
|
|
clob1(int n)
|
|
{
|
|
char buf[512];
|
|
char *cp;
|
|
|
|
if (n <= 0)
|
|
return;
|
|
for (cp = buf; cp < &buf[512]; *cp++ = (char)0xFF)
|
|
;
|
|
clob1(n - 1);
|
|
}
|
|
|
|
/*
|
|
* Add the given header fields to the retained list.
|
|
* If no arguments, print the current list of retained fields.
|
|
*/
|
|
int
|
|
retfield(void *v)
|
|
{
|
|
char **list = v;
|
|
|
|
return ignore1(list, ignore + 1, "retained");
|
|
}
|
|
|
|
/*
|
|
* Add the given header fields to the ignored list.
|
|
* If no arguments, print the current list of ignored fields.
|
|
*/
|
|
int
|
|
igfield(void *v)
|
|
{
|
|
char **list = v;
|
|
|
|
return ignore1(list, ignore, "ignored");
|
|
}
|
|
|
|
int
|
|
saveretfield(void *v)
|
|
{
|
|
char **list = v;
|
|
|
|
return ignore1(list, saveignore + 1, "retained");
|
|
}
|
|
|
|
int
|
|
saveigfield(void *v)
|
|
{
|
|
char **list = v;
|
|
|
|
return ignore1(list, saveignore, "ignored");
|
|
}
|
|
|
|
int
|
|
ignore1(char *list[], struct ignoretab *tab, const char *which)
|
|
{
|
|
char field[LINESIZE];
|
|
int h;
|
|
struct ignore *igp;
|
|
char **ap;
|
|
|
|
if (*list == NULL)
|
|
return igshow(tab, which);
|
|
for (ap = list; *ap != 0; ap++) {
|
|
istrcpy(field, *ap);
|
|
if (member(field, tab))
|
|
continue;
|
|
h = hash(field);
|
|
igp = (struct ignore *) ecalloc(1, sizeof (struct ignore));
|
|
igp->i_field = ecalloc((unsigned) strlen(field) + 1,
|
|
sizeof (char));
|
|
(void)strcpy(igp->i_field, field);
|
|
igp->i_link = tab->i_head[h];
|
|
tab->i_head[h] = igp;
|
|
tab->i_count++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Print out all currently retained fields.
|
|
*/
|
|
int
|
|
igshow(struct ignoretab *tab, const char *which)
|
|
{
|
|
int h;
|
|
struct ignore *igp;
|
|
char **ap, **ring;
|
|
|
|
if (tab->i_count == 0) {
|
|
(void)printf("No fields currently being %s.\n", which);
|
|
return 0;
|
|
}
|
|
ring = salloc((tab->i_count + 1) * sizeof (char *));
|
|
ap = ring;
|
|
for (h = 0; h < HSHSIZE; h++)
|
|
for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
|
|
*ap++ = igp->i_field;
|
|
*ap = 0;
|
|
qsort(ring, tab->i_count, sizeof (char *), igcomp);
|
|
for (ap = ring; *ap != 0; ap++)
|
|
(void)printf("%s\n", *ap);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Compare two names for sorting ignored field list.
|
|
*/
|
|
static int
|
|
igcomp(const void *l, const void *r)
|
|
{
|
|
return (strcmp(*(const char *const *)l, *(const char *const *)r));
|
|
}
|