GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
gnc-server.c
1 /*
2  * FILE:
3  * gnc-server.c
4  *
5  * FUNCTION:
6  * Experimental gnucash server
7  * Written as a demo, not real code.
8  * A 'real' server would be a bit more architected than this;
9  * this implementation doesn't hide interfaces sufficiently.
10  */
11 
12 #include <glib.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <unistd.h>
17 
18 #include "gnc-engine.h"
19 #include "io-gncxml.h"
20 
21 #include <fcgi_stdio.h>
22 
23 
24 /* ======================================================== */
25 /* XXX -- hack alert -- should do the below in some far more
26  * elegant fashion ... */
27 
28 static void
29 reject_user_agent (const char *user_agent)
30 {
31  printf("Content-type: text/html\r\n"
32  "\r\n"
33  "<html>\n"
34  "<head><title>ERROR</title></head>\n"
35  "<body bgcolor=#ffffff>\n"
36  "<h1>Error - Wrong Browser</h1>\n"
37  "Your browser was deteted to be %s<p>\n"
38  "This server returns finacial data (XML) that only\n"
39  "the GnuCash client understands. You must use GnuCash\n"
40  "to view this data\n"
41  "</body></html>\n",
42  user_agent);
43 }
44 
45 static void
46 reject_method (const char * method)
47 {
48  printf("Content-type: text/html\r\n"
49  "\r\n"
50  "<html>\n"
51  "<head><title>ERROR</title></head>\n"
52  "<body bgcolor=#ffffff>\n"
53  "<h1>Error - Unsupported Method</h1>\n"
54  "Your browser sent METHOD=%s\n"
55  "<p>Only METHOD=POST is supported\n"
56  "</body></html>\n",
57  method);
58 }
59 
60 static void
61 reject_session (const char * session)
62 {
63  printf("Content-type: text/html\r\n"
64  "\r\n"
65  "<html>\n"
66  "<head><title>ERROR</title></head>\n"
67  "<body bgcolor=#ffffff>\n"
68  "<h1>Error - Invalid Session ID</h1>\n"
69  "Your browser sent the session id %s\n"
70  "<p>This is not a valid session ID\n"
71  "</body></html>\n",
72  session);
73 }
74 
75 static void
76 reject_auth (void)
77 {
78  printf("Content-type: text/html\r\n"
79  "\r\n"
80  "<html>\n"
81  "<head><title>ERROR</title></head>\n"
82  "<body bgcolor=#ffffff>\n"
83  "<h1>Error - Bad Login</h1>\n"
84  "Your supplied a bad username or password\n"
85  "<p>Try again\n"
86  "</body></html>\n");
87 }
88 
89 /* ======================================================== */
90 /* XXX -- hack alert -- cheesy user authentication and tracking
91  * this should be replaced by something more professional
92  *
93  * This implementation uses gnucash GncGUID's to track sessions,
94  * but the API is designed so that anything that can be converted
95  * to a string & back will work.
96  */
97 
98 GList * logged_in_users = NULL;
99 
100 /* The auth_user() routine authenticates the user. If the
101  * authentication fails, then NULL is returned. If the authentication
102  * suceeds, then a string that uniquely identifies this session
103  * should be returned. (When the user logs off, the session
104  * should become invalid.
105  */
106 
107 static const char *
108 auth_user (const char * name, const char *passwd, char *buff)
109 {
110  GncGUID *guid;
111  const char *session_auth_string;
112 
113  /* hack alert - XXX - we do no authentication whatsoever,
114  * any user is allowed to login. We only reject null users.
115  */
116  if (!name || !passwd) return NULL;
117 
118  guid = guid_new();
119  logged_in_users = g_list_prepend (logged_in_users, guid);
120 
121  guid_to_string_buffer (guid, buff);
122  return buff;
123 }
124 
125 /*
126  * The have_session() routine checks to see whether the given
127  * session string corresponds to a valid session. It returns
128  * true if it does.
129  */
130 
131 static gboolean
132 have_session (const char *session_auth_string)
133 {
134  GncGUID guid;
135  GList *next = logged_in_users;
136 
137  string_to_guid (session_auth_string, &guid);
138 
139  while (next)
140  {
141  if (guid_equal (&guid, next->data)) return TRUE;
142  next = next->next;
143  }
144 
145  /* guid was not found */
146  return FALSE;
147 }
148 
149 /* ======================================================== */
150 /* handy utility routine for finding a cookie in the cookie string */
151 
152 static const char *
153 find_cookie (const char * cookie_name)
154 {
155  const char *cookie_string;
156  size_t len;
157  len = strlen (cookie_name);
158 
159  cookie_string = getenv ("HTTP_COOKIE");
160  if (!cookie_string) return NULL;
161 
162  while (cookie_string)
163  {
164  if (!strncmp (cookie_string, cookie_name, len) &&
165  ('=' == cookie_string[len]))
166  {
167  return cookie_string + len + 1;
168  }
169  cookie_string = strchr (cookie_string, ';');
170  if (cookie_string) cookie_string ++;
171  }
172 
173  return NULL;
174 }
175 
176 /* ======================================================== */
177 /* simpleminded utility parses GET/POST string for username,
178  * password. Not only is it totally inflexible as to what
179  * it looks for, but it also fails to url-decode, so that
180  * characters like & cannot be used insode of passwords
181  * XXX hack alert above should be fixed.
182  */
183 static void
184 parse_for_login (char * bufp, char **namep, char **passwdp)
185 {
186  if (!bufp) return;
187 
188  while (bufp)
189  {
190  if (!strncmp (bufp, "name=", 5))
191  {
192  *namep = bufp + 5;
193  }
194  else if (!strncmp (bufp, "passwd=", 7))
195  {
196  *passwdp = bufp + 7;
197  }
198 
199  bufp = strchr (bufp, '&');
200  if (bufp)
201  {
202  *bufp = 0x0;
203  bufp++;
204  }
205  }
206 }
207 
208 /* ======================================================== */
209 
210 int
211 main (int argc, char *argv[])
212 {
213  int err, fake_argc = 1;
214  char * fake_argv[] = {"hello", 0};
215  QofBook *book;
216  Account *root;
217  char *request_bufp, *reply_bufp;
218  int rc, sz;
219 
220  /* intitialize the engine */
221  gnc_engine_init (fake_argc, fake_argv);
222 
223  /* contact the database, which is a flat file for this demo */
224  /* this should really be an SQL server */
225  book = qof_book_new ();
226 
227  rc = gnc_book_begin (book, "file:/tmp/demo.gnucash", FALSE);
228  if (!rc) goto bookerrexit;
229 
230  rc = gnc_book_load (book);
231  if (!rc) goto bookerrexit;
232 
233  /* the root pointer points to our local cache of the data */
234  root = gnc_book_get_root_account (book);
235 
236  /* --------------------------------------------------- */
237  /* done with initialization, go into event loop */
238 
239  while (FCGI_Accept() >= 0)
240  {
241  GList *split_list;
242  Query *q = NULL;
243  const char *request_method;
244  const char *user_agent;
245  const char *auth_string;
246  const char *content_length;
247  gchar guidstr[GUID_ENCODING_LENGTH+1];
248  int read_len = 0;
249  int send_accts = 0;
250 
251  /* get the user agent; reject if wrong agent */
252  user_agent = getenv ("HTTP_USER_AGENT");
253  if (strncmp ("gnucash", user_agent, 7))
254  {
255  reject_user_agent (user_agent);
256  continue;
257  }
258 
259  /* get the request method */
260  request_method = getenv ("REQUEST_METHOD");
261  if (strcmp ("POST", request_method))
262  {
263  /* method=post is the only spported method*/
264  reject_method(request_method);
265  continue;
266  }
267 
268  /* ----------------------------------------------- */
269  /* look for an authentication cookie */
270  auth_string = find_cookie ("gnc-server");
271 
272  /* found the cookie, lets make sure that it is valid */
273  if (auth_string)
274  {
275  gboolean valid_session;
276  valid_session = have_session (auth_string);
277  if (!valid_session)
278  {
279 
280  /* XXX invalid sessions are a sign of hacking;
281  * this event should be noted in a security log
282  * and the server admin contacted.
283  */
284  reject_session (auth_string);
285  continue;
286  }
287  }
288 
289  /* go ahead and read the message body.
290  * we'll need this soon enough */
291  content_length = getenv("CONTENT_LENGTH");
292  read_len = atoi (content_length);
293 
294  /* read 'read_len' bytes from stdin ... */
295  request_bufp = (char *) g_malloc (read_len);
296  fread (request_bufp, read_len, 1, stdin);
297 
298  /* if no previously authenticated session,
299  * authenticate now */
300  if (!auth_string)
301  {
302  char *name = NULL, *passwd = NULL;
303  parse_for_login (request_bufp, &name, &passwd);
304 
305  auth_string = auth_user (name, passwd, guidstr);
306  if (!auth_string)
307  {
308  reject_auth();
309  g_free (request_bufp);
310  continue;
311  }
312  send_accts = 1;
313  }
314 
315  /* ----------------------------------------------- */
316  /* send only the accounts to the user */
317  if (send_accts)
318  {
319  /* print the HTTP header */
320  printf("Content-type: text/gnc-xml\r\n"
321  "Set-Cookie: %s\r\n"
322  "Content-Length: %d\r\n"
323  "\r\n",
324  auth_string, sz);
325 
326  /* since this is the first time the user is logging
327  * in, send them the full set of accounts.
328  * (Do not send them any transactions yet).
329  */
330  gncxml_write_account_tree_to_buf(root, &reply_bufp, &sz);
331 
332  /* send the xml to the client */
333  printf ("%s", reply_bufp);
334  g_free (request_bufp);
335 
336  /* wait for the next request */
337  continue;
338  }
339 
340  /* ----------------------------------------------- */
341  /* If we got to here, then the ser should be sending
342  * us a query xml.
343  * we should somehow error check that what we got
344  * is really a valid query
345  */
346 
347  /* conver the xml input into a gnucash query structure... */
348  q = gncxml_read_query (request_bufp, read_len);
349  xaccQuerySetGroup (q, root);
350 
351  /* hack -- limit to 30 splits ... */
353  split_list = qof_query_run (q);
354 
355  /* poke those splits into an ccount group structure */
356  /* XXX not implemented */
357 
358  /* send the account group structure back to the user */
359  /* XXX not implemented */
360 
361  qof_query_destroy (q);
362  g_free (request_bufp);
363 
364  }
365 
366 bookerrexit:
367 
368  err = gnc_book_get_error (book);
369 
370  /* 500 Server Error */
371  FCGI_SetExitStatus (500);
372 
373  printf("Content-type: text/plain\r\n\r\n"
374  "error was %s\n", strerror (err));
375 
376  FCGI_Finish();
377 
378  /* close the book */
379  qof_book_destroy (book);
380 
381  /* shut down the engine */
383 
384  sleep (1);
385 
386  /* must return a non-zero error code, otherwise fastcgi
387  * attempts to respawn this daemon. */
388  return 500;
389 }
390 
void gnc_engine_shutdown(void)
Definition: gnc-engine.c:160
gboolean string_to_guid(const gchar *string, GncGUID *guid)
GncGUID * guid_new(void)
QofBook * qof_book_new(void)
api for Version 1 XML-based file format
Definition: guid.h:65
void qof_query_set_max_results(QofQuery *q, int n)
void qof_query_destroy(QofQuery *q)
gboolean guid_equal(const GncGUID *guid_1, const GncGUID *guid_2)
#define GUID_ENCODING_LENGTH
Definition: guid.h:74
void gnc_engine_init(int argc, char **argv)
Definition: gnc-engine.c:139
All type declarations for the whole Gnucash engine.
GList * qof_query_run(QofQuery *query)
void qof_book_destroy(QofBook *book)