[net.sources] A new, original mail program.... dmail PART I of II

dillon@ucbvax.BERKELEY.EDU (Matt Dillon) (12/07/85)

	I wrote this mail program after becomming totally fed up with both
MH and standard mail....  The code is completely original.

#-----cut here-----cut here-----cut here-----cut here-----
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Place the shar file into an empty directory
# 4. Execute the file with /bin/sh (not csh) to create the files:
#	Makefile
#	README
#	commands.c
#	dmail.h
#	dmail.help
#	dmkhelp.c
#	do_lists.c
# This archive created: Fri Dec  6 23:24:16 1985
export PATH; PATH=/bin:$PATH
echo shar: extracting "'Makefile'" '(1775 characters)'
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat << \!Funky!Stuff! > 'Makefile'

#   V1.00 (first distribution)
#
#   Written by Matthew Dillon, distribution date 6 December 1985
#
#   (C)1985 by Matthew Dillon
#
#   This code is completely original.  I declare this code to be public
#   domain.  You may have this code as long as any redistributions
#   contain this and all other files in their entirety, especially these
#   comments.
#
#   Send bug reports and other gripes 
#
#   dillon@ucb-vax.berkeley.edu         ARPA NET
#   ...!ucbvax!dillon                   USENET
#
#   INSTRUCTIONS FOR COMPILING:
#
#   (1) Set DESTDIR & HELP_DIR to some global directory accessable to
#       everybody (or whatever).  Also set HELP_CHMOD and PROG_CHMOD
#       to the right thing if you don't like the defaults.
#
#   (2) Do ONE of the following:
#       make            -defaults to internal help file
#       make external   -external help file (executable is smaller)
#
#   (3) make install
#

CC = cc

CFLAGS      = 
DEST_DIR    =   /tmp
HELP_DIR    =   /tmp
HELP_FILE   =   $(HELP_DIR)/dmail.help
HELP_CHMOD  =   644
PROG_CHMOD  =   751

OBJECTS     =   globals.o main.o do_lists.o sub.o execom.o commands.o range.o \
                load_mail.o sendmail.o set.o help.o

HEADERS     =   dmail.h execom.h


internal:   dmkhelp  $(OBJECTS)
    $(CC) $(CFLAGS) -o dmail $(OBJECTS)

external: $(OBJECTS)
    $(CC) $(CFLAGS) -c -DHELPFILE=\"$(HELP_FILE)\" help.c
    $(CC) $(CFLAGS) -o dmail $(OBJECTS)

$(OBJECTS) : $(HEADERS)

help.o:		dmkhelp
    ./dmkhelp > .dmkout
    $(CC) $(CFLAGS) -c help.c

dmkhelp:    dmail.help dmkhelp.o
    $(CC) $(CFLAGS) -o dmkhelp dmkhelp.o

clean:
    rm -f *.o make.out a.out core 

install:
    cp dmail $(DEST_DIR)
    cp dmail.help $(HELP_DIR)
    chmod $(PROG_CHMOD) $(HELP_DIR)/dmail
    chmod $(HELP_CHMOD) $(DEST_DIR)/dmail.help

!Funky!Stuff!
fi # end of overwriting check
echo shar: extracting "'README'" '(2253 characters)'
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
cat << \!Funky!Stuff! > 'README'

README FILE FOR DMAIL v1.00

Read Makefile for compiling and installation procedures. 
    
Dmail compiles fine on UNIX BSD 4.2/4.3.  A man page is coming soon,
though every command as a full help page (online from Dmail).

AN EXAMPLE OF A .DMAILRC FILE: (happens to be mine)
---------------------------------------------------------------------------
alias normal    "setlist -s 18 From 38 Subject 10 To 0 Cc 0 Date"
alias from      "setlist -s 66 From; list; normal"
alias me        "select To dillon , Cc dillon"
alias bugs      "select To root staff manag , Cc staff manag root"
alias trek      "select To trek , Cc trek"
alias notme     "select -s To !dillon; resel -s Cc !dillon; resel From !dillon"
alias hack      "select To hacker , Cc hacker"
alias page      set page more
alias nopage    unset page
alias k         tag
alias kn        "tag ; next"
alias spool     "g /usr/spool/mail/dillon ~/Dmail/mbox"
alias keep      "g ~/Dmail/keep"
alias mbox      "g ~/Dmail/mbox"
alias q         "select -s all; write ~/Dmail/keep -s tag; delete -s tag; quit"
alias g         "select -s all; write ~/Dmail/keep -s tag; delete -s tag; qswi"
set amiga       "decwrl!pyramid!amiga!support"
set header      ~/.mailheader
set ask
normal
cd ~/Dmail
---------------------------------------------------------------------------

In the above example, I have created a Dmail directory to hold all my
folders.  Each folder will be a file containing multiple messages, fully
compatible with /usr/spool/ and mbox.

my dmail alias is this:
alias dmail '\dmail -O -l ~/Dmail/.dmailrc -o ~/Dmail/mbox -F Cc -F Date'

NOTE: you don't need to alias dmail to anything.  without any arguments,
it acts like /bin/Mail getting your mail from your spool, and placing 
read mail on quit to mbox in your home directory.  I use -O so dmail
gives me a command prompt even if there is no mail, and the -F options
tell dmail to load those subjects into memory automatically (because I'm
going to select on them immediately anyway).  If a field which you select
on is not in memory, dmail must go to the mail file to find the field.
This is transparent.

GOOD LUCK!

                    -Matt

                    dillon@ucb-vax.berkeley.edu
                    ...!ucbvax!dillon

