GnuCash 2.4.99
gnc-server.c
00001 /*
00002  * FILE:
00003  * gnc-server.c
00004  *
00005  * FUNCTION:
00006  * Experimental gnucash server
00007  * Written as a demo, not real code.
00008  * A 'real' server would be a bit more architected than this;
00009  * this implementation doesn't hide interfaces sufficiently.
00010  */
00011 
00012 #include <glib.h>
00013 #include <stdio.h>
00014 #include <stdlib.h>
00015 #include <string.h>
00016 #include <unistd.h>
00017 
00018 #include "gnc-engine.h"
00019 #include "io-gncxml.h"
00020 
00021 #include <fcgi_stdio.h>
00022 
00023 
00024 /* ======================================================== */
00025 /* XXX -- hack alert -- should do the below in some far more
00026  * elegant fashion ...  */
00027 
00028 static void
00029 reject_user_agent (const char *user_agent)
00030 {
00031     printf("Content-type: text/html\r\n"
00032            "\r\n"
00033            "<html>\n"
00034            "<head><title>ERROR</title></head>\n"
00035            "<body bgcolor=#ffffff>\n"
00036            "<h1>Error - Wrong Browser</h1>\n"
00037            "Your browser was deteted to be %s<p>\n"
00038            "This server returns finacial data (XML) that only\n"
00039            "the GnuCash client understands.  You must use GnuCash\n"
00040            "to view this data\n"
00041            "</body></html>\n",
00042            user_agent);
00043 }
00044 
00045 static void
00046 reject_method (const char * method)
00047 {
00048     printf("Content-type: text/html\r\n"
00049            "\r\n"
00050            "<html>\n"
00051            "<head><title>ERROR</title></head>\n"
00052            "<body bgcolor=#ffffff>\n"
00053            "<h1>Error - Unsupported Method</h1>\n"
00054            "Your browser sent METHOD=%s\n"
00055            "<p>Only METHOD=POST is supported\n"
00056            "</body></html>\n",
00057            method);
00058 }
00059 
00060 static void
00061 reject_session (const char * session)
00062 {
00063     printf("Content-type: text/html\r\n"
00064            "\r\n"
00065            "<html>\n"
00066            "<head><title>ERROR</title></head>\n"
00067            "<body bgcolor=#ffffff>\n"
00068            "<h1>Error - Invalid Session ID</h1>\n"
00069            "Your browser sent the session id %s\n"
00070            "<p>This is not a valid session ID\n"
00071            "</body></html>\n",
00072            session);
00073 }
00074 
00075 static void
00076 reject_auth (void)
00077 {
00078     printf("Content-type: text/html\r\n"
00079            "\r\n"
00080            "<html>\n"
00081            "<head><title>ERROR</title></head>\n"
00082            "<body bgcolor=#ffffff>\n"
00083            "<h1>Error - Bad Login</h1>\n"
00084            "Your supplied a bad username or password\n"
00085            "<p>Try again\n"
00086            "</body></html>\n");
00087 }
00088 
00089 /* ======================================================== */
00090 /* XXX -- hack alert -- cheesy user authentication and tracking
00091  * this should be replaced by something more professional
00092  *
00093  * This implementation uses gnucash GncGUID's to track sessions,
00094  * but the API is designed so that anything that can be converted
00095  * to a string & back will work.
00096  */
00097 
00098 GList * logged_in_users = NULL;
00099 
00100 /* The auth_user() routine authenticates the user.  If the
00101  * authentication fails, then NULL is returned. If the authentication
00102  * suceeds, then a string that uniquely identifies this session
00103  * should be returned.  (When the user logs off, the session
00104  * should become invalid.
00105  */
00106 
00107 static const char *
00108 auth_user (const char * name, const char *passwd)
00109 {
00110     GncGUID *guid;
00111     const char *session_auth_string;
00112 
00113     /* hack alert - XXX - we do no authentication whatsoever,
00114      * any user is allowed to login.  We only reject null users.
00115      */
00116     if (!name || !passwd) return NULL;
00117 
00118     guid = g_new (GncGUID, 1);
00119     guid_new (guid);
00120     logged_in_users = g_list_prepend (logged_in_users, guid);
00121     session_auth_string = guid_to_string (guid); /* THREAD UNSAFE */
00122     return session_auth_string;
00123 }
00124 
00125 /*
00126  * The have_session() routine checks to see whether the given
00127  * session string corresponds to a valid session.  It returns
00128  * true if it does.
00129  */
00130 
00131 static gboolean
00132 have_session (const char *session_auth_string)
00133 {
00134     GncGUID guid;
00135     GList *next = logged_in_users;
00136 
00137     string_to_guid (session_auth_string, &guid);
00138 
00139     while (next)
00140     {
00141         if (guid_equal (&guid, next->data)) return TRUE;
00142         next = next->next;
00143     }
00144 
00145     /* guid was not found */
00146     return FALSE;
00147 }
00148 
00149 /* ======================================================== */
00150 /* handy utility routine for finding a cookie in the cookie string */
00151 
00152 static const char *
00153 find_cookie (const char * cookie_name)
00154 {
00155     const char *cookie_string;
00156     size_t len;
00157     len = strlen (cookie_name);
00158 
00159     cookie_string = getenv ("HTTP_COOKIE");
00160     if (!cookie_string) return NULL;
00161 
00162     while (cookie_string)
00163     {
00164         if (!strncmp (cookie_string, cookie_name, len) &&
00165                 ('=' == cookie_string[len]))
00166         {
00167             return cookie_string + len + 1;
00168         }
00169         cookie_string = strchr (cookie_string, ';');
00170         if (cookie_string) cookie_string ++;
00171     }
00172 
00173     return NULL;
00174 }
00175 
00176 /* ======================================================== */
00177 /* simpleminded utility parses GET/POST string for username,
00178  * password.  Not only is it totally inflexible as to what
00179  * it looks for, but it also fails to url-decode, so that
00180  * characters like & cannot be used insode of passwords
00181  * XXX hack alert above should be fixed.
00182  */
00183 static void
00184 parse_for_login (char * bufp, char **namep, char **passwdp)
00185 {
00186     if (!bufp) return;
00187 
00188     while (bufp)
00189     {
00190         if (!strncmp (bufp, "name=", 5))
00191         {
00192             *namep = bufp + 5;
00193         }
00194         else if (!strncmp (bufp, "passwd=", 7))
00195         {
00196             *passwdp = bufp + 7;
00197         }
00198 
00199         bufp = strchr (bufp, '&');
00200         if (bufp)
00201         {
00202             *bufp = 0x0;
00203             bufp++;
00204         }
00205     }
00206 }
00207 
00208 /* ======================================================== */
00209 
00210 int
00211 main (int argc, char *argv[])
00212 {
00213     int err, fake_argc = 1;
00214     char * fake_argv[] = {"hello", 0};
00215     QofBook *book;
00216     Account *root;
00217     char *request_bufp, *reply_bufp;
00218     int rc, sz;
00219 
00220     /* intitialize the engine */
00221     gnc_engine_init (fake_argc, fake_argv);
00222 
00223     /* contact the database, which is a flat file for this demo */
00224     /* this should really be an SQL server */
00225     book = qof_book_new ();
00226 
00227     rc = gnc_book_begin (book, "file:/tmp/demo.gnucash", FALSE);
00228     if (!rc) goto bookerrexit;
00229 
00230     rc = gnc_book_load (book);
00231     if (!rc) goto bookerrexit;
00232 
00233     /* the root pointer points to our local cache of the data */
00234     root = gnc_book_get_root_account (book);
00235 
00236     /* --------------------------------------------------- */
00237     /* done with initialization, go into event loop */
00238 
00239     while (FCGI_Accept() >= 0)
00240     {
00241         GList *split_list;
00242         Query *q = NULL;
00243         const char *request_method;
00244         const char *user_agent;
00245         const char *auth_string;
00246         const char *content_length;
00247         int read_len = 0;
00248         int send_accts = 0;
00249 
00250         /* get the user agent; reject if wrong agent */
00251         user_agent = getenv ("HTTP_USER_AGENT");
00252         if (strncmp ("gnucash", user_agent, 7))
00253         {
00254             reject_user_agent (user_agent);
00255             continue;
00256         }
00257 
00258         /* get the request method */
00259         request_method = getenv ("REQUEST_METHOD");
00260         if (strcmp ("POST", request_method))
00261         {
00262             /* method=post is the only spported method*/
00263             reject_method(request_method);
00264             continue;
00265         }
00266 
00267         /* ----------------------------------------------- */
00268         /* look for an authentication cookie */
00269         auth_string = find_cookie ("gnc-server");
00270 
00271         /* found the cookie, lets make sure that it is valid */
00272         if (auth_string)
00273         {
00274             gboolean valid_session;
00275             valid_session = have_session (auth_string);
00276             if (!valid_session)
00277             {
00278 
00279                 /* XXX invalid sessions are a sign of hacking;
00280                  * this event should be noted in a security log
00281                  * and the server admin contacted.
00282                  */
00283                 reject_session (auth_string);
00284                 continue;
00285             }
00286         }
00287 
00288         /* go ahead and read the message body.
00289          * we'll need this soon enough */
00290         content_length = getenv("CONTENT_LENGTH");
00291         read_len = atoi (content_length);
00292 
00293         /* read 'read_len' bytes from stdin ... */
00294         request_bufp = (char *) g_malloc (read_len);
00295         fread (request_bufp, read_len, 1, stdin);
00296 
00297         /* if no previously authenticated session,
00298          * authenticate now */
00299         if (!auth_string)
00300         {
00301             char *name = NULL, *passwd = NULL;
00302             parse_for_login (request_bufp, &name, &passwd);
00303 
00304             auth_string = auth_user (name, passwd);
00305             if (!auth_string)
00306             {
00307                 reject_auth();
00308                 g_free (request_bufp);
00309                 continue;
00310             }
00311             send_accts = 1;
00312         }
00313 
00314         /* ----------------------------------------------- */
00315         /* send only the accounts to the user */
00316         if (send_accts)
00317         {
00318             /* print the HTTP header */
00319             printf("Content-type: text/gnc-xml\r\n"
00320                    "Set-Cookie: %s\r\n"
00321                    "Content-Length: %d\r\n"
00322                    "\r\n",
00323                    auth_string, sz);
00324 
00325             /* since this is the first time the user is logging
00326              * in, send them the full set of accounts.
00327              * (Do not send them any transactions yet).
00328              */
00329             gncxml_write_account_tree_to_buf(root, &reply_bufp, &sz);
00330 
00331             /* send the xml to the client */
00332             printf ("%s", reply_bufp);
00333             g_free (request_bufp);
00334 
00335             /* wait for the next request */
00336             continue;
00337         }
00338 
00339         /* ----------------------------------------------- */
00340         /* If we got to here, then the ser should be sending
00341          * us a query xml.
00342          * we should somehow error check that what we got
00343          * is really a valid query
00344          */
00345 
00346         /* conver the xml input into a gnucash query structure... */
00347         q = gncxml_read_query (request_bufp, read_len);
00348         xaccQuerySetGroup (q, root);
00349 
00350         /* hack -- limit to 30 splits ... */
00351         qof_query_set_max_results (q, 30);
00352         split_list = qof_query_run (q);
00353 
00354         /* poke those splits into an ccount group structure */
00355         /* XXX not implemented */
00356 
00357         /* send the account group structure back to the user */
00358         /* XXX not implemented */
00359 
00360         qof_query_destroy (q);
00361         g_free (request_bufp);
00362 
00363     }
00364 
00365 bookerrexit:
00366 
00367     err = gnc_book_get_error (book);
00368 
00369     /* 500 Server Error */
00370     FCGI_SetExitStatus (500);
00371 
00372     printf("Content-type: text/plain\r\n\r\n"
00373            "error was %s\n", strerror (err));
00374 
00375     FCGI_Finish();
00376 
00377     /* close the book */
00378     qof_book_destroy (book);
00379 
00380     /* shut down the engine */
00381     gnc_engine_shutdown ();
00382 
00383     sleep (1);
00384 
00385     /* must return a non-zero error code, otherwise fastcgi
00386      * attempts to respawn this daemon. */
00387     return 500;
00388 }
00389 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines