1 #ident "Kalopa: $Id: ofxread.c,v 1.6 2008/10/15 15:11:13 dtynan Exp $"
2 
3 /*
4  * $Id: ofxread.c,v 1.6 2008/10/15 15:11:13 dtynan Exp $
5  *
6  * Copyright (c) 2005, Kalopa Research Limited.  All rights reserved.
7  * This is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published
9  * by the Free Software Foundation; either version 2, or (at your
10  * option) any later version.
11  *
12  * It is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
15  * License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this product; see the file COPYING.  If not, write to
19  * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
20  * USA.
21  *
22  * THIS SOFTWARE IS PROVIDED BY KALOPA RESEARCH LIMITED "AS IS" AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
24  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25  * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL KALOPA
26  * RESEARCH LIMITED BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
29  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
33  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34  *
35  * ABSTRACT
36  *
37  * $Log: ofxread.c,v $
38  * Revision 1.6  2008/10/15 15:11:13  dtynan
39  * Mammoth change to convert from old-style database configuration to new
40  * schema where an RC file defines the database access.
41  *
42  * Revision 1.5  2008/10/13 20:59:37  dtynan
43  * Extensive changes (part 1) for change to database configuration.
44  *
45  * Revision 1.4  2008/01/15 18:10:43  dtynan
46  * Changed to use new company name and copyright, also fixed a few old
47  * files with the wrong license.
48  *
49  * Revision 1.3  2006/07/31 10:17:51  dtynan
50  * Massive changeover to a more Ruby/Rails primary key naming convention.
51  *
52  * Revision 1.2  2005/03/22 09:41:20  dtynan
53  * Extensive upgrade to make sure the copyright block was
54  * consistent and GPL'ed.
55  *
56  * Revision 1.1  2005/03/07 16:51:13  dtynan
57  * New utility to parse an OFX file and populate the database.
58  */
59 
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <unistd.h>
63 #include <syslog.h>
64 #include <string.h>
65 #include <time.h>
66 #include <ctype.h>
67 
68 #include "beanie.h"
69 #include "hacks.h"
70 
71 #define PROGNAME	"ofxread"
72 
73 #define OFXHDR_HEADER		0
74 #define OFXHDR_DTYPE		1
75 #define OFXHDR_VERSION		2
76 #define OFXHDR_SECURITY		3
77 #define OFXHDR_ENCODE		4
78 #define OFXHDR_CHARSET		5
79 #define OFXHDR_COMPR		6
80 #define OFXHDR_OLDUID		7
81 #define OFXHDR_NEWUID		8
82 
83 #define OFXBODY_OFX		9
84 #define OFXBODY_SIGNON		10
85 #define OFXBODY_SONRS		11
86 #define OFXBODY_STATUS		12
87 #define OFXBODY_CCMSG		13
88 #define OFXBODY_CCTNRS		14
89 #define OFXBODY_CCSTMT		15
90 #define OFXBODY_CCFROM		16
91 #define OFXBODY_BANKTL		17
92 #define OFXBODY_STMNT		18
93 #define OFXBODY_LEDGER		19
94 
95 #define OFXTAG_ACCTID		20
96 #define OFXTAG_BALAMT		21
97 #define OFXTAG_CODE		22
98 #define OFXTAG_CURDEF		23
99 #define OFXTAG_DTACCTUP		24
100 #define OFXTAG_DTASOF		25
101 #define OFXTAG_DTEND		26
102 #define OFXTAG_DTPOSTED		27
103 #define OFXTAG_DTSERVER		28
104 #define OFXTAG_DTSTART		29
105 #define OFXTAG_DTUSER		30
106 #define OFXTAG_FITID		31
107 #define OFXTAG_LANGUAGE		32
108 #define OFXTAG_NAME		33
109 #define OFXTAG_SEVERITY		34
110 #define OFXTAG_TRNAMT		35
111 #define OFXTAG_TRNTYPE		36
112 #define OFXTAG_TRNUID		37
113 
114 /*
115  * Header types...
116  */
117 struct	header	{
118 	char	*name;
119 	int	type;
120 } headers[] = {
121 	{"ACCTID",		OFXTAG_ACCTID},
122 	{"BALAMT",		OFXTAG_BALAMT},
123 	{"BANKTRANLIST",	OFXBODY_BANKTL},
124 	{"CCACCTFROM",		OFXBODY_CCFROM},
125 	{"CCSTMTRS",		OFXBODY_CCSTMT},
126 	{"CCSTMTTRNRS",		OFXBODY_CCTNRS},
127 	{"CHARSET",		OFXHDR_CHARSET},
128 	{"CODE",		OFXTAG_CODE},
129 	{"COMPRESSION",		OFXHDR_COMPR},
130 	{"CREDITCARDMSGSRSV1",	OFXBODY_CCMSG},
131 	{"CURDEF",		OFXTAG_CURDEF},
132 	{"DATA",		OFXHDR_DTYPE},
133 	{"DTACCTUP",		OFXTAG_DTACCTUP},
134 	{"DTASOF",		OFXTAG_DTASOF},
135 	{"DTEND",		OFXTAG_DTEND},
136 	{"DTPOSTED",		OFXTAG_DTPOSTED},
137 	{"DTSERVER",		OFXTAG_DTSERVER},
138 	{"DTSTART",		OFXTAG_DTSTART},
139 	{"DTUSER",		OFXTAG_DTUSER},
140 	{"ENCODING",		OFXHDR_ENCODE},
141 	{"FITID",		OFXTAG_FITID},
142 	{"LANGUAGE",		OFXTAG_LANGUAGE},
143 	{"LEDGERBAL",		OFXBODY_LEDGER},
144 	{"NAME",		OFXTAG_NAME},
145 	{"NEWFILEUID",		OFXHDR_NEWUID},
146 	{"OFX",			OFXBODY_OFX},
147 	{"OFXHEADER",		OFXHDR_HEADER},
148 	{"OLDFILEUID",		OFXHDR_OLDUID},
149 	{"SECURITY",		OFXHDR_SECURITY},
150 	{"SEVERITY",		OFXTAG_SEVERITY},
151 	{"SIGNONMSGSRSV1",	OFXBODY_SIGNON},
152 	{"SONRS",		OFXBODY_SONRS},
153 	{"STATUS",		OFXBODY_STATUS},
154 	{"STMTTRN",		OFXBODY_STMNT},
155 	{"TRNAMT",		OFXTAG_TRNAMT},
156 	{"TRNTYPE",		OFXTAG_TRNTYPE},
157 	{"TRNUID",		OFXTAG_TRNUID},
158 	{"VERSION",		OFXHDR_VERSION},
159 	{NULL,			0}
160 };
161 
162 /*
163  *
164  */
165 struct	ofxhdr	{
166 	int	headerid;
167 	int	dtype;
168 	int	version;
169 	int	security;
170 	int	encoding;
171 	int	charset;
172 	int	compression;
173 	int	oldfileuid;
174 	int	newfileuid;
175 };
176 
177 int		lineno;
178 int		nerrors;
179 int		verbose;
180 int		nomacct;
181 char		*fname;
182 
183 struct	db_creditcard	*ccp = NULL;
184 
185 /*
186  *
187  */
188 int	handlestate(FILE *, int, int);
189 void	findaccount(char *);
190 void	error(char *);
191 void	usage();
192 
193 /*
194  *
195  */
196 int
main(int argc,char * argv[])197 main(int argc, char *argv[])
198 {
199 	int i;
200 	char *cname;
201 	struct db_company *comp;
202 	FILE *fp;
203 
204 	cname = NULL;
205 	opterr = verbose = 0;
206 	nomacct = 0;
207 	while ((i = getopt(argc, argv, "c:qv")) != EOF) {
208 		switch (i) {
209 		case 'c':
210 			cname = optarg;
211 			break;
212 
213 		case 'v':
214 			verbose = 1;
215 			break;
216 
217 		default:
218 			usage();
219 		}
220 	}
221 	/*
222 	 * Find the company that we're processing...
223 	 */
224 	if (loadconfig(cname) < 0)
225 		berror(PROGNAME, "cannot load configuration file");
226 	if ((argc - optind) < 1)
227 		usage();
228 	while (optind < argc) {
229 		lineno = nerrors = 0;
230 		fname = argv[optind++];
231 		if ((fp = fopen(fname, "r")) == NULL) {
232 			fprintf(stderr, "?ofxload: ");
233 			perror(fname);
234 		} else {
235 			handlestate(fp, 0, 0);
236 			fclose(fp);
237 		}
238 	}
239 	exit(0);
240 }
241 
242 /*
243  *
244  */
245 int
handlestate(FILE * fp,int indent,int type)246 handlestate(FILE *fp, int indent, int type)
247 {
248 	int i, isend;
249 	char *cp, *xp, input[512];
250 	struct header *hp;
251 	dbow_conn *dbp;
252 
253 	while (fgets(input, sizeof(input), fp) != NULL) {
254 		lineno++;
255 		if ((cp = strpbrk(input, "\n\r")) != NULL)
256 			*cp = '\0';
257 		if (indent == 0) {
258 			if (strcmp(input, "<OFX>") == 0) {
259 				handlestate(fp, 1, OFXBODY_OFX);
260 				return(0);
261 			}
262 			if ((cp = strchr(input, ':')) == NULL) {
263 				error("invalid syntax");
264 				continue;
265 			}
266 			*cp++ = '\0';
267 			for (hp = headers; hp->name != NULL; hp++)
268 				if (strcmp(input, hp->name) == 0)
269 					break;
270 			if (hp->name == NULL) {
271 				error("invalid syntax");
272 				continue;
273 			}
274 		} else {
275 			cp = input;
276 			if (*cp++ != '<') {
277 				error("invalid syntax");
278 				continue;
279 			}
280 			if (*cp == '/') {
281 				isend = 1;
282 				cp++;
283 			} else
284 				isend = 0;
285 			if ((xp = strchr(cp, '>')) != NULL)
286 				*xp++ = '\0';
287 			for (hp = headers; hp->name != NULL; hp++)
288 				if (strcmp(cp, hp->name) == 0)
289 					break;
290 			if (hp->name == NULL) {
291 				error("invalid OFX symbol");
292 				return(0);
293 			}
294 			if (isend)
295 				return(hp->type);
296 			switch (hp->type) {
297 			case OFXBODY_SIGNON:
298 			case OFXBODY_SONRS:
299 			case OFXBODY_STATUS:
300 			case OFXBODY_CCMSG:
301 			case OFXBODY_CCTNRS:
302 			case OFXBODY_CCSTMT:
303 			case OFXBODY_CCFROM:
304 			case OFXBODY_BANKTL:
305 			case OFXBODY_LEDGER:
306 				i = handlestate(fp, indent + 1, hp->type);
307 				break;
308 
309 			case OFXTAG_ACCTID:
310 				findaccount(xp);
311 				break;
312 
313 			case OFXBODY_STMNT:
314 				if (nomacct == 0) {
315 					error("cannot find card account");
316 					break;
317 				}
318 				if ((ccp = db_creditcardalloc()) == NULL)
319 					break;
320 				if (verbose)
321 					printf(">>> Transaction:-\n");
322 				ccp->nomacct_id = nomacct;
323 				i = handlestate(fp, indent + 1, hp->type);
324 				if (ccp != NULL) {
325 					ccp->journal_id = 0;
326 					ccp->state = 0;
327 					db_insertcreditcard(dbp, ccp);
328 					db_creditcardfree(ccp);
329 					ccp = NULL;
330 				}
331 				break;
332 
333 			case OFXTAG_TRNTYPE:
334 				if (ccp == NULL)
335 					break;
336 				if (verbose)
337 					printf("TX type:\t%s\n", xp);
338 				if (strcmp(xp, "DEBIT") == 0)
339 					ccp->crtype = DEBIT;
340 				else if (strcmp(xp, "CREDIT") == 0)
341 					ccp->crtype = CREDIT;
342 				break;
343 
344 			case OFXTAG_DTPOSTED:
345 				if (ccp == NULL)
346 					break;
347 				parsedate(xp, &ccp->pdate);
348 				if (verbose)
349 					printf("Date posted:\t%s", ctime(&ccp->pdate));
350 				break;
351 
352 			case OFXTAG_DTUSER:
353 				if (ccp == NULL)
354 					break;
355 				parsedate(xp, &ccp->udate);
356 				if (verbose)
357 					printf("Date of TX:\t%s", ctime(&ccp->udate));
358 				break;
359 
360 			case OFXTAG_TRNAMT:
361 				if (ccp == NULL)
362 					break;
363 				ccp->amount = atof(xp);
364 				if (verbose)
365 					printf("Amount:\t\t%.2f\n", ccp->amount);
366 				break;
367 
368 			case OFXTAG_FITID:
369 				if (ccp == NULL)
370 					break;
371 				if (db_findcreditcardbyfitid(dbp,xp) != NULL) {
372 					error("duplicate transaction");
373 					db_creditcardfree(ccp);
374 					ccp = NULL;
375 					break;
376 				}
377 				ccp->fitid = strdup(xp);
378 				if (verbose)
379 					printf("FITID:\t\t%s\n", ccp->fitid);
380 				break;
381 
382 			case OFXTAG_NAME:
383 				if (ccp == NULL)
384 					break;
385 				ccp->descr = strdup(xp);
386 				if (verbose)
387 					printf("Description:\t%s\n", ccp->descr);
388 				break;
389 			}
390 		}
391 	}
392 	return(0);
393 }
394 
395 /*
396  *
397  */
398 void
findaccount(char * acctno)399 findaccount(char *acctno)
400 {
401 	char *cp, *xp;
402 	struct db_bankacct *bap;
403 	dbow_conn *dbp = getdbase();
404 
405 	for (bap = db_findbankacctfirst(dbp); bap != NULL;
406 					bap = db_findbankacctnext(dbp, bap)) {
407 		for (cp = acctno, xp =bap->number; *cp != '\0'; cp++, xp++) {
408 			if (*xp == ' ')
409 				xp++;
410 			if (*cp != 'X' && *cp != 'x' && *cp != *xp)
411 				break;
412 		}
413 		if (*cp == '\0') {
414 			nomacct = bap->nomacct_id;
415 			if (verbose)
416 				printf("Account: %s\n", bap->name);
417 			break;
418 		}
419 	}
420 	if (bap != NULL)
421 		db_bankacctfree(bap);
422 }
423 
424 /*
425  *
426  */
427 void
error(char * msg)428 error(char *msg)
429 {
430 	fprintf(stderr, "\"%s\", line %d: %s.\n", fname, lineno, msg);
431 	nerrors++;
432 }
433 
434 /*
435  *
436  */
437 void
usage()438 usage()
439 {
440 	fprintf(stderr, "Usage: ofxload [-c <comp>] [-v] file.ofx ...\n");
441 	exit(2);
442 }
443