!Funky!Stuff!
fi # end of overwriting check
echo shar: extracting "'commands.c'" '(11203 characters)'
if test -f 'commands.c'
then
	echo shar: will not over-write existing file "'commands.c'"
else
cat << \!Funky!Stuff! > 'commands.c'

/*
 * COMMANDS.C
 *
 *  Matthew Dillon, 6 December 1985
 *
 *  DMAIL  (C) 1985  Matthew Dillon
 *
 *  Global Routines:    DO_QUIT()
 *                      DO_EXIT() 
 *                      DO_CD()
 *                      DO_ECHO()
 *                      DO_GO()
 *                      DO_SOURCE()
 *                      DO_SHELL()
 *                      DO_WRITE()
 *                      DO_DELNEXT()
 *                      DO_NUMBER()
 *                      DO_NEXT()
 *                      DO_HEADER()
 *                      DO_TYPE()
 *                      DO_DELETE()
 *                      DO_UNDELETE()
 *                      DO_MARK()
 *
 *  Static Routines:    None.
 *
 */

#include <stdio.h>
#include <fcntl.h>
#include "dmail.h"

#define LAST_TYPE   0
#define LAST_HEADER 1

static int Last_operation;
static int Last_deleted = -1;

do_quit(garbage, com)
char *garbage;
{
    int fd, r, back;
    char *str;
    char *nulav[3];

    nulav[0] = "";
    nulav[1] = "";
    nulav[2] = NULL;
    push_break();
    if (get_inode (mail_file) == get_inode (output_file)) {
        back = save_file (0, 0, ST_DELETED | ST_STORED);
    } else {
        r = write_file (output_file, O_CREAT, ST_READ, ST_DELETED | ST_STORED);
        if (r < 0) {
            printf ("Unable to write to %s\n", output_file);
            back = save_file (0, 0, ST_DELETED | ST_STORED);
        } else {
            back = save_file (0, 0, ST_READ | ST_DELETED | ST_STORED);
        }
    }
    if (back) 
        printf ("%d  kept in %s\n", back, mail_file);
    sleep (1);
    if ((fd = open (mail_file, O_RDONLY, 0)) >= 0) {
        read (fd, Buf, 1);
        close (fd);
    }
    if (!com) 
        done (0);
    free_entry();
    if (av[1] == 0) {
        if (!Silence)
            puts ("NO FROM FILE SPECIFIED");
        av[1] = mail_file;
        av[2] = NULL;
    }
    mail_file = realloc (mail_file, strlen(av[1]) + 1);
    strcpy (mail_file, av[1]);
    str = (av[2]) ? av[2] : mail_file;
    output_file = realloc (output_file, strlen(str) + 1);
    strcpy (output_file, str);
    initial_load_mail();
    m_select (nulav, M_RESET);
    pop_break();
    if (!Silence)
        printf ("\nRF %-20s   WF %-20s\n", mail_file, output_file);
}


do_exit(garbage, com)
char *garbage;
{
    char *str;
    char *nulav[3];

    nulav[0] = "";
    nulav[1] = "";
    nulav[2] = NULL;
    if (!com)
        done (0);
    push_break();
    free_entry();
    if (av[1] == 0) {
        if (!Silence)
            puts ("NO FROM FILE SPECIFIED");
        av[1] = mail_file;
        av[2] = NULL;
    }
    mail_file = realloc (mail_file, strlen(av[1]) + 1);
    strcpy (mail_file, av[1]);
    str = (av[2]) ? av[2] : mail_file;
    output_file = realloc (output_file, strlen(str) + 1);
    strcpy (output_file, str);
    initial_load_mail();
    m_select (nulav, M_RESET);
    pop_break();
    if (!Silence)
        printf ("\nRF %-20s   WF %-20s\n", mail_file, output_file);
}


do_cd()
{
    char *dir = (ac < 2) ? home_dir : av[1];

    if (chdir(dir) < 0) {
        printf ("Cannot CD to %s\n", dir);
        return (-1);
    }
    return (1);
}


do_echo(str)
char *str;
{
    puts (next_word(str));
    return (1);
}


do_go()
{
    int i;

    if (ac < 2) {
        puts ("go to which article?");
    }
    rewind_range (1);
    i = get_range();
    if (i < 0) {
        if (!Silence)
            printf ("Message #%d does not exist\n", i);
        return (-1);
    }
    if (i == 0) {
        if (!Silence)
            puts ("No Message");
        return (-1);
    }
    Current = indexof(i);
    return (1);
}


do_source()
{
    char comline[1024];
    FILE *fi = NULL;

    if (ac < 2) {
        puts ("No file argument to source");
        return (-1);
    }
    if (push_base()) {
        push_break();
        pop_base();
        if (fi != NULL)
            fclose (fi);
        pop_break();
        return (-1);
    }
    push_break();
    fi = fopen (av[1], "r");
    pop_break();
    if (fi == NULL) {
        printf ("Cannot open %s\n", av[1]);
        return (-1);
    }
    while (fgets (comline, 1024, fi) != NULL) {
        comline[strlen(comline) - 1] = '\0';
        exec_command (comline);
    }
    push_break();
    fclose (fi);
    fi = NULL;
    pop_break();
    pop_base();
    return (1);
}


