|
GnuCash 2.4.99
|
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
1.7.4