1 /* Mail processing routines for GNATS.
2    Copyright (C) 2000, 2002 Free Software Foundation, Inc.
3    Contributed by Bob Manson (manson@juniper.net).
4 
5 This file is part of GNU GNATS.
6 
7 GNU GNATS is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
11 
12 GNU GNATS is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with GNU GNATS; see the file COPYING.  If not, write to the Free
19 Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111, USA.  */
20 
21 #include "gnats.h"
22 #include "query.h"
23 #include "pcodes.h"
24 #include "mail.h"
25 
26 void
addMessageFormat(DatabaseInfo database,MailMessageFormat format)27 addMessageFormat (DatabaseInfo database, MailMessageFormat format)
28 {
29   format->next = getMailFormatList (database);
30   setMailFormatList (database, format);
31 }
32 
33 MailMessageFormat
findMailFormat(const DatabaseInfo database,const char * name)34 findMailFormat (const DatabaseInfo database, const char *name)
35 {
36   MailMessageFormat p;
37 
38   for (p = getMailFormatList (database); p != NULL; p = p->next)
39     {
40       if (strcmp (p->name, name) == 0)
41 	{
42 	  break;
43 	}
44     }
45   return p;
46 }
47 
48 /* get_responsible_address - dredges the responsible party out of the
49      appropriate places.  This routine should be NIS Correct, but it isn't.  */
50 
51 AdmEntry *
get_responsible_address(const DatabaseInfo database,const char * person)52 get_responsible_address (const DatabaseInfo database, const char *person)
53 {
54   AdmEntry *res = NULL;
55   AdmEntry *responsible_chain = NULL;
56 
57   if (fieldDefForIndex (RESPONSIBLE (database)) != NULL)
58     {
59       responsible_chain
60 	= fieldDefForIndex (RESPONSIBLE (database))->adm_contents;
61       res = find_chain_entry (responsible_chain, person);
62     }
63 
64   /* First check the responsible file; if there's no entry, try the
65      passwd file.  */
66   if (res != NULL)
67     {
68       if (res->admFields[ResponsibleAdmAlias] == NULL ||
69 	  res->admFields[ResponsibleAdmAlias] == '\0')
70 	{
71 	  if (res->admFields[ResponsibleAdmAlias] != NULL)
72 	    {
73 	      free (res->admFields[ResponsibleAdmAlias]);
74 	    }
75 	  res->admFields[ResponsibleAdmAlias]
76 	    = xstrdup (res->admFields[ResponsibleAdmKey]);
77 	}
78     }
79   else
80     {
81       struct passwd *passwd;
82       char *p;
83 
84       res = alloc_adm_entry (3);
85       res->field = RESPONSIBLE (database);
86 
87       /* We should always allow names to show up as responsible, even
88 	 if they aren't listed in the passwd file---folks don't remember
89 	 (or usually need) to change closed PRs if the listed person
90 	 happens to leave the company.  */
91       res->admFields[ResponsibleAdmKey] = (char *) xstrdup (person);
92       res->admFields[ResponsibleAdmAlias] = (char *) xstrdup (person);
93 
94       if ((passwd = getpwnam (person)) != 0)
95 	{
96 	  /* Some passwd entries have commas for finger programs that
97 	     understand office phone numbers, etc.  Chop 'em off.  */
98 	  p = (char *) strchr (passwd->pw_gecos, ',');
99 	  if (p != NULL)
100 	    *p = '\0';
101 	  res->admFields[ResponsibleAdmFullname]
102 	    = (char *) xstrdup (passwd->pw_gecos);
103 	}
104       else
105 	{
106 	  res->admFields[ResponsibleAdmFullname] = xstrdup("");
107 	}
108     }
109 
110   return res;
111 }
112 
113 
114 static char *
get_one_responsible_addr(const DatabaseInfo database,int full,int strict,const char * name)115 get_one_responsible_addr (const DatabaseInfo database,
116 			  int full, int strict, const char *name)
117 {
118   AdmEntry *r;
119   char *p;
120   char *address = NULL;
121 
122   /* If it already vaguely resembles a full email address, then just
123      return it as is.  */
124   if (strpbrk (name, "<(@") != NULL)
125     {
126       return xstrdup (name);
127     }
128 
129   /* Strip off (Foo Bar) or anything after a space in the name. */
130   p = (char *) strchr (name, ' ');
131   if (p != (char *) NULL)
132     {
133       *p = '\0';
134     }
135   p = (char *) strchr (name, '(');
136   if (p != (char *) NULL)
137     {
138       *p = '\0';
139     }
140 
141   r = get_responsible_address (database, name);
142 
143   if (r != NULL && ((! strict)
144 		    || r->admFields[ResponsibleAdmFullname][0] != '\0'))
145     {
146       if (full)
147 	{
148 	  asprintf (&address, "%s:%s:%s", r->admFields[ResponsibleAdmKey],
149 		    r->admFields[ResponsibleAdmFullname],
150 		    r->admFields[ResponsibleAdmAlias]);
151 	}
152       else
153 	{
154 	  /* Make sure if the person putting the entry in accidentally
155 	     added spaces or such after the colon, we strip them off.
156 
157 	     XXX ??? !!! We shouldn't do this here; instead, make sure
158 	     the entry we return from get_responsible_address () is
159 	     clean, or better still, do it when we read entries from
160 	     the adm files.  */
161 	  char *addr = r->admFields[ResponsibleAdmAlias];
162 
163 	  while (*addr != '\0' && ! isalpha ((int) *addr))
164 	    {
165 	      addr++;
166 	    }
167 	  if (*addr != '\0')
168 	    {
169 	      address = xstrdup (addr);
170 	    }
171 	  else
172 	    {
173 	      address = xstrdup (r->admFields[ResponsibleAdmKey]);
174 	    }
175 	}
176     }
177   free_adm_entry (r);
178 
179   return address;
180 }
181 
182 static char *
getOneAddress(const char ** addrPtr)183 getOneAddress (const char **addrPtr)
184 {
185   const char *addr = *addrPtr;
186   const char *addrStart, *addrEnd;
187   char *res;
188 
189   if (addrPtr == NULL || *addrPtr == NULL)
190     {
191       return NULL;
192     }
193 
194   /* skip over leading white space */
195   while (*addr && isspace((int)(unsigned char)*addr))
196     {
197       addr++;
198     }
199   addrStart = addr;
200 
201   while (*addr != ',' && *addr != '\0')
202     {
203       char nextc = '\0';
204 
205       switch (*addr)
206 	{
207 	case '"':
208 	  nextc = '"';
209 	  break;
210 	case '(':
211 	  nextc = ')';
212 	  break;
213 	case '<':
214 	  nextc = '>';
215 	  break;
216 	}
217       if (nextc != '\0')
218 	{
219 	  /* XXX ??? !!! This probably isn't handling backquotes properly.  */
220 	  char *naddr = strchr (addr + 1, nextc);
221 	  if (naddr == NULL)
222 	    {
223 	      addr += strlen (addr);
224 	      break;
225 	    }
226 	  addr = naddr;
227 	}
228       addr++;
229     }
230   addrEnd = addr;
231 
232   /* ignore any ending white space */
233   while (addr > addrStart && isspace((int)(unsigned char)*(addr-sizeof(*addr))))
234     {
235       addr--;
236     }
237 
238   if (addr <= addrStart)
239     res = NULL;
240   else
241     res = xstrndup (addrStart, addr - addrStart);
242 
243   *addrPtr = (*addrEnd == '\0') ? NULL : addrEnd+1;
244   return res;
245 }
246 
247 char *
get_responsible_addr(const DatabaseInfo database,int full,int strict,const char * name)248 get_responsible_addr (const DatabaseInfo database,
249 		      int full, int strict, const char *name)
250 {
251   const char *c = name;
252   char *res = NULL;
253 
254   while (c != NULL)
255     {
256       char *respaddr;
257       char *addr = getOneAddress (&c);
258       if (addr == NULL || *addr == '\0')
259         {
260 	  break;
261         }
262       respaddr = get_one_responsible_addr (database, full, strict, addr);
263 
264       if (respaddr != NULL)
265 	{
266 	  if (res != NULL)
267 	    {
268 	      append_string (&res, ",");
269 	    }
270 	  append_string (&res, respaddr);
271 	  free (respaddr);
272 	}
273       free (addr);
274     }
275   return res;
276 }
277 
278 char *
generateMailAddress(PR * pr,PR * oldPR,MailAddress * address,FormatNamedParameter * params)279 generateMailAddress (PR *pr, PR *oldPR, MailAddress *address,
280 		     FormatNamedParameter *params)
281 {
282   FieldList addrList;
283 
284   if (address->fixedAddress != NULL)
285     {
286       return get_responsible_addr (pr->database, 0, 0, address->fixedAddress);
287     }
288 
289   for (addrList = address->addresses;
290        addrList != NULL;
291        addrList = addrList->next)
292     {
293       int mustBeFreed = 0;
294 
295       const char *value = get_field_value (pr, oldPR, addrList->ent, params,
296 					   &mustBeFreed);
297 
298       if (value != NULL && value[0] != '\0')
299 	{
300 	  char *res = get_responsible_addr (pr->database, 0, 0, value);
301 	  size_t len = strlen (res);
302 	  /* Badness 10000. XXX ??? !!! */
303 	  if (res[len - 1] == '\n')
304 	    {
305 	      res[len - 1] = '\0';
306 	    }
307 	  if (mustBeFreed)
308 	    {
309 	      free ((char *) value);
310 	    }
311 	  return res;
312 	}
313       if (mustBeFreed && value != NULL)
314 	{
315 	  free ((char *) value);
316 	}
317     }
318   return NULL;
319 }
320 
321 static char *
insertAddress(char * addressList,const char * addrToInsert)322 insertAddress (char *addressList, const char *addrToInsert)
323 {
324   if (addrToInsert == NULL)
325     {
326       return addressList;
327     }
328   else if (addressList == NULL)
329     {
330       return xstrdup (addrToInsert);
331     }
332   else
333     {
334       size_t oldlen = strlen (addressList);
335       size_t addrToInsertLen = strlen (addrToInsert);
336       size_t newlen = oldlen + addrToInsertLen + 2;
337       char *p = addressList;
338       char *res;
339 
340       while (*p != '\0')
341 	{
342 	  char *prevp = p;
343 	  char *lastCharInAddr;
344 
345 	  p = strchr (p, ',');
346 	  if (p == NULL)
347 	    {
348 	      p = prevp + strlen (prevp);
349 	    }
350 
351 	  lastCharInAddr = p - 1;
352 
353 	  while (lastCharInAddr > prevp && isspace ((int)(unsigned char) *lastCharInAddr))
354 	    {
355 	      lastCharInAddr--;
356 	    }
357 
358 	  if (((size_t) (lastCharInAddr - prevp + 1)) == addrToInsertLen
359 	      && memcmp (prevp, addrToInsert, addrToInsertLen) == 0)
360 	    {
361 	      return addressList;
362 	    }
363 	  if (*p == ',')
364 	    {
365 	      p++;
366 	    }
367 	}
368 
369       res = xrealloc (addressList, newlen);
370       res[oldlen] = ',';
371       strcpy (res + oldlen + 1, addrToInsert);
372       return res;
373     }
374 }
375 
376 /* Composes a mail message using FORMAT as the mail message format. */
377 int
composeMailMessage(PR * pr,PR * oldPR,const char * formatName,FormatNamedParameter * parameters,BadFields bad_fields,ErrorDesc * err ATTRIBUTE_UNUSED)378 composeMailMessage (PR *pr, PR *oldPR, const char *formatName,
379 		    FormatNamedParameter *parameters,
380 		    BadFields bad_fields,
381 		    ErrorDesc *err ATTRIBUTE_UNUSED)
382 {
383   MailMessageFormat format = findMailFormat (pr->database, formatName);
384   FILE *msg;
385   char *fromAddr;
386   char *toAddr = NULL;
387   char *replyToAddr = NULL;
388   MailAddressList *p;
389 
390   if (format == NULL)
391     {
392       return -1;
393     }
394 
395   block_signals ();
396 
397   fromAddr = generateMailAddress (pr, oldPR, format->fromAddress, parameters);
398   if (fromAddr == NULL)
399     {
400       fromAddr = (char *)gnatsAdminMailAddr (pr->database);
401     }
402   for (p = format->toAddresses; p != NULL; p = p->next)
403     {
404       char *theAddr = generateMailAddress (pr, oldPR, p->address, parameters);
405       if (theAddr != NULL)
406 	{
407 	  toAddr = insertAddress (toAddr, theAddr);
408 	  free (theAddr);
409 	}
410     }
411 
412   for (p = format->replyTo; p != NULL; p = p->next)
413     {
414       char *theAddr = generateMailAddress (pr, oldPR, p->address, parameters);
415       if (theAddr != NULL)
416 	{
417 	  replyToAddr = insertAddress (replyToAddr, theAddr);
418 	  free (theAddr);
419 	}
420     }
421 
422   msg = open_mail_file (pr->database);
423   if (msg == NULL)
424     {
425       return -1;
426     }
427   fprintf (msg, "From: %s\n", fromAddr);
428   fprintf (msg, "To: %s\n", toAddr);
429   if (replyToAddr != NULL)
430     {
431       fprintf (msg, "Reply-To: %s\n", replyToAddr);
432     }
433   process_format (msg, NULL, pr, oldPR, format->header, "\n", parameters);
434   fprintf (msg, "\n");
435 
436   /* XXX ??? !!! This should be handled as a format parameter.  */
437   if (bad_fields != NULL)
438     {
439       /* Report the fields that were bad, separating them by an empty line.  */
440       BadFields t;
441       for (t = bad_fields; t != NULL ; t = nextBadField (t))
442 	{
443 	  FieldIndex field = badFieldIndex (t);
444 	  const char *value = badFieldValue (t);
445 	  if (value != NULL)
446 	    {
447 	      fprintf (msg,
448 		       "\tNote: There was a bad value `%s' for the field `%s'.\n\tIt was set to the default value of `%s'.\n\n",
449 		       value, fieldDefForIndex (field)->name,
450 		       fieldDefForIndex (field)->default_value);
451 	    }
452 	  else
453 	    {
454 	      fprintf (msg,
455 		       "\tNote: There was a missing value for the field `%s'.\n\tIt was set to the default value of `%s'.\n\n",
456 		       fieldDefForIndex (field)->name,
457 		       fieldDefForIndex (field)->default_value);
458 	    }
459 	}
460     }
461 
462   process_format (msg, NULL, pr, oldPR, format->body, "\n", parameters);
463   close_mail_file (msg);
464 
465   free (fromAddr);
466   free (toAddr);
467   if (replyToAddr != NULL)
468     {
469       free (replyToAddr);
470     }
471 
472   unblock_signals ();
473 
474   return 0;
475 }
476 
477 FormatNamedParameter *
allocateNamedParameter(const char * name,const char * value,FormatNamedParameter * next)478 allocateNamedParameter (const char *name, const char *value,
479 			FormatNamedParameter *next)
480 {
481   FormatNamedParameter *res
482     = (FormatNamedParameter *) xmalloc (sizeof (FormatNamedParameter));
483   res->name = xstrdup (name);
484   if (value != NULL)
485     {
486       res->value  = xstrdup (value);
487     }
488   else
489     {
490       res->value = NULL;
491     }
492   res->next = next;
493   return res;
494 }
495 
496 const char *
getNamedParameterValue(FormatNamedParameter * list,const char * name)497 getNamedParameterValue (FormatNamedParameter *list, const char *name)
498 {
499   while (list != NULL)
500     {
501       if (strcmp (list->name, name) == 0)
502 	{
503 	  return list->value;
504 	}
505       list = list->next;
506     }
507   return NULL;
508 }
509 
510 void
freeFormatParameterList(FormatNamedParameter * list)511 freeFormatParameterList (FormatNamedParameter *list)
512 {
513   while (list != NULL)
514     {
515       FormatNamedParameter *next = list->next;
516       free (list->name);
517       free (list->value);
518       free (list);
519       list = next;
520     }
521 }
522 
523 void
freeMailAddress(MailAddress * p)524 freeMailAddress (MailAddress *p)
525 {
526   if (p != NULL)
527     {
528       if (p->fixedAddress != NULL)
529 	{
530 	  free (p->fixedAddress);
531 	}
532       if (p->addresses != NULL)
533 	{
534 	  freeFieldList (p->addresses);
535 	}
536       free (p);
537     }
538 }
539 
540 void
freeMailAddressList(MailAddressList * p)541 freeMailAddressList (MailAddressList *p)
542 {
543   while (p != NULL)
544     {
545       MailAddressList *next = p->next;
546       freeMailAddress (p->address);
547       free (p);
548       p = next;
549     }
550 }
551 
552 void
freeMessageFormat(MailMessageFormat p)553 freeMessageFormat (MailMessageFormat p)
554 {
555   if (p->name != NULL)
556     {
557       free (p->name);
558     }
559   freeMailAddressList (p->toAddresses);
560   freeMailAddress (p->fromAddress);
561   freeMailAddressList (p->replyTo);
562   freeQueryFormat (p->header);
563   freeQueryFormat (p->body);
564   free (p);
565 }
566 
567 void
freeMessageFormatList(MailMessageFormat formatList)568 freeMessageFormatList (MailMessageFormat formatList)
569 {
570   while (formatList != NULL)
571     {
572       MailMessageFormat next = formatList->next;
573       freeMessageFormat (formatList);
574       formatList = next;
575     }
576 }
577