do_shell(str)
char *str;
{
    int pid, ret;
    char *shell;
    char *args;

    shell = getenv("SHELL");
    if (shell == NULL)
        shell = "/bin/sh";
    args = "-fc";
    push_break();
    str = next_word (str);
    if (strlen (str)) {
        if ((pid = vfork()) == 0) {
            execl (shell, shell, args, str, NULL);
            _exit (1);
        }
    } else {
        if ((pid = vfork()) == 0) {
            execl (shell, shell, NULL);
            _exit (1);
        }
    }
    while ((ret = wait(0)) > 0) {
        if (ret == pid)
            break;
    }
    pop_break();
    return (1);
}


do_write()
{
    char *file;
    int r, count = 0;
    register int i, j;

    if (ac < 2) {
        puts ("You must specify at least a file-name");
        return (-1);
    }
    file = av[1];
    rewind_range (2);
    push_break();
    while (i = get_range()) {
        j = indexof (i);
        if (j >= 0  &&  !(Entry[j].status & ST_DELETED)) {
            Entry[j].status |= ST_STORED | ST_SCR;
            ++count;
        }
    }
    r = write_file (file, O_CREAT, ST_SCR, 0);
    rewind_range (2);
    if (r > 0) {
        while (i = get_range()) {
            j = indexof (i);
            if (j >= 0)
                Entry[j].status &= ~ST_SCR;
        }
        if (!Silence)
            printf ("%d Items written\n", count);
    } else {
        while (i = get_range()) {
            j = indexof (i);
            if (j >= 0)
                Entry[j].status &= ~(ST_SCR | ST_STORED);
        }
        printf ("Could not write to file %s\n", file);
    }
    pop_break();
    return (1);
}


do_delnext()
{
    static int warning;

    if (!warning  &&  Last_operation == LAST_HEADER) {
        ++warning;
        puts ("Note that the next command is displaying headers only at");
        puts ("this point.  (one-time warning, NOTHING deleted");
        return (-1);
    }
    if (do_mark("", ST_DELETED) > 0)
        return (do_next("", 1));
    return (-1);
}


do_number(str, com)
char *str;
int com;
{
    int x;

    x = indexof (atoi(str));
    if (x < 0) {
        puts ("Non existant message");
        return (-1);
    }
    Current = x;
    switch (Last_operation) {
    case LAST_TYPE:
        return (do_type());
    case LAST_HEADER:
        return (do_header());
    default:
        puts ("Internal Error NEXT");
        return (-1);
    }
}


do_next(str, com)
char *str;
{
    int ok;

    push_break();
    if (com > 0) {
        if (++Current > Entries)
            Current = Entries;
        if (fix() < 0) {
            puts ("End of file");
            pop_break();
            return (-1);
        }
        --com;
    }
    if (com < 0) {
        ++com;
        ok = 0;
        while (--Current >= 0) {
            if (Entry[Current].no  &&  !(Entry[Current].status & ST_DELETED)) {
                ok = 1;
                break;
            }
        }
        if (!ok) {
            puts ("Start of file");
            Current = 0;
            fix();
            pop_break();
            return (-1);
        }
    }
    pop_break();
    if (!com) {
        switch (Last_operation) {
        case LAST_TYPE:
            return (do_type());
        case LAST_HEADER:
            return (do_header());
        }
    }
    return (1);
}


do_header()
{
    char buf[1024];

    Last_operation = LAST_HEADER;
    if (push_base()) {
        push_break();
        pop_base();
        PAGER (-1);
        fseek (m_fi, 0, 0);
        fflush (stdout);
        pop_break();
        return (-1);
    }
    if (single_position() < 0)
        return (-1);
    PAGER (0);
    sprintf (Puf, "MESSAGE HEADER #%d (%d) %s\n",
            Entry[Current].no, 
            Current + 1,
            (Entry[Current].status & ST_DELETED) ? "  DELETED" : "");
    PAGER (Puf);
    sprintf (Puf, "From %s\n", Entry[Current].from);
    PAGER (Puf);
    while (fgets (buf, 1024, m_fi) != NULL) {
        FPAGER (buf);
        if (*buf == '\n') {
            PAGER (-1);
            pop_base();
            return (1);
        }
    }
    PAGER ("END OF FILE ENCOUNTERED");
    PAGER (-1);
    pop_base();
    return (-1);
}


do_type()
{
    char buf[1024];
    int i;

    Last_operation = LAST_TYPE;
    if (push_base()) {
        push_break();
        pop_base();
        PAGER (-1);
        fseek (m_fi, 0, 0);
        fflush (stdout);
        pop_break();
        return (-1);
    }
    if (single_position() < 0)
        return (-1);
    if (skip_to_data (m_fi) < 0) {
        printf ("Cannot find data for message %d\n", Entry[Current].no);
        return (-1);
    }
    PAGER (0);
    sprintf (Puf, "MESSAGE TEXT #%d (%d) %s\n", 
            Entry[Current].no, 
            Current + 1,
            (Entry[Current].status & ST_DELETED) ? "  DELETED" : "");
    PAGER (Puf);
    for (i = 0; i < Listsize; ++i) {
        if (*Entry[Current].fields[header[i]]) {
            sprintf (Puf, "%-10s %s", 
                    Find[header[i]].search,
                    Entry[Current].fields[header[i]]);
            PAGER (Puf);
        }
    }
    PAGER ("");
    while ((fgets (buf, 1024, m_fi) != NULL)  &&  strncmp (buf, "From ", 5)) 
        FPAGER (buf);
    Entry[Current].status |= ST_READ;
    PAGER (-1);
    pop_base();
    return (1);
}


