#ifndef lint
static char RCSid[] = "$Header: ups.c,v 1.8 86/12/11 15:58:18 scooter Exp $";
#endif

/*
 * ups - user interface to the package delivery system
 *
 * usage: ups user@host file1 file2 ...
 *
 * $Author: scooter $
 * $Revision: 1.8 $
 * $Date: 86/12/11 15:58:18 $
 *
 * $Log:	ups.c,v $
 * Revision 1.8  86/12/11  15:58:18  scooter
 * Added alias expansion code which allows ups to follow /usr/lib/aliases.
 * 
 * Revision 1.7  86/09/19  18:53:15  scooter
 * Added -i option for mail specification
 * 
 * Revision 1.6  86/09/18  15:19:45  scooter
 * More fixes to the '.' problam
 * 
 * Revision 1.5  86/09/17  09:43:38  scooter
 * Added code to do automatic renaming of "." files for delivery to
 * avoid the "cannot delete" problem
 * 
 * Revision 1.4  85/08/21  22:27:45  scooter
 * Release revision: added more complete RCS headers.
 * 
 */

#include <stdio.h>
#include <pwd.h>
#include <ndbm.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <netdb.h>
#include <ctype.h>

#define	LS "/bin/ls"

struct	passwd *getpwuid();
FILE 	*fopen();
int	rem;
int	qflg = 0;
int	iflg = 0;
int	on   = 1;
char	buffer[BUFSIZ*5];
char	mbuffer[BUFSIZ*4];

struct	user_list {
	char	*u_user;
	char	*u_host;
	struct	user_list *u_next;
};

main(argc, argv)
char **argv;
int argc;
{
	char	myhost[BUFSIZ];
	int	file;
	int	ret;
	char	*user,*host,*tmp,*rindex(),*index();
	struct	passwd *mypwent;
	struct	user_list *u_list,*u_top, *alias_expand();

	gethostname(myhost,BUFSIZ);

	if( (mypwent = getpwuid(getuid())) == NULL )
	{
		fprintf(stderr,"ups: who are you?\n");
		exit(1);
	}

	argv++;
	argc--;
	while (argc && *argv[0] == '-')
	{
		char 	c;

		switch (c = argv[0][1])
		{

		case 'q':
			qflg++;
			break;

		case 'i':
			iflg++;
			break;

		default:
			fprintf(stderr,"ups: unknown option %c\n",c);

		}
		argv++;
		argc--;
	}

	if (argc <= 1 || qflg)
		upsread(mypwent);

	if (argc < 2) {
		fprintf(stderr,"usage: ups user@host file1 file2 ...\n");
		exit(0);
	}

	/*
 * Get the name of the destination user and host
 */

	user = argv[0];
	host = rindex(user,'@');
	fprintf(stderr, "user = %s\n", user);
#ifndef	ALIASES
	if (host == NULL)
		host = myhost;
	else
		*host++ = '\0';
#else
	if (host != NULL)
		*host++ = '\0';

	u_top = alias_expand(user,host,myhost);

#endif	ALIASES

#ifdef DEBUG
	printf("Here we are again\n");
#endif

	if (iflg)
		upsgetmsg(mbuffer);
	else
		mbuffer[0] = '\0';

	while (u_top != NULL)
	{

#ifdef	DEBUG
		printf("Connecting to %s for user %s\n",u_top->u_host,u_top->u_user);
#endif
		rem = upsconnect(u_top->u_host);	/* Connect to the server */

		/*
		 * Send the to name, from name, and our name
		 */

		tmp = index(mypwent->pw_gecos,',');
		if (tmp)
			*tmp = '\0';

		sprintf(buffer, "%s\n%s\n%s\n%s", u_top->u_user,
		    mypwent->pw_name, mypwent->pw_gecos,mbuffer);

#ifdef	DEBUG
		printf("Sending: %s\n",buffer);
		fflush(stdout);
#endif	DEBUG

		write(rem, buffer, strlen(buffer)+1);
		ret = read(rem, buffer, BUFSIZ);
		if (buffer[0])
			problem(1);

		for (file = 1 ; file < argc ; file++)
		{

#ifdef	DEBUG
			printf("Sending file: %s\n",argv[file]);
#endif	DEBUG

			sendfile(argv[file]);
		}

		sprintf(buffer, "-Done-");
		write(rem, buffer, strlen(buffer)+1);
		read(rem, buffer, BUFSIZ);	/* get result */
		if (buffer[0])		/* problem? */
			problem(1);	/* yes, go handle it */

		close(rem);
		u_top = u_top->u_next;
	}

}




