diff --git a/src/sbbs3/services.c b/src/sbbs3/services.c
index 9588390afd5541e1c016e639a8e8865d051cdd3b..399ca9f0bdecc48e4ea18579b90bb254623cbe04 100644
--- a/src/sbbs3/services.c
+++ b/src/sbbs3/services.c
@@ -301,8 +301,14 @@ js_read(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 static JSBool
 js_readln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
+	char		ch;
 	char*		buf;
+	int			i;
 	int32		len=512;
+	BOOL		rd;
+	time_t		start;
+	int32		timeout=30;	/* seconds */
+	JSString*	str;
 	service_client_t* client;
 
 	if((client=(service_client_t*)JS_GetContextPrivate(cx))==NULL)
@@ -310,15 +316,48 @@ js_readln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 
 	if(argc)
 		JS_ValueToInt32(cx,argv[0],&len);
-	
-	if((buf=alloca(len))==NULL)
-		return(JS_TRUE);
 
-	len=recv(client->socket,buf,len,0);	/* Need to switch to sockreadline */
+	if((buf=(char*)alloca(len+1))==NULL) {
+		JS_ReportError(cx,"Error allocating %u bytes",len+1);
+		return(JS_FALSE);
+	}
+
+	if(argc>1)
+		JS_ValueToInt32(cx,argv[1],(int32*)&timeout);
 
-	if(len>0)
-		*rval = STRING_TO_JSVAL(JS_NewStringCopyN(cx,buf,len));
+	start=time(NULL);
+	for(i=0;i<len;) {
+
+		if(!socket_check(client->socket,&rd,NULL,1000))
+			break;		/* disconnected */
+
+		if(!rd) {
+			if(time(NULL)-start>timeout) {
+				*rval = JSVAL_NULL;
+				return(JS_TRUE);	/* time-out */
+			}
+			continue;	/* no data */
+		}
+
+		if(recv(client->socket, &ch, 1, 0)!=1)
+			break;
+
+		if(ch=='\n' /* && i>=1 */) /* Mar-9-2003: terminate on sole LF */
+			break;
+
+		buf[i++]=ch;
+	}
+	if(i>0 && buf[i-1]=='\r')
+		buf[i-1]=0;
+	else
+		buf[i]=0;
+
+	str = JS_NewStringCopyZ(cx, buf);
+	if(str==NULL)
+		return(JS_FALSE);
 
+	*rval = STRING_TO_JSVAL(str);
+		
 	return(JS_TRUE);
 }