/* source: fdname.c */
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
/* Published under the GNU General Public License V.2, see file COPYING */

/* the subroutine sockname prints the basic info about the address of a socket
   NOTE: it works on UNIX (kernel) file descriptors, not on libc files! */

#include "config.h"
#include "xioconfig.h"	/* what features are enabled */

#include "sysincludes.h"

#include "mytypes.h"
#include "compat.h"
#include "error.h"
#include "sycls.h"
#include "sysutils.h"

#include "filan.h"


struct sockopt {
   int so;
   char *name;
};


int statname(const char *file, int fd, int filetype, FILE *outfile, char style);
int cdevname(int fd, FILE *outfile);
int sockname(int fd, FILE *outfile, char style);
int unixame(int fd, FILE *outfile);
int tcpname(int fd, FILE *outfile);


int fdname(const char *file, int fd, FILE *outfile, const char *numform,
	   char style) {
   struct stat buf = {0};
   int filetype;
   Debug1("checking file descriptor %u", fd);
   if (fd >= 0) {
      if (Fstat(fd, &buf) < 0) {
	 if (errno == EBADF) {
	    Debug2("fstat(%d): %s", fd, strerror(errno));
	    return -1;
	 } else {
	    Error2("fstat(%d): %s", fd, strerror(errno));
	 }
      }
      filetype = (buf.st_mode&S_IFMT)>>12;
      if (numform != NULL) {
	 fprintf(outfile, numform, fd);
      }
      return statname(file, fd, filetype, outfile, style);
   } else {
      if (Stat(file, &buf) < 0) {
	 Error2("stat(\"%s\"): %s", file, strerror(errno));
      }
      filetype = (buf.st_mode&S_IFMT)>>12;
      return statname(file, -1, filetype, outfile, style);
   }
}

#if HAVE_PROC_DIR_FD || HAVE_PROC_DIR_PATH
static int procgetfdname(int fd, char *filepath, size_t pathsize) {
   static pid_t pid = -1;
   char procpath[PATH_MAX];
   int len;

   /* even if configure has shown that we have /proc, we must check if it
      exists at runtime, because we might be in a chroot environment */
#if HAVE_STAT64
   {
      struct stat64 buf;
      if (Stat64("/proc", &buf) < 0) {
	 return -1;
      }
      if (!S_ISDIR(buf.st_mode)) {
	 return -1;
      }
   }
#else /* !HAVE_STAT64 */
   {
      struct stat buf;
      if (Stat("/proc", &buf) < 0) {
	 return -1;
      }
      if (!S_ISDIR(buf.st_mode)) {
	 return -1;
      }
   }
#endif /* !HAVE_STAT64 */

   if (pid < 0)  pid = Getpid();
   snprintf(procpath, sizeof(procpath), "/proc/"F_pid"/"
#if HAVE_PROC_DIR_PATH
	    "path"
#else
	    "fd"
#endif
	    "/%d", pid, fd);
   if ((len = Readlink(procpath, filepath, pathsize-1)) < 0) {
      Notice4("readlink(\"%s\", %p, "F_Zu"): %s",
	      procpath, filepath, pathsize, strerror(errno));
      len = 0;
   }
   filepath[len] = '\0';
   return 0;
}
#endif /* HAVE_PROC_DIR_FD || HAVE_PROC_DIR_PATH */

