Example #1
0
/* If connection is lost, mark all js sessions as dead. */
static void markAllDead(void)
{
	int cx;			/* edbrowse context */
	struct ebWindow *w;
	bool killed = false;

	for (cx = 1; cx < MAXSESSION; ++cx) {
		w = sessionList[cx].lw;
		if (!w)
			continue;
		if (w->winobj) {
			w->winobj = 0;
			w->docobj = 0;
			w->jcx = 0;
			killed = true;
		}
		while (w != sessionList[cx].fw) {
			w = w->prev;
			if (w->winobj) {
				w->winobj = 0;
				w->docobj = 0;
				w->jcx = 0;
				killed = true;
			}
		}
	}

	if (killed)
		i_puts(MSG_JSCloseSessions);
}				/* markAllDead */
Example #2
0
void scanMail(void)
{
	int nmsgs, m;

	if (!isInteractive)
		i_printfExit(MSG_FetchNotBackgnd);
	if (!mailDir)
		i_printfExit(MSG_NoMailDir);
	if (chdir(mailDir))
		i_printfExit(MSG_NoDirChange, mailDir);

	if (!umf) {
		umf = allocMem(strlen(mailUnread) + 12);
		sprintf(umf, "%s/", mailUnread);
		umf_end = umf + strlen(umf);
	}

/* How many mail messages? */
	unreadBase = 0;
	unreadStats();
	nmsgs = unreadCount;
	if (!nmsgs) {
		i_puts(MSG_NoMail);
		exit(0);
	}
	i_printf(MSG_MessagesX, nmsgs);

	loadAddressBook();

	for (m = 1; m <= nmsgs; ++m) {
		nzFree(lastMailText);
		lastMailText = 0;
/* Now grab the entire message */
		unreadStats();
		sprintf(umf_end, "%d", unreadMin);
		if (!fileIntoMemory(umf, &mailstring, &mailstring_l))
			showErrorAbort();
		unreadBase = unreadMin;

		if (presentMail() == 1)
			unlink(umf);
	}			/* loop over mail messages */

	exit(0);
}				/* scanMail */
Example #3
0
/* Read some data from the js process.
 * Close things down if there is any trouble from the read.
 * Returns 0 for ok or -1 for bad read. */
static int readFromJS(void *data_p, int n)
{
	unsigned char *bytes_p = (unsigned char *)data_p;
	int rc;
	if (n == 0)
		return 0;
	while (n > 0) {
		rc = read(pipe_in[0], bytes_p, n);
		debugPrint(7, "js read %d", rc);
		if (rc <= 0) {
/* Oops - can't read from the process any more */
			i_puts(MSG_JSEngineRW);
			js_kill();
			markAllDead();
			return -1;
		}
		n -= rc;
		bytes_p += rc;
	}
	return 0;
}				/* readFromJS */
Example #4
0
bool sendMailCurrent(int sm_account, bool dosig)
{
	const char *reclist[MAXRECAT + 1];
	char *recmem;
	const char *atlist[MAXRECAT + 1];
	char *atmem;
	char *s, *t;
	char cxbuf[4];
	int lr, la, ln;
	char *refline = 0;
	int nrec = 0, nat = 0, nalt = 0;
	int account = localAccount;
	int j;
	bool rc = false;
	bool subj = false;

	if (cw->browseMode) {
		setError(MSG_MailBrowse);
		return false;
	}
	if (cw->sqlMode) {
		setError(MSG_MailDB);
		return false;
	}
	if (cw->dirMode) {
		setError(MSG_MailDir);
		return false;
	}
	if (cw->binMode) {
		setError(MSG_MailBinary2);
		return false;
	}
	if (!cw->dol) {
		setError(MSG_MailEmpty);
		return false;
	}

	if (!validAccount(account))
		return false;

	recmem = initString(&lr);
	atmem = initString(&la);

/* Gather recipients and attachments, until we reach subject: */
	for (ln = 1; ln <= cw->dol; ++ln) {
		char *line = (char *)fetchLine(ln, -1);

		if (memEqualCI(line, "to:", 3) ||
		    memEqualCI(line, "mailto:", 7) ||
		    memEqualCI(line, "cc:", 3) ||
		    memEqualCI(line, "bcc:", 4) ||
		    memEqualCI(line, "reply to:", 9) ||
		    memEqualCI(line, "reply to ", 9)) {
			char cc = 0;
			if (toupper(line[0]) == 'C')
				cc = '^';
			if (toupper(line[0]) == 'B')
				cc = '?';
			if (toupper(line[0]) == 'R')
				line += 9;
			else
				line = strchr(line, ':') + 1;
			while (*line == ' ' || *line == '\t')
				++line;
			if (*line == '\n') {
				setError(MSG_RecipNone2, ln);
				goto done;
			}
			if (nrec == MAXRECAT) {
				setError(MSG_RecipMany, MAXRECAT);
				goto done;
			}
			++nrec;
			for (t = line; *t != '\n'; ++t) ;
			if (cc) {
				if (!lr) {
					setError(MSG_MailFirstCC);
					goto done;
				}
				stringAndChar(&recmem, &lr, cc);
			}
			stringAndBytes(&recmem, &lr, line, t + 1 - line);
			continue;
		}

		if (memEqualCI(line, "attach:", 7)
		    || memEqualCI(line, "alt:", 4)) {
			if (toupper(line[1]) == 'T')
				line += 7;
			else
				line += 4, ++nalt;
			while (*line == ' ' || *line == '\t')
				++line;
			if (*line == '\n') {
				setError(MSG_AttLineX, ln);
				goto done;
			}
			if (nat == MAXRECAT) {
				setError(MSG_RecipMany, MAXRECAT);
				goto done;
			}
			++nat;
			for (t = line; *t != '\n'; ++t) ;
			stringAndBytes(&atmem, &la, line, t + 1 - line);
			continue;
		}

		if (memEqualCI(line, "account:", 8)) {
			line += 8;
			while (*line == ' ' || *line == '\t')
				++line;
			if (!isdigitByte(*line) ||
			    (account = strtol(line, &line, 10)) == 0 ||
			    account > maxAccount || *line != '\n') {
				setError(MSG_MailAccountBadLineX, ln);
				goto done;
			}
			continue;
		}

		if (memEqualCI(line, "references:", 11)) {
			if (!refline)
				refline = line;
			continue;
		}

		if (memEqualCI(line, "subject:", 8)) {
			while (*line == ' ' || *line == '\t')
				++line;
			subj = true;
		}

		break;
	}			/* loop over lines */

	if (sm_account)
		account = sm_account;
	if (!subj) {
		setError(((ln > cw->dol) + MSG_MailFirstLine), ln);
		goto done;
	}

	if (nrec == 0) {
		setError(MSG_RecipNone3);
		goto done;
	}

	for (s = recmem, j = 0; *s; s = t + 1, ++j) {
		t = strchr(s, '\n');
		*t = 0;
		reclist[j] = s;
	}
	reclist[j] = 0;
	for (s = atmem, j = 0; *s; s = t + 1, ++j) {
		t = strchr(s, '\n');
		*t = 0;
		atlist[j] = s;
	}
	atlist[j] = 0;

	sprintf(cxbuf, "%d", context);
	rc = sendMail(account, reclist, cxbuf, ln, atlist, refline, nalt,
		      dosig);

done:
	nzFree(recmem);
	nzFree(atmem);
	if (!rc && intFlag)
		setError(MSG_Interrupted);
	if (rc)
		i_puts(MSG_OK);
	return rc;
}				/* sendMailCurrent */
Example #5
0
/* Return 0 for ok, 1 to delete the mail, -1 to stop.
 * stop is only meaningful for imap. */