do_mark(garbage, mask)
char *garbage;
{
    int count = 0;
    register int i, j;

    rewind_range (1);
    push_break();
    while (i = get_range()) {
        j = indexof (i);
        if (j >= 0) {
            if (mask & ST_DELETED)
                Last_deleted = j;
            if ((Entry[j].status & mask) != mask) {
                Entry[j].status |= mask;
                if (Entry[j].status & ST_DELETED)
                    Entry[j].status &= ~(ST_STORED | ST_READ | ST_TAG);
                ++count;
            }
        }
    }
    if (!Silence)
        printf ("%d  Items\n", count);
    pop_break();
    return (1);
}


do_unmark(garbage, mask)
char *garbage;
{
    int count = 0;
    register int i, j;
    register struct ENTRY *en;

    push_break();
    if (ac == 1 && (mask & ST_DELETED) && Last_deleted != -1)  {
        en = &Entry[Last_deleted];
        if (en->no) {
            en->status &= ~mask;
            printf ("Undeleted last deleted message (# %d)\n", en->no);
            Current = Last_deleted;
            Last_deleted = -1;
        } else {
            puts ("Last deleted message not within current select bounds");
            pop_break();
            return (-1);
        }
        pop_break();
        return (1);
    }
    rewind_range (1);
    while (i = get_range()) {
        j = indexof (i);
        if (j >= 0) {
            if (Entry[j].status & mask) {
                Entry[j].status &= ~mask;
                ++count;
            }
        }
    }
    if (!Silence)
        printf ("%d  Items\n", count);
    pop_break();
    return ((count) ? 1 : -1);
}


!Funky!Stuff!
fi # end of overwriting check
echo shar: extracting "'dmail.h'" '(2465 characters)'
if test -f 'dmail.h'
then
	echo shar: will not over-write existing file "'dmail.h'"
else
cat << \!Funky!Stuff! > 'dmail.h'

/*
 * DMAIL.H
 */

#define MAXTYPE      16     /* Max number of different fields remembered     */
#define EXSTART      3      /* Beginning of dynamic fields, rest are wired   */
#define MAXLIST      16     /* Maximum # list elements in SETLIST            */
#define LONGSTACK    64     /* Maximum # levels for the longjump stack       */
#define MAILMODE     0600   /* Standard mail mode for temp. files            */
#define MAXFIELDSIZE 4096   /* Maximum handlable field size (& scratch bufs) */

#define LEVEL_SET    0      /* which variable set to use                     */
#define LEVEL_ALIAS  1
#define LEVEL_MALIAS 2

#define R_INCLUDE   1       /* Include message      For DO_REPLY()  */
#define R_FORWARD   2       /* Forward message      */
#define R_REPLY     3       /* Reply to message     */
#define R_MAIL      4       /* Mail from scratch    */

#define M_RESET     0
#define M_CONT      1


#define PAGER(Puf)      _pager(Puf, 1)      /* Auto newline */
#define FPAGER(Puf)     _pager(Puf, 0)      /* output as is */
#define push_base()     (setjmp (env[1 + Longstack]) ? 1 : (++Longstack, 0))
#define pop_base()      --Longstack
#define push_break()    ++Breakstack
#define pop_break()     --Breakstack

#define ST_DELETED  0x0001  /* Status flag.. item has been deleted  */
#define ST_READ     0x0002  /* item has been read or marked         */
#define ST_STORED   0x0010  /* item has been written                */
#define ST_TAG      0x0020  /* item has been taged                  */
#define ST_SCR      0x0080  /* scratch flag to single out messages  */

#include <stdio.h>
#include <setjmp.h>

struct ENTRY {
    long fpos;
    int  no;
    int  status;
    char *from;
    char *fields[MAXTYPE];
};

static struct FIND {
    char *search;
    int  len;
    int  notnew;
};

extern char *getenv(), *malloc(), *realloc(), *next_word(), *get_field();
extern char *alloca();
extern char *get_var();

extern char *mail_file;
extern char *user_name;
extern char *output_file;
extern char *home_dir;
extern char *visual;
extern char Buf[];
extern char Puf[];
extern char *av[];
extern int  _ls, Longstack, Breakstack;
extern int  Debug;
extern int  Entries, Current;
extern int  Silence;
extern int  ac;
extern FILE *m_fi;
extern struct ENTRY *Entry;
extern struct FIND  Find[];
extern jmp_buf env[];

extern int width[], header[], Listsize;
extern int No_load_mail;

extern char *S_sendmail;
extern int S_page, S_novibreak, S_verbose, S_ask;

!Funky!Stuff!
fi # end of overwriting check
echo shar: extracting "'dmail.help'" '(18932 characters)'
if test -f 'dmail.help'
then
	echo shar: will not over-write existing file "'dmail.help'"
else
cat << \!Funky!Stuff! > 'dmail.help'

'help TOPIC' for more information on a command.  Many commands take 
message numbers or message lists:
    [msg]   is an optionaly specified message number (usually the 
        operation is on the current message if no number is
        specified)
    <list>  is an optionaly specified message list.  Message lists
        consists of number ranges of the form N -N N- or N-N, and
        keywords (help keywords)


OTHER HELP AVAILABLE:   pager sendmail tilde header newmail keywords

DMAIL written by Matthew Dillon, U.C. Berkeley -UCF (C) 1985 by Matthew Dillon


.pager
set page [rows/command]

    'page' is a SET variable which determines the type of paging list and
