/*
  $Id: MSMessage.m,v 3.1 1997/10/27 11:22:42 lukeh Exp $

  Copyright (c) 1996, 1997 Luke Howard.
  All rights reserved.

  Portions Copyright (C) 1993 Zik Saleeba <zik@zikzak.net>
  Portions Copyright (C) 1993 Andrew Herbert <andrew@mira.net.au>
  Portions Copyright (C) 1992 Sun Microsystems. Inc.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:
  1. Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
  3. All advertising materials mentioning features or use of this software
     must display the following acknowledgement:
         This product includes software developed by Luke Howard.
  4. The name of the other may not be used to endorse or promote products
     derived from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY LUKE HOWARD ``AS IS'' AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED.  IN NO EVENT SHALL LUKE HOWARD BE LIABLE
  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  SUCH DAMAGE.
 */

#import <Foundation/Foundation.h>

#import "MSMessage.h"

#ifdef NeXT
#import <libc.h>
#import <sys/types.h>
#import <sys/time.h>
#import <sys/stat.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <net/if.h>
#import <arpa/inet.h>
#import <netdb.h>
#import <stdio.h>
#import <fcntl.h>
#import <string.h>
#import <signal.h>
#import <ctype.h>
#import <unistd.h>
#import <pwd.h>
#import <sys/param.h>
#import <errno.h>
#import <stdarg.h>
#endif

#import <msend.h>

#ifdef NeXT
char *strdup(const char *str);
#endif

static char *empty_arg = "";
static id logDelegate = nil;

void mslog(char *message, ...);

void mslog(char *message, ...)
{
	NSString *string;
	va_list ap;

	va_start(ap, message);
	if (errno && errno != ENOENT) {
		string = [[NSString alloc] initWithFormat:[NSString stringWithFormat:@"%s: %s", message, strerror(errno)] arguments:ap];
	} else {
		string = [[NSString alloc] initWithFormat:[NSString stringWithCString:message] arguments:ap];
	}

	va_end(ap);

	if (logDelegate) {
		[logDelegate log:string];
	} else {
		NSLog(string);
	}
	va_end(ap);
	[string release];
}

@interface MSMessage (InternalMethods)
- (BOOL)assembleMessage;
- (BOOL)udpMsg;
- (BOOL)tcpMsg;
@end

@implementation MSMessage
/* vend a new message object */
+ (MSMessage *)message
{
	return [[self class] messageTo:nil];
}

+ (MSMessage *)messageTo:(NSString *)recipient
{
	return [[[self class] alloc] initWithRecipient:recipient];
}

- init
{
	return [self initWithRecipient:nil];
}

- initWithRecipient:(NSString *)recipient
{
	[super init];

	if (recipient != nil)
		[self setRecipient:recipient];

	use_tcp = NO;
	broadcasting = NO;

	messageBody = [[NSMutableString alloc] init];

	retries = 4;
	Sin.sin_family = AF_INET;

	errno = 0;

	return self;
}

+ (void)setLogDelegate:(id <MSLogging>)delegate
{
    [delegate retain];
    [logDelegate release];
    logDelegate = delegate;
}

- (void)log:(NSString *)err
{
	NSLog(err);
}

- (void)setPortToDefault
{
	struct servent *sp;

	sp = getservbyname("message", use_tcp ? "tcp" : "udp" );
	if(sp)
		Sin.sin_port = sp->s_port;
	else
		Sin.sin_port = htons(18); /* from the RFC */
}

- (void)setUseTcp:(BOOL)useTcp
{
	use_tcp=useTcp;
}

- (BOOL)useTcp
{
	return use_tcp;
}

- (void)dealloc
{
	if (_recipient_ptr)
		free(_recipient_ptr);
	[messageBody release];
	[super dealloc];
}

/* accessor methods for the recipient (eg. user@host) */
- (void)setRecipient:(NSString *)aRecipient
{
	char local_name[MAXHOSTNAMELEN];

	_recipient_ptr = strdup([aRecipient cString]);

/* (almost) verbatim from msend.c */
	host = (char *)strchr(_recipient_ptr, '@');

	if (host == NULL)
		host = empty_arg;
	else
		*host++ = '\0';

	term = (char *)strchr(_recipient_ptr, ':');
	if(term == NULL)
		term = empty_arg;
	else
		*term++ = '\0';
	if(!strcmp(term, "all"))        /* external form is "all" */
		strcpy(term, "*");              /* protocol uses "*" */

	user = _recipient_ptr;


	if (host[0] == '\0')
		{
		gethostname(local_name, sizeof(local_name));
		host = strdup(local_name); /* local_name is local to our stack */
		}

}

