fixed instructions
[tridge/junkcode.git] / movemail.c
1 /* movemail foo bar -- move file foo to file bar,
2    locking file foo the way /bin/mail respects.
3    Copyright (C) 1986, 1992, 1993, 1994 Free Software Foundation, Inc.
4
5 This file is part of GNU Emacs.
6
7 GNU Emacs is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
11
12 GNU Emacs is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs; see the file COPYING.  If not, write to
19 the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
20
21 /* Extracted from XEmacs 19.13, which said: "synched up with: FSF 19.28." */
22
23 /* Important notice: defining MAIL_USE_FLOCK or MAIL_USE_LOCKF *will
24    cause loss of mail* if you do it on a system that does not normally
25    use flock as its way of interlocking access to inbox files.  The
26    setting of MAIL_USE_FLOCK and MAIL_USE_LOCKF *must agree* with the
27    system's own conventions.  It is not a choice that is up to you.
28
29    So, if your system uses lock files rather than flock, then the only way
30    you can get proper operation is to enable movemail to write lockfiles there.
31    This means you must either give that directory access modes
32    that permit everyone to write lockfiles in it, or you must make movemail
33    a setuid or setgid program.  */
34
35 /*
36  * Modified January, 1986 by Michael R. Gretzinger (Project Athena)
37  *
38  * Added POP (Post Office Protocol) service.  When compiled -DPOP
39  * movemail will accept input filename arguments of the form
40  * "po:username".  This will cause movemail to open a connection to
41  * a pop server running on $MAILHOST (environment variable).  Movemail
42  * must be setuid to root in order to work with POP.
43  * 
44  * New module: popmail.c
45  * Modified routines:
46  *      main - added code within #ifdef MAIL_USE_POP; added setuid (getuid ())
47  *              after POP code. 
48  * New routines in movemail.c:
49  *      get_errmsg - return pointer to system error message
50  *
51  */
52
53 #define NO_SHORTNAMES   /* Tell config not to load remap.h */
54 #include <stdlib.h>
55 #include <unistd.h>
56 #include <time.h> /* for time() */
57 #include <stdio.h> /* for printf() */
58 #include <string.h> /* strcpy() */
59
60 #include <sys/types.h>
61 #include <sys/stat.h>
62 #include <sys/file.h>
63 #include <errno.h>
64
65 #ifndef WAITTYPE
66 #define WAITTYPE int
67 #endif /* !WAITTYPE */
68
69 #ifndef WIFEXITED
70 #include <sys/wait.h>
71 #endif /* !WIFEXITED */
72
73 #ifndef WRETCODE
74 #define WRETCODE(w) WEXITSTATUS(w)
75 #endif /* !WRETCODE */
76
77
78 #ifdef MSDOS
79 #undef access
80 #endif /* MSDOS */
81
82 #ifdef USG
83 #include <fcntl.h>
84 #include <unistd.h>
85 #if defined (sun)
86 #include <stdlib.h>
87 #endif /* sun */
88 #ifndef F_OK
89 #define F_OK 0
90 #define X_OK 1
91 #define W_OK 2
92 #define R_OK 4
93 #endif
94 #endif /* USG */
95
96 #ifdef HAVE_UNISTD_H
97 #include <unistd.h>
98 #endif
99
100 #ifdef XENIX
101 #include <sys/locking.h>
102 #endif
103
104 #ifdef MAIL_USE_LOCKF
105 #define MAIL_USE_SYSTEM_LOCK
106 #endif
107
108 #ifdef MAIL_USE_FLOCK
109 #define MAIL_USE_SYSTEM_LOCK
110 #endif
111
112 #ifdef MAIL_USE_MMDF
113 extern int lk_open (), lk_close ();
114 #endif
115
116 /* Cancel substitutions made by config.h for Emacs.  */
117 #undef open
118 #undef read
119 #undef write
120 #undef close
121
122 static char *concat (char *s1, char *s2, char *s3);
123 static void *xmalloc (unsigned int size);
124 #ifndef errno
125 extern int errno;
126 #endif
127
128 static void error (char *s1, char *s2, char *s3);
129 static void fatal (char *s1, char *s2);
130 static void pfatal_with_name (char *name);
131 static void pfatal_and_delete (char *name);
132
133 #if defined(linux) && defined(__GLIBC__)
134
135 #if !defined(HAVE_STRERROR)
136 #define HAVE_STRERROR 1
137 #endif /* HAVE_STRERROR */
138
139 #endif /* LINUX && __GLIBC__ */
140
141 #ifndef HAVE_STRERROR
142 char *strerror (int);
143 #endif
144
145 /* Nonzero means this is name of a lock file to delete on fatal error.  */
146 char *delete_lockname;
147
148 void
149 main (argc, argv)
150      int argc;
151      char **argv;
152 {
153   char *inname, *outname;
154   int indesc, outdesc;
155   int nread;
156   WAITTYPE status;
157
158 #ifndef MAIL_USE_SYSTEM_LOCK
159   struct stat st;
160   long now;
161   int tem;
162   char *lockname, *p;
163   char *tempname;
164   int desc;
165 #endif /* not MAIL_USE_SYSTEM_LOCK */
166
167   delete_lockname = 0;
168
169   if (argc < 3)
170     fatal ("two arguments required", "");
171
172   inname = argv[1];
173   outname = argv[2];
174
175 #ifdef MAIL_USE_MMDF
176   mmdf_init (argv[0]);
177 #endif
178
179   /* Check access to output file.  */
180   if (access (outname, F_OK) == 0 && access (outname, W_OK) != 0)
181     pfatal_with_name (outname);
182
183   /* Also check that outname's directory is writeable to the real uid.  */
184   {
185     char *buf = (char *) xmalloc (strlen (outname) + 1);
186     char *p;
187     strcpy (buf, outname);
188     p = buf + strlen (buf);
189     while (p > buf && p[-1] != '/')
190       *--p = 0;
191     if (p == buf)
192       *p++ = '.';
193     if (access (buf, W_OK) != 0)
194       pfatal_with_name (buf);
195     free (buf);
196   }
197
198 #ifdef MAIL_USE_POP
199   if (!memcmp (inname, "po:", 3))
200     {
201       int status; char *user;
202
203       for (user = &inname[strlen (inname) - 1]; user >= inname; user--)
204         if (*user == ':')
205           break;
206
207       status = popmail (user, outname);
208       exit (status);
209     }
210
211   setuid (getuid ());
212 #endif /* MAIL_USE_POP */
213
214   /* Check access to input file.  */
215   if (access (inname, R_OK | W_OK) != 0)
216     pfatal_with_name (inname);
217
218 #ifndef MAIL_USE_MMDF
219 #ifndef MAIL_USE_SYSTEM_LOCK
220   /* Use a lock file named /usr/spool/mail/$USER.lock:
221      If it exists, the mail file is locked.  */
222   /* Note: this locking mechanism is *required* by the mailer
223      (on systems which use it) to prevent loss of mail.
224
225      On systems that use a lock file, extracting the mail without locking
226      WILL occasionally cause loss of mail due to timing errors!
227
228      So, if creation of the lock file fails
229      due to access permission on /usr/spool/mail,
230      you simply MUST change the permission
231      and/or make movemail a setgid program
232      so it can create lock files properly.
233
234      You might also wish to verify that your system is one
235      which uses lock files for this purpose.  Some systems use other methods.
236
237      If your system uses the `flock' system call for mail locking,
238      define MAIL_USE_SYSTEM_LOCK in config.h or the s-*.h file
239      and recompile movemail.  If the s- file for your system
240      should define MAIL_USE_SYSTEM_LOCK but does not, send a bug report
241      to bug-gnu-emacs@prep.ai.mit.edu so we can fix it.  */
242
243   lockname = concat (inname, ".lock", "");
244   tempname = (char *) xmalloc (strlen (inname) + strlen ("EXXXXXX") + 1);
245   strcpy (tempname, inname);
246   p = tempname + strlen (tempname);
247   while (p != tempname && p[-1] != '/')
248     p--;
249   *p = 0;
250   strcpy (p, "EXXXXXX");
251   mktemp (tempname);
252   unlink (tempname);
253
254   while (1)
255     {
256       /* Create the lock file, but not under the lock file name.  */
257       /* Give up if cannot do that.  */
258       desc = open (tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
259       if (desc < 0)
260         pfatal_with_name ("lock file--see source file lib-src/movemail.c");
261       close (desc);
262
263       tem = link (tempname, lockname);
264       unlink (tempname);
265       if (tem >= 0)
266         break;
267       sleep (1);
268
269       /* If lock file is a minute old, unlock it.  */
270       if (stat (lockname, &st) >= 0)
271         {
272           now = time (0);
273           if (st.st_ctime < now - 60)
274             unlink (lockname);
275         }
276     }
277
278   delete_lockname = lockname;
279 #endif /* not MAIL_USE_SYSTEM_LOCK */
280 #endif /* not MAIL_USE_MMDF */
281
282   if (fork () == 0)
283     {
284       setuid (getuid ());
285
286 #ifndef MAIL_USE_MMDF
287 #ifdef MAIL_USE_SYSTEM_LOCK
288       indesc = open (inname, O_RDWR);
289 #else  /* if not MAIL_USE_SYSTEM_LOCK */
290       indesc = open (inname, O_RDONLY);
291 #endif /* not MAIL_USE_SYSTEM_LOCK */
292 #else  /* MAIL_USE_MMDF */
293       indesc = lk_open (inname, O_RDONLY, 0, 0, 10);
294 #endif /* MAIL_USE_MMDF */
295
296       if (indesc < 0)
297         pfatal_with_name (inname);
298
299 #if defined (BSD) || defined (XENIX)
300       /* In case movemail is setuid to root, make sure the user can
301          read the output file.  */
302       /* This is desirable for all systems
303          but I don't want to assume all have the umask system call */
304       umask (umask (0) & 0333);
305 #endif /* BSD or Xenix */
306       outdesc = open (outname, O_WRONLY | O_CREAT | O_EXCL, 0666);
307       if (outdesc < 0)
308         pfatal_with_name (outname);
309 #ifdef MAIL_USE_SYSTEM_LOCK
310 #ifdef MAIL_USE_LOCKF
311       if (lockf (indesc, F_LOCK, 0) < 0) pfatal_with_name (inname);
312 #else /* not MAIL_USE_LOCKF */
313 #ifdef XENIX
314       if (locking (indesc, LK_RLCK, 0L) < 0) pfatal_with_name (inname);
315 #else
316       if (flock (indesc, LOCK_EX) < 0) pfatal_with_name (inname);
317 #endif
318 #endif /* not MAIL_USE_LOCKF */
319 #endif /* MAIL_USE_SYSTEM_LOCK */
320
321       {
322         char buf[1024];
323
324         while (1)
325           {
326             nread = read (indesc, buf, sizeof buf);
327             if (nread != write (outdesc, buf, nread))
328               {
329                 int saved_errno = errno;
330                 unlink (outname);
331                 errno = saved_errno;
332                 pfatal_with_name (outname);
333               }
334             if (nread < sizeof buf)
335               break;
336           }
337       }
338
339 #ifdef BSD
340       if (fsync (outdesc) < 0)
341         pfatal_and_delete (outname);
342 #endif
343
344       /* Check to make sure no errors before we zap the inbox.  */
345       if (close (outdesc) != 0)
346         pfatal_and_delete (outname);
347
348 #ifdef MAIL_USE_SYSTEM_LOCK
349 #if defined (STRIDE) || defined (XENIX)
350       /* Stride, xenix have file locking, but no ftruncate.  This mess will do. */
351       close (open (inname, O_CREAT | O_TRUNC | O_RDWR, 0666));
352 #else
353       ftruncate (indesc, 0L);
354 #endif /* STRIDE or XENIX */
355 #endif /* MAIL_USE_SYSTEM_LOCK */
356
357 #ifdef MAIL_USE_MMDF
358       lk_close (indesc, 0, 0, 0);
359 #else
360       close (indesc);
361 #endif
362
363 #ifndef MAIL_USE_SYSTEM_LOCK
364       /* Delete the input file; if we can't, at least get rid of its
365          contents.  */
366 #ifdef MAIL_UNLINK_SPOOL
367       /* This is generally bad to do, because it destroys the permissions
368          that were set on the file.  Better to just empty the file.  */
369       if (unlink (inname) < 0 && errno != ENOENT)
370 #endif /* MAIL_UNLINK_SPOOL */
371         creat (inname, 0600);
372 #endif /* not MAIL_USE_SYSTEM_LOCK */
373
374       exit (0);
375     }
376
377   wait (&status);
378   if (!WIFEXITED (status))
379     exit (1);
380   else if (WRETCODE (status) != 0)
381     exit (WRETCODE (status));
382
383 #if !defined (MAIL_USE_MMDF) && !defined (MAIL_USE_SYSTEM_LOCK)
384   unlink (lockname);
385 #endif /* not MAIL_USE_MMDF and not MAIL_USE_SYSTEM_LOCK */
386   exit (0);
387 }
388 \f
389 /* Print error message and exit.  */
390
391 static void
392 fatal (s1, s2)
393      char *s1, *s2;
394 {
395   if (delete_lockname)
396     unlink (delete_lockname);
397   error (s1, s2, "");
398   exit (1);
399 }
400
401 /* Print error message.  `s1' is printf control string, `s2' is arg for it. */
402
403 static void
404 error (s1, s2, s3)
405      char *s1, *s2, *s3;
406 {
407   printf ("movemail: ");
408   printf (s1, s2, s3);
409   printf ("\n");
410 }
411
412 static void
413 pfatal_with_name (name)
414      char *name;
415 {
416   char *s;
417
418   s = concat ("", strerror (errno), " for %s");
419   fatal (s, name);
420 }
421
422 static void
423 pfatal_and_delete (name)
424      char *name;
425 {
426   char *s;
427
428   s = concat ("", strerror (errno), " for %s");
429   unlink (name);
430   fatal (s, name);
431 }
432
433 /* Return a newly-allocated string whose contents concatenate those of s1, s2, s3.  */
434
435 static char *
436 concat (s1, s2, s3)
437      char *s1, *s2, *s3;
438 {
439   int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
440   char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
441
442   strcpy (result, s1);
443   strcpy (result + len1, s2);
444   strcpy (result + len1 + len2, s3);
445   *(result + len1 + len2 + len3) = 0;
446
447   return result;
448 }
449
450 /* Like malloc but get fatal error if memory is exhausted.  */
451
452 static void *
453 xmalloc (size)
454      unsigned int size;
455 {
456   void *result = (void *) malloc (size);
457   if (!result)
458     fatal ("virtual memory exhausted", (char *) 0);
459   return result;
460 }
461 \f
462 /* This is the guts of the interface to the Post Office Protocol.  */
463
464 #ifdef MAIL_USE_POP
465
466 #include <sys/socket.h>
467 #include <netinet/in.h>
468 #include <netdb.h>
469 #include <stdio.h>
470 #include <pwd.h>
471
472 #ifdef USG
473 #include <fcntl.h>
474 /* Cancel substitutions made by config.h for Emacs.  */
475 #undef open
476 #undef read
477 #undef write
478 #undef close
479 #endif /* USG */
480
481 #define NOTOK (-1)
482 #define OK 0
483 #define DONE 1
484
485 char *progname;
486 FILE *sfi;
487 FILE *sfo;
488 char Errmsg[80];
489
490 static int debug = 0;
491
492 char *get_errmsg ();
493 char *getenv ();
494 int mbx_write ();
495
496 int
497 popmail (user, outfile)
498      char *user;
499      char *outfile;
500 {
501   char *host;
502   int nmsgs, nbytes;
503   char response[128];
504   register int i;
505   int mbfi;
506   FILE *mbf;
507   struct passwd *pw = (struct passwd *) getpwuid (getuid ());
508   if (pw == NULL)
509     fatal ("cannot determine user name");
510
511   host = getenv ("MAILHOST");
512   if (host == NULL) 
513     {
514       fatal ("no MAILHOST defined");
515     }
516
517   if (pop_init (host) == NOTOK) 
518     {
519       fatal (Errmsg);
520     }
521
522   if (getline (response, sizeof response, sfi) != OK) 
523     {
524       fatal (response);
525     }
526
527   if (pop_command ("USER %s", user) == NOTOK 
528       || pop_command ("RPOP %s", pw->pw_name) == NOTOK) 
529     {
530       pop_command ("QUIT");
531       fatal (Errmsg);
532     }
533
534   if (pop_stat (&nmsgs, &nbytes) == NOTOK) 
535     {
536       pop_command ("QUIT");
537       fatal (Errmsg);
538     }
539
540   if (!nmsgs)
541   {
542     pop_command ("QUIT");
543     return 0;
544   }
545
546   mbfi = open (outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
547   if (mbfi < 0)
548   {
549     pop_command ("QUIT");
550     pfatal_and_delete (outfile);
551   }
552   fchown (mbfi, getuid (), -1);
553
554   if ((mbf = fdopen (mbfi, "w")) == NULL)
555     {
556       pop_command ("QUIT");
557       pfatal_and_delete (outfile);
558     }
559
560   for (i = 1; i <= nmsgs; i++) 
561     {
562       mbx_delimit_begin (mbf);
563       if (pop_retr (i, mbx_write, mbf) != OK) 
564         {
565           pop_command ("QUIT");
566           close (mbfi);
567           unlink (outfile);
568           fatal (Errmsg);
569         }
570       mbx_delimit_end (mbf);
571       fflush (mbf);
572     }
573
574   if (fsync (mbfi) < 0)
575     {
576       pop_command ("QUIT");
577       pfatal_and_delete (outfile);
578     }
579
580   if (close (mbfi) == -1)
581     {
582       pop_command ("QUIT");
583       pfatal_and_delete (outfile);
584     }
585
586   for (i = 1; i <= nmsgs; i++) 
587     {
588       if (pop_command ("DELE %d", i) == NOTOK) 
589         {
590           /* Better to ignore this failure.  */
591         }
592     }
593
594   pop_command ("QUIT");
595   return (0);
596 }
597
598 int
599 pop_init (host)
600      char *host;
601 {
602   register struct hostent *hp;
603   register struct servent *sp;
604   int lport = IPPORT_RESERVED - 1;
605   struct sockaddr_in sin;
606   register int s;
607
608   hp = gethostbyname (host);
609   if (hp == NULL) 
610     {
611       sprintf (Errmsg, "MAILHOST unknown: %s", host);
612          /* Reviewed 4.51 safe use of sprintf */
613       return NOTOK;
614     }
615
616   sp = getservbyname ("pop", "tcp");
617   if (sp == 0) 
618     {
619       strcpy (Errmsg, "tcp/pop: unknown service");
620       return NOTOK;
621     }
622
623   sin.sin_family = hp->h_addrtype;
624   memcpy ((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
625   sin.sin_port = sp->s_port;
626   s = rresvport (&lport);
627   if (s < 0) 
628     {
629       sprintf (Errmsg, "error creating socket: %s", get_errmsg ());
630          /* Reviewed 4.51 safe use of sprintf */
631       return NOTOK;
632     }
633
634   if (connect (s, (char *)&sin, sizeof sin) < 0) 
635     {
636       sprintf (Errmsg, "error during connect: %s", get_errmsg ());
637          /* Reviewed 4.51 safe use of sprintf */
638       close (s);
639       return NOTOK;
640     }
641
642   sfi = fdopen (s, "r");
643   sfo = fdopen (s, "w");
644   if (sfi == NULL || sfo == NULL) 
645     {
646       sprintf (Errmsg, "error in fdopen: %s", get_errmsg ());
647          /* Reviewed 4.51 safe use of sprintf */
648       close (s);
649       return NOTOK;
650     }
651
652   return OK;
653 }
654
655 int
656 pop_command (fmt, a, b, c, d)
657      char *fmt;
658 {
659   char buf[128];
660   char errmsg[64];
661
662   sprintf (buf, fmt, a, b, c, d);   /* Reviewed 4.51 safe use of sprintf */
663
664   if (debug) fprintf (stderr, "---> %s\n", buf);
665   if (putline (buf, Errmsg, sfo) == NOTOK) return NOTOK;
666
667   if (getline (buf, sizeof buf, sfi) != OK) 
668     {
669       strcpy (Errmsg, buf);
670       return NOTOK;
671     }
672
673   if (debug) fprintf (stderr, "<--- %s\n", buf);
674   if (*buf != '+') 
675     {
676       strcpy (Errmsg, buf);
677       return NOTOK;
678     } 
679   else 
680     {
681       return OK;
682     }
683 }
684
685     
686 pop_stat (nmsgs, nbytes)
687      int *nmsgs, *nbytes;
688 {
689   char buf[128];
690
691   if (debug) fprintf (stderr, "---> STAT\n");
692   if (putline ("STAT", Errmsg, sfo) == NOTOK)
693     return NOTOK;
694
695   if (getline (buf, sizeof buf, sfi) != OK) 
696     {
697       strcpy (Errmsg, buf);
698       return NOTOK;
699     }
700
701   if (debug) fprintf (stderr, "<--- %s\n", buf);
702   if (*buf != '+') 
703     {
704       strcpy (Errmsg, buf);
705       return NOTOK;
706     } 
707   else 
708     {
709       sscanf (buf, "+OK %d %d", nmsgs, nbytes);
710       return OK;
711     }
712 }
713
714 pop_retr (msgno, action, arg)
715      int (*action)();
716 {
717   char buf[128];
718
719   sprintf (buf, "RETR %d", msgno);   /* Reviewed 4.51 safe use of sprintf */
720   if (debug) fprintf (stderr, "%s\n", buf);
721   if (putline (buf, Errmsg, sfo) == NOTOK) return NOTOK;
722
723   if (getline (buf, sizeof buf, sfi) != OK) 
724     {
725       strcpy (Errmsg, buf);
726       return NOTOK;
727     }
728
729   while (1) 
730     {
731       switch (multiline (buf, sizeof buf, sfi)) 
732         {
733         case OK:
734           (*action)(buf, arg);
735           break;
736         case DONE:
737           return OK;
738         case NOTOK:
739           strcpy (Errmsg, buf);
740           return NOTOK;
741         }
742     }
743 }
744
745 getline (buf, n, f)
746      char *buf;
747      register int n;
748      FILE *f;
749 {
750   register char *p;
751   int c;
752
753   p = buf;
754   while (--n > 0 && (c = fgetc (f)) != EOF)
755     if ((*p++ = c) == '\n') break;
756
757   if (ferror (f)) 
758   {
759     strcpy (buf, "error on connection");
760     return NOTOK;
761   }
762
763   if (c == EOF && p == buf) 
764   {
765     strcpy (buf, "connection closed by foreign host");
766     return DONE;
767   }
768
769   *p = NULL;
770   if (*--p == '\n') *p = NULL;
771   if (*--p == '\r') *p = NULL;
772   return OK;
773 }
774
775 multiline (buf, n, f)
776      char *buf;
777      register int n;
778      FILE *f;
779 {
780   if (getline (buf, n, f) != OK) return NOTOK;
781   if (*buf == '.') 
782     {
783       if (*(buf+1) == NULL) 
784         return DONE;
785       else 
786         strcpy (buf, buf+1);
787     }
788   return OK;
789 }
790
791 char *
792 get_errmsg ()
793 {
794   return strerror (errno);
795 }
796
797 putline (buf, err, f)
798      char *buf;
799      char *err;
800      FILE *f;
801 {
802   fprintf (f, "%s\r\n", buf);
803   fflush (f);
804   if (ferror (f)) 
805     {
806       strcpy (err, "lost connection");
807       return NOTOK;
808     }
809   return OK;
810 }
811
812 mbx_write (line, mbf)
813      char *line;
814      FILE *mbf;
815 {
816   fputs (line, mbf);
817   fputc (0x0a, mbf);
818 }
819
820 mbx_delimit_begin (mbf)
821      FILE *mbf;
822 {
823   fputs ("\f\n0, unseen,,\n", mbf);
824 }
825
826 mbx_delimit_end (mbf)
827      FILE *mbf;
828 {
829   putc ('\037', mbf);
830 }
831
832 #endif /* MAIL_USE_POP */
833 \f
834 #ifndef HAVE_STRERROR
835 char *
836 strerror (errnum)
837      int errnum;
838 {
839   extern char *sys_errlist[];
840   extern int sys_nerr;
841
842   if (errnum >= 0 && errnum < sys_nerr)
843     return sys_errlist[errnum];
844   return (char *) "Unknown error";
845 }
846
847 #endif /* ! HAVE_STRERROR */