display commands will use.  If not defined, no filter is used.  If a numerical
value (i.e. 24) is specified, the page length will be set to that value and
a rather stupid, simple internal paging routine will be used.  If the 
variable is set to an alpha-numeric string, output will be piped through
that filter.  For instance:

    unset page      -no paging
    set page more       -use more filter
    set page page       -use 'page' filter
    set page > x        -redirect to a file (be careful)
    set page 24     -use internal paging, page length 24 rows.

    For internal paging, use <return> to continue, or your INTR 
    character to break out

.sendmail
set sendmail sendmail-path

    Inform DMAIL as to where the sendmail program is.  The default
(variable unset) is /usr/lib/sendmail.  This variable is useful only for
those of us who like to hack-up our own sendmail.

.tilde
~ ~user directory expansion

    In all expressions except those within double quotes, the tilde
`~` will be expanded to either your home directory, or the directory of
a specified user, depending.  Note that '*' and '?' are not expanded by
DMAIL, though they will be by any shell commands you execute.

    It is probably a good idea to use ~ in any aliases, etc... in case
you change directories using the 'cd' command.

    alias resource ~/.dmailrc       -example using ~

.header
set header filepath

    Set the location of your header file, which will appended to the
scratch mail file before you are placed in the editor (usually vi).

    set header ~/.header            -set header file to ~/.header
    unset header                -no header file

.newmail

    Whenever newmail arrives, it will be automatically incorporated into
a running DMAIL.  However, to see it, you must 'select all' (or select on
anything that would include it).

.keywords
.range
.message

    Many commands in DMAIL require a range of messages be given.  A Range
consists of message numbers (3 4 5), message ranges (1-45 -9 9-), and 
keywords.  Keywords select certain messages from the entire SELECTED list:

    all     -All messages
    tag     -All TAGGED messages 
    mark        -All MARKED (read) messages
    deleted     -All DELETED messages
    written     -All WRITTEN messages 
    untag       -All messages NOT TAGGED 
    unmark      -All messages NOT MARKED (i.e. read)
    undeleted   -All messages NOT DELETED
    unwritten   -All messages NOT WRITTEN

    Only the first three letters need be specified.  For instance, the
'all' keyword selects all the messages currently selected.  You could select
on some subject, say, and the 'delete all'. 

    The message number 0 refers to the last message. the 'undeleted'
keyword exists only for completeness; you will probably never use it.


.delete
DELETE <message list>

    Mark the specified messages for deletion.  They will no longer show up
on LISTings, (gaps will appear in message numbering).  However, you can
still TYPE them, if you remember the message number, and you can always
UNDELETE them.  Remember that the particular message # you've deleted  
may be different if you change the SELECT parameters.  For example,    
message #3 selecting 'To' & 'foo' may actually be message #45 when you 
are selecting ALL (see SELECT).  Upon a QUIT, messages marked for
deletion are actually deleted from the mail file.

.undelete
UNDELETE   <message list>

    UNDELETES messages.  Without arguments, UNDELETE will 
restore the last message you deleted.  Specifying 'all' (undelete all), will
undelete any deleted messages in the currently selected message list.

.header
HEADER [message]

    Display the entire header of a message.  This does not cause the
message to be marked 'read'.  TYPE, on the other hand, only displays
header information specified by SETLIST.

.type
TYPE [message]

    Type the text of a message.  Only header fields defined by SETLIST
are displayed.  Otherwise, only the text is displayed.  This marks 
the particular message as 'read', and also makes that message the 
current message.

.echo
ECHO [string]

    Echo the given string onto the screen.

.go
GO #
    go to a message, don't type it out or anything.  Remember that you
can go to the last message by using the message # 0.  By placing a keyword
(help range), you can go to the first TAGGED message, etc...

.reply
.Reply
REPLY

    Reply to the current letter.  There are two forms of 'reply'.  The
first does not include the senders original letter, the second does.
In both cases, Dmail will place you in VI, with the To:, Cc:, and
subject lines filled out.  The second form is 'Reply', with an 
upper case 'R'.  This form includes the sender's message shifted to
the right with '>'s on the left hand side.  See FORWARD for another
method of replying to mail.

    In any case, you may get the sender's letter by reading the file '#'
from VI.  That sequence would be ':r\\#' 

    See MAIL for more information on VI

.forward
FORWARD [user user user....]

    Forward the current message to a list of users.  The sender's
entire message is placed in the text portion.  The To: field will
contain the user's named above, and the Subject: field will contain
a 'Fo:' (you append your own subject)

    See MAIL for more information on VI

.mail
MAIL [user user user user]

    Mail to [users].  You are given a VI to work from, and may modify
any of the header fields.  the From: field is inserted automatically
by SENDMAIL, but you can overide it if you wish.  

    Quitting out of VI without writing the output file will cause an
abort, and no mail will be sent.   Additionaly, you may use the 'vibreak'
variable to enable your INTR character (usually CTL-C) to break you out of
VI.

    When modifying the users list in To and Cc fields, remember that 
they should be all comma delimited, or none comma delimited.

.select
SELECT ALL
SELECT Field  match match match match...
SELECT Field  !match
SELECT Field  match match match , Field match , .....

    Select what you want to look at.  Select will take the field header
you supply and attempt to match arguments with that field in the mail
file.  You can then work on the selected material as if the rest of
your mail didn't exist.  For instance, 'select To dillon', will select
all messages addressed to you.  Note that case is checked for the
FIELD-HEADER, but not for arguments, so the latter will also find 
anything addressed to Dillon or DILLON.  You only have to give a
partial match, so 'select To di' would work just as well. 

    Select then, allows you to quickly find what you want even though
