#include "gb-include.h" #include "Proxy.h" #include "Statsdb.h" #include "Msg13.h" #include "XmlDoc.h" //#include "seo.h" // g_secret_tran_key and api_key char *g_secret_tran_key = NULL; char *g_secret_api_key = NULL; Proxy g_proxy; // turn this off now //bool g_isYippy = true; bool g_isYippy = false; #define MINCHARGE 5.00 static void gotTcpReplyWrapper ( void *state , TcpSocket *s ) ; static void proxyHandlerWrapper ( TcpSocket *s ); //static void gotReplyWrapperPage( void *state, TcpSocket *s ); static void gotHttpReplyWrapper ( void *state, UdpSlot *slot ) ; //static void gotDataFeedRequestWrapper( void *state ); static void uncountStripe ( class StateControl *stC ) ; static bool sendPageAccount ( class TcpSocket *s , class HttpRequest *r ) ; struct StateControl{ long m_userId32; float m_price; long m_accessType; long m_pageNum; bool m_isYippySearch; long long m_start; long m_reqNum; SafeBuf m_sb; TcpSocket *m_s; long long m_startTime; bool m_isQuery; unsigned long m_hash; long m_hostId; long m_raw; long m_stripe ; long m_numQueryTerms ; // hash32() of "code" long m_ch; UdpSlot *m_slot; char *m_slotReadBuf; long m_slotReadBufMaxSize; //long m_forward; long m_retries; long long m_timeout; HttpRequest m_hr; Host *m_forwardHost; float m_pending; bool m_isEventGuru; }; #define UIF_ADMIN 0x01 #define UIF_OLDUSER 0x02 // PageRoot.cpp's pageaddurl now uses userinfo... class UserInfo { public: // unique userid long m_userId32; // outstanding requests costs this much float m_pending; long m_signUpDate; // strings include terminating \0 char m_login[32]; char m_password[32]; char m_xmlFeedCode[16]; char m_creditCardNum[64]; // no punct, just digits char m_cvv[5]; // "1234\0" char m_creditCardExpires[6]; // "05/13\0" char m_creditCardType[32]; // visa // new stuff char m_email[80]; char m_phone[30]; // european stuff for credit card processing char m_firstName[40]; char m_lastName[40]; char m_address[80]; char m_city[30]; char m_state[30]; char m_country[30]; char m_zip[20]; // session info: long long m_lastSessionId64; long m_lastActionTime; long m_lastLoginIP; float m_accountBalance; long m_flags; }; // values for SummaryRec::m_accessType #define AT_SEARCHFEED_OLD 1 #define AT_SEARCHFEED_NEW 2 #define AT_COMPETITOR_BACKLINKS 3 #define AT_RELATED_QUERIES 4 #define AT_MISSING_TERMS 5 #define AT_MATCHING_QUERIES 6 #define AT_COMPETITOR_PAGES 7 #define AT_ADDURL 8 #define MAX_ACCESS_TYPE 8 static void freeStateControl ( StateControl *stC ); Proxy::Proxy() { m_proxyId = -1; m_proxyRunning = false; for (long i =0; i < MAX_HOSTS; i++) m_numOutstanding[i] = 0; for ( long i = 0 ; i < MAX_STRIPES ; i++ ) { m_termsOutOnStripe [i] = 0; m_queriesOutOnStripe [i] = 0; m_stripeLastHostId [i] = -1; } m_nextStripe = 0; m_lastHost = 0; m_mainHost = 0; //m_msg3c = NULL; } Proxy::~Proxy() { /* if(m_msg3c) { // free the msg3c mdelete(m_msg3c, sizeof(Msg3c), "Proxy-Msg3c"); delete m_msg3c; m_msg3c = NULL; } */ } bool Proxy::initHttpServer ( unsigned short httpPort, unsigned short httpsPort ) { if ( ! g_httpServer.init( httpPort, httpsPort, proxyHandlerWrapper ) ) { return false; } return true; } bool Proxy::initProxy ( long proxyId, unsigned short udpPort, unsigned short udpPort2,UdpProtocol *dp ) { // let's ensure our core file can dump struct rlimit lim; lim.rlim_cur = lim.rlim_max = RLIM_INFINITY; if ( setrlimit(RLIMIT_CORE,&lim) ){ log("proxy: setrlimit: core: %s", mstrerror(errno) ); return false; } //lim.rlim_cur = lim.rlim_max = 4000; //if ( setrlimit(RLIMIT_NOFILE,&lim) ){ // log("proxy: setrlimit: numfds: %s", mstrerror(errno) ); // return false; //} //log("proxy: set max fds to 4000"); m_proxyId = proxyId; // set this in Hostdb too! g_hostdb.m_hostId = proxyId; m_proxyRunning = true; // load up hosts.conf /*char *hostsConf = "./hosts.conf"; g_hostdb.reset(); if ( ! g_hostdb.init(hostsConf, m_proxyId, NULL, true ) ) {//isproxy log("db: hostdb init failed." ); return 1; }*/ //gb.conf should be in the same directory as gb //if ( ! g_conf.init ( "./" ) ) { // , h->m_hostId ) ) { // log("db: Conf init failed." ); return 1; } //We log the http requests, althogh this is directly done by us g_conf.m_logHttpRequests = true; //Don't send email alerts, the machines on the cluster can do that //g_conf.m_sendEmailAlerts = false; // my new email // if proxy, always have autosave on so we can save user accouting // info regularly for billing feed access. save every 5 minutes. g_conf.m_autoSaveFrequency = 5; // no, now proxies do too! for out of socket conditions in tcpserver g_conf.m_sendEmailAlerts = true; g_conf.m_sendEmailAlertsToEmail1 = true; // verizon bought us out... smtp-sl.vtext.com // was messages.alltel.com strcpy ( g_conf.m_email1Addr , "5054503518@vtext.com"); strcpy ( g_conf.m_email1From , "sysadmin@gigablast.com"); // got ip 69.78.67.53 for 'smtp-sl.vtext.com' //strcpy ( g_conf.m_email1MX , "gbmxrec-vtext.com"); strcpy ( g_conf.m_email1MX , "10.5.54.47"); //if ( ! g_hostdb2.validateIps ( &g_conf ) ) { // log("db: Failed to validate ips." ); return 1;} // init the loop, needs g_conf //if ( ! g_loop.init() ) { // log("db: Loop init failed." ); return false; } // . autoban must be on // . MDW: why? leave it alone. not good for buzz. //g_conf.m_doAutoBan = true; if (!g_autoBan.init()){ log("autoban: init failed."); return false; } //for pingserver //if ( ! g_hostdb.validateIps ( &g_conf ) ) { // log("db: Failed to validate ips." ); return 1;} //if ( ! g_udpServer.init( udpPort ,dp,2/*niceness*/, // 10000000 , // readBufSIze // 10000000 , // writeBufSize // 60 , // pollTime in ms // 10000 )){ // max udp slots // log("db: UdpServer init failed." ); return 0; //} // start pinging right away, udpServer has already been init'ed if ( ! g_pingServer.init() ) { log("db: PingServer init failed." ); return false; } if ( ! g_pingServer.registerHandler() ) return false; //Also have to init pages because we need to know which requests to //forward. html/gif's, etc can be taken care here itself. g_pages.init ( ); // load up the dmoz categories here char structureFile[256]; sprintf(structureFile, "%scatdb/gbdmoz.structure.dat", g_hostdb.m_dir); g_categories = &g_categories1; if (g_categories->loadCategories(structureFile) != 0) { log("cat: Loading Categories From %s Failed.", structureFile); //return 1; } log(LOG_INFO, "cat: Loaded Categories From %s.", structureFile); Msg13 msg13; if ( ! msg13.registerHandler () ) return false; // . then dns Distributed client // . server should listen to a socket and register with g_loop // . Only the distributed cache shall call the dns server. if ( ! g_dns.init( g_hostdb.m_myHost->m_dnsClientPort ) ) { log("db: Dns distributed client init failed." ); return 1; } MsgC msgc; if ( ! msgc.registerHandler() ) return false; //need to init collectiondb too because of addurl //set isdump to true because we aren't going to store any data in the //collection if ( !g_collectiondb.loadAllCollRecs( ) ){ //isDump log ("db: collectiondb init failed."); return false; } //init g_msg g_msg = ""; // load accounting info if ( ! loadUserBufs ( ) ) return log("proxy: failed to load user bufs"); return true; } void proxyHandlerWrapper ( TcpSocket *s ){ g_proxy.handleRequest (s); } static long s_yippySearchesOut = 0; bool Proxy::handleRequest (TcpSocket *s){ // if we are a spider compression proxy, do not really act like // a proxy at all! if ( g_hostdb.m_myHost->m_type == HT_SCPROXY ) { //httprequest changes the buf g_httpServer.requestHandler(s); g_msg = ""; return true; } /* char buf[MAX_REQ_LEN+10]; long bufSize = s->m_readOffset; if ( bufSize > MAX_REQ_LEN - 1 ) bufSize = MAX_REQ_LEN - 1; memcpy( buf, s->m_readBuf, bufSize ); buf[bufSize] = '\0'; */ //m_s = s; HttpRequest hr; //bool status = hr.set ( buf , bufSize , s ) ; bool status = hr.set ( s->m_readBuf , s->m_readOffset , s ) ; if ( ! status ) { // log a bad request log("http: Got bad request from %s: %s", iptoa(s->m_ip),mstrerror(g_errno)); // cancel the g_errno, we'll send a BAD REQUEST reply to them g_errno = 0; // . this returns false if blocked, true otherwise // . this sets g_errno on error // . this will destroy(s) if cannot malloc send buffer g_httpServer.sendErrorReply ( s , 400, "Bad Request" ); return false; } bool isAdmin = g_conf.isRootAdmin(s,&hr); long redirLen = hr.getRedirLen() ; char *redir = NULL; if(redirLen > 0) redir = hr.getRedir(); // redirect everyone away if we should if ( !redir && !isAdmin && // . we put this here to redirect all traffic somewhere else // . you can set that url in the master controls // . it only redirects there if the raw/code/site/sites is NULL *g_conf.m_redirect != '\0' && hr.getLong("xml", -1) == -1 && hr.getLong("raw", -1) == -1 && hr.getString("code") == NULL && hr.getString("site") == NULL && hr.getString("sites") == NULL) { //direct all non-raw, non admin traffic away. redir = g_conf.m_redirect; redirLen = gbstrlen(g_conf.m_redirect); } // . we may be serving multiple hostnames // . www.gigablast.com, gigablast.com, www.inifinte.info, // infinite.info, www.microdemocracy.com // . get the host: field from the MIME // . should be NULL terminated char *host = hr.getHost(); char *hdom = host; if ( strncasecmp(hdom,"www.",4) == 0 ) hdom += 4; if ( strncasecmp(hdom,"www2.",5) == 0 ) hdom += 5; if ( strncasecmp(hdom,"www1.",5) == 0 ) hdom += 5; // auto redirect eventguru.com to www.eventguru.com so cookies // are consistent if ( ! redir && ( strcasecmp ( host , "eventguru.com" ) == 0 || strcasecmp ( hdom , "flurbit.com" ) == 0 || strcasecmp ( hdom , "flurbits.com" ) == 0 || strcasecmp ( hdom , "flurpit.com" ) == 0 || strcasecmp ( hdom , "flurbot.com" ) == 0 || strcasecmp ( hdom , "flurbits.com" ) == 0 || strcasecmp ( hdom , "flurbyte.com" ) == 0 || strcasecmp ( hdom , "eventstereo.com" ) == 0 || strcasecmp ( hdom , "eventcrier.com" ) == 0 || strcasecmp ( hdom , "eventwidget.com" ) == 0 ) ) { redir = "http://www.eventguru.com/"; redirLen = gbstrlen(redir); } bool isEventGuru = false; if ( strcasecmp(hdom,"eventguru.com") == 0 ) isEventGuru = true; #ifdef MATTWELLS #define HTTPS_REDIR 1 #endif if ( redirLen > 0 && redir ) { #ifdef HTTPS_REDIR redirect: #endif HttpMime m; m.makeRedirMime (redir,redirLen); // . move the reply to a send buffer // . don't make sendBuf bigger than g_httpMaxSendBufSize long sendBufSize = m.getMimeLen(); if ( sendBufSize > g_conf.m_httpMaxSendBufSize ) sendBufSize = g_conf.m_httpMaxSendBufSize; char *sendBuf = (char *) mmalloc (sendBufSize,"HttpServer"); if ( ! sendBuf ) return g_httpServer.sendErrorReply(s,500, mstrerror(g_errno)); memcpy ( sendBuf , m.getMime() , sendBufSize ); // . send it away // . this returns false if blocked, true otherwise // . this sets g_errno on error // . this will destroy "s" on error // . "f" is the callback data // . it's passed to cleanUp() on completion/error before socket // is recycled/destroyed // . this will call getMsgPiece() to fill up sendBuf from file TcpServer *tcp = s->m_this; if ( ! tcp->sendMsg ( s , sendBuf , sendBufSize , sendBufSize , sendBufSize , NULL , // data for callback NULL ) ) // callback return false; // it didn't block or there was an error return true; } // . just requesting a static file, like rants.html or logo.gif? // . if so just handle that as a normal html/image file long n = g_pages.getDynamicPageNumber ( &hr ); char *path = hr.getPath(); //long pathLen = hr.getPathLen(); // serve events on the gigablast.com domain: if ( path && strncmp(path,"/events",7) == 0 ) isEventGuru = true; /* bool badPage = false; if ( n < 0 ) badPage = true; if ( hr.getRedirLen() > 0 ) badPage = true; if (pathLen == 11 && strncmp ( path , "/index.html" ,11 ) ==0 ) badPage = false; if (pathLen >= 4 && strncmp ( path , "/seo" ,4 ) ==0 ) badPage = false; if ( g_isYippy ) badPage = false; if ( badPage ) { //httprequest changes the buf g_httpServer.requestHandler(s); g_msg = ""; return true; } */ // . i guess right now the proxy is handling admin pages itself // . i am changing this so that if &forward= is in the url then // the proxy forwards the control page request to the given hostid long forward = hr.getLong("forward",-1); bool handleIt = true; if ( forward != -1 ) handleIt = false; //if ( n == PAGE_ROOT ) handleIt = false; if ( n == PAGE_GET ) handleIt = false; if ( n == PAGE_RESULTS ) handleIt = false; if ( n == PAGE_DIRECTORY ) handleIt = false; // proxy now handles the shell addurl page, the actual request // made by the ajax in the shell to add the url has a &id= in it // and should go to the backend for adding the url. but we can // handle the shell, just the add url page html including the ajax // script. long cgiId = hr.getLong("id",0); if ( n == PAGE_ADDURL && cgiId ) handleIt = false; // send this to gk144 if ( strncmp(path,"/seo",4) == 0 ) handleIt = false; if ( strncmp(path,"/seoapi",7) == 0 ) handleIt = true; // we're just a tcp proxy if yippy if ( g_isYippy ) handleIt = false; // special pages. any html page will now need to call // msgfb to get the user info, like the name "Matt Wells" to // post in the black bar, so we can't really do this on the // proxy any more, we have to route all the way to the cluster. //if ( pathLen >= 7 && strnstr(path,".html",pathLen) ) handleIt =false; // same for xml... (eventguru.xml search provider list) //if ( pathLen >= 7 && strnstr(path,".xml",pathLen) ) handleIt = false; // for stats pages etc for yippy proxy if ( g_isYippy && ! strncmp(path,"/master",7) ) handleIt = true; /* if ( ! strncmp(path,"/blog.html" ,10) ) handleIt = false; if ( ! strncmp(path,"/terms.html" ,11) ) handleIt = false; if ( ! strncmp(path,"/privacy.html",13) ) handleIt = false; if ( ! strncmp(path,"/account.html",13) ) handleIt = false; if ( ! strncmp(path,"/bio.html",13) ) handleIt = false; */ // our new cached page representation format if ( ! strncmp(path,"/?id=" ,5 ) ) handleIt = false; // log the request iff filename does not end in .gif .jpg . char *f = NULL; long flen = 0; if ( isEventGuru ) { f = hr.getFilename(); flen = hr.getFilenameLen(); } // proxy will handle eventguru images i guess bool isGif = ( f && flen >= 4 && strncmp(&f[flen-4],".gif",4) == 0 ); bool isJpg = ( f && flen >= 4 && strncmp(&f[flen-4],".jpg",4) == 0 ); bool isBmp = ( f && flen >= 4 && strncmp(&f[flen-4],".bmp",4) == 0 ); bool isPng = ( f && flen >= 4 && strncmp(&f[flen-4],".png",4) == 0 ); bool isIco = ( f && flen >= 4 && strncmp(&f[flen-4],".ico",4) == 0 ); bool isPic = (isGif | isJpg | isBmp | isPng || isIco); // use event guru favicon? //if ( isEventGuru && isIco && strcmp(f,"favicon.ico") == 0 ) { // f = "eventguru_favicon.ico"; // flen = gbstrlen(f); //} // eventguru.com host: in mime? if ( isEventGuru && ! isPic ) handleIt = false; // only proxy holds the accounting info if ( ! strncmp ( path ,"/account", 8 ) ) { printRequest(s, &hr); return sendPageAccount ( s , &hr ); } // get the server this socket uses TcpServer *tcp = s->m_this; long max; if ( tcp == &g_httpServer.m_ssltcp ) max = g_conf.m_httpsMaxSockets; else max = g_conf.m_httpMaxSockets; #ifdef HTTPS_REDIR // if hitting root page then tell them to go to https // if not autobanned... but if it is an autobanned request on root // page it should have go the turing test above! if ( n == PAGE_ROOT && ! g_isYippy && // not event guru homepage ! isEventGuru && // if not already on https tcp != &g_httpServer.m_ssltcp && // do not redirect http://www.gigablast.com/?c=dmoz3 (directory)! hr.m_cgiBufLen <=1 ) { // redirect to https site redir = "https://www.gigablast.com/"; // // if we are gk267 then redirect to https://www2.gigablast.com/ // static long s_ip2 = 0; if ( ! s_ip2 ) s_ip2 = atoip("10.5.56.77"); if ( (long)g_hostdb.m_myIp == s_ip2 ) redir = "https://www2.gigablast.com/"; redirLen = gbstrlen(redir); goto redirect; } #endif // . page addurl uses the udpserver to send the addurl stuff to one of // the hosts, so we need udpserver. // . handle the request ourselves if it is not one of these // pages and "forward" was not specified in the url cgi fields if ( handleIt ) { //httprequest changes the buf g_httpServer.requestHandler(s); g_msg = ""; return true; } // limit yippy's "GET /search" requests to 50 out... long ymax = 150; // 150;//50;//25; 300 for one process is good ymax = g_conf.m_maxYippyOut; bool isYippySearch = false; if ( g_isYippy && ! strncmp(path,"/search?",8) ) isYippySearch = true; // is it a search request from a toolbar? we do not want to fuck // with those requests with the anti-bot code below bool isYippyToolBarRequest = false; if ( g_isYippy && isYippySearch ) { long iflen; char *ifs = hr.getString("input-form",&iflen,NULL); if ( ! ifs ) isYippyToolBarRequest = true; } // just a safety catch if ( max < 20 ) max = 20; // enforce the open socket quota iff not admin and not from intranet if ( ! isAdmin && tcp->m_numIncomingUsed >= max && !tcp->closeLeastUsed()) { static long s_last = 0; static long s_count = 0; long now = getTimeLocal(); if ( now - s_last < 5 ) s_count++; else { log("query: Too many sockets open. Sending 500 " "http status code to %s. (msgslogged=%li)[2]", iptoa(s->m_ip),s_count); s_count = 0; s_last = now; } g_stats.m_closedSockets++;; return g_httpServer.sendErrorReply ( s , 500 , "Too many sockets open."); } // // yippy traffic control // if ( isYippySearch && s_yippySearchesOut >= ymax ) { // // log a note // bool banned = false; long yqLen; char *yq = hr.getString("query",&yqLen,NULL); if ( ! yq ) yq = ""; if( ! isYippyToolBarRequest && !g_autoBan.hasPerm(s->m_ip, NULL,0,//code, codeLen, 0,0,//uip, uipLen, s, &hr, NULL,//&testBuf, true ) ) { // justCheck )) banned = true; log("proxy: got banned search req cutoff %s (%s)", iptoa(s->m_ip),yq); } else log("proxy: got cutoff search req %s (%s)", iptoa(s->m_ip),yq); if ( ! banned ) { static long s_last = 0; static long s_count = 0; long now = getTimeLocal(); if ( now - s_last < 5 ) s_count++; else { log("query: Too many oustanding yippy search " "requests, %li. closing socket on %s. " "(repeats=%li)", ymax, iptoa(s->m_ip),s_count); s_count = 0; s_last = now; } } g_stats.m_closedSockets++; return g_httpServer.sendErrorReply ( s , 500 , "Too many search requests. " "Please reload your " "browser to try again."); } ///// // // who dare accesses us? manually or automatically... // ///// long USERID32 = 0; UserInfo *UI = NULL; // // the type of thing being accessed... // long accessType = getAccessType ( &hr ); // // the cost of the accesses in USD // float price = getPrice ( accessType ); // ////////// // // automated feed access verification // ////////// // did they provide an access code? long codeLen = 0; char *code = hr.getString("code", &codeLen, NULL); // only feed access supplies a userid in the http get request long userId32b = hr.getLong("userid",0); // if there is a code but no userid, it might be an old style // request from client, etc... so search for those guys by // code... if ( code && userId32b == 0 ) { // get the userid from the provided code... long nu = m_userInfoBuf.length()/sizeof(UserInfo); if ( nu > 10 ) nu = 10; UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart(); for ( long i =0 ; i < nu ; i++ ) { UserInfo *ui = &uis[i]; if ( strcmp ( ui->m_xmlFeedCode,code) ) continue; userId32b = ui->m_userId32; break; } // code is invalid if is not for an old client if ( userId32b == 0 ) code = NULL; } // if we have both a code and userid, check to see if it is correct if ( code ) { // get it UserInfo *ui = getUserInfoFromId ( userId32b ); // not found is bad! or if code does not match if ( price!=0.0 && (! ui || strcmp(ui->m_xmlFeedCode,code))){ char *msg = "Permission denied. Check your " "userid and code cgi parms."; return g_httpServer.sendErrorReply(s,500,msg); } // check funds. m_pending is a positive amount, so is price. // accountBalance is how much money they have in their acct float cur = 0.0; if ( ui ) cur = ui->m_accountBalance - ui->m_pending - price; if ( ui && cur < 0.0 && // we do not use the new system to track ... // we still need to bill them the old way ! ( ui->m_flags & (UIF_OLDUSER | UIF_ADMIN) ) ) { char *msg = "Not enough funds in account to " "process request."; return g_httpServer.sendErrorReply(s,500,msg); } // set this USERID32 = userId32b; // save it UI = ui; // otherwise, it is pending! //stC->m_pending = price; //ui->m_pending += price; } //////// // // paid manual access // //////// if ( ! code ) { // get logged in user, if any UserInfo *ui = getLoggedInUserInfo2 ( &hr , s , NULL ); // get access type if ( accessType == AT_SEARCHFEED_NEW ) price = 0.0; if ( accessType == AT_SEARCHFEED_OLD ) price = 0.0; // store it if ( ui ) USERID32 = ui->m_userId32; // save it UI = ui; } //////// // // the almighty autoban // //////// bool doAutoBan = true;//g_conf.m_doAutoBan; // if doing gigablast, only do autoban on PAGE_RESULTS if ( n != PAGE_RESULTS ) doAutoBan = false; // if they provided a valid code do not do autoban. if the code // was invalid we will have returned an error msg above if ( code ) doAutoBan = false; // assume not banned bool banned = false; // only check it for search results that have no valid "code" char testBufSpace[2048]; SafeBuf testBuf(testBufSpace, 2048); if ( doAutoBan ) { long uipLen; char *uip = hr.getString("uip", &uipLen, NULL); long ip = s->m_ip; bool good = g_autoBan.hasPerm(ip, NULL,0,//code, codeLen, uip, uipLen, s, &hr, &testBuf, false ); // justCheck if ( ! good ) banned = true; } // special yippy logging case if ( banned && isYippySearch ) { // log it long yqLen; char *yq = hr.getString("query",&yqLen,NULL); if ( ! yq ) yq = ""; log("proxy: got banned search req %s (%s)", iptoa(s->m_ip),yq); } // print the turing test if autoban gave us one if ( banned ) { g_msg = " (error: autoban rejected.)"; // this is the turing test i guess in testBuf if( testBuf.length() > 0 ) { printRequest(s, &hr); //g_stats.m_numSuccess++; return g_httpServer. sendDynamicPage(s, testBuf.getBufStart(), testBuf.length(), 0); } printRequest(s, &hr); //g_stats.m_numSuccess++; long rawFormat = hr.getLong("xml", 0); // was "raw" // support old raw=9 crap as well rawFormat = hr.getLong("raw",rawFormat); if ( rawFormat > 0 ) return g_httpServer.sendQueryErrorReply (s,402, mstrerror(EBUYFEED), rawFormat, g_errno, "You have exceeded the allowed " "amount of free searches. You can buy " "credits by creating an account at " "https://www.gigablast.com/account"); // if html format just return msg ... return g_httpServer.sendErrorReply(s,500,mstrerror(EBUYFEED)); } // hash the code long ch3 = 0; if ( code ) ch3 = hash32n ( code ); // . increment their "outstanding requests" count // . customers now have a limit of outstanding requests to // prevent abuse... vivisimo does not use "uip" and they // frequently get attacked by a spammer // . if autoban is off we still increment these counts, so if autoban // gets turned on the counts will be unaffected /* long bytesReceived = hr.getRequestLen(); bool overLimit = g_autoBan.incRequestCount ( ch3 , bytesReceived ); if ( g_conf.m_doAutoBan && overLimit ) { g_msg = " (error: too many outstanding requests)"; printRequest ( s , &hr ); // get how many bytes were sent out long bs; bool st = g_httpServer.sendErrorReply(s,500,g_msg,&bs); g_autoBan.decRequestCount ( ch3 , bs ); return st; } */ bool err2 = false; if ( err2 ) { hadError2: g_errno = ENOMEM; log("proxy: new(%i): %s",sizeof(StateControl), mstrerror(g_errno)); g_msg = " (error: out of memory.)"; printRequest(s, &hr); long bs; bool st; st=g_httpServer.sendErrorReply(s,500,mstrerror(g_errno),&bs); g_autoBan.decRequestCount ( ch3 , bs ); return s; } // if we get here that means we've got something to forward. StateControl *stC; try { stC = new (StateControl) ; } // return true and set g_errno if couldn't make a new File class catch ( ... ) { goto hadError2; } mnew ( stC, sizeof(StateControl), "Proxy"); // make a copy of this now if ( ! stC->m_hr.copy ( &hr ) ) { mdelete(stC,sizeof(StateControl),"Proxy"); delete(stC); goto hadError2; } // reset to -1 in case freeStateControl is called stC->m_hostId = -1; stC->m_slot = NULL; // store the code for decementing the oustanding request count below stC->m_ch = ch3; // support &xml=1 or &raw=9 or &raw=8 to indicate xml output is wanted stC->m_raw = hr.getLong ( "xml", 0 ); stC->m_raw = hr.getLong("raw",stC->m_raw); stC->m_s = s; stC->m_pageNum = n; stC->m_startTime = gettimeofdayInMilliseconds(); stC->m_isQuery = false; //check if we've got a query if ( n == PAGE_RESULTS ) stC->m_isQuery = true; stC->m_hash = hash32( hr.getRequest(), hr.getRequestLen() ); // assume we are not doing a search query (stripe load balancing) stC->m_stripe = -1; stC->m_isYippySearch = isYippySearch; // log it if ( isYippySearch ) { long yqLen; char *yq = hr.getString("query",&yqLen,NULL); if ( ! yq ) yq = ""; log("proxy: got ok search req %s (%s)", iptoa(s->m_ip),yq); } // yippy tcp proxy? long x; char *sendBuf ; long sendBufSize ; long sendBufUsed ; long msgTotalSize ; long timeout ; if ( g_isYippy ) { // make it sticky, based on ip unsigned long iph = hash32((char *)&stC->m_s->m_ip,4); x = iph % 4; char *hn[] = { "10.36.14.4" , // teaski1 "10.36.14.5" , // teaski2 "10.36.14.17" , // teaski3 "10.36.14.44" }; // teaski4 char *host = hn[x]; // debug //host = "www2.gigablast.com"; SafeBuf *sb = &stC->m_sb; sb->safeMemcpy(s->m_readBuf,s->m_readBufSize); sendBuf = sb->getBufStart(); sendBufSize = sb->length(); sendBufUsed = sb->length(); msgTotalSize = sb->length(); // make it a long time so we are less likely to overload // the teaski servers, wait for reply from reach one... timeout = 60 * 1000; // in milliseconds // note it //log("proxy: sending request \"%s\" to %s",sendBuf,host); static long s_reqNum = 0; stC->m_reqNum = s_reqNum; s_reqNum++; stC->m_start = gettimeofdayInMilliseconds(); // debug log debug //log("proxy: forwarding reqNum=%li from %s to %s", // stC->m_reqNum,iptoa(stC->m_s->m_ip),host); // only allow so many outstanding to avoid overloading // the teaski servers if ( stC->m_isYippySearch ) s_yippySearchesOut++; // forward to a teaski if ( ! g_httpServer.m_tcp.sendMsg ( host, // hostname gbstrlen(host), 80, sendBuf, sendBufSize, sendBufUsed, msgTotalSize, stC, gotTcpReplyWrapper, timeout, -1, -1)) return false; // it did not block, wtf? return true; } // we need to know how many terms (excluding synonyms) // so we can do stripe load balancing by number of query terms. // i.e. sending 3 queries of only one term each is about the // same as sending one larger query to a single stripe. char *qs = hr.getString("q",NULL); Query q; if ( qs ) q.set2 ( qs , langUnknown , false ); // 2 = autodetect bool // clear g_errno in case Query::set() set it g_errno = 0; // save it. might be zero! stC->m_numQueryTerms = q.getNumTerms(); Host *h = NULL; //if ( forward >= 0 ) // h = g_hostdb.getHost ( forward ); //if we want the main page, or if it is addurl, cannot send addurl to // another host if turing is on. Pick 1 host and keep sending to it if ( n == PAGE_REINDEX || n == PAGE_INJECT || n == PAGE_ADDURL || //n == PAGE_ROOT || // FOR DEBUG!! //n == PAGE_RESULTS || // FOR DEBUG!! n == PAGE_SITEDB ) // get host #0 h = g_hostdb.getHost ( 0 ); /* no longer - flurbit root page is the search page... else if ( n == PAGE_ADDURL || pathLen == 1 || ( pathLen == 11 && strncmp ( path , "/index.html" ,11 ) == 0 ) ){ long numTries = 0; while ( g_hostdb.isDead(m_mainHost) && numTries++ < g_hostdb.getNumHosts() ){ m_mainHost++; if ( m_mainHost >= g_hostdb.getNumHosts() ) m_mainHost = 0; } h = g_hostdb.getHost( m_mainHost ); m_numOutstanding[m_mainHost]++; } */ else h = pickBestHost ( stC ); // save in case of timeout below //stC->m_forward = -1;// forward; stC->m_retries = 0; stC->m_forwardHost = h; // . TODO: make both this and Multicast.cpp use a getTimeout() function // . default timeout to 8 seconds stC->m_timeout = 8 * 1000; // set the timeout long firstResult = hr.getLong("s", 0); long docsWanted = hr.getLong("n", 10); // how many docsids request? first 4 bytes of request. long rr = hr.getLong("rerank",-1); // . how many milliseconds of waiting before we re-route? // . 100 ms per doc wanted, but if they all end up // clustering then docsWanted is no indication of the // actual number of titleRecs (or title keys) read // . it may take a while to do dup removal on 1 million docs long long wait = 5000 + 100 * (docsWanted+firstResult); // those big UOR queries should not get re-routed all the time wait += 1000 * stC->m_numQueryTerms; // a min of 8 seconds is good if ( wait < 8000 ) wait = 8000; // seems like buzz is hammering the cluster and 0x39's are // timing out too much because of huge title recs taking // forever with Msg20 //if ( wait < 120000 ) wait = 120000; // never re-route if it has a rerank, those take forever if ( rr >= 0 ) wait = 3000 * 1000; // set it stC->m_timeout = wait; /////////////////////////////// // // HACK: PRICE GATEWAY INTERCEPTION // // . page add url first loads the main page, then it has some // ajax to re-get the same url but with &id=xxx where xxx != 0 // . so when gk144 gets a /addurl?id=123 request it will trigger // the injection and send back the injection status which the ajax // prints below the text box containing the url. the returned html // is just a little msg which the ajax sets the content of the msgbox // div to. // . so, we being the proxy will now pre-check any /addurl?id=123 // request to make sure they are logged in. if not we will just // return a simple, you need to login, msg // . and if signed in, make sure they have to $10 available otherwise // tell them they need to add money (check m_pending too) // . otherwise we have to let is pass through and look at the reply // msg from gk144. if it says "Success" then we deduct from their // m_accountBalance. otherwise we do not charge them... // . we should use this same logic for the seo html pages as well... // // // // . who is accessing what and for how much... // . we don't store "UI" since safebuf of UserInfos might realloc on us // stC->m_userId32 = USERID32; stC->m_accessType = accessType; stC->m_price = price; // if they have "code=" in the url then that is an xml feed and // we forawrd the request to the back-end right away now if ( code ) { // UI Must be non-NULL since code was valid UI->m_pending += price; return forwardRequest ( stC ); } // . forward any request besides addurl through // . this add url request is the one sent from the ajax on the add url // shell page because we check way above for "id" where cgiId is. //if ( accessType != AT_ADDURL ) { // do not charge for anything else! stC->m_price = 0; // for now the tool and free and you don't have to login //if ( UI ) UI->m_pending += price; return forwardRequest ( stC ); //} SafeBuf msg; float toolPrice = getPrice(AT_ADDURL); if ( ! UI ) msg.safePrintf("You need to " "login to use " "this tool. Each added url is $%.02f", toolPrice); if ( UI && UI->m_accountBalance - UI->m_pending - toolPrice<0 ) msg.safePrintf("The Add Url tool costs $%.02f and your " "account balance is below this. " "You need to add more funds" " to use this " "tool.", toolPrice); // . for every tool we ask "Are you sure? [yes] [no]" 1 or 0 // . must be confirmed long confirmed = hr.getLong("confirmed",0); if ( msg.length() == 0 && confirmed != 1 ) { // make another onclick ajax event msg.safePrintf("Are you sure you want to add this url " "for $%.02f? " //"" "" ,h32 ,rand64); } // // return the reason why the tool could not charge the user // if ( msg.length() ) { printRequest( s, &hr ); g_httpServer.sendDynamicPage ( s , msg.getBufStart(), msg.length(), // cachetime in secs // make it forever // to avoid hitting // back button and // reinjecting a url 86400*100 ); freeStateControl ( stC ); return true; } // assume the addurl goes through UI->m_pending += price; // send the request return forwardRequest ( stC ); } bool Proxy::forwardRequest ( StateControl *stC ) { // this was a function arg... now it is in "stC" Host *h = stC->m_forwardHost; stC->m_hostId = h->m_hostId; TcpSocket *s = stC->m_s; //log (LOG_DEBUG,"query: proxy: (hash=%lu) %s from " // "hostId #%li, port %i", stC->m_hash, hr.getRequest(), // h->m_hostId,h->m_httpPort); // if sending to the temporary network, add one to port long port = h->m_httpPort; if ( g_conf.m_useTmpCluster ) port += 1; // put ip at end of request char *req = s->m_readBuf; long reqSize = s->m_readOffset; // but then TcpServer.cpp leaves some room for a \0 and ip char *p = req + reqSize; // NULL terminate it *p = '\0'; p += 1; // then add in ip *(long *)p = s->m_ip; p += 4; bool isQCProxy = (g_hostdb.m_myHost->m_type & HT_QCPROXY); // . alter the request buffer // . set the please compress reply flag if ( isQCProxy && *req == 'G' ) *req = 'Z'; // update size reqSize = p - req; // sanity check if ( reqSize > s->m_readBufSize ) { char *xx=NULL;*xx=0;} // sanity check if ( h->m_isProxy ) { char *xx=NULL;*xx=0; } // if we are a QUERY COMPRESSION proxy send to the specified address long dstIp = h->m_ip; long dstPort = h->m_port; long dstId = h->m_hostId; if ( isQCProxy ) { dstIp = g_hostdb.m_myHost->m_forwardIp; dstPort = g_hostdb.m_myHost->m_forwardPort; dstId = -1; } // . default is to use the old engine. // . only precise=1 uses new engine. HttpRequest *hr = &stC->m_hr; bool sendToNewEngine = false; long precise = hr->getLong("precise",-1); if ( precise == 1 ) sendToNewEngine = true; // or if precise not given, an no code, send to new engine if ( precise == -1 && ! stC->m_ch ) sendToNewEngine = true; // if precise not given, and a code, sent to fast engine if ( precise == -1 && stC->m_ch ) sendToNewEngine = false; // explicit precise? if ( precise == 0 ) sendToNewEngine = false; // if no code (not a search feed) then do new engine //if ( ! stC->m_ch ) sendToNewEngine = true; #ifndef _USE_GK144_ sendToNewEngine = false; #endif // // . HACK: always send to gk144 unless code= is specified // . test on www2.gigablast.com proxy first... // if ( //! stC->m_ch && sendToNewEngine && stC->m_pageNum != PAGE_DIRECTORY && ! g_isYippy ) { dstIp = atoip("10.5.54.154",11); dstPort = 9000; // udp (not dns) dstId = -1; // not a host in our hosts.conf } // rewrite &xml=1 as &raw=8 so old search engine sends back xml if ( ! sendToNewEngine && req[0]=='G' && req[1]=='E' && req[2]=='T' && req[3] == ' ' ) { // replace &xml=1 in request with &raw=8 to support others char *p = req + 4; char *pend = req + reqSize; // skip GET for ( ; p < pend ; p++ ) { // stop after url is over if ( *p == ' ' ) break; // match? if ( p[0] != '?' && p[0] != '&' ) continue; if ( p[1] != 'x' ) continue; if ( p[2] != 'm' ) continue; if ( p[3] != 'l' ) continue; if ( p[4] != '=' ) continue; if ( p[5] != '1' ) continue; p[1] = 'r'; p[2] = 'a'; p[3] = 'w'; p[5] = '9'; break; } // code is invalid if is not for an old client //if ( userId32b == 0 ) code = NULL; } // . let's use the udp server instead because it quickly switches // to using eth1 if eth0 does two or more resends without an ACK, // and vice versa. this ensure that if a network switch fails then // we won't notice it besides a possible one-time 100ms delay. // . additionally, we can now accept tcp requests for admin pages // even if such requests come from the proxy ip! because now they // will just have to be from our ssh tunnel!! // . returns false and sets g_errno on error, true on success // . after resending the request 4 times with no ACK recv'd, call // it a EUDPTIMEDOUT error and deal with that below... bool status; status = g_udpServer.sendRequest ( req , reqSize , 0xfd , //msgType 0xfd for fwd dstIp , // h->m_ip , dstPort , // h->m_port , dstId , // h->m_hostId , NULL , // the slotPtr stC , // state gotHttpReplyWrapper , stC->m_timeout , -1 , // backoff -1 , // maxwait NULL , // replyBuf 0 , // replyBufMaxSize 0 , // niceness 4 );// maxResends // if no error, return false, we blocked if ( status ) return false; // wtf? if it fails unchage the pending... UserInfo *ui = getUserInfoFromId ( stC->m_userId32 ); if ( ui ) ui->m_pending -= stC->m_price; //bool status; /* status = g_httpServer.getDoc ( h->m_ip, port , // h->m_httpPort, s->m_readBuf, s->m_readOffset, stC, gotReplyWrapperPage, timeout, 10000000, 10000000, false ); // return false if it blocked if (!status) return false; */ //if not, we've got an error g_httpServer.sendErrorReply(s,500,mstrerror(g_errno)); freeStateControl(stC); // we send out what we read, s->m_readOffset bytes g_autoBan.decRequestCount ( stC->m_ch , s->m_readOffset ); return true; } void gotHttpReplyWrapper ( void *state, UdpSlot *slot ) { // TcpSocket *s ){ g_proxy.gotReplyPage(state,slot); } //void Proxy::gotReplyPage ( void *state, TcpSocket *s ){ void Proxy::gotReplyPage ( void *state, UdpSlot *slot ) { StateControl *stC = (StateControl *) state; char *reply = slot->m_readBuf; long size = slot->m_readBufSize; char *req = slot->m_sendBufAlloc; // . AND this is what we forwaded to a host in the flock // . we can free this because it reference the tcp buffer slot->m_sendBufAlloc = NULL; // decrement the oustanding request count g_autoBan.decRequestCount ( stC->m_ch , slot->m_readBufSize ); // . try another host if this one times out // . if it is dead before we send to it then it will not ACK our // requests, we will resend 10 times within about 300 ms and then // we will get slot->m_errno set to EUDPTIMEDOUT // . it will also set the errno to EUDPTIMEDOUT if the timeout we // gave sendRequest() above is reached. if ( slot->m_errno == EUDPTIMEDOUT && //stC->m_forward < 0 && // try this thrice i guess... hopefully we won't pick the same // host we did before! ++stC->m_retries <= 3 ) { // reduce the query load counts uncountStripe ( stC ); // pick another host! should NEVER return NULL Host *h = pickBestHost ( stC ); log("proxy: hostid #%li timed out. req=%s Rerouting " "forward request " "to hostid #%li instead.",stC->m_hostId, req,//stC->m_s->m_readBuf, h->m_hostId); // . try a resend! // . it will block or it will call sendErrorReply stC->m_forwardHost = h; forwardRequest ( stC );//, h ); // all done return; } // save it so we can free "reply" when state is destroyed stC->m_slot = slot; // save reply so we can free it when this state is freed // no i am just transferring into the socket's sendbuf now stC->m_slotReadBuf = NULL; //stC->m_slotReadBuf = slot->m_readBuf; //stC->m_slotReadBufMaxSize = slot->m_readBufMaxSize; // should we uncompress the reply? bool doUncompress = ( req[0] == 'Z' ); // do not allow regular proxy to uncompress it though! if ( ! (g_hostdb.m_myHost->m_type & HT_QCPROXY ) ) doUncompress=false; // don't let udp server free the reply, we forward this to end user slot->m_readBuf = NULL; // sanity check //if ( s->m_readOffset < 0 ) { char *xx=NULL;*xx=0; } if ( slot->m_readBufSize < 0 ) { char *xx=NULL;*xx=0; } long long nowms = gettimeofdayInMilliseconds(); //m_numOutstanding[stC->m_hostId]--; //if ( s->m_readOffset == 0 ){ if ( size == 0 ){ log (LOG_WARN,"query: Proxy: Lost the request"); // give a 500 httpstatus to just decrement UserInfo::m_pending addAccessPoint ( stC , nowms , 500 ); g_errno = EBADREQUEST; hadError: log(LOG_WARN,"proxy: error=%s req=%s",mstrerror(g_errno),req); g_httpServer.sendErrorReply(stC->m_s,500,mstrerror(g_errno)); freeStateControl(stC); return; } unsigned long long took = nowms - stC->m_startTime; // if reply was compressed then uncompress it if ( doUncompress ) { // sanity check if ( size < 12 ) { char *xx=NULL;*xx=0; } // parse it up unsigned char *p = (unsigned char *)reply; // get the sizes long need = *(long *)p; p += 4; // uncompressed total size long size1 = *(long *)p; p += 4; // size of compressed mime long size2 = *(long *)p; p += 4; // size of compressed content // note it //logf(LOG_DEBUG,"proxy: uncompressing from %li to %li", // size1+size2+12,need); // make the decompressed buf unsigned char *dbuf = (unsigned char *)mmalloc ( need,"pdbuf"); unsigned char *dend = dbuf + need; if ( ! dbuf ) goto hadError; unsigned char *dptr = dbuf; unsigned long bytes1 = dend - dptr; // ucompress the http mime int err1 = gbuncompress (dptr , &bytes1, p , size1 ); p += size1; dptr += bytes1; if ( size2 ) { unsigned long bytes2 = dend - dptr; // uncompress the http content int err2 = gbuncompress ( dptr , &bytes2, p , size2 ); p += size2; dptr += bytes2; if ( err2 != Z_OK || err1 != Z_OK ) { g_errno = EUNCOMPRESSERROR; goto hadError; } } // sanity check if ( dptr - dbuf != need ) { char *xx=NULL;*xx=0; } // free original compressed reply mfree ( reply , size , "origreply"); // now re-set these to the uncompressed mime/pagecontent reply = (char *)dbuf; size = need; // . and this is for freeing it after it is transmitted // . likewise, let's directly transmit this reply and // let udpserver free it when done //stC->m_slotReadBuf = (char *)dbuf; //stC->m_slotReadBufMaxSize = need; } // if we are a regular proxy forwarding a compressed reply to a // query compression proxy, then the reply is compressed, just leave // it alone if ( ( req[0] == 'Z' ) && (g_hostdb.m_myHost->m_type & HT_PROXY ) ) { // . now record the request in our accounting system. // . we assume the reply is error-free at this point // . it might be for a GET /seo too, not just stC->m_isQuery addAccessPoint ( stC , nowms , 200 ); // httpStatus //now should be able to print HttpRequest r; r.set(stC->m_s->m_readBuf, stC->m_s->m_readOffset, stC->m_s); // log the request printRequest(stC->m_s, &r, took, NULL,0);//content,contentLen); // add stat for stats graph if ( stC->m_isQuery ) { g_stats.logAvgQueryTime(stC->m_startTime); // i dont check if query is raw or not long color = 0x00b58869; if ( stC->m_raw ) color = 0x00753d30; long long nowms = gettimeofdayInMilliseconds(); // . add the stat // . use brown for the stat g_stats.addStat_r ( 0 , stC->m_startTime , nowms , //"query", color , STAT_QUERY ); // add to statsdb as well g_statsdb.addStat ( 0 , // niceness "query" , stC->m_startTime , nowms , stC->m_numQueryTerms ); g_stats.m_numSuccess++; } // forward it to the qcproxy. true -> do not re-compress! //g_httpServer.sendReply2(NULL,0,reply,size,stC->m_s,true); // let tcp server free it when done g_httpServer.m_tcp.sendMsg ( stC->m_s , reply , size , size , size , NULL , NULL ); // free mem freeStateControl(stC); return; } //char *reply = s->m_readBuf; //long size = s->m_readOffset; HttpMime mime; // re-store original mime from uncompressed mime mime.set ( reply, size, NULL); long httpStatus = mime.getHttpStatus(); if ( httpStatus != 200 ) g_msg = " (error: unknown.)"; // . now record the request in our accounting system. // . we assume the reply is error-free at this point // . it might be for a GET /seo too, not just stC->m_isQuery // . this should update the account balance addAccessPoint ( stC , nowms , httpStatus ); if ( stC->m_isQuery && httpStatus == 200 ){ g_stats.logAvgQueryTime(stC->m_startTime); // i dont check if query is raw or not long color = 0x00b58869; if ( stC->m_raw ) color = 0x00753d30; long long nowms = gettimeofdayInMilliseconds(); // . add the stat // . use brown for the stat g_stats.addStat_r ( 0 , stC->m_startTime , nowms , //"query", color , STAT_QUERY ); // add to statsdb as well g_statsdb.addStat ( 0 , // niceness "query" , stC->m_startTime , nowms , stC->m_numQueryTerms ); g_stats.m_numSuccess++; /*m_numSuccess++; m_totalQueryTime += took; if ( m_numSuccess % 20 == 0 ){ long avgTime = m_totalQueryTime / m_numSuccess; log ( LOG_INFO,"proxy: did the last %li successful " "queries in %llu ms, latency %li ms. Total " "queries %li", m_numSuccess,m_totalQueryTime,avgTime, m_numQueries ); } //we just log the last 2000 successful queries if ( m_numSuccess > 2000 ){ m_numSuccess = 0; m_totalQueryTime = 0; m_numQueries = 0; }*/ } else if ( stC->m_isQuery && httpStatus != 200 ) g_stats.m_numFails++; //now should be able to print HttpRequest r; r.set(stC->m_s->m_readBuf, stC->m_s->m_readOffset, stC->m_s); /* if ( g_conf.m_logQueryTimes ) logf( LOG_TIMING,"query: proxy: got back %li bytes page " "with status %li for request %s in %li ms", size, stC->m_hash, httpStatus, r.getRequest(), took );*/ //char *content = s->m_readBuf + mime.getMimeLen(); char *content = reply + mime.getMimeLen(); long contentLen = size - mime.getMimeLen(); printRequest(stC->m_s, &r, took, content,contentLen); /* char charset[1024]; memcpy ( charset, mime.getCharset(), mime.getCharsetLen() ); charset[mime.getCharsetLen()] = '\0'; char *contentType; switch ( mime.getContentType() ){ case CT_UNKNOWN : contentType = " "; break; case CT_HTML : contentType = "text/html"; break; case CT_TEXT : contentType = "text/plain"; break; case CT_XML : contentType = "text/xml"; break; case CT_PDF : contentType = "application/pdf"; break; case CT_DOC : contentType = "application/msword"; break; case CT_XLS : contentType = "application/vnd.ms-excel"; break; case CT_PPT : contentType = "application/mspowerpoint"; break; case CT_PS : contentType = "application/postscript"; break; } */ /* g_httpServer.sendDynamicPage ( stC->m_s , content , contentLen , 25 , // cachetime in secs // pick up key changes // this was 0 before false , // POSTREply? contentType, // content type -1 , // http status -1->200 // CRAP WHAT ABOUT COOKIE IN MIME??? NULL , // cookie charset ); */ /* g_httpServer.sendReply2 ( mime.getMime() , mime.getMimeLen() , content , contentLen , stC->m_s , false ); */ // . add the login bar to all pages we send back // . we could also use to automatically update copyright years // and add any common elements to every page... // . make a new reply to send back... // . it may free the old "reply" or it may set newReply=reply... long newReplySize = size; char *newReply = reply; // make sure it is HTTP/1.0 not HTTP/1.1 if ( reply[0] == 'H' && reply[1] == 'T' && reply[2] == 'T' && reply[3] == 'P' && reply[4] == '/' && reply[5] == '1' && reply[6] == '.' && reply[7] == '1' ) reply[7] = '0'; // do not print login bars in the xml!! do not print for ixquick // which gets results in html... if ( ! stC->m_raw && ! stC->m_ch && ! stC->m_isEventGuru ) newReply = storeLoginBar ( reply , size , // transmit size size , // allocsize // so we can quickly jump over the // mime in the reply... mime.getMimeLen() , //stC->m_s->m_userId32 , //stC->m_userId32, &newReplySize , &stC->m_hr ); // . try this one instead // . returns false if blocked TcpServer *tcp = &g_httpServer.m_tcp; // are we using ssl? if ( stC->m_s->m_ssl ) tcp = &g_httpServer.m_ssltcp; tcp->sendMsg ( stC->m_s , newReply , newReplySize , newReplySize , newReplySize , NULL, NULL); // do not let udpslot free that we are sending it off //slot->m_readBuf = NULL; freeStateControl(stC); } void freeStateControl ( StateControl *stC ){ if ( ! stC ) return; if ( ! g_isYippy && stC->m_hostId >= 0 ) { g_proxy.m_numOutstanding[stC->m_hostId]--; uncountStripe ( stC ); } // free the reply buffer if ( stC->m_slot && ! g_isYippy ) { // save reply so we can free it when this state is freed char *reply = stC->m_slotReadBuf; long size = stC->m_slotReadBufMaxSize; if ( reply ) mfree ( reply , size , "proxy" ); // do not double free! stC->m_slotReadBuf = NULL; } mdelete(stC,sizeof(StateControl),"Proxy"); delete(stC); } void uncountStripe ( StateControl *stC ) { // if stripe is -1, it was not a search query request long stripe = stC->m_stripe; if ( stripe < 0 ) return; // a more refined load balancing act g_proxy.m_termsOutOnStripe[stripe] -= stC->m_numQueryTerms; // dec this too g_proxy.m_queriesOutOnStripe[stripe]--; } // . now do stripe balancing // . this prevents one machine from receving all the Msg39 requests while its // twin gets none Host *Proxy::pickBestHost( StateControl *stC ) { // sanity check, for m_stripeLastHostId array size, which is only 8 now long numStripes = g_hostdb.getNumStripes(); if ( numStripes > MAX_STRIPES ) { char*xx=NULL;*xx=0; } // see which stripes have non-dead hosts! char stripeDead[MAX_STRIPES]; bool allDead = true; memset ( stripeDead , 1 , MAX_STRIPES ); long nh = g_hostdb.getNumHosts(); for ( long i = 0 ; i < nh ; i++ ) { Host *h = g_hostdb.getHost(i); if ( g_hostdb.isDead ( h ) ) continue; // hey, we are not all dead! allDead = false; // clear it stripeDead [ h->m_stripe ] = 0; } // . get the stripe with the least # of outstanding query terms // . in the event of a tie, give each stripe an equal shot so we // balance out the wear-n-tear on the drives. //long mini = m_nextStripe; //long min = m_termsOutOnStripe[mini]; //bool tied = false; // start at this stripe long ns = m_nextStripe; long min ; long minns = -1; for ( long i = 0 ; i < numStripes ; i++ ) { // get stripe number if ( ++ns >= numStripes ) ns = 0; // skip if whole stripe is dead, and other stripes are not dead if ( stripeDead[ns] && ! allDead ) continue; // how loaded is this stripe? long termsOut = m_termsOutOnStripe[ns]; // skip if his load is tied or higher than our current winner if ( minns != -1 && termsOut >= min ) continue; // got a new winner minns = ns; min = termsOut; } // sanity check if ( minns == -1 ) { char *xx=NULL;*xx=0; } // rotate the prefered next stripe if ( ++m_nextStripe >= numStripes ) m_nextStripe = 0; // find the next host in line for stripe #minns long bestHostId = m_stripeLastHostId[minns]; // count iterations //long count = 0; loop: // inc it, wrap it if ( ++bestHostId >= g_hostdb.getNumHosts() ) bestHostId = 0; // do not do infinite loops //if ( count++ >= g_hostdb.getNumHosts() ) { // log ("proxy: all hosts are dead. critical error."); // g_errno = EBADENGINEER; // g_httpServer.sendErrorReply(stC->m_s,500,mstrerror(g_errno)); // freeStateControl(stC); // return; //} // skip if dead if ( g_hostdb.isDead( bestHostId ) && ! allDead ) goto loop; // get the host Host *h = g_hostdb.getHost ( bestHostId ); // saity check if ( h->m_isProxy ) { char *xx=NULL;*xx=0; } // advance until it is from the least-loaded stripe if ( h->m_stripe != minns ) goto loop; // add query terms to it for load balancing purposes m_termsOutOnStripe[minns] += stC->m_numQueryTerms; // inc this too m_queriesOutOnStripe[minns]++; // save it m_stripeLastHostId[minns] = bestHostId; // store ino into state so we can reduce the count when we get a reply stC->m_stripe = minns; //stC->m_numQueryTerms = numQueryTerms; // return it return g_hostdb.getHost( bestHostId ); } /* Host *Proxy::pickBestHost( ) { long bestHost = m_lastHost; bestHost++; if ( bestHost >= g_hostdb.getNumHosts() ) bestHost = 0; //check if the host is dead. if dead cycle through for a live host //Also check if it has got outstanding requests long numTried = 0; while ((m_numOutstanding[bestHost]>0 || g_hostdb.isDead(bestHost)) && numTried < 10 ){ bestHost++; if ( bestHost >= g_hostdb.getNumHosts() ) bestHost = 0; numTried++; //This could go into an infinite loop if no hosts are on //so adding numTried here too. Of course if no hosts are //on, we're gonna lose the request while ( numTried < 10 && g_hostdb.isDead( bestHost ) ){ bestHost++; if ( bestHost >= g_hostdb.getNumHosts() ) bestHost = 0; numTried++; } } m_lastHost = bestHost; m_numOutstanding[bestHost]++; Host *h = g_hostdb.getHost( bestHost ); return h; } */ void Proxy::printRequest(TcpSocket *s, HttpRequest *r, unsigned long long took , char *content, long contentLen ) { //LOG THE REQUEST /* // . if it is a post request, log the posted data, too char cgi[20058]; cgi[0] = '\0'; if ( r->isPOSTRequest() ) { long plen = r->m_cgiBufLen; if ( plen >= 20052 ) plen = 20052; char *pp1 = cgi ; char *pp2 = r->m_cgiBuf; // . when parsing cgi parms, HttpRequest converts the // &'s to \0's so it can avoid having to malloc a // separate m_cgiBuf // . now it also converts ='s to 0's, so flip flop back // and forth char dd = '='; for ( long i = 0 ; i < plen ; i++ , pp1++, pp2++ ) { if ( *pp2 == '\0' ) { *pp1 = dd; if ( dd == '=' ) dd = '&'; else dd = '='; continue; } if ( *pp2 == ' ' ) *pp1 = '+'; else *pp1 = *pp2; } if ( r->m_cgiBufLen >= 20052 ) { pp1[0]='.'; pp1[1]='.'; pp1[2]='.'; pp1 += 3; } *pp1 = '\0'; } */ // get time format: 7/23/1971 10:45:32 time_t tt = getTimeLocal(); struct tm *timeStruct = localtime ( &tt ); char bufTime[64]; strftime ( bufTime , 63 , "%b %d %T", timeStruct); //char *ref = r->getReferer (); // if autobanned and we should not log, return now if (g_msg&&!g_conf.m_logAutobannedQueries && strstr(g_msg,"autoban")){ g_msg = ""; return; } /* // fix cookie for logging char cbuf[5000]; char *pc = r->m_cookiePtr; long pclen = r->m_cookieLen; if ( pclen >= 4998 ) pclen = 4998; char *pcend = r->m_cookiePtr + pclen; char *dst = cbuf; for ( ; pc < pcend ; pc++ ) { *dst = *pc; if ( ! *pc ) *dst = ';'; dst++; } *dst = '\0'; if ( ! cgi[0] ) logf (LOG_INFO,"http: %s %s %s %s cookie=\"%s\" %s %s", bufTime,iptoa(s->m_ip),r->getRequest(), ref,cbuf,r->getUserAgent(),g_msg); else logf (LOG_INFO,"http: %s %s %s %s %s cookie=\"%s\" %s %s", bufTime,iptoa(s->m_ip),r->getRequest(), cgi,ref,cbuf,r->getUserAgent(),g_msg); */ char *req = s->m_readBuf; //long reqLen = s->m_readOffset; logf (LOG_INFO,"http: %s %s %s %s", bufTime,iptoa(s->m_ip),req,//r->getRequest(), g_msg); //reset g_msg g_msg = ""; if ( (long)took < g_conf.m_logQueryTimeThreshold ) return; if ( ! g_conf.m_logQueryReply || ! content || contentLen <= 0 ) { logf (LOG_INFO,"http: Took %llu ms " "(len=%li bytes) " "for request %s", took, contentLen, r->getRequest()); return; } // copy into buf char *p = (char *)mmalloc ( contentLen+1,"proxycont"); if ( ! p ) return; for ( long i = 0 ; i < contentLen ; i++ ) { if ( content[i] && ! is_binary_a(content[i]) ) { p[i]=content[i]; continue; } // fix 0's and binary stuff p[i]='?'; } // null terminate p[contentLen]=0; logf (LOG_INFO,"http: Took %llu ms " "(len=%li bytes) " "for request %s reply=%s", took, contentLen, r->getRequest(),content); mfree ( p , contentLen+1, "proxycont"); } // for yippy only! void gotTcpReplyWrapper ( void *state , TcpSocket *s ) { class StateControl *stC = (StateControl *)state; // i guess s->m_sendBuf is in StateControl::m_sb safebuf so // avoid double free. it directly called TcpServer::sendMsg which // does not do a copy like httpserver s->m_sendBuf = NULL; if ( stC->m_isYippySearch ) s_yippySearchesOut--; // get the reply from the teaski machine char *reply = s->m_readBuf; long replySize = s->m_readOffset; //long long took = gettimeofdayInMilliseconds() - stC->m_start; if ( ! reply ) { g_errno = EBADREPLY; replySize = 0; char ipbuf[64]; sprintf(ipbuf,"%s",iptoa(s->m_ip)); char ipbuf2[64]; sprintf(ipbuf2,"%s",iptoa(stC->m_s->m_ip)); char *creq = ""; if ( stC->m_s && stC->m_s->m_readBuf && stC->m_s->m_readOffset ) creq = stC->m_s->m_readBuf; log("proxy: got a zero length reply from %s. err=%s " "client=%s clientreq=%s", ipbuf, mstrerror(g_errno), ipbuf2, creq ); // debug log debug //log("proxy: returning reply to %s replysize=%li " // "reqnum=%li (took=%llims)", // iptoa(stC->m_s->m_ip), // replySize,stC->m_reqNum,took); g_httpServer.sendErrorReply(stC->m_s,500,mstrerror(g_errno)); freeStateControl(stC); return; } if ( g_errno ) { log("proxy: got error in reply from %s. err=%s", iptoa(s->m_ip),mstrerror(g_errno)); // debug log debug //log("proxy: returning reply to %s replysize=%li " // "reqnum=%li (took=%llims) (err=%s)", // iptoa(stC->m_s->m_ip), // replySize,stC->m_reqNum,took,mstrerror(g_errno)); g_httpServer.sendErrorReply(stC->m_s,500,mstrerror(g_errno)); freeStateControl(stC); return; } /* // debug log debug long max; char c; if ( reply ) { max = 1500; if ( replySize < max ) max = replySize; max--; if ( max < 0 ) max = 0; c = reply[max]; reply[max] = 0; } //log("proxy: returning reply back to client. size=%li reply=%s", // replySize,reply); log("proxy: returning reqNum=%li for %s replysize=%li " "(took=%llims)", stC->m_reqNum, iptoa(stC->m_s->m_ip), replySize,took); if ( reply ) reply[max] = c; */ // . forward the teaski reply back to client's browser // . it should free the reply buf when done g_httpServer.sendReply2(NULL, // mime 0, // mimelen reply, // content replySize, // contentlen stC->m_s, true); // already compressed? freeStateControl(stC); } /////////////////////////// // // USER ACCOUNTING FUNCTIONS // /////////////////////////// // every day has a summary rec class SummaryRec { public: long m_userId32; char m_accessType; // how many times this access type was done: long m_numAccesses; // how much user was charged for all these accesses: float m_totalCost; // how long all replies took in milliseconds: long long m_totalProcessTime; char m_month; char m_day; short m_year; }; #define DRF_DEPOSIT 1 #define DRF_WITHDRAW 2 #define DRF_WITHDRAW_FEE 3 // every time a deposit or withdrawal is made we have one of these class DepositRec { public: long m_userId32; float m_depositAmount; long m_depositDate; // . use transactionid for doing CREDITs back to user // . i've seen it > 5B so use a long long long long m_authorizeNetTransactionId; long m_flags; }; // returns NULL and sets g_errno on error UserInfo *Proxy::getUserInfoForFeedAccess ( HttpRequest *hr ) { // assume no error g_errno = 0; //char *user = hr->getString("user",NULL); // we also store the username along with session id //if ( ! user ) user = r->getStringFromCookie("user",NULL); long userId32 = hr->getLong("userid",0); char *code = hr->getString("code",NULL); // if no userid or code given, just rely on autoban then, // so do not set g_errno, just return NULL if ( ! userId32 && ! code ) return NULL; // allow others to just specify their codes to get // results and not the userid if ( ! userId32 && code ) { UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart(); long ni = m_userInfoBuf.length() / sizeof(UserInfo) ; for ( long i = 0 ; i < ni && i < 5 ; i++ ) { // shortcut UserInfo *ui = &uis[i]; // must be an "old" user like others if ( ! (ui->m_flags & (UIF_OLDUSER|UIF_ADMIN))) continue; // then check for code match if ( strcmp(code,ui->m_xmlFeedCode) ) continue; // matched! return ui; } // they gave a code, amd no userid, and their code was // unmatched... that's perm denied g_errno = EPERMDENIED; return NULL; } UserInfo *ui = getUserInfoFromId ( userId32 ); // if user name has no record, that's permission denied if ( ! ui ) { log("proxy: user not found for userid=%li",userId32); g_errno = EPERMDENIED; return NULL; } // codes must match, too! if ( ! code || strcmp(code,ui->m_xmlFeedCode) ) { g_errno = EPERMDENIED; log("proxy: permission denied for userid=%li code=%s", userId32,code); return NULL; } return ui; } long Proxy::getAccessType ( HttpRequest *hr ) { char *path = hr->getPath(); long pathLen = hr->getPathLen(); char c = path[pathLen]; path[pathLen] = '\0'; long accessType = 0; if ( strncmp(path,"/addurl",7) == 0 ) { // assume old accessType = AT_ADDURL; } bool isSearch = false; // old access method (in Xml.cpp/Pages.cpp) if ( strncmp(path,"/cgi/0.cgi?",11) == 0 ) // assume old isSearch = true; // another form in Xml.cpp/Pages.cpp if ( strncmp(path,"/index.php?",11) == 0 ) // assume old isSearch = true; // searching the old index? if ( strncmp(path,"/search?",8) == 0 ) isSearch = true; // default will be fast search if ( isSearch ) { // assume fast search accessType = AT_SEARCHFEED_OLD; // new index search? if ( hr->getLong("precise",0)==1) accessType = AT_SEARCHFEED_NEW; } // seo page? if ( strncmp(path,"/seo?",5) == 0 ) { char *page = hr->getString("page",NULL); if ( page && strcmp(page,"matchingqueries") == 0 ) accessType = AT_MATCHING_QUERIES; if ( page && strcmp(page,"competitorpages") == 0 ) accessType = AT_COMPETITOR_PAGES; if ( page && strcmp(page,"competitorbacklinks") == 0 ) accessType = AT_COMPETITOR_BACKLINKS; if ( page && strcmp(page,"relatedqueries") == 0 ) accessType = AT_RELATED_QUERIES; if ( page && strcmp(page,"missingterms") == 0 ) accessType = AT_MISSING_TERMS; } // revert path[pathLen] = c; return accessType; } // in dollars! float Proxy::getPrice ( long accessType ) { if ( accessType == 0 ) return 0.0; if ( accessType == AT_SEARCHFEED_OLD ) // $1 CPM return 1.00 / 1000.0; if ( accessType == AT_SEARCHFEED_NEW ) // $2.50 CPM return 2.50 / 1000.0; if ( accessType == AT_MATCHING_QUERIES ) return 9.99; // $10 if ( accessType == AT_COMPETITOR_PAGES ) return 9.99; // $10 if ( accessType == AT_COMPETITOR_BACKLINKS ) return 9.99; // $10 if ( accessType == AT_RELATED_QUERIES ) return 14.99; // $15 if ( accessType == AT_MISSING_TERMS ) return 19.99; // $20 if ( accessType == AT_ADDURL ) return 4.99; // $10 char *xx=NULL;*xx=0; return 0.0; } // . return false with g_errno set on error, true otherwise // . when a reply has been generated we call this to accumulate stats // . we update the SummaryRec and UserInfo record, as well as adding a // record to statsdb! // . make the proxy run on ssds for extra reliability // . accesses are also logged by back-end machines in case we need // to rebuild access points, however, we'd lose CC info etc. // . so backup userdb periodically maybe using cron to gk37 or something. // . now they must have "&user=username&code=sdfsdfdfs" // . processTime is in milliseconds (ms) bool Proxy::addAccessPoint ( StateControl *stC , long long nowms , long httpStatus ) { HttpRequest *hr = &stC->m_hr; //UserInfo *ui = getUserInfoForFeedAccess ( hr ); // it might be an XML feed access OR an add url or seo tool access UserInfo *ui = getUserInfoFromId ( stC->m_userId32 ); // ALWAYS CALL THIS! if ( ui ) ui->m_pending -= stC->m_price; // wtf? if no code or no user or incorrect code, it will return NULL // with g_errno set on error if ( ! ui && ! g_errno ) return true; // error? wtf... if ( g_errno ) return false; // error getting results? do not charge for it then... if ( httpStatus != 200 ) { log("proxy: got http reply error status=%li",httpStatus); return true; } char accessType = getAccessType ( hr ); // no cost? if ( accessType == 0 ) return true; return addAccessPoint2 ( ui , accessType , nowms , stC->m_startTime ); } bool Proxy::addAccessPoint2 ( UserInfo *ui , char accessType , long long nowms , long long startTime ) { if ( ! ui ) { log("proxy: addaccesspoint2 ui was NULL!" ); return false; } float price = getPrice ( accessType ); // get the summary rec for this day and user... SummaryRec *sr = getSummaryRec ( ui->m_userId32 , accessType ); // wtf? error! if ( ! sr ) return false; // the account balance of course ui->m_accountBalance -= price; long long processTime = nowms - startTime; // stC->m_startTime; // increment counters, all else should be fixed by getSummaryRec() sr->m_numAccesses++; sr->m_totalProcessTime += processTime; sr->m_totalCost += price; // // . a new statsdb key/data type i guess // . but now let's add // g_statsdb.addStat ( 0 , // niceness "query" , startTime , nowms , processTime , // value // set parmHash to 1 so we add oldVal/newVal 0 , 0 , // oldval 0 ,// newval // this is unique for each user! and must NOT // be recycled in the event you delete a user... ui->m_userId32 ); return true; } // . use Proxy::m_sumBuf to hold the summary recs // . one summaryrec per day/user/accesstype tuple so they can see how many // queries they did of each type per day SummaryRec *Proxy::getSummaryRec ( long userId32 , char accessType ) { // . get epoch time utc. make sure proxy time is always in sync. // . add that to Process.cpp to check to make sure time sync server // process is running! long now = getTimeLocal(); static long s_nextDay = -1; static long s_thisDay = 0; static long s_thisMonth = 0; static long s_thisYear = 0; if ( s_nextDay == -1 || now > s_nextDay ) { struct tm *timeStruct = gmtime ( &now ); s_thisDay = timeStruct->tm_mday; s_thisMonth = timeStruct->tm_mon+1; // 0..11 so make it 1..12 s_thisYear = timeStruct->tm_year + 1900; long elapsed = timeStruct->tm_min * 60 + timeStruct->tm_sec; long left = 86400 - elapsed; s_nextDay = now + left; } // make a unique key for this summary rec, one per day per user unsigned long long h64 = (unsigned long)userId32; unsigned long t = s_thisYear; t <<= 16; t |= s_thisMonth; t <<= 8; t |= s_thisDay; t <<= 8; t |= accessType; h64 <<= 32; h64 |= t; // use hashtable of summary rec ptrs long *sumOffPtr; sumOffPtr = (long *)m_srht.getValue ( &h64 ); //SummaryRec **srp = (SummaryRec **)m_srht.getValue ( &h64 ); // if there, return it! //if ( srp ) return *srp; if ( sumOffPtr ) { SummaryRec *sr; sr = (SummaryRec *)(m_sumBuf.getBufStart() + *sumOffPtr); return sr; } // // otherwise, we gotta make one! // // g_errno should be set if this fails! if ( ! m_sumBuf.reserve ( sizeof(SummaryRec) ) ) return NULL; // ref it SummaryRec *sr = (SummaryRec *)m_sumBuf.getBuf(); // init it memset(sr,0,sizeof(SummaryRec)); sr->m_accessType = accessType; sr->m_year = s_thisYear; sr->m_month = s_thisMonth; sr->m_day = s_thisDay; sr->m_totalCost = 0; sr->m_totalProcessTime = 0; sr->m_userId32 = userId32; // advance it m_sumBuf.incrementLength ( sizeof(SummaryRec) ); long sumOff = (long)(((char *)sr) - m_sumBuf.getBufStart()); // hash it if ( ! m_srht.addKey ( &h64 , &sumOff ) ) return NULL; return sr; } ////////////////// // // PRINT THE USER INFO // ////////////////// class StateUser { public: //long m_errno; TcpSocket *m_socket; //Msg0 m_msg0; //Msg4 m_msg4; long long m_sessionId64; long m_userId32; //HttpRequest m_hr; SafeBuf m_sb; SafeBuf m_sb2; SafeBuf m_authNetMsg; bool m_transactionSuccessful; bool m_attemptedTransaction; float m_deposit; // dollar amount float m_refund; // dollar amount (95%) float m_refundFee; // 5% long long m_refundTransId; //bool m_doWithdraw; //float m_deposit; // authorize.net's reply TcpSocket *m_docSocket; HttpRequest m_hr; long m_submittingNewUser; // is this really the admin logged in as another user? bool m_isAdmin; long long m_adminSessId; long m_adminId; // for holding error msg and pointing m_depositErr to it SafeBuf m_tmpBuf1; char m_tmpBuf2[20]; // /// set these from get request // char *m_user; char *m_pwd; char *m_pwd2; char *m_ccNum; char *m_ccType; char *m_cvv; char *m_exp; char *m_fc; //float m_deposit; char *m_email; char *m_phone; char m_terms; // european stuff char *m_firstName; char *m_lastName; char *m_city; char *m_state; char *m_country; char *m_zip; char *m_address; char *m_userError; char *m_pwdError; char *m_pwd2Error; char *m_ccNumError; char *m_ccTypeError; char *m_cvvError; char *m_expError; char *m_fcError; char *m_depositError; char *m_refundError; char *m_emailError; char *m_phoneError; char *m_termsError; //long m_transactionId; long m_error; }; char *getAccessTypeString ( long at ) { if ( at == AT_SEARCHFEED_OLD ) return "fast search feed"; if ( at == AT_SEARCHFEED_NEW ) return "precise search feed"; if ( at == AT_COMPETITOR_BACKLINKS ) return "competitor backlinks tool"; if ( at == AT_RELATED_QUERIES ) return "related queries tool"; if ( at == AT_MISSING_TERMS ) return "missing terms tool"; if ( at == AT_ADDURL ) return "add url tool"; return "unknown access type"; } void gotGifWrapper ( void *su ) { g_proxy.gotGif ( (StateUser *)su ); } // userId32 is always < 0x7fffffff to avoid sign bit issues UserInfo *Proxy::getUserInfoFromId ( long userId32 ) { UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart(); long ni = m_userInfoBuf.length() / sizeof(UserInfo) ; // we added 1 to the userid32 to avoid using 0 if ( userId32-1 >= ni ) return NULL; if ( userId32-1 < 0 ) return NULL; return &uis[userId32-1]; } UserInfo *Proxy::getLoggedInUserInfo2 ( HttpRequest *hr , TcpSocket *socket, SafeBuf *errmsg ) { // are they logging in for the first time, or carrying a sessionid // in their cookie? char *login = hr->getString("login",NULL); char *password = hr->getString("password",NULL); // userid32 is used with sessionid long long sessionId64 = hr->getLongLongFromCookie("sessionid",0LL); long userId32 = hr->getLongFromCookie("userid",0); // if supplying "user", then they must also supply "pwd"! if ( ! password ) login = NULL; // print login page? if ( ! login && ! sessionId64 ) return NULL; // let this override in case sessionid expires if ( login ) sessionId64 = 0LL; long now = getTimeLocal(); // try to match user password or session id UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart(); long ni = m_userInfoBuf.length() / sizeof(UserInfo) ; UserInfo *ui; long i; for ( i = 0 ; i < ni ; i++ ) { // shortcut ui = &uis[i]; // login with existing session id? if ( sessionId64 ) { // check it if ( ui->m_lastSessionId64 != sessionId64 ) continue; // userid32 must match too! if ( ui->m_userId32 != userId32 ) continue; // check time though. give them 10 minutes... if ( now - ui->m_lastActionTime > 10*60 ) { if ( errmsg ) errmsg->safePrintf("Session expired. " "Please " "re-login."); sessionId64 = 0; return NULL; } // update timestamp so it is 10 minutes since time // of LAST action... ui->m_lastActionTime = getTimeLocal(); return ui; //break; } // . login? "user" must be non-null, and "pwd" too // . skip if no username match if ( strcmp ( ui->m_login , login ) ) continue; // if pwd does not match, stop! error! if ( strcmp ( ui->m_password, password ) ) break; // ok, i guess password matched, set a session id // assign a sessionid now. make it always positive! long long1 = rand() % 0x7fffffff; long long2 = rand() % 0x7fffffff; unsigned long long newSessionId64 = long1; newSessionId64 <<= 32; newSessionId64 |= long2; // ensure not 0 if ( newSessionId64 == 0 ) newSessionId64 = 1; // add this session if to rec and re-add ui->m_lastSessionId64 = newSessionId64; ui->m_lastActionTime = getTimeLocal(); ui->m_lastLoginIP = socket->m_ip; // save session id! g_proxy.saveUserBufs(); return ui; } if ( errmsg ) errmsg->safePrintf("Login error. Incorrect username " "or password."); return NULL; } // call this once at start of functions in sendPageAccount() UserInfo *Proxy::getLoggedInUserInfo ( StateUser *su , SafeBuf *errmsg ) { HttpRequest *hr = &su->m_hr; TcpSocket *socket = su->m_socket; // reset shit su->m_userId32 = -1; su->m_sessionId64 = 0; su->m_isAdmin = false; su->m_adminSessId = 0LL; su->m_adminId = 0; UserInfo *ui = getLoggedInUserInfo2 ( hr , socket , errmsg ); if ( ! ui ) return NULL; // ok, it's good! su->m_userId32 = ui->m_userId32; su->m_sessionId64 = ui->m_lastSessionId64; // are they really the admin, logged in as a user? long long asi = hr->getLongLongFromCookie("adminsessid",0LL); if ( ! asi ) return ui; // admin IP be local ip for security! if ( ! hr->m_isLocal ) return ui; // see if it matches UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart(); long ni = m_userInfoBuf.length() / sizeof(UserInfo) ; long i; for ( i = 0 ; i < ni ; i++ ) { // shortcut UserInfo *ui = &uis[i]; // skip if not admin if ( ! ( ui->m_flags & UIF_ADMIN ) ) continue; // check it if ( ui->m_lastSessionId64 != asi ) continue; // got a match su->m_isAdmin = true; // save the underlying admin user info su->m_adminSessId = asi; su->m_adminId = ui->m_userId32; } return ui; } void removeSpaceTrails ( char *s , long maxBytes ) { //char *end = s + gbstrlen(s) - 1; //while ( *end == ' ' ) { // *end = '\0'; // end--; //} if ( ! s ) return; // empty already? if ( ! *s ) return; // remove all spaces now too! char *src = s; char *dst = s; char *max = s + maxBytes - 1; for ( ; *src ; src++ ) { if ( *src == ' ' ) continue; *dst = *src; dst++; if ( dst >= max ) break; } *dst = '\0'; return; } bool Proxy::doesUsernameExist ( char *user ) { UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart(); long ni = m_userInfoBuf.length() / sizeof(UserInfo) ; long i; for ( i = 0 ; i < ni ; i++ ) { // shortcut UserInfo *ui = &uis[i]; // skip if no match if ( strcmp ( ui->m_login , user ) ) continue; return true; } return false; } void setFieldErrors ( StateUser *su ) { // is this submitting a new user? HttpRequest *hr = &su->m_hr; long new1 = hr->getLong("new",0); // check email address format bool hadAt = false; bool hadFirstChar = false; bool badChar = false; bool hadCharAfterAt = false; bool hadDot = false; bool hadCharAfterDot = false; for ( char *p = su->m_email ; *p ; p++ ) { if ( ! hadAt && is_alnum_a(*p) ) hadFirstChar = true; if ( ! is_ascii(*p) ) badChar = true; if ( *p == '@' ) hadAt = true; if ( hadAt && is_alnum_a(*p ) ) hadCharAfterAt = true; if ( hadCharAfterAt && *p =='.' ) hadDot = true; if ( hadDot && is_alnum_a(*p) ) hadCharAfterDot = true; } if ( ! hadAt || ! hadFirstChar || badChar || ! hadCharAfterAt || ! hadDot || ! hadCharAfterDot ) { su->m_emailError = "Bad email address"; su->m_error = 1; } // check phone # long numDigits = 0; bool hadAlpha = false; bool badPhoneChar = false; for ( char *p = su->m_phone ; *p ; p++ ) { if ( is_alpha_a(*p) ) hadAlpha = true; if ( is_digit(*p) ) { numDigits++; continue; } if ( *p == '(' ) continue; if ( *p == ')' ) continue; if ( *p == '-' ) continue; if ( *p == ' ' ) continue; badPhoneChar = true; } if ( badPhoneChar ) { su->m_phoneError = "Use only digits and ( , ) " "or - in the phone number"; su->m_error = 1; } if ( hadAlpha || numDigits < 7 ) { su->m_phoneError = "Bad phone number"; su->m_error = 1; } // check username bool badUserChar = false; for ( char *p = su->m_user ; *p ; p++ ) { if ( ! is_alnum_a(*p) ) badUserChar = true; } if ( badUserChar ) { su->m_userError = "Username can only contain letters " "and numbers in ASCII"; su->m_error = 1; } if ( ! su->m_user[0] ) { su->m_userError = "Username is required"; su->m_error = 1; } if ( ! su->m_pwd[0] ) { su->m_pwdError = "Password required"; su->m_error = 1; } if ( ! su->m_pwd2[0] ) { su->m_pwd2Error = "Repeated password required"; su->m_error = 1; } if ( ! su->m_email[0] ) { su->m_emailError = "Email address required"; su->m_error = 1; } if ( ! su->m_phone[0] ) { su->m_phoneError = "Phone number required"; su->m_error = 1; } // check cvv bool badCVVChar = false; for ( char *p = su->m_cvv ; *p ; p++ ) { if ( ! is_digit(*p) ) badCVVChar = true; } if ( badCVVChar ) { su->m_cvvError = "CVV can only be digits, no spaces"; su->m_error = 1; } if ( ! su->m_cvv[0] ) { su->m_cvvError = "cvv is required"; su->m_error = 1; } // check ccnum bool badCC = false; for ( char *p = su->m_ccNum ; *p ; p++ ) { if ( ! is_digit(*p) && *p != ' ' && *p != '*' && *p != '-' ) badCC = true; } if ( badCC ) { su->m_ccNumError = "Credit Card Number can only " "have digits spaces and hyphens in it"; su->m_error = 1; } if ( ! su->m_ccNum[0] ) { su->m_ccNumError = "credit card # is required"; su->m_error = 1; } // MM/YY long digitsBefore = 0; bool hadSlash = false; long digitsAfter = 0; bool badExpChar = false; for ( char *p = su->m_exp ; *p ; p++ ) { if ( ! is_digit(*p) && *p != '/' ) badExpChar = true; if ( ! hadSlash && is_digit(*p) ) digitsBefore++; if ( *p == '/' ) hadSlash = true; if ( hadSlash && is_digit(*p) ) digitsAfter++; } if ( digitsBefore <= 0 || digitsBefore > 2 || ! hadSlash || digitsAfter != 2 ) { su->m_expError = "Format must be \"MM/YY\" where " "MM is the month number and YY is the year " "number"; su->m_error = 1; } long elen = 0; if ( su->m_exp ) elen = gbstrlen(su->m_exp); if ( ! su->m_expError[0] && elen >= 4 ) { long yy = atoi(su->m_exp + elen-2); if ( yy < 13 ) { su->m_expError = "Expiration year can not be " "before 2013"; su->m_error = 1; } } if ( ! su->m_exp[0] ) { su->m_expError = "credit card expiration is required"; su->m_error = 1; } if ( ! su->m_ccType[0] ) { su->m_ccTypeError = "credit card type is required"; su->m_error = 1; } if ( ! su->m_fc[0] ) { su->m_fcError = "xml feed pass code is required"; su->m_error = 1; } // TODO: check other aspects of the provided data... if ( su->m_pwd[0] && su->m_pwd2[0] && strcmp(su->m_pwd,su->m_pwd2) ){ su->m_pwd2Error = "Password does not match the " "above password"; su->m_error = 1; } // make sure username is new if ( new1 && su->m_user[0] && g_proxy.doesUsernameExist ( su->m_user ) ) { su->m_userError = "Username already exists. " "Try another."; su->m_error = 1; } if ( new1 && ! su->m_terms ) { su->m_termsError = "You must agree to the terms to " "proceed"; su->m_error = 1; } if ( new1 && su->m_deposit < MINCHARGE ) { su->m_tmpBuf1.safePrintf("You must initially deposit " "a minimum of $%.02f to set up " "an account", MINCHARGE); su->m_depositError = su->m_tmpBuf1.getBufStart(); su->m_error = 1; } } bool printLoginPage ( StateUser *su , SafeBuf *errmsg ) { HttpRequest *hr = &su->m_hr; // print login page? char *bs1 = "
"; char *bs2 = ""; char *msg = ""; if ( errmsg ) msg = errmsg->getBufStart(); if ( errmsg && errmsg->length() == 0 ) { bs1 = ""; bs2 = ""; msg = ""; } char *u = hr->getString("login",NULL,""); char *pwd = hr->getString("password",NULL,""); SafeBuf sb; sb.safePrintf("" "Gigablast - Login" "
" "" "" "" "
" "
" "
" "" "" "" "" "" "
" "" "User Login" "

