Current state of my dtmail work.
[oweals/cde.git] / cde / programs / dtmail / libDtMail / Common / DtMailRc.C
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /*
24  *+SNOTICE
25  *
26  *      $TOG: DtMailRc.C /main/9 1998/07/23 18:02:08 mgreess $
27  *
28  *      RESTRICTED CONFIDENTIAL INFORMATION:
29  *      
30  *      The information in this document is subject to special
31  *      restrictions in a confidential disclosure agreement between
32  *      HP, IBM, Sun, USL, SCO and Univel.  Do not distribute this
33  *      document outside HP, IBM, Sun, USL, SCO, or Univel without
34  *      Sun's specific written approval.  This document and all copies
35  *      and derivative works thereof must be returned or destroyed at
36  *      Sun's request.
37  *
38  *      Copyright 1993, 1995, 1995 Sun Microsystems, Inc.  All rights reserved.
39  *
40  *+ENOTICE
41  */
42
43 /*
44  * Format of the command description table.
45  * The actual table is declared and initialized
46  * in lex.c
47  */
48
49 #include <stdlib.h>
50 #include <signal.h>
51 #include <sys/param.h>
52 #include <sys/wait.h>
53 #include <ctype.h>
54 #include <pwd.h>
55 #include <DtMail/DtMail.hh>
56 #include <DtMail/DtMailError.hh>
57 #include <DtMail/Threads.hh>
58 #include <DtMail/IO.hh>
59 #include <EUSCompat.h>
60 #include "str_utils.h"
61
62 struct cmd {
63     char *c_name;   /* Name of command */
64     int (*c_func)(char **, DtMail::MailRc *);     /* Implementor of the command */
65     void (*c_write)(const char * verbatim, char ** args, FILE * outf);
66     short c_argtype;     /* Type of arglist (see below) */
67     short c_msgflag;     /* Required flags of messages */
68     short c_msgmask;     /* Relevant flags of messages */
69 };
70
71 struct var *DtMail::MailRc::variables[HSHSIZE]; /* Pointer to active var list */
72 #ifdef __osf__
73 typedef DtMail::MailRc::globals DtMailRc_globals;
74 DtMailRc_globals  DtMail::MailRc::glob;
75 #else
76 struct DtMail::MailRc::globals DtMail::MailRc::glob;
77 #endif
78 char *DtMail::MailRc::nullfield;
79 Boolean DtMail::MailRc::clearAliases;
80
81 #ifdef HAS_VFORK
82   #define DTMAIL_FORK   vfork
83 #else
84   #define DTMAIL_FORK   fork
85 #endif
86
87 #define MAXIMUM_PATH_LENGTH        2048
88
89 /* can't initialize unions */
90
91 #define c_minargs c_msgflag             /* Minimum argcount for RAWLIST */
92 #define c_maxargs c_msgmask             /* Max argcount for RAWLIST */
93 #define HASHSIZE 120
94
95
96 // defines needed by mail-parse
97 #define LINESIZE 10240        /* max readable line width */
98 #define MAXARGC  1024    /* Maximum list of raw strings */
99 #define equal(a, b)   (strcmp(a,b)==0)/* A nice function to string compare */
100 #define NONE  ((struct cmd *) 0) /* The nil pointer to command tab */
101
102 #define CANY            0               /* Execute in send or receive mode */
103 #define CRCV            1               /* Execute in receive mode only */
104 #define CSEND           2               /* Execute in send mode only */
105 #define CTTY            3               /* Execute if attached to a tty only */
106 #define CNOTTY          4               /* Execute if not attached to a tty */
107
108 #define STRLIST  1       /* A pure string */
109 #define RAWLIST  2       /* Shell string list */
110 #define F 01000  /* Is a conditional command */
111 #define NOLIST   3       /* Just plain 0 */
112 #define CANY            0               /* Execute in send or receive mode */
113 #define NOSTR           ((char *) 0)    /* Null string pointer */
114 // #define NOFILE_DTMAIL          20      /* this define is here for      */
115 //                                 /* compatibility purposes only  */
116 //                                 /* and will be removed in a     */
117 //                                 /* later release                */
118
119
120
121
122 struct var {
123     struct  var *v_link;            /* Forward link to next variable */
124     char    *v_name;                /* The variable's name */
125     char    *v_value;               /* And it's current value */
126     int     v_written;              /* It has been written on this pass. */
127 };      
128
129 struct hash {
130     struct hash *h_next;
131     char *h_key;
132     void *h_value;
133     int h_written;
134 };
135
136 struct cmd cmdtab[] = {
137 { "unset",      DtMail::MailRc::unset, DtMail::MailRc::wunset,     RAWLIST,        1,      1000, },
138 { "alias",      DtMail::MailRc::group, DtMail::MailRc::wgroup, RAWLIST, 2,      1000, },
139 { "group",      DtMail::MailRc::group, DtMail::MailRc::wgroup, RAWLIST, 2,      1000, },
140 { "set",        DtMail::MailRc::set,   DtMail::MailRc::wset, RAWLIST,   1,      1000,},
141 { "ignore",     DtMail::MailRc::igfield, DtMail::MailRc::wigfield, RAWLIST,        1,      1000, },
142 { "source",     DtMail::MailRc::source, DtMail::MailRc::wsource, RAWLIST,        1,      1000, },
143 { "if",         DtMail::MailRc::ifcmd, DtMail::MailRc::wifcmd, F|RAWLIST, 1, 1, },
144 { "else",       DtMail::MailRc::elsecmd, DtMail::MailRc::welsecmd, F|RAWLIST, 0, 0, },
145 { "endif",      DtMail::MailRc::endifcmd, DtMail::MailRc::wendifcmd, F|RAWLIST, 0, 0, },
146 { "alternates", DtMail::MailRc::alternates, DtMail::MailRc::walternates, RAWLIST,        1,      1000, },
147 { "clearaliases", DtMail::MailRc::clearaliases, DtMail::MailRc::wclearaliases, RAWLIST, 0, 0, },
148 { 0, 0,         0,              0,      0,      0 }
149 };
150
151 #if defined(sun)
152 #define SYSTEM_MAILRC   "/etc/mail/mail.rc"
153 #elif defined(_AIX) || defined(__osf__) || defined(linux)
154 #define SYSTEM_MAILRC   "/usr/share/lib/Mail.rc"
155 #elif defined(USL) || defined(__hpux)
156 #define SYSTEM_MAILRC   "/usr/share/lib/mailx.rc"
157 #elif defined(__uxp__)
158 #define SYSTEM_MAILRC   "/etc/mail/mailx.rc"
159 #elif defined(CSRG_BASED)
160 #define SYSTEM_MAILRC   "/etc/mail.rc"
161 #endif
162
163 // constructor
164 DtMail::MailRc::MailRc(DtMailEnv &env, DtMail::Session *session)
165 {
166   //char *line = NULL;
167    
168     session = session;
169
170
171     env.clear();
172     _parseError = DTME_NoError;
173
174     input = NULL;
175
176     init_globals();
177     
178     char * mail_rc = getenv("MAILRC");
179     if (mail_rc) {
180         _mailrc_name = strdup(mail_rc);
181     }
182     else {
183         passwd pw;
184         GetPasswordEntry(pw);
185         
186         _mailrc_name = (char *)malloc(MAXIMUM_PATH_LENGTH);
187         
188         strcpy(_mailrc_name, pw.pw_dir);
189         strcat(_mailrc_name, "/.mailrc");
190     }
191     
192     cond = CANY;
193   
194     char line[LINESIZE];
195     int rval = 0;
196     line[0] = '\0';
197     
198     if ((rval = this->load(_mailrc_name, line)) != 0) {
199         char *msg = new char[MAXIMUM_PATH_LENGTH + LINESIZE];
200         if (rval == -1) {
201             sprintf(msg, "error while reading \'%s\' in %s\n", line,
202                         _mailrc_name);
203         }
204         else if (rval == -2) {
205             sprintf(msg, "%s\n", line);
206         }
207         else if (rval == -3) {
208             sprintf(msg, "error: no endif for conditional if statement while reading %s\n", _mailrc_name);
209             env.setError(DTME_ResourceParsingNoEndif, NULL);
210             _parseError = DTME_ResourceParsingNoEndif;
211         }
212                 
213         env.logError(DTM_FALSE, "%s", msg);
214         delete [] msg;
215     }
216
217     rval = 0;
218     line[0] = '\0';
219
220     if ((rval = this->load(SYSTEM_MAILRC, line)) != 0) {
221         char *msg = new char[MAXIMUM_PATH_LENGTH + LINESIZE];
222
223         if (rval == -1) {
224             sprintf(msg, "error while reading \'%s\' in %s\n",
225                     line, SYSTEM_MAILRC);
226             env.logError(DTM_FALSE, "%s", msg);
227         } else if (rval == -2) {
228             /* Ignore error if the system logfile does not exist. */
229         }
230         delete [] msg;
231     }
232     
233 }
234
235 DtMail::MailRc::~MailRc(void)
236 {
237     free(_mailrc_name);
238 }
239
240 // return true if in list
241 DtMailBoolean DtMail::MailRc::ignore(DtMailEnv &env, const char *name)
242 {
243     
244     char *ignore_list = (char *)MailRc::hm_test((struct hash **)glob.g_ignore, (char *)name);
245     
246     env.clear();
247     
248     if(ignore_list != NULL)  // we have a valid ignore list
249       {
250           return DTM_TRUE;
251       }
252     else
253       {
254           env.setError(DTME_NoObjectValue, NULL);
255           return DTM_FALSE;
256       }
257     
258 }
259
260 void
261 DtMail::MailRc::addIgnore(DtMailEnv & error, const char * name)
262 {
263     error.clear();
264     add_ignore((char *)name);
265 }
266
267 void
268 DtMail::MailRc::removeIgnore(DtMailEnv & error, const char * name)
269 {
270     error.clear();
271     if(MailRc::hm_test((struct hash **)glob.g_ignore, (char *)name)) {
272         MailRc::hm_delete((struct hash **)glob.g_ignore, (char *)name);
273     }
274 }
275
276 const char *DtMail::MailRc::getAlias(DtMailEnv &env, const char * name)
277 {
278     
279     char *return_value = (char *)hm_test((struct hash **)glob.g_alias, (char *)name);
280     
281     env.clear();
282     
283     if(return_value == NULL)
284       {
285           env.setError(DTME_NoObjectValue, NULL);
286           return NULL;
287       }
288     else
289       {
290           return return_value;
291       }
292     
293 }
294
295 void
296 DtMail::MailRc::setAlias(DtMailEnv & error,
297                          const char * name,
298                          const char * value)
299 {
300     error.clear();
301
302     if (hm_test((struct hash **)glob.g_alias, (char *)name)) {
303         hm_delete((struct hash **)glob.g_alias, (char *)name);
304     }
305
306     add_alias((char *)name, (char *)value);
307 }
308
309 void
310 DtMail::MailRc::removeAlias(DtMailEnv & error,
311                             const char * name)
312 {
313     error.clear();
314
315     if (hm_test((struct hash **)glob.g_alias, (char *)name)) {
316         hm_delete((struct hash **)glob.g_alias, (char *)name);
317     }
318 }
319
320 // 
321 // Adapted from uuencode and uudecode written by Ian Lance Taylor.
322 //
323 #define ENCRYPT(c) ((c) ? (((c)&077) + ' ' + 2) : ('`' + 2))
324 #define DECRYPT(c) ((((c) - ' ') & 077) - 2)
325
326 int 
327 DtMail::MailRc::encryptedLength(int length)
328 {
329     // One for the length, one for the '\0', expansion factor of 4/3.
330     int encrypted_length = (2 + (4 * ((length+2)/3)));
331     return encrypted_length;
332 }
333
334 void 
335 DtMail::MailRc::encryptValue(char *to, char *from, int buflen)
336 {
337     int         ch, length;
338     char        *p;
339     int         i=0;
340   
341     memset(to, 0, buflen);
342
343     length = strlen(from);
344     to[i++] = ENCRYPT(length);
345
346     for (p=from; length>0; length-=3, p+=3)
347     {
348         ch = *p >> 2;
349         ch = ENCRYPT(ch);
350         to[i++] = (char) ch;
351
352         ch = ((*p << 4) & 060) | ((p[1] >> 4) & 017);
353         ch = ENCRYPT(ch);
354         to[i++] = (char) ch;
355
356         ch = ((p[1] << 2) & 074) | ((p[2] >> 6) & 03);
357         ch = ENCRYPT(ch);
358         to[i++] = (char) ch;
359
360         ch = p[2] & 077;
361         ch = ENCRYPT(ch);   
362         to[i++] = (char) ch;
363     }
364 }
365
366 int
367 DtMail::MailRc::decryptValue(char *to, char *from, int buflen)
368 {
369     int         ch, length;
370     int         i=0;
371     char        *p;
372    
373     memset(to, 0, buflen);
374     p = from;
375
376     length = DECRYPT(*p);
377     if (length<=0) return 1;
378
379     for (++p; length>0; p+=4, length-=3)
380     {
381         if (length>=3)
382         {
383             ch = DECRYPT(p[0]) << 2 | DECRYPT(p[1]) >> 4;
384             to[i++] = (char) ch;
385             ch = DECRYPT(p[1]) << 4 | DECRYPT(p[2]) >> 2;
386             to[i++] = (char) ch;
387             ch = DECRYPT(p[2]) << 6 | DECRYPT(p[3]);
388             to[i++] = (char) ch;
389         }
390         else
391         {
392             if (length>=1)
393             {
394                 ch = DECRYPT(p[0]) << 2 | DECRYPT(p[1]) >> 4;
395                 to[i++] = (char) ch;
396             }
397             if (length>=2)
398             {
399                 ch = DECRYPT(p[1]) << 4 | DECRYPT(p[2]) >> 2;
400                 to[i++] = (char) ch;
401             }
402         }
403     }
404     return 0;
405 }
406
407 // if set with value return no error in env and set value
408 // if set with no value return no error in env and return empty string
409 // if not set, return error in env and leave value alone
410
411 void DtMail::MailRc::getValue(DtMailEnv &env, 
412                               const char * var, 
413                               const char ** value,
414                               DtMailBoolean decrypt)
415 {
416     char *get_result = mt_value((char *)var);
417     
418     *value = NULL;
419     env.clear();
420     
421     if (get_result != NULL)
422     {
423         if (decrypt)
424         {
425             int length = encryptedLength(strlen(get_result));
426             *value = (char*) malloc(length);
427             if (decryptValue((char*) *value, get_result, length))
428               strcpy((char*) *value, get_result);
429         }
430         else
431           *value = strdup((char *)get_result);
432     }
433     else
434       env.setError(DTME_NoObjectValue, NULL);
435 }
436
437 void
438 DtMail::MailRc::setValue(DtMailEnv & error,
439                          const char * var,
440                          const char * value,
441                          DtMailBoolean encrypt)
442 {
443     error.clear();
444
445     if (encrypt && strlen(value))
446     {
447         int     length = encryptedLength(strlen(value));
448         char    *encrypted = (char*) malloc(length);
449         encryptValue(encrypted, (char*) value, length);
450         mt_assign((char*) var, (char*) encrypted);
451     }
452     else
453       mt_assign((char*) var, (char*) value);
454 }
455
456 void
457 DtMail::MailRc::removeValue(DtMailEnv & error,
458                             const char * var)
459 {
460     error.clear();
461
462     mt_deassign((char *)var);
463 }
464
465 // return alternates list
466 const char * DtMail::MailRc::getAlternates(DtMailEnv &env)
467 {
468     register int    i;
469     register struct hash *h;
470     struct hash **table = (struct hash **)glob.g_alternates;
471     int len;
472     
473     env.clear();
474     
475     // we don't free this memory...
476     alternate_list = NULL;
477     
478     if(table == NULL)
479       {
480           env.setError(DTME_NoObjectValue, NULL);
481           return NULL;
482       }
483     
484     i = HASHSIZE;
485     while (--i >= 0) {
486         
487         h = *table++;
488         while (h) {
489             len = strlen((const char*)h->h_key);
490             
491             if(alternate_list == NULL)
492               {
493                   alternate_list = (char *)malloc(len + 1); // plus terminator
494                   strcpy(alternate_list, (const char*)h->h_key);
495               }
496             else
497               {
498                   len += strlen(alternate_list);
499                   alternate_list = (char *)realloc(alternate_list, 
500                                                    len + 2); // plus terminator
501                   // and space
502                   strcat(alternate_list, " "); // add space
503                   strcat(alternate_list, (const char *)h->h_key);
504                   
505               }
506             
507             h = h->h_next;
508         }
509         
510     }
511     
512     return (alternate_list);
513     
514 }
515
516 void
517 DtMail::MailRc::setAlternate(DtMailEnv & error, const char * alt)
518 {
519     error.clear();
520
521     add_alternates((char *)alt);
522 }
523
524 void
525 DtMail::MailRc::removeAlternate(DtMailEnv & error, const char * name)
526 {
527     error.clear();
528
529     if(MailRc::hm_test((struct hash**)glob.g_alternates, (char *)name)) {
530         MailRc::hm_delete((struct hash **)glob.g_alternates, (char *)name);
531     }
532 }
533
534 void
535 DtMail::MailRc::update(DtMailEnv & error)
536 {
537     error.clear();
538     
539     // Generate a temporary file.
540     char *tmp_mailrc = new char[MAXIMUM_PATH_LENGTH];
541     strcpy(tmp_mailrc, _mailrc_name);
542     strcat(tmp_mailrc, ".tmp");
543     
544     FILE * outf = fopen(tmp_mailrc, "w+");
545     if (outf == NULL) {
546         error.setError(DTME_ObjectCreationFailed);
547         delete [] tmp_mailrc;
548         return;
549     }
550     
551     // Now open the mailrc for input.
552     FILE * inf = fopen(_mailrc_name, "r");
553     if (inf != NULL) {
554         // Now we will read the input file, and copy it to the output,
555         // based on type and changes to the mailrc.
556         //
557         updateByLine(inf, outf);
558         
559         fclose(inf);
560     }
561     
562     // Now we need to make a final scan. This will cause new values
563     // to be written and the written flag to be reset for the next
564     // update.
565     //
566     mt_scan(outf);
567
568     hm_scan((struct hash **)glob.g_alias, ngroup, outf);
569
570     // Alternates and groups we will add to a list, and then
571     // put them out in one batch.
572     //
573     DtVirtArray<char *> value_list(32);
574
575     hm_scan((struct hash **)glob.g_ignore, nigfield, &value_list);
576
577     if (value_list.length()) {
578         fwrite("ignore ", 1, 7, outf);
579         while (value_list.length()) {
580             char * val = value_list[0];
581             fwrite(val, 1, strlen(val), outf);
582             fwrite(" ", 1, 1, outf);
583             value_list.remove(0);
584         }
585         fwrite("\n", 1, 1, outf);
586     }
587
588     hm_scan((struct hash **)glob.g_alternates, nalternates, &value_list);
589
590     if (value_list.length()) {
591         fwrite("alternates ", 1, 11, outf);
592         while (value_list.length()) {
593             char * val = value_list[0];
594             fwrite(val, 1, strlen(val), outf);
595             fwrite(" ", 1, 1, outf);
596             value_list.remove(0);
597         }
598         fwrite("\n", 1, 1, outf);
599     }
600
601     fclose(outf);
602     if (rename(tmp_mailrc, _mailrc_name)) {
603         error.setError(DTME_ObjectAccessFailed);
604         delete [] tmp_mailrc;
605         return;
606     }
607     delete [] tmp_mailrc;
608 }
609
610 void DtMail::MailRc::getAliasList(hm_callback stuffing_func, void *client_data)
611 {
612   hm_scan((struct hash **)glob.g_alias, stuffing_func, client_data);
613 }
614
615
616 DtVirtArray<char *> *DtMail::MailRc::getAliasList()
617 {       
618
619   DtVirtArray<char *>   *value_list = NULL;
620
621   value_list = new DtVirtArray<char *>(10);
622
623   hm_scan((struct hash **)glob.g_alias, nalias, value_list);
624
625   return value_list;
626 }
627
628 DtVirtArray<char *> *DtMail::MailRc::getIgnoreList()
629 {       
630
631   DtVirtArray<char *>   *value_list = NULL;
632
633   value_list = new DtVirtArray<char *>(10);
634
635   hm_scan((struct hash **)glob.g_ignore, nignorelist, value_list);
636
637   return value_list;
638 }
639
640 void
641 DtMail::MailRc::updateByLine(FILE * inf, FILE * outf)
642 {
643     // We are going to keep two line buffers, unlike the first
644     // pass parser. One is the line, with continuations, verbatim.
645     // If nothing has changed, then we put this into the output
646     // unmodified. The other is the line with continuations stripped
647     // so we can finish parsing and analyzing the line.
648     //
649     char *verbatim = new char[LINESIZE];
650     char *for_parser = new char[LINESIZE];
651     char *linebuf = new char[LINESIZE];
652     int  at_eof = 0;
653
654     while(!at_eof) {
655         verbatim[0] = 0;
656         for_parser[0] = 0;
657
658         while(1) {
659             if (readline(inf, linebuf) <= 0) {
660                 at_eof = 1;
661                 break;
662             }
663
664             int len = strlen(linebuf);
665             if (len == 0 || linebuf[len - 1] != '\\') {
666                 break;
667             }
668
669             strcat(verbatim, linebuf);
670             strcat(verbatim, "\n");
671
672             linebuf[len - 1] = ' ';
673             strcat(for_parser, linebuf);
674         }
675         if (at_eof) {
676             break;
677         }
678
679         strcat(verbatim, linebuf);
680         strcat(verbatim, "\n");
681         strcat(for_parser, linebuf);
682         outputLine(verbatim, for_parser, outf);
683     }
684
685     delete [] verbatim;
686     delete [] for_parser;
687     delete [] linebuf;
688 }
689
690 void
691 DtMail::MailRc::outputLine(const char * verbatim,
692                            const char * for_parser,
693                            FILE * outf)
694 {
695     char *arglist[MAXARGC];
696     const char * start = for_parser;
697     while(*start && isspace(*start)) {
698         start++;
699     }
700
701     // If we have a comment, write it out and move on.
702     //
703     if (*start == '#') {
704         fwrite(verbatim, 1, strlen(verbatim), outf);
705         return;
706     }
707
708     char word[LINESIZE];
709
710     // Pull off the command word.
711     //
712     char * cp2 = word;
713     while (*start && !strchr(" \t0123456789$^.:/-+*'\"", *start))
714         *cp2++ = *start++;
715     *cp2 = '\0';
716
717     if (equal(word, "")) {
718         fwrite(verbatim, 1, strlen(verbatim), outf);
719         return;
720     }
721
722     struct cmd * com = (struct cmd *)lex(word);
723     if (com == NONE) {
724         // We dont know the command, so just copy the line.
725         //
726         fwrite(verbatim, 1, strlen(verbatim), outf);
727         return;
728     }
729
730     // We will simply rewrite conditionals.
731     if ((com->c_argtype & F)) {
732         fwrite(verbatim, 1, strlen(verbatim), outf);
733         return;
734     }
735
736     int c;
737     switch (com->c_argtype & ~(F)) {
738       case STRLIST:
739         /*
740          * Just the straight string, with
741          * leading blanks removed.
742          */
743         while (strchr(" \t", *start)) {
744             start++;
745         }
746         com->c_write(verbatim, (char **)&start, outf);
747         break;
748         
749       case RAWLIST:
750         /*
751          * A vector of strings, in shell style.
752          */
753         if ((c = getrawlist((char *)start, arglist,
754                             sizeof arglist / sizeof *arglist)) < 0)
755             break;
756         if (c < com->c_minargs) {
757             fprintf(stderr,"%s requires at least %d arg(s)\n",
758                     com->c_name, com->c_minargs);
759             break;
760         }
761         if (c > com->c_maxargs) {
762             fprintf(stderr,"%s takes no more than %d arg(s)\n",
763                     com->c_name, com->c_maxargs);
764             break;
765         }
766         com->c_write(verbatim, arglist, outf);
767         freerawlist(arglist);
768         break;
769         
770       case NOLIST:
771         /*
772          * Just the constant zero, for exiting,
773          * eg.
774          */
775         com->c_write(verbatim, NULL, outf);
776         break;
777         
778       default:
779         fprintf(stderr,"Unknown argtype %#x", com->c_argtype);
780         fprintf(stderr, "NEED TO FIX THIS FOR DTMAIL!\n");
781 /*              mt_done(1); */
782     }
783
784 }
785
786 // init the global hash structure
787 void DtMail::MailRc::init_globals()
788 {
789     glob.g_myname = NULL; 
790     
791     glob.g_ignore = this->hm_alloc(); 
792     glob.g_retain = this->hm_alloc(); 
793     glob.g_alias = this->hm_alloc(); 
794     glob.g_alternates = this->hm_alloc(); 
795     ssp = -1;  
796     
797     nullfield = new char[3];
798     
799     strcpy(nullfield, "");
800     
801     alternate_list = NULL;
802     
803 }
804
805 // load a "user definintion" file
806 int
807 DtMail::MailRc::load(char *name, char* line)
808 {
809     register FILE *in, *oldin;
810     int ret=0;
811     
812     if ((in = fopen(name, "r")) == NULL) {
813         sprintf(line, "can not open file %s\n", name);
814         return(-2);
815     }
816     line[0] = '\0';
817     oldin = input;
818     input = in;
819     sourcing = 0;
820     if ((ret = commands(line)) != 0) {
821         input = oldin;
822         fclose(in);
823         return ret;
824     }
825     input = oldin;
826     fclose(in);
827     return ret;
828 }
829
830 /*
831  * Interpret user commands one by one.  If standard input is not a tty,
832  * print no prompt.
833  */
834
835 int
836 DtMail::MailRc::commands(char* iline)
837 {
838     register int n;
839     char *linebuf = new char[LINESIZE];
840     char *line = new char[LINESIZE];
841     
842     for (;;) {
843         /*
844          * Read a line of commands from the current input
845          * and handle end of file specially.
846          */
847         
848         n = 0;
849         linebuf[0] = '\0';
850         for (;;) {
851             if (readline(input, line) <= 0) {
852                 if (n != 0)
853                     break;
854                 if (sourcing) {
855                     unstack();
856                     goto more;
857                 }
858                 // Conditional if statement with no corresponding endif 
859                 if (cond != CANY)
860                 {
861                     delete [] linebuf;
862                     delete [] line;
863                     return(-3);
864                 }
865                 delete [] linebuf;
866                 delete [] line;
867                 return 0;
868             }
869             if ((n = strlen(line)) == 0)
870                 break;
871             n--;
872             if (line[n] != '\\')
873                 break;
874             line[n++] = ' ';
875             if (n > LINESIZE - strlen(linebuf) - 1)
876                 break;
877             strcat(linebuf, line);
878         }
879         n = LINESIZE - strlen(linebuf) - 1;
880         if (strlen(line) > n) {
881             fprintf(stderr,
882                     "Line plus continuation line too long:\n\t%s\n\nplus\n\t%s\n",
883                     linebuf, line);
884             if (sourcing) {
885                 unstack();
886                 goto more;
887             }
888             // Conditional if statement with no corresponding endif 
889             if (cond != CANY)
890             {
891                 delete [] linebuf;
892                 delete [] line;
893                 return(-3);
894             }
895             delete [] linebuf;
896             delete [] line;
897             return 0;
898         }
899         strncat(linebuf, line, n);
900         if (execute(linebuf)) {
901                 strncpy(iline, linebuf, LINESIZE);
902                 iline[LINESIZE-1] = '\0';
903             delete [] linebuf;
904             delete [] line;
905             return(-1);
906         }
907       more:             ;
908     }
909     delete [] linebuf;
910     delete [] line;
911 }
912
913 /*
914  * Execute a single command.  If the command executed
915  * is "quit," then return non-zero so that the caller
916  * will know to return back to main, if he cares.
917  * Contxt is non-zero if called while composing mail.
918  */
919
920 int DtMail::MailRc::execute(char linebuf[])
921 {
922     char word[LINESIZE];
923     char *arglist[MAXARGC];
924     struct cmd *com;
925     char *cp, *cp2;
926     int c;
927 //      int muvec[2];
928     int e;
929     int newcmd = 0;
930     
931     /*
932      * Strip the white space away from the beginning
933      * of the command, then scan out a word, which
934      * consists of anything except digits and white space.
935      *
936      * Handle ! escapes differently to get the correct
937      * lexical conventions.
938      */
939     
940     cp = linebuf;
941     while (*cp && strchr(" \t", *cp))
942         cp++;
943     /*
944      * Throw away comment lines here.
945      */
946     if (*cp == '#') {
947         if (*++cp != '-') {
948             return(0);
949         }
950         /* the special newcmd header -- "#-" */
951         newcmd = 1;
952         
953         /* strip whitespace again */
954         while (*++cp && strchr(" \t", *cp));
955     }
956     cp2 = word;
957     while (*cp && !strchr(" \t0123456789$^.:/-+*'\"", *cp))
958         *cp2++ = *cp++;
959     *cp2 = '\0';
960     
961     /*
962      * Look up the command; if not found, complain.
963      * We ignore blank lines to eliminate confusion.
964      */
965     
966     if (equal(word, ""))
967         return(0);
968     
969     com = (struct cmd *)lex(word);
970     if (com == NONE) {
971         if (newcmd) {
972             /* this command is OK not to be found; that way
973              * we can extend the .mailrc file with new
974              * commands and not kill old mail and mailtool
975              * programs.
976              */
977             return(0);
978         }
979         fprintf(stderr,"Unknown command: \"%s\"\n", word);
980         if (sourcing)
981             unstack();
982         return(1);
983     }
984     
985     /*
986      * See if we should execute the command -- if a conditional
987      * we always execute it, otherwise, check the state of cond.  
988      */
989     
990     if ((com->c_argtype & F) == 0) {
991         if (cond == CSEND || cond == CTTY )
992           {
993               return(0);
994           }
995         
996 #ifdef undef
997         if (cond == CRCV && !rcvmode || cond == CSEND && rcvmode ||
998             cond == CTTY && !intty || cond == CNOTTY && intty)
999           {
1000               return(0);
1001           }
1002 #endif 
1003     }
1004     
1005     /*
1006      * Process the arguments to the command, depending
1007      * on the type he expects.  Default to an error.
1008      * If we are sourcing an interactive command, it's
1009      * an error.
1010      */
1011     
1012     e = 1;
1013     switch (com->c_argtype & ~(F)) {
1014       case STRLIST:
1015         /*
1016          * Just the straight string, with
1017          * leading blanks removed.
1018          */
1019         while (strchr(" \t", *cp))
1020             cp++;
1021         e = (*com->c_func)(&cp, this);
1022         break;
1023         
1024       case RAWLIST:
1025         /*
1026          * A vector of strings, in shell style.
1027          */
1028         if ((c = getrawlist(cp, arglist,
1029                             sizeof arglist / sizeof *arglist)) < 0)
1030             break;
1031         if (c < com->c_minargs) {
1032             fprintf(stderr,"%s requires at least %d arg(s)\n",
1033                     com->c_name, com->c_minargs);
1034             break;
1035         }
1036         if (c > com->c_maxargs) {
1037             fprintf(stderr,"%s takes no more than %d arg(s)\n",
1038                     com->c_name, com->c_maxargs);
1039             break;
1040         }
1041         e = (*com->c_func)(arglist, this);
1042         freerawlist(arglist);
1043         break;
1044         
1045       case NOLIST:
1046         /*
1047          * Just the constant zero, for exiting,
1048          * eg.
1049          */
1050         e = (*com->c_func)(0, this);
1051         break;
1052         
1053       default:
1054         fprintf(stderr,"Unknown argtype %#x", com->c_argtype);
1055         fprintf(stderr, "NEED TO FIX THIS FOR DTMAIL!\n");
1056 /*              mt_done(1); */
1057     }
1058     
1059     /*
1060      * Exit the current source file on
1061      * error.
1062      */
1063     
1064     if (e && sourcing)
1065         unstack();
1066     
1067     /* ZZZ:katin: should we return an error here? */
1068     return(e);
1069 }
1070
1071 /*
1072  * Read up a line from the specified input into the line
1073  * buffer.  Return the number of characters read.  Do not
1074  * include the newline at the end.
1075  */
1076
1077 int
1078 DtMail::MailRc::readline(FILE *ibuf, char *linebuf)
1079 {
1080     register char *cp;
1081     register int c;
1082     int seennulls = 0;
1083     
1084     clearerr(ibuf);
1085     c = getc(ibuf);
1086     for (cp = linebuf; c != '\n' && c != EOF; c = getc(ibuf)) {
1087         if (c == 0) {
1088             if (!seennulls) {
1089                 fprintf(stderr,
1090                         "Mail: ignoring NULL characters in mail\n");
1091                 seennulls++;
1092             }
1093             continue;
1094         }
1095         if (cp - linebuf < LINESIZE-2)
1096             *cp++ = c;
1097     }
1098     *cp = 0;
1099     if (c == EOF && cp == linebuf)
1100         return(0);
1101     return(cp - linebuf + 1);
1102 }
1103
1104
1105 /*
1106  * Pop the current input back to the previous level.
1107  * Update the "sourcing" flag as appropriate.
1108  */
1109
1110 void DtMail::MailRc::unstack()
1111 {
1112     if (ssp < 0) {
1113         fprintf(stderr,"\"Source\" stack over-pop.\n");
1114         sourcing = 0;
1115         return;
1116     }
1117     fclose(input);
1118     if (cond != CANY)
1119         fprintf(stderr,"Unmatched \"if\"\n");
1120     cond = sstack[ssp].s_cond;
1121     input = sstack[ssp--].s_file;
1122     if (ssp < 0)
1123         sourcing = 0;
1124 }
1125 /*
1126  * Find the correct command in the command table corresponding
1127  * to the passed command "word"
1128  */
1129
1130 void *DtMail::MailRc::lex(char word[])
1131 {
1132     register struct cmd *cp;
1133
1134     for (cp = &cmdtab[0]; cp->c_name != NOSTR; cp++)
1135         if (DtMail::MailRc::isprefix(word, cp->c_name))
1136             return(cp);
1137     return(NONE);
1138 }
1139
1140 /*
1141  * Determine if as1 is a valid prefix of as2.
1142  * Return true if yep.
1143  */
1144
1145 int DtMail::MailRc::isprefix(char *as1, char *as2)
1146 {
1147     register char *s1, *s2;
1148     
1149     s1 = as1;
1150     s2 = as2;
1151     while (*s1++ == *s2)
1152         if (*s2++ == '\0')
1153             return(1);
1154     return(*--s1 == '\0');
1155 }
1156
1157 /*
1158  * Scan out the list of string arguments, shell style
1159  * for a RAWLIST.
1160  */
1161
1162 int DtMail::MailRc::getrawlist(char line[], char **argv, int argc)
1163 {
1164     register char **ap, *cp, *cp2;
1165     char linebuf[LINESIZE], quotec;
1166     register char **last;
1167     
1168     ap = argv;
1169     cp = line;
1170     last = argv + argc - 1;
1171     while (*cp != '\0') {
1172         while (*cp && strchr(" \t", *cp))
1173             cp++;
1174         cp2 = linebuf;
1175         quotec = 0;
1176         while (*cp != '\0') {
1177             if (quotec) {
1178                 if (*cp == quotec) {
1179                     quotec=0;
1180                     cp++;
1181                 } else
1182                     *cp2++ = *cp++;
1183             } else {
1184                 if (*cp && strchr(" \t", *cp))
1185                     break;
1186                 if (*cp && strchr("'\"", *cp))
1187                     quotec = *cp++;
1188                 else
1189                     *cp2++ = *cp++;
1190             }
1191         }
1192         *cp2 = '\0';
1193         if (cp2 == linebuf)
1194             break;
1195         if (ap >= last) {
1196             fprintf(stderr,
1197                     "Too many elements in the list; excess discarded\n");
1198             break;
1199         }
1200         *ap++ = strdup((char*)linebuf);
1201     }
1202     *ap = NOSTR;
1203     return(ap-argv);
1204 }
1205
1206 void DtMail::MailRc::freerawlist(char **argv)
1207 {
1208     
1209     while(*argv) {
1210         free((char*)*argv);
1211         argv++;
1212     }
1213 }
1214
1215 /*
1216  * Get the value of a variable and return it.
1217  * Look in the environment if its not available locally.
1218  */
1219
1220 char *DtMail::MailRc::mt_value(char name[])
1221 {
1222     register struct var *vp;
1223     register char *cp;
1224 //        extern char *getenv();
1225     
1226     if ((vp = lookup(name, (struct var **)this->variables)) == (struct var *)NULL)
1227         cp = getenv(name);
1228     else
1229         cp = vp->v_value;
1230     return (cp);
1231 }
1232
1233 /*
1234  * Locate a variable and return its variable
1235  * node.
1236  */
1237 struct var *DtMail::MailRc::lookup(char *name, struct var **hasharray)
1238 {
1239     register struct var *vp;
1240     register int h;
1241     
1242     h = hash(name);
1243     for (vp = hasharray[h]; vp != (struct var *)NULL; vp = vp->v_link)
1244         if (strcmp(vp->v_name, name) == 0)
1245             return (vp);
1246     return ((struct var *)NULL);
1247 }
1248
1249
1250 /*
1251  * Put add users to a group.
1252  */
1253
1254 int DtMail::MailRc::group(char **argv, DtMail::MailRc *)
1255 {
1256     int size;
1257     char *buf;
1258     char *s;
1259     char **ap;
1260     
1261     /*
1262      * Insert names from the command list into the group.
1263      * Who cares if there are duplicates?  They get tossed
1264      * later anyway.
1265      */
1266     
1267     /* compute the size of the buffer */
1268     size = 0;
1269     for (ap = argv+1; *ap != NOSTR; ap++) {
1270         size += strlen(*ap) +1;
1271     }
1272     buf = (char *)malloc(size);
1273     s = buf;
1274     for (ap = argv+1; *ap != NOSTR; ap++) {
1275         strcpy(s, *ap);
1276         s += strlen(s);
1277         *s++ = ' ';
1278     }
1279     *--s = '\0';
1280     
1281     MailRc::add_alias((char *)argv[0], (char *)buf);
1282     free(buf);
1283     return(0);
1284 }
1285
1286 void
1287 DtMail::MailRc::wgroup(const char * verbatim, char ** argv, FILE * outf)
1288 {
1289     int size = 0;
1290     char * buf;
1291     char * s;
1292     char ** ap;
1293
1294     for (ap = argv+1; *ap != NOSTR; ap++) {
1295         size += strlen(*ap) +1;
1296     }
1297     buf = (char *)malloc(size);
1298     s = buf;
1299     for (ap = argv+1; *ap != NOSTR; ap++) {
1300         strcpy(s, *ap);
1301         s += strlen(s);
1302         *s++ = ' ';
1303     }
1304     *--s = '\0';
1305
1306     char * cur = (char *)hm_test((struct hash **)glob.g_alias, argv[0]);
1307     if (!cur || (!clearAliases && hm_ismarked((struct hash **)glob.g_alias, 
1308                         argv[0]))) {
1309         // Its been removed or written. Dont write it to the file.
1310         free(buf);
1311         return;
1312     }
1313
1314     if (strcmp(cur, buf) == 0) {
1315         // It hasnt changed. Write the original to preserve spacing.
1316         //
1317         fwrite(verbatim, 1, strlen(verbatim), outf);
1318     }
1319     else {
1320         // It has changed. Create a new alias.
1321         //
1322         fwrite("alias ", 1, 6, outf);
1323         fwrite(argv[0], 1, strlen(argv[0]), outf);
1324         fwrite(" ", 1, 1, outf);
1325         fwrite(cur, 1, strlen(cur), outf);
1326         fwrite("\n", 1, 1, outf);
1327     }
1328
1329     hm_mark((struct hash **)glob.g_alias, argv[0]);
1330 }
1331
1332 void
1333 DtMail::MailRc::ngroup(char * key, void * data, void * client_data)
1334 {
1335     char * value = (char *)data;
1336     FILE * outf = (FILE *)client_data;
1337
1338     fwrite("alias ", 1, 6, outf);
1339     fwrite(key, 1, strlen(key), outf);
1340     fwrite(" ", 1, 1, outf);
1341     fwrite(value, 1, strlen(value), outf);
1342     fwrite("\n", 1, 1, outf);
1343 }
1344
1345 void
1346 DtMail::MailRc::nalias(char * key, void * data, void * client_data)
1347 {
1348     DtVirtArray<char *> *value_list = (DtVirtArray<char *> *)client_data;
1349     char *new_alias = NULL;
1350     char *white_space = NULL;
1351     int m_size = 0;
1352     int  i, num_spaces = 0;
1353     int key_len = strlen(key);
1354     // figure out whitespace for formatting
1355     // assume 13 for normal sized alias name
1356
1357     if(key_len < 13)  // need to add spaces
1358       {
1359         num_spaces = 13 - key_len;
1360
1361         white_space = (char *)malloc(num_spaces + 1);
1362         white_space[0] = '\0';
1363
1364         for(i = 0; i < num_spaces; i++)
1365           white_space[i] = ' ';
1366
1367         white_space[num_spaces] = '\0';
1368
1369 //        strcat(white_space, " ");
1370
1371         /* make an alias string */
1372         m_size = key_len + strlen((char *)white_space)
1373                  + strlen((char *)data) + strlen(" = ") + 1;
1374         new_alias = (char *)malloc(m_size);
1375     
1376         sprintf(new_alias, "%s%s = %s",key, white_space, data);
1377
1378       }
1379     else
1380       {
1381                 /* make an alias string */
1382         m_size = key_len + strlen((char *)data) + strlen(" = ") + 1;
1383         new_alias = (char *)malloc(m_size);
1384     
1385         sprintf(new_alias, "%s = %s",key, data);
1386
1387       }
1388     
1389     value_list->append(new_alias);
1390
1391 }
1392
1393 void
1394 DtMail::MailRc::nignorelist(char * key, void * data, void * client_data)
1395 {
1396     data = data;
1397     DtVirtArray<char *> *value_list = (DtVirtArray<char *> *)client_data;
1398     char *new_ignore = NULL;
1399
1400     new_ignore = (char *)malloc(strlen((char *)key) + 2);
1401
1402     sprintf(new_ignore, "%s", key);
1403
1404     value_list->append(new_ignore);
1405
1406 }
1407
1408
1409 /*
1410  * Set or display a variable value.  Syntax is similar to that
1411  * of csh.
1412  */
1413
1414 int DtMail::MailRc::set(char **arglist, DtMail::MailRc *)
1415 {
1416     register char *cp, *cp2;
1417     char varbuf[LINESIZE], **ap;
1418     int errs;
1419     
1420     errs = 0;
1421     for (ap = arglist; *ap != NOSTR; ap++) {
1422         cp = *ap;
1423         cp2 = varbuf;
1424         while (*cp != '=' && *cp != '\0')
1425             *cp2++ = *cp++;
1426         *cp2 = '\0';
1427         if (*cp == '\0')
1428             cp = "";
1429         else
1430             cp++;
1431         if (equal(varbuf, "")) {
1432             fprintf(stderr,"Non-null variable name required\n");
1433             errs++;
1434             continue;
1435         }
1436         MailRc::mt_assign(varbuf, cp);
1437     }
1438     return(errs);
1439 }
1440
1441 void
1442 DtMail::MailRc::wset(const char * verbatim,
1443                      char ** arglist,
1444                      FILE * outf)
1445 {
1446     char varbuf[LINESIZE];
1447     char *cp, *cp2, **ap;
1448
1449     for (ap = arglist; *ap != NOSTR; ap++) {
1450         cp = *ap;
1451         cp2 = varbuf;
1452         while (*cp != '=' && *cp != '\0')
1453             *cp2++ = *cp++;
1454         *cp2 = '\0';
1455         if (*cp == '\0')
1456             cp = "";
1457         else
1458             cp++;
1459         if (equal(varbuf, "")) {
1460             continue;
1461         }
1462
1463         // Lookup the current value, and see if we should rewrite this
1464         // variable.
1465         //
1466         struct var * vp = lookup(varbuf, (struct var **)variables);
1467         if (vp == NULL || vp->v_written) {
1468             // If the original input line was set novar then just write
1469             // it out again. We can not easily track duplicates here.
1470             //
1471             if (varbuf[0] == 'n' && varbuf[1] == 'o') {
1472                 fwrite(verbatim, 1, strlen(verbatim), outf);
1473             }
1474
1475             // This variable has been unassigned or previously written.
1476             // Remove it from the file, by not writing it.
1477             //
1478             continue;
1479         }
1480
1481         // Compare the values. If equal, then write the line verbatim to
1482         // preserve the users original spacing and quoting.
1483         //
1484         if (strcmp(cp, vp->v_value) == 0) {
1485             fwrite(verbatim, 1, strlen(verbatim), outf);
1486             vp->v_written = 1;
1487         }
1488         else {
1489             // Write the variable, with a new value.
1490             //
1491             fwrite("set ", 1, 4, outf);
1492             fwrite(varbuf, 1, strlen(varbuf), outf);
1493             if (strlen(vp->v_value) > 0) {
1494                 fwrite("='", 1, 2, outf);
1495                 fwrite(vp->v_value, 1, strlen(vp->v_value), outf);
1496                 fwrite("'", 1, 1, outf);
1497             }
1498             fwrite("\n", 1, 1, outf);
1499             vp->v_written = 1;
1500         }
1501     }
1502 }
1503
1504 /*
1505  * Unset a bunch of variable values.
1506  */
1507
1508 int DtMail::MailRc::unset(char **arglist, DtMail::MailRc *)
1509 {
1510         register char **ap;
1511
1512         for (ap = arglist; *ap != NOSTR; ap++)
1513                 (void) MailRc::mt_deassign(*ap);
1514         return(0);
1515 }
1516
1517 void
1518 DtMail::MailRc::wunset(const char * verbatim,
1519                      char ** arglist,
1520                      FILE * outf)
1521 {
1522   arglist = arglist;
1523   if(verbatim != NULL && outf != NULL)
1524     fwrite(verbatim, 1, strlen(verbatim), outf);
1525 }
1526
1527 /*
1528  * Hash the passed string and return an index into
1529  * the variable or group hash table.
1530  */
1531 int DtMail::MailRc::hash(char *name)
1532 {
1533     register unsigned h;
1534     register char *cp;
1535     
1536     for (cp = name, h = 0; *cp; h = (h << 2) + *cp++)
1537         ;
1538     return (h % HSHSIZE);
1539 }
1540
1541 /* manage the alias list */
1542 void DtMail::MailRc::add_alias(char *name, char *value)
1543 {
1544     char *old;
1545     char *new_ptr;
1546     
1547     /* aliases to the same name get appended to the list */
1548     old = (char*)MailRc::hm_test((struct hash **)glob.g_alias, name);
1549     if (old) {
1550         
1551         int size;
1552         
1553         size = strlen(value) + strlen(old) + 2;
1554         new_ptr = (char *)malloc(size);
1555         sprintf(new_ptr, "%s %s", value, old);
1556         
1557         /* delete any old bindings for the name */
1558         MailRc::hm_delete((struct hash**)glob.g_alias, name);
1559         
1560         /* add the new binding */
1561         MailRc::hm_add((struct hash**)glob.g_alias, name, new_ptr, size);
1562         
1563         /* free up the temporary space */
1564         free(new_ptr);
1565     } else {
1566         /* add the new binding */
1567         MailRc::hm_add((struct hash**)glob.g_alias, name, value, strlen(value) +1);
1568     }
1569 }
1570
1571 /*
1572  * Assign a value to a mail variable.
1573  */
1574 void DtMail::MailRc::mt_assign(char *name,char * val)
1575 {
1576     
1577     if (name[0]=='-')
1578         (void) mt_deassign(name+1);
1579     else if (name[0]=='n' && name[1]=='o')
1580         (void) mt_deassign(name+2);
1581     else if ((val[0] == 'n' || val[0] == 'N') &&
1582              (val[1] == 'o' || val[1] == 'O') && val[2] == '\0') {
1583         (void) mt_deassign(name);
1584     } else mt_puthash(name, vcopy(val), MailRc::variables);
1585 }
1586
1587 int DtMail::MailRc::mt_deassign(char *s)
1588 {
1589     register struct var *vp, *vp2;
1590     register int h;
1591     
1592     if ((vp2 = lookup(s, MailRc::variables)) == (struct var *)NULL) {
1593         return (1);
1594     }
1595     h = hash(s);
1596     if (vp2 == MailRc::variables[h]) {
1597         MailRc::variables[h] = MailRc::variables[h]->v_link;
1598         vfree(vp2->v_name);
1599         vfree(vp2->v_value);
1600         free((char *)vp2);
1601         return (0);
1602     }
1603     for (vp = MailRc::variables[h]; vp->v_link != vp2; vp = vp->v_link)
1604         ;
1605     vp->v_link = vp2->v_link;
1606     vfree(vp2->v_name);
1607     vfree(vp2->v_value);
1608     free((char *)vp2);
1609     return (0);
1610 }
1611
1612 /*
1613  * associate val with name in hasharray
1614  */
1615 void DtMail::MailRc::mt_puthash(char *name, char *val, struct var **hasharray)
1616 {
1617     register struct var *vp;
1618     register int h;
1619     
1620     vp = lookup(name, hasharray);
1621     if (vp == (struct var *)NULL) {
1622         h = hash(name);
1623         vp = (struct var *) (calloc(sizeof *vp, 1));
1624         vp->v_name = vcopy(name);
1625         vp->v_link = hasharray[h];
1626         vp->v_written = 0;
1627         hasharray[h] = vp;
1628     } else
1629         vfree(vp->v_value);
1630     vp->v_value = val;
1631 }
1632
1633 void
1634 DtMail::MailRc::mt_scan(FILE * outf)
1635 {
1636     for (int slot = 0; slot < HSHSIZE; slot++) {
1637         for (var * vp = MailRc::variables[slot]; vp != (struct var *)NULL; vp = vp->v_link) {
1638             if (!vp->v_written) {
1639                 fwrite("set ", 1, 4, outf);
1640                 fwrite(vp->v_name, 1, strlen(vp->v_name), outf);
1641                 if (strlen(vp->v_value)) {
1642                     fwrite("='", 1, 2, outf);
1643                     fwrite(vp->v_value, 1, strlen(vp->v_value), outf);
1644                     fwrite("'", 1, 1, outf);
1645                 }
1646                 fwrite("\n", 1, 1, outf);
1647             }
1648             vp->v_written = 0;
1649         }
1650     }
1651 }
1652
1653 /*
1654  * Copy a variable value into permanent space.
1655  * Do not bother to alloc space for "".
1656  */
1657 char *DtMail::MailRc::vcopy(char *str)
1658 {
1659     
1660     if (strcmp(str, "") == 0)
1661         return ("");
1662     return (strdup(str));
1663 }
1664
1665 /*
1666  * Free up a variable string.  We do not bother to allocate
1667  * strings whose value is "" since they are expected to be frequent.
1668  * Thus, we cannot free same!
1669  */
1670 void DtMail::MailRc::vfree(char *cp)
1671 {
1672     
1673     if (strcmp(cp, "") != 0)
1674         free(cp);
1675 }
1676
1677 void *DtMail::MailRc::hm_alloc(void)
1678 {
1679     struct hash **table;
1680     
1681     table = (struct hash**)malloc(sizeof (struct hash) * HASHSIZE);
1682     memset(table, '\0', sizeof (struct hash) * HASHSIZE);
1683     return (table);
1684 }
1685 void DtMail::MailRc::hm_add(struct hash **table, 
1686                             char *key, 
1687                             void *value, 
1688                             int size)
1689 {
1690     int index;
1691     register struct hash *h;
1692     
1693     if (!table)
1694         return;
1695     
1696     index = hash_index(key);
1697     h = (struct hash *)malloc(sizeof (struct hash));
1698     h->h_next = table[index];
1699     h->h_written = 0;
1700     table[index] = h;
1701     h->h_key = strdup(key);
1702     if (size && value != NULL) {
1703         h->h_value = malloc(size);
1704         memcpy(h->h_value, value, size);
1705     } else {
1706         h->h_value = nullfield;
1707     }
1708 }
1709
1710
1711 void DtMail::MailRc::hm_delete(struct hash **table, char *key)
1712 {
1713     register int index;
1714     register struct hash *h;
1715     register struct hash *old;
1716     
1717     if (!table)
1718         return;
1719     
1720     index = hash_index(key);
1721     old = NULL;
1722     h = table[index];
1723     while (h) {
1724         if (strcasecmp(h->h_key, key) == 0) {
1725             /* found the match */
1726             if (old == NULL)
1727                 table[index] = h->h_next;
1728             else
1729                 old->h_next = h->h_next;
1730             
1731             free_hash(h);
1732             break;
1733         }
1734         
1735         old = h;
1736         h = h->h_next;
1737     }
1738 }
1739
1740
1741 void *DtMail::MailRc::hm_test(struct hash **table, char *key)
1742 {
1743     register struct hash *h;
1744     
1745     if (!table)
1746         return (NULL);
1747     
1748     h = table[hash_index(key)];
1749     
1750     while (h) {
1751         if (strcasecmp(h->h_key, key) == 0) {
1752             /* found a match */
1753             return (h->h_value);
1754         }
1755         
1756         h = h->h_next;
1757     }
1758     
1759     return (NULL);
1760 }
1761
1762 void DtMail::MailRc::hm_mark(struct hash **table, char *key)
1763 {
1764     register struct hash *h;
1765     
1766     if (!table)
1767         return;
1768     
1769     h = table[hash_index(key)];
1770     
1771     while (h) {
1772         if (strcasecmp(h->h_key, key) == 0) {
1773             h->h_written = 1;
1774             return;
1775         }
1776         
1777         h = h->h_next;
1778     }
1779 }
1780
1781 int DtMail::MailRc::hm_ismarked(struct hash **table, char *key)
1782 {
1783     register struct hash *h;
1784     
1785     if (!table) return 0;
1786     h = table[hash_index(key)];
1787     while (h)
1788     {
1789         if (strcasecmp(h->h_key, key) == 0) return(h->h_written);
1790         h = h->h_next;
1791     }
1792
1793     return 0;
1794 }
1795
1796 void
1797 DtMail::MailRc::hm_scan(struct hash **table, hm_callback callback, void * client_data)
1798 {
1799     for (int slot = 0; slot < HASHSIZE; slot++) {
1800         for (struct hash * h = table[slot]; h; h = h->h_next) {
1801             if (!h->h_written) {
1802                 callback(h->h_key, h->h_value, client_data);
1803             }
1804             h->h_written = 0;
1805         }
1806     }
1807 }
1808
1809 int DtMail::MailRc::hash_index(char *key)
1810 {
1811     register unsigned h;
1812     register char *s;
1813     register int c;
1814     
1815     s = key;
1816     h = 0;
1817     while (*s) {
1818         c = *s++;
1819         if (isupper(c)) {
1820             c = tolower(c);
1821         }
1822         h = (h << 2) + c;
1823     }
1824     
1825     return (h % HASHSIZE);
1826 }
1827
1828 void DtMail::MailRc::free_hash(struct hash *h)
1829 {
1830     free(h->h_key);
1831     if (h->h_value != nullfield) {
1832         free(h->h_value);
1833     }
1834     free(h);
1835 }
1836
1837 /*
1838  * Add the given header fields to the ignored list.
1839  * If no arguments, print the current list of ignored fields.
1840  */
1841 int DtMail::MailRc::igfield(char *list[], DtMail::MailRc *)
1842 {
1843     char **ap;
1844     
1845     for (ap = list; *ap != 0; ap++) {
1846 //                DP(("igfield: adding %s\n", *ap));
1847         MailRc::add_ignore(*ap);
1848     }
1849     return(0);
1850 }
1851
1852 void
1853 DtMail::MailRc::wigfield(const char * verbatim, char ** list, FILE * outf)
1854 {
1855     char ** ap;
1856     Boolean ignore_written = FALSE;
1857
1858     // We need to see that every name in this line still exists. If not,
1859     // then we will make a second pass, rewriting the line with only the
1860     // valid ignores.
1861     //
1862     int good = 1;
1863     for (ap = list; *ap && good; ap++) {
1864         if (!hm_test((struct hash **)glob.g_ignore, *ap)) {
1865             good = 0;
1866             break;
1867         }
1868     }
1869
1870     if (good) {
1871         fwrite(verbatim, 1, strlen(verbatim), outf);
1872         for (ap = list; *ap; ap++) {
1873             hm_mark((struct hash **)glob.g_ignore, *ap);
1874         }
1875         return;
1876     }
1877
1878     // Create a new ignore line, leaving out the ignores that have
1879     // been removed. Also, dont write any ignores that have been
1880     // previously written.
1881     //
1882     for (ap = list; *ap; ap++) {
1883         if (hm_test((struct hash **)glob.g_ignore, *ap) &&
1884         !hm_ismarked((struct hash **)glob.g_ignore, *ap)) {
1885                 // Only write 'ignore' if there is something in the list
1886                 if (!ignore_written) {
1887                         fwrite("ignore ", 1, 7, outf);
1888                         ignore_written = TRUE;
1889                 }
1890                 fwrite(*ap, 1, strlen(*ap), outf);
1891                 fwrite(" ", 1, 1, outf);
1892                 hm_mark((struct hash **)glob.g_ignore, *ap);
1893         }
1894      }
1895      if (ignore_written)
1896         fwrite("\n", 1, 1, outf);
1897 }
1898
1899 void
1900 DtMail::MailRc::nigfield(char * key, void *, void * client_data)
1901 {
1902     DtVirtArray<char *> *value_list = (DtVirtArray<char *> *)client_data;
1903
1904     value_list->append(key);
1905 }
1906
1907 /* manage the retain/ignore list */
1908 void DtMail::MailRc::add_ignore(char *name)
1909 {
1910     if(! MailRc::hm_test((struct hash **)glob.g_ignore, name)) {
1911         /* name is not already there... */
1912         MailRc::hm_add((struct hash **)glob.g_ignore, name, NULL, 0);
1913     }
1914 }
1915
1916
1917 /*
1918  * Set the list of alternate names.
1919  */
1920 int DtMail::MailRc::alternates(char **namelist, DtMail::MailRc *)
1921 {
1922     while (*namelist != NULL)
1923         MailRc::add_alternates(*namelist++);
1924     return(0);
1925 }
1926
1927 void
1928 DtMail::MailRc::walternates(const char * verbatim, char ** list, FILE * outf)
1929 {
1930     // This is like ignores. We need to make sure all of the alternates
1931     // on this command are still in the database.
1932     //
1933     char ** ap;
1934     int good = 1;
1935     for (ap = list; *ap && good; ap++) {
1936         if (!hm_test((struct hash **)glob.g_alternates, *ap)) {
1937             good = 0;
1938             break;
1939         }
1940     }
1941
1942     if (good) {
1943         fwrite(verbatim, 1, strlen(verbatim), outf);
1944         for (ap = list; *ap; ap++) {
1945             hm_mark((struct hash **)glob.g_alternates, *ap);
1946         }
1947         return;
1948     }
1949
1950     // Write out the alternates that still exist and have not been
1951     // previously written.
1952     //
1953     Boolean written = FALSE;
1954     for (ap = list; *ap; ap++) {
1955         if (hm_test((struct hash **)glob.g_alternates, *ap) &&
1956             !hm_ismarked((struct hash **)glob.g_alternates, *ap)) {
1957             if (!written) {
1958                 fwrite("alternates ", 1, 11, outf);
1959                 written=TRUE;
1960             }
1961             fwrite(*ap, 1, strlen(*ap), outf);
1962             fwrite(" ", 1, 1, outf);
1963             hm_mark((struct hash **)glob.g_alternates, *ap);
1964         }
1965     }
1966 }
1967
1968 void
1969 DtMail::MailRc::nalternates(char * key, void *, void * client_data)
1970 {
1971     DtVirtArray<char *> *value_list = (DtVirtArray<char *> *)client_data;
1972
1973     value_list->append(key);
1974 }
1975
1976 /* manage the alternate name list */
1977 void DtMail::MailRc::add_alternates(char *name)
1978 {
1979     if(! MailRc::hm_test((struct hash**)glob.g_alternates, name)) {
1980         /* name is not already there... */
1981         MailRc::hm_add((struct hash **)glob.g_alternates, name, NULL, 0);
1982     }
1983 }
1984
1985 /*
1986  * Determine the current folder directory name.
1987  */
1988 int
1989 DtMail::MailRc::getfolderdir(char *name)
1990 {
1991         char *folder;
1992
1993         if ((folder = mt_value("folder")) == NOSTR)
1994                 return(-1);
1995         if (*folder == '/')
1996                 strcpy(name, folder);
1997         else
1998                 sprintf(name, "%s/%s", mt_value("HOME"), folder);
1999         return(0);
2000 }
2001
2002 /*
2003  * Take a file name, possibly with shell meta characters
2004  * in it and expand it by using "sh -c echo filename"
2005  * Return the file name as a dynamic string.
2006  */
2007
2008 char *
2009 DtMail::MailRc::expand(char *name)
2010 {
2011         char *xname = new char[LINESIZE];
2012         char *cmdbuf = new char[LINESIZE];
2013         char *str;
2014         register int pid, l;
2015         register char *cp, *Shell;
2016         int s, pivec[2];
2017         struct stat sbuf;
2018
2019         if (name[0] == '+' && getfolderdir(cmdbuf) >= 0) {
2020                 sprintf(xname, "%s/%s", cmdbuf, name + 1);
2021                 str = expand(xname);
2022                 delete [] xname;
2023                 delete [] cmdbuf;
2024                 return str;
2025         }
2026         if (!strpbrk(name, "~{[*?$`'\"\\")) {
2027                 return(strdup(name));
2028         }
2029         if (pipe(pivec) < 0) {
2030                 perror("pipe");
2031                 delete [] xname;
2032                 delete [] cmdbuf;
2033                 return(strdup(name));
2034         }
2035         sprintf(cmdbuf, "echo %s", name);
2036         if ((pid = DTMAIL_FORK()) == 0) {
2037                 Shell = mt_value("SHELL");
2038                 if (Shell == NOSTR || *Shell=='\0')
2039                         Shell = "/bin/sh";
2040                 close(pivec[0]);
2041                 close(1);
2042                 dup(pivec[1]);
2043                 close(pivec[1]);
2044                 close(2);
2045                 execlp(Shell, Shell, "-c", cmdbuf, (char *)0);
2046                 _exit(1);
2047         }
2048         if (pid == -1) {
2049                 perror("fork");
2050                 close(pivec[0]);
2051                 close(pivec[1]);
2052                 delete [] xname;
2053                 delete [] cmdbuf;
2054                 return(NOSTR);
2055         }
2056         close(pivec[1]);
2057         l = read(pivec[0], xname, LINESIZE);
2058         close(pivec[0]);
2059         while (wait(&s) != pid);
2060                 ;
2061         s &= 0377;
2062         if (s != 0 && s != SIGPIPE) {
2063                 fprintf(stderr, "Echo failed\n");
2064                 goto err;
2065         }
2066         if (l < 0) {
2067                 perror("read");
2068                 goto err;
2069         }
2070         if (l == 0) {
2071                 fprintf(stderr, "%s: No match\n", name);
2072                 goto err;
2073         }
2074         if (l == LINESIZE) {
2075                 fprintf(stderr, "Buffer overflow expanding %s\n", name);
2076                 goto err;
2077         }
2078         xname[l] = 0;
2079         for (cp = &xname[l-1]; *cp == '\n' && cp > xname; cp--)
2080                 ;
2081         *++cp = '\0';
2082         if (strchr(xname, ' ') && stat(xname, &sbuf) < 0) {
2083                 fprintf(stderr, "%s: Ambiguous\n", name);
2084                 goto err;
2085         }
2086
2087         delete [] xname;
2088         delete [] cmdbuf;
2089         return(strdup(xname));
2090
2091 err:
2092         fflush(stderr);
2093         delete [] xname;
2094         delete [] cmdbuf;
2095         return(NOSTR);
2096 }
2097
2098 int
2099 DtMail::MailRc::source(char **arglist, DtMail::MailRc *self)
2100 {
2101   char *fname = arglist[0];
2102   FILE *fi;
2103   char *cp;
2104
2105   /* if any of the three if test failed, return 0,
2106    * since we have not updated the input and stack pointer yet
2107    */
2108   if ((cp = self->expand(fname)) == NOSTR)
2109     return(0);
2110
2111   if ((fi = fopen(cp, "r")) == NULL) {
2112     free(cp);
2113     return(0);
2114   }
2115   free(cp);
2116
2117   if (self->ssp >= NOFILE - 2) {
2118     fclose(fi);
2119     return(0);
2120   }
2121   self->sstack[++(self->ssp)].s_file = self->input;
2122   self->sstack[self->ssp].s_cond = self->cond;
2123   self->cond = CANY;
2124   self->input = fi;
2125   self->sourcing++;
2126
2127   return(0);
2128 }
2129
2130 void
2131 DtMail::MailRc::wsource(const char * verbatim, char ** arglist, FILE * outf)
2132 {
2133   arglist = arglist;
2134   if(verbatim != NULL && outf != NULL)
2135     fwrite(verbatim, 1, strlen(verbatim), outf);
2136 }
2137
2138 int
2139 DtMail::MailRc::ifcmd(char **arglist, DtMail::MailRc *self)
2140 {
2141         register char *cp;
2142
2143         if (self->cond != CANY) {
2144                 fprintf(stderr,"Illegal nested \"if\"\n");
2145                 return(1);
2146         }
2147         self->cond = CANY;
2148         cp = arglist[0];
2149         switch (*cp) {
2150         case 'r': case 'R':
2151                 self->cond = CRCV;
2152                 break;
2153
2154         case 's': case 'S':
2155                 self->cond = CSEND;
2156                 break;
2157
2158         case 't': case 'T':
2159                 self->cond = CTTY;
2160                 break;
2161
2162         default:
2163                 fprintf(stderr,"Unrecognized if-keyword: \"%s\"\n",
2164                         cp);
2165                 return(1);
2166         }
2167         return(0);
2168 }
2169
2170 void
2171 DtMail::MailRc::wifcmd(const char * verbatim, char ** arglist, FILE * outf)
2172 {
2173   arglist = arglist;
2174   if(verbatim != NULL && outf != NULL)
2175     fwrite(verbatim, 1, strlen(verbatim), outf);
2176 }
2177
2178
2179 int
2180 DtMail::MailRc::elsecmd(char **, DtMail::MailRc *self)
2181 {
2182
2183         switch (self->cond) {
2184         case CANY:
2185                 fprintf(stderr, "\"Else\" without matching \"if\"\n");
2186                 return(1);
2187
2188         case CSEND:
2189                 self->cond = CRCV;
2190                 break;
2191
2192         case CRCV:
2193                 self->cond = CSEND;
2194                 break;
2195
2196         case CTTY:
2197                 self->cond = CNOTTY;
2198                 break;
2199
2200         case CNOTTY:
2201                 self->cond = CTTY;
2202                 break;
2203
2204         default:
2205                 fprintf(stderr,"invalid condition encountered\n");
2206                 self->cond = CANY;
2207                 break;
2208         }
2209         return(0);
2210 }
2211
2212 void
2213 DtMail::MailRc::welsecmd(const char * verbatim, char ** arglist, FILE * outf)
2214 {
2215   arglist = arglist;
2216   if(verbatim != NULL && outf != NULL)
2217     fwrite(verbatim, 1, strlen(verbatim), outf);
2218 }
2219
2220
2221 int
2222 DtMail::MailRc::endifcmd(char **, DtMail::MailRc *self)
2223 {
2224
2225         if (self->cond == CANY) {
2226                 fprintf(stderr,"\"Endif\" without matching \"if\"\n");
2227                 return(1);
2228         }
2229         self->cond = CANY;
2230         return(0);
2231 }
2232
2233 void
2234 DtMail::MailRc::wendifcmd(const char * verbatim, char ** arglist, FILE * outf)
2235 {
2236   arglist = arglist;
2237   if(verbatim != NULL && outf != NULL)
2238     fwrite(verbatim, 1, strlen(verbatim), outf);
2239 }
2240
2241 int
2242 DtMail::MailRc::clearaliases(char **, DtMail::MailRc *)
2243 {
2244         DtVirtArray<char *>   *value_list = NULL;
2245         DtMailEnv error;
2246         
2247         value_list = new DtVirtArray<char *>(10);
2248
2249         hm_scan((struct hash **)glob.g_alias, nalias, value_list);
2250
2251         while (value_list->length()) {
2252                 char *buf, *val = (*value_list)[0];
2253                 if ((buf = strchr(val, ' ')) != NULL)
2254                         *buf = '\0';
2255                 if (hm_test((struct hash **)glob.g_alias, val))
2256                         hm_delete((struct hash **)glob.g_alias, val);
2257                 value_list->remove(0);
2258         }
2259         delete value_list;
2260         clearAliases = 1;
2261         return 0;
2262 }
2263 void
2264 DtMail::MailRc::wclearaliases(const char *, char **, FILE *)
2265 {
2266 }