/*
 * parse_sun.c
 *
 * Module for Linux automountd to parse a Sun-format automounter map
 */

#include <stdio.h>
#include <malloc.h>
#include <errno.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <dlfcn.h>
#include <syslog.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>

#define MODULE_PARSE
#include "automount.h"

static int udpproto;
static short port_discard;

int parse_version = AUTOFS_PARSE_VERSION; /* Required by protocol */

#define MODPREFIX "parse(sun): "

#if 0
static struct mountlib {
  void *dlhandle;
  int *version;
  mount_init_t init;
  mount_mount_t mount;
  mount_done_t done;
} mount_nfs, mount_local;

static int get_mount_lib(char *name, struct mountlib *lib)
{
  char path[PATH_MAX];

  sprintf(path, "%s//mount_%s.so", AUTOFS_LIB_DIR, name);
  if ( !(lib->dlhandle = dlopen(path, RTLD_NOW)) ) {
    syslog(LOG_CRIT, MODPREFIX "cannot open %s mount library", name);
    return 1;
  }
  if ( !(lib->version = (int *) dlsym(lib->dlhandle, "mount_version")) ||
       *lib->version != AUTOFS_MOUNT_VERSION ) {
    syslog(LOG_CRIT, MODPREFIX "%s mount library version mismatch", name);
    dlclose(lib->dlhandle);
    return 1;
  }
  lib->init = (mount_init_t) dlsym(lib->dlhandle, "mount_init");
  lib->mount = (mount_mount_t) dlsym(lib->dlhandle, "mount_mount");
  lib->done = (mount_done_t) dlsym(lib->dlhandle, "mount_done");

  if ( !lib->init || !lib->mount || !lib->done ) {
    syslog(LOG_CRIT, MODPREFIX "%s mount library corrupt", name);
    dlclose(lib->dlhandle);
    return 1;
  }
  return 0;
}
#endif

int parse_init(int argc, char **argv, void **context)
{
  struct protoent *udp;
  struct servent *port_dis;

#if 0
  if ( get_mount_lib("nfs", &mount_nfs) )
    return 1;
  if ( get_mount_lib("local", &mount_local) ) {
    dlclose(mount_nfs.dlhandle);
    return 1;
  }
  
  if ( mount_nfs.init(argc,argv) || mount_local.init(0,NULL) ) {
    dlclose(mount_nfs.dlhandle);
    dlclose(mount_local.dlhandle);
    return 1;
  }
#endif

  /* These are context independent */
  udp = getprotobyname("udp");
  udpproto = udp ? udp->p_proto : 0;
  port_dis = getservbyname("discard","udp");
  if ( port_dis )
    port_discard = port_dis->s_port;
  else
    port_discard = htons(9);    /* 9 is the standard discard port */

  return 0;
}

int parse_mount(char *root, char *name, int name_len, char *mapent, void *context)
{
  char *pmapent, *colon, *p, *q, **haddr;
  struct hostent *he;
  struct sockaddr_in saddr, laddr;
  int sock, len, amp, local, err;

  if ( mapent[0] == '/' ) {
    /* Uh-oh, a multientry; don't support now */
    free(mapent);
    syslog(LOG_NOTICE, "entry %s is a multipath entry", name);
    return 1;
  }

  amp = 0;
  for ( p = mapent ; *p ; p++ )
    if ( *p == '&' )
      amp++;
  if ( amp ) {
    pmapent = malloc(strlen(mapent) + (strlen(name)-1)*amp + 1);
    if (!pmapent) {
      free(mapent);
      syslog(LOG_ERR, MODPREFIX "malloc: %m");
      return 1;
    }
    q = pmapent;
    for ( p = mapent ; *p ; p++ ) {
      if ( *p == '&' ) {
	strcpy(q,name);
	q = strchr(q,'\0');
      } else
	*(q++) = *p;
    }
    free(mapent);
    mapent = pmapent;
  }

  syslog(LOG_DEBUG, MODPREFIX "parsed %s -> %s", name, mapent);

  colon = strchr(mapent, ':');
  if ( !colon ) {
    /* No colon, take this as a symlink (local) entry */
    syslog(LOG_DEBUG, MODPREFIX "entry %s -> %s: no colon, assume local", name, mapent);
    chdir(root);
    err = symlink(mapent, name);
    if ( err && errno == EEXIST )
      err = 0;
    if ( err )
      syslog(LOG_NOTICE, MODPREFIX "symlink %s: %m", name);
    chdir("/");
    free(mapent);
    return err ? 1 : 0;
  }

  *colon = '\0';
  if ( !(he = gethostbyname(mapent)) ) {
    free(mapent);
    syslog(LOG_NOTICE, MODPREFIX "entry %s: host %s: lookup failure",
	   name, mapent);
    return 1;			/* No such host */
  }

  /* Probe to see if we are the local host.  Open a UDP socket and see
     if the local address is the same as the remote one */

  local = 0;
  for ( haddr = he->h_addr_list ; *haddr ; haddr++ ) {
    sock = socket(AF_INET, SOCK_DGRAM, udpproto);
    if ( sock < 0 ) {
      syslog(LOG_ERR, MODPREFIX "socket: %m");
      free(mapent);
      return 1;
    }
    saddr.sin_family = AF_INET;
    bcopy(*haddr, &saddr.sin_addr, he->h_length);
    saddr.sin_port = port_discard;

    len = sizeof(laddr);
    if ( connect(sock, (struct sockaddr *) &saddr, sizeof(saddr)) < 0 
	 || getsockname(sock, (struct sockaddr *) &laddr, &len) < 0 ) {
      syslog(LOG_ERR, MODPREFIX "connect+getsockname failed for %s", name);
      close(sock);
      free(mapent);
      return 1;
    }
    close(sock);
    
    if ( !memcmp(&saddr.sin_addr,&laddr.sin_addr,he->h_length) ) {
      local = 1;
      break;
    }
  }

  if ( local ) {
    /* Local host -- do a symlink */
    
    syslog(LOG_DEBUG, MODPREFIX "%s is local, symlinking", name);
    chdir(root);
    err = symlink(colon+1, name);
    if ( err && errno == EEXIST )
      err = 0;
    if ( err )
      syslog(LOG_NOTICE, MODPREFIX "symlink %s: %m", name);
    chdir("/");
    free(mapent);
    return err ? 1 : 0;
  } else {
    /* Not a local host - do a mount */
    
    chdir(root);
    *colon = ':';
    syslog(LOG_DEBUG, MODPREFIX "calling mkdir %s", name);
    if ( mkdir(name, 0555) && errno != EEXIST ) {
      syslog(LOG_NOTICE, MODPREFIX "mkdir %s failed: %m", name);
      return 1;
    }
    syslog(LOG_DEBUG, MODPREFIX "calling mount -t nfs %s %s", mapent, name);
    err = spawnl(_PATH_MOUNT, _PATH_MOUNT, "-t", "nfs", mapent, name, NULL);
    free(mapent);
    if ( err )
      syslog(LOG_NOTICE, MODPREFIX "nfs entry %s: mount failure",
	     name);
    else
      syslog(LOG_DEBUG, MODPREFIX "mounted %s", name);
    return ( err != 0 );
  }
}

int parse_done(void *context)
{
  return 0;
}