int statname(const char *file, int fd, int filetype, FILE *outfile,
	     char style) {
   char filepath[PATH_MAX];

   filepath[0] = '\0';
#if HAVE_PROC_DIR_FD || HAVE_PROC_DIR_PATH
   if (fd >= 0) {
      procgetfdname(fd, filepath, sizeof(filepath));
      if (filepath[0] == '/') {
	 file = filepath;
      }
   }
#endif /*  HAVE_PROC_DIR_FD || HAVE_PROC_DIR_PATH */
   /* now see for type specific infos */
   switch (filetype) {
   case (S_IFIFO>>12):	/* 1, FIFO */
      fputs("pipe", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
   case (S_IFCHR>>12):	/* 2, character device */
      if (cdevname(fd, outfile) == 0) {
	 if (file) fprintf(outfile, " %s", file);
      }
      break;
   case (S_IFDIR>>12):	/* 4, directory */
      fputs("dir", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
   case (S_IFBLK>>12):	/* 6, block device */
      fputs("blkdev", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
   case (S_IFREG>>12):	/* 8, regular file */
      fputs("file", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
   case (S_IFLNK>>12):	/* 10, symbolic link */
      fputs("link", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
   case (S_IFSOCK>>12): /* 12, socket */
#if _WITH_SOCKET
      if (fd >= 0) {
	 sockname(fd, outfile, style);
      } else if (file) {
	 fprintf(outfile, "socket %s", file);
      } else {
	 fputs("socket", outfile);
      }
#else
      Error("SOCKET support not compiled in");
      return -1;
#endif /* !_WITH_SOCKET */
      break;
#ifdef S_IFDOOR
   case (S_IFDOOR>>12):	/* 13, door (Solaris) */
      fputs("door", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
#endif /* HAVE_MACRO_S_IFDOOR */
#ifdef S_IFPORT
   case (S_IFPORT>>12):	/* 14, event port (Solaris) */
      fputs("event_port", outfile);
      if (file) fprintf(outfile, " %s", file);
      break;
#endif /* HAVE_MACRO_S_IFPORT */
   }
   /* ioctl() */
   fputc('\n', outfile);

   return 0;
}


/* character device analysis */
/* return -1 on error, 0 if no name was found, or 1 if it printed ttyname */
int cdevname(int fd, FILE *outfile) {
   int ret;

   if ((ret = Isatty(fd)) < 0) {
      Error2("isatty(%d): %s", fd, strerror(errno));
      return -1;
   }
   if (ret > 0) {
      char *name;

      fputs("tty", outfile);
      if ((name = Ttyname(fd)) != NULL) {
	 fputc(' ', outfile);
	 fputs(name, outfile);
	 return 1;
      }
   } else {
      fputs("chrdev", outfile);
   }
   return 0;
}

int sockettype(int socktype, char *typename, size_t typenamemax) {
   switch (socktype) {
   case SOCK_STREAM:    strncpy(typename,  "stream",    typenamemax); break;
   case SOCK_DGRAM:     strncpy(typename,  "dgram",     typenamemax); break;
   case SOCK_SEQPACKET: strncpy(typename,  "seqpacket", typenamemax); break;
   case SOCK_RAW:       strncpy(typename,  "raw",       typenamemax); break;
   case SOCK_RDM:       strncpy(typename,  "rdm",       typenamemax); break;
#ifdef SOCK_PACKET
   case SOCK_PACKET:    strncpy(typename,  "packet",    typenamemax); break;
#endif
   default:             snprintf(typename, typenamemax, "socktype%u", socktype); break;
   }
   return 0;
}

#if _WITH_SOCKET
int sockname(int fd, FILE *outfile, char style) {
#define FDNAME_OPTLEN 256
#define FDNAME_NAMELEN 256
   socklen_t optlen;
#if (WITH_IP4 || WITH_IP6) && ( HAVE_GETPROTOBYNUMBER || HAVE_GETPROTOBYNUMBER_R )
   struct protoent protoent, *protoentp;
#endif
#define PROTONAMEMAX 1024
   char protoname[PROTONAMEMAX] = "";
#if defined(SO_PROTOCOL) || defined(SO_PROTOTYPE)
   int proto = 0;
#endif
   int opttype;
#ifdef SO_ACCEPTCONN
   int optacceptconn = 0; 	/* OpenBSD does not give value on unix dgram */
#endif
   int result /*0, i*/;
   char socknamebuff[FDNAME_NAMELEN];
   char peernamebuff[FDNAME_NAMELEN];
   /* in Linux these optcodes are 'enum', but on AIX they are bits! */
   union sockaddr_union sockname, peername;	/* the longest I know of */
   socklen_t socknamelen, peernamelen;
#     define TYPENAMEMAX 16
      char typename[TYPENAMEMAX];
#if 0 && defined(SIOCGIFNAME)
   /*Linux struct ifreq ifc = {{{ 0 }}};*/
   struct ifreq ifc = {{ 0 }};
#endif
   int rc;

#if defined(SO_PROTOCOL) || defined(SO_PROTOTYPE)
   optlen = sizeof(proto);
#ifdef SO_PROTOCOL
   rc = Getsockopt(fd, SOL_SOCKET, SO_PROTOCOL,   &proto,         &optlen);
#elif defined(SO_PROTOTYPE)
   rc = Getsockopt(fd, SOL_SOCKET, SO_PROTOTYPE,  &proto,         &optlen);
#endif
   if (rc < 0) {
      Notice5("getsocktop(%d, SOL_SOCKET, "
#ifdef SO_PROTOCOL
	    "SO_PROTOCOL"
#else
	    "SO_PROTOTYPE"
#endif
	    ", &%p, {"F_socklen"}): errno=%d (%s)", fd, &proto, optlen, errno, strerror(errno));
   }
#endif /* defined(SO_PROTOCOL) || defined(SO_PROTOTYPE) */
   optlen = sizeof(opttype);
   Getsockopt(fd, SOL_SOCKET, SO_TYPE,       &opttype,       &optlen);
   sockettype(opttype, typename, sizeof(typename));

   optlen = sizeof(optacceptconn);
#ifdef SO_ACCEPTCONN
   Getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &optacceptconn, &optlen);
#endif

#if (WITH_IP4 || WITH_IP6) && ( defined(SO_PROTOCOL) || defined(SO_PROTOTYPE) )
#if HAVE_GETPROTOBYNUMBER_R==1 /* Linux */
   rc = getprotobynumber_r(proto, &protoent, protoname, sizeof(protoname), &protoentp);
   if (protoentp == NULL) {
      Warn2("sockname(): getprotobynumber_r(proto=%d, ...): %s",
	    proto, strerror(rc));
   }
   strncpy(protoname, protoentp->p_name, sizeof(protoname));
#elif HAVE_GETPROTOBYNUMBER_R==2 /* Solaris */
   {
#     define FILAN_GETPROTOBYNUMBER_R_BUFLEN 1024
      char buffer[FILAN_GETPROTOBYNUMBER_R_BUFLEN];
      protoentp = getprotobynumber_r(proto, &protoent, buffer, FILAN_GETPROTOBYNUMBER_R_BUFLEN);
      strncpy(protoname, protoentp->p_name, sizeof(protoname));
   }
#elif HAVE_GETPROTOBYNUMBER_R==3 /* AIX, OpenBSD */
   {
      struct protoent_data proto_data = { 0 }; 	/* OpenBSD might SIGSEGV */
      rc = getprotobynumber_r(proto, &protoent, &proto_data);
      if (rc == 0) {
	 strncpy(protoname, protoent.p_name, sizeof(protoname));
	 endprotoent_r(&proto_data);
      }
   }
#else
   switch (proto) {
   case IPPROTO_TCP:  strcpy(protoname, "tcp"); break;
   case IPPROTO_UDP:  strcpy(protoname, "udp"); break;
   case IPPROTO_SCTP: strcpy(protoname, "sctp"); break;
   default: sprintf(protoname, "proto%d", proto); break;
   }
#endif
#else /* ! (defined(SO_PROTOCOL) || defined(SO_PROTOTYPE)) */
   if (opttype == SOCK_STREAM) {
      strcpy(protoname, "(stream)");
   } else if (opttype == SOCK_DGRAM) {
      strcpy(protoname, "(dgram)");
#ifdef SOCK_RAW
   } else if (opttype == SOCK_RAW) {
      strcpy(protoname, "(raw)");
#endif
#ifdef SOCK_RDM
   } else if (opttype == SOCK_RDM) {
      strcpy(protoname, "(rdm)");
#endif
#ifdef SOCK_SEQPACKET
   } else if (opttype == SOCK_SEQPACKET) {
      strcpy(protoname, "(seqpacket)");
#endif
#ifdef SOCK_DCCP
   } else if (opttype == SOCK_DCCP) {
      strcpy(protoname, "(dccp)");
#endif
#ifdef SOCK_PACKET
   } else if (opttype == SOCK_PACKET) {
      strcpy(protoname, "(packet)");
#endif
   } else {
      strcpy(protoname, "socket");
   }
#endif /* ! (defined(SO_PROTOCOL) || defined(SO_PROTOTYPE)) */
   socknamelen = sizeof(sockname);
   result = Getsockname(fd, &sockname.soa, &socknamelen);
   if (result < 0) {
      Error2("getsockname(%d): %s", fd, strerror(errno));
      return -1;
   }

   peernamelen = sizeof(peername);
   result = Getpeername(fd, (struct sockaddr *)&peername, &peernamelen);
   if (result < 0) {
      Warn2("getpeername(%d): %s", fd, strerror(errno));
   }

   switch (sockname.soa.sa_family) {
#if WITH_UNIX
   case AF_UNIX:
     switch (style) {
     case 's':
      fprintf(outfile, "unix%s%s %s",
	      opttype==SOCK_DGRAM?"datagram":"",
#ifdef SO_ACCEPTCONN
	      optacceptconn?"(listening)":
#endif
	      "",
	      sockaddr_unix_info(&sockname.un, socknamelen,
				 socknamebuff, sizeof(socknamebuff)));
      break;
     case 'S':
	/* sockettype(opttype, typename, TYPENAMEMAX); */
	fprintf(outfile, "unix %s-%s %s %s",
		sockaddr_unix_info(&sockname.un, socknamelen,
				   socknamebuff, sizeof(socknamebuff)),
		sockaddr_unix_info(&peername.un, peernamelen,
				   peernamebuff, sizeof(peernamebuff)),
		typename,
#ifdef SO_ACCEPTCONN
		optacceptconn?"(listening)":
#endif
		"");
	break;
     }
     break;
#endif /* WITH_UNIX */
#if WITH_IP4
   case AF_INET:
     switch (style) {
     case 's':
      switch (opttype) {
#if WITH_TCP
      case SOCK_STREAM:
	 fprintf(outfile, "%s%s %s %s",
		 protoname,
#ifdef SO_ACCEPTCONN
		 optacceptconn?"(listening)":
#endif
		 "",
		 sockaddr_inet4_info(&sockname.ip4,
				     socknamebuff, sizeof(socknamebuff)),
		 sockaddr_inet4_info(&peername.ip4,
				     peernamebuff, sizeof(peernamebuff)));
	 break;
#endif
#if WITH_UDP
      case SOCK_DGRAM:
	 fprintf(outfile, "%s%s %s %s",
		 protoname,
#ifdef SO_ACCEPTCONN
		 optacceptconn?"(listening)":
#endif
		 "",
		 sockaddr_inet4_info(&sockname.ip4,
				     socknamebuff, sizeof(socknamebuff)),
		 sockaddr_inet4_info(&peername.ip4,
				     peernamebuff, sizeof(peernamebuff)));
	 break;
#endif
      default:
	 fprintf(outfile, "ip %s",
		 sockaddr_inet4_info(&sockname.ip4,
				     socknamebuff, sizeof(socknamebuff)));
	 break;
      }
      break;
     case 'S':
	fprintf(outfile, "%s %s-%s (%s) %s",
		protoname,
		 sockaddr_inet4_info(&sockname.ip4,
				     socknamebuff, sizeof(socknamebuff)),
		 sockaddr_inet4_info(&peername.ip4,
				     peernamebuff, sizeof(peernamebuff)),
		typename,
#ifdef SO_ACCEPTCONN
		 optacceptconn?"(listening)":
#endif
		"");
	break;
     }
     break;
#endif /* WITH_IP4 */

#if WITH_IP6
   case AF_INET6:
     switch (style) {
     case 's':
      switch (opttype) {
#if WITH_TCP
      case SOCK_STREAM:
	 fprintf(outfile, "%s6%s %s %s",
		 protoname,
#ifdef SO_ACCEPTCONN
		 optacceptconn?"(listening)":
#endif
		 "",
		 sockaddr_inet6_info(&sockname.ip6,
				     socknamebuff, sizeof(socknamebuff)),
		 sockaddr_inet6_info(&peername.ip6,
				     peernamebuff, sizeof(peernamebuff)));
	 break;
#endif
#if WITH_UDP
      case SOCK_DGRAM:
	 fprintf(outfile, "%s6%s %s %s",
		 protoname,
#ifdef SO_ACCEPTCONN
		 optacceptconn?"(listening)":
#endif
		 "",
		 sockaddr_inet6_info(&sockname.ip6,
				     socknamebuff, sizeof(socknamebuff)),
		 sockaddr_inet6_info(&peername.ip6,
				     peernamebuff, sizeof(peernamebuff)));
	 break;
#endif
      default:
	 fprintf(outfile, "ip6 %s",
		 sockaddr_inet6_info(&sockname.ip6,
				     socknamebuff, sizeof(socknamebuff)));
	 break;
      }
      break;
     case 'S':
	fprintf(outfile, "%s6 %s-%s (%s) %s",
		protoname,
		 sockaddr_inet6_info(&sockname.ip6,
				     socknamebuff, sizeof(socknamebuff)),
		 sockaddr_inet6_info(&peername.ip6,
				     peernamebuff, sizeof(peernamebuff)),
		typename,
#ifdef SO_ACCEPTCONN
		 optacceptconn?"(listening)":
#endif
		"");
	break;
     }
     break;
#endif /* WITH_IP6 */

   default:
      fprintf(outfile, "socket(family/domain=%d)", sockname.soa.sa_family);
   }

#if HAVE_GETPROTOENT
   if (ipproto >= 0) {
      endprotoent();
   }
#endif
   return result;
#undef FDNAME_OPTLEN
#undef FDNAME_NAMELEN
}
#endif /* _WITH_SOCKET */