" "" "" "" "" "" "" "" "" "" "" "
Username:
" "Password:" "%s" "%s" "%s" "
" "" "
" "" "
" "OR" "
" "
" "Create a new account" "" "
" "" "" , u , pwd , bs1 , msg , bs2 ); g_httpServer.sendDynamicPage ( su->m_socket, sb.getBufStart(), sb.length(), 0 , // cachetime in secs false , // postreply? "text/html", // content type -1, // http status -1->200 NULL,//cookiePtr, "utf-8" ); mdelete(su,sizeof(StateUser),"usprox"); delete(su); return true; } bool printLogoutPage ( StateUser *su ) { //HttpRequest *hr = &su->m_hr; // print login page? SafeBuf sb; sb.safePrintf("" "Gigablast - Logout" "
" "" "" "" "
" "
" ); if ( su->m_userId32 <= 0 ) sb.safePrintf("You were not logged in. Why are you trying " "to logout again?"); else sb.safePrintf("You have been successfully logged out."); sb.safePrintf("

" "Return to homepage or" "

" "Return to login page"); sb.safePrintf("" "" "" ); // getLoggedInUserId should have set the sessionId64 SafeBuf cb; // if we are the admin logged in as someone else in // disguise, then redirect back to our main page. but if we are // the admin and NOT logged in as someone else, then log us out // as normal! if ( su->m_isAdmin && su->m_adminSessId != su->m_sessionId64 ) { sb.reset(); sb.safePrintf(""); cb.safePrintf("Set-Cookie: sessionid=%lli;\r\n" "Set-Cookie: userid=%li;\r\n" , su->m_adminSessId , su->m_adminId ); } else cb.safePrintf("Set-Cookie: sessionid=0;\r\n" "Set-Cookie: userid=0;\r\n" "Set-Cookie: adminsessid=0;\r\n" ); char *cookiePtr = NULL; if ( cb.length() ) cookiePtr = cb.getBufStart(); g_httpServer.sendDynamicPage ( su->m_socket, sb.getBufStart(), sb.length(), 0 , // cachetime in secs false , // postreply? "text/html", // content type -1, // http status -1->200 cookiePtr, "utf-8" ); mdelete(su,sizeof(StateUser),"usprox"); delete(su); return true; } bool sendRedirect ( StateUser *su ) { //HttpRequest *hr = &su->m_hr; // they logged in successfully from the post, redirect SafeBuf rb; rb.safePrintf(""); // this must be legit! if ( su->m_sessionId64 <= 0 ) { char *xx=NULL;*xx=0; } if ( su->m_userId32 <= 0 ) { char *xx=NULL;*xx=0; } // getLoggedInUserId should have set the sessionId64 SafeBuf cb; cb.safePrintf("Set-Cookie: sessionid=%lli;\r\n" "Set-Cookie: userid=%li;\r\n" ,su->m_sessionId64 ,su->m_userId32); char *cookiePtr = NULL; if ( cb.length() ) cookiePtr = cb.getBufStart(); // . send this page // . encapsulates in html header and tail // . make a Mime g_httpServer.sendDynamicPage ( su->m_socket, rb.getBufStart(), rb.length(), 0 , // cachetime in secs false , // postreply? "text/html", // content type -1, // http status -1->200 cookiePtr, "utf-8" ); // i guess it copies the safebuf contents.. mdelete(su,sizeof(StateUser),"usprox"); delete(su); return true; } // . returns false if blocked, true otherwise // . sets errno on error // . make a web page displaying user info // . use https only! // . password and username transmitted with post for login // . just set cookie to the session id then // . call g_httpServer.sendDynamicPage() to send it bool sendPageAccount ( TcpSocket *s , HttpRequest *hr2 ) { // only call this from host #0! so we can use msg5 not msg0 // and store all of userdb on host #0 and twins. if ( ! g_proxy.isProxy() ) { char *xx=NULL;*xx=0; } // ensure only https! this is sensitive stuff if ( ! s->m_ssl ) { char *msg = "Only access account info using https!"; g_httpServer.sendErrorReply ( s, 500, msg ); return true; } bool err5 = false; if ( err5 ) { hadError5: g_errno = ENOMEM; log("proxy: new(%i): %s",sizeof(StateUser),mstrerror(g_errno)); g_httpServer.sendErrorReply(s,500,mstrerror(g_errno)); return true; } // // shit, we gotta save state since makeGif can block // StateUser *su; try { su = new (StateUser) ; } catch ( ... ) { goto hadError5; } mnew ( su, sizeof(StateUser), "suprox" ); // copy shit. if it fails return 500 http error. if ( ! su->m_hr.copy ( hr2 ) ) { mdelete(su,sizeof(StateUser),"suprox"); delete(su); goto hadError5; } // use that now HttpRequest *hr = &su->m_hr; // copy socket and ui offset su->m_socket = s; // bogus values su->m_sessionId64 = 0; su->m_userId32 = -1; // reset flags su->m_transactionSuccessful = false; su->m_attemptedTransaction = false; su->m_deposit = hr->getFloat ("deposit",0.0); su->m_refund = hr->getFloat ("refund",0.0); su->m_refundFee = 0.0; // refunds require the original transaction's id from authorize.net su->m_refundTransId = hr->getLongLong("transid",0LL); // set this crap from the GET request su->m_user = hr->getString("user",NULL,""); su->m_pwd = hr->getString("pwd",NULL,""); su->m_pwd2 = hr->getString("pwd2",NULL,""); su->m_ccNum = hr->getString("cc",NULL,""); su->m_ccType = hr->getString("cctype",NULL,""); su->m_cvv = hr->getString("cvv",NULL,""); su->m_exp = hr->getString("exp",NULL,""); su->m_fc = hr->getString("fc",NULL,""); su->m_phone = hr->getString("phone",NULL,""); su->m_email = hr->getString("email",NULL,""); su->m_terms = hr->getLong("terms",0); // clear these su->m_error = 0; su->m_userError = ""; su->m_pwdError = ""; su->m_pwd2Error = ""; su->m_cvvError = ""; su->m_ccNumError = ""; su->m_expError = ""; su->m_ccTypeError = ""; su->m_depositError= ""; su->m_refundError = ""; su->m_fcError = ""; su->m_phoneError = ""; su->m_emailError = ""; su->m_termsError = ""; // european stuff su->m_firstName = ""; su->m_lastName = ""; su->m_address = ""; su->m_city = ""; su->m_state = ""; su->m_country = ""; su->m_zip = ""; // remove trailing spaces from all. include max bytes too includng \0 removeSpaceTrails ( su->m_user , 32 ); removeSpaceTrails ( su->m_pwd , 32 ); removeSpaceTrails ( su->m_pwd2 , 32 ); removeSpaceTrails ( su->m_ccNum , 64 ); removeSpaceTrails ( su->m_ccType , 32); removeSpaceTrails ( su->m_cvv , 5); removeSpaceTrails ( su->m_exp , 6 ); removeSpaceTrails ( su->m_fc ,16 ); removeSpaceTrails ( su->m_phone ,30); removeSpaceTrails ( su->m_email ,80); // european stuff for credit card processing removeSpaceTrails ( su->m_firstName,40); removeSpaceTrails ( su->m_lastName,40); removeSpaceTrails ( su->m_address,80); removeSpaceTrails ( su->m_city,30); removeSpaceTrails ( su->m_state,30); removeSpaceTrails ( su->m_country,30); removeSpaceTrails ( su->m_zip,30); // are we dealing with a form submission? long submit = hr->getLong("submitted",0); long new1 = hr->getLong("new",0); long edit = hr->getLong("edit",0); long logout = hr->getLong("logout",0); if ( new1 ) edit = 0; if ( new1 ) su->m_submittingNewUser = 1; else su->m_submittingNewUser = 0; // they must be logged in SafeBuf errmsg; // this will set su->m_userId32 and su->m_sessionId64 UserInfo *ui = g_proxy.getLoggedInUserInfo ( su , &errmsg ) ; if ( logout ) return printLogoutPage ( su ); // if not logged in, print the login page if ( su->m_userId32 <= 0 && ! new1 ) return printLoginPage( su , &errmsg ); // if not doing a submit of an edit or a new user action, // then take these values from the UserInfo if ( ! submit && ! new1 ) { su->m_user = ui->m_login; su->m_pwd = ui->m_password; su->m_pwd2 = ui->m_password; su->m_ccNum = ui->m_creditCardNum; su->m_ccType = ui->m_creditCardType; su->m_cvv = ui->m_cvv; su->m_exp = ui->m_creditCardExpires; su->m_fc = ui->m_xmlFeedCode; //su->m_deposit = 0.0; su->m_phone = ui->m_phone; su->m_email = ui->m_email; su->m_firstName = ui->m_firstName; su->m_lastName = ui->m_lastName; su->m_city = ui->m_city; su->m_state = ui->m_state; su->m_country = ui->m_country; su->m_zip = ui->m_zip; su->m_address = ui->m_address; } // if they posted to us from the login page, then redirect them // so the client can refresh on their browser or go back to the page // on their browser. otherwise it asks if you want to confirm the post // submission. if ( hr->getLong("fromloginpage",0) ) return sendRedirect ( su ); // we are logged in at this point, so get this //UserInfo *ui = g_proxy.getUserInfoFromId ( su->m_userId32 ); // they submitted a deposit/withdrawal request? if ( su->m_deposit != 0.0 && ui ) { if ( su->m_deposit < 0.0 ) { su->m_depositError = "Deposit amount must be positive"; su->m_error = 1; return g_proxy.printAccountingInfoPage ( su ); } // error? if ( su->m_deposit < MINCHARGE ) { su->m_tmpBuf1.safePrintf("Minimum deposit is $%.02f", MINCHARGE); su->m_depositError = su->m_tmpBuf1.getBufStart(); su->m_error = 1; return g_proxy.printAccountingInfoPage ( su ); } // this will ultimately display the account info page // on success i guess! return g_proxy.hitCreditCard ( su ); } if ( su->m_refund != 0.0 ) { // deal with bad refund amounts if ( su->m_refund < 0.0 ) { su->m_refundError = "Refund amount must be positive"; su->m_error = 1; return g_proxy.printAccountingInfoPage ( su ); } // ensure they have enough if they are withdrawing, not deposit if ( ui->m_accountBalance < su->m_refund ) { su->m_refundError = "You can not refund more than " "the account balance. "; su->m_error = 1; return g_proxy.printAccountingInfoPage ( su ); } return g_proxy.hitCreditCard ( su ); } // if adding new user with blank form generate some defaults if ( new1 && ! submit ) { su->m_deposit = MINCHARGE; // make a random number for the search feed code long rc = rand() & 0x7fffffff; sprintf(su->m_tmpBuf2,"%li",rc); su->m_fc = su->m_tmpBuf2; } // . try to detect errors on submitted data // . sets StateUser::m_userError, m_pwdError, etc. // . sets StateUser::m_error on any error being set if ( submit ) setFieldErrors ( su ); // if the submitted data has something wrong then tell the user // what is wrong and allow them to fix it and resubmit if ( submit && su->m_error ) return g_proxy.printEditForm( su ); // . if we couldn't find anything wrong, try to make a deposit // . do not add the userinfo record itself until deposit is made ok if ( submit && new1 ) return g_proxy.hitCreditCard ( su ); if ( submit && edit ) { // get the userinfo class SafeBuf errmsg; // copy edited info over strcpy(ui->m_password,su->m_pwd); strcpy(ui->m_xmlFeedCode,su->m_fc); // . do not update this if it has ****'s in it // . but if they entered a whole new credit card # then // update it, in which case we will have no *'s in it if ( su->m_ccNum[0] != '*' ) strcpy(ui->m_creditCardNum,su->m_ccNum); strcpy(ui->m_cvv,su->m_cvv); strcpy(ui->m_creditCardExpires,su->m_exp); strcpy(ui->m_creditCardType,su->m_ccType); strcpy(ui->m_phone,su->m_phone); strcpy(ui->m_email,su->m_email); // save the new info g_proxy.saveUserBufs(); // now redirect to accounting info page so they can hit the // back button to go back to this page without having to // confirm a post resubmission log("proxy: sending redirect 2"); return sendRedirect ( su ); // now display it in the normal page display //return g_proxy.printAccountingInfoPage ( su ); } // sanity test. submit should NOT be set here!!! if ( submit ) { mdelete(su,sizeof(StateUser),"suprox"); delete(su); log("proxy: bad submit"); g_errno = EBADENGINEER; g_httpServer.sendErrorReply(s,500,mstrerror(g_errno)); return true; } // print blank add new user form. "GET /account?new=1" if ( new1 ) return g_proxy.printEditForm ( su ); // edit existing user account. "GET /account?edit=1" if ( edit ) return g_proxy.printEditForm ( su ); return g_proxy.printAccountingInfoPage ( su ); } void gotDepositDocWrapper ( void *st , TcpSocket *ts ) { StateUser *su = (StateUser *)st; su->m_docSocket = ts; g_proxy.gotDepositDoc ( su ); } /* long Proxy::getNextTransactionId ( ) { // make sure s_lastTransId is set to our last transaction id PLUS one! static long s_lastTransId = -1; if ( s_lastTransId == -1 ) { long nd = m_depositBuf.length() / sizeof(DepositRec); DepositRec *drs = (DepositRec *)m_depositBuf.getBufStart(); for ( long i = 0 ; i < nd ; i++ ) { DepositRec *dr = &drs[i]; if ( dr->m_transactionId > s_lastTransId ) s_lastTransId = dr->m_transactionId; } // advance over that s_lastTransId++; } long save = s_lastTransId; s_lastTransId++; return save; } */ // . returns false if blocked, true otherwise // . sets g_errno on error and returns true // . sets su->m_authenticated to true or false bool Proxy::hitCreditCard ( StateUser *su ) { // assume not authentic su->m_transactionSuccessful = false; su->m_attemptedTransaction = true; //HttpRequest *hr = &su->m_hr; // ensure plenty room long need = sizeof(DepositRec) * 10; // on error, set the m_authNetMsg if ( ! m_depositBuf.reserve ( need ) ) return gotDepositDoc(su); // get a unique transaction id //su->m_transactionId = getNextTransactionId(); // connect to authorize.net SafeBuf url; bool testMode = false; if ( testMode ) url.safePrintf("https://test.authorize.net/gateway/" "transact.dll?"); else url.safePrintf( "https://secure.authorize.net/gateway/" "transact.dll?"); //UserInfo *ui = getUserInfoFromId ( su->m_userId32 ); // make the POST // see http://developer.authorize.net/guides/AIM/wwhelp/wwhimpl/js/html/wwhelp.htm // see (List of API Fields at end) url.safePrintf("x_card_num=%s" //"&x_cust_id=%li" "&x_customer_ip=%s" "&x_delim_data=1" // must be 1 for AIM "&x_description=gigablast.com+search+engine+services" //"&x_invoice_num=%li" "&x_email_customer=1" // %li" "&x_version=3.0" "&x_currency_code=USD" "&x_recurring_billing=0" "&x_relay_response=0" // we are using AIM , su->m_ccNum //, ui->m_userId32 , iptoa(su->m_socket->m_ip) //, su->m_invoiceNum // also store in DepositRec //, ui->m_sendEmails // send email to user ); // sanity check //if ( su->m_invoiceNum < 0 ) { char *xx=NULL;*xx=0; } // a negative amount will be a withdraw if ( su->m_refund != 0.0 ) { // we'll record this in deposit buf su->m_refundFee = .05 * su->m_refund; // this is kinda bogus... url.safePrintf("&x_type=CREDIT" // this is kinda bogus since we do not see it // has refunding original transactions but // rather getting a withdrawal "&x_trans_id=%lli" // do we need x_split_tender_id ??? // use that INSTEAD of x_trans_id... //"&x_split_tender_id=%lli" "&x_amount=%.02f" , su->m_refundTransId , su->m_refund - su->m_refundFee ); } else url.safePrintf("&x_type=AUTH_CAPTURE" "&x_amount=%.02f" , su->m_deposit ); // user's email address: url.safePrintf("&x_email="); url.urlEncode ( su->m_email ); url.safePrintf("&x_exp_date="); url.urlEncode ( su->m_exp ); url.safePrintf("&x_phone="); url.urlEncode ( su->m_phone ); //url.safePrintf("&x_card_num="); //url.urlEncode( su->m_ccNum ); // // INSERT YOUR secret transaction/api key for authorize.net // url.safePrintf("&x_tran_key=%s",g_secret_tran_key); url.safePrintf("&x_login=%s",g_secret_api_key); //url.safePrintf("&x_tran_key=xxxxxxxxxxxxxxx"); //url.safePrintf("&x_login=yyyyyyyyyy"); // european requires stuff /* url.safePrintf("&x_first_name="); url.urlEncode ( ui->m_firstName ); url.safePrintf("&x_last_name="); url.urlEncode ( ui->m_lastName ); url.safePrintf("&x_address="); url.urlEncode ( ui->m_address ); url.safePrintf("&x_city="); url.urlEncode ( ui->m_city ); url.safePrintf("&x_state="); url.urlEncode ( ui->m_state ); url.safePrintf("&x_zip="); url.urlEncode( ui->m_zip ); */ log("proxy: contacting authorize.net"); // show it log("proxy: authorize.netrequest=%s",url.getBufStart()); // returns false if blocked, true otherwise if ( ! g_httpServer.getDoc ( url.getBufStart() , 0 , // ip, 0 means unknown 0 , // offset -1 , // size (use GET request) 0 , // ifmodifiedsince su , // state gotDepositDocWrapper , 60 * 1000 , // 60 sec timeout 0 , // proxyip 0 , // proxyport 1000000, // 1MB maxtextdoclen 1000000, // 1MB maxotherdoclen "Gigabot/1.0" , // useragent "HTTP/1.0", // proto true , // do post? YES!!! NULL )) // cookie // return false if it blocked return false; // this never blocks? if ( ! gotDepositDoc ( su ) ) log("proxy: got error in gotDepositDoc: %s", mstrerror(g_errno)); // never blocks return true; } long long rand63 ( ) { unsigned long r1 = rand(); unsigned long r2 = rand(); unsigned long long r = r1; r <<= 32; r |= r2; r &= 0x7fffffffffffffffLL; return r; } // . parse a field out of an authorize.net reply // . first fieldNum is 0 char *getField ( char *docHTML , long fieldNum , long *replyMsgLen ) { char *p = docHTML; long numCommas = 0; for ( ; *p ; p++ ) { if ( *p == ',' ) numCommas++; if ( numCommas == fieldNum ) { p++; break; } } // return NULL if not there if ( ! *p ) return NULL; // find next comma char *next = strchr ( p , ',' ); // not there? long plen; if ( ! next ) plen = gbstrlen(p); else plen = next - p; // use that *replyMsgLen = plen; return p; } // . returns false and sets g_errno on error, true otherwise // . does not block i guess bool Proxy::gotDepositDoc ( StateUser *su ) { // another error? ETCPTIMDOUT? if ( g_errno ) { log("proxy: bad authorize.net reply"); su->m_error = 1; su->m_authNetMsg.safePrintf("Error communicating with " "authorize.net to charge " "credit card. %s", mstrerror(g_errno)); // there was an error - redisplay the new user form if ( su->m_submittingNewUser ) return printEditForm ( su ); // otherwise, we were making another deposit return printAccountingInfoPage ( su ); } // parse reply TcpSocket *ts = su->m_docSocket; char *reply = ts->m_readBuf; long replySize = ts->m_readOffset; HttpMime mime; Url redirUrl; mime.set ( reply , replySize , &redirUrl ); // log it log("proxy: authorize.net reply: %s",reply); long status = mime.getHttpStatus(); if ( status != 200 ) { log("proxy: bad authorize.net mime stats = %li",status); su->m_error = 1; su->m_authNetMsg.safePrintf("Error communicating with " "authorize.net to charge " "credit card. HTTP status " "%li",status); // there was an error - redisplay the new user form if ( su->m_submittingNewUser ) return printEditForm ( su ); // otherwise, we were making another deposit return printAccountingInfoPage ( su ); } // get msg char *docHTML = reply + mime.getMimeLen(); //long docLen = replySize - mime.getMimeLen(); // a,b,c,ERRMSG,... // see http://developer.authorize.net/guides/AIM/wwhelp/wwhimpl/js/html/wwhelp.htm // the first number: // 1: approved // 2: declined // 3: error // 4: held for review long returnCode = atol(docHTML); // show it has msg long replyMsgLen = 0; char *replyMsg = getField ( docHTML , 3 , &replyMsgLen ); if ( replyMsgLen ) su->m_authNetMsg.safePrintf("

