/* $USAGI: writed.c,v 1.7 2001/02/13 10:31:53 yoshfuji Exp $ */

/*
 * This code is copyrighted. Please see the file COPYING for a full notice.
 */

/*
 * writed - Receive network write connections with inetd.
 *
 * Note:  This is mostly fingerd.c with all occurances of
 * "finger" changed to "write". Well, not so much any more.
 *
 * $Log: writed.c,v $
 * Revision 1.7  1999/10/02 01:46:45  netbug
 * some extranious header files commented out
 * added socklen_t to MCONFIG.in and to writed.c
 *
 * Revision 1.6  1999/08/01 00:05:24  dholland
 * Use new version of confgen. Set version to 0.15. Update README.
 *
 * Revision 1.5  1997/06/09 01:32:03  dholland
 * minor glibc fixes
 *
 * Revision 1.4  1997/03/08 17:24:30  dholland
 * Oops, fix warning.
 *
 * Revision 1.3  1997/03/08 12:42:04  dholland
 * Don't depend on gcc extensions
 *
 * Revision 1.2  1996/11/25 18:43:05  dholland
 * clean compile.
 *
 * Revision 1.1  1996/11/23  19:50:40  dholland
 * Initial revision
 *
 * Revision 1.8  1996/04/29  21:43:25  dholland
 * Copyright fixes.
 *
 * Revision 1.7  1996/04/29  20:25:43  dholland
 * Config improvements.
 *
 * Revision 1.6  1996/04/29  19:59:14  dholland
 * Add RCS stuff.
 *
 */

char copyright[] =
  "@(#) Copyright (c) 1983 Regents of the University of California.\n"
  "All rights reserved.\n";
/*
 * From: /afs/rel-eng.athena.mit.edu/project/release/current/
 *        source/bsd-4.3/common/etc/RCS/writed.c,v 
 *   1.3 90/04/05 18:31:44 epeisach Exp
 */
char rcsid[] = "$Id: writed.c,v 1.7 1999/10/02 01:46:45 netbug Exp $";
#ifdef _USAGI
#include "version.h"
#else
#include "../version.h"
#endif

/*
 * Write server.
 */
/* #include <mit-copyright.h> */

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

#ifdef ultrix  /* missing protos */
struct sockaddr;
int getpeername(int fd, struct sockaddr *, int *len);
#endif

/* Should be set by makefile */
#ifndef BINDIR
#define BINDIR "/usr/local/bin"
#endif

#define WS " \t\r\n"
#define AC 6  /* max # of args to write, incl terminating null */

/*
#define DEBUG
*/

static void fatal2(char *t) {
  printf("%s\n", t);
  exit(1);
}

static void fatal(char *s, char *t) {
  if (errno!=0) printf("%s: %s\n", s, strerror(errno));
  fatal2(t);
}

static void secure(const char *fromarg) {
#ifdef INET6
  struct sockaddr_storage sa;
#else
  struct sockaddr sa;
#endif
  socklen_t salen = sizeof(sa);
#ifdef INET6
  struct addrinfo hints, *res0, *res;
  int gai;
  char peeraddr[NI_MAXHOST], fromaddr[NI_MAXHOST];
#else
  struct hostent *ho;
  int i;
#endif
  char *u, *h, *t;

  u = strdup(fromarg);
  if (!u) fatal("strdup", "Out of memory");
  h = strchr(u, '@');
  if (!h) fatal2("Usage: write user [tty]");
  *h++=0;
  t = strchr(h, '@');
  if (t) {
    *t++=0;
    if (strchr(t, '@')) fatal2("Usage: write user [tty]");
  }

  if (getpeername(0, (struct sockaddr *) &sa, &salen) < 0) {
    fatal("getpeername", "Where are you?");
  }

#ifdef INET6
  memset(&hints, 0, sizeof(hints));
  switch(((struct sockaddr *)&sa)->sa_family) {
  case AF_INET6:
    /* XXX: use AI_V4MAPPED */
    if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6*)&sa)->sin6_addr)) {
      struct in_addr in;
      unsigned short port;
      in.s_addr = ((struct sockaddr_in6*)&sa)->sin6_addr.s6_addr32[3];
      port = ((struct sockaddr_in6*)&sa)->sin6_port;
      memset(&sa, 0, sizeof(sa));
      ((struct sockaddr_in*)&sa)->sin_family = AF_INET;
      ((struct sockaddr_in*)&sa)->sin_addr.s_addr = in.s_addr;
      ((struct sockaddr_in*)&sa)->sin_port = port;
      salen = sizeof(struct sockaddr_in);
      hints.ai_family = PF_INET;
    } else {
      hints.ai_family = PF_INET6;
    }
    break;
  case AF_INET:
    hints.ai_family = PF_INET;
    break;
  default: /*XXX*/
    hints.ai_family = PF_UNSPEC;
  }
  hints.ai_socktype = SOCK_STREAM;