you may have 12000 messages (though it may take a while with that many)
You may also specify what you DON'T want to select on:

 select To !foo

will select all letters not addressed to 'foo'.  You may select on any
field you wish.  At any time, you may say 'select ALL' to select the
entire message list.  Use RESELECT to select on the current message
list.  SELECT always selects from the entire message-list

 select Cc hack , To hack

will select any mail with Cc or To fields containing hack.  You may
have as many comma operators as you wish.  The comma must be a field
of its own (spaces on either side)

.reselect
RESELECT ALL
RESELECT Field  match match match match...
RESELECT Field  !match

    SEE SELECT.  Reselect allows you to CONTINUE to narrow down a topic
or whatever.  It will select on the current message list (which you have
already narrowed down with SELECT or RESELECT).

.defer
DEFER

    Deselects any marked messages .. messages marked as 'read'.  This is
as if you did a RESELECT on all unread messages in the current select field.
Thus, the messages will be renumbered.  To see these messages again, you must
use SELECT.

.list
LIST <message list>
LIST            -Lists all selected messages

    Display header information on a message as specified by SETLIST,
in a one line per message format.  The default lists ALL messages.

Leftword flags:    r    -indicates message has been read.  Message will be
                         moved to the destination file on QUIT
                   >    -indicates message is the current message
                   w    -indicates message has been written to a file.
                         Message will be deleted from source file on QUIT
           T    -indicates message has been taged by the user

.next
NEXT

    Execute TYPE or HEADER for the next message, depending on which of
TYPE or HEADER was last executed by you

._next
_NEXT

    Go to next message, do not print it out.

.back
BACK

    Execute TYPE or HEADER for the previous message, depending on which
of TYPE or HEADER was last executed by you

._back
_BACK

    Go to previous message, do not print it out.

.dt
DT
    Delete current message, type (or header) next message.  This command
will warn you when you reach the end of the message list.

    References: DELETE and NEXT

.set
SET [variable [string]]

    With no arguments, SET prints out all currently active variables.
Note that this variable list is a different list than the ALIAS list.  With
one argument, the specified variable is displayed if it exists, or created
if it doesn't.  With more than one argument, the specified variable is set
to the specified string.  Variables may be references on the command line by
$variable .  The variable's contents will replace the reference.  Unlike
aliases, however, variable substitutions may take place anywhere on the 
command line rather than substitute just the command name.  Note also that
if you use a $ substitution for an argument of a command, the entire 
variable's contents is ONE argument (i.e. if a = "b c d", and you say
something like: 'unset $a', it would attempt to unset a single variable
whos name is "b c d".

    There are several reserved SET variables, which define options in 
DMAIL.  Changing these will modify the option:

    page        set paging on or to a specific command (i.e. more)
    sendmail    set the path to the sendmail program
    vibreak     enable your INTR character even when in VI.
    verbose     reflects verbose option to sendmail
    header      header file to append to any messages you send.

    page

     This variable determines what kind of paging is used for LIST,
     TYPE, and HEADER commands.  If the variable does not exist, paging
     is turned off.  If set to null (no string), an internal paging
     routine is used.  If set to a value, an internal paging routine is
     used using the value as the page length.  the 'page' variable can
     also be set to a command, such as 'more' or 'page', in which case,
     the output is piped through those commands:

     set page       Turn paging on (internal page routine)
     set page 25        Internal page routine... 25 rows/screen
     set page more      Use 'more' command to pipe output through
     set page page      Use 'page' command to pipe output through

     you could also conceviably say:  'set page cat > x', or 
     'set page cat | lpr', but be very careful.

    sendmail

     This variable will redirect DMAIL as to where the mailer program
     is.  The mailer program must be compatible with /usr/lib/sendmail
     which is the default used if the 'sendmail' variable isn't set
     to anything:

     set sendmail bin/mysendmail

    vibreak

     This variable, when set, allows the INTR character to abort a 
     reply, mail, or forward command.  Otherwise, if this variable is
     not present, INTR will not abort the above commands.

    verbose

     This variable, when set, causes the -v flag to be sent to 
     sendmail.  In addition, DMAIL will wait for sendmail to complete
     before returning your prompt.

    header

     The file specified by this variable will be appended onto the temp
     vi files in reply and mail.  The file is appended before you go into
     vi, so when you do, what you see is still what you get.

.alias
ALIAS [variable [string]]

    Create an alias for a command.  With no arguments, ALIAS will display
all active aliases.  With one argument, a particular alias is displayed (if
it exists), or defined (if it did not previously exist).  With more than one
argument, the particular alias is set to the string list specified.

alias
alias hack "select From hacker , To hacker , Cc hacker"
alias bye quit
alias stuff "setlist 60 To ; list"

    to unalias an alias, use the UNALIAS command.

.unset
.unalias
UNSET var var var...
UNALIAS var var var...

    Eradicate variables or aliases from memory.

.setlist
SETLIST [-s] [columns] Field [columns] Field ...

    -s prevents display of the list.
Set the list format used for LIST and TYPE.  The optional [columns]
indicates how many columns to allocate for the Field specified.  The 
Field can be a partial match. However, case is observed:

setlist 18 Fro  38 Sub  10 To  0 Dat

18 columns for the From: field, etc... when TYPEing messages, the
[columns] is ignored, and each field is printed in its entirety.
Note that 0 columns have been allocated for the Date: field.  
Therefore, the Date: field will not show up on the LIST command,
but will show up in the TYPE command.