"); // then the reply msg su->m_authNetMsg.safeMemcpy ( replyMsg , replyMsgLen ); // get the reason code long scLen; char *scPtr = getField(docHTML,2,&scLen); long rc = 0; if ( scPtr ) rc = atol2(scPtr,scLen); if ( returnCode != 1 && su->m_refund != 0.0 && rc == 54 ) // if this happens consider issuing a void and we can // save the credit card processing fees!!! su->m_authNetMsg.safePrintf(" You need to wait 25 hours for " "the charge to settle before a " "refund can be issued. "); su->m_authNetMsg.pushChar('\0'); // error? this means it has NOT been approved if ( returnCode != 1 ) { su->m_error = 1; // there was an error - redisplay the new user form if ( su->m_submittingNewUser ) return printEditForm ( su ); // otherwise, we were making another deposit return printAccountingInfoPage ( su ); } // ensure userid32 unique! if ( su->m_submittingNewUser ) { long numUsers = m_userInfoBuf.length() / sizeof(UserInfo) ; su->m_userId32 = numUsers + 1; } float amount; // make it negative for storing in deposit rec if ( su->m_refund != 0.0 ) amount = -1.0 * su->m_refund; else amount = su->m_deposit; // log it for posterity log("proxy: successfully charged $%.02f to userid %li" ,amount ,su->m_userId32 ); long need = sizeof(DepositRec) * 2; if ( ! m_depositBuf.reserve (need) ) { su->m_error = 1; su->m_authNetMsg.safePrintf("Error adding deposit to buf. " "userid32=%lu amt=$%.02f" ,su->m_userId32 ,amount); char *xx=NULL;*xx=0; } // good, now add it to buffer DepositRec dr; memset ( &dr , 0 , sizeof(DepositRec) ); long now = getTimeLocal(); if ( su->m_userId32 <= 0 ) { char *xx=NULL;*xx=0; } dr.m_userId32 = su->m_userId32; dr.m_depositDate = now; // // . we need this for doing CREDITs/withdrawals back to the user // . i've seen this # > 5B so use a long long // long tlen; char *tid = getField(docHTML,6,&tlen); if ( tid ) { char c = tid[tlen]; tid[tlen] = '\0'; long long transId = atoll(tid); tid[tlen] = c; // the authorize.net transaction id dr.m_authorizeNetTransactionId = transId; log("proxy: got transactionid=%lli",transId); } // if depositing... if ( su->m_deposit != 0.0 ) { dr.m_depositAmount = su->m_deposit; dr.m_flags = DRF_DEPOSIT; m_depositBuf.safeMemcpy ( &dr , sizeof(DepositRec)); } // withdraw fee? add that into this table too else { if ( su->m_refundFee <= 0.0 ) { char *xx=NULL;*xx=0; } dr.m_depositAmount = -1 * su->m_refundFee; dr.m_flags = DRF_WITHDRAW_FEE; m_depositBuf.safeMemcpy ( &dr , sizeof(DepositRec) ); // amount is negative, withdraw fee is positive dr.m_depositAmount = -1*(su->m_deposit - su->m_refundFee); dr.m_flags = DRF_WITHDRAW; m_depositBuf.safeMemcpy ( &dr , sizeof(DepositRec) ); } // success! su->m_transactionSuccessful = true; // if not submitting a new user, just add the amount to our // account balance and save, then print the accounting info page if ( ! su->m_submittingNewUser ) { // get user rec UserInfo *ui = g_proxy.getUserInfoFromId ( su->m_userId32 ); // add it. will be negative for a refund. ui->m_accountBalance += amount; // save the deposit to disk saveUserBufs(); // print the accounting info page with success msg su->m_authNetMsg.safePrintf("Despoit of $%.02f was " "successful",amount); //return printAccountingInfoPage ( su ); // shit actually, to avoid re-submits more so, make it // redirect i guess log("proxy: sending redirect 3"); return sendRedirect ( su ); } ///////// // // add the new user here!! // ///////// UserInfo u2; memset ( &u2 , 0 , sizeof(UserInfo) ); strncpy(u2.m_login, su->m_user,30); strncpy(u2.m_password, su->m_pwd,30); strncpy(u2.m_xmlFeedCode, su->m_fc,14); strncpy(u2.m_creditCardNum, su->m_ccNum,62); strncpy(u2.m_cvv, su->m_cvv,4); strncpy(u2.m_creditCardExpires, su->m_exp,5); strncpy(u2.m_creditCardType, su->m_ccType,30); // visa u2.m_pending = 0; u2.m_flags = 0; u2.m_accountBalance = su->m_deposit; //strncpy(u2.m_creditCardAddress,ccAddr,250); u2.m_signUpDate = getTimeLocal(); u2.m_userId32 = su->m_userId32; u2.m_lastSessionId64 = rand63(); u2.m_lastActionTime = now; u2.m_lastLoginIP = su->m_socket->m_ip; // note this as well log ("proxy: successfully added userid %li to userbuf",su->m_userId32); // store it if ( ! m_userInfoBuf.safeMemcpy ( &u2 , sizeof(UserInfo) ) ) return false; // save it. this blocks for now... if ( ! saveUserBufs() ) { log("proxy: error saving user bufs: %s",mstrerror(g_errno)); return false; } // we are auto-logged in //su->m_userId32 = userId32; su->m_sessionId64 = u2.m_lastSessionId64; // now print the accounting page. they are an official user now. // no! because if they try to reload that page or go back to that // page it asks to confirm form resubmission! then it will just // say that that username already exists! //return printAccountingInfoPage ( su ); // so instead, let's redirect them to /account and set the cookie! // make a cookie in case they just logged in through this page! // otherwise the login doesn't "stick" return sendRedirect ( su ); } // returns false and sets g_errno on error, true otherwise. does not block // i guess... bool Proxy::printEditForm ( StateUser *su ) { HttpRequest *hr = &su->m_hr; TcpSocket *s = su->m_socket; // print form to add new user? or submitting new user info? long new1 = hr->getLong("new",0); // print form to edit user? or did they submit their edits? long edit = hr->getLong("edit",0); // are we dealing with a form submission? long submit = hr->getLong("submitted",0); // get the user we are editing into "ui" UserInfo *ui = NULL; SafeBuf errmsg; if ( edit ) ui = getUserInfoFromId ( su->m_userId32 ); char *msg = ""; // wtf? if ( edit && !ui ) { msg = "Could not find user to edit."; //hadError7: mdelete(su,sizeof(StateUser),"usprox"); delete(su); g_httpServer.sendErrorReply(s,500,msg); return true; } SafeBuf sb; // show the user info form sb.safePrintf( "" "Gigablast - Account Information" "" "
" "" "" "" "
" "
" "
" "" "" "" "" "
" "" "New User Information" "