static int presentMail(void)
{
	int j, k;
	const char *redirect = NULL;	/* send mail elsewhere */
	char key = 0;
	const char *atname = NULL;	/* name of file or attachment */
	bool delflag = false;	/* delete this mail */
	bool scanat = false;	/* scan for attachments */
	int displine;
	int stashNumber = -1;

/* clear things out from the last message */
	if (lastMailInfo)
		freeMailInfo(lastMailInfo);
	lastMailInfo = 0;

	if (sessionList[1].lw)
		cxQuit(1, 2);
	cs = 0;
	cxSwitch(1, false);

	iuReformat(mailstring, mailstring_l, &mailu8, &mailu8_l);
	if (mailu8) {
		if (!addTextToBuffer((pst) mailu8, mailu8_l, 0, false))
			showErrorAbort();
	} else {
		if (!addTextToBuffer((pst) mailstring, mailstring_l, 0, false))
			showErrorAbort();
	}

	browseCurrentBuffer();

	if (!passMail) {
		redirect = mailRedirect(lastMailInfo->to,
					lastMailInfo->from,
					lastMailInfo->reply,
					lastMailInfo->subject);
	}

	if (redirect) {
		if (!isimap) {
			delflag = true;
			key = 'w';
			if (*redirect == '-')
				++redirect, key = 'u';
			if (stringEqual(redirect, "x"))
				i_puts(MSG_Junk);
			else
				printf("> %s\n", redirect);
		} else {
			if (*redirect == '-')
				++redirect;
			if (stringEqual(redirect, "x"))
				redirect = NULL;
		}
	}

/* display the next page of mail and get a command from the keyboard */
	displine = 1;
paging:
	if (!delflag) {		/* show next page */
		if (displine <= cw->dol) {
			for (j = 0; j < 20 && displine <= cw->dol;
			     ++j, ++displine) {
				char *showline = (char *)fetchLine(displine, 1);
				k = pstLength((pst) showline);
				showline[--k] = 0;
				printf("%s\n", showline);
				nzFree(showline);
			}
		}
	}

/* get key command from user */
key_command:
	if (delflag)
		goto writeMail;

/* interactive prompt depends on whether there is more text or not */
	printf("%c ", displine > cw->dol ? '?' : '*');
	fflush(stdout);
	key = getLetter((isimap ? "q? nwWuUasd" : "q? nwud"));
	printf("\b\b\b");
	fflush(stdout);

	switch (key) {
	case 'q':
		i_puts(MSG_Quit);
		exit(0);

	case 'n':
		i_puts(MSG_Next);
		goto afterinput;

	case 's':
		i_puts(MSG_Stop);
		goto afterinput;

	case 'd':
		i_puts(MSG_Delete);
		delflag = true;
		goto afterinput;

	case ' ':
		if (displine > cw->dol)
			i_puts(MSG_EndMessage);
		goto paging;

	case '?':
		i_puts(isimap ? MSG_ImapReadHelp : MSG_MailHelp);
		goto key_command;

	case 'a':
		key = 'w';	/* this will scan attachments */
		scanat = true;

	case 'w':
	case 'W':
	case 'u':
	case 'U':
		break;

	default:
		i_puts(MSG_NYI);
		goto key_command;
	}			/* switch */

/* At this point we're saving the mail somewhere. */
writeMail:
	if (!isimap || isupper(key))
		delflag = true;
	atname = 0;
	if (!isimap)
		atname = redirect;

	if (scanat)
		goto attachOnly;

saveMail:
	if (!atname)
		atname = getFileName(MSG_FileName, redirect, false, false);
	if (stringEqual(atname, "x"))
		goto afterinput;

	char exists = fileTypeByName(atname, false);
	int fsize;		/* file size */
	int fh = open(atname, O_WRONLY | O_TEXT | O_CREAT | O_APPEND, 0666);
	if (fh < 0) {
		i_printf(MSG_NoCreate, atname);
		goto saveMail;
	}
	if (exists)
		write(fh,
		      "======================================================================\n",
		      71);
	if (key == 'u') {
		if (write(fh, mailstring, mailstring_l) < mailstring_l) {
badsave:
			i_printf(MSG_NoWrite, atname);
			close(fh);
			goto saveMail;
		}
		close(fh);
		fsize = mailstring_l;
	} else {

/* key = w, write the file - if pop then save the original unformatted */
		if (!isimap && mailStash) {
			char *rmf;	/* raw mail file */
			int rmfh;	/* file handle to same */
/* I want a fairly easy filename, in case I want to go look at the original.
* Not a 30 character message ID that I am forced to cut&paste.
* 4 or 5 digits would be nice.
* So the filename looks like /home/foo/.Trash/rawmail/36921
* I pick the digits randomly.
* Please don't accumulate 100,000 emails before you empty your trash.
* It's good to have a cron job empty the trash early Sunday morning.
*/

			k = strlen(mailStash);
			rmf = allocMem(k + 12);
/* Try 20 times, then give up. */
			for (j = 0; j < 20; ++j) {
				int rn = rand() % 100000;	/* random number */
				sprintf(rmf, "%s/%05d", mailStash, rn);
				if (fileTypeByName(rmf, false))
					continue;
/* dump the original mail into the file */
				rmfh =
				    open(rmf,
					 O_WRONLY | O_TEXT | O_CREAT | O_APPEND,
					 0666);
				if (rmfh < 0)
					break;
				if (write(rmfh, mailstring, mailstring_l) <
				    mailstring_l) {
					close(rmfh);
					unlink(rmf);
					break;
				}
				close(rmfh);
/* written successfully, remember the stash number */
				stashNumber = rn;
				break;
			}
		}

		fsize = 0;
		for (j = 1; j <= cw->dol; ++j) {
			char *showline = (char *)fetchLine(j,
							   1);
			int len = pstLength((pst)
					    showline);
			if (write(fh, showline, len) < len)
				goto badsave;
			nzFree(showline);
			fsize += len;
		}		/* loop over lines */

		if (stashNumber >= 0) {
			char addstash[60];
			sprintf(addstash, "\nUnformatted %05d\n", stashNumber);
			k = strlen(addstash);
			if (write(fh, addstash, k) < k)
				goto badsave;
			fsize += k;
		}

		close(fh);

attachOnly:

		if (nattach)
			writeAttachments(lastMailInfo);
		else if (scanat)
			i_puts(MSG_NoAttachments);
	}			/* unformat or format */

	if (scanat)
		goto afterinput;
/* print "mail saved" message */
	i_printf(MSG_MailSaved, fsize);
	if (exists)
		i_printf(MSG_Appended);
	nl();

afterinput:
	nzFree(mailstring);
	mailstring = 0;
	nzFree(mailu8);
	mailu8 = 0;

	if (delflag)
		return 1;
	if (key == 's')
		return -1;
	return 0;
}				/* presentMail */
Example #6
0
/* Start the js process. */
static void js_start(void)
{
	int pid;
	char *jsprog;

#if defined(DOSLIKE) && !defined(HAVE_PTHREAD_H)
	debugPrint(5,
		   "no pthread, so no communication channels for javascript");
	allowJS = false;
	return;
#endif // defined(DOSLIKE) && !defined(HAVE_PTHREAD_H)

#ifndef DOSLIKE
/* doesn't hurt to do this more than once */
	signal(SIGPIPE, SIG_IGN);
#endif // !DOSLIKE

	debugPrint(5, "setting of communication channels for javascript");

	if (PIPE(pipe_in)) {
		i_puts(MSG_JSEnginePipe);
		allowJS = false;
		return;
	}

	if (PIPE(pipe_out)) {
		i_puts(MSG_JSEnginePipe);
		allowJS = false;
		close(pipe_in[0]);
		close(pipe_in[1]);
		return;
	}
#if defined(DOSLIKE)
#if defined(HAVE_PTHREAD_H)
	/* windows implementation of fork() using pthreads */
	pid = pthread_create(&tid, NULL, child_proc, 0);
#else // !HAVE_PTHREAD_h
	pid = 1;
#endif // HAVE_PTHREAD_H y/n
	if (pid) {
		i_puts(MSG_JSEngineFork);
		allowJS = false;
		close(pipe_in[0]);
		close(pipe_in[1]);
		close(pipe_out[0]);
		close(pipe_out[1]);
		return;
	}
	js_pid = 1;
#else // !(defined(DOSLIKE) && defined(HAVE_PTHREAD_H)
	pid = fork();
	if (pid < 0) {
		i_puts(MSG_JSEngineFork);
		allowJS = false;
		close(pipe_in[0]);
		close(pipe_in[1]);
		close(pipe_out[0]);
		close(pipe_out[1]);
		return;
	}

	if (pid) {		/* parent */
		js_pid = pid;
		close(pipe_in[1]);
		close(pipe_out[0]);
		return;
	}

/* child here, exec the back end js process */
	close(pipe_in[0]);
	close(pipe_out[1]);
	sprintf(arg1, "%d", pipe_out[0]);
	sprintf(arg2, "%d", pipe_in[1]);
	debugPrint(5, "spawning edbrowse-js %s %s", arg1, arg2);
	execlp(progname, "edbrowse", "--mode", "js", arg1, arg2, NULL);

/* oops, process did not exec */
/* write a message from this child, saying js would not exec */
	head.magic = EJ_MAGIC;
	head.highstat = EJ_HIGH_PROC_FAIL;
	head.lowstat = EJ_LOW_EXEC;
	write(pipe_in[1], &head, sizeof(head));
	exit(90);
#endif // defined(DOSLIKE) && defined(HAVE_PTHREAD_H) y/n
}				/* js_start */
Example #7
0
/* Read the entire message from js, then take action.
 * Thus messages will remain in sync. */