#endif

#ifdef INET6
  gai = getaddrinfo(h, NULL, &hints, &res0);
  if (gai) fatal("getaddrinfo", "Where are you?");
#else
  ho = gethostbyname(h);
  if (!ho) fatal("gethostbyname", "Where are you?");
#endif

  free(u);

#ifdef INET6
  if (getnameinfo((struct sockaddr *)&sa, salen,
                  peeraddr, sizeof(peeraddr), NULL, 0,
                  NI_NUMERICHOST)) {
    freeaddrinfo(res0);
    fatal("getnameinfo", "where are you?");
  }
#endif

#ifdef INET6
  for (res=res0; res; res=res->ai_next) {
    if (getnameinfo(res->ai_addr, res->ai_addrlen,
                    fromaddr, sizeof(fromaddr), NULL, 0,
                    NI_NUMERICHOST)) {
#ifdef DEBUG
      printf("Cannot do getnameinfo()\n");
      fflush(stdout);
#endif
      continue;
    }
#ifdef DEBUG
    printf("Testing: %s vs %s\n", fromaddr, peeraddr);
    fflush(stdout);
#endif
    if (!strcmp(fromaddr, peeraddr)) {
      freeaddrinfo(res0);
      return;
    }
  }
#else
  for (i=0; ho->h_addr_list[i]; i++) {
#ifdef DEBUG
    unsigned char *p = ho->h_addr_list[i];
    unsigned char *q = (char *) &sin.sin_addr;
    printf("Testing: %u.%u.%u.%u vs %u.%u.%u.%u [%d]\n", 
	   p[0], p[1], p[2], p[3], q[0], q[1], q[2], q[3], ho->h_length);
    fflush(stdout);
#endif
    if (ho->h_length > (int)sizeof(((struct sockaddr_in *)&sa)->sin_addr)) {
	ho->h_length = sizeof(((struct sockaddr_in *)&sa)->sin_addr);
    }
    if (!memcmp(ho->h_addr_list[i], (char *) &((struct sockaddr_in *)&sa)->sin_addr, ho->h_length))
      return;
  }
#endif

  fatal2("Host name lookup error");
}


int main(void /*int argc, char *argv[]*/) {
  char line[BUFSIZ], *av[AC];
  int i=2;
  av[0] = "write";
  av[1] = "-f";
  *line=0;
#ifdef DEBUG
  printf("writed ready\n");
  fflush(stdout);
#endif
  fgets(line, BUFSIZ, stdin);
  for (av[i]=strtok(line, WS); av[i++] && i<AC; av[i]=strtok(NULL, WS));
  av[AC-1] = NULL;
  dup2(0, 1);
  dup2(0, 2);
  secure(av[2]);
#ifdef DEBUG
  printf("Ok, all clear\n");
  fflush(stdout);
#endif
  execv(BINDIR "/write", av);
  execv("/usr/local/bin/write", av);
  execv("/usr/bin/write", av);
  execv("/bin/write", av);
  fatal("execv", "Can't find binary for write");
  return 0;
}
