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