- (NSString *)host
{
	return [NSString stringWithCString:host];
}

- (NSString *)user
{
	return [NSString stringWithCString:user];
}

- (NSString *)term
{
	return [NSString stringWithCString:term];
}

- (void)setPort:(short)port
{
	Sin.sin_port = htons(port);
}

- (void)setRetries:(int)r
{
	retries = r;
}

- (int)retries
{
	return retries;
}

- (short)port
{
	return ntohs(Sin.sin_port);
}

- (void)setBroadcasting:(BOOL)doBroadcasting
{
	broadcasting = doBroadcasting;
}

- (BOOL)broadcasting
{
	return broadcasting;
}

- (BOOL)assembleMessage
{
	const char		*msg_text = [messageBody cString];

	char		linebuff[256];
	char		*dp, *lp;
	FILE		*sigfile;
	char		*signature;
	char		*homedir;
	struct passwd	*pwd;
	int		buflen;

	buflen = 1024;
	msg = malloc(buflen);
	if (msg == NULL)
		return NO; /* out of memory */

	*msg = 'B'; /* as per RFC */
	msg_len = 1;

	filter(user);
	append_buffer(&msg, &buflen, &msg_len, user, 1);
	filter(term);
	append_buffer(&msg, &buflen, &msg_len, term, 1);

	if (msg_text)
		do_line(&msg, &buflen, &msg_len, msg_text);
	else
		return NO;

	append_buffer(&msg, &buflen, &msg_len, "",  1);
	lp = (char *)getlogin();
	if (lp == NULL || *lp == '\0')
		{
		struct passwd *me;
		me = getpwuid(getuid());
		if (me == NULL)
			return NO; /* memory leak XXX */
		lp = me->pw_name;
		}

	append_buffer(&msg, &buflen, &msg_len, lp, 1);

	dp = (char *)ttyname(2);
	if (dp == NULL)
		append_buffer(&msg, &buflen, &msg_len, "", 1);
	else
		append_buffer(&msg, &buflen, &msg_len, dp, 1);

	sprintf(linebuff, "%ld", time(NULL));
	append_buffer(&msg, &buflen, &msg_len, linebuff, 1);

	homedir = (char *)getenv("HOME");
	if (homedir == NULL)
		{
		pwd = (struct passwd *)getpwnam(lp); /* fixed bug in msend.c */
		homedir = pwd->pw_dir;
		}
	sprintf(linebuff, "%s/.msgsig", homedir);
	signature = "";
	sigfile = fopen(linebuff, "r");
	if (sigfile != NULL)
		{
                linebuff[sizeof(linebuff)-1] = '\0';
                if (fgets(linebuff, sizeof(linebuff)-1, sigfile) != NULL) {
                        if (linebuff[strlen(linebuff)-1] == '\n')
                                linebuff[strlen(linebuff)-1] = '\0';
                        filter(linebuff);
                        signature = linebuff;
                }
                fclose(sigfile);
        }

	append_buffer(&msg, &buflen, &msg_len, signature, 1);

	return YES;
}

- (void)setDebugging:(BOOL)flag
{
	debug=flag;
}