static int readMessage(void)
{
	int l;
	char *msg;		/* error message from js */

	if (readFromJS(&head, sizeof(head)) < 0)
		return -1;	/* read failed */

	if (head.magic != EJ_MAGIC) {
/* this should never happen */
		js_kill();
		i_puts(MSG_JSEngineSync);
		markAllDead();
		return -1;
	}

	if (head.highstat >= EJ_HIGH_HEAP_FAIL) {
		js_kill();
/* perhaps a helpful message, before we close down js sessions */
		if (head.highstat == EJ_HIGH_PROC_FAIL)
			allowJS = false;
		if (head.lowstat == EJ_LOW_EXEC)
			i_puts(MSG_JSEngineExec);
		if (head.lowstat == EJ_LOW_MEMORY)
			i_puts(MSG_JavaMemError);
		if (head.lowstat == EJ_LOW_RUNTIME)
			i_puts(MSG_JSEngineRun);
		if (head.lowstat == EJ_LOW_SYNC)
			i_puts(MSG_JSEngineSync);
		markAllDead();
		return -1;
	}

	if (head.side) {
		effects = allocMem(head.side + 1);
		if (readFromJS(effects, head.side) < 0) {
			free(effects);
			effects = 0;
			return -1;
		}
		effects[head.side] = 0;
// because debugPrint always puts on a newline
		effects[head.side - 1] = 0;
		debugPrint(4, "< side effects\n%s", effects);
		processEffects();
	}

/* next grab the error message, if there is one */
	l = head.msglen;
	if (l) {
		msg = allocMem(l + 1);
		if (readFromJS(msg, l)) {
			free(msg);
			return -1;
		}
		msg[l] = 0;
		if (debugLevel >= 3) {
/* print message, this will be in English, and mostly for our debugging */
			if (jsSourceFile) {
				if (debugFile)
					fprintf(debugFile, "%s line %d: ",
						jsSourceFile, head.lineno);
				else
					printf("%s line %d: ",
					       jsSourceFile, head.lineno);
			}
			debugPrint(3, "%s", msg);
		}
		free(msg);
	}

/*  Read in the requested property, if there is one.
 * The calling function must handle the property. */
	l = head.proplength;
	proptype = head.proptype;
	if (l) {
		propval = allocMem(l + 1);
		if (readFromJS(propval, l)) {
			free(propval);
			propval = 0;
			return -1;
		}
		propval[l] = 0;
	}

/* stop at the first js error when debugging */
	if (head.msglen && debugLevel >= 5) {
		head.highstat = EJ_HIGH_CX_FAIL;
		head.lowstat = 0;
		debugPrint(5, "js abort due to error while debugging");
	}

	if (head.highstat == EJ_HIGH_CX_FAIL) {
		if (head.lowstat == EJ_LOW_VARS)
			i_puts(MSG_JSEngineVars);
		if (head.lowstat == EJ_LOW_CX)
			i_puts(MSG_JavaContextError);
		if (head.lowstat == EJ_LOW_WIN)
			i_puts(MSG_JavaWindowError);
		if (head.lowstat == EJ_LOW_DOC)
			i_puts(MSG_JavaObjError);
		if (head.lowstat == EJ_LOW_CLOSE)
			i_puts(MSG_PageDone);
		else
			i_puts(MSG_JSSessionFail);
		freeJavaContext(cw);
/* should I free and zero the property at this point? */
	}

	return 0;
}				/* readMessage */
Example #8
0
static void
retsFromOdbc(void)
{
    void *q, *q1;
    int i, l;
    int fd, flags;
    bool yearfirst, indata = false;
    long dt;			/* temporarily hold date or time */
    char *s;
    short c_type;		/* C data type */
    long input_length, output_length;
    char tbuf[20];		/* temp buf, for dates and times */
    double fmoney;		/* float version of money */
    int blobcount = 0;
    bool fbc = fetchBlobColumns;

    /* no blobs unless proven otherwise */
    rv_blobLoc = 0;
    rv_blobSize = nullint;

    if(!rv_numRets)
	errorPrint("@calling retsFromOdbc() with no returns pending");

    stmt_text = "retsFromOdbc";
    debugStatement();

/* count the blobs */
    if(fbc)
	for(i = 0; i < rv_numRets; ++i)
	    if(rv_type[i] == 'B' || rv_type[i] == 'T')
		++blobcount;
    if(blobcount > 1) {
	i_puts(MSG_DBManyBlobs);
	fbc = false;
    }

    for(i = 0; i < rv_numRets; ++i) {
	if(!indata) {
	    q = va_arg(sqlargs, void *);
	    if(!q) {
		if(i)
		    break;
		indata = true;
	    }
	}
	if(indata) {
	    if(rv_type[i] == 'S') {
		q = retstring[i];
		rv_data[i].ptr = q;
	    } else
		q = rv_data + i;
	}
	if((int)q < 1000 && (int)q > -1000)
	    errorPrint("2retsFromOdbc, pointer too close to 0");
	q1 = q;
	tbuf[0] = 0;
	output_length = 0;

	switch (rv_type[i]) {
	case 'S':
	    c_type = SQL_C_CHAR;
	    input_length = STRINGLEN + 1;
	    *(char *)q = 0;	/* null */
	    break;

	case 'C':
	    c_type = SQL_C_CHAR;
	    input_length = 2;
	    *(char *)q = 0;	/* null */
	    q1 = tbuf;
	    break;

	case 'F':
	    c_type = SQL_C_DOUBLE;
	    input_length = 8;
	    *(double *)q = nullfloat;	/* null */
	    break;

	case 'N':
	    c_type = SQL_C_SLONG;
	    input_length = 4;
	    *(long *)q = nullint;	/* null */
	    break;

	case 'M':
	    c_type = SQL_C_DOUBLE;
	    input_length = 8;
	    fmoney = nullfloat;
	    q1 = &fmoney;
	    break;

	case 'D':
	    c_type = SQL_C_CHAR;
	    input_length = 11;
	    q1 = tbuf;
	    break;

	case 'I':
	    c_type = SQL_C_CHAR;
	    input_length = 10;
	    q1 = tbuf;
	    break;

	case 'B':
	case 'T':
	    c_type = SQL_C_BINARY;
	    input_length = sizeof (blobbuf);
	    q1 = blobbuf;
	    *(long *)q = nullint;
	    break;

	default:
	    errorPrint("@retsFromOdbc, rv_type[%d] = %c", i, rv_type[i]);
	}			/* switch */

	if(everything_null || c_type == SQL_C_BINARY && !fbc) {
	    rc = SQL_SUCCESS;
	    output_length = SQL_NULL_DATA;
	} else {
	    rc = SQLGetData(hstmt, (ushort) (i + 1),
	       c_type, q1, input_length, &output_length);
	    /* we'll deal with blob overflow later */
	    if(rc == SQL_SUCCESS_WITH_INFO && c_type == SQL_C_BINARY &&
	       output_length > sizeof (blobbuf))
		rc = SQL_SUCCESS;
	    if(errorTrap(0))
		break;
	    if(output_length == SQL_NO_TOTAL)
		errorPrint
		   ("@retsFromOdbc cannot get size of data for column %d",
		   i + 1);
	}

	/* Postprocess the return values. */
	/* For instance, turn string dates into our own 4-byte format. */
	s = tbuf;
	clipString(s);
	switch (rv_type[i]) {
	case 'C':
	    *(char *)q = tbuf[0];
	    break;

	case 'S':
	    clipString(q);
	    break;

	case 'D':
	    yearfirst = false;
	    if(s[4] == '-')
		yearfirst = true;
	    dt = stringDate(s, yearfirst);
	    if(dt < 0)
		errorPrint("@database holds invalid date %s", s);
	    *(long *)q = dt;
	    break;

	case 'I':
	    /* thanks to stringTime(), this works
	       for either hh:mm or hh:mm:ss */
	    if(s[0] == 0)
		*(long *)q = nullint;
	    else {
		/* Note that Informix introduces a leading space,
		   how about ODBC? */
		leftClipString(s);
		if(s[1] == ':')
		    shiftRight(s, '0');
		dt = stringTime(s);
		if(dt < 0)
		    errorPrint("@database holds invalid time %s", s);
		*(long *)q = dt;
	    }
	    break;

	case 'M':
	    if(fmoney == nullfloat)
		dt = nullint;
	    else
		dt = fmoney * 100.0 + 0.5;
	    *(long *)q = dt;
	    break;

	case 'B':
	case 'T':
	    if(output_length == SQL_NULL_DATA)
		break;
	    /* note, 0 length blob is treated as a null blob */
	    if(output_length == 0)
		break;
	    /* the size of the blob is returned, in an int. */
	    *(long *)q = output_length;
	    rv_blobSize = output_length;

	    if(isnullstring(rv_blobFile)) {
		/* the blob is always allocated; you have to free it! */
		/* SQL doesn't null terminate its text blobs, but we do. */
		rv_blobLoc = allocMem(output_length + 1);
		l = output_length;
		if(l > sizeof (blobbuf))
		    l = sizeof (blobbuf);
		memcpy(rv_blobLoc, blobbuf, l);
		if(l < output_length) {	/* more to do */
		    long waste;
		    rc = SQLGetData(hstmt, (ushort) (i + 1),
		       c_type, (char *)rv_blobLoc + l,
		       output_length - l, &waste);
		    if(rc) {
			nzFree(rv_blobLoc);
			rv_blobLoc = 0;
			*(long *)q = nullint;
			errorTrap(0);
			goto breakloop;
		    }		/* error getting rest of blob */
		}		/* blob is larger than the buffer */
		if(rv_type[i] == 'T')	/* null terminate */
		    ((char *)rv_blobLoc)[output_length] = 0;
		break;
	    }

	    /* blob in memory */
	    /* at this point the blob is being dumped into a file */
	    flags = O_WRONLY | O_BINARY | O_CREAT | O_TRUNC;
	    if(rv_blobAppend)
		flags = O_WRONLY | O_BINARY | O_CREAT | O_APPEND;
	    fd = eopen(rv_blobFile, flags, 0666);
	    rc = SQL_SUCCESS;
	    while(true) {
		int outbytes;
		l = output_length;
		if(l > sizeof (blobbuf))
		    l = sizeof (blobbuf);
		outbytes = write(fd, blobbuf, l);
		if(outbytes < l) {
		    close(fd);
		    errorPrint("2cannot write to file %s, errno %d",
		       rv_blobFile, errno);
		}
		if(l == output_length)
		    break;

		/* get the next chunk from ODBC */
		rc = SQLGetData(hstmt, (ushort) (i + 1),
		   c_type, q1, input_length, &output_length);
		if(rc == SQL_SUCCESS_WITH_INFO && output_length > input_length)
		    rc = SQL_SUCCESS;	/* data truncation error */
	    }

	    close(fd);
	    errorTrap(0);
	    break;

	}			/* switch */

    }				/* loop over returned elements */