home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume25 / listserv5.31 / part06 < prev    next >
Encoding:
Internet Message Format  |  1991-12-12  |  54.3 KB

  1. Subject: v25i040: listserv5.31 - mailing list management system, Part06/06
  2. Newsgroups: comp.sources.unix
  3. Approved: vixie@pa.dec.com
  4.  
  5. Submitted-By: tasos@cs.bu.edu
  6. Posting-Number: Volume 25, Issue 40
  7. Archive-Name: listserv5.31/part06
  8.  
  9. #! /bin/sh
  10. # This is a shell archive.  Remove anything before this line, then unpack
  11. # it by saving it into a file and typing "sh file".  To overwrite existing
  12. # files, type "sh file -c".  You can also feed this as standard input via
  13. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  14. # will see the following message at the end:
  15. #        "End of archive 6 (of 6)."
  16. # Contents:  src/listserv.c
  17. # Wrapped by vixie@cognition.pa.dec.com on Fri Dec 13 18:31:10 1991
  18. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  19. if test -f 'src/listserv.c' -a "${1}" != "-c" ; then 
  20.   echo shar: Will not clobber existing file \"'src/listserv.c'\"
  21. else
  22. echo shar: Extracting \"'src/listserv.c'\" \(52668 characters\)
  23. sed "s/^X//" >'src/listserv.c' <<'END_OF_FILE'
  24. X/*
  25. X  ----------------------------------------------------------------------------
  26. X  |                        DISCUSSION LIST SERVER                            |
  27. X  |                                         |
  28. X  |                             Version 5.31                                  |
  29. X  |                                                                          |
  30. X  |                (or, when Computer Science gets to you)                   |
  31. X  |                                                                          |
  32. X  |                    Written by Anastasios Kotsikonas                      |
  33. X  |                           (tasos@cs.bu.edu)                              |
  34. X  |                                                                          |
  35. X  | AGREEMENT: This software can be used and distributed freely as long      |
  36. X  | as you do not remove or alter the Copyright notice in the file defs.h;   |
  37. X  | this notice is #define'd in the symbol VERSION. Although you may alter   |
  38. X  | the code provided, you may not alter the functions create_header()       |
  39. X  | and create_multi_recipient_header() in list.c and listserv.c.            |
  40. X  | By using this software you are bound by this agreement.                  |
  41. X  | This software comes with no warranties and cannot be sold for profit.    |
  42. X  | The AGREEMENT and COPYRIGHT notices should be included in all source     |
  43. X  | files when distributing this software.                                   |
  44. X  | COPYRIGHT: Copyright (c) 1991, Anastasios C. Kotsikonas                  |
  45. X  ----------------------------------------------------------------------------
  46. X
  47. X  This is the system's server. People send requests to the listserv
  48. X  for subscription, removal of subscription, general information request, etc.
  49. X  The recognized commands are as follows:
  50. X    help [command]
  51. X    set <list> [<option> <value>]         option: mail
  52. X                                          value: ack, noack, postpone
  53. X    subscribe <list> <name>
  54. X    unsubscribe <list> (alias signoff)
  55. X    recipients <list> (alias review)
  56. X    information <list>
  57. X    statistics <list> [subscriber email address(es)]
  58. X    shutdown <password>
  59. X    restart <password>
  60. X    lists
  61. X    index [archive]
  62. X    get <archive> <filename> [parts]
  63. X    release
  64. X
  65. X  Commands are sent to listserv one on each line of the message. The user
  66. X  is notified of the first invalid request; all subsequent commands
  67. X  are ignored -- imagine someone sending an entire book!! For each 
  68. X  successfully completed command, a confirmation is sent back to the sender.
  69. X  Commands/requests may be abbreviated.
  70. X
  71. X  The user may request help in general, or on a specific command; he can
  72. X  'set list mail ack' in which case his/her messages to the list will be
  73. X  echoed back to him/her, and 'set list mail noack' (the opposite);
  74. X  A 'set list mail postpone' request will not send any
  75. X  messages to the subscriber until he resets it to one of the other options
  76. X  (used to suppress sending email temporarily). 'set list' with no arguments
  77. X  returns the current values for all options.
  78. X  To subscribe to the list, a user has to give his/her full name; 
  79. X  he/she may leave the list by issuing an 'unsubscribe' request;
  80. X  a list of the current subscribers is obtained through a 'recipients' request;
  81. X  a copy of the sender's message is also sent to peer lists in this case.
  82. X  Moreover, general information is available by means of an 'information'
  83. X  request, and finally 'statistics' compiles a count of messages sent by
  84. X  each subscriber (unless specific subscriber address are given) as well as
  85. X  a total count of messages on file; again, a copy of the message will be
  86. X  forwarded to all peer lists.
  87. X
  88. X  The entire system may be remotely shut down by way of a 'shutdown' request;
  89. X  this request must be followed by a password that must match the one defined
  90. X  in config. Note that this may result in requests being queued with the
  91. X  'shutdown' one not being serviced; note thought that a copy of the original
  92. X  requests can be found in MAIL_COPY as defined in listserv.h.
  93. X  Likewise, the system may be remotely restarted by issuing a 'restart'
  94. X  request with the proper password. Note that a 'restart' request has no
  95. X  effect after a 'shutdown' (because the server is not running), and that
  96. X  any requests queued with the 'restart' will be serviced (the system will
  97. X  not restart until all requests are serviced).
  98. X
  99. X  A list of all discussion lists served by this server can be obtained by
  100. X  a 'lists' request.
  101. X
  102. X  Information on the current release of this server can be obtained by
  103. X  a 'release' request.
  104. X
  105. X  The system includes a means for users to obtain files. The files
  106. X  can be placed anywhere in the system and are archived under
  107. X              /usr/server/archives/*
  108. X  Each of these archives has an index of subarchives and a directory
  109. X  of files that can be obtained from that archive; there is a master
  110. X  archive in archives/listserv: the file INDEX contains names and full paths
  111. X  to all of the archives (including listserv); the file DIR is a directory 
  112. X  of files that are archived under listserv. A user
  113. X  may obtain a list of all the archives and their files by sending an
  114. X  'index' request. That request followed by a specific archive will
  115. X  send a list of files for that archive and all of its subsrchives.
  116. X  The format of the INDEX file is as follows: one line per archive
  117. X  with the following:
  118. X
  119. X          archive-name full-path-to-its-directory
  120. X
  121. X  A file can be obtained via a 'get' request, specifying the archive and
  122. X  the file to get. It is possible that a file has been split in various
  123. X  parts, in which case multiple emails with the various subparts will
  124. X  be sent to the user. Note that only the master index is used in this
  125. X  case for locating the archive. Individual parts of the split file may
  126. X  be obtained by specifying them as arguments.
  127. X
  128. X  A file may be archived with the farch utility, or manually by
  129. X  editing the DIR file of the target archive. The format is as follows:
  130. X  one line per file containing the following information:
  131. X
  132. X         filename number-of-subparts directory-to-find-it
  133. X
  134. X  The restriction is that the actual disk file should be all in lower case.
  135. X
  136. X  A new archive may be created by creating a subdirectory under
  137. X  /usr/server/archives (the new directory may not be all in lower-case letters),
  138. X  updating the master INDEX (archives/listserv/INDEX) and all lower-level
  139. X  indeces (if any), creating a new INDEX file in the new directory with one
  140. X  entry (the new archive itself), and creating an empty DIR file in the
  141. X  same new directory.
  142. X
  143. X  The hierarchical structure is only logical and directory hierarchy does
  144. X  not matter. All archives though have to be placed under /usr/server/archives.
  145. X
  146. X  All commands may be abbreviated except 'shutdown' and 'restart'.
  147. X
  148. X  COMMAND LINE OPTIONS:
  149. X    -1: Same as for the list program.
  150. X    -r: This option may be repeated an infinite number of times and is
  151. X        always followed by a valid listserv command (as outlined above).
  152. X        This forces a restriction to be placed on the specified command;
  153. X        whenever a user makes such a request, the request will be rejected
  154. X        if the number of users currently on the system is greater than
  155. X        the threshold specified in listserv.h. This option was added due
  156. X        to the fact that the 'statistics' request may take up a lot of
  157. X        resources to complete.
  158. X    -e: Echo reports to the screen.
  159. X    -n: Do not notify peer servers.
  160. X    -d: Disable a listserv command. This makes totally unknown to the server.
  161. X        However, help is still available for the particular request.
  162. X    -D: Turn debug on. A transaction of the last email sent out is kept
  163. X        in the files /usr/server/sent and /usr/server/received. This assumes
  164. X    use of the 'system' mail method.
  165. X
  166. X  EXIT CODES:
  167. X    0: OK
  168. X    1: Could not open file
  169. X    2: Could not lock file
  170. X    3: Command line option error
  171. X    4: Syntax error in file
  172. X    5: Could not spawn
  173. X    6: Shutdown request
  174. X    7: Restart request
  175. X    8: Received system signal
  176. X
  177. X  Approximate algorithm:
  178. X  {
  179. X    Place a lock so that no other programs will access the same files.
  180. X    Read the SERVER_MAIL_FILE
  181. X    if new messages have arrived then {
  182. X      For each message do {
  183. X        If the message is sent by MAILER-DAEMON forward it to MANAGER
  184. X        else {
  185. X      if the person in not in the IGNORED file, then
  186. X          scan each line of the body of the message and treat it as a
  187. X          command with possible arguments. Reply to the sender for each such
  188. X          request. If a request cannot be processed, send an error message to
  189. X          the sender and ignore all subsequent requests.
  190. X        }
  191. X      }
  192. X    }
  193. X  }
  194. X
  195. X  Required files:
  196. X    SUBSCRIBERS       <-- The list of subscribed people (diff. for each list)
  197. X    ALIASES          <-- Aliases of email addresses of subscribers, news &
  198. X                  peers.
  199. X    PEERS          <-- A list of peers for a particular list (where appl.)
  200. X    IGNORED           <-- The list of undesired people (one for the server
  201. X              and one for each list)
  202. X    SERVER_LOCK_FILE  <-- Lock file
  203. X
  204. X  Input files:
  205. X    HEADERS           <-- Used for the 'statistics' request (see list.c)
  206. X    SERVER_MAIL_FILE  <-- Where new messages go
  207. X    MAIL_COPY         <-- Copy of this file (actual work file)
  208. X    MSG_NO            <-- Read last message count
  209. X    SUBSCRIBERS
  210. X    IGNORED     
  211. X
  212. X  Output files:
  213. X    SERVER_MBOX       <-- A log of all messages sent
  214. X    SUBSCRIBERS       <-- After updating
  215. X    OLD_SUBSCRIBERS   <-- Temporary
  216. X    MSG_NO            <-- Write last message count
  217. X    REPORT_SERVER     <-- Progress report
  218. X    MAILFORWARD       <-- Completed message (with header and the
  219. X                          the body of the message) to be forwarded
  220. X
  221. X  Format of the SUBSCRIBERS, PEERS and IGNORED files:
  222. X    See comments for list.c
  223. X
  224. X  Recommended usage:
  225. X    % listserv [-r statistics] &
  226. X  or
  227. X    % listserv [-r statistics] -1
  228. X
  229. X*/
  230. X
  231. X#include <stdio.h>
  232. X#include <malloc.h>
  233. X#include <string.h>
  234. X#include <unistd.h>
  235. X#include <sys/types.h>
  236. X#include <sys/stat.h>
  237. X#include <fcntl.h>
  238. X#include <signal.h>
  239. X#include "defs.h"
  240. X#include "listserv.h"
  241. X#include "struct.h"
  242. X#include "global.h"
  243. X
  244. X/* 
  245. X  Function prototypes:
  246. X*/
  247. X
  248. X#ifdef __STDC__
  249. X#include <stdarg.h>
  250. extern int  syscom (char *, ...);
  251. X#else
  252. X#include <varargs.h>
  253. extern int  syscom ();
  254. X#endif
  255. extern int  sys_config (FILE *, SYS *);
  256. extern void report_progress (FILE *, char *, int);
  257. extern void init_signals (void);
  258. extern void catch_signals (void);
  259. extern void setup_string (char *, char *, char *);
  260. extern char *upcase (char *);
  261. extern char *locase (char *);
  262. extern void distribute (FILE *, void (*)(char *, char *, BOOLEAN),
  263. X            FILE *, char *, char *, char *, char *);
  264. extern char *cleanup_name (char *);
  265. extern void cleanup_request (char *s);
  266. extern int  getopt (int, char **, char *);
  267. extern void get_list_name (char *, char *);
  268. extern int  get_list_id (char *, SYS *, int);
  269. extern void shrink (char *);
  270. extern BOOLEAN sysmail (char *);
  271. extern BOOLEAN subscribed (FILE *, char *, char *, char *, char *, char *);
  272. extern BOOLEAN strinstr (char *, char *, char *);
  273. extern BOOLEAN ignore_sender (FILE *, char *, FILE *);
  274. extern BOOLEAN requested_part (char *, int);
  275. X
  276. void   main (int, char **, char **);
  277. void   process_message (char *, char *);
  278. void   action (char *, char *, char *);
  279. void   create_header (FILE **, char *, char *, char *, char *);
  280. void   reject_mail (char *, char *);
  281. void   help (char *, char *, char *);
  282. void   unsubscribe (char *, char *, char *);
  283. void   subscribe (char *, char *, char *);
  284. void   set (char *, char *, char *);
  285. void   recipients (char *, char *, char *);
  286. void   info (char *, char *, char *);
  287. void   stats (char *, char *, char *);
  288. void   Shutdown (char *, char *, char *);
  289. void   restart (char *, char *, char *);
  290. void   lists (char *, char *, char *);
  291. void   get (char *, char *, char *);
  292. void   Index (char *, char *, char *);
  293. void   notify_peer_servers (char *, char *, char *, char *);
  294. void   init_commands (void);
  295. void   usage (void);
  296. void   server_config (char *);
  297. void   gexit (void);
  298. X
  299. X/*
  300. X  The control structure of the server. Check if mail has arrived.
  301. X  If so, copy it to MAIL_COPY and proceed to lower level.
  302. X  First, the command line options are analyzed (for restrictions, etc.).
  303. X*/
  304. X
  305. void main (int argc, char **argv, char **envp)
  306. X{
  307. X  struct stat stat_buf;
  308. X  char *options = "1r:d:enD";
  309. X  int c;
  310. X  BOOLEAN execute_once = FALSE, notok;
  311. X  int i, j, k;
  312. X  FILE *f;
  313. X  char error [MAX_LINE];
  314. X
  315. X  extern char *optarg;
  316. X  extern int optopt;
  317. X
  318. X  init_commands();
  319. X  while ((c = getopt (argc, argv, options)) != EOF)
  320. X    switch ((char) c) {
  321. X      case '1': execute_once = TRUE; break;
  322. X      case 'e': tty_echo = TRUE; break;
  323. X      case 'n': do_not_notify_peer_server = TRUE; break;
  324. X      case 'D': debug = TRUE; break;
  325. X      case 'r': 
  326. X      case 'd':
  327. X        notok = TRUE;
  328. X        k = 0;
  329. X        upcase (optarg);
  330. X        for (i = 0; i < MAX_COMMANDS; ++i) {
  331. X          notok &= (((j = strncmp (optarg, commands[i].name, strlen (optarg)))
  332. X                    != 0) ? 1 : 0);
  333. X          if (!j) {
  334. X            ++k;
  335. X        if (c == 'r')
  336. X              restricted_commands |= commands[i].mask;
  337. X        else if (c == 'd')
  338. X          disabled_commands |= commands[i].mask;
  339. X      }
  340. X    }
  341. X        if (notok)
  342. X          fprintf (stderr, "listserv: Unrecognized request '%s'\n", optarg),
  343. X          exit (3);
  344. X        if (k > 1) /* ambiguous command */
  345. X          fprintf (stderr, "listserv: Ambiguous request '%s'\n", optarg),
  346. X          exit (3);
  347. X        break;
  348. X      case ':':
  349. X          fprintf (stderr, "listserv: Option '%c' requires an argument.\n",
  350. X           optopt);
  351. X          exit (3);
  352. X      case '?':
  353. X      default:
  354. X        usage ();
  355. X    }
  356. X#ifndef _MINIX
  357. X  if (lockf (open (SERVER_LOCK_FILE, O_RDWR), F_TLOCK, 0))
  358. X    fprintf (stderr, "listserv: Unable to lock %s. Aborting.\n", 
  359. X             SERVER_LOCK_FILE),
  360. X    exit (2);
  361. X#endif
  362. X  if (!execute_once)
  363. X    printf ("%s\n", VERSION);
  364. X  init_signals();
  365. X  catch_signals();
  366. X  if ((report = fopen (REPORT_SERVER, "a")) == NULL)
  367. X    fprintf (stderr, "listserv: Unable to open %s\n", REPORT_SERVER),
  368. X    exit (1);
  369. X  nlists = sys_config (report, &sys);
  370. X  if (sys.options & USE_ENV_VAR) {
  371. X    if ((sys.mail.method = (char *) malloc (256 * sizeof (char))) == NULL)
  372. X      report_progress (report, "\nmain(): malloc failed", TRUE),
  373. X      exit (16);
  374. X    sprintf (sys.mail.method, "env - %s=%s %s ", sys.mail.env_var,
  375. X         sys.server.address, sys.mail.mail_prog);
  376. X   }
  377. X  if ((msg_no = fopen (MSG_NO, "r")) != NULL)
  378. X    fscanf (msg_no, "%d\n", &request_no),
  379. X    fclose (msg_no);
  380. X    
  381. X  if ((f = fopen (PID_SERVER, "w")) != NULL)
  382. X    fprintf (f, "%d", getpid()),
  383. X    fclose (f);
  384. X  signal (SIGINT, gexit);
  385. X
  386. X  do {
  387. X    if (!stat (SERVER_MAIL_FILE, &stat_buf) && stat_buf.st_size > 0) {
  388. X      syscom ("cp %s %s", SERVER_MAIL_FILE, MAIL_COPY);
  389. X      syscom ("cat %s >> %s", MAIL_COPY, SERVER_MBOX);
  390. X      syscom ("echo >> %s", SERVER_MBOX);
  391. X      if (!unlink (SERVER_MAIL_FILE))
  392. X    syscom ("touch %s", SERVER_MAIL_FILE), /* rewrite file */
  393. X    syscom ("chmod 666 %s", SERVER_MAIL_FILE);
  394. X      if ((mail = fopen (MAIL_COPY, "r")) == NULL)
  395. X    sprintf (error, "listserv: Could not open %s", MAIL_COPY),
  396. X        report_progress (report, error, TRUE),
  397. X        exit (1);
  398. X      report_progress (report, NEW_ARRIVAL, FALSE);
  399. X      distribute (mail, (void *) process_message, report, NULL, NULL, NULL,
  400. X          NULL);
  401. X      fclose (mail);  /* Done */
  402. X      shrink (message_idsf);
  403. X      unlink (MAIL_COPY);  /* Done delivering */
  404. X    }
  405. X    else if (!execute_once) /* No mail to deliver */
  406. X      if (sys.frequency > 0)
  407. X        sleep (sys.frequency);
  408. X  } while (!execute_once);
  409. X  fclose (report);
  410. X  free ((char *) sys.mail.method);
  411. X  unlink (PID_SERVER);
  412. X  if (restart_sys)
  413. X    exit (7); /* Exit status of 7 signifies a restart request */
  414. X  exit (0);
  415. X}
  416. X
  417. X/*
  418. X  Process each message. Isolate each command and pass it to action().
  419. X  Before that, check if the message is from MAILER_DAEMON, in which case
  420. X  forward the message to MANAGER. Any messages from people listed in the
  421. X  IGNORED file are not processed.
  422. X  Note: Please look at the documentation for list.c for the definition of a
  423. X  mailer daemon.
  424. X*/
  425. X
  426. void process_message (char *sender, char *linecopy)
  427. X{
  428. X  char line [MAX_LINE];          /* ... from the the current message */
  429. X  char request [MAX_LINE];       /* holds each command */
  430. X  char report_msg [MAX_LINE];    /* message to be written to REPORT */
  431. X  char sender_copy [MAX_LINE];
  432. X  char id_copy [MAX_LINE];
  433. X  FILE *f;
  434. X  BOOLEAN loop = FALSE;
  435. X
  436. X  peer_server_request = FALSE;
  437. X  report_msg[0] = line[0] = message_id[0] = id_copy[0] = RESET (sender_copy);
  438. X  strcpy (sender_copy, sender);  /* we do not like converting the actual */
  439. X  upcase (sender_copy);          /* address to upper case */
  440. X  sprintf (report_msg, "Request #%04d:%s\n", ++request_no, sender);
  441. X  report_progress (report, report_msg, FALSE);
  442. X  if ((msg_no = fopen (MSG_NO, "w")) == NULL)
  443. X    sprintf (report_msg, "\nprocess_message(): Could not open %s", MSG_NO),
  444. X    report_progress (report, report_msg, TRUE),
  445. X    exit (1);
  446. X  fprintf (msg_no, "%d\n", request_no);
  447. X  fclose (msg_no);
  448. X
  449. X  while (!feof (mail) && line[0] != '\n') { /* Skip to beginning of message */
  450. X    if (!strncmp (line, SUBJECT, strlen (SUBJECT))) {
  451. X      sprintf (line, "%s", line + strlen (SUBJECT));
  452. X      if (!strncmp (line, PEER_SERVER_REQUEST, strlen (PEER_SERVER_REQUEST)))
  453. X    peer_server_request = TRUE;
  454. X    }
  455. X    if (!strncmp (line, MESSAGE_ID1, strlen (MESSAGE_ID1)) ||
  456. X        !strncmp (line, MESSAGE_ID2, strlen (MESSAGE_ID2)) ||
  457. X        !strncmp (line, MESSAGE_ID3, strlen (MESSAGE_ID3)))
  458. X      strcpy (message_id, line + strlen ("Message-") + 4),
  459. X      message_id [strlen (message_id) - 1] = EOS, /* \n -> \0 */
  460. X      strcpy (id_copy, message_id);
  461. X      upcase (id_copy);
  462. X    RESET (line);
  463. X    fgets (line, MAX_LINE - 2, mail);
  464. X  }
  465. X
  466. X  if (message_id[0] != EOS) { /* Check for mail loop using Message-Id: */
  467. X    sprintf (message_idsf, "%s/%s", PATH", MESSAGE_IDS_F);
  468. X    if (message_ids = fopen (message_idsf, "r")) {
  469. X      if (ignore_sender (message_ids, id_copy, report))
  470. X        loop = TRUE;
  471. X      fclose (message_ids);
  472. X    }
  473. X    if (!loop) {
  474. X      if ((message_ids = fopen (message_idsf, "a")) == NULL)
  475. X        sprintf (report_msg, "\nprocess_message(): Could not open %s",
  476. X                 message_idsf),
  477. X    report_progress (report, report_msg, TRUE),
  478. X        exit (1);
  479. X      fprintf (message_ids, "%s %s\n", message_id, sender); /* Save new id */
  480. X      fclose (message_ids);
  481. X    }
  482. X  }
  483. X
  484. X  if (strinstr (MAILER_DAEMON, sender_copy, "|")) {
  485. X    /* Send message to MANAGER */
  486. X    create_header (&f, MAILFORWARD, sys.server.address, sys.manager, sender);
  487. X    fprintf (f, "\nReturned mail by %s\n--------------------------------------\
  488. X-----------------------------------------\n",
  489. X             sender);
  490. X    while (!feof (mail) && 
  491. X           (strncmp (line, START_OF_MESSAGE, strlen (START_OF_MESSAGE)))) {
  492. X      RESET (line);
  493. X      fgets (line, MAX_LINE - 2, mail);
  494. X      fprintf (f, "%s", line);
  495. X    }
  496. X    COMPLETE_TELNET (f);
  497. X    fclose (f);
  498. X    DELIVER_MAIL (sys.manager);
  499. X    RESET (report_msg);
  500. X    sprintf (report_msg, "Forwarding message to %s\n", sys.manager);
  501. X    report_progress (report, report_msg, FALSE);
  502. X  }
  503. X  else /* Isolate request and call action() */
  504. X    while (!feof (mail) && 
  505. X           (strncmp (line, START_OF_MESSAGE, strlen (START_OF_MESSAGE)))) {
  506. X      cleanup_request (line);
  507. X      RESET (request);
  508. X      sscanf (line, "%s ", request);
  509. X      upcase (request);
  510. X      if (!strcmp (request, START_OF_SIGNATURE))
  511. X    one_rejection = TRUE; /* End of requests, start of .signature */
  512. X      else if (request[0] != EOS && !one_rejection && !loop)
  513. X        action (line, request, sender);
  514. X      RESET (line);
  515. X      fgets (line, MAX_LINE - 2, mail);
  516. X    }
  517. X  strcpy (linecopy, line);
  518. X  one_rejection = FALSE;
  519. X  report_progress (report, "", -TRUE); /* -TRUE for no leading newline */
  520. X}
  521. X
  522. X/*
  523. X  Recognize the 'request' and call the appropriate routine, or reject the
  524. X  message if the request is not recognized. Isolate any parameters.
  525. X  If a restriction is in force for the recognized request, then get
  526. X  the number of users currently on the system and reject the request if
  527. X  this number is greater that the predetermined threshold.
  528. X*/
  529. X
  530. void action (char *line, char *request, char *sender)
  531. X{
  532. X  char params [MAX_LINE];
  533. X  char error [MAX_LINE];
  534. X  char list_name [MAX_LINE];
  535. X  int i, nusers;
  536. X  FILE *f;
  537. X
  538. X  report_progress (report, line, FALSE);
  539. X  RESET (error);
  540. X  sprintf (server_ignoredf, "%s/%s", PATH", IGNORED);
  541. X  if ((ignored = fopen (server_ignoredf, "r")) == NULL)
  542. X    sprintf (error, "action(): Could not open %s", server_ignoredf),
  543. X    report_progress (report, error, TRUE),
  544. X    exit (1);
  545. X  if (ignore_sender (ignored, sender, report))
  546. X    goto abort;
  547. X  fclose (ignored);
  548. X  upcase (sender);
  549. X  RESET (params);
  550. X  sprintf (params, "%s", line + strlen (request)); /* Get parameters */
  551. X  upcase (params);
  552. X  for (i = 0; i < MAX_COMMANDS; i++) /* Walk through the valid requests */
  553. X    if (! strncmp (request, commands[i].name, strlen (request)) &&
  554. X    ! (disabled_commands & commands[i].mask)) {
  555. X      if (strncmp (request, "SHUTDOWN", strlen (request)) &&
  556. X          strncmp (request, "RESTART", strlen (request)) &&
  557. X          strncmp (request, "LISTS", strlen (request)) &&
  558. X      strncmp (request, "INDEX", strlen (request)) &&
  559. X      strncmp (request, "GET", strlen (request)) &&
  560. X      strncmp (request, "RELEASE", strlen (request)) &&
  561. X          strncmp (request, "HELP", strlen (request))) {
  562. X        get_list_name (params, list_name);
  563. X        listid = get_list_id (list_name, &sys, nlists);
  564. X        server_config (list_name);
  565. X        if (listid < 0) {
  566. X          if (list_name[0] == EOS)
  567. X            sprintf (error, "%s: Missing list name\n", request);
  568. X          else
  569. X            sprintf (error, "%s: Unknown list name %s\n", request, list_name);
  570. X          reject_mail (sender, error);
  571. X          return;
  572. X        }
  573. X        if ((ignored = fopen (ignoredf, "r")) == NULL)
  574. X          sprintf (error, "action(): Could not open %s", ignoredf),
  575. X          report_progress (report, error, TRUE),
  576. X          exit (1);
  577. X        if (ignore_sender (ignored, sender, report))
  578. X          goto abort;
  579. X        if (commands[i].mask & sys.lists[listid].disabled_commands) {
  580. X      sprintf (error, "%s requests for list %s are disabled\n",
  581. X           request, sys.lists[listid].alias);
  582. X      reject_mail (sender, error);
  583. X      goto abort;
  584. X    }
  585. X      }
  586. X      if (restricted_commands & commands[i].mask) { /* Restriction set */
  587. X        syscom ("%s | %s -F, '{ print $3 }' > %s", UPTIME, AWK, USERS_FILE);
  588. X        if ((f = fopen (USERS_FILE, "r")) == NULL)
  589. X      sprintf (error, "action(): Could not open %s", USERS_FILE),
  590. X          report_progress (report, error, TRUE),
  591. X          exit (1);
  592. X        fscanf (f, "%d", &nusers);
  593. X        fclose (f);
  594. X        unlink (USERS_FILE);
  595. X        if (nusers > sys.users) {
  596. X          create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  597. X          fprintf (f, "This request takes a considerable amount of resources \
  598. and certain restrictions\nare currently in force. Please resubmit your \
  599. request at a later time.\n");
  600. X      COMPLETE_TELNET (f);
  601. X          fclose (f);
  602. X      DELIVER_MAIL (sender);
  603. X          strcat (request, ": restriction enforced\n");
  604. X          report_progress (report, request, FALSE);
  605. X          goto abort;
  606. X        }
  607. X      }
  608. X      commands[i].func (request, params, sender); /* Call routine */
  609. X      goto abort;
  610. X    }
  611. X  sprintf (error, "Unrecognized request %s\n", request);
  612. X  reject_mail (sender, error);
  613. X  abort:
  614. X  fclose (ignored);
  615. X}
  616. X
  617. X/*
  618. X  Create a message header addressed to 'sender' with the given 'subject'.
  619. X  Note: some mailers require the following format when using telnet:
  620. X*/
  621. X
  622. void create_header (FILE **f, char *filename, char *sender, char *recipient, 
  623. X            char *subject)
  624. X{
  625. X  char error [MAX_LINE];
  626. X
  627. X  if ((*f = fopen (filename, "w")) == NULL)
  628. X    RESET (error),
  629. X    sprintf (error, "\ncreate_header(): Could not open %s", filename),
  630. X    report_progress (report, error, TRUE),
  631. X    exit (1);
  632. X  locase (recipient);
  633. X  if (sys.options & USE_TELNET)
  634. X    fprintf (*f, "HELO\nMAIL From: <%s>\nRCPT To: <%s>\nDATA\n",
  635. X             sender, recipient);
  636. X  if (message_id[0] != EOS)
  637. X    fprintf (*f, "Message-Id: %s\n", message_id);
  638. X  fprintf (*f, "Comment: %s\nErrors-To: %s\nReply-To: <%s>\n\
  639. Sender: %s\nVersion: %s\nFrom: %s\nTo: %s\nSubject: %s\n\n",
  640. X       sys.server.comment, sys.manager, sender, sender, VERSION, sender,
  641. X       recipient, subject);
  642. X}
  643. X
  644. X/*
  645. X  Send a message to 'sender' indicating an invalid request. The body of
  646. X  the message is given in 'text'. Only one such mail is sent to the user
  647. X  for each of his/her invalid requests. All subsequent requests are ignored.
  648. X*/
  649. X
  650. void reject_mail (char *sender, char *text)
  651. X{
  652. X  FILE *f;
  653. X  if (one_rejection)
  654. X    return;
  655. X  report_progress (report, text, FALSE);
  656. X  one_rejection = TRUE;
  657. X  create_header (&f, MAILFORWARD, sys.server.address, sender, 
  658. X         "Invalid request");
  659. X  fprintf (f, "%s\nReport any problems to '%s'.\nFor a list of the available \
  660. requests send a message to %s\nwith the word 'help' in the body of the \
  661. message.\n\nPS: Any subsequent requests that you might have submitted have \
  662. been ignored.\n", text, sys.manager, sys.server.address);
  663. X  COMPLETE_TELNET (f);
  664. X  fclose (f);
  665. X  DELIVER_MAIL (sender);
  666. X}
  667. X
  668. X/*
  669. X  Provide help on the various commands to 'sender'.
  670. X*/
  671. X
  672. void help (char *request, char *params, char *sender)
  673. X{
  674. X  FILE *f;
  675. X  char error [MAX_LINE];
  676. X  char param [MAX_LINE];
  677. X  char moreparams [MAX_LINE];
  678. X  BOOLEAN notok = TRUE;
  679. X  int i;
  680. X
  681. X  sprintf (request + strlen (request), "%s", params); /* Used as a subject */
  682. X  error[0] = param[0] = RESET (moreparams);
  683. X  sscanf (params, "%s %s\n", param, moreparams);
  684. X  if (param[0] != EOS) {  /* Check option validity */
  685. X    for (i = 0; i < MAX_COMMANDS; ++i)
  686. X      notok &= ((strncmp (param, commands[i].name, strlen (param)) != 0) ? 
  687. X                1 : 0);
  688. X    if (notok) {
  689. X      sprintf (error, "Invalid HELP topic%s", params);
  690. X      reject_mail (sender, error);
  691. X      return;
  692. X    }
  693. X  }
  694. X  if (moreparams[0] != EOS) {  /* More than one option given */
  695. X    sprintf (error, "Too many HELP topics: %s\n", moreparams);
  696. X    reject_mail (sender, error);
  697. X    return;
  698. X  }
  699. X  create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  700. X  fclose (f);
  701. X  if (param[0] == EOS)
  702. X    syscom ("cat %s >> %s", HELP_GENERAL, MAILFORWARD);
  703. X  else {
  704. X    if (!strncmp (param, "SET", strlen (param)))
  705. X      syscom ("cat %s >> %s", HELP_SET, MAILFORWARD);
  706. X    if (!strncmp (param, "SUBSCRIBE", strlen (param)))
  707. X      syscom ("cat %s >> %s", HELP_SUBSCRIBE, MAILFORWARD);
  708. X    if (!strncmp (param, "UNSUBSCRIBE", strlen (param)) ||
  709. X        !strncmp (param, "SIGNOFF", strlen(param)))
  710. X      syscom ("cat %s >> %s", HELP_UNSUBSCRIBE, MAILFORWARD);
  711. X    if (!strncmp (param, "RECIPIENTS", strlen (param)) ||
  712. X        !strncmp (param, "REVIEW", strlen(param)))
  713. X      syscom ("cat %s >> %s", HELP_RECIPIENTS, MAILFORWARD);
  714. X    if (!strncmp (param, "INFORMATION", strlen (param)))
  715. X      syscom ("cat %s >> %s", HELP_INFORMATION, MAILFORWARD);
  716. X    if (!strncmp (param, "STATISTICS", strlen (param)))
  717. X      syscom ("cat %s >> %s", HELP_STATISTICS, MAILFORWARD);
  718. X    if (!strncmp (param, "LISTS", strlen (param)))
  719. X      syscom ("cat %s >> %s", HELP_LISTS, MAILFORWARD);
  720. X    if (!strncmp (param, "INDEX", strlen (param)))
  721. X      syscom ("cat %s >> %s", HELP_INDEX, MAILFORWARD);
  722. X    if (!strncmp (param, "GET", strlen (param)))
  723. X      syscom ("cat %s >> %s", HELP_GET, MAILFORWARD);
  724. X    if (!strncmp (param, "RELEASE", strlen (param)))
  725. X      syscom ("cat %s >> %s", HELP_RELEASE, MAILFORWARD);
  726. X  }
  727. X  APPEND_TELNET ("help");
  728. X  DELIVER_MAIL (sender);
  729. X}
  730. X
  731. X/*
  732. X  Unsubscribe a member if he/she is listed in SUBSCRIBERS.
  733. X*/
  734. X
  735. void unsubscribe (char *request, char *params, char *sender)
  736. X{
  737. X  FILE *f;
  738. X  char error [MAX_LINE];
  739. X  char param [MAX_LINE];
  740. X  BOOLEAN status;
  741. X  
  742. X  sprintf (request + strlen (request), " %s%s", sys.lists[listid].alias,
  743. X           params); /* Used as a subject */
  744. X  error[0] = RESET (param);
  745. X  sscanf (params, "%s", param);
  746. X  if (param[0] != EOS) { /* No parameters accepted */
  747. X    sprintf (error, "Invalid UNSUBSCRIBE option%s", params);
  748. X    reject_mail (sender, error);
  749. X    return;
  750. X  }
  751. X  if (!(status = subscribed (report, sender, subscribersf, newsf, peersf,
  752. X                 aliasesf))) {
  753. X    sprintf (error, "%s: %sYou are not subscribed to %s\n", sender, request,
  754. X             sys.lists[listid].address);
  755. X    reject_mail (sender, error);
  756. X    return;
  757. X  }
  758. X  else if (status > SUBSCRIBED) { /* Notify manager */
  759. X    NOTIFY_MANAGER ("Attempt to unsubscribe new or peer");
  760. X    return;
  761. X  }
  762. X  /* Now move the current list of subscribers to a temporary file; then
  763. X     copy each entry of this file to SUBSCRIBERS excluding the
  764. X     user to be removed. */
  765. X  syscom ("mv %s %s", subscribersf, OLD_SUBSCRIBERS);
  766. X  syscom ("grep -i -v '%s' %s > %s", sender, OLD_SUBSCRIBERS, subscribersf);
  767. X  unlink (OLD_SUBSCRIBERS);
  768. X  syscom ("mv %s %s", aliasesf, OLD_SUBSCRIBERS);
  769. X  syscom ("grep -i -v %s %s > %s", sender, OLD_SUBSCRIBERS, aliasesf);
  770. X  unlink (OLD_SUBSCRIBERS);
  771. X  create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  772. X  fprintf (f, "You have been removed from list %s\nThanks for being with us.\n",
  773. X           sys.lists[listid].address);
  774. X  COMPLETE_TELNET (f);
  775. X  fclose (f);
  776. X  DELIVER_MAIL (sender);
  777. X}
  778. X
  779. X/*
  780. X  Subscribe a new user if he/she is not already subscribed.
  781. X*/
  782. X
  783. void subscribe (char *request, char *params, char *sender)
  784. X{
  785. X  FILE *f;
  786. X  char error [MAX_LINE];
  787. X  char name [MAX_LINE];
  788. X  int i;
  789. X  BOOLEAN status;
  790. X
  791. X  sprintf (request + strlen (request), " %s%s", sys.lists[listid].alias,
  792. X           params); /* Used as a subject */
  793. X  error[0] = RESET (name);
  794. X  cleanup_name (params); /* Remove extraneous characters */
  795. X  sscanf (params, "%s\n", name);
  796. X  if (name[0] == EOS) {  /* No user's name */
  797. X    sprintf (error, "No name given to SUBSCRIBE\n");
  798. X    reject_mail (sender, error);
  799. X    return;
  800. X  }
  801. X  if ((status = subscribed (report, sender, subscribersf, newsf, peersf,
  802. X                aliasesf)) == SUBSCRIBED) {
  803. X    sprintf (error, "%s: You are already subscribed to %s\n", sender, 
  804. X             sys.lists[listid].address);
  805. X    reject_mail (sender, error);
  806. X    return;
  807. X  }
  808. X  else if (status > SUBSCRIBED) { /* Notify manager */
  809. X    NOTIFY_MANAGER ("Attempt to subscribe news or peer");
  810. X    return;
  811. X  }
  812. X  if ((f = fopen (subscribersf, "a")) == NULL)
  813. X    sprintf (error, "subscribe(): Could not open %s", subscribersf),
  814. X    report_progress (report, error, TRUE),
  815. X    exit (1);
  816. X  fprintf (f, "%s ", sender);
  817. X  for (i = 0; i < MAX_SET_OPTIONS; i++)  /* Copy all options */
  818. X    fprintf (f, "%s ", default_values[i]);
  819. X  fprintf (f, "%s\n", params);
  820. X  fclose (f);
  821. X  create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  822. X  fprintf (f, "You have been added to list %s\nRequests to %s\n", 
  823. X           sys.lists[listid].address, sys.server.address);
  824. X  fclose (f);
  825. X  syscom ("cat %s >> %s", welcomef, MAILFORWARD);
  826. X  APPEND_TELNET ("subscribe");
  827. X  DELIVER_MAIL (sender);
  828. X}
  829. X
  830. X/*
  831. X  Set options for user if he/she is subscribed.
  832. X  Adding more SET options:
  833. X  - In listserv.h, define the new MAX_SET_OPTIONS.
  834. X  - In listserv.h, define the new option in options[], the valid
  835. X    values in values[] and the default_values[].
  836. X  - the MAIL option should be first in list; this assumption is made by list
  837. X    and listserv.
  838. X*/
  839. X
  840. void set (char *request, char *params, char *sender)
  841. X{
  842. X  FILE *f, *from, *to;
  843. X  char error [MAX_LINE];
  844. X  char option [MAX_LINE];
  845. X  char subscriber [MAX_LINE];
  846. X  char oldmodes [MAX_SET_OPTIONS] [MAX_LINE];
  847. X  char newmode [MAX_LINE];
  848. X  char moreparams [MAX_LINE];
  849. X  char name [MAX_LINE];
  850. X  int  i, index;
  851. X  BOOLEAN status;
  852. X
  853. X  sprintf (request + strlen (request), " %s%s", sys.lists[listid].alias,
  854. X           params); /* Used as a subject */
  855. X  error[0] = option[0] = moreparams[0] = RESET (newmode);
  856. X  sscanf (params, "%s %s %s\n", option, newmode, moreparams); /* Get params */
  857. X  upcase (option);
  858. X  upcase (newmode);
  859. X  if (!(status = subscribed (report, sender, subscribersf, newsf, peersf,
  860. X                 aliasesf))) {
  861. X    sprintf (error, "%s: %sYou are not subscribed to %s\n", sender, request,
  862. X             sys.lists[listid].address);
  863. X    reject_mail (sender, error);
  864. X    return;
  865. X  }
  866. X  else if (status > SUBSCRIBED) { /* Notify manager */
  867. X    NOTIFY_MANAGER ("Attempt to set mode for news or peer");
  868. X    return;
  869. X  }
  870. X  if (option[0] != EOS) {
  871. X    for (index = 0; index < MAX_SET_OPTIONS; index++)
  872. X      if (! strcmp (option, options[index]))
  873. X    break;
  874. X    if (index == MAX_SET_OPTIONS) {
  875. X      sprintf (error, "Invalid SET option %s\n", option);
  876. X      reject_mail (sender, error);
  877. X      return;
  878. X    }
  879. X    if (! strinstr (values[index], newmode, "|")) {
  880. X      sprintf (error, "%s SET %s value %s\n", 
  881. X               (newmode[0] != EOS ? "Invalid" : "Missing"), 
  882. X           options[index], newmode);
  883. X      reject_mail (sender, error);
  884. X      return;
  885. X    }
  886. X  }
  887. X  if (moreparams[0] != EOS) {  /* More than one option given */
  888. X    sprintf (error, "Too many SET parameters: %s\n", moreparams);
  889. X    reject_mail (sender, error);
  890. X    return;
  891. X  }
  892. X  if (option[0] == EOS) { /* Status inquiry */
  893. X    syscom ("grep -i '%s' %s | %s -d' ' -f2,%d > %s", sender, subscribersf,
  894. X        CUT, MAX_SET_OPTIONS + 1, OLD_SUBSCRIBERS);
  895. X    if (! one_rejection) {
  896. X      create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  897. X      fprintf (f, "Current settings are:\n");
  898. X      if ((from = fopen (OLD_SUBSCRIBERS, "r")) == NULL)
  899. X     sprintf (error, "set(): Could not open %s", OLD_SUBSCRIBERS),
  900. X    report_progress (report, error, TRUE),
  901. X    exit (1);
  902. X      for (i = 0; i < MAX_SET_OPTIONS; i++)
  903. X    RESET (newmode),
  904. X    fscanf (from, "%s ", newmode),
  905. X    fprintf (f, "%s = %s\n", options[i], newmode);
  906. X      fflush (f);
  907. X      fclose (from);
  908. X      COMPLETE_TELNET (f);
  909. X      fclose (f);
  910. X      DELIVER_MAIL (sender);
  911. X      unlink (OLD_SUBSCRIBERS);
  912. X      return;
  913. X    }
  914. X  }
  915. X  /* Change of mode */
  916. X  syscom ("mv %s %s", subscribersf, OLD_SUBSCRIBERS);
  917. X  if ((from = fopen (OLD_SUBSCRIBERS, "r")) == NULL)
  918. X    sprintf (error, "set(): Could not open %s", OLD_SUBSCRIBERS),
  919. X    report_progress (report, error, TRUE),
  920. X    exit (1);
  921. X  if ((to = fopen (subscribersf, "w")) == NULL)
  922. X    sprintf (error, "set(): Could not open %s", subscribersf),
  923. X    report_progress (report, error, TRUE),
  924. X    exit (1);
  925. X  while (!feof (from)) {
  926. X    subscriber[0] = RESET (name);
  927. X    extract_subscriber (from, subscriber);
  928. X    for (i = 0; i < MAX_SET_OPTIONS; i++)
  929. X      RESET (oldmodes[i]),
  930. X      fscanf (from, "%s ", oldmodes[i]);
  931. X    fgets (name, MAX_LINE - 2, from);
  932. X    upcase (subscriber);
  933. X    if (!feof (from)) {
  934. X      fprintf (to, "%s ", subscriber);
  935. X      if (! strcmp (sender, subscriber))
  936. X    for (i = 0; i < MAX_SET_OPTIONS; i++)
  937. X      fprintf (to, "%s ", (i == index) ? newmode : oldmodes[i]);
  938. X      else
  939. X    for (i = 0; i < MAX_SET_OPTIONS; i++)
  940. X      fprintf (to, "%s ", oldmodes[i]);
  941. X      fprintf (to, "%s", name);
  942. X    }
  943. X  }
  944. X  fclose (from);
  945. X  fclose (to);
  946. X  unlink (OLD_SUBSCRIBERS);
  947. X  if (! one_rejection) {
  948. X    create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  949. X    fprintf (f, "%s mode reset to %s\n", options[index], newmode);
  950. X    COMPLETE_TELNET (f);
  951. X    fclose (f);
  952. X    DELIVER_MAIL (sender);
  953. X  }
  954. X}
  955. X
  956. X/*
  957. X  Provide user with the current list of subscribers. Peer servers are
  958. X  also notified and they send their own compilations.
  959. X*/
  960. X
  961. void recipients (char *request, char *params, char *sender)
  962. X{
  963. X  char error [MAX_LINE];
  964. X  char param [MAX_LINE];
  965. X  FILE *f;
  966. X
  967. X  sprintf (request + strlen (request), " %s%s", sys.lists[listid].alias,
  968. X           params); /* Used as a subject */
  969. X  error[0] = RESET (param);
  970. X  sscanf (params, "%s", param);
  971. X  if (param[0] != EOS) {
  972. X    sprintf (error, "Invalid RECIPIENTS option%s", params);
  973. X    reject_mail (sender, error);
  974. X    return;
  975. X  }
  976. X  create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  977. X  fprintf (f, "Here is the current list of subscribers:\n\n");
  978. X  fclose (f);
  979. X  syscom ("%s -d\" \" -f1,3-6 %s > %s", CUT, subscribersf, recipf);
  980. X  syscom ("%s -f %s %s >> %s", AWK, AWK_PROG, recipf, MAILFORWARD);
  981. X  unlink (recipf);
  982. X  syscom ("echo Total number of subscribers: `cat %s | wc -l` >> %s",
  983. X          subscribersf, MAILFORWARD);
  984. X  APPEND_TELNET ("recipients");
  985. X  DELIVER_MAIL (sender);
  986. X  notify_peer_servers (peersf, "recipients", params, sender);
  987. X}
  988. X
  989. X/*
  990. X  Provide user with general information about the list. A reminder, the
  991. X  actual text is in the INFO_FILE.
  992. X*/
  993. X
  994. void info (char *request, char *params, char *sender)
  995. X{
  996. X  char error [MAX_LINE];
  997. X  char param [MAX_LINE];
  998. X  FILE *f;
  999. X
  1000. X  sprintf (request + strlen (request), " %s%s", sys.lists[listid].alias,
  1001. X           params); /* Used as a subject */
  1002. X  error[0] = RESET (param);
  1003. X  sscanf (params, "%s", param);
  1004. X  if (param[0] != EOS) {
  1005. X    sprintf (error, "Invalid INFORMATION option%s", params);
  1006. X    reject_mail (sender, error);
  1007. X    return;
  1008. X  }
  1009. X  create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  1010. X  fclose (f);
  1011. X  syscom ("cat %s >> %s", infof, MAILFORWARD);
  1012. X  APPEND_TELNET ("info");
  1013. X  DELIVER_MAIL (sender);
  1014. X}
  1015. X
  1016. X/*
  1017. X  Collect and send statistics about all subscribers, by grepping through
  1018. X  HEADERS. If a user has selected particular names (asterisks are OK) then
  1019. X  give statistics about these people only. Also include a total count
  1020. X  of messages on file. Peer servers are also notified and they send their
  1021. X  own compilations.
  1022. X*/
  1023. X
  1024. void stats (char *request, char *params, char *sender)
  1025. X{
  1026. X  FILE *f;
  1027. X  char subscriber [MAX_LINE], error [MAX_LINE];
  1028. X  char junk [MAX_LINE];
  1029. X  struct stat stat_buf;
  1030. X
  1031. X  sprintf (request + strlen (request), " %s%s", sys.lists[listid].alias,
  1032. X           params); /* Used as a subject */
  1033. X  params [strlen (params) - 1] = EOS; /* Remove \n */
  1034. X  junk[0] = RESET (error);
  1035. X  sscanf (params, "%s", junk);
  1036. X  if (junk[0] == EOS) /* No specific subscribers to report on */
  1037. X    RESET (params);
  1038. X  if (stat (headersf, &stat_buf)) {
  1039. X    NOTIFY_OF_BAD_ARCHIVE ("Sorry, no mail archive found.\n", NULL);
  1040. X    return;
  1041. X  }
  1042. X  create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  1043. X  fprintf (f, "Here are the number of messages per subscriber:\n\n");
  1044. X  fclose (f);
  1045. X  syscom ("%s %s %s %s %s %s", STATS_PROG, PATH", subscribersf,
  1046. X          headersf, MAILFORWARD, params);
  1047. X  APPEND_TELNET ("stats");
  1048. X  DELIVER_MAIL (sender);
  1049. X  notify_peer_servers (peersf, "statistics", params, sender);
  1050. X}
  1051. X
  1052. X/*
  1053. X  Exit with a shutdown status if the correct password is provided.
  1054. X*/
  1055. X
  1056. void Shutdown (char *request, char *params, char *sender)
  1057. X{
  1058. X  char error [MAX_LINE];
  1059. X  char passwd [MAX_LINE];
  1060. X
  1061. X  sscanf (params, "%s\n", passwd);
  1062. X  if (!strcmp (sys.server.password, passwd))
  1063. X    report_progress (report, "SHUTDOWN request accepted", TRUE),
  1064. X    exit (6); /* Exit status of 6 signifies a shutdown request */
  1065. X  RESET (error);
  1066. X  sprintf (error, "Unrecognized request %s\n", request),
  1067. X  reject_mail (sender, error);
  1068. X}
  1069. X
  1070. X/*
  1071. X  Set the global variable 'restart_sys' to TRUE if the correct password
  1072. X  is given.
  1073. X*/
  1074. X
  1075. void restart (char *request, char *params, char *sender)
  1076. X{
  1077. X  char error [MAX_LINE];
  1078. X  char passwd [MAX_LINE];
  1079. X
  1080. X  sscanf (params, "%s\n", passwd);
  1081. X  if (!strcmp (sys.server.password, passwd)) { 
  1082. X    report_progress (report, "RESTART request accepted\n", FALSE);
  1083. X    restart_sys = TRUE;
  1084. X    return;
  1085. X  }
  1086. X  RESET (error);
  1087. X  sprintf (error, "Unrecognized request %s\n", request);
  1088. X  reject_mail (sender, error);
  1089. X}
  1090. X
  1091. X/*
  1092. X  Provide 'sender' with a list of all discussion lists served by this server.
  1093. X*/
  1094. X
  1095. void lists (char *request, char *params, char *sender)
  1096. X{
  1097. X  char param [MAX_LINE];
  1098. X  FILE *f;
  1099. X  int i;
  1100. X
  1101. X  sprintf (request + strlen (request), "%s", params); /* Used as a subject */
  1102. X  RESET (param);
  1103. X  sscanf (params, "%s", param);
  1104. X  if (param[0] != EOS) {
  1105. X    sprintf (param, "Invalid LISTS option%s", params);
  1106. X    reject_mail (sender, param);
  1107. X    return;
  1108. X  }
  1109. X  create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  1110. X  fprintf (f, "Here is the current active list of discussion lists served by \
  1111. this server:\n\n");
  1112. X  for (i = 0; i < nlists; ++i)
  1113. X    fprintf (f, "%s\t%s\n", sys.lists[i].address, sys.lists[i].comment);
  1114. X  COMPLETE_TELNET (f);
  1115. X  fclose (f);
  1116. X  DELIVER_MAIL (sender);
  1117. X}
  1118. X
  1119. X/*
  1120. X  Get an index of files for the specified archive, or the master archive
  1121. X  if none specified.
  1122. X
  1123. X  Adapted USER CONTRIBUTED FUNCTION.
  1124. X*/
  1125. X
  1126. void Index (char *request, char *params, char *sender)
  1127. X{
  1128. X  FILE *f, *index, *dir, *master;
  1129. X  char error [MAX_LINE];
  1130. X  char archive [MAX_LINE];
  1131. X  char arch [MAX_LINE];
  1132. X  char line [MAX_LINE];
  1133. X  char file [MAX_LINE];
  1134. X  char fullpath [MAX_LINE];
  1135. X  char moreparams [MAX_LINE];
  1136. X  char fullname [MAX_LINE];
  1137. X  char desc [MAX_LINE];
  1138. X  char junk [MAX_LINE];
  1139. X  BOOLEAN found;
  1140. X  int count, parts;
  1141. X
  1142. X  sprintf (request + strlen (request), "%s", params); /* Used as a subject */
  1143. X  error[0] = archive[0] = fullpath[0] = fullname[0] = RESET (moreparams);
  1144. X  sscanf (params, "%s %s", archive, moreparams);
  1145. X  locase (archive);
  1146. X  if (moreparams[0] != EOS) {
  1147. X    sprintf (error, "Too many arguments to INDEX: %s\n", moreparams);
  1148. X    reject_mail (sender, error);
  1149. X    return;
  1150. X  }
  1151. X  if (archive[0] == EOS) /* Get archive */
  1152. X    strcpy (archive, DEFAULT_ARCHIVE);
  1153. X  sprintf (fullpath, "%s/%s/%s", ARCHIVE_DIR, DEFAULT_ARCHIVE, INDEX);
  1154. X  if ((master = fopen (fullpath, "r")) == NULL) {
  1155. X    NOTIFY_OF_BAD_ARCHIVE ("%s: Sorry, no master index found.\n", archive);
  1156. X    return;
  1157. X  }
  1158. X  found = FALSE;
  1159. X  while (!feof (master)) { /* Look at the master index for fullpath */
  1160. X    fullpath[0] = arch[0] = RESET (line);
  1161. X    fgets (line, MAX_LINE - 2, master);
  1162. X    if (line[0] != EOS) {
  1163. X      sscanf (line, "%s %s\n", arch, fullpath);
  1164. X      locase (arch);
  1165. X      if (!strcmp (arch, archive)) {
  1166. X     found = TRUE;
  1167. X    break;
  1168. X      }
  1169. X    }
  1170. X  }
  1171. X  fclose (master);
  1172. X  if (!found) {
  1173. X    NOTIFY_OF_BAD_ARCHIVE ("Sorry, archive %s not found.\n", archive);
  1174. X    return;
  1175. X  }
  1176. X  sprintf (fullname, "%s/%s", fullpath, INDEX);
  1177. X  if ((index = fopen (fullname, "r")) == NULL) { /* Open index */
  1178. X    NOTIFY_OF_BAD_ARCHIVE ("Sorry, no index found in archive %s.\n", archive);
  1179. X    return;
  1180. X  }
  1181. X  create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  1182. X  count = 0;
  1183. X  while (!feof (index)) { /* Echo archive; goto archive a get DIR file */
  1184. X    fullpath[0] = archive[0] = RESET (line);
  1185. X    fgets (line, MAX_LINE - 2, index);
  1186. X    if (line[0] != EOS) {
  1187. X      sscanf (line, "%s %s\n", archive, fullpath);
  1188. X      fprintf (f, "\n%s: %s -- Files:\n", (!count ?  "Archive" : "Subarchive"),
  1189. X           archive);
  1190. X      if (chdir (fullpath))
  1191. X    fprintf (f, "%s: Sorry, archive out of date.\n", archive);
  1192. X      else { /* Open DIR and get file names */
  1193. X    if ((dir = fopen (DIR, "r")) == NULL)
  1194. X      fprintf (f, "Cannot obtain directory information.\n");
  1195. X    else
  1196. X      while (!feof (dir)) {
  1197. X        desc[0] = line[0] = junk[0] = RESET (file);
  1198. X        fscanf (dir, "%s %d %s", file, &parts, junk);
  1199. X        fgets (desc, MAX_LINE - 2, dir);
  1200. X        if (desc[strlen (desc) - 1] == '\n')
  1201. X          desc[strlen (desc) - 1] = EOS;
  1202. X        if (file[0] != EOS)
  1203. X          fprintf (f, "  %s (%d part%s %s%s\n", file, parts,
  1204. X               (parts > 1 ? "s)" : ")"),
  1205. X               ((desc[0] != EOS) ? "--" : " "), desc);
  1206. X      }
  1207. X    fclose (dir);
  1208. X      }
  1209. X    }
  1210. X    ++count;
  1211. X  }
  1212. X  fclose (index);
  1213. X  COMPLETE_TELNET (f);
  1214. X  fclose (f);
  1215. X  DELIVER_MAIL (sender);
  1216. X}
  1217. X
  1218. X/*
  1219. X  Send the requested file from the specified archive. The file may have been
  1220. X  split in subparts, and in this case several emails will be sent -- one
  1221. X  for each part. The user may also obtain certain parts.
  1222. X
  1223. X  Adapted USER CONTRIBUTED FUNCTION.
  1224. X*/
  1225. X
  1226. void get (char *request, char *params, char *sender)
  1227. X{
  1228. X  FILE *f, *dir, *master;
  1229. X  char error [MAX_LINE];
  1230. X  char archive [MAX_LINE];
  1231. X  char arch [MAX_LINE];
  1232. X  char fullpath [MAX_LINE];
  1233. X  char moreparams [MAX_LINE];
  1234. X  char filename [MAX_LINE];
  1235. X  char fullname [MAX_LINE];
  1236. X  char dirpath [MAX_LINE];
  1237. X  char file [MAX_LINE];
  1238. X  char line [MAX_LINE];
  1239. X  char copy [MAX_LINE];
  1240. X  struct stat stat_buf;
  1241. X  int i, count = 0;
  1242. X  BOOLEAN found;
  1243. X
  1244. X  sprintf (request + strlen (request), "%s", params); /* Used as a subject */
  1245. X  error[0] = archive[0] = fullpath[0] = fullname[0] = dirpath[0] =
  1246. X  filename[0] = RESET (moreparams);
  1247. X  params [strlen (params) - 1] = EOS; /* Remove \n */
  1248. X  sscanf (params, "%s %s %s", archive, filename, moreparams);
  1249. X  locase (archive);
  1250. X  locase (filename);
  1251. X  if (archive[0] == EOS || filename[0] == EOS) { /* Missing args */
  1252. X    sprintf (error, "GET: missing archive or file name\n");
  1253. X    reject_mail (sender, error);
  1254. X    return;
  1255. X  }
  1256. X  if (moreparams[0] != EOS) { /* Specified parts to get */
  1257. X    strcpy (copy, filename);
  1258. X    upcase (copy);
  1259. X    do {
  1260. X      sprintf (params, "%s", params + 1);
  1261. X    } while (strncmp (params, copy, strlen (copy)));
  1262. X    sprintf (params, "%s", params + strlen (copy));
  1263. X    sprintf (params, "%s", strchr (params, moreparams[0]));
  1264. X  }
  1265. X  sprintf (fullpath, "%s/%s/%s", ARCHIVE_DIR, DEFAULT_ARCHIVE, INDEX);
  1266. X  if ((master = fopen (fullpath, "r")) == NULL) {
  1267. X    NOTIFY_OF_BAD_ARCHIVE ("%s: Sorry, no master index found.\n", archive);
  1268. X    return;
  1269. X  }
  1270. X  found = FALSE;
  1271. X  while (!feof (master)) { /* Look at the master index for fullpath */
  1272. X    fullpath[0] = arch[0] = RESET (line);
  1273. X    fgets (line, MAX_LINE - 2, master);
  1274. X    if (line[0] != EOS) {
  1275. X      sscanf (line, "%s %s\n", arch, fullpath);
  1276. X      locase (arch);
  1277. X      if (!strcmp (arch, archive)) {
  1278. X        found = TRUE;
  1279. X        break;
  1280. X      }
  1281. X    }
  1282. X  }
  1283. X  fclose (master);
  1284. X  if (!found) {
  1285. X    NOTIFY_OF_BAD_ARCHIVE ("Sorry, archive %s not found.\n", archive);
  1286. X    return;
  1287. X  }
  1288. X  sprintf (dirpath, "%s/%s", fullpath, DIR);
  1289. X  if ((dir = fopen (dirpath, "r")) == NULL) { /* Dir archive */
  1290. X    NOTIFY_OF_BAD_ARCHIVE ("Unable to dir archive %s\n", archive);
  1291. X    return;
  1292. X  }
  1293. X  while (!feof (dir)) { /* Get location and file-count of file to send */
  1294. X    file[0] = fullpath[0] = RESET (line);
  1295. X    fgets (line, MAX_LINE - 2, dir);
  1296. X    if (line[0] != EOS) {
  1297. X      sscanf (line, "%s %d %s", file, &count, fullpath);
  1298. X      locase (file);
  1299. X      if (!strcmp (filename, file))
  1300. X    break;
  1301. X    }
  1302. X  }
  1303. X  fclose (dir);
  1304. X  for (i = 1; i <= count; i++) { /* Send all parts of the file */
  1305. X    RESET (fullname);
  1306. X    if (count > 1)
  1307. X      sprintf (fullname, "%s/%s%d", fullpath, filename, i);
  1308. X    else
  1309. X      sprintf (fullname, "%s/%s", fullpath, filename);
  1310. X    if (moreparams[0] != EOS && !requested_part (params, i))
  1311. X      continue;
  1312. X    syscom ("uncompress %s > /dev/null 2>&1", fullname);
  1313. X    if (stat (fullname, &stat_buf)) {
  1314. X      NOTIFY_OF_BAD_ARCHIVE ("Sorry, file %s not found in specified archive.\n",
  1315. X                filename);
  1316. X      return;
  1317. X    }
  1318. X    create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  1319. X    fprintf (f, "Archive %s: file %s, part %d/%d:\n\n", archive, filename, i,
  1320. X         count);
  1321. X    fprintf (f, "------------------------------ Cut here \
  1322. X------------------------------\n");
  1323. X    fclose (f);
  1324. X    syscom ("cat %s >> %s", fullname, MAILFORWARD);
  1325. X    syscom ("echo ------------------------------ Cut here \
  1326. X------------------------------ >> %s", MAILFORWARD);
  1327. X    APPEND_TELNET ("get");
  1328. X    DELIVER_MAIL (sender);
  1329. X    syscom ("compress %s > /dev/null 2>&1", fullname);
  1330. X  }
  1331. X}
  1332. X
  1333. X/*
  1334. X  Give specific information about this release.
  1335. X*/
  1336. X
  1337. void release (char *request, char *params, char *sender)
  1338. X{
  1339. X  char param [MAX_LINE];
  1340. X  FILE *f;
  1341. X
  1342. X  sprintf (request + strlen (request), "%s", params); /* Used as a subject */
  1343. X  RESET (param);
  1344. X  sscanf (params, "%s", param);
  1345. X  if (param[0] != EOS) {
  1346. X    sprintf (param, "Invalid RELEASE option%s", params);
  1347. X    reject_mail (sender, param);
  1348. X    return;
  1349. X  }
  1350. X  create_header (&f, MAILFORWARD, sys.server.address, sender, request);
  1351. X  fprintf (f, "UNIX Listserv, version %s\n", VERSION);
  1352. X  fprintf (f, "Last update: %s\nManager: %s\nListserv address: %s\n",
  1353. X       UPDATE_DATE, sys.manager, sys.server.address);
  1354. X  COMPLETE_TELNET (f);
  1355. X  fclose (f);
  1356. X  DELIVER_MAIL (sender);
  1357. X}
  1358. X
  1359. X/*
  1360. X  Forward a 'request' to all servers handling peer lists.
  1361. X*/
  1362. X
  1363. void notify_peer_servers (char *file, char *request, char *params, char *sender)
  1364. X{
  1365. X  FILE *f, *mail;
  1366. X  char *mail_method;
  1367. X  char email [MAX_LINE];
  1368. X  char mode [MAX_LINE];
  1369. X  char alias [MAX_LINE];
  1370. X  char listserv [MAX_LINE];
  1371. X  char servers [MAX_LINE];
  1372. X  char error [MAX_LINE];
  1373. X  char subject [MAX_LINE];
  1374. X
  1375. X  if (peer_server_request || do_not_notify_peer_server)
  1376. X    return;
  1377. X  if ((f = fopen (file, "r")) == NULL)
  1378. X    sprintf (error, "notify_peer_servers(): Could not open %s", file),
  1379. X    report_progress (report, error, TRUE),
  1380. X    exit (1);
  1381. X  sprintf (subject, "%s%s", PEER_SERVER_REQUEST, sys.server.address);
  1382. X  RESET (servers);
  1383. X  if (sys.options & USE_ENV_VAR) {
  1384. X    if ((mail_method = (char *) malloc ((10 + strlen (sys.mail.env_var) +
  1385. X                     strlen (sender) +
  1386. X                     strlen (sys.mail.mail_prog))
  1387. X                    * sizeof (char))) == NULL)
  1388. X      report_progress (report,  "\nnotify_peer_servers(): malloc failed",
  1389. X               TRUE),
  1390. X      exit (16);
  1391. X    sprintf (mail_method, "env - %s=%s %s ", sys.mail.env_var,
  1392. X         sender, sys.mail.mail_prog);
  1393. X  }
  1394. X  else {
  1395. X    if ((mail_method = (char *) malloc ((strlen (sys.mail.method) + 1) 
  1396. X                    * sizeof (char))) == NULL)
  1397. X      report_progress (report, "\nnotify_peer_servers(): malloc failed",
  1398. X               TRUE),
  1399. X      exit (16);
  1400. X    strcpy (mail_method, sys.mail.method);
  1401. X  }
  1402. X  while (!feof (f)) {
  1403. X    email[0] = mode[0] = alias[0] = RESET (listserv);
  1404. X    fscanf (f, "%s %s %s %s\n", email, mode, alias, listserv);
  1405. X    if (email[0] != EOS) { /* Send mail to peer server */
  1406. X      sprintf (servers + strlen (servers), "%s\n", listserv);
  1407. X      create_header (&mail, MAILFORWARD, sender, listserv, 
  1408. X             subject);
  1409. X      fprintf (mail, "%s %s %s\n", request, alias, params);
  1410. X      COMPLETE_TELNET (mail);
  1411. X      fclose (mail);
  1412. X      if (sys.options & USE_SYSMAIL)
  1413. X    sysmail (MAILFORWARD);
  1414. X      else
  1415. X    syscom ("%s %s < %s", mail_method,
  1416. X        (((sys.options & USE_TELNET) == 0) ? locase (listserv) : ""),
  1417. X        MAILFORWARD);
  1418. X    }
  1419. X  }
  1420. X  if (servers[0] != EOS) { /* Notify sender as well */
  1421. X    sprintf (subject, "Notification from %s", sys.server.address);
  1422. X    create_header (&mail, MAILFORWARD, sys.server.address, sender, subject);
  1423. X    fprintf (mail, "%s: Your request has been forwarded to the following peer \
  1424. servers,\nwho will forward you with a copy of their own results:\n\n%s", 
  1425. upcase (request), servers);
  1426. X    COMPLETE_TELNET (mail);
  1427. X    fclose (mail);
  1428. X    if (sys.options & USE_SYSMAIL)
  1429. X      sysmail (MAILFORWARD);
  1430. X    else
  1431. X      syscom ("%s %s < %s", mail_method,
  1432. X          (((sys.options & USE_TELNET) == 0) ? locase (sender) : ""),
  1433. X          MAILFORWARD);
  1434. X  }
  1435. X  free ((char *) mail_method);
  1436. X  fclose (f);
  1437. X}
  1438. X
  1439. X/*
  1440. X  Initialize the commands[].
  1441. X*/
  1442. X
  1443. void init_commands ()
  1444. X{
  1445. X  commands[0].name = "HELP";
  1446. X  commands[0].mask = 0x001;
  1447. X  commands[0].func = help;
  1448. X  commands[1].name = "SET";
  1449. X  commands[1].mask = 0x002;
  1450. X  commands[1].func = set;
  1451. X  commands[2].name = "SUBSCRIBE";
  1452. X  commands[2].mask = 0x004;
  1453. X  commands[2].func = subscribe;
  1454. X  commands[3].name = "UNSUBSCRIBE";
  1455. X  commands[3].mask = 0x008;
  1456. X  commands[3].func = unsubscribe;
  1457. X  commands[4].name = "SIGNOFF";
  1458. X  commands[4].mask = 0x008;
  1459. X  commands[4].func = unsubscribe;
  1460. X  commands[5].name = "RECIPIENTS";
  1461. X  commands[5].mask = 0x010;
  1462. X  commands[5].func = recipients;
  1463. X  commands[6].name = "REVIEW";
  1464. X  commands[6].mask = 0x010;
  1465. X  commands[6].func = recipients;
  1466. X  commands[7].name = "INFORMATION";
  1467. X  commands[7].mask = 0x020;
  1468. X  commands[7].func = info;
  1469. X  commands[8].name = "STATISTICS";
  1470. X  commands[8].mask = 0x040;
  1471. X  commands[8].func = stats;
  1472. X  commands[9].name = "SHUTDOWN";
  1473. X  commands[9].mask = 0x080;
  1474. X  commands[9].func = Shutdown;
  1475. X  commands[10].name = "RESTART";
  1476. X  commands[10].mask = 0x100;
  1477. X  commands[10].func = restart;
  1478. X  commands[11].name = "LISTS";
  1479. X  commands[11].mask = 0x200;
  1480. X  commands[11].func = lists;
  1481. X  commands[12].name = "INDEX";
  1482. X  commands[12].mask = 0x400;
  1483. X  commands[12].func = Index;
  1484. X  commands[13].name = "GET";
  1485. X  commands[13].mask = 0x800;
  1486. X  commands[13].func = get;
  1487. X  commands[14].name = "RELEASE";
  1488. X  commands[14].mask = 0x1000;
  1489. X  commands[14].func = release;
  1490. X}
  1491. X
  1492. void usage ()
  1493. X{
  1494. X  fprintf (stderr, "Usage: listserv [-1] [-e] [-n] {[-r <request>]}* \
  1495. X{[-d <request>]}* [-D]\n\
  1496. X-1: Execute only once.\n\
  1497. X-e: Echo reports to the screen.\n\
  1498. X-n: Do not notify peer servers.\n\
  1499. X-r: Set restriction on 'request'.\n\
  1500. X-d: Disable 'request'.\n\
  1501. X-D: Turn debug on.\n");
  1502. X  exit (3);
  1503. X}
  1504. X
  1505. void server_config (char *alias)
  1506. X{
  1507. X  setup_string (infof, alias, INFO_FILE);
  1508. X  setup_string (recipf, alias, RECIP_FILE);
  1509. X  setup_string (welcomef, alias, WELCOME_FILE);
  1510. X  setup_string (subscribersf, alias, SUBSCRIBERS);
  1511. X  setup_string (aliasesf, alias, ALIASES);
  1512. X  setup_string (newsf, alias, NEWSF);
  1513. X  setup_string (peersf, alias, PEERS);
  1514. X  setup_string (headersf, alias, HEADERS);
  1515. X  setup_string (ignoredf, alias, IGNORED);
  1516. X}
  1517. X
  1518. X/*
  1519. X  Graceful exit. Remove pid file.
  1520. X*/
  1521. X
  1522. void gexit ()
  1523. X{
  1524. X  unlink (PID_SERVER);
  1525. X  exit (0);
  1526. X}
  1527. END_OF_FILE
  1528. if test 52668 -ne `wc -c <'src/listserv.c'`; then
  1529.     echo shar: \"'src/listserv.c'\" unpacked with wrong size!
  1530. fi
  1531. # end of 'src/listserv.c'
  1532. fi
  1533. echo shar: End of archive 6 \(of 6\).
  1534. cp /dev/null ark6isdone
  1535. MISSING=""
  1536. for I in 1 2 3 4 5 6 ; do
  1537.     if test ! -f ark${I}isdone ; then
  1538.     MISSING="${MISSING} ${I}"
  1539.     fi
  1540. done
  1541. if test "${MISSING}" = "" ; then
  1542.     echo You have unpacked all 6 archives.
  1543.     rm -f ark[1-9]isdone
  1544. else
  1545.     echo You still need to unpack the following archives:
  1546.     echo "        " ${MISSING}
  1547. fi
  1548. ##  End of shell archive.
  1549. exit 0
  1550.