upsconnect(host)
char *host;
{
	struct hostent *hp;
	struct servent *sp;
	struct	sockaddr_in sin;
	int s;

	hp = gethostbyname(host);
	if (hp == NULL) {
		static struct hostent def;
		static struct in_addr defaddr;
		static char namebuf[128];
		int inet_addr();

		defaddr.s_addr = inet_addr(host);
		if (defaddr.s_addr == -1) {
			printf("unknown host: %s\n", host);
			exit(1);
		}
		strcpy(namebuf, host);
		def.h_name = namebuf;
		def.h_addr = (char *)&defaddr;
		def.h_length = sizeof (struct in_addr);
		def.h_addrtype = AF_INET;
		def.h_aliases = 0;
		hp = &def;
	}
	sp = getservbyname("ups", "tcp");
	if (sp == 0) {
		fprintf(stderr,"tcp/ups: unknown service\n");
		exit(1);
	}
	sin.sin_family = hp->h_addrtype;
	bcopy(hp->h_addr, (char *)&sin.sin_addr, hp->h_length);
	sin.sin_port = sp->s_port;
	s = socket(hp->h_addrtype, SOCK_STREAM, 0);
	if (s < 0) {
		fflush(stderr);
		perror("ups (socket)");
		exit(1);
	}
	setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,&on,sizeof(on));
#ifdef	DEBUG
	setsockopt(s,SOL_SOCKET,SO_DEBUG,&on,sizeof(on));
#endif	DEBUG
	if (connect(s, (char *)&sin, sizeof (sin)) < 0) {
		fflush(stderr);
		perror("ups (connect)");
		close(s);
		exit(1);
	}
	return(s);
}




sendfile(file)
char *file;
{
	struct	stat fstatus;
	int	loc,n;
	char	*tmp,*fname;

	if ( (loc = open(file,O_RDONLY)) <= 0 ) {
		perror("ups (open)");
		return(1);
	}
	if (fstat(loc, &fstatus)) {
		perror("ups (fstat)");
		return(1);
	}

	if ((fstatus.st_mode&S_IFMT) != S_IFREG) {
		switch (fstatus.st_mode&S_IFMT)
		{
		case S_IFDIR:
			tmp = "directory";
			break;
		case S_IFCHR:
			tmp = "character device";
			break;
		case S_IFBLK:
			tmp = "block device";
			break;
		case S_IFLNK:
			tmp = "symbolic link";
			break;
		case S_IFSOCK:
			tmp = "socket";
			break;
		}

		fprintf(stderr,
		    "ups: %s is a %s, only regular files may be sent\n",
		    file,tmp);
		fflush(stderr);
		return(1);
	}

	/*
	 * Send the file name
	 */

	/*
	 * First strip off the directory path
	 */
	sprintf(buffer,"%s",file);
	tmp = rindex(buffer,'/');
	if (tmp)
		*tmp++ = '\0';
	else
		tmp = buffer;

	fname = tmp;

	if (*tmp == '.')
	{
		while ( (*tmp == '.') && (*tmp != '\0') )tmp++;

		fprintf(stdout,
		    "WARNING: file %s has been renamed to %s for delivery\n",
		    fname,tmp);
	}

	write(rem, tmp, strlen(tmp)+1);
	read(rem, buffer, BUFSIZ);
	if (buffer[0])		/* problem */
	{
		problem(0);
		return(1);
	}

	/*
	 * Send the file size in bytes
	 */

	sprintf(buffer, "%D",fstatus.st_size);
	write(rem, buffer, strlen(buffer)+1);
	read(rem, buffer, BUFSIZ);
	if (buffer[0])		/* problem */
	{
		problem(0);
		return(1);
	}

	/*
	 * Send the file
	 */
	while (n = read(loc, buffer, BUFSIZ))
		write(rem, buffer, n);
	close(loc);
	read(rem, buffer, BUFSIZ); /* get result */
	if (buffer[0])		/* problem */
	{
		problem(0);
		return(1);
	}
	sprintf(buffer, "%u", fstatus.st_mode);
	write(rem, buffer, strlen(buffer)+1);
	read(rem, buffer, BUFSIZ); /* get result */
	if (buffer[0])		/* problem */
	{
		problem(0);
		return(1);
	}
	return(0);
}



upsread(mypwent)
struct	passwd *mypwent;
{
	union	wait status;
	int	pid;
	char	c,line[BUFSIZ],*tmp;