" ); /* if ( new1 && submit && ! su->m_deposit ) { sb.safePrintf("
" "Unable to charge " "Credit Card. Please check credit card info " "below and verify it is accurate before " "resubmitting."); sb.cat ( su->m_authNetMsg ); sb.safePrintf("
"); } */ sb.safePrintf( "
" "" ); if ( su->m_attemptedTransaction && ! su->m_transactionSuccessful ) { sb.safePrintf("" , su->m_deposit , su->m_authNetMsg.getBufStart() ); } // username if ( new1 ) { sb.safePrintf("" "" "" "" ""); // password sb.safePrintf("" "" ""); // password2 sb.safePrintf("" "" ""); // email sb.safePrintf("" "" ""); // phone # sb.safePrintf("" "" ""); // creidt card # sb.safePrintf("" "" ""); // credit card type char *s1 = ""; char *s2 = ""; char *s3 = ""; char *s4 = ""; if ( !strcmp(su->m_ccType,"Visa") ) s1 = " selected"; if ( !strcmp(su->m_ccType,"MasterCard") ) s2 = " selected"; if ( !strcmp(su->m_ccType,"Discovery") ) s3 = " selected"; if ( !strcmp(su->m_ccType,"AmericanExpress") ) s4 = " selected"; sb.safePrintf("" "" ""); // ccv sb.safePrintf("" "" ""); // expiration sb.safePrintf("" "" ""); // deposit amount // TODO: add su->m_depositError! if ( new1 ) { float deposit = su->m_deposit; if ( ! submit ) deposit = MINCHARGE;//100.00; sb.safePrintf("" "" ""); } // search feed code sb.safePrintf("" "" ""); if ( new1 ) { char *cs = ""; if ( su->m_terms ) cs = " checked"; sb.safePrintf(""); } sb.safePrintf("
" "Failed to deposit $%.02f. %s" //" Please ensure your credit card info is " //"correct." "
Username" , su->m_user ); } else { sb.safePrintf("
Username%s" "" , su->m_user , su->m_user ); } if ( su->m_userError[0] ) sb.safePrintf("
%s",su->m_userError); sb.safePrintf("
Password" , su->m_pwd ); if ( su->m_pwdError[0] ) sb.safePrintf("
%s",su->m_pwdError); sb.safePrintf("
Repeat Password" , su->m_pwd2 ); if ( su->m_pwd2Error[0] ) sb.safePrintf("
%s",su->m_pwd2Error); sb.safePrintf("
Email Address" , su->m_email ); if ( su->m_emailError[0] ) sb.safePrintf("
%s",su->m_emailError); else sb.safePrintf("
" "Required for emailing you receipts, or a " "new password should you forget it." ""); sb.safePrintf("
Phone Number" , su->m_phone ); if ( su->m_phoneError[0] ) sb.safePrintf("
%s",su->m_phoneError); sb.safePrintf("
Credit Card Numberm_ccNum)>12 ) sb.safePrintf("************%s\">", su->m_ccNum + 12); else sb.safePrintf("%s\">", su->m_ccNum ); if ( su->m_ccNumError[0] ) sb.safePrintf("
%s",su->m_ccNumError); sb.safePrintf("
Credit Card Type" , s1,s2,s3,s4 ); if ( su->m_ccTypeError[0] ) sb.safePrintf("
%s",su->m_ccTypeError); sb.safePrintf("
3-digit CVV  " "(on back " "of card)" , su->m_cvv ); if ( su->m_cvvError[0] ) sb.safePrintf("
%s",su->m_cvvError); sb.safePrintf("
" "Credit Card Expiration  " "" "(MM/YY)" "" , su->m_exp ); if ( su->m_expError[0] ) sb.safePrintf("
%s",su->m_expError); sb.safePrintf("
" "Initial Deposit Amount" "" "
" "Minimum deposit " "is $%.02f" "" ,deposit ,MINCHARGE ); if ( su->m_depositError[0] ) sb.safePrintf("
%s", su->m_depositError); sb.safePrintf("
XML Feed Code" , su->m_fc ); if ( su->m_fcError[0] ) sb.safePrintf("
%s",su->m_fcError); else sb.safePrintf("
" "A secret code used along with your userid " "to access the XML feeds." ""); sb.safePrintf("
" " " "I have read and " "agree to these terms." , cs); if ( su->m_termsError[0] ) sb.safePrintf("
%s",su->m_termsError); sb.safePrintf("
"); // so sendPageAccount() knows when it receives a submit sb.safePrintf(""); if ( new1 ) sb.safePrintf(""); if ( edit ) sb.safePrintf(""); sb.safePrintf("
" "Cancel   " ""); sb.safePrintf("
"); sb.safePrintf("" "" "" ); g_httpServer.sendDynamicPage ( s , sb.getBufStart(), sb.length(), 0 , // cachetime in secs false , // postreply? "text/html", // content type -1, // http status -1->200 NULL, // cookiePtr, "utf-8" ); return true; } bool Proxy::saveUserBufs() { log("proxy: saving user bufs"); if ( m_sumBuf.saveToFile ( g_hostdb.m_dir , "usersum.dat" ) < 0 ) return false; if ( m_userInfoBuf.saveToFile ( g_hostdb.m_dir , "userinfo.dat" ) < 0 ) return false; if ( m_depositBuf.saveToFile ( g_hostdb.m_dir,"userdeposit.dat" ) < 0 ) return false; return true; } // and make summary rec table, "m_srht" bool Proxy::loadUserBufs ( ) { // these return 0 if file does not exist, which may be the case if ( m_sumBuf.fillFromFile ( g_hostdb.m_dir , "usersum.dat" ) < 0 ) return false; if ( m_userInfoBuf.fillFromFile(g_hostdb.m_dir , "userinfo.dat")<0) return false; if ( m_depositBuf.fillFromFile(g_hostdb.m_dir,"userdeposit.dat")< 0) return false; // scan users and zero out m_pending, we might have shutdown before // the operation could complete so do not charge for it UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart(); long ni = m_userInfoBuf.length() / sizeof(UserInfo) ; long i; for ( i = 0 ; i < ni ; i++ ) { // shortcut UserInfo *ui = &uis[i]; if ( ui->m_pending == 0.0 ) continue; log("proxy: erasing pending=%.02f for uid=%li", ui->m_pending,ui->m_userId32); ui->m_pending = 0.0; } // // make the hashtable for summaryrecs // m_srht.reset(); long ns = m_sumBuf.length() / sizeof(SummaryRec); SummaryRec *ss = (SummaryRec *)m_sumBuf.getBufStart(); long needSlots = ns * 2; if ( ! m_srht.set(8,4,needSlots,NULL,0,false,0,"srectbl") ) { log("proxy: failed to alloc srht"); return false; } for ( long i = 0 ; i < ns ; i++ ) { // shortcut SummaryRec *sr = &ss[i]; // get the offset long sumOff = ((char *)sr) - m_sumBuf.getBufStart(); // make key unsigned long long h64 = (unsigned long)sr->m_userId32; unsigned long t = sr->m_year; t <<= 16; t |= sr->m_month; // 1..12 t <<= 8; t |= sr->m_day; // 1..31 t <<= 8; t |= sr->m_accessType; h64 <<= 32; h64 |= t; // add it if ( ! m_srht.addKey ( &h64 , &sumOff ) ) { // sr ) ) { log("proxy: failed to load user bufs"); return false; } } // if first time, then initialize with records for our old clients // like existing clients long nr = m_userInfoBuf.length() / sizeof(UserInfo); if ( nr >= 5 ) return true; // clear it all m_userInfoBuf.reset(); long userId = 1; // matt wells, admin login UserInfo ui; memset(&ui,0,sizeof(UserInfo)); strcpy(ui.m_login,"admin"); strcpy(ui.m_password,"admin123"); strcpy(ui.m_xmlFeedCode,"admincode"); strcpy(ui.m_email,"admin@admin.com"); ui.m_flags = UIF_ADMIN; ui.m_accountBalance = 0.0; ui.m_userId32 = userId++; m_userInfoBuf.safeMemcpy(&ui,sizeof(UserInfo)); // success! return true; } bool Proxy::printAccountingInfoPage ( StateUser *su , SafeBuf *errmsg ) { HttpRequest *hr = &su->m_hr; TcpSocket *socket = su->m_socket; // get the userinfo for logged in user //SafeBuf errmsg; // this will be NULL if userid32 is -1 (invalid, not logged in, etc.) UserInfo *ui = getUserInfoFromId ( su->m_userId32 ); // print html page into this safebuf SafeBuf *sb = &su->m_sb; // ok, they are authenticated, show their account stats sb->safePrintf("" "Gigablast - Account Information" "" ); //insertLoginBarDirective ( sb ); sb->safePrintf( "" "
" "" "" "" "
" "
" "" "" "" "" "
" "" "Account Information" "
" "\n" ); if ( su->m_transactionSuccessful && su->m_deposit > 0.0 ) { sb->safePrintf("
" "%s " "Successfully deposited $%.02f. " "



\n", su->m_authNetMsg.getBufStart(), su->m_deposit ); } else if ( su->m_attemptedTransaction && su->m_refund > 0.0 ) { sb->safePrintf("
" "%s " "Failed to refund $%.02f. " "



\n", su->m_authNetMsg.getBufStart(), su->m_refund ); } else if ( su->m_attemptedTransaction ) { sb->safePrintf("
" "%s " "Failed to charge $%.02f. " "Please verify your credit card number, " "credit card type, " "3-digit CVV and expiration date." "



\n", su->m_authNetMsg.getBufStart(), su->m_deposit ); } // likewise, put refund errors here too! if ( su->m_refundError[0] ) sb->safePrintf("
" "

" "%s " "Failed to refund $%.02f. " "




\n", su->m_refundError , su->m_refund ); char *ccnum = ui->m_creditCardNum; if ( gbstrlen ( ccnum) > 12 ) ccnum = ccnum + 12; sb->safePrintf( "
" "" "" "" "" "" //"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "
" //"
" "User Information [" "edit]" //"
" "
Username%sLOGOUT
Password%s
Email%s
Phone%s
Credit Card #************%s
User ID%liSee the XML Search " "Feed page or the SEO API " "page for details on using this.
XML Feed Code%sSee the XML Search " "Feed page or the SEO API " "page for details on using this.
\n" , ui->m_login , "*******" , ui->m_email , ui->m_phone // only disply last 4 digits , ccnum , ui->m_userId32 , ui->m_xmlFeedCode //, ui->m_accountBalance ); if ( ui->m_userId32 == 1 && (ui->m_flags & UIF_ADMIN) ) { sb->safePrintf ( "
"); sb->safePrintf ( "
"); sb->safePrintf ( "
"); printUsers ( sb ) ; } /////////////// // // DEPOSIT GUI // /////////////// sb->safePrintf ( "
"); sb->safePrintf ( "
"); sb->safePrintf ( "
"); sb->safePrintf("Account Balance:   $%.02f

" , ui->m_accountBalance ); if ( ui->m_pending != 0.0 ) sb->safePrintf("Pending Actions:   $%.02f

" , ui->m_pending); sb->safePrintf( "" "
" "
" "Make " //"" "deposit of " "" ,MINCHARGE); // the admin can credit the account if he receives a wire or a check // from a user... /* if ( su->m_isAdmin ) sb->safePrintf("
" "" "Record Wire of " " " " Desc: " "
" "Record Check of " " " " Desc: " ); */ sb->safePrintf(//"" "';" "\" " "name=dotrans value=OK>" ); if ( su->m_depositError[0] ) sb->safePrintf("
%s", su->m_depositError); sb->safePrintf("
" "" "\n" "
\n" ); //sb->safePrintf("
"); //////////////// // // DEPOSIT TABLE // // . all the deposits and withdrawls they've ever made // //////////////// sb->safePrintf ( "
"); sb->safePrintf ( "
"); sb->safePrintf ( "
"); printDepositTable ( sb , ui->m_userId32 ); long cutoff; bool printedSomething; SummaryRec *srs = (SummaryRec *)m_sumBuf.getBufStart(); long nsr = m_sumBuf.length() / sizeof(SummaryRec); /////////////// // // DAILY BREAKDOWN // // print daily query stats for selected month // ////////////// sb->safePrintf ( "
"); sb->safePrintf ( "
"); sb->safePrintf ( "
"); long now = getTimeLocal(); struct tm *timeStruct = gmtime ( &now ); long currentMonth = timeStruct->tm_mon+1; // 1 to 12 long currentYear = timeStruct->tm_year+1900; long currentDay = timeStruct->tm_mday; static char *s_mnames[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; long month = hr->getLong("month",currentMonth); long year = hr->getLong("year",currentYear); long day = hr->getLong("day",currentDay); if ( month < 1 ) month = 1; if ( month > 12 ) month = 12; if ( day < 1 ) day = 1; if ( day > 31 ) day = 31; char *monthName = s_mnames[month-1]; sb->safePrintf( "" "" "" "" // the first column in this pane splitter table , monthName , year ); cutoff = sb->length(); // transaction table in left pane sb->safePrintf ( "" "" "" "" "" "" ); // scan daily transactions for that month and this user //SummaryRec *srs = (SummaryRec *)m_sumBuf.getBufStart(); //long nsr = m_sumBuf.length() / sizeof(SummaryRec); printedSomething = false; for ( long i = 0 ; i < nsr ; i++ ) { SummaryRec *sr = &srs[i]; if ( sr->m_userId32 != ui->m_userId32 ) continue; //if ( sr->m_day != day ) continue; if ( sr->m_month != month ) continue; if ( sr->m_year != year ) continue; char *as = getAccessTypeString(sr->m_accessType); // they should be in order by date... printedSomething = true; sb->safePrintf("" //"" "" "" "" "" // "new search feed", etc. "\n" //, (long)sr->m_month , (long)sr->m_day , sr->m_totalCost , sr->m_numAccesses , as ); } if ( ! printedSomething ) { sb->setLength ( cutoff ); sb->safePrintf(""); } sb->safePrintf("
" "" //"
" "Daily Breakdown for %s %li" //"
" "
" "
DAYTotal CostTotal AccessesDescription
%02li/%02li%02li$%.02f%li%s
" "There is no data for " "this because you have not accessed the feed " "yet using your XML Feed Code" "
\n"); ///////// // // print feed access MONTHLY SUMMARY table // //////// sb->safePrintf ( "
"); sb->safePrintf ( "
"); sb->safePrintf ( "
"); sb->safePrintf ( "" "" ); cutoff = sb->length(); sb->safePrintf( "" "" "" "" "" "" "" "" ); // . add up SummaryRec stats for each month and transaction type // for this user // . scan daily transactions for that month and this user // . go out 20 years for each access type long numCursors = (MAX_ACCESS_TYPE+1) * 20 * 12; long need = numCursors * sizeof(SummaryRec); char tmp[need]; SafeBuf ss(tmp,need); // reset to all zeros ss.zeroOut(); // pointers SummaryRec *cursors = (SummaryRec *)ss.getBufStart(); // . maybe just make a hash key of each summary rec based on // it's month and year and access type for ( long i = 0 ; i < nsr ; i++ ) { SummaryRec *sr = &srs[i]; if ( sr->m_userId32 != ui->m_userId32 ) continue; // we go out 20 years long index = (long)sr->m_accessType * (12*20); index += (sr->m_year - 2013) * 12; // we use 1..12 for month range, so subtract 1 index += sr->m_month - 1; // now get it SummaryRec *cursor = &cursors[index]; // just in case it is our first time hitting "cursor" cursor->m_accessType = sr->m_accessType; cursor->m_month = sr->m_month; cursor->m_year = sr->m_year; cursor->m_day = 0; // we are accumulating all days! // accumulate access stats for that day cursor->m_numAccesses += sr->m_numAccesses; cursor->m_totalCost += sr->m_totalCost; cursor->m_totalProcessTime += sr->m_totalProcessTime; } printedSomething = false; // now print out the cursors for each month in reverse! for ( long i = numCursors - 1 ; i >= 0 ; i-- ) { // shortcut SummaryRec *cursor = &cursors[i]; // 0 is not a valid access type if ( cursor->m_accessType == 0 ) continue; // print it otherwise char *as = getAccessTypeString(cursor->m_accessType); printedSomething = true; sb->safePrintf("" "" "" "" "" ""// price per access "" "" , (long)cursor->m_month , (long)cursor->m_year , as , cursor->m_numAccesses , cursor->m_totalCost , cursor->m_totalCost / (float)cursor->m_numAccesses , (long)(cursor->m_totalProcessTime / (float)cursor->m_numAccesses) ); } if ( ! printedSomething ) { sb->setLength ( cutoff ); sb->safePrintf(""); } // end monthly summary table sb->safePrintf("
" //"
" "Monthly Summary" //"
" "
DateDescription# AccessesTotal CostCost Per AccessAvg. Process Time
%02li/%04li%s%li$%.02f$%.05f%lims
" "There is no data for " "this because you have not accessed the feed " "yet using your XML Feed Code" "
"); // needs this for cookie setting su->m_socket = socket; //////////// // // pretty graph in right pane of query latency and shit i guess // //////////// // // convert to timestamps in seconds struct tm timeStruct2 ; timeStruct2.tm_mday = day; // 1 to 31 timeStruct2.tm_year = year - 1900; timeStruct2.tm_mon = month - 1; // 0 to 11 timeStruct2.tm_hour = 0; timeStruct2.tm_sec = 0; time_t t1 = mktime ( &timeStruct2 ); // utc i guess long nextYear = year; long nextMonth = month; if ( nextMonth >= 13 ) { nextMonth = 1; nextYear++; } timeStruct2.tm_mon = nextMonth - 1; // 0 to 11 timeStruct2.tm_year = nextYear - 1900; time_t t2 = mktime ( &timeStruct2 ); // utc i guess if ( ! g_statsdb.makeGIF ( t2 , // end t1 , // start 10 , // samples in moving avg... &su->m_sb2, su, gotGifWrapper , su->m_userId32 ) ) return false; return gotGif ( su ); } bool Proxy::gotGif ( StateUser *su ) { SafeBuf *sb = &su->m_sb; HttpRequest *hr = &su->m_hr; // cat the gif on sb->cat ( su->m_sb2 ); sb->safePrintf("

" "
" "Copyright © 2013. All rights reserved." "
" "" "" ); // sanity! if ( su->m_sessionId64 < 0 ) { char *xx=NULL;*xx=0; } // make a cookie in case they just logged in through this page! // otherwise the login doesn't "stick" SafeBuf cb; cb.safePrintf("Set-Cookie: sessionid=%lli;\r\n" "Set-Cookie: userid=%li;\r\n" ,su->m_sessionId64 ,(long)su->m_userId32); // if admin logs in as another user by clicking on their login name // in the users table, we still need to know that it is the admin, // so use this special cookie UserInfo *ui = getUserInfoFromId ( su->m_userId32 ); if ( ui && (ui->m_flags & UIF_ADMIN) ) cb.safePrintf("Set-Cookie: adminsessid=%lli;\r\n" ,su->m_sessionId64); char *cookiePtr = NULL; if ( cb.length() ) cookiePtr = cb.getBufStart(); // . send this page // . encapsulates in html header and tail // . make a Mime g_httpServer.sendDynamicPage ( su->m_socket, sb->getBufStart(), sb->length(), 0 , // cachetime in secs false , // postreply? "text/html", // content type -1, // http status -1->200 cookiePtr, "utf-8" , hr ); // i guess it copies the safebuf contents.. mdelete(su,sizeof(StateUser),"usprox"); delete(su); return true; } bool Proxy::printDepositTable ( SafeBuf *sb , long userId32 ) { long nd = m_depositBuf.length() / sizeof(DepositRec); DepositRec *drs = (DepositRec *)m_depositBuf.getBufStart(); UserInfo *ui = getUserInfoFromId ( userId32 ); sb->safePrintf("" "" "" "" //"" "" "" "" "" ); for ( long i = nd - 1 ; i >= 0 ; i-- ) { DepositRec *dr = &drs[i]; if ( dr->m_userId32 != userId32 ) continue; // convert timestamp to month/day/year in utc time_t tt = dr->m_depositDate;//getTimeLocal(); struct tm *timeStruct = gmtime ( &tt ); sb->safePrintf("" , (long)(timeStruct->tm_mon + 1) // month 0..11 , (long)timeStruct->tm_mday //day of month 1..31 , (long)(timeStruct->tm_year+1900) // year , (long)(timeStruct->tm_hour) , (long)(timeStruct->tm_min) ); //sb->safePrintf("",dr->m_transactionId); char *bs1 = ""; char *bs2 = ""; // is it a withdrawl? if ( dr->m_depositAmount < 0.0 ) { bs1 = ""; bs2 = ""; } char *desc = "unknown"; if ( dr->m_flags == DRF_DEPOSIT ) desc = "deposit"; if ( dr->m_flags == DRF_WITHDRAW ) desc = "withdraw"; if ( dr->m_flags == DRF_WITHDRAW_FEE ) desc = "withdraw fee"; sb->safePrintf("" "" "" , bs1 , dr->m_depositAmount , bs2 , desc , dr->m_authorizeNetTransactionId ); float refundAmt = dr->m_depositAmount; if ( refundAmt > ui->m_accountBalance ) // subtract a penny because account balance // might be off by a fraction of a penny // and it will say that you are trying to // refund more than the account balance refundAmt = ui->m_accountBalance - .01 ; // there is a 5% refund charge //refundAmt *= 0.95; sb->safePrintf("" , refundAmt , dr->m_authorizeNetTransactionId ); sb->safePrintf("\n"); } sb->safePrintf("
" "Transaction Table" "
TimeTransIdAmountDescriptionTransaction ID
" "%li/%li/%li %li:%02li" "%li%s%.02f%s%s%llirefund" "
\n"); return true; } #define BARSIZE 250 /* // put a 100 byte directive into th src bool Proxy::insertLoginBarDirective ( SafeBuf *sb ) { if ( ! sb->reserve(BARSIZE) ) return false; long len = sb->length(); //sb.safePrintf("
%%login%%
"); char *dir = "%%login%%"; long dlen = gbstrlen(dir); sb->safeMemcpy(dir,dlen); long inserted = sb->length() - len; // make directive exactly 180 bytes... long remain = BARSIZE - inserted; memset (sb->getBuf() , ' ' , remain ); sb->incrementLength ( remain ); return true; } */ char *Proxy::storeLoginBar ( char *reply , long replySize , long replyAllocSize, long mimeLen, //long userId32 , long *newReplySize , HttpRequest *hr ) { // assume new reply identical to old reply char *newReply = reply; *newReplySize = replySize; // did mime have error in reply? // if so, do not insert login bar if ( strcmp(reply,"HTTP/1.0 ") == 0 ) { long httpStatus = atol2(reply+9,3); if ( httpStatus != 200 ) return reply; } // userid is in cookie long userId32 = hr->getLongFromCookie("userid",0); UserInfo *ui = getUserInfoFromId ( userId32 ); long long sessionId64 = hr->getLongLongFromCookie("sessionid",0); if ( ui && ui->m_lastSessionId64 != sessionId64 ) ui = NULL; SafeBuf cu; char *currentUrl = ""; if ( hr ) { hr->getCurrentUrl ( cu ); currentUrl = cu.getBufStart(); } //return reply; // if too small, just return the original reply char *content = reply + mimeLen; char *contentEnd = reply + replySize; long contentLen = contentEnd - content; if ( contentLen < 20 ) return newReply; // find = pend ) return newReply; // scan to end of body tag for ( ; p < pend && *p != '>' ; p++ ); // no end of body tag? if ( p >= pend ) return newReply; // skip that p++; // make the insertion SafeBuf ib; // if not logged in... print login link if ( ! ui ) { ib.safePrintf ( "
" "[" "login]
"); } else { // if logged in print user name (38 bytes) ib.safePrintf("
" "logged in as " ""); ib.safeStrcpy ( ui->m_login); ib.safePrintf(" [logout]
"); } // how much do we need? long need = replySize + ib.length(); // a \0 terminating i guess //need++; // make a new one SafeBuf sb; if ( ! sb.reserve ( need ) ) { log("proxy: failed to store login bar"); return newReply; } // copy up to end of body tag sb.safeMemcpy ( reply , p - reply ); // then the insertion sb.safeMemcpy ( &ib ); // then the rest sb.safeMemcpy ( p , contentEnd - p ); // then the \0 i guess //sb.addSilentNull(); //sb.reserve ( 1 ); // all done. +1 for the \0 mfree ( reply , replyAllocSize , "prrepl"); // extricate newReply = sb.getBufStart(); *newReplySize = sb.length(); sb.detachBuf(); // fix overreading! // fix the fucking content-length in the mime... char *mp = strnstr(newReply,"Content-Length:",*newReplySize); if ( ! mp ) mp = strnstr(newReply,"Content-length:",*newReplySize); if ( ! mp ) { log("proxy: fuck, no content-length: in mime"); long len = *newReplySize; if ( len > 300 ) len = 300; len--; if ( len <= 0 ) { log("proxy: fuck, 0 length reply"); return newReply; } char c = newReply[len]; newReply[len] = '\0'; log("proxy: fuck: reply=%s",newReply); newReply[len] = c; return newReply; } // temp fix take it out because it is not working right mp[0] = 'x'; return newReply; // point to first digit in there mp += 16; // store our new content length as ascii into test buf char test[64]; long len = sprintf(test,"%li",(long)(newReplySize-mimeLen)); // find end char *end = mp; while ( *end && is_digit(*end) ) end++; // copy backwards then char *src = test + len - 1; for ( ; src >= test ; src-- , end-- ) *end = *src; // that should be it!! return newReply; } void Proxy::printUsers ( SafeBuf *sb ) { sb->safePrintf( "" "" "" "" ); UserInfo *uis = (UserInfo *)m_userInfoBuf.getBufStart(); long ni = m_userInfoBuf.length() / sizeof(UserInfo) ; long i; for ( i = 0 ; i < ni ; i++ ) { // shortcut UserInfo *ui = &uis[i]; // begin new row? if ( i % 5 == 0 ) { if ( i > 0 ) sb->safePrintf(""); sb->safePrintf(""); } // if admin clicks this link then he logs in as that user, // but if admin we should still have set our cookie // adminsessid to our current session id so we know we are // also the admin! sb->safePrintf("" ,i ,ui->m_login ,ui->m_password ,ui->m_login //,ui->m_userId32 ); } sb->safePrintf("\n"); sb->safePrintf("
" "" //"
" "User Table" //"
" "
" "
%li. " "" "%s
\n"); }