home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1994 March / Source_Code_CD-ROM_Walnut_Creek_March_1994.iso / compsrcs / misc / volume41 / mailagnt / part11 < prev    next >
Encoding:
Text File  |  1993-12-02  |  53.8 KB  |  1,589 lines

  1. Newsgroups: comp.sources.misc
  2. From: Raphael Manfredi <ram@acri.fr>
  3. Subject: v41i011:  mailagent - Flexible mail filtering and processing package, v3.0, Part11/26
  4. Message-ID: <1993Dec2.133918.18570@sparky.sterling.com>
  5. X-Md4-Signature: ca55f9db7a7e930cd14e0fd5351ba0ab
  6. Sender: kent@sparky.sterling.com (Kent Landfield)
  7. Organization: Advanced Computer Research Institute, Lyon, France.
  8. Date: Thu, 2 Dec 1993 13:39:18 GMT
  9. Approved: kent@sparky.sterling.com
  10.  
  11. Submitted-by: Raphael Manfredi <ram@acri.fr>
  12. Posting-number: Volume 41, Issue 11
  13. Archive-name: mailagent/part11
  14. Environment: UNIX, Perl
  15. Supersedes: mailagent: Volume 33, Issue 93-109
  16.  
  17. #! /bin/sh
  18. # This is a shell archive.  Remove anything before this line, then feed it
  19. # into a shell via "sh file" or similar.  To overwrite existing files,
  20. # type "sh file -c".
  21. # The tool that generated this appeared in the comp.sources.unix newsgroup;
  22. # send mail to comp-sources-unix@uunet.uu.net if you want that tool.
  23. # Contents:  agent/filter/io.c agent/filter/parser.c
  24. #   agent/pl/matching.pl
  25. # Wrapped by ram@soft208 on Mon Nov 29 16:49:56 1993
  26. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  27. echo If this archive is complete, you will see the following message:
  28. echo '          "shar: End of archive 11 (of 26)."'
  29. if test -f 'agent/filter/io.c' -a "${1}" != "-c" ; then 
  30.   echo shar: Will not clobber existing file \"'agent/filter/io.c'\"
  31. else
  32.   echo shar: Extracting \"'agent/filter/io.c'\" \(17850 characters\)
  33.   sed "s/^X//" >'agent/filter/io.c' <<'END_OF_FILE'
  34. X/*
  35. X
  36. X    #     ####            ####
  37. X    #    #    #          #    #
  38. X    #    #    #          #
  39. X    #    #    #   ###    #
  40. X    #    #    #   ###    #    #
  41. X    #     ####    ###     ####
  42. X
  43. X    I/O routines.
  44. X*/
  45. X
  46. X/*
  47. X * $Id: io.c,v 3.0 1993/11/29 13:48:10 ram Exp ram $
  48. X *
  49. X *  Copyright (c) 1990-1993, Raphael Manfredi
  50. X *  
  51. X *  You may redistribute only under the terms of the Artistic License,
  52. X *  as specified in the README file that comes with the distribution.
  53. X *  You may reuse parts of this distribution only within the terms of
  54. X *  that same Artistic License; a copy of which may be found at the root
  55. X *  of the source tree for mailagent 3.0.
  56. X *
  57. X * $Log: io.c,v $
  58. X * Revision 3.0  1993/11/29  13:48:10  ram
  59. X * Baseline for mailagent 3.0 netwide release.
  60. X *
  61. X */
  62. X
  63. X#include "config.h"
  64. X#include "portable.h"
  65. X#include <sys/types.h>
  66. X#include "hash.h"
  67. X#include "parser.h"
  68. X#include "lock.h"
  69. X#include "logfile.h"
  70. X#include "environ.h"
  71. X#include "sysexits.h"
  72. X#include <stdio.h>
  73. X#include <errno.h>
  74. X#include <sys/stat.h>
  75. X
  76. X#ifdef I_SYS_WAIT
  77. X#include <sys/wait.h>
  78. X#endif
  79. X
  80. X#ifdef I_FCNTL
  81. X#include <fcntl.h>
  82. X#else
  83. X#include <sys/fcntl.h>
  84. X#endif
  85. X#ifdef I_SYS_FILE
  86. X#include <sys/file.h>
  87. X#endif
  88. X
  89. X#ifdef I_STRING
  90. X#include <string.h>
  91. X#else
  92. X#include <strings.h>
  93. X#endif
  94. X#include "confmagic.h"
  95. X
  96. X#define BUFSIZE        1024            /* Amount of bytes read in a single call */
  97. X#define CHUNK        (10 * BUFSIZE)    /* Granularity of pool */
  98. X#define MAX_STRING    2048            /* Maximum string's length */
  99. X#define AGENT_WAIT    "agent.wait"    /* File listing out-of-the-queue mails */
  100. X#define AGENT_LOCK    "perl.lock"        /* Lock file used by mailagent */
  101. X
  102. Xprivate void pool_realloc();    /* Extend pool zone */
  103. Xprivate int get_lock();            /* Attempt to get a lockfile */
  104. Xprivate void release_agent();    /* Remove mailagent's lock if needed */
  105. Xprivate int process_mail();        /* Process mail by feeding the mailagent */
  106. Xprivate void queue_mail();        /* Queue mail for delayed processing */
  107. Xprivate char *write_file();        /* Write mail on disk */
  108. Xprivate char *save_file();        /* Emergency saving into a file */
  109. X
  110. Xprivate char *mail = (char *) 0;    /* Where mail is stored */
  111. Xprivate int len;                    /* Mail length in bytes */
  112. Xprivate int queued = 0;                /* True when mail queued safely */
  113. X
  114. Xextern int errno;                /* System call error status */
  115. Xextern char *malloc();            /* Memory allocation */
  116. Xextern char *realloc();            /* Re-allocation of memory pool */
  117. Xextern char *logname();            /* User's login name */
  118. Xextern int loglvl;                /* Logging level */
  119. X
  120. Xprivate void read_stdin()
  121. X{
  122. X    /* Read the whole stdandard input into memory and return a pointer to its
  123. X     * location in memory. Any I/O error is fatal. Set the length of the
  124. X     * data read into 'len'.
  125. X     */
  126. X
  127. X    int size;                    /* Current size of memory pool */
  128. X    int amount = 0;                /* Total amount of data read */
  129. X    int n;                        /* Bytes read by last system call */
  130. X    char *pool;                    /* Where input is stored */
  131. X    char buf[BUFSIZE];
  132. X
  133. X    size = CHUNK;
  134. X    pool = malloc(size);
  135. X    if (pool == (char *) 0)
  136. X        fatal("out of memory");
  137. X
  138. X    add_log(19, "reading mail");
  139. X
  140. X    while (n = read(0, buf, BUFSIZE)) {
  141. X        if (n == -1) {
  142. X            add_log(1, "SYSERR read: %m (%e)");
  143. X            fatal("I/O error");
  144. X        }
  145. X        if (size - amount < n)                /* Pool not big enough */
  146. X            pool_realloc(&pool, &size);        /* Resize it or fail */
  147. X        bcopy(buf, pool + amount, n);        /* Copy read bytes */
  148. X        amount += n;                        /* Update amount of bytes read */
  149. X    }
  150. X
  151. X    len = amount;                /* Indicate how many bytes where read */
  152. X
  153. X    add_log(16, "got mail (%d bytes)", amount);
  154. X
  155. X    mail = pool;                /* Where mail is stored */
  156. X}
  157. X
  158. Xpublic void process()
  159. X{
  160. X    char *queue;                        /* Location of mailagent's queue */
  161. X
  162. X    (void) umask(077);                    /* Files we create are private ones */
  163. X
  164. X    queue = ht_value(&symtab, "queue");    /* Fetch queue location */
  165. X    if (queue == (char *) 0)
  166. X        fatal("queue directory not defined");
  167. X
  168. X    read_stdin();                        /* Read mail */
  169. X    (void) get_lock();                    /* Get a lock file */
  170. X    queue_mail(queue);                    /* Process also it locked */
  171. X    release_lock();                        /* Release lock file if necessary */
  172. X}
  173. X
  174. Xpublic int was_queued()
  175. X{
  176. X    return queued;            /* Was mail queued? */
  177. X}
  178. X
  179. Xprivate void pool_realloc(pool, size)
  180. Xchar **pool;
  181. Xint *size;
  182. X{
  183. X    /* Make more room in pool and update parameters accordingly */
  184. X
  185. X    char *cpool = *pool;    /* Current location */
  186. X    int csize = *size;        /* Current size */
  187. X
  188. X    csize += CHUNK;
  189. X    cpool = realloc(cpool, csize);
  190. X    if (cpool == (char *) 0)
  191. X        fatal("out of memory");
  192. X    *pool = cpool;
  193. X    *size = csize;
  194. X}
  195. X
  196. Xprivate int get_lock()
  197. X{
  198. X    /* Try to get a filter lock in the spool directory. Propagate the return
  199. X     * status of filter_lock(): 0 for ok, -1 for failure.
  200. X     */
  201. X
  202. X    char *spool;                        /* Location of spool directory */
  203. X
  204. X    spool = ht_value(&symtab, "spool");    /* Fetch spool location */
  205. X    if (spool == (char *) 0)
  206. X        fatal("spool directory not defined");
  207. X
  208. X    return filter_lock(spool);            /* Get a lock in spool directory */
  209. X}
  210. X
  211. Xprivate void release_agent()
  212. X{
  213. X    /* In case of abnormal failure, the mailagent may leave its lock file
  214. X     * in the spool directory. Remove it if necessary.
  215. X     */
  216. X
  217. X    char *spool;                    /* Location of spool directory */
  218. X    char agentlock[MAX_STRING];        /* Where lock file is held */
  219. X    struct stat buf;                /* Stat buffer */
  220. X
  221. X    spool = ht_value(&symtab, "spool");    /* Fetch spool location */
  222. X    if (spool == (char *) 0)            /* Should not happen */
  223. X        return;
  224. X
  225. X    sprintf(agentlock, "%s/%s", spool, AGENT_LOCK);
  226. X    if (-1 == stat(agentlock, &buf))
  227. X        return;                        /* Assume no lock file left behind */
  228. X
  229. X    if (-1 == unlink(agentlock)) {
  230. X        add_log(1, "SYSERR unlink: %m (%e)");
  231. X        add_log(2, "ERROR could not remove mailagent's lock");
  232. X    } else
  233. X        add_log(5, "NOTICE removed mailagent's lock");
  234. X}
  235. X
  236. Xprivate void queue_mail(queue)
  237. Xchar *queue;                /* Location of the queue directory */
  238. X{
  239. X    char *where;            /* Where mail is stored */
  240. X    char real[MAX_STRING];    /* Real queue mail */
  241. X    char *base;                /* Pointer to base name */
  242. X    struct stat buf;        /* To make sure queued file remains */
  243. X
  244. X    where = write_file(queue, "Tm");
  245. X    if (where == (char *) 0) {
  246. X        add_log(1, "ERROR unable to queue mail");
  247. X        fatal("try again later");
  248. X    }
  249. X
  250. X    /* If we have a lock, create a qm* file suitable for mailagent processing.
  251. X     * Otherwise, create a fm* file and the mailagent will process it
  252. X     * immediately.
  253. X     */
  254. X    if (is_locked())
  255. X        sprintf(real, "%s/%s%d", queue, "qm", progpid);
  256. X    else
  257. X        sprintf(real, "%s/%s%d", queue, "fm", progpid);
  258. X
  259. X    if (-1 == rename(where, real)) {
  260. X        add_log(1, "SYSERR rename: %m (%e)");
  261. X        add_log(2, "ERROR could not rename %s into %s", where, real);
  262. X        fatal("try again later");
  263. X    }
  264. X
  265. X    /* Compute base name of queued mail */
  266. X    base = rindex(real, '/');
  267. X    if (base++ == (char *) 0)
  268. X        base = real;
  269. X
  270. X    add_log(4, "QUEUED [%s] %d bytes", base, len);
  271. X    queued = 1;
  272. X
  273. X    /* If we got a lock, then no mailagent is running and we may process the
  274. X     * mail. Otherwise, do nothing. The mail will be processed by the currently
  275. X     * active mailagent.
  276. X     */
  277. X
  278. X    if (!is_locked())            /* Another mailagent is running */
  279. X        return;                    /* Leave mail in queue */
  280. X
  281. X    if (0 == process_mail(real)) {
  282. X        /* Mailagent may have simply queued the mail for itself by renaming
  283. X         * it, so of course we would not be able to remove it. Hence the
  284. X         * test for ENOENT to avoid error messages when the file does not
  285. X         * exit any more.
  286. X         */
  287. X        if (-1 == unlink(real) && errno != ENOENT) {
  288. X            add_log(1, "SYSERR unlink: %m (%e)");
  289. X            add_log(2, "ERROR could not remove queued mail");
  290. X        }
  291. X        return;
  292. X    }
  293. X    /* Paranoia: make sure the queued mail is still there */
  294. X    if (-1 == stat(real, &buf)) {
  295. X        queued = 0;            /* Or emergency_save() would not do anything */
  296. X        add_log(1, "SYSERR stat: %m (%e)");
  297. X        add_log(1, "ERROR queue file [%s] vanished", base);
  298. X        if (-1 == emergency_save())
  299. X            add_log(1, "ERROR mail probably lost");
  300. X    } else {
  301. X        add_log(4, "WARNING mailagent failed, [%s] left in queue", base);
  302. X        release_agent();    /* Remove mailagent's lock file if needed */
  303. X    }
  304. X}
  305. X
  306. Xprivate int process_mail(location)
  307. Xchar *location;
  308. X{
  309. X    /* Process mail held in 'location' by invoking the mailagent on it. If the
  310. X     * command fails, return -1. Otherwise, return 0;
  311. X     * Note that we will exit if the first fork is not possible, but that is
  312. X     * harmless, because we know the mail was safely queued, otherwise we would
  313. X     * not be here trying to make the mailagent process it.
  314. X     */
  315. X    
  316. X    FILE *fp;                /* The file pointer on pipe */
  317. X    char cmd[MAX_STRING];    /* The built command */
  318. X    char buf[MAX_STRING];    /* To store output from mailagent */
  319. X    char **envp;            /* Environment pointer */
  320. X#ifdef UNION_WAIT
  321. X    union wait status;        /* Waiting status */
  322. X#else
  323. X    int status;                /* Status from command */
  324. X#endif
  325. X    int xstat;                /* The exit status value */
  326. X    int pid;                /* Pid of our children */
  327. X    int res;                /* Result from wait */
  328. X
  329. X    if (loglvl <= 20) {        /* Loggging level higher than 20 is for tests */
  330. X        pid = fork();
  331. X        if (pid == -1) {    /* Resources busy, most probably */
  332. X            release_lock();
  333. X            add_log(1, "SYSERR fork: %m (%e)");
  334. X            add_log(6, "NOTICE exiting to save resources");
  335. X            exit(EX_OK);    /* Exiting will also release sendmail process */
  336. X        } else if (pid != 0)
  337. X            exit(EX_OK);    /* Release waiting sendmail */
  338. X    }
  339. X
  340. X    /* Now hopefully we detached ourselves from sendmail, which thinks the mail
  341. X     * has been delivered. Not yet, but close. Simply wait a little in case
  342. X     * more mail is comming. This process is going to remain alive while the
  343. X     * mailagent is running so as to trap any weird exit status. But the size
  344. X     * of the perl process (with script compiled) is about 1650K on my MIPS,
  345. X     * so the more we delay the invocation, the better.
  346. X     */
  347. X
  348. X    if (loglvl < 12)        /* Loggging level 12 and higher is for debugging */
  349. X        sleep(60);            /* Delay invocation of mailagent */
  350. X    progpid = getpid();        /* This may be the child (if fork succeded) */
  351. X    envp = make_env();        /* Build new environment */
  352. X
  353. X    pid = vfork();            /* Virtual fork this time... */
  354. X    if (pid == -1) {
  355. X        add_log(1, "SYSERR vfork: %m (%e)");
  356. X        add_log(1, "ERROR cannot run mailagent");
  357. X        return -1;
  358. X    }
  359. X
  360. X    if (pid == 0) {            /* This is the child */
  361. X        execle(PERLPATH, "perl", "-S", "mailagent", location, (char *) 0, envp);
  362. X        add_log(1, "SYSERR execle: %m (%e)");
  363. X        add_log(1, "ERROR cannot run perl to start mailagent");
  364. X        exit(EX_UNAVAILABLE);
  365. X    } else {                /* Parent process */
  366. X        while (pid != (res = wait(&status)))
  367. X            if (res == -1) {
  368. X                add_log(1, "SYSERR wait: %m (%e)");
  369. X                return -1;
  370. X            }
  371. X
  372. X#ifdef WEXITSTATUS
  373. X        if (WIFEXITED(status)) {            /* Exited normally */
  374. X            xstat = WEXITSTATUS(status);
  375. X            if (xstat != 0) {
  376. X                add_log(3, "ERROR mailagent returned status %d", xstat);
  377. X                return -1;
  378. X            }
  379. X        } else if (WIFSIGNALED(status)) {    /* Signal received */
  380. X            xstat = WTERMSIG(status);
  381. X            add_log(3, "ERROR mailagent terminated by signal %d", xstat);
  382. X            return -1;
  383. X        } else if (WIFSTOPPED(status)) {    /* Process stopped */
  384. X            xstat = WSTOPSIG(status);
  385. X            add_log(3, "WARNING mailagent stopped by signal %d", xstat);
  386. X            add_log(6, "NOTICE terminating mailagent, pid %d", pid);
  387. X            if (-1 == kill(pid, 15))
  388. X                add_log(1, "SYSERR kill: %m (%e)");
  389. X            return -1;
  390. X        } else
  391. X            add_log(1, "BUG please report bug 'posix-wait' to author");
  392. X#else
  393. X#ifdef UNION_WAIT
  394. X        xstat = status.w_status;
  395. X#else
  396. X        xstat = status;
  397. X#endif
  398. X        if ((xstat & 0xff) == 0177) {        /* Process stopped */
  399. X            xstat >>= 8;
  400. X            add_log(3, "WARNING mailagent stopped by signal %d", xstat);
  401. X            add_log(6, "NOTICE terminating mailagent, pid %d", pid);
  402. X            if (-1 == kill(pid, 15))
  403. X                add_log(1, "SYSERR kill: %m (%e)");
  404. X            return -1;
  405. X        } else if ((xstat & 0xff) != 0) {    /* Signal received */
  406. X            xstat &= 0xff;
  407. X            if (xstat & 0200) {                /* Dumped a core ? */
  408. X                xstat &= 0177;
  409. X                add_log(3, "ERROR mailagent dumped core on signal %d", xstat);
  410. X            } else
  411. X                add_log(3, "ERROR mailagent terminated by signal %d", xstat);
  412. X            return -1;
  413. X        } else {
  414. X            xstat >>= 8;
  415. X            if (xstat != 0) {
  416. X                add_log(3, "ERROR mailagent returned status %d", xstat);
  417. X                return -1;
  418. X            }
  419. X        }
  420. X#endif
  421. X    }
  422. X    
  423. X    add_log(19, "mailagent ok");
  424. X
  425. X    return 0;
  426. X}
  427. X
  428. Xpublic int emergency_save()
  429. X{
  430. X    /* Save mail in emeregency files and add the path to the agent.wait file,
  431. X     * so that the mailagent knows where to look when processing its queue.
  432. X     * Return -1 if the mail was not sucessfully saved, 0 otherwise.
  433. X     */
  434. X
  435. X    char *where;            /* Where file was stored (static data) */
  436. X    char *home = homedir();    /* Location of the home directory */
  437. X    char path[MAX_STRING];    /* Location of the AGENT_WAIT file */
  438. X    char *queue;            /* Location of the queue directory */
  439. X    char *emergdir;            /* Emergency directory */
  440. X    int fd;                    /* File descriptor to write in AGENT_WAIT */
  441. X    int size;                /* Length of 'where' string */
  442. X
  443. X    if (mail == (char *) 0)
  444. X        return -1;            /* Mail not read yet */
  445. X
  446. X    if (queued) {
  447. X        add_log(6, "NOTICE mail was safely queued");
  448. X        return 0;
  449. X    }
  450. X
  451. X    emergdir = ht_value(&symtab, "emergdir");
  452. X    if ((emergdir != (char *) 0) && (char *) 0 != (where = save_file(emergdir)))
  453. X        goto ok;
  454. X    if ((home != (char *) 0) && (char *) 0 != (where = save_file(home)))
  455. X        goto ok;
  456. X    if (where = save_file("/usr/spool/uucppublic"))
  457. X        goto ok;
  458. X    if (where = save_file("/var/spool/uucppublic"))
  459. X        goto ok;
  460. X    if (where = save_file("/usr/tmp"))
  461. X        goto ok;
  462. X    if (where = save_file("/var/tmp"))
  463. X        goto ok;
  464. X    if (where = save_file("/tmp"))
  465. X        goto ok;
  466. X
  467. X    return -1;        /* Could not save mail anywhere */
  468. X
  469. Xok:
  470. X    add_log(6, "DUMPED in %s", where);
  471. X    fprintf(stderr, "%s: DUMPED in %s\n", progname, where);
  472. X
  473. X    /* Attempt to write path of saved mail in the AGENT_WAIT file */
  474. X
  475. X    queue = ht_value(&symtab, "queue");
  476. X    if (queue == (char *) 0)
  477. X        return 0;
  478. X    sprintf(path, "%s/%s", queue, AGENT_WAIT);
  479. X    if (-1 == (fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0600))) {
  480. X        add_log(1, "SYSERR open: %m (%e)");
  481. X        add_log(6, "WARNING mailagent ignores where mail was left");
  482. X        return 0;
  483. X    }
  484. X    size = strlen(where);
  485. X    where[size + 1] = '\0';            /* Make room for trailing new-line */
  486. X    where[size] = '\n';
  487. X    if (-1 == write(fd, where, size + 1)) {
  488. X        add_log(1, "SYSERR write: %m (%e)");
  489. X        add_log(4, "ERROR could not append to %s", path);
  490. X        add_log(6, "WARNING mailagent ignores where mail was left");
  491. X    } else {
  492. X        where[size] = '\0';
  493. X        add_log(7, "NOTICE memorized %s", where);
  494. X        queued = 1;
  495. X    }
  496. X    close(fd);
  497. X
  498. X    return 0;
  499. X}
  500. X
  501. Xprivate char *save_file(dir)
  502. Xchar *dir;                /* Where saving should be done (directory) */
  503. X{
  504. X    /* Attempt to write mail in directory 'dir' and return a pointer to static
  505. X     * data holding the path name of the saved file if writing was ok.
  506. X     * Otherwise, return a null pointer and unlink any already created file.
  507. X     */
  508. X
  509. X    struct stat buf;                /* Stat buffer */
  510. X
  511. X    /* Make sure 'dir' entry exists, although we do not make sure it is really
  512. X     * a directory. If 'dir' is in fact a file, then open() will loudly
  513. X     * complain. We only want to avoid spurious log messages.
  514. X     */
  515. X
  516. X    if (-1 == stat(dir, &buf))        /* No entry in file system, probably */
  517. X        return (char *) 0;            /* Saving failed */
  518. X
  519. X    return write_file(dir, logname());
  520. X}
  521. X
  522. Xprivate char *write_file(dir, template)
  523. Xchar *dir;                /* Where saving should be done (directory) */
  524. Xchar *template;            /* First part of the file name */
  525. X{
  526. X    /* Attempt to write mail in directory 'dir' and return a pointer to static
  527. X     * data holding the path name of the saved file if writing was ok.
  528. X     * Otherwise, return a null pointer and unlink any already created file.
  529. X     * The file 'dir/template.$$' is created (where '$$' refers to the pid of
  530. X     * the current process). As login name <= 8 and pid is <= 5, we are below
  531. X     * the fatidic 14 chars limit for filenames.
  532. X     */
  533. X
  534. X    static char path[MAX_STRING];    /* Path name of created file */
  535. X    int fd;                            /* File descriptor */
  536. X    register4 int n;                /* Result from the write system call */
  537. X    register1 char *mailptr;        /* Pointer into mail buffer */
  538. X    register2 int length;            /* Number of bytes already written */
  539. X    register3 int amount;            /* Amount of bytes written by last call */
  540. X    struct stat buf;                /* Stat buffer */
  541. X
  542. X    sprintf(path, "%s/%s.%d", dir, template, progpid);
  543. X
  544. X    if (-1 == (fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600))) {
  545. X        add_log(1, "SYSERR open: %m (%e)");
  546. X        add_log(2, "ERROR cannot create file %s", path);
  547. X        return (char *) 0;
  548. X    }
  549. X
  550. X    /* Write the mail on disk. We do not call a single write on the mail buffer
  551. X     * as in "write(fd, mail, len)" in case the mail length exceeds the maximum
  552. X     * amount of bytes the system can atomically write.
  553. X     */
  554. X    
  555. X    for (
  556. X        mailptr = mail, length = 0;
  557. X        length < len;
  558. X        mailptr += amount, length += amount
  559. X    ) {
  560. X        amount = len - length;
  561. X        if (amount > BUFSIZ)        /* Do not write more than BUFSIZ */
  562. X            amount = BUFSIZ;
  563. X        n = write(fd, mailptr, amount);
  564. X        if (n == -1 || n != amount) {
  565. X            if (n == -1)
  566. X                add_log(1, "SYSERR write: %m (%e)");
  567. X            add_log(2, "ERROR cannot write to file %s", path);
  568. X            close(fd);
  569. X            goto error;                /* Remove file and report error */
  570. X        }
  571. X    }
  572. X
  573. X    close(fd);
  574. X    add_log(19, "mail in %s", path);
  575. X
  576. X    /* I don't really trust writes through NFS soft-mounted partitions, and I
  577. X     * am also suspicious about hard-mounted ones. I could have opened the file
  578. X     * with the O_SYNC flag, but the effect on NFS is not well defined either.
  579. X     * So, let's just make sure the mail has been correctly written on the disk
  580. X     * by comparing the file size and the orginal message size. If they differ,
  581. X     * complain and return an error.
  582. X     */
  583. X
  584. X    if (-1 == stat(path, &buf))        /* No entry in file system, probably */
  585. X        return (char *) 0;            /* Saving failed */
  586. X
  587. X    if (buf.st_size != len) {        /* Not written entirely */
  588. X        add_log(2, "ERROR mail truncated to %d bytes (had %d)",
  589. X            buf.st_size, len);
  590. X        goto error;                    /* Remove file and report error */
  591. X    }
  592. X
  593. X    return path;            /* Where mail was writen (static data) */
  594. X
  595. Xerror:        /* Come here when a write error has been detected */
  596. X
  597. X    if (-1 == unlink(path)) {
  598. X        add_log(1, "SYSERR unlink: %m (%e)");
  599. X        add_log(4, "WARNING leaving %s around", path);
  600. X    }
  601. X
  602. X    return (char *) 0;
  603. X}
  604. X
  605. X#ifndef HAS_RENAME
  606. Xpublic int rename(from, to)
  607. Xchar *from;                /* Original name */
  608. Xchar *to;                /* Target name */
  609. X{
  610. X    (void) unlink(to);
  611. X    if (-1 == link(from, to))
  612. X        return -1;
  613. X    if (-1 == unlink(from))
  614. X        return -1;
  615. X
  616. X    return 0;
  617. X}
  618. X#endif
  619. X
  620. END_OF_FILE
  621.   if test 17850 -ne `wc -c <'agent/filter/io.c'`; then
  622.     echo shar: \"'agent/filter/io.c'\" unpacked with wrong size!
  623.   fi
  624.   # end of 'agent/filter/io.c'
  625. fi
  626. if test -f 'agent/filter/parser.c' -a "${1}" != "-c" ; then 
  627.   echo shar: Will not clobber existing file \"'agent/filter/parser.c'\"
  628. else
  629.   echo shar: Extracting \"'agent/filter/parser.c'\" \(16535 characters\)
  630.   sed "s/^X//" >'agent/filter/parser.c' <<'END_OF_FILE'
  631. X/*
  632. X
  633. X #####     ##    #####    ####   ######  #####            ####
  634. X #    #   #  #   #    #  #       #       #    #          #    #
  635. X #    #  #    #  #    #   ####   #####   #    #          #
  636. X #####   ######  #####        #  #       #####    ###    #
  637. X #       #    #  #   #   #    #  #       #   #    ###    #    #
  638. X #       #    #  #    #   ####   ######  #    #   ###     ####
  639. X
  640. X    Parse a configuration file.
  641. X*/
  642. X
  643. X/*
  644. X * $Id: parser.c,v 3.0 1993/11/29 13:48:18 ram Exp ram $
  645. X *
  646. X *  Copyright (c) 1990-1993, Raphael Manfredi
  647. X *  
  648. X *  You may redistribute only under the terms of the Artistic License,
  649. X *  as specified in the README file that comes with the distribution.
  650. X *  You may reuse parts of this distribution only within the terms of
  651. X *  that same Artistic License; a copy of which may be found at the root
  652. X *  of the source tree for mailagent 3.0.
  653. X *
  654. X * $Log: parser.c,v $
  655. X * Revision 3.0  1993/11/29  13:48:18  ram
  656. X * Baseline for mailagent 3.0 netwide release.
  657. X *
  658. X */
  659. X
  660. X#include "config.h"
  661. X#include "portable.h"
  662. X#include "hash.h"
  663. X#include "msg.h"
  664. X#include <sys/types.h>
  665. X#include "logfile.h"
  666. X#include "environ.h"
  667. X#include <stdio.h>
  668. X#include <ctype.h>
  669. X#include <pwd.h>
  670. X#include <sys/stat.h>
  671. X
  672. X#ifdef I_STRING
  673. X#include <string.h>
  674. X#else
  675. X#include <strings.h>
  676. X#endif
  677. X
  678. X#ifndef HAS_GETHOSTNAME
  679. X#ifdef HAS_UNAME
  680. X#include <sys/utsname.h>
  681. X#endif
  682. X#endif
  683. X#include "confmagic.h"
  684. X
  685. X#define MAX_STRING    2048            /* Maximum length for strings */
  686. X#define SYMBOLS        50                /* Expected number of symbols */
  687. X
  688. X/* Function declarations */
  689. Xpublic void read_conf();            /* Read configuration file */
  690. Xpublic void set_env_vars();            /* Set envrionment variables */
  691. Xprivate void secure();                /* Perform basic security checks on file */
  692. Xprivate void check_perm();            /* Check permissions on file */
  693. Xprivate void get_home();            /* Extract home from /etc/passwd */
  694. Xprivate void substitute();            /* Variable and ~ substitutions */
  695. Xprivate void add_home();            /* Replace ~ with home directory */
  696. Xprivate void add_variable();        /* Replace $var by its value */
  697. Xprivate void insert_value();        /* Record variable value in H table */
  698. Xprivate char *machine_name();        /* Return the machine name */
  699. Xprivate char *strip_down();            /* Strip down domain name from host name */
  700. Xprivate void strip_comment();        /* Strip trailing comment in config line */
  701. Xprivate void start_log();            /* Start up logging */
  702. X
  703. Xprivate char *home = (char *) 0;    /* Location of the home directory */
  704. Xpublic struct htable symtab;        /* Symbol table */
  705. X
  706. Xextern char *strsave();                /* Save string value in memory */
  707. Xextern struct passwd *getpwuid();    /* Fetch /etc/passwd entry from uid */
  708. Xextern char *getenv();                /* Get environment variable */
  709. X
  710. Xpublic void read_conf(file)
  711. Xchar *file;
  712. X{
  713. X    /* Read file in the home directory and build a symbol H table on the fly.
  714. X     * The ~ substitution and usual $var substitution occur (but not ${var}).
  715. X     */
  716. X    
  717. X    char path[MAX_STRING];            /* Full path of the config file */
  718. X    char *rules;                    /* Path of the rule file, if any */
  719. X    char mailagent[MAX_STRING];        /* Path of the configuration file */
  720. X    FILE *fd;                        /* File descriptor used for config file */
  721. X    int line = 0;                    /* Line number */
  722. X
  723. X    if (home == (char *) 0)            /* Home not already artificially set */
  724. X        get_home();                    /* Get home directory via /etc/passwd */
  725. X
  726. X    /* Build full path for configuration file, based on $HOME */
  727. X    strcpy(path, home);
  728. X    strcat(path, "/");
  729. X    strcat(path, file);
  730. X    strcpy(mailagent, path);        /* Save configuration path for later */
  731. X
  732. X    fd = fopen(path, "r");
  733. X    if (fd == (FILE *) 0)
  734. X        fatal("cannot open config file %s", path);
  735. X
  736. X    /* Initialize the H table */
  737. X    if (-1 == ht_create(&symtab, SYMBOLS))
  738. X        fatal("cannot create symbol table");
  739. X
  740. X    while((char *) 0 != fgets(path, MAX_STRING - 1, fd)) {
  741. X        line ++;                    /* One more line */
  742. X        substitute(path);            /* Standard parameter substitutions */
  743. X        insert_value(path, line);    /* Record value in hash table */
  744. X    }
  745. X    fclose(fd);
  746. X
  747. X
  748. X    /* Some security checks are in order here, or someone could set up a fake
  749. X     * a config file for us and then let the mailagent execute arbitrary 
  750. X     * commands under our uid. These tests are performed after the parsing of
  751. X     * the file, to allow logging of errors.
  752. X     */
  753. X
  754. X    start_log();                    /* Start up loging */
  755. X    secure(mailagent);                /* Perform basic security checks */
  756. X
  757. X    /* Final security check on the rule file, if provided. The constraints are
  758. X     * the same as those for the ~/.mailagent configuration file. This is
  759. X     * because a rule can specify a RUN command, which will start a process
  760. X     * with the user's privileges.
  761. X     */
  762. X
  763. X    rules = ht_value(&symtab, "rules");    /* Fetch rules location */
  764. X    if (rules == (char *) 0)            /* No rule file, that's fine */
  765. X        return;
  766. X
  767. X    check_perm(rules);                /* Might not exist, don't use secure() */
  768. X}
  769. X
  770. Xprivate void start_log()
  771. X{
  772. X    /* Start up logging, if possible. Note that not defining a logging
  773. X     * directory or a logging level is a fatal error.
  774. X     */
  775. X
  776. X    char logfile[MAX_STRING];        /* Location of logfile */
  777. X    char *value;                    /* Symbol value */
  778. X    int level = 0;                    /* Logging level wanted */
  779. X
  780. X    value = ht_value(&symtab, "logdir");    /* Fetch logging directory */
  781. X    if (value == (char *) 0)
  782. X        fatal("logging directory not defined");
  783. X    strcpy(logfile, value);
  784. X    strcat(logfile, "/");
  785. X
  786. X    value = ht_value(&symtab, "log");        /* Basename of the log file */
  787. X    if (value == (char *) 0)
  788. X        fatal("logfile not defined");
  789. X    strcat(logfile, value);
  790. X
  791. X    value = ht_value(&symtab, "level");        /* Fetch logging level */
  792. X    if (value == (char *) 0)
  793. X        fatal("no logging level defined");
  794. X    sscanf(value, "%d", &level);
  795. X
  796. X    set_loglvl(level);                        /* Logging level wanted */
  797. X    if (-1 == open_log(logfile))
  798. X        fprintf(stderr, "%s: cannot open logfile %s\n", progname, logfile);
  799. X}
  800. X
  801. Xprivate void secure(file)
  802. Xchar *file;
  803. X{
  804. X    /* Make sure the file is owned by the effective uid, and that it is not
  805. X     * world writable. Otherwise, simply abort with a fatal error.
  806. X     * Returning from this routine implies that the security checks succeeded.
  807. X     */
  808. X
  809. X    struct stat buf;        /* Statistics buffer */
  810. X
  811. X    if (-1 == stat(file, &buf)) {
  812. X        add_log(1, "SYSERR stat: %m (%e)");
  813. X        fatal("cannot stat file %s", file);
  814. X    }
  815. X
  816. X    check_perm(file);        /* Check permissions */
  817. X}
  818. X
  819. Xprivate void check_perm(file)
  820. Xchar *file;
  821. X{
  822. X    /* Check basic permissions on the specified file. If cannot be world
  823. X     * writable and must be owned by the user. If the file specified does not
  824. X     * exist, no error is reported however.
  825. X     */
  826. X
  827. X    struct stat buf;        /* Statistics buffer */
  828. X
  829. X    if (-1 == stat(file, &buf))
  830. X        return;
  831. X
  832. X#ifndef S_IWOTH
  833. X#define S_IWOTH 00002        /* Write permissions for other */
  834. X#endif
  835. X
  836. X    if (buf.st_mode & S_IWOTH)
  837. X        fatal("file %s is world writable!", file);
  838. X
  839. X    if (buf.st_uid != geteuid())
  840. X        fatal("file %s not owned by user!", file);
  841. X}
  842. X
  843. Xpublic char *homedir()
  844. X{
  845. X    return home;            /* Location of the home directory */
  846. X}
  847. X
  848. Xpublic void env_home()
  849. X{
  850. X    home = getenv("HOME");        /* For tests only -- see main.c */
  851. X    if (home != (char *) 0)
  852. X        home = strsave(home);    /* POSIX getenv() returns ptr to static data */
  853. X}
  854. X
  855. Xprivate void get_home()
  856. X{
  857. X    /* Get home directory out of /etc/passwd file */
  858. X
  859. X    struct passwd *pp;                /* Pointer to passwd entry */
  860. X
  861. X    pp = getpwuid(geteuid());
  862. X    if (pp == (struct passwd *) 0)
  863. X        fatal("cannot locate home directory");
  864. X    home = strsave(pp->pw_dir);
  865. X    if (home == (char *) 0)
  866. X        fatal("no more memory");
  867. X}
  868. X
  869. Xpublic void set_env_vars(envp)
  870. Xchar **envp;                /* The environment pointer */
  871. X{
  872. X    /* Set the all environment variable correctly. If the configuration file
  873. X     * defines a variable of the form 'p_host' where "host" is the lowercase
  874. X     * name of the machine (domain name stripped), then that value is prepended
  875. X     * to the current value of the PATH variable. We also set HOME and TZ if
  876. X     * there is a 'timezone' variable in the config file.
  877. X     */
  878. X
  879. X    char *machine = machine_name();        /* The machine name */
  880. X    char *path_val;                        /* Path value to append */
  881. X    char *tz;                            /* Time zone value */
  882. X    char name[MAX_STRING];                /* Built 'p_host' */
  883. X
  884. X    init_env(envp);                        /* Built the current environment */
  885. X
  886. X    /* If there is a path: entry in the ~/.mailagent, it is used to replace
  887. X     * then current PATH value. This entry is of course not mandatory. If not
  888. X     * present, we'll simply prepend the added path 'p_host' to the existing
  889. X     * value provided by sendmail, cron, or whoever invoked us.
  890. X     */
  891. X    path_val = ht_value(&symtab, "path");
  892. X    if (path_val != (char *) 0) {
  893. X        if (-1 == set_env("PATH", path_val))
  894. X            fatal("cannot initialize PATH");
  895. X    }
  896. X
  897. X    sprintf(name, "p_%s", machine);        /* Name of field in ~/.mailagent */
  898. X    path_val = ht_value(&symtab, name);    /* Exists ? */
  899. X    if (path_val != (char *) 0) {        /* Yes, prepend its value */
  900. X        add_log(19, "updating PATH with '%s' from config file", name);
  901. X        if (-1 == prepend_env("PATH", ":"))
  902. X            fatal("cannot set PATH variable");
  903. X        if (-1 == prepend_env("PATH", path_val))
  904. X            fatal("cannot set PATH variable");
  905. X    }
  906. X
  907. X    /* Also set a correct value for the home directory */
  908. X    if (-1 == set_env("HOME", home))
  909. X        fatal("cannot set HOME variable");
  910. X
  911. X    /* If there is a 'timezone' variable, set TZ accordingly */
  912. X    tz = ht_value(&symtab, "timezone");    /* Exists ? */
  913. X    if (tz != (char *) 0) {
  914. X        if (-1 == set_env("TZ", tz))
  915. X            add_log(1, "ERROR cannot set TZ variable");
  916. X    }
  917. X}
  918. X
  919. Xprivate void substitute(value)
  920. Xchar *value;
  921. X{
  922. X    /* Run parameter and ~ substitution in-place */
  923. X
  924. X    char buffer[MAX_STRING];        /* Copy on which we work */
  925. X    char *ptr = buffer;                /* To iterate over the buffer */
  926. X    char *origin = value;            /* Save origin pointer */
  927. X
  928. X    strcpy(buffer, value);            /* Make a copy of original line */
  929. X    while (*value++ = *ptr)            /* Line is updated in-place */
  930. X        switch(*ptr++) {
  931. X        case '~':                    /* Replace by home directory */
  932. X            add_home(&value);
  933. X            break;
  934. X        case '$':                    /* Variable substitution */
  935. X            add_variable(&value, &ptr);
  936. X            break;
  937. X        }
  938. X}
  939. X
  940. Xprivate void add_home(to)
  941. Xchar **to;                        /* Pointer to address in substituted text */
  942. X{
  943. X    /* Add home directory at the current location. If the 'home' symbol has
  944. X     * been found, use that instead.
  945. X     */
  946. X
  947. X    char *value = *to - 1;        /* Go back to overwrite the '~' */
  948. X    char *ptr = home;            /* Where home directory string is stored */
  949. X    char *symbol;                /* Symbol entry for 'home' */
  950. X
  951. X    if (strlen(home) == 0)        /* As a special case, this is empty when */
  952. X        ptr = "/";                /* referring to the root directory */
  953. X
  954. X    symbol = ht_value(&symtab, "home");        /* Maybe we saw  'home' already */
  955. X    if (symbol != (char *) 0)                /* Yes, we did */
  956. X        ptr = symbol;                        /* Use it for ~ substitution */
  957. X
  958. X    while (*value++ = *ptr++)    /* Copy string */
  959. X        ;
  960. X
  961. X    *to = value - 1;            /* Update position in substituted string */
  962. X}
  963. X
  964. Xprivate void add_variable(to, from)
  965. Xchar **to;                        /* Pointer to address in substituted text */
  966. Xchar **from;                    /* Pointer to address in original text */
  967. X{
  968. X    /* Add value of variable at the current location */
  969. X
  970. X    char *value = *to - 1;        /* Go back to overwrite the '$' */
  971. X    char *ptr = *from;            /* Start of variable's name */
  972. X    char buffer[MAX_STRING];    /* To hold the name of the variable */
  973. X    char *name = buffer;        /* To advance in buffer */
  974. X    char *dol_value;            /* $value of variable */
  975. X
  976. X    /* Get variable's name */
  977. X    while (*name++ = *ptr) {
  978. X        if (isalnum(*ptr))
  979. X            ptr++;
  980. X        else
  981. X            break;
  982. X    }
  983. X
  984. X    *(name - 1) = '\0';            /* Ensure null terminated string */
  985. X    *from = ptr;                /* Update pointer in original text */
  986. X
  987. X    /* Fetch value of variable recorded so far */
  988. X    dol_value = ht_value(&symtab, buffer);
  989. X    if (dol_value == (char *) 0)
  990. X        return;
  991. X
  992. X    /* Do the variable substitution */
  993. X    while (*value++ = *dol_value++)
  994. X        ;
  995. X    
  996. X    *to = value - 1;            /* Update pointer to substituted text */
  997. X}
  998. X
  999. Xprivate void insert_value(path, line)
  1000. Xchar *path;                        /* The whole line */
  1001. Xint line;                        /* The line number, for error reports */
  1002. X{
  1003. X    /* Analyze the line after parameter substitution and record the value of
  1004. X     * the variable in the hash table. The line has the following format:
  1005. X     *    name  :  value    # trailing comment
  1006. X     * If only spaces are encoutered or if the first non blank value is a '#',
  1007. X     * then the line is ignored. Otherwise, any error in parsing is reported.
  1008. X     */
  1009. X
  1010. X    char name[MAX_STRING];                /* The name of the variable */
  1011. X    char *nptr = name;                    /* To fill in the name buffer */
  1012. X
  1013. X    while (isspace(*path))                /* Skip leading spaces */
  1014. X        path++;
  1015. X
  1016. X    if (*path == '#')                    /* A comment */
  1017. X        return;                            /* Ignore the whole line */
  1018. X    if (*path == '\0')                    /* A line full of spaces */
  1019. X        return;                            /* Ignore it */
  1020. X
  1021. X    while (*nptr++ = *path) {            /* Copy everything until non alphanum */
  1022. X        if (*path == '_') {
  1023. X            /* Valid variable character, although not 'isalnum' */
  1024. X            path++;
  1025. X            continue;
  1026. X        } else if (!isalnum(*path++))    /* Reached a non-alphanumeric char */
  1027. X            break;                        /* We got variable name */
  1028. X    }
  1029. X    *(nptr - 1) = '\0';                    /* Overwrite the ':' with '\0' */
  1030. X    path--;                                /* Go back on non-alphanum char */
  1031. X    while (*path)                        /* Now go and find the ':' */
  1032. X        if (*path++ == ':')                /* Found it */
  1033. X            break;
  1034. X
  1035. X    /* We reached the end of the string without seeing a ':' */
  1036. X    if (*path == '\0') {
  1037. X        fprintf(stderr, "syntax error in config file, line %d\n", line);
  1038. X        return;
  1039. X    }
  1040. X
  1041. X    while (isspace(*path))                    /* Skip leading spaces in value */
  1042. X        path++;
  1043. X    path[strlen(path) - 1] = '\0';            /* Chop final newline */
  1044. X    strip_comment(path);                    /* Remove trailing comment */
  1045. X    (void) ht_put(&symtab, name, path);        /* Add value into symbol table */
  1046. X}
  1047. X
  1048. Xprivate void strip_comment(line)
  1049. Xchar *line;
  1050. X{
  1051. X    /* Remove anything after first '#' on line (trailing comment) and also
  1052. X     * strip any trailing spaces (including those right before the '#'
  1053. X     * character).
  1054. X     */
  1055. X
  1056. X    char *first = (char *) 0;        /* First space in sequence */
  1057. X    char c;                            /* Character at current position */
  1058. X
  1059. X    while (c = *line++) {
  1060. X        if (isspace(c) && first != (char *) 0)
  1061. X            continue;
  1062. X        if (c == '#') {                    /* This has to be a comment */
  1063. X            if (first != (char *) 0)    /* Position of first preceding space */
  1064. X                *first = '\0';            /* String ends at first white space */
  1065. X            *(line - 1) = '\0';            /* Also truncate at '#' position */
  1066. X            return;                        /* Done */
  1067. X        }
  1068. X        if (isspace(c))
  1069. X            first = line - 1;            /* Record first space position */
  1070. X        else
  1071. X            first = (char *) 0;            /* Signal: no active first space */
  1072. X    }
  1073. X
  1074. X    /* We have not found any '#' sign, so there is no comment in this line.
  1075. X     * However, there might be trailing white spaces... Trim them.
  1076. X     */
  1077. X    
  1078. X    if (first != (char *) 0)
  1079. X        *first = '\0';                    /* Get rid of trailing white spaces */
  1080. X}
  1081. X
  1082. Xprivate char *machine_name()
  1083. X{
  1084. X    /* Compute the local machine name, using only lower-cased names and
  1085. X     * stipping down any domain name. The result points on a freshly allocated
  1086. X     * string. A null pointer is returned in case of error.
  1087. X     */
  1088. X    
  1089. X#ifdef HAS_GETHOSTNAME
  1090. X    char name[MAX_STRING + 1];        /* The host name */
  1091. X#else
  1092. X#ifdef HAS_UNAME
  1093. X    struct utsname un;                /* The internal uname structure */
  1094. X#else
  1095. X#ifdef PHOSTNAME
  1096. X    char *command = PHOSTNAME;        /* Shell command to get hostname */
  1097. X    FILE *fd;                        /* File descriptor on popen() */
  1098. X    char name[MAX_STRING + 1];        /* The host name read from command */
  1099. X    char buffer[MAX_STRING + 1];    /* Input buffer */
  1100. X#endif
  1101. X#endif
  1102. X#endif
  1103. X
  1104. X#ifdef HAS_GETHOSTNAME
  1105. X    if (-1 != gethostname(name, MAX_STRING))
  1106. X        return strip_down(name);
  1107. X
  1108. X    add_log(1, "SYSERR gethostname: %m (%e)");
  1109. X    return (char *) 0;
  1110. X#else
  1111. X#ifdef HAS_UNAME
  1112. X    if (-1 != uname(&un))
  1113. X        return strip_down(un.nodename);
  1114. X
  1115. X    add_log(1, "SYSERR uname: %m (%e)");
  1116. X    return (char *) 0;
  1117. X#else
  1118. X#ifdef PHOSTNAME
  1119. X    fd = popen(PHOSTNAME, "r");
  1120. X    if (fd != (FILE *) 0) {
  1121. X        fgets(buffer, MAX_STRING, fd);
  1122. X        fclose(fd);
  1123. X        sscanf(buffer, "%s", name);
  1124. X        return strip_down(name);
  1125. X    }
  1126. X
  1127. X    add_log(1, "SYSERR cannot run %s: %m (%e)", PHOSTNAME);
  1128. X#endif
  1129. X    return strip_down(MYHOSTNAME);
  1130. X#endif
  1131. X#endif
  1132. X}
  1133. X
  1134. Xprivate char *strip_down(host)
  1135. Xchar *host;
  1136. X{
  1137. X    /* Return a freshly allocated string containing the host name. The string
  1138. X     * is lower-cased and the domain part is removed from the name.
  1139. X     * If any '-' is found in the hostname, it is translated into a '_', since
  1140. X     * it would not otherwise be a valid variable name for perl.
  1141. X     */
  1142. X    
  1143. X    char name[MAX_STRING + 1];        /* Constructed name */
  1144. X    char *ptr = name;
  1145. X    char c;
  1146. X
  1147. X    if (host == (char *) 0)
  1148. X        return (char *) 0;
  1149. X
  1150. X    while (c = *host) {                /* Lower-case name */
  1151. X        if (isupper(c))
  1152. X            *ptr = tolower(c);
  1153. X        else {
  1154. X            if (c == '-')            /* Although '-' is a valid hostname char */
  1155. X                c = '_';            /* It's not a valid perl variable char */
  1156. X            *ptr = c;
  1157. X        }
  1158. X        if (c != '.') {                /* Found a domain delimiter? */
  1159. X            host++;                    /* No, continue */
  1160. X            ptr++;
  1161. X        } else
  1162. X            break;                    /* Yes, we end processing there */
  1163. X    }
  1164. X    *ptr = '\0';                    /* Ensure null-terminated string */
  1165. X
  1166. X    add_log(19, "hostname is %s", name);
  1167. X
  1168. X    return strsave(name);            /* Save string in memory */
  1169. X}
  1170. X
  1171. END_OF_FILE
  1172.   if test 16535 -ne `wc -c <'agent/filter/parser.c'`; then
  1173.     echo shar: \"'agent/filter/parser.c'\" unpacked with wrong size!
  1174.   fi
  1175.   # end of 'agent/filter/parser.c'
  1176. fi
  1177. if test -f 'agent/pl/matching.pl' -a "${1}" != "-c" ; then 
  1178.   echo shar: Will not clobber existing file \"'agent/pl/matching.pl'\"
  1179. else
  1180.   echo shar: Extracting \"'agent/pl/matching.pl'\" \(16083 characters\)
  1181.   sed "s/^X//" >'agent/pl/matching.pl' <<'END_OF_FILE'
  1182. X;# $Id: matching.pl,v 3.0 1993/11/29 13:49:00 ram Exp ram $
  1183. X;#
  1184. X;#  Copyright (c) 1990-1993, Raphael Manfredi
  1185. X;#  
  1186. X;#  You may redistribute only under the terms of the Artistic License,
  1187. X;#  as specified in the README file that comes with the distribution.
  1188. X;#  You may reuse parts of this distribution only within the terms of
  1189. X;#  that same Artistic License; a copy of which may be found at the root
  1190. X;#  of the source tree for mailagent 3.0.
  1191. X;#
  1192. X;# $Log: matching.pl,v $
  1193. X;# Revision 3.0  1993/11/29  13:49:00  ram
  1194. X;# Baseline for mailagent 3.0 netwide release.
  1195. X;#
  1196. X;# 
  1197. X#
  1198. X# Matching functions
  1199. X#
  1200. X
  1201. X# List of special header selector, for which a pattern without / is to be
  1202. X# taken as an equality with the login name of the address. If there are some
  1203. X# metacharacters, then a match will be attempted on that name. For each of
  1204. X# those special headers, we record the name of the subroutine to be called.
  1205. X# If a matching function is not specified, the default is 'match_var'.
  1206. X# The %Amatcher gives the name of the fields which contains an address.
  1207. Xsub init_matcher {
  1208. X    %Matcher = (
  1209. X        'From',                'match_single',
  1210. X        'To',                'match_list',
  1211. X        'Cc',                'match_list',
  1212. X        'Apparently-To',    'match_list',
  1213. X        'Newsgroups',        'match_list',
  1214. X        'Sender',            'match_single',
  1215. X        'Resent-From',        'match_single',
  1216. X        'Resent-To',        'match_list',
  1217. X        'Resent-Cc',        'match_list',
  1218. X        'Resent-Sender',    'match_single',
  1219. X        'Reply-To',            'match_single',
  1220. X    );
  1221. X    %Amatcher = (
  1222. X        'From',                1,
  1223. X        'To',                1,
  1224. X        'Cc',                1,
  1225. X        'Apparently-To',    1,
  1226. X        'Sender',            1,
  1227. X        'Resent-From',        1,
  1228. X        'Resent-To',        1,
  1229. X        'Resent-Cc',        1,
  1230. X        'Resent-Sender',    1,
  1231. X        'Reply-To',            1,
  1232. X    );
  1233. X}
  1234. X
  1235. X# Transform a shell-style pattern into a perl pattern
  1236. Xsub perl_pattern {
  1237. X    local($_) = @_;        # The shell pattern
  1238. X    s/\./\\./g;            # Escape .
  1239. X    s/\*/.*/g;            # Transform * into .*
  1240. X    s/\?/./g;            # Transform ? into .
  1241. X    $_;                    # Perl pattern
  1242. X}
  1243. X
  1244. X# Take a pattern as written in the rule file and make it suitable for
  1245. X# pattern matching as understood by perl. If the pattern starts with a
  1246. X# leading /, nothing is done. Otherwise, a set of / are added.
  1247. X# match (1st case).
  1248. Xsub make_pattern {
  1249. X    local($_) = shift(@_);
  1250. X    unless (m|^/|) {                # Pattern does not start with a /
  1251. X        $_ = &perl_pattern($_);        # Simple words specified via shell patterns
  1252. X        $_ = "/^$_\$/";                # Anchor pattern
  1253. X    }
  1254. X    # The whole pattern is inserted within () to make at least one
  1255. X    # backreference. Otherwise, the following could happen:
  1256. X    #    $_ = '1 for you';
  1257. X    #    @matched = /^\d/;
  1258. X    #    @matched = /^(\d)/;
  1259. X    # In both cases, the @matched array is set to ('1'), with no way to
  1260. X    # determine whether it is due to a backreference (2nd case) or a sucessful
  1261. X    # match. Knowing we have at least one bracketed reference is enough to
  1262. X    # disambiguate.
  1263. X    s|^/(.*)/|/($1)/|;        # Enclose whole pattern within ()
  1264. X    $_;                        # Pattern suitable for eval'ed matching
  1265. X}
  1266. X
  1267. X# ### Main matching entry point ###
  1268. X# ### (called from &apply_rules in pl/analyze.pl)
  1269. X# Attempt a match of a set of pattern, for each possible selector. The selector
  1270. X# string given can contain multiple selectors separated by white spaces.
  1271. Xsub match {
  1272. X    local($selector) = shift(@_);    # The selector on which pattern applies
  1273. X    local($pattern) = shift(@_);    # The pattern or script to apply
  1274. X    local($range) = shift(@_);        # The range on which pattern applies
  1275. X    local($matched) = 0;            # Matching status returned
  1276. X    # If the pattern is held within double quotes, it is assumed to be the name
  1277. X    # of a file from which patterns may be found (one per line, shell comments
  1278. X    # being ignored).
  1279. X    if ($pattern !~ /^"/) {
  1280. X        $matched = &apply_match($selector, $pattern, $range);
  1281. X    } else {
  1282. X        # Load patterns from file whose name is given between "quotes"
  1283. X        local(@filepat) = &include_file($pattern, 'pattern');
  1284. X        # Now do the match for all the patterns. Stop as soon as one matches.
  1285. X        foreach (@filepat) {
  1286. X            $matched = &apply_match($selector, $_, $range);
  1287. X            last if $matched;
  1288. X        }
  1289. X    }
  1290. X    $matched ? 1 : 0;        # Return matching status (guaranteed numeric)
  1291. X}
  1292. X
  1293. X# Attempt a pattern match on a set of selectors, and set the special macro %&
  1294. X# to the name of the regexp-specified fields which matched.
  1295. Xsub apply_match {
  1296. X    local($selector) = shift(@_);    # The selector on which pattern applies
  1297. X    local($pattern) = shift(@_);    # The pattern or script to apply
  1298. X    local($range) = shift(@_);        # The range on which pattern applies
  1299. X    local($matched) = 0;            # True when a matching occurred
  1300. X    local($inverted) = 0;            # True whenever all '!' match succeeded
  1301. X    local($invert) = 1;                # Set to false whenever a '!' match fails
  1302. X    local($match);                    # Matching status reported
  1303. X    local($not) = '';                # Shall we negate matching status?
  1304. X    if ($selector eq 'script') {    # Pseudo header selector
  1305. X        $matched = &evaluate(*pattern);
  1306. X    } else {                        # True header selector
  1307. X
  1308. X        # There can be multiple selectors separated by white spaces. As soon as
  1309. X        # one of them matches, we stop and return true. A selector may contain
  1310. X        # metacharacters, in which case a regular pattern matching is attempted
  1311. X        # on the true *header* fields (i.e. we skip the pseudo keys like Body,
  1312. X        # Head, etc..). For instance, Return.* would attempt a match on the
  1313. X        # field Return-Receipt-To:, if present. The special macro %& is set
  1314. X        # to the list of all the fields on which the match succeeded
  1315. X        # (alphabetically sorted).
  1316. X
  1317. X        foreach $select (split(/ /, $selector)) {
  1318. X            $not = '';
  1319. X            $select =~ s/^!// && ($not = '!');
  1320. X            # Allowed metacharacters are listed here (no braces wanted)
  1321. X            if ($select =~ /\.|\*|\[|\]|\||\\|\^|\?|\+|\(|\)/) {
  1322. X                $match = &expr_selector_match($select, $pattern, $range);
  1323. X            } else {
  1324. X                $match = &selector_match($select, $pattern, $range);
  1325. X            }
  1326. X            if ($not) {                                # Negated test
  1327. X                $invert = !$match if $invert;
  1328. X                $inverted = $invert if !$match;        # '!' tests AND'ed
  1329. X            } else {
  1330. X                $matched = $match;                    # Normal tests OR'ed
  1331. X            }
  1332. X            last if $matched;        # Stop when matching status known
  1333. X        }
  1334. X    }
  1335. X    $matched || $inverted;            # Return matching status
  1336. X}
  1337. X
  1338. X# Attempt a pattern match on a set of selectors, and set the special macro %&
  1339. X# to the name of the field which matched. If there is more than one such
  1340. X# selector, values are separated using comas. If selector is preceded by a '!',
  1341. X# then the matching status is negated and *all* the tested fields are recorded
  1342. X# within %& when the returned status is 'true'.
  1343. Xsub expr_selector_match {
  1344. X    local($selector) = shift(@_);    # The selector on which pattern applies
  1345. X    local($pattern) = shift(@_);    # The pattern or script to apply
  1346. X    local($range) = shift(@_);        # The range on which pattern applies
  1347. X    local($matched) = 0;            # True when a matching occurred
  1348. X    local(@keys) = sort keys %Header;
  1349. X    local($match);                    # Local matching status
  1350. X    local($not) = '';                # Shall boolean value be negated?
  1351. X    local($orig_ampersand) = $macro_ampersand;    # Save %&
  1352. X    $selector =~ s/^!// && ($not = '!');
  1353. X    &add_log("field '$selector' has metacharacters") if $loglvl > 18;
  1354. X    field: foreach $key (@keys) {
  1355. X        next if $Pseudokey{$key};        # Skip Body, All...
  1356. X        &add_log("'$select' tried on '$key'") if $loglvl > 19;
  1357. X        next unless eval '$key =~ /' . $select . '/';
  1358. X        $match = &selector_match($key, $pattern, $range);
  1359. X        $matched = 1 if $match;            # Only one match needed
  1360. X        # Record matching field for futher reference if a match occurred and
  1361. X        # the selector does not start with a '!'. Record all the tested fields
  1362. X        # if's starting with a '!' (because that's what is interesting in that
  1363. X        # case). In that last case, the original macro will be restored if any
  1364. X        # match occurs.
  1365. X        if ($not || $match) {
  1366. X            $macro_ampersand .= ',' if $macro_ampersand;
  1367. X            $macro_ampersand =~ s/;,$/;/;
  1368. X            $macro_ampersand .= $key;
  1369. X        }
  1370. X        if ($match) {
  1371. X            &add_log("obtained match with '$key' field")
  1372. X                if $loglvl > 18;
  1373. X            next field;                # Try all the matching selectors
  1374. X        }
  1375. X        &add_log("no match with '$key' field") if $loglvl > 18;
  1376. X    }
  1377. X    $macro_ampersand .= ';';        # Set terminated with a ';'
  1378. X    # No need to negate status if selector was preceded by a '!': this will
  1379. X    # be done by apply match.
  1380. X    $macro_ampersand = $orig_ampersand if $not && $matched;    # Restore %&
  1381. X    &add_log("matching status for '$selector' ($range) is '$matched'")
  1382. X        if $loglvl > 18;
  1383. X    $matched;                        # Return matching status
  1384. X}
  1385. X
  1386. X# Attempt a match of a pattern against a selector, return boolean status.
  1387. X# If pattern is preceded by a '!', the boolean status is negated.
  1388. Xsub selector_match {
  1389. X    local($selector) = shift(@_);    # The selector on which pattern applies
  1390. X    local($pattern) = shift(@_);    # The pattern to apply
  1391. X    local($range) = shift(@_);        # The range on which pattern applies
  1392. X    local($matcher);                # Subroutine used to do the match
  1393. X    local($matched);                # Record matching status
  1394. X    local($not) = '';                # Shall we apply NOT on matching result?
  1395. X    $selector = &header'normalize($selector);    # Normalize case
  1396. X    $matcher = $Matcher{$selector};
  1397. X    $matcher = 'match_var' unless $matcher;
  1398. X    $pattern =~ s/^!// && ($not = '!');
  1399. X    $matched = &$matcher($selector, $pattern, $range);
  1400. X    $matched = !$matched if $not;    # Revert matching status if ! pattern
  1401. X    if ($loglvl > 19) {
  1402. X        local($logmsg) = "matching '$not$pattern' on '$selector' ($range) was ";
  1403. X        $logmsg .= $matched ? "true" : "false";
  1404. X        &add_log($logmsg);
  1405. X    }
  1406. X    $matched;                # Return matching status
  1407. X}
  1408. X
  1409. X# Pattern matching functions:
  1410. X#    They are invoked as function($selector, $pattern, $range) and return true
  1411. X#    if the pattern is found in the variable, according to some internal rules
  1412. X#    which are different among the functions. For instance, match_single will
  1413. X#    attempt a match with a login name or a regular pattern matching on the
  1414. X#    whole variable if the pattern was not a single word.
  1415. X
  1416. X# Matching is done in a header which only contains an internet address. The
  1417. X# $range parameter is ignored (does not make any sense here). An optional 4th
  1418. X# parameter may be supplied to specify the matching buffer. If absent, the
  1419. X# corresponding header line is used -- this feature is used by &match_list.
  1420. Xsub match_single {
  1421. X    local($selector, $pattern, $range, $buffer) = @_;
  1422. X    local($login) = 0;                # Set to true when attempting login match
  1423. X    local(@matched);
  1424. X    unless (defined $buffer) {        # No buffer for matching was supplied
  1425. X        $buffer = $Header{$selector};
  1426. X    }
  1427. X    # If we attempt a match on a field holding e-mail addresses and the pattern
  1428. X    # is anchored at the beginning with a /^, then we only keep the address
  1429. X    # part and remove the comment if any. Otherwise, the field is left alone.
  1430. X    # Of course, if the pattern is only a single name, we extract the login
  1431. X    # name for matching purposes...
  1432. X    if ($Amatcher{$selector}) {                    # Field holds an e-mail address
  1433. X        $buffer = (&parse_address($buffer))[0] if $pattern =~ m|^/\^|;
  1434. X        if ($pattern =~ m|^[-\w.*?]+\s*$|) {    # Single name may have - or .
  1435. X            $buffer = (&parse_address($buffer))[0];
  1436. X            $buffer = &login_name($buffer);        # Match done only on login name
  1437. X            $pattern =~ tr/A-Z/a-z/;    # Cannonicalize name to lower case
  1438. X        }
  1439. X        $login = 1 unless $pattern =~ m|^/|;    # Ask for case-insensitive match
  1440. X    }
  1441. X    $buffer =~ s/^\s+//;                # Remove leading spaces
  1442. X    $buffer =~ s/\s+$//;                # And trailing ones
  1443. X    $pattern = &make_pattern($pattern);
  1444. X    $pattern .= "i" if $login;            # Login matches are case-insensitive
  1445. X    @matched = eval '($buffer =~ ' . $pattern . ');';
  1446. X    # If buffer is empty, we have to recheck the pattern in a non array context
  1447. X    # to see if there is a match. Otherwise, /(.*)/ does not seem to match an
  1448. X    # empty string as it returns an empty string in $matched[0]...
  1449. X    $matched[0] = eval '$buffer =~ ' . $pattern if $buffer eq '';
  1450. X    &eval_error;                        # Make sure eval worked
  1451. X    &update_backref(*matched);            # Record non-null backreferences
  1452. X    $matched[0];                        # Return matching status
  1453. X}
  1454. X
  1455. X# Matching is done on a header field which may contains multiple addresses
  1456. X# This will not work if there is a ',' in the comment part of the addresses,
  1457. X# but I never saw that and I don't want to write complex code for that--RAM.
  1458. X# If a range is specified, then only the items specified by the range are
  1459. X# actually used.
  1460. Xsub match_list {
  1461. X    local($selector, $pattern, $range) = @_;
  1462. X    local($_) = $Header{$selector};    # Work on a copy of the line
  1463. X    tr/\n/ /;                        # Make one big happy line
  1464. X    local(@list) = split(/,/);        # List of addresses
  1465. X    local($min, $max) = &mrange($range, scalar(@list));
  1466. X    return 0 unless $min;            # No matching possible if null range
  1467. X    local($buffer);                    # Buffer on which pattern matching is done
  1468. X    local($matched) = 0;            # Set to true when matching has occurred
  1469. X    @list = @list[$min - 1 .. ($max > $#list ? $#list : $max - 1)]
  1470. X        if $min != 1 || $max != 9_999_999;
  1471. X    foreach $buffer (@list) {
  1472. X        # Call match_single to perform the actual match and supply the matching
  1473. X        # buffer as the last argument. Note that since range does not make
  1474. X        # any sense for single matches, undef is passed on instead.
  1475. X        $matched = &match_single($selector, $pattern, undef, $buffer);
  1476. X        last if $matched;
  1477. X    }
  1478. X    $matched;
  1479. X}
  1480. X
  1481. X# Look for a pattern in a multi-line context
  1482. Xsub match_var {
  1483. X    local($selector, $pattern, $range) = @_;
  1484. X    local($lines) = 0;                    # Number of lines in matching buffer
  1485. X    if ($range ne '<1,->') {            # Optimize: count lines only if needed
  1486. X        $lines = $Header{$selector} =~ tr/\n/\n/;
  1487. X    }
  1488. X    local($min, $max) = &mrange($range, $lines);
  1489. X    return 0 unless $min;                # No matching possible if null range
  1490. X    local($buffer);                        # Buffer on which matching is attempted
  1491. X    local(@buffer);                        # Same, whith range line selected
  1492. X    local(@matched);
  1493. X    $pattern = &make_pattern($pattern);
  1494. X    # Optimize, since range selection is the exception and not the rule.
  1495. X    # Most likely, we use the default selection, i.e. we take everything...
  1496. X    if ($min != 1 || $max != 9_999_999) {
  1497. X        @buffer = split(/\n/, $Header{$selector});
  1498. X        @buffer = @buffer[$min - 1 .. ($max > $#buffer ? $#buffer : $max - 1)];
  1499. X        $buffer = join("\n", @buffer);        # Keep only selected lines
  1500. X        undef @buffer;                        # May be big, so free ASAP
  1501. X    } else {
  1502. X        $buffer = $Header{$selector};
  1503. X    }
  1504. X    $* = 1;                                # Multi-line matching is attempted
  1505. X    @matched = eval '($buffer =~ ' . $pattern . ');';
  1506. X    # If buffer is empty, we have to recheck the pattern in a non array context
  1507. X    # to see if there is a match. Otherwise, /(.*)/ does not seem to match an
  1508. X    # empty string as it returns an empty string in $matched[0]...
  1509. X    $matched[0] = eval '$buffer =~ ' . $pattern if $buffer eq '';
  1510. X    &eval_error;                        # Make sure eval worked
  1511. X    &update_backref(*matched);            # Record non-null backreferences
  1512. X    $* = 0;
  1513. X    $matched[0];                        # Return matching status
  1514. X}
  1515. X
  1516. X#
  1517. X# Backreference handling
  1518. X#
  1519. X
  1520. X# Reseet the backreferences at the beginning of each rule match attempt
  1521. X# The backreferences include %& and %1 .. %99.
  1522. Xsub reset_backref {
  1523. X    $macro_ampersand = '';            # List of matched generic selector
  1524. X    @Backref = ();                    # Stores backreferences provided by perl
  1525. X}
  1526. X
  1527. X# Update the backward reference array. There is a maximum of 99 backreferences
  1528. X# per filter rule. The argument list is an array of all the backreferences
  1529. X# found in the pattern matching, but the first item has to be skipped: it is
  1530. X# the whole matching string -- see comment on make_pattern().
  1531. Xsub update_backref {
  1532. X    local(*array) = @_;                # Array holding $1 .. $9, $10 ..
  1533. X    local($i, $val);
  1534. X    for ($i = 1; $i < @array; $i++) {
  1535. X        $val = $array[$i];
  1536. X        push(@Backref, $val);        # Stack backreference for later perusal
  1537. X        &add_log("stacked '$val' as backreference") if $loglvl > 18;
  1538. X    }
  1539. X}
  1540. X
  1541. X#
  1542. X# Range interpolation
  1543. X#
  1544. X
  1545. X# Return minimum and maximum for range value. A range is specified as <min,max>
  1546. X# but '-' may be used as min for 1 and max as a symbolic constant for the
  1547. X# maximum value. An arbitrarily large number is returned in that case. If a
  1548. X# negative value is used, it is added to the number of items and rounded towards
  1549. X# 1 if still negative. That way, it is possible to request the last 10 items.
  1550. Xsub mrange {
  1551. X    local($range, $items) = @_;
  1552. X    local($min, $max) = (1, 9_999_999);
  1553. X    local($rmin, $rmax) = $range =~ /<\s*([\d-]*)\s*,\s*([\d-]*)\s*>/;
  1554. X    $rmin = $min if $rmin eq '' || $rmin eq '-';
  1555. X    $rmax = $max if $rmax eq '' || $rmax eq '-';
  1556. X    $rmin = $rmin + $items + 1 if $rmin < 0;
  1557. X    $rmax = $rmax + $items + 1 if $rmax < 0;
  1558. X    $rmin = 1 if $rmin < 0;
  1559. X    $rmax = 1 if $rmax < 0;
  1560. X    ($rmin, $rmax) = (0, 0) if $rmin > $rmax;    # Null range if min > max
  1561. X    return ($rmin, $rmax);
  1562. X}
  1563. X
  1564. END_OF_FILE
  1565.   if test 16083 -ne `wc -c <'agent/pl/matching.pl'`; then
  1566.     echo shar: \"'agent/pl/matching.pl'\" unpacked with wrong size!
  1567.   fi
  1568.   # end of 'agent/pl/matching.pl'
  1569. fi
  1570. echo shar: End of archive 11 \(of 26\).
  1571. cp /dev/null ark11isdone
  1572. MISSING=""
  1573. for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ; do
  1574.     if test ! -f ark${I}isdone ; then
  1575.     MISSING="${MISSING} ${I}"
  1576.     fi
  1577. done
  1578. if test "${MISSING}" = "" ; then
  1579.     echo You have unpacked all 26 archives.
  1580.     echo "Now run 'sh PACKNOTES', then read README and type Configure.'"
  1581.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1582. else
  1583.     echo You still must unpack the following archives:
  1584.     echo "        " ${MISSING}
  1585. fi
  1586. exit 0
  1587.  
  1588. exit 0 # Just in case...
  1589.