	sprintf(buffer,"%s/%s",UPSDIR,mypwent->pw_name);
	if (qflg)
	{
		if (!access(buffer,F_OK)) {
			fprintf(stderr,
			    "You have ups files awaiting you (type ups to accept delivery)\n");
		}
		exit(0);
	}
	if (access(buffer,F_OK)) {
		fprintf(stderr,"Nothing waiting in ups\n");
		exit(0);
	}
	fprintf(stdout,"\nYou have the following files awaiting delivery:\n\n");
	if (pid = vfork())
	{
		wait(&status);
	} else {
		execl(LS,"ls","-C",buffer,(char *)0);
		perror("ups (exec)");
		return(0);
	}
	fprintf(stdout,"\n");

	fprintf(stdout,
	    "Do you wish to accept delivery in your\n");
	fprintf(stdout,
	    "current directory (%s)? ",
	    getwd(line));

getinp:
	if (fgets(line,BUFSIZ,stdin) == NULL) {
		fprintf(stdout,"\n");
		exit(0);
	}

	tmp = &line[0];
	while (isspace(*tmp)) tmp++;

	switch (*tmp) {
	case 'y':
	case 'Y':
		sprintf(line,"mv -i %s/* . ; rmdir %s",buffer,buffer);
		system(line);
		exit(0);

	case 'n':
	case 'N':
		exit(0);

	default:
		fprintf(stdout,"Please answer 'yes' or 'no': ");
		goto getinp;
	}
}



/*
 * problem() is called when the install demon process return a non-zero reply.
 * This usually means something recognizable went wrong and we should expect
 * a reason to follow. Read in the reason, output it on the terminal and die.
 */

problem(die)
int die;
{
	char buf[BUFSIZ];	/* place to read into */

	if (read(rem, buf, BUFSIZ) > 0)		/* if we have something */
		fprintf(stderr, "ups: %s", buf);
	if (die) {
		close(rem);		/* close network channel */
		exit(1);
	}
}

int
upsgetmsg(mailbuffer)
char *mailbuffer;
{
	int done = 0;

	fprintf(stdout,
	    "Enter your message followed by '.<RETURN>' or a <CTRL>D:\n");

	while (!done)
	{
		fprintf(stdout,"> ");
		if (gets(mailbuffer) == NULL)
		{
			fprintf(stdout,"\n");
			done++;
		} else if ( (*mailbuffer == '.') && (*(mailbuffer+1) == '\0'))
		{
			done++;
			*mailbuffer = '\0';
		} else {
			while(*mailbuffer != '\0')
				mailbuffer++;
			*mailbuffer++ = '\n';
			*mailbuffer = '\0';
		}
	}
	fprintf(stdout,"[EOT]\n");
}



struct user_list *
alias_expand(user,host,myhost)
char *user,*host,*myhost;
{
	char	*malloc();
	DBM	*dp;
	datum	key,content;
	struct	user_list *list,*top,*alloc_list();
	char	*cp,*tp,*hp;

	list = top = (struct user_list *)NULL;

	if (host != NULL)
		return(alloc_list(user,host));

	dp = dbm_open("/usr/lib/aliases", O_RDONLY, 0644);

	if (dp == NULL) {
		if(host == NULL)
			return(alloc_list(user, myhost));
		else
			return(alloc_list(user, host));
	}

	key.dptr = user;
	key.dsize = strlen(user) + 1;

	content = dbm_fetch(dp,key);
	if (content.dptr == NULL)
		return(alloc_list(user,myhost));
	cp = content.dptr;

	while (cp != NULL)
	{
		tp = cp;
		cp = index(cp,',');
		if (cp != NULL)
			*cp++ = '\0';
		if (index(tp,'!')) {
			fprintf(stderr,
			    "ups: WARNING: cannot alias %s to %s (no ups over uucp)\n",
			    user,hp);
			continue;
		} else if(index(tp,'|')) {
			fprintf(stderr,
			    "ups: WARNING: cannot alias %s to %s (no shells allowed)\n",
			    user,hp);
			continue;
		}
		hp = tp;
		hp = index(tp,'@');
		if (hp == NULL)
			hp = myhost;
		else
			*hp++ = '\0';
		if (top == NULL)
			top = list = alloc_list(tp,hp);
		else
		{
			list->u_next = alloc_list(tp,hp);
			list = list->u_next;
		}
	}
	return(top);
}

struct	user_list *alloc_list(user,host)
char *user,*host;
{
	char *malloc();
	struct	user_list *list;

	list = (struct user_list *)malloc(sizeof(struct user_list));
	list->u_user = malloc(strlen(user) + 1);
	list->u_host = malloc(strlen(host) + 1);
	strcpy(list->u_user,user);
	strcpy(list->u_host,host);
	list->u_next = (struct user_list *)NULL;
	return(list);
}
