/*

			Speak Freely for Unix
                     Look Who's Listening Client

*/

#include "speakfree.h"
#include "version.h"

static int sock;		      /* Our socket */
static char *prog;		      /* Program name */ 

static struct sockaddr_in lookhost;   /* Look who's listening host, if any */
static int lwlport = Internet_Port + 2; /* Look Who's Listening port */
static int debugging = FALSE;	      /* Debug mode enabled */
static int showmessage = FALSE;       /* Show server message */

struct lwl {
    struct lwl *next;		      /* Next connection */
    long ltime; 		      /* Time of last update */
    unsigned long ssrc; 	      /* Session source descriptor */
    long naddr; 		      /* Internet address */
    short port; 		      /* Port address */
    char *cname;		      /* Canonical name */ 
    char *name; 		      /* User name */
    char *email;		      /* Electronic mail address */
    char *phone;		      /* Telephone number */
    char *loc;			      /* Geographic location */
    char *tool; 		      /* Application name */
};

/*  PROG_NAME  --  Extract program name from argv[0].  */

static char *prog_name(arg)
  char *arg;
{
    char *cp = strrchr(arg, '/');

    return (cp != NULL) ? cp + 1 : arg;
}

/*  USAGE  --  Print how-to-call information.  */

static void usage()
{
    V fprintf(stderr, "%s  --  Speak Freely: Look Who's Listening Lookup.\n", prog);
    V fprintf(stderr, "               %s.\n", Relno);
    V fprintf(stderr, "\n");
    V fprintf(stderr, "Usage: %s [options]\n", prog);
    V fprintf(stderr, "Options:\n");
    V fprintf(stderr, "           -C          Output sfmike connection address(es)\n");
    V fprintf(stderr, "           -D          Debugging output on stderr\n");
    V fprintf(stderr, "           -Hhost:port Query designated host\n");
    V fprintf(stderr, "           -L          List table of matches\n");
    V fprintf(stderr, "           -M          Show server message\n");
    V fprintf(stderr, "           -U          Print this message\n");
    V fprintf(stderr, "\n");
    V fprintf(stderr, "by John Walker\n");
    V fprintf(stderr, "   http://www.fourmilab.ch/\n");
}

/*  DUPSDESITEM  --  Make a copy of an SDES item and advance the pointer
		     past it.  */

static char *dupSdesItem(cp)
  char **cp;
{
    char *ip = *cp, *bp;
    int l = ip[1] & 0xFF;

    bp = malloc(l + 1);
    if (bp != NULL) {
	bcopy(ip + 2, bp, l);
	bp[l] = 0;
    }
    *cp = ip + l + 2;
    return bp;
}

/*  DESTROYLWL	--  Release storage associated with an LWL list item.  */

static void destroyLwl(lw)
  struct lwl *lw;
{
    if (lw->cname != NULL) {
	free(lw->cname);
    }
    if (lw->name != NULL) {
	free(lw->name);
    }
    if (lw->email != NULL) {
	free(lw->email);
    }
    if (lw->phone != NULL) {
	free(lw->phone);
    }
    if (lw->loc != NULL) {
	free(lw->loc);
    }
    if (lw->tool != NULL) {
	free(lw->tool);
    }
    free(lw);
}

/*  DUMPLWL  --  Dump an LWL on the specified stream.  */

static void dumpLwl(fo, lw)
  FILE *fo;
  struct lwl *lw;
{
    struct sockaddr_in s;

    s.sin_addr.s_addr = lw->naddr;
    fprintf(fo, "SSRC = %X IP = %s Port %d %s", lw->ssrc, inet_ntoa(s.sin_addr),
		lw->port, ctime(&(lw->ltime)));
    if (lw->cname != NULL) {
        fprintf(fo, "    CNAME = %s\n", lw->cname);
    }
    if (lw->name != NULL) {
        fprintf(fo, "     NAME = %s\n", lw->name);
    }
    if (lw->email != NULL) {
        fprintf(fo, "    EMAIL = %s\n", lw->email);
    }
    if (lw->phone != NULL) {
        fprintf(fo, "    PHONE = %s\n", lw->phone);
    }
    if (lw->loc != NULL) {
        fprintf(fo, "      LOC = %s\n", lw->loc);
    }
    if (lw->tool != NULL) {
        fprintf(fo, "     TOOL = %s\n", lw->tool);
    }
}

/*  Main program.  */

