Added John Brezak's port of mailquery to krb5
authorTheodore Tso <tytso@mit.edu>
Tue, 9 Aug 1994 02:13:15 +0000 (02:13 +0000)
committerTheodore Tso <tytso@mit.edu>
Tue, 9 Aug 1994 02:13:15 +0000 (02:13 +0000)
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@4073 dc483132-0cff-0310-8789-dd5450dbe970

src/appl/mailquery/Makefile.in [new file with mode: 0644]
src/appl/mailquery/configure.in [new file with mode: 0644]
src/appl/mailquery/mailquery.M [new file with mode: 0644]
src/appl/mailquery/mailquery.c [new file with mode: 0644]
src/appl/mailquery/pop.h [new file with mode: 0644]
src/appl/mailquery/poplib.c [new file with mode: 0644]

diff --git a/src/appl/mailquery/Makefile.in b/src/appl/mailquery/Makefile.in
new file mode 100644 (file)
index 0000000..9261a99
--- /dev/null
@@ -0,0 +1,23 @@
+CFLAGS = $(CCOPTS) -DKPOP -DKRB5 $(DEFS) $(LOCALINCLUDE)
+
+all::
+
+KLIB = $(TOPLIBD)/libkrb5.a $(TOPLIBD)/libcrypto.a $(ISODELIB) $(COMERRLIB) $(DBMLIB)
+
+HESIODLIB =
+
+mailquery: mailquery.o poplib.o
+       $(CC) $(CFLAGS) -o mailquery mailquery.o poplib.o $(KLIB) $(HESIODLIB)
+
+mailquery.o:   $(srcdir)/mailquery.c
+poplib.o:      $(srcdir)/poplib.c
+
+all:: mailquery
+
+clean::
+       $(RM) mailquery.o poplib.o mailquery
+
+install::
+       cp mailquery ${DESTDIR}$(CLIENT_BINDIR)/mailquery
+       cp mailquery.M ${DESTDIR}$(CLIENT_MANDIR)/mailquery.1
+
diff --git a/src/appl/mailquery/configure.in b/src/appl/mailquery/configure.in
new file mode 100644 (file)
index 0000000..3673532
--- /dev/null
@@ -0,0 +1,7 @@
+AC_INIT(mailquery.c)
+WITH_CCOPTS
+AC_SET_BUILDTOP
+CONFIG_RULES
+AC_FUNC_CHECK(strerror,AC_DEFINE(HAS_STRERROR))
+KRB_INCLUDE
+AC_OUTPUT(Makefile,[EXTRA_RULES])
diff --git a/src/appl/mailquery/mailquery.M b/src/appl/mailquery/mailquery.M
new file mode 100644 (file)
index 0000000..113146f
--- /dev/null
@@ -0,0 +1,41 @@
+.\"
+.\" (c) Copyright 1994 HEWLETT-PACKARD COMPANY
+.\" 
+.\" To anyone who acknowledges that this file is provided 
+.\" "AS IS" without any express or implied warranty:
+.\" permission to use, copy, modify, and distribute this 
+.\" file for any purpose is hereby granted without fee, 
+.\" provided that the above copyright notice and this 
+.\" notice appears in all copies, and that the name of 
+.\" Hewlett-Packard Company not be used in advertising or 
+.\" publicity pertaining to distribution of the software 
+.\" without specific, written prior permission.  Hewlett-
+.\" Packard Company makes no representations about the 
+.\" suitability of this software for any purpose.
+.\"
+.\" $Id
+.\"
+.TH mailquery 1
+.SH NAME
+mailquery \- queries a pop server about how much mail is available
+.SH SYNTAX
+.B mailquery
+[-\fId\fR\|]
+[-\fIv\fR\|]
+[\fI user\fR[@\fIhost\fR\|]]
+.SH DESCRIPTION
+The
+.PN mailquery 
+command queries a POP server for information about how much mail a user
+has.  The program will exit with status = 0 if there is mail, and with
+status = 1 if there is no mail.  The -v flag can be used to get a more 
+verbose report.  The -d flag can be used to turn on debugging output
+in the pop library code.  The pop server can be specified either by
+setting the environment variable MAILHOST, or on the command line.
+If no user is specified, user is set to the person who ran the program.
+.SH OPTIONS
+
+.SH BUGS/LIMITATIONS
+
+.SH SEE ALSO
+popper(8)
diff --git a/src/appl/mailquery/mailquery.c b/src/appl/mailquery/mailquery.c
new file mode 100644 (file)
index 0000000..6f4d0b2
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * (c) Copyright 1994 HEWLETT-PACKARD COMPANY
+ * 
+ * To anyone who acknowledges that this file is provided 
+ * "AS IS" without any express or implied warranty:
+ * permission to use, copy, modify, and distribute this 
+ * file for any purpose is hereby granted without fee, 
+ * provided that the above copyright notice and this 
+ * notice appears in all copies, and that the name of 
+ * Hewlett-Packard Company not be used in advertising or 
+ * publicity pertaining to distribution of the software 
+ * without specific, written prior permission.  Hewlett-
+ * Packard Company makes no representations about the 
+ * suitability of this software for any purpose.
+ */
+/*
+ * Mailquery - contact the POP mail host an see if a user has
+ *             mail. By default the result if reflected in the
+ *             exit status.
+ *             
+ * Usage: mailquery [-dv] [-e <cmd>]
+ *      -d - debug
+ *      -v - print result
+ *      -e - exec this command if there is mail.
+ */
+#include <pwd.h>
+#include <fcntl.h>
+#include <sys/file.h>  
+#include <stdio.h>
+#ifdef HESIOD
+#include <hesiod.h>
+#endif
+#include "pop.h"
+
+
+extern int pop_debug;
+int verbose = 0;
+char *exec_cmd;
+
+main(argc, argv)
+     int argc;
+     char *argv[];
+{
+    extern char *getenv();
+    int nbytes;
+    char *mhost = NULL, *mhp;
+    char *user = 0;
+    struct passwd * pwd;
+    char response[128];
+    char c;
+    extern int optind;
+    extern char *optarg;
+#ifdef HESIOD
+    struct hes_postoffice *p;
+#endif /* HESIOD */
+    char *index();
+
+    while ((c = getopt(argc, argv, "dve:")) != EOF) {
+       switch (c) {
+         case 'd':
+           pop_debug = 1;
+           break;
+
+         case 'e':
+           exec_cmd = optarg;
+           break;
+           
+         case 'v':
+           verbose = 1;
+           break;
+           
+         case '?':
+           usage();
+           exit(1);
+       }
+    }
+    
+    argc -= optind;
+    argv += optind;
+
+    if (argc > 0) {
+       user = argv[0];
+       if ((mhost = index(argv[0], '@')) != NULL) {
+           *mhost = '\0';
+           mhost++;
+       }
+#ifndef HESIOD
+        else {
+           mhost = DEFMAILHOST;
+       }
+#endif
+    }
+    
+    if (user == (char *) 0 || *user == '\0') {
+       if ((pwd = getpwuid(getuid())) == NULL) {
+           perror("getpwuid");
+           exit(1);
+       }
+       user = pwd->pw_name;
+    }
+
+    if ((mhost == NULL) &&
+        (mhp = getenv("MAILHOST")))
+            mhost = mhp;
+
+#ifdef HESIOD
+    if (mhost == NULL) {
+            p = hes_getmailhost(user);
+            if (p != NULL && strcmp(p->po_type, "POP") == 0)
+                    mhost = p->po_host;
+            else {
+                    fprintf(stderr,"no POP server listed in Hesiod for %s\n", user);
+                    exit(1);
+            } 
+    }
+#endif /* HESIOD */
+
+    if (mhost == NULL) {
+       mhost = DEFMAILHOST;
+    }
+
+    nbytes = mailquery(mhost, user);
+    
+    if ((nbytes > 0) && (exec_cmd != 0)) {
+       if (pop_debug)
+         fprintf(stderr, "about to execute %s\n", exec_cmd);
+       system(exec_cmd);
+    }
+    
+    exit(nbytes == 0);
+
+}
+
+mailquery(mhost, user)
+     char *mhost;
+     char *user;
+{
+    int nbytes, nmsgs;
+    
+    if (pop_init(mhost, 0) == NOTOK) {
+       error(Errmsg);
+       exit(1);
+    }
+
+#ifdef KPOP
+    if (pop_command("USER %s", user) == NOTOK || 
+        pop_command("PASS %s", user) == NOTOK) {
+#else /* !KPOP */
+    if (pop_command("USER %s", user) == NOTOK || 
+        pop_command("RPOP %s", user) == NOTOK) {
+#endif /* KPOP */
+            error(Errmsg);
+            (void) pop_command("QUIT");
+            exit (1);
+    } 
+
+    if (pop_stat(&nmsgs, &nbytes) == NOTOK) {
+            error(Errmsg);
+            (void) pop_command("QUIT");
+            exit (1);
+    }
+
+    if (verbose)
+      printf("%d messages (%d bytes) on host %s\n", nmsgs, nbytes, mhost);
+
+    return nbytes;
+}
+    
+usage()
+{
+    fprintf(stderr, "usage: mailquery [-d] [-v] [-e cmd] [user[@host]]\n");
+} 
+
+
diff --git a/src/appl/mailquery/pop.h b/src/appl/mailquery/pop.h
new file mode 100644 (file)
index 0000000..e7ffc4f
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * (c) Copyright 1994 HEWLETT-PACKARD COMPANY
+ * 
+ * To anyone who acknowledges that this file is provided 
+ * "AS IS" without any express or implied warranty:
+ * permission to use, copy, modify, and distribute this 
+ * file for any purpose is hereby granted without fee, 
+ * provided that the above copyright notice and this 
+ * notice appears in all copies, and that the name of 
+ * Hewlett-Packard Company not be used in advertising or 
+ * publicity pertaining to distribution of the software 
+ * without specific, written prior permission.  Hewlett-
+ * Packard Company makes no representations about the 
+ * suitability of this software for any purpose.
+ *
+ * $Id$
+ * 
+ */
+
+/* defines for pop library */
+
+#define NOTOK (-1)
+#define OK 0
+#define DONE 1
+
+#define DEFMAILHOST "mailhost"
+
+int pop_init(), pop_getline();
+char *get_errmsg();
+int pop_command();
+int pop_stat();
+int pop_retr();
+char *concat();
+
+extern char Errmsg[];
diff --git a/src/appl/mailquery/poplib.c b/src/appl/mailquery/poplib.c
new file mode 100644 (file)
index 0000000..2b4c863
--- /dev/null
@@ -0,0 +1,493 @@
+/*
+ * (c) Copyright 1994 HEWLETT-PACKARD COMPANY
+ * 
+ * To anyone who acknowledges that this file is provided 
+ * "AS IS" without any express or implied warranty:
+ * permission to use, copy, modify, and distribute this 
+ * file for any purpose is hereby granted without fee, 
+ * provided that the above copyright notice and this 
+ * notice appears in all copies, and that the name of 
+ * Hewlett-Packard Company not be used in advertising or 
+ * publicity pertaining to distribution of the software 
+ * without specific, written prior permission.  Hewlett-
+ * Packard Company makes no representations about the 
+ * suitability of this software for any purpose.
+ */
+#if !defined(lint) && !defined(_NOIDENT)
+static char rcsid[] = "@(#)$Header$";
+#endif
+/*
+ * Poplib - library routines for speaking POP
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <errno.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <stdio.h>
+#if defined(KRB4) && defined(KRB5)
+# error You cannot define both KRB4 and KRB5
+#endif
+#ifndef KPOP_SERVICE
+#define KPOP_SERVICE "kpop"
+#endif
+#ifdef KPOP
+#ifdef KRB4
+#include <krb.h>
+#endif
+#ifdef KRB5
+#include <krb5/krb5.h>
+#include <krb5/ext-proto.h>
+#include <krb5/los-proto.h>
+#include <com_err.h>
+#include <ctype.h>
+#endif
+#endif
+
+#include "pop.h"
+
+void *xmalloc();
+
+char Errmsg[80];               /* to return error messages */
+int pop_debug;
+
+static FILE *sfi = 0;
+static FILE *sfo = 0;
+
+pop_init(host, reserved)
+char *host;
+int reserved;
+{
+    register struct hostent *hp;
+    register struct servent *sp;
+    int lport = IPPORT_RESERVED - 1;
+    struct sockaddr_in sin;
+    int s;
+    char *get_errmsg();
+    char response[1024];
+    char *routine;
+#ifdef KPOP
+#ifdef KRB4
+    CREDENTIALS cred;
+    KTEXT ticket = (KTEXT)NULL;
+    int rem;
+#endif
+#ifdef KRB5
+    krb5_error_code retval;
+    krb5_ccache ccdef;
+    krb5_principal client = NULL, server = NULL;
+    krb5_error *err_ret = NULL;
+    register char *cp;
+#endif
+#endif
+
+    if (sfi && sfo) {
+       return;
+    }
+
+    hp = gethostbyname(host);
+    if (hp == NULL) {
+       sprintf(Errmsg, "MAILHOST unknown: %s", host);
+       return(NOTOK);
+    }
+
+#ifdef KPOP
+    sp = getservbyname(KPOP_SERVICE, "tcp");
+    if (sp == 0) {
+       (void) strcpy(Errmsg, "tcp/kpop: unknown service");
+       return(NOTOK);
+    }
+#else /* !KPOP */
+    sp = getservbyname("pop", "tcp");
+    if (sp == 0) {
+       (void) strcpy(Errmsg, "tcp/pop: unknown service");
+       return(NOTOK);
+    }
+#endif /* KPOP */
+    if (sp == 0) {
+       strcpy(Errmsg, "tcp/pop: unknown service");
+       return(NOTOK);
+    }
+
+    sin.sin_family = hp->h_addrtype;
+    bcopy(hp->h_addr, (char *)&sin.sin_addr, hp->h_length);
+    sin.sin_port = sp->s_port;
+#ifdef KPOP
+    s = socket(AF_INET, SOCK_STREAM, 0);
+#else /* !KPOP */
+    if (reserved) 
+      s = rresvport(&lport);
+    else
+      s = socket(AF_INET, SOCK_STREAM, 0);
+#endif /* KPOP */
+    
+    if (s < 0) {
+       sprintf(Errmsg, "error creating socket: %s", get_errmsg());
+       return(NOTOK);
+    }
+
+    if (connect(s, (struct sockaddr *)&sin, sizeof sin) < 0) {
+       sprintf(Errmsg, "error during connect: %s", get_errmsg());
+       close(s);
+       return(NOTOK);
+    }
+
+#ifdef KPOP
+#ifdef KRB4
+    /* Get tgt creds from ticket file. This is used to calculate the
+     * lifetime for the pop ticket so that it expires with the
+     * tgt */
+    rem = krb_get_cred("krbtgt", krb_realmofhost(hp->h_name), krb_realmofhost(hp->h_name), &cred);
+    if (rem == KSUCCESS) {
+            long lifetime;
+            lifetime = ((cred.issue_date + ((unsigned char)cred.lifetime * 5 * 60)) - time(0)) / (5 * 60);
+            if (lifetime > 0)
+                    krb_set_lifetime(lifetime);
+    }            
+    ticket = (KTEXT)malloc( sizeof(KTEXT_ST) );
+    rem = krb_sendauth(0L, s, ticket, "pop", hp->h_name, (char *)0,
+                      0, (MSG_DAT *) 0, (CREDENTIALS *) 0,
+                      (bit_64 *) 0, (struct sockaddr_in *)0,
+                      (struct sockaddr_in *)0,"ZMAIL0.0");
+    if (rem != KSUCCESS) {
+       (void) sprintf(Errmsg, "kerberos error: %s",krb_err_txt[rem]);
+       (void) close(s);
+       return(NOTOK);
+    }
+#endif /* KRB4 */
+#ifdef KRB5
+    krb5_init_ets();
+
+    routine = "krb5_cc_default";
+    if (retval = krb5_cc_default(&ccdef)) {
+    krb5error:
+       sprintf(Errmsg, "%s: krb5 error: %s", routine, error_message(retval));
+       close(s);
+       return(NOTOK);
+    }
+    routine = "krb5_cc_get_principal";
+    if (retval = krb5_cc_get_principal(ccdef, &client)) {
+       goto krb5error;
+    }
+
+#if 0
+    /* lower-case to get name for "instance" part of service name */
+    for (cp = hp->h_name; *cp; cp++)
+       if (isupper(*cp))
+           *cp = tolower(*cp);
+#endif
+
+    routine = "krb5_sname_to_principal";
+    if (retval = krb5_sname_to_principal(hp->h_name, "pop",
+                                        KRB5_NT_UNKNOWN,
+                                        &server)) {
+       goto krb5error;
+    }
+
+    retval = krb5_sendauth((krb5_pointer) &s, "KPOPV1.0", client, server,
+                          AP_OPTS_MUTUAL_REQUIRED,
+                          0,           /* no checksum */
+                          0,           /* no creds, use ccache instead */
+                          ccdef,
+                          0,           /* don't need seq # */
+                          0,           /* don't need a subsession key */
+                          &err_ret,
+                          0);          /* don't need reply */
+    krb5_free_principal(server);
+    if (retval) {
+       if (err_ret && err_ret->text.length) {
+           sprintf(Errmsg, "krb5 error: %s [server says '%*s'] ",
+                   error_message(retval),
+                   err_ret->text.length,
+                   err_ret->text.data);
+           krb5_free_error(err_ret);
+       } else
+           sprintf(Errmsg, "krb5_sendauth: krb5 error: %s", error_message(retval));
+       close(s);
+       return(NOTOK);
+    }
+#endif /* KRB5 */
+#endif /* KPOP */
+
+    sfi = fdopen(s, "r");
+    sfo = fdopen(s, "w");
+    if (sfi == NULL || sfo == NULL) {
+       sprintf(Errmsg, "error in fdopen: %s", get_errmsg());
+       close(s);
+       return(NOTOK);
+    }
+
+    if (getline(response, sizeof response, sfi) != OK) {
+       error(response);
+       return(NOTOK);
+    }
+    if (pop_debug)
+      fprintf(stderr, "<--- %s\n", response);
+
+    return(OK);
+}
+
+pop_command(fmt, a, b, c, d)
+char *fmt;
+{
+    char buf[1024];
+    char errmsg[64];
+
+    sprintf(buf, fmt, a, b, c, d);
+
+    if (pop_debug) fprintf(stderr, "---> %s\n", buf);
+    if (putline(buf, Errmsg, sfo) == NOTOK) return(NOTOK);
+
+    if (getline(buf, sizeof buf, sfi) != OK) {
+       strcpy(Errmsg, buf);
+       return(NOTOK);
+    }
+
+    if (pop_debug) fprintf(stderr, "<--- %s\n", buf);
+    if (*buf != '+') {
+       strcpy(Errmsg, buf);
+       return(NOTOK);
+    } else {
+       return(OK);
+    }
+}
+
+pop_query(nbytes, user)
+     int *nbytes;
+     char *user;
+{
+    char buf[1024];
+
+    if (strlen(user) > 120) {
+       if (pop_debug) fprintf(stderr, "username %s too long\n", user);
+       return NOTOK;
+    }
+    
+    sprintf(buf, "QUERY %s", user);    
+    if (pop_debug) fprintf(stderr, "---> %s\n", buf);
+    if (putline(buf, Errmsg, sfo) == NOTOK) return (NOTOK);
+
+    if (getline(buf, sizeof buf, sfi) != OK) {
+       strcpy(Errmsg, buf);
+       return NOTOK;
+    }
+
+    if (pop_debug) fprintf(stderr, "<--- %s\n", buf);
+    if (*buf != '+') {
+       strcpy(Errmsg, buf);
+       return NOTOK;
+    } else {
+       sscanf(buf, "+OK %d", nbytes);
+       return OK;
+    }
+}
+    
+pop_stat(nmsgs, nbytes)
+int *nmsgs, *nbytes;
+{
+    char buf[1024];
+
+    if (pop_debug) fprintf(stderr, "---> STAT\n");
+    if (putline("STAT", Errmsg, sfo) == NOTOK) return(NOTOK);
+
+    if (getline(buf, sizeof buf, sfi) != OK) {
+       strcpy(Errmsg, buf);
+       return(NOTOK);
+    }
+
+    if (pop_debug) fprintf(stderr, "<--- %s\n", buf);
+    if (*buf != '+') {
+       strcpy(Errmsg, buf);
+       return(NOTOK);
+    } else {
+       sscanf(buf, "+OK %d %d", nmsgs, nbytes);
+       return(OK);
+    }
+}
+
+pop_retr(msgno, action, arg)
+int (*action)();
+{
+    char buf[1024];
+    int nbytes = 0;
+    
+    sprintf(buf, "RETR %d", msgno);
+
+    if (pop_debug)
+      fprintf(stderr, "---> %s\n", buf);
+
+    if (putline(buf, Errmsg, sfo) == NOTOK) return(NOTOK);
+
+    if (getline(buf, sizeof buf, sfi) != OK) {
+       strcpy(Errmsg, buf);
+       return(NOTOK);
+    }
+    if (pop_debug)
+      fprintf(stderr, "<--- %s\n", buf);
+
+    sscanf(buf, "+OK %d", &nbytes);
+
+    while (1) {
+       switch (multiline(buf, sizeof buf, sfi)) {
+       case OK:
+            if ((*action)(buf, arg, nbytes) < 0) {
+                strcat(Errmsg, get_errmsg());
+                return (DONE); /* Some error occured in action */
+            }
+           break;
+       case DONE:
+           return (OK);
+       case NOTOK:
+           strcpy(Errmsg, buf);
+           return (NOTOK);
+       }
+    }
+}
+
+pop_getline(buf, n)
+     char *buf;
+     int n;
+{
+    return getline(buf, n, sfi);
+}
+
+getline(buf, n, f)
+char *buf;
+register int n;
+FILE *f;
+{
+    register char *p;
+    int c;
+
+    p = buf;
+    while (--n > 0 && (c = fgetc(f)) != EOF)
+      if ((*p++ = c) == '\n') break;
+
+    if (ferror(f)) {
+       strcpy(buf, "error on connection");
+       return (NOTOK);
+    }
+
+    if (c == EOF && p == buf) {
+       strcpy(buf, "connection closed by foreign host");
+       return (DONE);
+    }
+
+    *p = NULL;
+    if (*--p == '\n') *p = NULL;
+    if (*--p == '\r') *p = NULL;
+    return(OK);
+}
+
+multiline(buf, n, f)
+char *buf;
+register int n;
+FILE *f;
+{
+    if (getline(buf, n, f) != OK) return (NOTOK);
+    if (*buf == '.') {
+       if (*(buf+1) == NULL) {
+           return (DONE);
+       } else {
+           strcpy(buf, buf+1);
+       }
+    }
+    return(OK);
+}
+
+#ifndef HAS_STRERROR
+char *
+strerror(e)
+    int e;
+{
+    extern int errno, sys_nerr;
+    extern char *sys_errlist[];
+
+    if (errno < sys_nerr)
+      s = sys_errlist[errno];
+    else
+      s = "unknown error";
+}
+#endif
+
+char *
+get_errmsg()
+{
+    char *s = strerror(errno);
+    
+    return(s);
+}
+
+putline(buf, err, f)
+char *buf;
+char *err;
+FILE *f;
+{
+    fprintf(f, "%s\r\n", buf);
+    fflush(f);
+    if (ferror(f)) {
+       strcpy(err, "lost connection");
+       return(NOTOK);
+    }
+    return(OK);
+}
+
+
+/* Print error message and exit.  */
+
+fatal (s1, s2)
+     char *s1, *s2;
+{
+  error (s1, s2);
+  exit (1);
+}
+
+/* Print error message.  `s1' is printf control string, `s2' is arg for it. */
+
+error (s1, s2, s3)
+     char *s1, *s2, *s3;
+{
+  printf ("poplib: ");
+  printf (s1, s2, s3);
+  printf ("\n");
+}
+
+pfatal_with_name (name)
+     char *name;
+{
+  char *s = concat ("", strerror(errno), " for %s");
+
+  fatal (s, name);
+}
+
+/* Return a newly-allocated string whose contents concatenate those of s1, s2, s3.  */
+
+char *
+concat (s1, s2, s3)
+     char *s1, *s2, *s3;
+{
+  int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
+  char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
+
+  strcpy (result, s1);
+  strcpy (result + len1, s2);
+  strcpy (result + len1 + len2, s3);
+  *(result + len1 + len2 + len3) = 0;
+
+  return result;
+}
+
+/* Like malloc but get fatal error if memory is exhausted.  */
+
+void *
+xmalloc (size)
+     int size;
+{
+  void *result = malloc (size);
+  if (!result)
+    fatal ("virtual memory exhausted", 0);
+  return result;
+}