.cd
CD PATH

    cd, as in csh.  Changes your base directory.  You can use
 the shell escape '! pwd' to get your current working directory.

.source
SOURCE file

    Source a file.  The file is read in an executed in sequence.

.preserve
PRE <message list>

    PRESERVE messages.  A message is MARKED if it has been read (has an 'r'
flag from the LIST command).  Marked messages are moved from your readfile
into your outfile upon a QUIT.  If you are reading and writing to the same
file (i.e. from your mbox to your mbox), the 'r' flag has no effect. 

    However, if you are reading from your spool file, and want to keep
read messages in your spool (that is, not move them to your mbox), you want
to PRESERVE them.  This command simply unmarks them, so they appear not to
have been read.

.mark
MARK <message list>

    Mark messages specified as being already 'read'.  Remember that if 
you executed DMAIL without a -f option, any message 'read' at the time
you quit will be moved to MBOX (or file specified by -o)

.tag
.untag
TAG <list>
UNTAG <list>

    The TAG command allow you to flag any message.  You can tag a set of
messages, then reference them all at once.  For instance, if you tag 
interesting messages as you glance at them, you may then write them all
to a file by 'write filename tag',  or list them with 'list tag'.  
Alternately, you could delete all your taged messages with a single delete
command 'delete tag'.  The 'tag' operand works in the same way as the 'all'
operand, except it works only on taged messages.

    UNTAG will untag a particular message in your message list.  For
instance, to untag any taged messages in the entire message list, you would:

 select all
 untag all  OR   untag tag

    Note that 'untag all' and 'untag tag' have the same effect.

.write
WRITE file <message list>

    Write the given messages or the current message to a file.  The file
file is appended to.  Remember that you may specify 'all' to write
all messages in the current select field to the file.  Messages will be
marked as having been writen, and will be deleted from the mail file
when you 'quit'.  However, you may cause them to be kept in the mail
file by UNdeleting the messages (i.e.  undelete all)

    You can also TAG the messages you want to write, and say
'write file tag' to write to the file all taged messages.

.!
! [shell command]

    Give yourself a shell or execute a shell command.  The shell forked
is that specified by your SHELL enviroment variable, or /bin/sh if there is
no SHELL enviroment variable.

.x
X (EXIT)

    EXIT out of DMAIL without changing any files.  Usually, one exits
with QUIT, which would cause deleted messages to disappear, and TYPEd
messages to go to MBOX (if you did not use the -f option with DMAIL).

    If your outfile is the same as your infile, reading a message does
not effect anything.

.quit
QUIT

    Quit out of DMAIL. Delete any messages that were marked for deletion
and if you executed DMAIL on /usr/spool/mail/ (default), any mail
marked 'read' will be placed in MBOX in your home directory

.xswitch
.qswitch
XSWITCH fromfile [tofile]
QSWITCH fromfile [tofile]

    Switch to a different set of files.  XSWITCH doesn't modify your
old from and to files before switching, QSWITCH works as if you had QUIT
stuff before switching to another set of files.

    If no [tofile] is specified, the new tofile will be the same as the
fromfile you specify.

.help
.?
HELP [topic]

    Give me help on a topic

.dmail
COMMAND LINE OPTIONS FOR DMAIL

    dmail -O [-l rcfile] -f [file] -o [file] -F field -F field -F field ...

    Default conditions:
        Home directory gotten from password entry
        User gotten from password entry
        Visual editor set to /usr/ucb/vi

        VI BREAKOUT enabled
        READ file is /usr/spool/mail/$USER
        WRITE file is $HOME/mbox
        From:,  To:, and Subject: fields will be loaded into memory.

    HOME    enviroment variable becomes home directory
    USER    enviroment variable becomes user name
    VISUAL  enviroment variable becomes editor used

    -O          Go into interactive mode, even if there is no
                mail to read.

    -f [from file]      Specify spool file to get mail from. If no file
                Argument is given, $HOME/MBOX is used.

    -o [to file]        Specify file to write to, If no Argument
                is given, $HOME/.MBOX is used.  Note that
                the default without -o is $HOME/MBOX

    -f -o           With no file arguments causes both READ and 
                WRITE files to be $HOME/.MBOX

    -F field        Append this field to those which will be
                used on initial load.  If, During usage of the
                program you specify a field which is not in 
                memory, DMAIL will be forced to re-load the
                entire spool file, which can take a long time
                if you have more than 64K in the file

    -l rcfile       Use this as the rc file rather than .dmailrc

    -L          Do not source the rc file on boot


!Funky!Stuff!
fi # end of overwriting check
echo shar: extracting "'dmkhelp.c'" '(1094 characters)'
if test -f 'dmkhelp.c'
then
	echo shar: will not over-write existing file "'dmkhelp.c'"
else
cat << \!Funky!Stuff! > 'dmkhelp.c'

/*
 * DMKHELP.C
 *
 *
 *  Matthew Dillon, 6 December 1985
 *
 *  (C) 1985  Matthew Dillon
 *
 *  Standalone C source.
 *
 *  Takes the file DMAIL.HELP (or that specified), and puts quotes and 
 * commas around each line, the output to stdout.  Used by Makefile to
 * place the help file on line (by making it a static array).  See the
 * Makefile.
 *
 */

#include <stdio.h>

#define HELPC "dmail.help"

static char buf[1024];