main(argc, argv)
  int argc;
  char *argv[];
{
    int i, argie, length, nret = 0, connex = FALSE,
	listing = FALSE, deefault = TRUE;
    struct sockaddr_in name;
    char *hostspec = NULL, *cp;

    /*	Process command line options.  */

    prog = prog_name(argv[0]);
    for (argie = 1; argie < argc; argie++) {
	char *op, opt;

	op = argv[argie];
        if (*op == '-') {
	    opt = *(++op);
	    if (islower(opt)) {
		opt = toupper(opt);
	    }

	    switch (opt) {
                case 'C':             /* -C  --  Output connect addresses */
		    connex = TRUE;
		    deefault = FALSE;
		    break;

                case 'D':             /* -D  --  Debug output to stderr */
		    debugging = TRUE;
		    break;

                case 'H':             /* -Hhost:port  --  Host/port to query */
		    hostspec = op + 1;
		    break;

                case 'L':             /* -L  --  List table of finds  */
		    listing = TRUE;
		    deefault = FALSE;
		    break;

                case 'M':             /* -M  --  Show server message */
		    showmessage = TRUE;
		    break;

                case 'U':             /* -U  --  Print usage information */
                case '?':             /* -?  --  Print usage information */
		    usage();
		    return 0;
	    }
	} else {
	    break;
	}
    }

    /* Try to locate host to contact for information. */

    if ((cp = hostspec) == NULL) {
        cp = getenv("SPEAKFREE_LWL_ASK");
	if (cp == NULL || strlen(cp) == 0) {
	    char *np;

            cp = getenv("SPEAKFREE_LWL_TELL");
	    if (cp == NULL) {
                fprintf(stderr, "%s: no host specified.\n", prog);
                fprintf(stderr, "%s: use -Hhost:port option or SPEAKFREE_LWL_ASK\n", prog);
                fprintf(stderr, "%s: or SPEAKFREE_LWL_TELL environment variables.\n", prog);
		return 2;
	    }

	    /* Multiple hosts may have been specified on the
	       SPEAKFREE_LWL_TELL statement.  If so, query only the
	       first. */

            if ((np = strchr(cp, ',')) != NULL) {
		*np = 0;
	    }
	}
    }

    if (cp != NULL) {
	struct hostent *h;
	char *ep;

        if ((ep = strchr(cp, ':')) != NULL) {
	    *ep = 0;
	    lwlport = atoi(ep + 1);
	}
	h = gethostbyname(cp);
	if (h != NULL) {
	    bcopy((char *) (h->h_addr), (char *) &lookhost.sin_addr.s_addr,
		sizeof lookhost.sin_addr.s_addr);
	    lookhost.sin_family = AF_INET;
	    lookhost.sin_port = htons(lwlport);
	} else {
            fprintf(stderr, "%s: server %s is unknown.\n", prog, cp);
	    return 2;
	}
    }

    /* Now loop through the command line arguments and pose
       each as a query to the server, then print whatever
       reply we get. */

    for (; showmessage || (argie < argc); argie++) {
	char p[1500];
	rtcp_t *rp = (rtcp_t *) p;
        static char query[] = "SFlq";
	int l;
	char *cp;

	/* Create the socket */

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0) {
            perror("opening stream socket");
	    return 1;
	}

	if (connect(sock, (struct sockaddr *) &lookhost, sizeof lookhost) < 0) {
            perror("connecting stream socket");
	    return 1;
	}

	/* Find assigned port value and print it. */

	length = sizeof(name);
	if (getsockname(sock, (struct sockaddr *) &name, &length) < 0) {
            perror("getting socket name");
	    return 1;
	}
#ifdef SHOW_SOCKET
        fprintf(stderr, "%s: socket port #%d\n", prog, ntohs(name.sin_port));
#endif

#ifdef RationalWorld
	rp->common.version = RTP_VERSION;
	rp->common.p = 0;
	rp->common.count = 1;
	rp->common.pt = RTCP_APP;
#else
	*((short *) rp) = htons((RTP_VERSION << 14) | RTCP_APP | (1 << 8));
#endif
	rp->r.sdes.src = 0;
	if (showmessage) {
	    argie--;
            bcopy("SFms", p + 8, 4);
	    rp->common.length = htons(2);
	    if (send(sock, p, 12, 0) < 0) {
                perror("sending server message request");
	    }
	} else {
	    bcopy(query, p + 8, 4);
	    strcpy(p + 12, argv[argie]);
	    cp = p + 13 + strlen(p + 12);
	    while ((cp - p) & 3) {
		*cp++ = 0;
	    }
	    rp->common.length = htons(((cp - p) / 4) - 1);
	    if (send(sock, p, cp - p, 0) < 0) {
                perror("sending look who's listening query message");
	    }
	}

	if ((l = recv(sock, p, sizeof p, 0)) > 0) {
	    if (showmessage) {
		showmessage = FALSE;
		fwrite(p + 12, l - 13, 1, stdout);
	    } else {
		int sdes_count;

#ifdef RationalWorld
		sdes_count = rp->common.count;
#else
		sdes_count = (((unsigned char *) rp)[0]) & 0x1F;
#endif
		cp = (char *) &(rp->r.sdes.src);
		while (sdes_count-- > 0) {
		    struct lwl *lw;

		    lw = (struct lwl *) malloc(sizeof(struct lwl));
		    if (lw != NULL) {
			int parsing = TRUE;
			struct sockaddr_in u;
			char s[40];

			bzero((char *) lw, sizeof(struct lwl));

			bcopy(cp, (char *) &(lw->ssrc), 4);
			cp += 4;

			while (parsing) {
			    switch ((*cp) & 0xFF) {

				case RTCP_SDES_CNAME:
				    lw->cname = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_NAME:
				    lw->name = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_EMAIL:
				    lw->email = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_PHONE:
				    lw->phone = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_LOC:
				    lw->loc = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_TOOL:
				    lw->tool = dupSdesItem(&cp);
				    break;

				case RTCP_SDES_PRIV:
				    {
					char *zp = dupSdesItem(&cp);

					if (zp != NULL) {
					    if (zp[0] == 1 &&
                                                zp[1] == 'P') {
						lw->port = atoi(zp + 2);
					    } else if (zp[0] == 1 &&
                                                       zp[1] == 'I') {
						lw->naddr = inet_addr(zp + 2);
					    }  else if (zp[0] == 1 &&
                                                        zp[1] == 'T') {
						lw->ltime = atol(zp + 2);
					    }
					    free(zp);
					}
				    }
				    break;

				case RTCP_SDES_END:
				    cp++;
				    while ((cp - p) & 3) {
					cp++;
				    }
				    parsing = FALSE;
				    break;

				default:
				    {
					char *zp = dupSdesItem(&cp);

					if (zp != NULL) {
					    free(zp);
					}
				    }
				    break;
			    }
			}
			if (debugging) {
			    dumpLwl(stderr, lw);
			}
			u.sin_addr.s_addr = lw->naddr;

			/* Default output listing: short address/cname (name) list. */

			if (deefault) {
			    char ipport[40], deef[256];

                            sprintf(ipport, "%s:%d", inet_ntoa(u.sin_addr), lw->port);
                            sprintf(deef, "%-24s %s", ipport, lw->cname);
			    if (lw->name) {
                                sprintf(deef + strlen(deef), " (%s)", lw->name);
			    }
                            printf("%s\n", deef);
			}

			/* Output connect IP:port arguments if requested. */

			if (connex) {
                            printf("%s-p%s:%d", nret > 0 ? " " : "",
				inet_ntoa(u.sin_addr), lw->port);
			}

			/* Produce long tabular listing of results.  */

			if (listing) {
			    char ipport[40];
			    struct tm *lt;

			    lt = localtime(&lw->ltime);
                            sprintf(ipport, "%s:%d", inet_ntoa(u.sin_addr), lw->port);
                            printf("\n%-24s %-48s %02d:%02d\n", ipport, lw->cname,
				lt->tm_hour, lt->tm_min);
			    if (lw->name != NULL) {
                                printf("%25s%s\n", "", lw->name);
			    }
			    if (lw->loc != NULL) {
                                printf("%25s%s\n", "", lw->loc);
			    }
			    if (lw->phone != NULL) {
                                printf("%25sPhone:  %s\n", "", lw->phone);
			    }
			    if (lw->email != NULL) {
                                printf("%25sE-mail: %s\n", "", lw->email);
			    }
			}

			nret++;
			destroyLwl(lw);
		    }
		}
	    }
	}
	if (l <= 0) {
            perror("reading reply from socket");
	    return 2;
	}
	close(sock);
    }
    if (connex) {
	if (nret > 0) {
            printf("\n");
	} else {
            fprintf(stderr, "No users found.\n");
	    return 2;
	}
    }
    return 0;
}