- (BOOL)udpMsg
{
	int r = retries;
	struct sockaddr_in *sp = &Sin;
	int s, delivered = 0;
	struct sockaddr_in SIN;
	fd_set ready;
	struct timeval to;
	int rval, toutwait, i;

	if (debug) NSLog(@"invoked udpMsg(...,%d)",r);

	r = retries; /* local copy */

        if((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		mslog("unable to open socket");
		return NO;
        }

	if (broadcasting) {
		i = 1;
		if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &i, sizeof i) < 0) {
			mslog("unable to configure socket for broadcast");
                    return NO;
		}
		find_broadcast_addresses(s);
	}

	SIN.sin_family = AF_INET;
	SIN.sin_addr.s_addr = htonl(INADDR_ANY);
	SIN.sin_port = htons(0);

	if(bind(s, (struct sockaddr *)&SIN, sizeof SIN) < 0) {
		mslog("unable to set local socket address");
		return NO;
	}

	toutwait = 3; /* starts at 3 secs and backs off by 2 secs every time */

	while(r--) {
		if (debug) NSLog(@"sending message...");
		if (broadcasting) {
                        for (i = 0; i < if_n; i++){
                                if_a[i].sin_port = sp->sin_port;
                                if(debug) NSLog(@"sending message to %s",
                                        inet_ntoa(if_a[i].sin_addr));
                                if(sendto(s, msg, msg_len, 0, (struct sockaddr *)&(if_a[i]),
                                sizeof if_a[i]) < 0) {
					mslog("unable to send message");
                                        return NO;
                                }
                        }
		}
		else {
			if (sendto(s, msg, msg_len, 0, (struct sockaddr *)sp, sizeof(*sp)) < 0) {
				mslog("unable to send message");
				return NO;
			}
		}
		if (r) {
			to.tv_sec = toutwait;
			to.tv_usec = 0;
			FD_ZERO(&ready);
			FD_SET(s, &ready);
			rval = select(20, &ready, (fd_set *)0, (fd_set *)0, &to);
			if (rval < 0)
				NSLog(@"Interrupt");
			if (rval == 1) {
				delivered = 1;
				udp_decode_ack(s);
				break;
			}
			toutwait += 2;
		}
	}
	if (!delivered)
		mslog("Message unacknowledged - may not have been received");

	close(s);
	return YES;

}

- (BOOL)tcpMsg
{
	int s;
	struct sockaddr_in SIN; /* so as not to hide instance var */
	struct sockaddr_in *sp = &Sin;
	char rcvbuf[256];
	int rval;

	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { return NO; }

	SIN.sin_family = AF_INET;
	SIN.sin_addr.s_addr = htonl(INADDR_ANY);
	SIN.sin_port = htons(0);

	if(bind(s, (struct sockaddr *)&SIN, sizeof SIN) < 0) {
		mslog("unable to bind local socket address");
		return NO;
	}

	if(connect(s, (struct sockaddr *)sp, sizeof(*sp)) < 0) {
		mslog("unable to connect to TCP server");
		return NO;
	}

	if(write(s, msg, msg_len) < 0) {
        	(void)close(s);
		mslog("unable to send message");	
		return NO;
	}

	rval = read(s, rcvbuf, sizeof rcvbuf);
	if (rval < 1) {
        	close(s);
		mslog("No reply received");
		return NO;
	}

	rcvbuf[(rval < sizeof(rcvbuf)) ? rval : sizeof(rcvbuf)-1] = 0;
	if (rcvbuf[0] == '+') {
		if (debug) NSLog(@"Message delivered to recipient (%s)\n", rcvbuf+1);
		return YES;
	}

	else if (rcvbuf[0] == '-') {
		mslog("Message wasn't delivered - %s.", rcvbuf+1);
		return NO;
	}

	else {
		mslog("Message wasn't delivered");
		return NO;
	}

	return NO;
}

/* accessor methods for the message itself */
- (void)setMessage:(NSString *)message
{
	[messageBody setString:message];
}

- (void)appendMessage:(NSString *)message
{
	[messageBody appendString:message];
}

- (NSString *)message
{
	return messageBody;
}


- (BOOL)ready
{
	if (host == NULL)
		return NO;
	if (user == NULL)
		return NO;
	if ([messageBody length] == 0)
		return NO;

	return YES;
}

/* post the message */
- (BOOL)post
{
	struct hostent *hp;

	if ([self ready] == NO)
		return NO;

	if (Sin.sin_port == 0)
		[self setPortToDefault];

	if (!broadcasting)
		{
		hp = gethostbyname(host);

		if (hp == NULL)
			{
			mslog("unknown host: %s", host);
			return NO;
			}
		memcpy((char *)&Sin.sin_addr, (char *)hp->h_addr, hp->h_length);
		}

/* assemble the message */
	if ([self assembleMessage] == NO)
		return NO;

	
	if (broadcasting)
		return [self udpMsg];

	if (use_tcp)
		if ([self tcpMsg] == NO)
			return NO;

	[self udpMsg];
	
	return YES;
			
}
@end

#ifdef NeXT
#if NS_TARGET_MINOR == 1
char *strdup(const char *str)
{
	size_t len = strlen(str) + 1;
	char *copy;

	copy = (char *)malloc(len);

	if (copy == NULL) return NULL;

	bcopy(str, copy, len);

	return copy;
}
#endif
#endif