main(argc, argv)
char *argv[];
{
    FILE *fi;
    char *ptr;
    int len;
    register int i;

    if (argc == 1)
        fi = fopen (HELPC, "r");
    else
        fi = fopen (argv[1], "r");
    if (fi == NULL) {
        puts ("CANNOT OPEN");
        exit (1);
    }
    while (fgets (buf, 1024, fi)) {
        len = strlen(buf) - 1;
        buf[len] = '\0';
        putchar ('\"');
        for (i = 0; i < len; ++i) {
            if (buf[i] == '\"') {
                putchar ('\\');
                putchar ('\"');
            } else {
                putchar (buf[i]);
            }
        }
        puts ("\",");
    }
    puts ("NULL");
    fclose (fi);
}


!Funky!Stuff!
fi # end of overwriting check
echo shar: extracting "'do_lists.c'" '(4118 characters)'
if test -f 'do_lists.c'
then
	echo shar: will not over-write existing file "'do_lists.c'"
else
cat << \!Funky!Stuff! > 'do_lists.c'

/*
 *  DO_LISTS.C
 *
 *  Matthew Dillon, 6 December 1985
 *
 *
 *  (C) 1985  Matthew Dillon
 *
 *  Global Routines:    DO_SETLIST()
 *                      DO_LIST()
 *                      DO_SELECT()
 *                      DO_DEFER()
 *
 *  Static Routines:    None.
 *
 *      LIST associated commands.
 *
 */

#include <stdio.h>
#include "dmail.h"


do_setlist(str)
char *str;
{
    int i, fw, idx, localecho = 1;
    int sac = 1;

    push_break();
    if (strcmp (av[sac], "-s") == 0) {
        ++sac;
        localecho = 0;
    }
    hold_load();
    if (ac > sac) {
        Listsize = 0;
        for (i = sac; i < ac; ++i) {
            fw = atoi(av[i]);
            if (fw > 4096)
                fw = 4096;
            if (fw < 0  ||  (*av[i] < '0'  ||  *av[i] > '9'))
                fw = 20;
            else
                ++i;
            if (i >= ac)
                continue;
            idx = get_extra_ovr (av[i]);
            if (idx < 0) {
                printf ("To many entries, cannot load: %s\n", av[i]);
                fflush (stdout);
                continue;
            }
            header[Listsize] = idx;
            width[Listsize] = fw;
            ++Listsize;
        }
    }
    nohold_load();
    pop_break();
    if (localecho) {
        puts ("");
        printf ("Entry   Width   Field\n\n");
        for (i = 0; i < Listsize; ++i) 
            printf ("%-6d   %-5d   %s\n", i, width[i], Find[header[i]].search);
        puts ("");
    }
    return (1);
}


/*
 * Pre-position #   0 >     Current article
 *                  1 -     Read already
 *
 */


do_list()
{
    int i, j;
    int start = 0;
    int end   = Entries;
    static char curr[10] = { "    " };

    if (ac == 1) {
        av[1] = "all";
        ++ac;
    }
    if (push_base()) {
        push_break();
        pop_base();
        PAGER (-1);
        pop_break();
        return (-1);
    }
    PAGER (0);
    FPAGER ("\n         ");
    for (j = 0; j < Listsize; ++j) {
        if (width[j]) {
            sprintf (Puf, "%-*.*s",
                    2 + width[j],
                    2 + width[j],
                    Find[header[j]].search);
            FPAGER (Puf);
        }
    }
    FPAGER ("\n");
    rewind_range (1);
    while (i = get_range()) {
        i = indexof(i);
        if (Entry[i].no  &&  ((Entry[i].status & ST_DELETED) == 0)) {
            curr[0] = (Entry[i].status & ST_TAG) ? 'T' : ' ';
            curr[1] = (i == Current) ? '>' : ' ';
            curr[2] = (Entry[i].status & ST_READ)    ? 'r' : ' ';
            curr[3] = (Entry[i].status & ST_STORED)  ? 'w' : ' ';
            sprintf (Puf, "%s%-3d", curr, Entry[i].no);
            FPAGER (Puf);
            for (j = 0; j < Listsize; ++j) {
                if (width[j]) {
                    sprintf(Puf, "  %-*.*s",
                            width[j], 
                            width[j],
                            Entry[i].fields[header[j]]);
                    FPAGER (Puf);
                }
            }
            FPAGER ("\n");
        }
    }
    FPAGER ("\n");
    PAGER (-1);
    pop_base();
    return (1);
}



do_select(str, mode)
char *str;
{
    int ret = 1;
    int localecho = 1;
    int avi = 1;
    int scr;
    char *nulav[3];

    nulav[0] = "";
    nulav[1] = "";
    nulav[2] = NULL;
    if (strcmp (av[avi], "-s") == 0) {
        localecho = 0;
        ++avi;
        --ac;
    }
    switch (ac) {
    case 2:
        if (localecho)
            puts ("SELECT ALL");
        ret = m_select (nulav, mode);
        break;
    case 1:
        break;
    default:
        ret = m_select (av + avi, mode);
        scr = indexof(0);
        if (scr > 0  &&  localecho)
            printf ("%d  Entries selected\n", Entry[scr].no);
        break;
    }
    if (ret < 0  &&  localecho) {
        puts ("Null field");
        return (-1);
    }
    return (1);
}


do_defer()
{
    register int i, j;

    push_break();
    j = 1;
    for (i = 0; i < Entries; ++i) {
        if (Entry[i].no)
            Entry[i].no = (Entry[i].status & ST_READ) ? 0 : j++;
    }
    pop_break();
    return (1);
}

!Funky!Stuff!
fi # end of overwriting check
#	End of shell archive
exit 0