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

  1. Newsgroups: comp.sources.misc
  2. From: Raphael Manfredi <ram@acri.fr>
  3. Subject: v41i008:  mailagent - Flexible mail filtering and processing package, v3.0, Part08/26
  4. Message-ID: <1993Dec2.133759.18345@sparky.sterling.com>
  5. X-Md4-Signature: c89cc6993bbb9714902e81dbf93e0de2
  6. Sender: kent@sparky.sterling.com (Kent Landfield)
  7. Organization: Advanced Computer Research Institute, Lyon, France.
  8. Date: Thu, 2 Dec 1993 13:37:59 GMT
  9. Approved: kent@sparky.sterling.com
  10.  
  11. Submitted-by: Raphael Manfredi <ram@acri.fr>
  12. Posting-number: Volume 41, Issue 8
  13. Archive-name: mailagent/part08
  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/hash.c agent/pl/cmdserv.pl patchlevel.h
  24. # Wrapped by ram@soft208 on Mon Nov 29 16:49:55 1993
  25. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  26. echo If this archive is complete, you will see the following message:
  27. echo '          "shar: End of archive 8 (of 26)."'
  28. if test -f 'agent/filter/hash.c' -a "${1}" != "-c" ; then 
  29.   echo shar: Will not clobber existing file \"'agent/filter/hash.c'\"
  30. else
  31.   echo shar: Extracting \"'agent/filter/hash.c'\" \(10498 characters\)
  32.   sed "s/^X//" >'agent/filter/hash.c' <<'END_OF_FILE'
  33. X/*
  34. X
  35. X #    #    ##     ####   #    #           ####
  36. X #    #   #  #   #       #    #          #    #
  37. X ######  #    #   ####   ######          #
  38. X #    #  ######       #  #    #   ###    #
  39. X #    #  #    #  #    #  #    #   ###    #    #
  40. X #    #  #    #   ####   #    #   ###     ####
  41. X
  42. X    Hash table handling (no item ever deleted).
  43. X*/
  44. X
  45. X/*
  46. X * $Id: hash.c,v 3.0 1993/11/29 13:48:08 ram Exp ram $
  47. X *
  48. X *  Copyright (c) 1990-1993, Raphael Manfredi
  49. X *  
  50. X *  You may redistribute only under the terms of the Artistic License,
  51. X *  as specified in the README file that comes with the distribution.
  52. X *  You may reuse parts of this distribution only within the terms of
  53. X *  that same Artistic License; a copy of which may be found at the root
  54. X *  of the source tree for mailagent 3.0.
  55. X *
  56. X * $Log: hash.c,v $
  57. X * Revision 3.0  1993/11/29  13:48:08  ram
  58. X * Baseline for mailagent 3.0 netwide release.
  59. X *
  60. X */
  61. X
  62. X#include "config.h"
  63. X#include "portable.h"
  64. X#include "hash.h"
  65. X#include "confmagic.h"
  66. X
  67. X#ifndef lint
  68. Xprivate char *rcsid =
  69. X    "$Id: hash.c,v 3.0 1993/11/29 13:48:08 ram Exp ram $";
  70. X#endif
  71. X
  72. Xprivate uint32 hashcode();            /* The hahsing function */
  73. Xprivate int prime();                /* Is a number a prime one? */
  74. Xprivate uint32 nprime();            /* Find next prime number */
  75. X
  76. Xextern char *malloc();                /* Memory allocation */
  77. Xextern char *calloc();                /* Character allocation */
  78. Xextern char *strsave();                /* Save string in memory */
  79. X
  80. Xpublic int ht_create(ht, n)
  81. Xstruct htable *ht;
  82. Xint n;
  83. X{
  84. X    /* Creates an H table to hold 'n' items with descriptor held in 'ht'. The
  85. X     * size of the table is optimized to avoid conflicts and is of course a
  86. X     * prime number. We take the first prime after (5 * n / 4).
  87. X     * The function returns 0 if everything was ok, -1 otherwise.
  88. X     */
  89. X
  90. X    int hsize;            /* Size of created table */
  91. X    char **array;        /* For array creation (keys/values) */
  92. X    
  93. X    hsize = nprime((5 * n) / 4);    /* Table's size */
  94. X
  95. X    array = (char **) calloc(hsize, sizeof(char *));    /* Array of keys */
  96. X    if (array == (char **) 0)
  97. X        return -1;                    /* Malloc failed */
  98. X    ht->h_keys = array;                /* Where array of keys is stored */
  99. X
  100. X    array = (char **) malloc(hsize * sizeof(char *));    /* Array of values */
  101. X    if (array == (char **) 0) {
  102. X        free(ht->h_keys);            /* Free keys array */
  103. X        return -1;                    /* Malloc failed */
  104. X    }
  105. X    ht->h_values = array;            /* Where array of keys is stored */
  106. X
  107. X    ht->h_size = hsize;                /* Size of hash table */
  108. X    ht->h_items = 0;                /* Table is empty */
  109. X
  110. X    return 0;            /* Creation was ok */
  111. X}
  112. X
  113. Xpublic char *ht_value(ht, skey)
  114. Xstruct htable *ht;
  115. Xchar *skey;
  116. X{
  117. X    /* Look for item associated with given key and returns its value.
  118. X     * Return a null pointer if item is not found.
  119. X     */
  120. X    
  121. X    register1 int32 key;        /* Hash code associated with string key */
  122. X    register2 int32 pos;        /* Position in H table */
  123. X    register3 int32 hsize;        /* Size of H table */
  124. X    register4 char **hkeys;        /* Array of keys */
  125. X    register5 int32 try = 0;    /* Count number of attempts */
  126. X    register6 int32 inc;        /* Loop increment */
  127. X
  128. X    /* Initializations */
  129. X    hsize = ht->h_size;
  130. X    hkeys = ht->h_keys;
  131. X    key = hashcode(skey);
  132. X
  133. X    /* Jump from one hashed position to another until we find the value or
  134. X     * go to an empty entry or reached the end of the table.
  135. X     */
  136. X    inc = 1 + (key % (hsize - 1));
  137. X    for (pos = key % hsize; try < hsize; try++, pos = (pos + inc) % hsize) {
  138. X        if (hkeys[pos] == (char *) 0)
  139. X            break;
  140. X        else if (0 == strcmp(hkeys[pos], skey))
  141. X            return ht->h_values[pos];
  142. X    }
  143. X
  144. X    return (char *) 0;            /* Item was not found */
  145. X}
  146. X
  147. Xpublic char *ht_put(ht, skey, val)
  148. Xstruct htable *ht;
  149. Xchar *skey;
  150. Xchar *val;
  151. X{
  152. X    /* Puts string held at 'val' tagged with key 'key' in H table 'ht'. If
  153. X     * insertion was successful, the address of the value is returned and the
  154. X     * value is copied in the array. Otherwise, return a null pointer.
  155. X     */
  156. X
  157. X    register1 int32 key;        /* Hash code associated with string key */
  158. X    register2 int32 pos;        /* Position in H table */
  159. X    register3 int32 hsize;        /* Size of H table */
  160. X    register4 char **hkeys;        /* Array of keys */
  161. X    register5 int32 try = 0;    /* Records number of attempts */
  162. X    register6 int32 inc;        /* Loop increment */
  163. X
  164. X    /* If the table is full at 75%, resize it to avoid performance degradations.
  165. X     * The extension updates the htable structure in place.
  166. X     */
  167. X    hsize = ht->h_size;
  168. X    if ((ht->h_items * 4) / 3 > hsize) {
  169. X        ht_xtend(ht);
  170. X        hsize = ht->h_size;
  171. X    }
  172. X    hkeys = ht->h_keys;
  173. X    key = hashcode(skey);
  174. X
  175. X    /* Jump from one hashed position to another until we find a free entry or
  176. X     * we reached the end of the table.
  177. X     */
  178. X    inc = 1 + (key % (hsize - 1));
  179. X    for (pos = key % hsize; try < hsize; try++, pos = (pos + inc) % hsize) {
  180. X        if (hkeys[pos] == (char *) 0) {            /* Found a free location */
  181. X            hkeys[pos] = strsave(skey);            /* Record item */
  182. X            ht->h_values[pos] = strsave(val);    /* Save string */
  183. X            ht->h_items++;                /* One more item */
  184. X            return ht->h_values[pos];
  185. X        } else if (0 == strcmp(hkeys[pos], skey))
  186. X            fatal("H table key conflict: %s", skey);
  187. X    }
  188. X
  189. X    return (char *) 0;        /* We were unable to insert item */
  190. X}
  191. X
  192. Xpublic char *ht_force(ht, skey, val)
  193. Xstruct htable *ht;
  194. Xchar *skey;
  195. Xchar *val;
  196. X{
  197. X    /* Replace value tagged with key 'key' in H table 'ht' with 'val'. If
  198. X     * insertion was successful, the address of the value is returned and the
  199. X     * value is copied in the array. Otherwise, return a null pointer (if table
  200. X     * is full and item was not found). The previous value is freed if any.
  201. X     * Otherwise, simply add the item in the table.
  202. X     */
  203. X
  204. X    register1 int32 key;        /* Hash code associated with string key */
  205. X    register2 int32 pos;        /* Position in H table */
  206. X    register3 int32 hsize;        /* Size of H table */
  207. X    register4 char **hkeys;        /* Array of keys */
  208. X    register5 int32 try = 0;    /* Records number of attempts */
  209. X    register6 int32 inc;        /* Loop increment */
  210. X
  211. X    /* If the table is full at 75%, resize it to avoid performance degradations.
  212. X     * The extension updates the htable structure in place.
  213. X     */
  214. X    hsize = ht->h_size;
  215. X    if ((ht->h_items * 4) / 3 > hsize) {
  216. X        ht_xtend(ht);
  217. X        hsize = ht->h_size;
  218. X    }
  219. X    hkeys = ht->h_keys;
  220. X    key = hashcode(skey);
  221. X
  222. X    /* Jump from one hashed position to another until we find a free entry or
  223. X     * we reached the end of the table.
  224. X     */
  225. X    inc = 1 + (key % (hsize - 1));
  226. X    for (pos = key % hsize; try < hsize; try++, pos = (pos + inc) % hsize) {
  227. X        if (hkeys[pos] == (char *) 0) {            /* Found a free location */
  228. X            hkeys[pos] = strsave(skey);            /* Record item */
  229. X            ht->h_values[pos] = strsave(val);    /* Save string */
  230. X            ht->h_items++;                        /* One more item */
  231. X            return ht->h_values[pos];
  232. X        } else if (0 == strcmp(hkeys[pos], skey)) {
  233. X            if (ht->h_values[pos])                /* If old value */
  234. X                free(ht->h_values[pos]);        /* Free it */
  235. X            ht->h_values[pos] = strsave(val);    /* Save string */
  236. X            return ht->h_values[pos];
  237. X        }
  238. X    }
  239. X
  240. X    return (char *) 0;        /* We were unable to insert item */
  241. X}
  242. X
  243. Xpublic int ht_xtend(ht)
  244. Xstruct htable *ht;
  245. X{
  246. X    /* The H table 'ht' is full and needs resizing. We add 50% of old size and
  247. X     * copy the old table in the new one, before freeing the old one. Note that
  248. X     * h_create multiplies the number we give by 5/4, so 5/4*3/2 yields ~2, i.e.
  249. X     * the final size will be the double of the previous one (modulo next prime
  250. X     * number).
  251. X     * Return 0 if extension was ok, -1 otherwise.
  252. X     */
  253. X
  254. X    register1 int32 size;            /* Size of old H table */
  255. X    register2 char **key;            /* To loop over keys */
  256. X    register3 char **val;            /* To loop over values */
  257. X    struct htable new_ht;
  258. X
  259. X    size = ht->h_size;
  260. X    if (-1 == ht_create(&new_ht, size + (size / 2)))
  261. X        return -1;        /* Extension of H table failed */
  262. X
  263. X    key = ht->h_keys;                /* Start of array of keys */
  264. X    val = ht->h_values;                /* Start of array of values */
  265. X
  266. X    /* Now loop over the whole table, inserting each item in the new one */
  267. X
  268. X    for (; size > 0; size--, key++, val++) {
  269. X        if (*key == (char *) 0)        /* Nothing there */
  270. X            continue;                /* Skip entry */
  271. X        if ((char *) 0 == ht_put(&new_ht, *key, *val)) {    /* Failed */
  272. X            free(new_ht.h_values);    /* Free new H table */
  273. X            free(new_ht.h_keys);
  274. X            fatal("BUG in ht_xtend");
  275. X        }
  276. X    }
  277. X
  278. X    /* Free old H table and set H table descriptor */
  279. X    free(ht->h_values);                /* Free in allocation order */
  280. X    free(ht->h_keys);                /* To make free happy (coalescing) */
  281. X    bcopy(&new_ht, ht, sizeof(struct htable));
  282. X
  283. X    return 0;        /* Extension was ok */
  284. X}
  285. X
  286. Xpublic int ht_start(ht)
  287. Xstruct htable *ht;
  288. X{
  289. X    /* Start iteration over H table. Return 0 if ok, -1 if the table is empty */
  290. X
  291. X    register1 int32 hpos;        /* Index in H table */
  292. X    register2 char **hkeys;        /* Array of keys */
  293. X    register3 int32 hsize;        /* Size of H table */
  294. X
  295. X    /* Initializations */
  296. X    hpos = 0;
  297. X    hkeys = ht->h_keys;
  298. X    hsize = ht->h_size;
  299. X
  300. X    /* Stop at first non-null key */
  301. X    for (; hpos < hsize; hpos++, hkeys++)
  302. X        if (*hkeys != (char *) 0)
  303. X            break;
  304. X    ht->h_pos = hpos;            /* First non-null postion */
  305. X
  306. X    return (hpos < hsize) ? 0 : -1;
  307. X}
  308. X
  309. Xpublic int ht_next(ht)
  310. Xstruct htable *ht;
  311. X{
  312. X    /* Advance to next item in H table, if possible. Return 0 if there is a
  313. X     * next item, -1 otherwise.
  314. X     */
  315. X
  316. X    register1 int32 hpos;        /* Index in H table */
  317. X    register2 char **hkeys;        /* Array of keys */
  318. X    register3 int32 hsize;        /* Size of H table */
  319. X
  320. X    /* Initializations */
  321. X    hpos = ht->h_pos + 1;
  322. X    hkeys = ht->h_keys + hpos;
  323. X    hsize = ht->h_size;
  324. X
  325. X    /* Stop at first non-null key */
  326. X    for (; hpos < hsize; hpos++, hkeys++)
  327. X        if (*hkeys != (char *) 0)
  328. X            break;
  329. X    ht->h_pos = hpos;            /* Next non-null postion */
  330. X
  331. X    return (hpos < hsize) ? 0 : -1;
  332. X}
  333. X
  334. Xpublic char *ht_ckey(ht)
  335. Xstruct htable *ht;
  336. X{
  337. X    /* Return pointer on current item's key */
  338. X
  339. X    return ht->h_keys[ht->h_pos];
  340. X}
  341. X
  342. Xpublic char *ht_cvalue(ht)
  343. Xstruct htable *ht;
  344. X{
  345. X    /* Return pointer on current item's value */
  346. X
  347. X    return ht->h_values[ht->h_pos];
  348. X}
  349. X
  350. Xpublic int ht_count(ht)
  351. Xstruct htable *ht;
  352. X{
  353. X    /* Return the number of items in the H table */
  354. X
  355. X    return ht->h_items;
  356. X}
  357. X
  358. Xprivate uint32 hashcode(s)
  359. Xregister3 char *s;
  360. X{
  361. X    /* Compute the hash code associated with given string s. The magic number
  362. X     * below is the greatest prime lower than 2^23.
  363. X     */
  364. X
  365. X    register1 uint32 hashval = 0;
  366. X    register2 uint32 magic = 8388593;
  367. X
  368. X    while (*s)
  369. X        hashval = ((hashval % magic) << 8) + (unsigned int) *s++;
  370. X
  371. X    return hashval;
  372. X}
  373. X
  374. Xprivate uint32 nprime(n)
  375. Xregister1 uint32 n;
  376. X{
  377. X    /* Return the closest prime number greater than `n' */
  378. X
  379. X    while (!prime(n))
  380. X        n++;
  381. X
  382. X    return n;
  383. X}
  384. X
  385. Xprivate int prime(n)
  386. Xregister2 uint32 n;
  387. X{
  388. X    /* Return 1 if `n' is a prime number */
  389. X
  390. X    register1 uint32 divisor;
  391. X
  392. X    if (n == 1)
  393. X        return 0;
  394. X    else if (n == 2)
  395. X        return 1;
  396. X    else if (n % 2) {
  397. X        for (
  398. X            divisor = 3; 
  399. X            divisor * divisor <= n;
  400. X            divisor += 2
  401. X        )
  402. X            if (0 == (n % divisor))
  403. X                return 0;
  404. X        return 1;
  405. X    }
  406. X    return 0;
  407. X}
  408. X
  409. END_OF_FILE
  410.   if test 10498 -ne `wc -c <'agent/filter/hash.c'`; then
  411.     echo shar: \"'agent/filter/hash.c'\" unpacked with wrong size!
  412.   fi
  413.   # end of 'agent/filter/hash.c'
  414. fi
  415. if test -f 'agent/pl/cmdserv.pl' -a "${1}" != "-c" ; then 
  416.   echo shar: Will not clobber existing file \"'agent/pl/cmdserv.pl'\"
  417. else
  418.   echo shar: Extracting \"'agent/pl/cmdserv.pl'\" \(40926 characters\)
  419.   sed "s/^X//" >'agent/pl/cmdserv.pl' <<'END_OF_FILE'
  420. X;# $Id: cmdserv.pl,v 3.0 1993/11/29 13:48:37 ram Exp ram $
  421. X;#
  422. X;#  Copyright (c) 1990-1993, Raphael Manfredi
  423. X;#  
  424. X;#  You may redistribute only under the terms of the Artistic License,
  425. X;#  as specified in the README file that comes with the distribution.
  426. X;#  You may reuse parts of this distribution only within the terms of
  427. X;#  that same Artistic License; a copy of which may be found at the root
  428. X;#  of the source tree for mailagent 3.0.
  429. X;#
  430. X;# $Log: cmdserv.pl,v $
  431. X;# Revision 3.0  1993/11/29  13:48:37  ram
  432. X;# Baseline for mailagent 3.0 netwide release.
  433. X;#
  434. X;# 
  435. X;# The command server is configured by a 'command' file, which lists the
  436. X;# available commands, their type and their locations. The command file has
  437. X;# the following format:
  438. X;#
  439. X;#   <cmd_name> <type> <hide> <collect> <path> <extra>
  440. X;#
  441. X;#  - cmd_name: the name of the command recognized by the server.
  442. X;#  - type: the type of command: shell, perl, var, flag, help or end.
  443. X;#  - hide: argument to hide in transcript (password usually).
  444. X;#  - collect: whether the command collects data following in mail message. Set
  445. X;#    to '-' means no, otherwise 'yes' means collecting is needed.
  446. X;#  - path: the location of the executable for shell commands (may be left out
  447. X;#    by specifying '-', in which case the command will be searched for in the
  448. X;#    path), the file where the command is implemented for perl commands, and
  449. X;#    the directory where help files are located for help, one file per command.
  450. X;#  - extra: either some options for shell commands or the name of the function
  451. X;#    within the perl file.
  452. X;#
  453. X;# Each command has an environment set up (part of the process environment for
  454. X;# shell commands, part of perl cmdenv package for other commands processed
  455. X;# by perl). This basic environment consists of:
  456. X;#  - jobnum: the job number of the current mailagent.
  457. X;#  - cmd: the command line as written in the message.
  458. X;#  - name: the command name.
  459. X;#  - log: what was logged in transcript (some args possibly concealed)
  460. X;#  - pack: packing mode for file sending.
  461. X;#  - path: destination for the command (where to send file / notification).
  462. X;#  - auth: set to true if valid envelope found (can "authenticate" sender).
  463. X;#  - uid: address of the sender of the message (where to send transcript).
  464. X;#  - user: user's e-mail, equivalent to UNIX euid here (initially uid).
  465. X;#  - trace: true when command trace wanted in transcript (shell commands).
  466. X;#  - powers: a colon separated list of privileges the user has.
  467. X;#  - errors: number of errors so far
  468. X;#  - requests: number of requests processed so far
  469. X;#  - eof: the end of file for collection mode
  470. X;#  - collect: true when collecting a file
  471. X;#  - disabled: a list of commands disabled (comma separated)
  472. X;#  - trusted: true when server in trust mode (where powers may be gainned)
  473. X;#  - debug: true in debug mode
  474. X;#  - approve: approve password for 'approve' commands, empty if no approve
  475. X;#
  476. X;# All convenience variables normally defined for the PERL command are also
  477. X;# made part of the command environment.
  478. X;#
  479. X;# For perl commands, collected data is available in the @buffer environment.
  480. X;# Shell commands can see those collected data by reading stdin.
  481. X;#
  482. X;# TODO:
  483. X;# Commands may be batched for later processing, in the batch queue. Each job
  484. X;# is recorded in a 'cm' file, the environment of the command itself is written
  485. X;# at the top, ending with a blank line and followed by the actual command to
  486. X;# be exectuted (i.e. the internal representation of 'cmd').
  487. X;#
  488. X#
  489. X# Command server
  490. X#
  491. X
  492. Xpackage cmdserv;
  493. X
  494. X$loaded = 0;            # Set to true when loading done
  495. X
  496. X# Initialize builtin server commands
  497. Xsub init {
  498. X    %Builtin = (                    # Builtins and their implemetation routine
  499. X        'addauth',    'run_addauth',    # Append to power clearance file
  500. X        'approve',    'run_approve',    # Record password for forthcoming command
  501. X        'delpower',    'run_delpower',    # Delete power from system
  502. X        'getauth',    'run_getauth',    # Get power clearance file
  503. X        'newpower',    'run_newpower',    # Add a new power to the system
  504. X        'passwd',    'run_passwd',    # Change power password, alternate syntax
  505. X        'password',    'run_password',    # Set new password for power
  506. X        'power',    'run_power',    # Ask for new power
  507. X        'powers',    'run_powers',    # A list of powers, along with clearances
  508. X        'release',    'run_release',    # Abandon power
  509. X        'remauth',    'run_remauth',    # Remove people from clearance file
  510. X        'set',        'run_set',        # Set internal variables
  511. X        'setauth',    'run_setauth',    # Set power clearance file
  512. X        'user',        'run_user',        # Commands on behalf of new user
  513. X    );
  514. X    %Conceal = (                    # Words to be hidden in transcript
  515. X        'power',    '2',            # Protect power password
  516. X        'password',    '2',            # Second argument is password
  517. X        'passwd',    '2,3',            # Both old and new passwords are concealed
  518. X        'newpower',    '2',            # Power password
  519. X        'delpower',    '2,3',            # Power password and security
  520. X        'getauth',    '2',            # Power password if no system clearance
  521. X        'setauth',    '2',            # Power password
  522. X        'addauth',    '2',            # Power password
  523. X        'remauth',    '2',            # Power passowrd
  524. X        'approve',    '1',            # Approve passoword
  525. X    );
  526. X    %Collect = (                    # Commands collecting more data from mail
  527. X        'newpower',    1,                # Takes list of allowed addresses
  528. X        'setauth',    1,                # Takes new list of allowed addresses
  529. X        'addauth',    1,                # Allowed addresses to be added
  530. X        'remauth',    1,                # List of addresses to be deleted
  531. X    );
  532. X    %Set = (                        # Internal variables which may be set
  533. X        'debug',    'flag',            # Debugging mode
  534. X        'eof',        'var',            # End of file marker (default is EOF)
  535. X        'pack',        'var',            # Packing mode for file sending
  536. X        'path',        'var',            # Destination address for file sending
  537. X        'trace',    'flag',            # The trace flag
  538. X    );
  539. X}
  540. X
  541. X# Load command file into memory, setting %Command, %Type, %Path and %Extra
  542. X# arrays, all indexed by a command name.
  543. Xsub load {
  544. X    $loaded = 1;                    # Do not come here more than once
  545. X    &init;                            # Initialize builtins
  546. X    return unless -s $cf'comserver;    # Empty or non-existent file
  547. X    return unless &'file_secure($cf'comserver, 'server command');
  548. X    unless (open(COMMAND, $cf'comserver)) {
  549. X        &'add_log("ERROR cannot open $cf'comserver: $!") if $'loglvl;
  550. X        &'add_log("WARNING server commands not loaded") if $'loglvl > 5;
  551. X        return;
  552. X    }
  553. X
  554. X    local($_);
  555. X    local($cmd, $type, $hide, $collect, $path, @extra);
  556. X    local(%known_type) = (
  557. X        'perl',        1,                # Perl script loaded dynamically
  558. X        'shell',    1,                # Program to run via fork/exec
  559. X        'help',        1,                # Help, send back files from dir
  560. X        'end',        1,                # End processing of requests
  561. X        'flag',        1,                # A variable flag
  562. X        'var',        1,                # An ascii variable
  563. X    );
  564. X    local(%set_type) = (
  565. X        'flag',        1,                # Denotes a flag variable
  566. X        'var',        1,                # Denotes an ascii variable
  567. X    );
  568. X
  569. X    while (<COMMAND>) {
  570. X        next if /^\s*#/;            # Skip comments
  571. X        next if /^\s*$/;            # Skip blank lines
  572. X        ($cmd, $type, $hide, $collect, $path, @extra) = split(' ');
  573. X        $path =~ s/~/$cf'home/;        # Perform ~ substitution
  574. X
  575. X        # Perl commands whose function name is not defined will bear the same
  576. X        # name as the command itself. If no path was specified, use the value
  577. X        # of the servdir configuration parameter from ~/.mailagent and assume
  578. X        # each command is stored in a cmd or cmd.pl file. Same for shell
  579. X        # commands, expected in a cmd or cmd.sh file. However, if the shell
  580. X        # command is not found there, it will be located at run-time using the
  581. X        # PATH variable.
  582. X        @extra = ($cmd) if $type eq 'perl' && @extra == 0;
  583. X        if ($type eq 'perl' || $type eq 'shell') {
  584. X            if ($path eq '-') {
  585. X                $path = "$cf'servdir/$cmd";
  586. X                $path = "$cf'servdir/$cmd.pl" if $type eq 'perl' && !-e $path;
  587. X                $path = "$cf'servdir/$cmd.sh" if $type eq 'shell' && !-e $path;
  588. X                $path = '-' if $type eq 'shell' && !-e $path;
  589. X            } elsif ($path !~ m|^/|) {
  590. X                $path = "$cf'servdir/$path";
  591. X            }
  592. X        }
  593. X
  594. X        # If path is specified, make sure it is valid
  595. X        if ($path ne '-' && !(-e $path && (-r _ || -x _))) {
  596. X            local($home) = $cf'home;
  597. X            $home =~ s/(\W)/\\$1/g;        # Escape possible metacharacters (+)
  598. X            $path =~ s/^$home/~/;
  599. X            &'add_log("ERROR command '$cmd' bound to invalid path $path")
  600. X                if $'loglvl > 1;
  601. X            next;                    # Ignore invalid command
  602. X        }
  603. X
  604. X        # Verify command type
  605. X        unless ($known_type{$type}) {
  606. X            &'add_log("ERROR command '$cmd' has unknown type $type")
  607. X                if $'loglvl > 1;
  608. X            next;                    # Skip to next command
  609. X        }
  610. X
  611. X        # If command is a variable, record it in the %Set array. Since all
  612. X        # variables are proceseed separately from commands, it is perfectly
  613. X        # legal to have both a command and a variable bearing the same name.
  614. X        if ($set_type{$type}) {
  615. X            $Set{$cmd} = $type;        # Record variable as being of given type
  616. X            next;
  617. X        }
  618. X
  619. X        # Load command into internal data structures
  620. X        $Command{$cmd}++;            # Record known command
  621. X        $Type{$cmd} = $type;
  622. X        $Path{$cmd} = $path;
  623. X        $Extra{$cmd} = join(' ', @extra);
  624. X        $Conceal{$cmd} = $hide if $hide ne '-';
  625. X        $Collect{$cmd}++ if $collect =~ /^y/i;
  626. X    }
  627. X    close COMMAND;
  628. X}
  629. X
  630. X# Process server commands held in the body, either by batching them or by
  631. X# executing them right away. A transcript is sent to the sender.
  632. X# Requires a previous call to 'setuid'.
  633. Xsub process {
  634. X    local(*body) = @_;                # Mail body
  635. X    local($_);                        # Current line processed
  636. X    local($metoo);                    # Send blind carbon copy to me too?
  637. X
  638. X    &load unless $loaded;            # Load commands unless already done
  639. X    $cmdenv'jobnum = $'jobnum;        # Propagate job number
  640. X    $metoo = $cf'user if $cf'scriptcc =~ /^on/i;
  641. X
  642. X    # Set up a mailer pipe to send the transcript back to the sender
  643. X    unless (open(MAILER, "|$cf'sendmail $cf'mailopt $cmdenv'uid $metoo")) {
  644. X        &'add_log("ERROR cannot start $cf'sendmail to mail transcript: $!")
  645. X            if $'loglvl > 1;
  646. X    }
  647. X
  648. X    # We may fork and have to close one end of the MAILER pipe, so make sure
  649. X    # no unflushed data ever remain...
  650. X    select((select(MAILER), $| = 1)[0]);
  651. X
  652. X    # Build up initial header. Be sure to add a junk precedence, since we do
  653. X    # not want to get any bounces.
  654. X    # For some reason, perl 4.0 PL36 fails with the here document construct
  655. X    # when using dataloading.
  656. X    print MAILER
  657. X"To: $cmdenv'uid
  658. XSubject: Mailagent session transcript
  659. XPrecedence: junk
  660. X$main'MAILER
  661. X
  662. X    ---- Mailagent session transcript for $cmdenv'uid ----
  663. X";
  664. X
  665. X    # Start message processing. Stop as soon as an ending command is reached,
  666. X    # or when more than 'maxerrors' errors have been detected. Also stop
  667. X    # processing when a signature is reached (introduced by '--').
  668. X
  669. X    foreach (@body) {
  670. X        if ($cmdenv'collect) {            # Collecting data for command
  671. X            if ($_ eq $cmdenv'eof) {    # Reached end of "file"
  672. X                $cmdenv'collect = 0;    # Stop collection
  673. X                &execute;                # Execute command
  674. X                undef @cmdenv'buffer;    # Free memory
  675. X            } else {
  676. X                push(@cmdenv'buffer, $_);
  677. X            }
  678. X            next;
  679. X        }
  680. X        if ($cmdenv'errors > $cf'maxerrors && !&root) {
  681. X            &finish('too many errors');
  682. X            last;
  683. X        }
  684. X        if ($cmdenv'requests > $cf'maxcmds && !&root) {
  685. X            &finish('too many requests');
  686. X            last;
  687. X        }
  688. X        next if /^\s*$/;            # Skip blank lines
  689. X        print MAILER "\n";            # Separate each command
  690. X        s/^\s*//;                    # Strip leading spaces
  691. X        &cmdenv'set_cmd($_);        # Set command environment
  692. X        $cmdenv'approve = '';        # Clear approve password
  693. X        &user_prompt;                # Copy line to transcript
  694. X        if (/^--\s*$/) {            # Signature reached
  695. X            &finish('.signature');
  696. X            last;
  697. X        }
  698. X        if ($Disabled{$cmdenv'name}) {        # Skip disabled commands
  699. X            $cmdenv'errors++;
  700. X            print MAILER "Disabled command.\n";
  701. X            print MAILER "FAILED.\n";
  702. X            &'add_log("DISABLED $cmdenv'log") if $'loglvl > 1;
  703. X            next;
  704. X        }
  705. X        unless (defined $Builtin{$cmdenv'name}) {
  706. X            unless (defined $Command{$cmdenv'name}) {
  707. X                $cmdenv'errors++;
  708. X                print MAILER "Unknown command.\n";
  709. X                print MAILER "FAILED.\n";
  710. X                &'add_log("UNKNOWN $cmdenv'log") if $'loglvl > 1;
  711. X                next;
  712. X            }
  713. X            if ($Type{$cmdenv'name} eq 'end') {    # Ending request?
  714. X                &finish("user's request");        # Yes, end processing then
  715. X                last;
  716. X            }
  717. X        }
  718. X        if (defined $Collect{$cmdenv'name}) {
  719. X            $cmdenv'collect = 1;        # Start collect mode
  720. X            next;                        # Grab things in @cmdenv'buffer
  721. X        }
  722. X        &execute;                # Execute command, report in transcript
  723. X    }
  724. X
  725. X    # If we are still in collecting mode, then the EOF marker was not found
  726. X    if ($cmdenv'collect) {
  727. X        &'add_log("ERROR did not reach eof mark '$cmdenv'eof'")
  728. X            if $'loglvl > 1;
  729. X        &'add_log("FAILED $cmdenv'log") if $'loglvl > 1;
  730. X        print MAILER "Could not find eof marker '$cmdenv'eof'.\n";
  731. X        print MAILER "FAILED.\n";
  732. X    }
  733. X
  734. X    print MAILER <<EOM;
  735. X
  736. X    ---- End of mailagent session transcript ----
  737. XEOM
  738. X    unless (close MAILER) {
  739. X        &'add_log("ERROR cannot mail transcript to $cmdenv'uid")
  740. X            if $'loglvl > 1;
  741. X    }
  742. X}
  743. X
  744. X#
  745. X# Command execution
  746. X#
  747. X
  748. X# Execute command recorded in the cmdenv environment. For each type of command,
  749. X# the routine 'exec_type' is called and returns 0 if ok. Builtins are dealt
  750. X# separately by calling the corresponding perl function.
  751. Xsub execute {
  752. X    $cmdenv'requests++;                # One more request
  753. X    local($log) = $cmdenv'log;        # Save log, since it could be modified
  754. X    local($failed) = &dispatch;        # Dispatch command
  755. X    if ($failed) {
  756. X        &'add_log("FAILED $log") if $'loglvl > 1;
  757. X        $cmdenv'errors++;
  758. X        print MAILER "FAILED.\n";
  759. X    } else {
  760. X        &'add_log("OK $log") if $'loglvl > 2;
  761. X        print MAILER "OK.\n";
  762. X    }
  763. X}
  764. X
  765. X# Dispatch command held in $cmdenv'name and return failure status (0 means ok).
  766. Xsub dispatch {
  767. X    local($failed) = 0;
  768. X    &'add_log("XEQ ($cmdenv'name) as $cmdenv'user") if $'loglvl > 10;
  769. X    if (defined $Builtin{$cmdenv'name}) {    # Deal separately with builtins
  770. X        eval "\$failed = &$Builtin{$cmdenv'name}";    # Call builtin function
  771. X        if (chop($@)) {
  772. X            print MAILER "Perl failure: $@\n";
  773. X            $@ .= "\n";        # Restore final char for &'eval_error call
  774. X            &'eval_error;    # Log error
  775. X            $@ = '';        # Clear evel error condition
  776. X            $failed++;        # Make sure failure is recorded
  777. X        }
  778. X    } else {
  779. X        # Command may be unknwon if called from 'user <email> command' or
  780. X        # from an 'approve <password> comamnd' type of invocation.
  781. X        if (defined $Type{$cmdenv'name}) {
  782. X            eval "\$failed = &exec_$Type{$cmdenv'name}";
  783. X        } else {
  784. X            print MAILER "Unknown command.\n";
  785. X            $cmdenv'errors++;
  786. X            $failed++;
  787. X        }
  788. X    }
  789. X    $failed;        # Report failure status
  790. X}
  791. X
  792. X# Shell command
  793. Xsub exec_shell {
  794. X    # Check for unsecure characters in shell command
  795. X    if ($cmdenv'cmd =~ /([=\$^&*([{}`\\|;><?])/ && !&root) {
  796. X        $cmdenv'errors++;
  797. X        print MAILER "Unsecure character '$1' in command line.\n";
  798. X        return 1;        # Failed
  799. X    }
  800. X
  801. X    # Initialize input script (if command operates in 'collect' mode)
  802. X    local($error) = 0;        # Error flag
  803. X    local($input) = '';        # Input file, when collecting
  804. X    if (defined $Collect{$cmdenv'name}) {
  805. X        $input = "$cf'tmpdir/input.cmd$$";
  806. X        unless (open(INPUT, ">$input")) {
  807. X            &'add_log("ERROR cannot create $input: $!") if $'loglvl;
  808. X            $error++;
  809. X        } else {
  810. X            foreach $collected (@cmdenv'buffer) {
  811. X                (print INPUT $collected, "\n") || $error++;
  812. X                &'add_log("SYSERR write: $!") if $error && $'loglvl;
  813. X                last if $error;
  814. X            }
  815. X            close(INPUT) || $error++;
  816. X            &'add_log("SYSERR close: $!") if $error == 1 && $'loglvl;
  817. X        }
  818. X        if ($error) {
  819. X            print MAILER "Cannot create input file ($!).\n";
  820. X            &'add_log("ERROR cannot initialize input file") if $'loglvl;
  821. X            unlink $input;
  822. X            return 1;        # Failed
  823. X        }
  824. X    }
  825. X
  826. X    # Create shell command file, whose purpose is to set up the environment
  827. X    # properly and do the appropriate file descriptors manipulations, which
  828. X    # is easier to do at the shell level, and cannot fully be done in perl 4.0
  829. X    # (see dup2 hack below).
  830. X    $cmdfile = "$cf'tmpdir/mess.cmd$$";
  831. X    unless (open(CMD, ">$cmdfile")) {
  832. X        &'add_log("ERROR cannot create $cmdfile: $!") if $'loglvl;
  833. X        print MAILER "Cannot create file comamnd file ($!).\n";
  834. X        unlink $input if $input;
  835. X        return 1;        # Failed
  836. X    }
  837. X
  838. X    # Initialize command environment
  839. X    local($key, $val);        # Key/value from perl's symbol table
  840. X    # Loop over perl's symbol table for the cmdenv package
  841. X    while (($key, $val) = each %_cmdenv) {
  842. X        local(*entry) = $val;        # Get definitaions of current slot
  843. X        next unless defined $entry;    # No variable slot
  844. X        ($val = $entry) =~ s/'/'"'"'/g;        # Keep simple quotes
  845. X        (print CMD "$key='$val' export $key\n") || $error++;
  846. X    }
  847. X    # Now add command invocation and input redirection. Standard input will be
  848. X    # the collect buffer, if any, and file descriptor #3 is a path to the
  849. X    # session transcript.
  850. X    local($redirect);
  851. X    $redirect = "<$input" if $input;
  852. X    local(@argv) = split(' ', $cmdenv'cmd);
  853. X    local($extra) = $Extra{$cmdenv'name};
  854. X    $argv[0] = $Path{$cmdenv'name} if defined $Path{$cmdenv'name};
  855. X    (print CMD "cd $cf'home\n") || $error++;    # Make sure we start from home
  856. X    (print CMD "exec 3>&2 2>&1\n") || $error++;    # See dup2 hack below
  857. X    (print CMD "$argv[0] $extra @argv[1..$#argv] $redirect\n") || $error++;
  858. X    close(CMD) || $error++;
  859. X    close CMD;
  860. X    if ($error) {
  861. X        &'add_log("ERROR cannot initialize $cmdfile: $!") if $'loglvl;
  862. X        unlink $cmdfile;
  863. X        unlink $input if $input;
  864. X        print MAILER "Cannot initialize command file ($!).\n";
  865. X        return 1;            # Failed
  866. X    }
  867. X
  868. X    &include($cmdfile, 'command', '<<< ') if $cmdenv'debug;
  869. X
  870. X    # Set up trace file
  871. X    $trace = "$cf'tmpdir/trace.cmd$$";
  872. X    unless (open(TRACE, ">$trace")) {
  873. X        &'add_log("ERROR cannot create $trace: $!") if $'loglvl;
  874. X        unlink $cmdfile;
  875. X        unlink $input if $input;
  876. X        print MAILER "Cannot create trace file ($!).\n";
  877. X        return 1;            # Failed
  878. X    }
  879. X
  880. X    # Now fork a child which will redirect stdout and stderr onto the trace
  881. X    # file and exec the command file.
  882. X
  883. X    local($pid) = fork;            # We fork here
  884. X    unless (defined $pid) {        # Apparently, we could not fork...
  885. X        &'add_log("SYSERR fork: $!") if $'loglvl;
  886. X        close TRACE;
  887. X        unlink $cmdfile, $trace;
  888. X        unlink $input if $input;
  889. X        print MAILER "Cannot fork ($!).\n";
  890. X        return 1;            # Failed
  891. X    }
  892. X
  893. X    # Child process runs the command
  894. X    if ($pid == 0) {                # Child process
  895. X        # Perform a dup2(MAILER, 3) to allow file descriptor #3 to be a way
  896. X        # for the shell script to reach the session transcript. Since perl
  897. X        # insists on closing all file descriptors >2 ($^F) during the exec, we
  898. X        # remap the current STDERR to MAILER temporarily. That way, it will
  899. X        # be transmitted to the child, which is a shell script doing an
  900. X        # 'exec 3>&2 2>&1', meaning the file #3 is the original MAILER and
  901. X        # stdout and stderr for the script go to the same trace file, as
  902. X        # intiallly attached to stdout.
  903. X        open(STDOUT, '>&TRACE');    # Redirect stdout to the trace file
  904. X        open(STDERR, '>&MAILER');    # Temporarily mapped to the MAILER file
  905. X        close(STDIN);                # Make sure there is no input
  906. X        exec "sh $cmdfile";            # Don't let perl use sh -c
  907. X        &'add_log("SYSERR exec: $!") if $'loglvl;
  908. X        &'add_log("ERROR cannot exec /bin/sh $cmdfile") if $'loglvl;
  909. X        print MAILER "Cannot exec command file ($!).\n";
  910. X        exit(9);
  911. X    }
  912. X
  913. X    close TRACE;        # Only child uses it
  914. X    wait;                # Wait for child
  915. X    unlink $cmdfile;    # Has been used and abused...
  916. X    unlink $input if $input;
  917. X
  918. X    if ($?) {            # Child exited with non-zero status
  919. X        local($status) = $? >> 8;
  920. X        &'add_log("ERROR child exited with status $status") if $'loglvl > 1;
  921. X        print MAILER "Command returned a non-zero status ($status).\n";
  922. X        $error = 1;
  923. X    }
  924. X    &include($trace, 'trace', '<<< ') if $error || $cmdenv'trace;
  925. X    unlink $trace;
  926. X    $error;                # Failure status
  927. X}
  928. X
  929. X# Perl command
  930. Xsub exec_perl {
  931. X    local($name) = $cmdenv'name;        # Command name
  932. X    local($fn) = $Extra{$name};            # Perl function to execute
  933. X    $fn = $name unless $fn;                # If none specified, use command name
  934. X    unless (&dynload'load('cmdenv', $Path{$name}, $fn)) {
  935. X        &'add_log("ERROR cannot load script for command $name") if $'loglvl;
  936. X        print MAILER "Cannot load $name command.\n";
  937. X        return 1;        # Failed
  938. X    }
  939. X    # Place in the cmdenv package context and call the function, propagating
  940. X    # the error status (1 for failure). Arguments are pre-split on space,
  941. X    # simply for convenience, but the command is free to parse the 'cmd'
  942. X    # variable itself.
  943. X    package cmdenv;
  944. X    local(*MAILER) = *cmdserv'MAILER;    # Propagate file descriptor
  945. X    local($fn) = $cmdserv'fn;            # Propagate function name
  946. X    local(@argv) = split(' ', $cmd);
  947. X    shift(@argv);                        # Remove command name
  948. X    local($res) = eval('&$fn(@argv)');    # Call function, get status
  949. X    if (chop $@) {
  950. X        &'add_log("ERROR in perl $name: $@") if $'loglvl;
  951. X        print MAILER "Perl error: $@\n";
  952. X        $res = 1;
  953. X    }
  954. X    $res;        # Propagate error status
  955. X}
  956. X
  957. X# Help command. Start by looking in the user's help directory, then in
  958. X# the public mailagent help directory. Users may disable help for a
  959. X# command by making an empty file in their own help dir.
  960. Xsub exec_help {
  961. X    local(@topic) = split(' ', $cmdenv'cmd);
  962. X    local($topic) = $topic[1];    # Help topic wanted
  963. X    local($help);                # Help file
  964. X    unless ($topic) {            # General builin help
  965. X        # Doesn't work with a here document form... (perl 4.0 PL36)
  966. X        print MAILER
  967. X"Following is a list of the known commands. Some additional help is available
  968. Xon a command basis by using 'help <command>', unless the command name is
  969. Xfollowed by a '*' character in which case no further help may be obtained.
  970. XCommands which collect input until an eof mark are marked with a trailing '='.
  971. X
  972. X";
  973. X        local(@cmds);            # List of known commands
  974. X        local($star);            # Does command have a help file?
  975. X        local($plus);            # Does command require additional input?
  976. X        local($online) = 0;        # Number of commands currently printed on line
  977. X        local($print);            # String printed for each command
  978. X        local($fieldlen) = 18;    # Amount of space dedicated to each command
  979. X        push(@cmds, keys(%Builtin), keys(%Command));
  980. X        foreach $cmd (sort @cmds) {
  981. X            $help = "$cf'helpdir/$cmd";
  982. X            $help = "$'privlib/help/$cmd" unless -e $help;
  983. X            $star = -s $help ? '' : '*';
  984. X            $plus = defined($Collect{$cmd}) ? '=' : '';
  985. X            # We print 4 commands on a single line
  986. X            $print = $cmd . $plus . $star;
  987. X            print MAILER $print, ' ' x ($fieldlen - length($print));
  988. X            if ($online++ == 3) {
  989. X                $online = 0;
  990. X                print MAILER "\n";
  991. X            }
  992. X        }
  993. X        print MAILER "\n" if $online;    # Pending line not completed yet
  994. X        print MAILER "\nEnd of command list.\n";
  995. X        return 0;    # Ok
  996. X    }
  997. X    $help = "$cf'helpdir/$topic";
  998. X    $help = "$'privlib/help/$cmd" unless -e $help;
  999. X    unless (-s $help) {
  1000. X        print MAILER "Help for '$topic' is not available.\n";
  1001. X        return 0;    # Not a failure
  1002. X    }
  1003. X    &include($help, "$topic help", '');    # Include file and propagate status
  1004. X}
  1005. X
  1006. X#
  1007. X# Builtins
  1008. X#
  1009. X
  1010. X# Approve command in advance by specifying a password. The syntax is:
  1011. X#    approve <password> [command]
  1012. X# and the password is simply recorded in the command environment. Then parsing
  1013. X# of the command is resumed.
  1014. X# NOTE: cannot approve a command which collects input (yet).
  1015. Xsub run_approve {
  1016. X    local($x, $password, @command) = split(' ', $cmdenv'cmd);
  1017. X    $cmdenv'approve = $password;            # Save approve password
  1018. X    &cmdenv'set_cmd(join(' ', @command));    # Set command environment
  1019. X    &dispatch;            # Execute command and propagate status
  1020. X}
  1021. X
  1022. X# Ask for new power. The syntax is:
  1023. X#    power <name> <password>
  1024. X# Normally, 'root' does not need to request for any other powers, less give
  1025. X# any password. However, for simplicity and uniformity, we simply grant it
  1026. X# with no checks.
  1027. Xsub run_power {
  1028. X    local($x, $name, $password) = split(' ', $cmdenv'cmd);
  1029. X    if (!$cmdenv'trusted) {        # Server has to be running in trusted mode
  1030. X        &power'add_log("WARNING cannot gain power '$name': not in trusted mode")
  1031. X            if $'loglvl > 5;
  1032. X    } elsif (&root || &power'grant($name, $password, $cmdenv'uid)) {
  1033. X        &power'add_log("granted power '$name' to $cmdenv'uid") if $'loglvl > 2;
  1034. X        &cmdenv'addpower($name);
  1035. X        return 0;        # Ok
  1036. X    }
  1037. X    print MAILER "Permission denied.\n";
  1038. X    1;        # Failed
  1039. X}
  1040. X
  1041. X# Release power. The syntax is:
  1042. X#    release <name>
  1043. X# If the 'root' power is released, other powers obtained while root or before
  1044. X# are kept. That way, it makes sense to ask for powers as root when the
  1045. X# password for some power has been changed. It is wise to release a power once
  1046. X# it is not needed anymore, since it may prevent mistakes.
  1047. Xsub run_release {
  1048. X    local($x, $name) = split(' ', $cmdenv'cmd);
  1049. X    &cmdenv'rempower($name);
  1050. X    0;        # Always ok
  1051. X}
  1052. X
  1053. X# List all powers with their clearances. The syntax is:
  1054. X#    powers <regexp>
  1055. X# and the 'system' power is needed to get the list. The root power or security
  1056. X# power is needed to get the root or security information. If no arguments are
  1057. X# specified, all the non-privileged powers (if you do not have root or security
  1058. X# clearance) are listed. If arguments are given, they are taken as regular
  1059. X# expression filters (perl way).
  1060. Xsub run_powers {
  1061. X    local($x, @regexp) = split(' ', $cmdenv'cmd);
  1062. X    unless (&cmdenv'haspower('system') || &cmdenv'haspower('security')) {
  1063. X        print MAILER "Permission denied.\n";
  1064. X        return 1;
  1065. X    }
  1066. X    unless (open(PASSWD, $cf'passwd)) {
  1067. X        &power'add_log("ERROR cannot open password file $cf'passwd: $!")
  1068. X            if $'loglvl;
  1069. X        print MAILER "Cannot open password file ($!).\n";
  1070. X        return 1;
  1071. X    }
  1072. X    print MAILER "List of currently defined powers:\n";
  1073. X    local($_);
  1074. X    local($power);            # Current power analyzed
  1075. X    local($matched);        # Did power match the regular expression?
  1076. X    while (<PASSWD>) {
  1077. X        ($power) = split(/:/);
  1078. X        # If any of the following regular expressions is incorrect, a die will
  1079. X        # be generated and caught by the enclosing eval.
  1080. X        $matched = @regexp ? 0 : 1;
  1081. X        foreach $regexp (@regexp) {
  1082. X            eval '$power =~ /$regexp/ && ++$matched;';
  1083. X            if (chop($@)) {
  1084. X                print MAILER "Perl failure: $@\n";
  1085. X                $@ = '';
  1086. X                close PASSWD;
  1087. X                return 1;
  1088. X            }
  1089. X            last if $matched;
  1090. X        }
  1091. X        next unless $matched;
  1092. X        print MAILER "\nPower: $power\n";
  1093. X        if (
  1094. X            ($power eq 'root' || $power eq 'security') &&
  1095. X            !&cmdenv'haspower($power)
  1096. X        ) {
  1097. X            print MAILER "(Cannot list clearance file: permission denied.)\n";
  1098. X            next;
  1099. X        }
  1100. X        &include(&power'authfile($power), "$power clearance");
  1101. X    }
  1102. X    close PASSWD;
  1103. X    0;
  1104. X}
  1105. X
  1106. X# Set new power password. The syntax is:
  1107. X#    password <name> <new>
  1108. X# To change a power password, you need to get the corresponding power or be
  1109. X# system, hence showing you know the password for that power or have greater
  1110. X# privileges. To change the 'root' and 'security' passwords, you need the
  1111. X# corresponding security clearance.
  1112. Xsub run_password {
  1113. X    local($x, $name, $new) = split(' ', $cmdenv'cmd);
  1114. X    local($required) = $name;
  1115. X    $required = 'system' unless &cmdenv'haspower($name);
  1116. X    $required = $name if $name eq 'root' || $name eq 'security';
  1117. X    unless (&cmdenv'haspower($required)) {
  1118. X        print MAILER "Permission denied (not enough power).\n";
  1119. X        return 1;
  1120. X    }
  1121. X    return 0 if 0 == &power'set_passwd($name, $new);
  1122. X    print MAILER "Could not change password, sorry.\n";
  1123. X    1;
  1124. X}
  1125. X
  1126. X# Set new power password. The syntax is:
  1127. X#    passwd <name> <old> <new>
  1128. X# You do not need to have the corresponding power to change the password since
  1129. X# the old password is requested. This is a short for the sequence:
  1130. X#    power <name> <old>
  1131. X#    password <name> <new>
  1132. X#    release <name>
  1133. X# excepted that even root has to give the correct old password if this form
  1134. X# is used.
  1135. Xsub run_passwd {
  1136. X    local($x, $name, $old, $new) = split(' ', $cmdenv'cmd);
  1137. X    unless (&power'authorized($name, $cmdenv'uid)) {
  1138. X        print MAILER "Permission denied (lacks authorization).\n";
  1139. X        return 1;
  1140. X    }
  1141. X    unless (&power'valid($name, $old)) {
  1142. X        print MAILER "Permission denied (invalid pasword).\n";
  1143. X        return 1;
  1144. X    }
  1145. X    return 0 if 0 == &power'set_passwd($name, $new);
  1146. X    print MAILER "Could not change password, sorry.\n";
  1147. X    1;
  1148. X}
  1149. X
  1150. X# Change user ID, i.e. e-mail address. The syntax is:
  1151. X#    user [<email> [command]]
  1152. X# and is used to execute some commands on behalf of another user. If a command
  1153. X# is specified, it is immediately executed with the new identity, which only
  1154. X# lasts for that time. Otherwise, the remaining commands are executed with that
  1155. X# new ID. If no email is specified, the original sender ID is restored.
  1156. X# All the powers are lost when a user command is executed, but this is only
  1157. X# temporary when the command is specified on the same line.
  1158. Xsub run_user {
  1159. X    local($x, $user, @command) = split(' ', $cmdenv'cmd);
  1160. X    local(%powers);
  1161. X    local($powers);
  1162. X    if (0 == @command && $cmdenv'powers ne '') {
  1163. X        print MAILER "Wiping out current powers ($cmdenv'powers).\n";
  1164. X        &cmdenv'wipe_powers;
  1165. X    }
  1166. X    if (0 != @command && $cmdenv'powers ne '') {
  1167. X        %powers = %cmdenv'powers;
  1168. X        $powers = $cmdenv'powers;
  1169. X        print MAILER "Current powers temporarily lost ($cmdenv'powers).\n";
  1170. X        &cmdenv'wipe_powers;
  1171. X    }
  1172. X    unless ($user) {            # Reverting to original sender ID
  1173. X        $cmdenv'user = $cmdenv'uid;
  1174. X        print MAILER "Back to original identity ($cmdenv'uid).\n";
  1175. X        return 0;
  1176. X    }
  1177. X    if (0 == @command) {
  1178. X        $cmdenv'user = $user;
  1179. X        print MAILER "New user identity: $cmdenv'user.\n";
  1180. X        return 0;
  1181. X    }
  1182. X
  1183. X    &cmdenv'set_cmd(join(' ', @command));    # Set command environment
  1184. X    local($failed) = &dispatch;                # Execute command
  1185. X
  1186. X    if (defined %powers) {
  1187. X        $cmdenv'powers = $powers;
  1188. X        %cmdenv'powers = %powers;
  1189. X        print MAILER "Restored powers ($powers).\n";
  1190. X    }
  1191. X
  1192. X    $failed;        # Propagate failure status
  1193. X}
  1194. X
  1195. X# Add a new power to the system. The syntax is:
  1196. X#    newpower <name> <password> [alias]
  1197. X# followed by a list of approved names who may request that power. The 'system'
  1198. X# power is required to add a new power. An alias should be specified if the
  1199. X# name is longer than 12 characters. The 'security' power is required to create
  1200. X# the root power, and root power is needed to create 'security'.
  1201. Xsub run_newpower {
  1202. X    local($x, $name, $password, $alias) = split(' ', $cmdenv'cmd);
  1203. X    if (
  1204. X        ($name eq 'root' && !&cmdenv'haspower('security')) ||
  1205. X        ($name eq 'security' && !&cmdenv'haspower('root')) ||
  1206. X        !&cmdenv'haspower('system')
  1207. X    ) {
  1208. X        print MAILER "Permission denied.\n";
  1209. X        return 1;
  1210. X    }
  1211. X    &newpower($name, $password, $alias);
  1212. X}
  1213. X
  1214. X# Actually add the new power to the system, WITHOUT any security checks. It
  1215. X# is up to the called to ensure the user has correct permissions. Return 0
  1216. X# if ok and 1 on error.
  1217. X# The clearance list is taken from @cmdenv'buffer.
  1218. Xsub newpower {
  1219. X    local($name, $password, $alias) = @_;
  1220. X    local($power) = &power'getpwent($name);
  1221. X    if (defined $power) {
  1222. X        print MAILER "Power '$name' already exists.\n";
  1223. X        return 1;
  1224. X    }
  1225. X    if (length($name) > 12 && !defined($alias)) {
  1226. X        # Compute a suitable alias name, which never appears externally anyway
  1227. X        # so it's not really important to use cryptic ones. First, reduce the
  1228. X        # power name to 10 characters.
  1229. X        $alias = $name;
  1230. X        $alias =~ tr/aeiouy//d;
  1231. X        $alias = substr($alias, 0, 6) . substr($alias, -6);
  1232. X        if (&power'used_alias($alias)) {
  1233. X            $alias = substr($alias, 0, 10);
  1234. X            local($tag) = 'AA';
  1235. X            local($try) = 100;
  1236. X            local($attempt);
  1237. X            while ($try--) {
  1238. X                $attempt = "$alias$tag";
  1239. X                last unless &power'used_alias($attempt);
  1240. X                $tag++;
  1241. X            }
  1242. X            $alias = $attempt;
  1243. X            if (&power'used_alias($alias)) {
  1244. X                print MAILER "Cannot auto-select any unused alias.\n";
  1245. X                return 1;    # Failed
  1246. X            }
  1247. X        }
  1248. X        print MAILER "(Selecting alias '$alias' for this power.)\n";
  1249. X    }
  1250. X    # Make sure alias is not too long. Don't try to shorten any user-specified
  1251. X    # alias if they took care of giving one instead of letting mailagent
  1252. X    # pick one up...
  1253. X    if (defined($alias) && length($alias) > 12) {
  1254. X        print MAILER "Alias name too long (12 characters max).\n";
  1255. X        return 1;
  1256. X    }
  1257. X    if (defined($alias) && &power'used_alias($alias)) {
  1258. X        print MAILER "Alias '$alias' is already in use.\n";
  1259. X        return 1;
  1260. X    }
  1261. X    if (defined($alias) && !&power'add_alias($name, $alias)) {
  1262. X        print MAILER "Cannot add alias, sorry.\n";
  1263. X        return 1;
  1264. X    }
  1265. X    unless (&power'set_auth($name, *cmdenv'buffer)) {
  1266. X        print MAILER "Cannot set authentication file, sorry.\n";
  1267. X        return 1;
  1268. X    }
  1269. X    if (-1 == &power'setpwent($name, "<$password>", '')) {
  1270. X        print MAILER "Cannot add power, sorry.\n";
  1271. X        return 1;
  1272. X    }
  1273. X    if (-1 == &power'set_passwd($name, $password)) {
  1274. X        print MAILER "Warning: could not insert password.\n";
  1275. X    }
  1276. X    0;
  1277. X}
  1278. X
  1279. X# Delete a power from the system. The syntax is:
  1280. X#    delpower <name> <password> [<security>]
  1281. X# deletes a power and its associated user list. The 'system' power is required
  1282. X# to delete most powers except 'root' and 'security'. The 'security' power may
  1283. X# only be deleted by security and the root power may only be deleted when the
  1284. X# security password is also specified.
  1285. Xsub run_delpower {
  1286. X    local($x, $name, $password, $security) = split(' ', $cmdenv'cmd);
  1287. X    if (
  1288. X        ($name eq 'security' && !&cmdenv'haspower($name)) ||
  1289. X        ($name eq 'root' && !&power'valid('security', $security)) ||
  1290. X        !&cmdenv'haspower('system')
  1291. X    ) {
  1292. X        print MAILER "Permission denied (not enough power).\n";
  1293. X        return 1;
  1294. X    }
  1295. X    unless (&root) {
  1296. X        unless (&power'valid($name, $password)) {
  1297. X            print MAILER "Permission denied (invalid password).\n";
  1298. X            return 1;
  1299. X        }
  1300. X    }
  1301. X    &delpower($name);
  1302. X}
  1303. X
  1304. X# Actually delete a power from the system, WITHOUT any security checks. It
  1305. X# is up to the called to ensure the user has correct permissions. Return 0
  1306. X# if ok and 1 on error.
  1307. Xsub delpower {
  1308. X    local($name) = @_;
  1309. X    local($power) = &power'getpwent($name);
  1310. X    if (!defined $power) {
  1311. X        print MAILER "Power '$name' does not exist.\n";
  1312. X        return 1;
  1313. X    }
  1314. X    local($auth) = &power'authfile($name);
  1315. X    if ($auth ne '/dev/null' && !unlink($auth)) {
  1316. X        &'add_log("SYSERR unlink: $!") if $'loglvl;
  1317. X        &'add_log("ERROR could not remove clearance file $auth") if $'loglvl;
  1318. X        print MAILER "Warning: could not remove clearance file.\n";
  1319. X    }
  1320. X    unless (&power'del_alias($name)) {
  1321. X        print MAILER "Warning: could not remove power alias.\n";
  1322. X    }
  1323. X    if (0 != &power'rempwent($name)) {
  1324. X        print MAILER "Failed (cannot remove password entry).\n";
  1325. X        return 1;
  1326. X    }
  1327. X    0;
  1328. X}
  1329. X
  1330. X# Replace current clearance file. The syntax is:
  1331. X#    setauth <name> <password>
  1332. X# and requires no special power if the password is given or if the power is
  1333. X# already detained. Otherwise, the system power is needed. For 'root' and
  1334. X# 'security' clearances, the corresponding power is needed as well.
  1335. Xsub run_setauth {
  1336. X    local($x, $name, $password) = split(' ', $cmdenv'cmd);
  1337. X    local($required) = $name;
  1338. X    $required = 'system' unless &cmdenv'haspower($name);
  1339. X    $required = $name if $name eq 'root' || $name eq 'security';
  1340. X    unless (&cmdenv'haspower($required)) {
  1341. X        unless (&power'valid($name, $password)) {
  1342. X            print MAILER "Permission denied.\n";
  1343. X            return 1;
  1344. X        }
  1345. X    }
  1346. X    unless (&power'set_auth($name, *cmdenv'buffer)) {
  1347. X        print MAILER "Cannot set authentication file, sorry.\n";
  1348. X        return 1;
  1349. X    }
  1350. X    0;
  1351. X}
  1352. X
  1353. X# Add users to clearance file. The syntax is:
  1354. X#    addauth <name> <password>
  1355. X# and requires no special power if the password is given or if the power is
  1356. X# already detained. Otherwise, the system power is needed. For 'root' and
  1357. X# 'security' clearances, the corresponding power is needed as well.
  1358. Xsub run_addauth {
  1359. X    local($x, $name, $password) = split(' ', $cmdenv'cmd);
  1360. X    local($required) = $name;
  1361. X    $required = 'system' unless &cmdenv'haspower($name);
  1362. X    $required = $name if $name eq 'root' || $name eq 'security';
  1363. X    unless (&cmdenv'haspower($required)) {
  1364. X        unless (&power'valid($name, $password)) {
  1365. X            print MAILER "Permission denied.\n";
  1366. X            return 1;
  1367. X        }
  1368. X    }
  1369. X    unless (&power'add_auth($name, *cmdenv'buffer)) {
  1370. X        print MAILER "Cannot add to authentication file, sorry.\n";
  1371. X        return 1;
  1372. X    }
  1373. X    0;
  1374. X}
  1375. X
  1376. X# Remove users from clearance file. The syntax is:
  1377. X#   remauth <name> <password>
  1378. X# and requires no special power if the password is given or if the power is
  1379. X# already detained. Otherwise, the system power is needed. For 'root' and
  1380. X# 'security' clearances, the corresponding power is needed as well.
  1381. Xsub run_remauth {
  1382. X    local($x, $name, $password) = split(' ', $cmdenv'cmd);
  1383. X    local($required) = $name;
  1384. X    $required = 'system' unless &cmdenv'haspower($name);
  1385. X    $required = $name if $name eq 'root' || $name eq 'security';
  1386. X    unless (&cmdenv'haspower($required)) {
  1387. X        unless (&power'valid($name, $password)) {
  1388. X            print MAILER "Permission denied.\n";
  1389. X            return 1;
  1390. X        }
  1391. X    }
  1392. X    unless (&power'rem_auth($name, *cmdenv'buffer)) {
  1393. X        print MAILER "Cannot remove from authentication file, sorry.\n";
  1394. X        return 1;
  1395. X    }
  1396. X    0;
  1397. X}
  1398. X
  1399. X# Get current clearance file. The syntax is:
  1400. X#    getauth <name> <password>
  1401. X# and requires no special power if the password is given or if the power is
  1402. X# already detained. Otherwise, the system power is needed for all powers,
  1403. X# and for 'root' or 'security', the corresponding power is required.
  1404. Xsub run_getauth {
  1405. X    local($x, $name, $password) = split(' ', $cmdenv'cmd);
  1406. X    local($required) = $name;
  1407. X    $required = 'system' unless &cmdenv'haspower($name);
  1408. X    $required = $name if $name eq 'root' || $name eq 'security';
  1409. X    unless (&cmdenv'haspower($required)) {
  1410. X        unless (&power'valid($name, $password)) {
  1411. X            print MAILER "Permission denied.\n";
  1412. X            return 1;
  1413. X        }
  1414. X    }
  1415. X    local($file) = &power'authfile($name);
  1416. X    &include($file, "$name clearance", '');    # Include file, propagate status
  1417. X}
  1418. X
  1419. X# Set internal variable. The syntax is:
  1420. X#    set <variable> <value>
  1421. X# and the corresponding variable from cmdenv package is set.
  1422. Xsub run_set {
  1423. X    local($x, $var, @args) = split(' ', $cmdenv'cmd);
  1424. X    unless (defined $Set{$var}) {
  1425. X        print MAILER "Unknown or read-only variable '$var'.\n";
  1426. X        return 1;        # Failed
  1427. X    }
  1428. X    local($type) = $Set{$var};        # The variable type
  1429. X    local($_)    ;                    # Value to assign to variable
  1430. X    if ($type eq 'flag') {
  1431. X        $_ = $args[0];
  1432. X        if ($_ eq '' || /on/i || /yes/i || /true/i) {
  1433. X            $val = 1;
  1434. X        } else {
  1435. X            $val = 0;
  1436. X        }
  1437. X    } else {
  1438. X        $val = join(' ', @args);
  1439. X    }
  1440. X    eval "\$cmdenv'$var = \$val";    # Set variable in cmdenv package
  1441. X    0;
  1442. X}
  1443. X
  1444. X#
  1445. X# Utilities
  1446. X#
  1447. X
  1448. X# Emit the user prompt in transcript, then copy current line
  1449. Xsub user_prompt {
  1450. X    if (&root) {
  1451. X        print MAILER "####> ";            # Command with no restrictions at all
  1452. X    } elsif ($cmdenv'powers ne '') {
  1453. X        print MAILER "====> ";            # Command with local privileges
  1454. X    } elsif ($cmdenv'user ne $cmdenv'uid) {
  1455. X        print MAILER "~~~~> ";            # Command on behalf of another user
  1456. X    } else {
  1457. X        print MAILER "----> ";            # Command from and for current user
  1458. X    }
  1459. X    print MAILER "$cmdenv'log\n";
  1460. X}
  1461. X
  1462. X# Include file in transcript, returning 1 on failure and 0 on success
  1463. X# If the third parameter is given, then it is used as leading marks, and
  1464. X# the enclosing digest lines are omitted.
  1465. Xsub include {
  1466. X    local($file, $description, $marks) = @_;
  1467. X    unless (open(FILE, $file)) {
  1468. X        &'add_log("ERROR cannot open $file: $!") if $'loglvl;
  1469. X        print MAILER "Cannot open $description file ($!).\n";
  1470. X        return 1;
  1471. X    }
  1472. X    local($_);
  1473. X    print MAILER "   --- Beginning of file ($description) ---\n"
  1474. X        unless defined $marks;
  1475. X    while (<FILE>) {
  1476. X        (print MAILER) unless defined $marks;
  1477. X        (print MAILER $marks, $_) if defined $marks;
  1478. X    }
  1479. X    close FILE;
  1480. X    print MAILER "   --- End of file ($description) ---\n"
  1481. X        unless defined $marks;
  1482. X    0;        # Success
  1483. X}
  1484. X
  1485. X# Signals end of processing
  1486. Xsub finish {
  1487. X    local($why) = @_;
  1488. X    print MAILER "End of processing ($why)\n";
  1489. X    &'add_log("END ($why)") if $'loglvl > 6;
  1490. X}
  1491. X
  1492. X# Check whether user has root powers or not.
  1493. Xsub root {
  1494. X    &cmdenv'haspower('root');
  1495. X}
  1496. X
  1497. X#
  1498. X# Server modes
  1499. X#
  1500. X
  1501. X# Allow server to run in trusted mode (where powers may be gained).
  1502. Xsub trusted {
  1503. X    if ($cmdenv'auth) {            # Valid envelope in mail header
  1504. X        $cmdenv'trusted = 1;    # Allowed to gain powers
  1505. X    } else {
  1506. X        &'add_log("WARNING unable to switch into trusted mode")
  1507. X            if $'loglvl > 5;
  1508. X    }
  1509. X}
  1510. X
  1511. X# Disable a list of commands, and only those commands.
  1512. Xsub disable {
  1513. X    local($cmds) = @_;        # List of disabled commands
  1514. X    undef %Disabled;        # Reset disabled commands, start with fresh set
  1515. X    foreach $cmd (split(/[\s,]+/, $cmds)) {
  1516. X        $Disabled{$cmd}++;
  1517. X    }
  1518. X    $cmdenv'disabled = join(',', sort keys %Disabled);    # No duplicates
  1519. X}
  1520. X
  1521. X#
  1522. X# Environment for server commands
  1523. X#
  1524. X
  1525. Xpackage cmdenv;
  1526. X
  1527. X# Set user identification (e-mail address) within cmdenv package
  1528. Xsub inituid {
  1529. X    # Convenience variables are part of the basic environment for all the
  1530. X    # server commands. This includes the $envelope variable, which is the
  1531. X    # user who has issued the request (real uid).
  1532. X    &hook'initvar('cmdenv');
  1533. X    $auth = 1;                # Assume valid envelope
  1534. X    $uid = (&'parse_address($envelope))[0];
  1535. X    if ($uid eq '') {        # No valid envelope
  1536. X        &'add_log("NOTICE no valid mail envelope") if $'loglvl > 6;
  1537. X        $uid = (&'parse_address($sender))[0];
  1538. X        $auth = 0;            # Will not be able to run in trusted mode
  1539. X    }
  1540. X    $user = $uid;            # Until further notice, euid = ruid
  1541. X    $path = $uid;            # And files are sent to the one who requested them
  1542. X    undef %powers;            # Reset power table
  1543. X    $powers = '';            # The linear version of powers
  1544. X    $errors = 0;            # Number of failed requests so far
  1545. X    $requests = 0;            # Total number of requests processed so far
  1546. X    $eof = 'EOF';            # End of file indicator in collection mode
  1547. X    $collect = 0;            # Not in collection mode
  1548. X    $trace = 0;                # Not in trace mode
  1549. X    $trusted = 0;            # Not in trusted mode
  1550. X}
  1551. X
  1552. X# Set command parameters
  1553. Xsub set_cmd {
  1554. X    ($cmd) = @_;
  1555. X    ($name) = $cmd =~ /^([\w-]+)/;    # Get command name
  1556. X    $name =~ tr/A-Z/a-z/;            # Cannonicalize to lower case
  1557. X
  1558. X    # Passwords in commands may need to be concealed
  1559. X    if (defined $cmdserv'Conceal{$name}) {
  1560. X        local(@argv) = split(' ', $cmd);
  1561. X        local(@pos) = split(/,/, $cmdserv'Conceal{$name});
  1562. X        foreach $pos (@pos) {
  1563. X            $argv[$pos] = '********' if defined $argv[$pos];
  1564. X        }
  1565. X        $log = join(' ', @argv);
  1566. X    } else {
  1567. X        $log = $cmd;
  1568. X    }
  1569. X}
  1570. X
  1571. X# Add a new power to the list once the user has been authenticated.
  1572. Xsub addpower {
  1573. X    local($newpower) = @_;
  1574. X    $powers{$newpower}++;
  1575. X    $powers = join(':', keys %powers);
  1576. X}
  1577. X
  1578. X# Remove power from the list.
  1579. Xsub rempower {
  1580. X    local($oldpower) = @_;
  1581. X    delete $powers{$oldpower};
  1582. X    $powers = join(':', keys %powers);
  1583. X}
  1584. X
  1585. X# Wipe out all the powers
  1586. Xsub wipe_powers {
  1587. X    undef %powers;
  1588. X    $powers = '';
  1589. X}
  1590. X
  1591. X# Check whether user has a given power... Note that 'root' has all powers
  1592. X# but 'security'.
  1593. Xsub haspower {
  1594. X    local($wanted) = @_;
  1595. X    $wanted eq 'security' ?
  1596. X        defined($powers{$wanted}) :
  1597. X        (defined($powers{'root'}) || defined($powers{$wanted}));
  1598. X}
  1599. X
  1600. Xpackage main;
  1601. X
  1602. END_OF_FILE
  1603.   if test 40926 -ne `wc -c <'agent/pl/cmdserv.pl'`; then
  1604.     echo shar: \"'agent/pl/cmdserv.pl'\" unpacked with wrong size!
  1605.   fi
  1606.   # end of 'agent/pl/cmdserv.pl'
  1607. fi
  1608. if test -f 'patchlevel.h' -a "${1}" != "-c" ; then 
  1609.   echo shar: Will not clobber existing file \"'patchlevel.h'\"
  1610. else
  1611.   echo shar: Extracting \"'patchlevel.h'\" \(75 characters\)
  1612.   sed "s/^X//" >'patchlevel.h' <<'END_OF_FILE'
  1613. X/* mailagent-3.0 - 1 Dec 1993 */
  1614. X
  1615. X#define VERSION 3.0
  1616. X#define PATCHLEVEL 0
  1617. END_OF_FILE
  1618.   if test 75 -ne `wc -c <'patchlevel.h'`; then
  1619.     echo shar: \"'patchlevel.h'\" unpacked with wrong size!
  1620.   fi
  1621.   # end of 'patchlevel.h'
  1622. fi
  1623. echo shar: End of archive 8 \(of 26\).
  1624. cp /dev/null ark8isdone
  1625. MISSING=""
  1626. 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
  1627.     if test ! -f ark${I}isdone ; then
  1628.     MISSING="${MISSING} ${I}"
  1629.     fi
  1630. done
  1631. if test "${MISSING}" = "" ; then
  1632.     echo You have unpacked all 26 archives.
  1633.     echo "Now run 'sh PACKNOTES', then read README and type Configure.'"
  1634.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1635. else
  1636.     echo You still must unpack the following archives:
  1637.     echo "        " ${MISSING}
  1638. fi
  1639. exit 0
  1640.  
  1641. exit 0 # Just in case...
  1642.