/* $Id: zserver.cxx,v 1.15 2002/11/19 22:37:53 cnidr Exp $ */ /************************************************************************ Copyright (c) A/WWW Enterprises, 2001-2002 Permission to use, copy, modify, distribute, and sell this software and its documentation, in whole or in part, for any purpose is hereby granted without fee. ************************************************************************/ /************************************************************************ Copyright Notice Copyright (c) MCNC, Clearinghouse for Networked Information Discovery and Retrieval, 1994-2002. Permission to use, copy, modify, distribute, and sell this software and its documentation, in whole or in part, for any purpose is hereby granted without fee, provided that 1. The above copyright notice and this permission notice appear in all copies of the software and related documentation. Notices of copyright and/or attribution which appear at the beginning of any file included in this distribution must remain intact. 2. Users of this software agree to make their best efforts (a) to return to MCNC any improvements or extensions that they make, so that these may be included in future releases; and (b) to inform MCNC/CNIDR of noteworthy uses of this software. 3. The names of MCNC and Clearinghouse for Networked Information Discovery and Retrieval may not be used in any advertising or publicity relating to the software without the specific, prior written permission of MCNC/CNIDR. THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MCNC/CNIDR BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ************************************************************************/ /*@@@ File: zserver.cxx Version: 2.00 $Revision: 1.15 $ Description: Z39.50 server Author: Kevin Gamiel, kevin.gamiel@cnidr.org @@@*/ #include "zserver.hxx" int main(int argc, char **argv) { ZSERVER *server; STRING Flag; INT x = 0; while (x < argc) { if (argv[x][0] == '-') { Flag = argv[x]; if (Flag.Equals("-V")) { cout << "zserver, Version " << VERS << endl; cout << "Copyright (c) 1995-2002 MCNC/CNIDR and A/WWW Enterprises" << endl; return 0; } } x++; } if (!setlocale(LC_CTYPE,"")) { cout << "Warning: Failed to set the locale!" << endl; } ios::sync_with_stdio(); server = new ZSERVER(argc, argv); server->StartServer(); delete server; return 0; } ZSERVER::ZSERVER(int argc, char **argv) : ZSESSION() { if(StartedByInetd()) { // // Route all console output to a file. // c_output_file.open(ZSERVER_INETD_OUTPUT,ios::app); #if defined(__GNUC__) && (__GNUC__ < 3) #ifndef __SUNPRO_CC cerr = c_output_file; cout = c_output_file; // for now, disallow this because of incompatibilities between // the relevant iostream classes..... #endif #endif c_debuglevel = 0; } INT VersionLength; // VersionLength = strlen("CNIDR zserver v")+strlen(ZserverVersion)+1; VersionLength = strlen("CNIDR zserver")+1; ZSERVER_NAME = new CHR[VersionLength]; // sprintf(ZSERVER_NAME, "CNIDR zserver v%s",ZserverVersion); sprintf(ZSERVER_NAME, "CNIDR zserver"); c_group = "Default"; c_inifile = "zserver.ini"; c_result_set = NULL; c_sapi = (SAPI *)NULL; c_default_db = ""; c_password = ""; STRING msg; cerr << endl << "CNIDR zserver, Version " << VERS; cerr << ", Copyright (c) 1995-2002 MCNC/CNIDR and A/WWW Enterprises\n" << endl; // Set defaults from zserver.ini in CWD (if available) LoadDefaultsFromFile(c_inifile, c_group); // Set defaults from command line STRING g, file; g = c_group; file = c_inifile; LoadDefaultsFromCommandLine(argc, argv, c_inifile, c_group, &g, &file); c_group = g; c_inifile = file; ExpandFileSpec(&c_inifile); c_inipath = c_inifile; RemoveFileName(&c_inipath); // // Now that we have loaded all possible combination of user variables, // lets copy those variables from the generic REGISTRY structure // into private class variables for ease of use. If any values // were not explicity set by the user, hard-coded defaults will // be inserted. // StoreDefaults(c_group); // Read list of databases to serve from zserver configuration file STRLIST Position, DBList; Position.AddEntry("Default"); Position.AddEntry("DBList"); c_defaults->GetData(Position, &DBList); c_database_count = DBList.GetTotalEntries(); if (c_database_count <= 0) { cerr << "No databases configured for zserver [" << c_inifile; cerr << "]." << endl; exit(1); } // Load SAPI configuration file c_defaults->ProfileGetString(c_group, "SAPI", "sapi.ini", &c_sapifile); if (c_debuglevel >= 2) { cerr << "zserver.ini Path = " << c_inifile << endl; cerr << "SAPI File = " << c_sapifile << endl; } // // If the filename has a leading slash, assume its a full path, // otherwise, append it to the inipath // if (c_sapifile.GetChr(1) != '/') c_sapifile.Insert(1, c_inipath); c_sapi = new SAPI(c_sapifile,DBList); if (!c_sapi) { cerr << "Failed to load CNIDR Search API [" << c_sapifile; cerr << "]" << endl; exit(1); } c_sapipath = c_sapifile; RemoveFileName(&c_sapipath); if(c_debuglevel >= 2) cerr << "Looking for map files in " << c_sapipath << endl; if (c_database_count <= 0) { cerr << "There are no databases to mount! "; cerr << "Edit DBList in " << c_inifile << endl; exit(1); } // Load the default field mapping table, bib1; c_mapping_table = new REGISTRY("map"); // Attempt mount the databases INT actual_db_count=0; STRING DBName; STRING MapFile; STRING Filename; STRLIST ResultList; STRINGINDEX count, j; SAPI_DB *TempDbPtr; REGISTRY *sapireg; ResultList.Clear(); sapireg = new REGISTRY("sapi"); sapireg->ProfileLoadFromFile(c_sapifile, ResultList); // sapireg->Dump(); for (INT i=1;i <= c_database_count;i++) { DBList.GetEntry(i, &DBName); cerr << endl; cerr << "Attempting to mount database " << DBName << endl; TempDbPtr = c_sapi->GetDatabasePtr(DBName); if (TempDbPtr == (SAPI_DB *)NULL) { cerr << "Database " << DBName << " not available!" << endl; break; } TempDbPtr->SetDebugLevel(c_debuglevel); if (!TempDbPtr->Initialize(c_password)) { cerr << "Failed to initialize " << DBName << ":"; TempDbPtr->GetLastErrorString(&msg); cerr << msg << endl; continue; } c_dblist.AddTail(TempDbPtr); cerr << "Successfully mounted." << endl; if (i == 1) c_default_db = DBName; actual_db_count++; // Load the field mapping for this database by stepping through any // mapping files the user has specified Position.Clear(); Position.AddEntry(DBName); Position.AddEntry("FieldMaps"); sapireg->GetData(Position, &ResultList); count = ResultList.GetTotalEntries(); if (count == 0) { // Store the default mapping table, bib1.map, for this database. Filename = c_sapipath; if (c_sapipath.GetLength() > 0) if (c_sapipath.GetChr(c_sapipath.GetLength()) != '/') Filename.Cat("/"); Filename.Cat("bib1.map"); AddToMappingTable(Filename, DBName); } else { // Load the explicitly given mapping tables for(j=1;j <= count;j++) { ResultList.GetEntry(j, &MapFile); MapFile.TrimLeading(); if (MapFile.GetChr(1) != '/') { Filename = c_sapipath; if (c_sapipath.GetLength() > 0) if (c_sapipath.GetChr(c_sapipath.GetLength())!='/') Filename.Cat("/"); Filename.Cat(MapFile); } else Filename = MapFile; AddToMappingTable(Filename, DBName); } } #ifdef DEBUG c_mapping_table->Dump(); #endif // delete sapireg; } delete sapireg; c_database_count = actual_db_count; TmpSapi=(SAPI *)NULL; if (c_database_count < 1) { cerr << "No local databases available!" << endl; } } ZSERVER::~ZSERVER() { if (c_result_set != NULL) // from J. Wehle delete c_result_set; if (c_sapi != NULL) delete c_sapi; } void ZSERVER::StartServer() { STRING StrVal; if (c_pidfile.GetLength() > 0) { pid_t MyPID; MyPID = getpid(); StrVal = (INT)MyPID; StrVal.WriteFile(c_pidfile); } // // Were we started by inetd or from the command line? // if(StartedByInetd()) { INT err; CHR msg[128]; // Can't do any debugging output until I fix the hexdir stuff to use // streams instead of stderr. // c_defaults->ProfileGetString(c_group, "DebugLevel", "1", &StrVal); // c_debuglevel = StrVal.GetInt(); c_debuglevel = 0; c_tcp = new TCPSOCK(); if ((err = c_tcp->LastError())) { c_tcp->ErrorMessage(err, msg, (sizeof(msg) - 1)); cerr << msg << endl; return; } c_tcp->SetSocket((TSOCKET)fileno(stdout)); StartSession(); } else { c_defaults->ProfileGetString(c_group, "DebugLevel", "1", &StrVal); c_debuglevel = StrVal.GetInt(); if(c_debuglevel >= 2) cerr << endl << "DebugLevel=" << c_debuglevel << "." << endl; if(c_server_type.CaseEquals("STANDALONE")) StartForkingServer(c_port); else /* Does not work yet if(c_server_type.CaseEquals("SERIAL")) { for(;;) { StartServer(c_port); sleep(5); } } else */ StartServer(c_port); } } /* * This should work on most systems, as long as they can handle the * SIGCHLD signal. The Posix version uses sigaction. BSD systems * might need to define HAVE_SIGINTERRUPT to enable the old version. */ INT ZSERVER::StartForkingServer(UINT Port) { if (!Listen(Port)) return 0; if (c_debuglevel >= 1) { cerr << "Forking Server Started, Port " << Port; cerr << "..." << endl; } pid_t child_pid, zpid; INT i,Status,j; INT id; INT MaxChildren; GlobalDebugLevel=c_debuglevel; ChildCount=0; MaxChildren = c_max_sessions; // Install an interrupt handler for SIGCHLD signal used by track of // the children for load balancing. Define NO_SIGINTERRUPT in the // Makefile CFLAGS if the compiler cannot find siginterrupt() #if defined(HAVE_SIGINTERRUPT) signal(SIGCHLD, ChildHandler); siginterrupt(SIGCHLD, 0); #elif defined(USE_OLDSIGNAL) signal(SIGCHLD, SIG_IGN); #else struct sigaction act, oact; // Set up 2 handler structures memset(&act, '\0', sizeof(act)); act.sa_handler = (void (*)(int))ChildHandler; // Install the handler sigemptyset(&act.sa_mask); // Clear the signal mask act.sa_flags = SA_RESTART | SA_NOCLDSTOP; if (sigaction(SIGCHLD, &act, &oact) != 0) // Error return 0; #endif sigset_t original_signal_set; sigset_t signal_set; sigemptyset(&signal_set); sigaddset(&signal_set, SIGCHLD); for(;;) { // Start looking for connections if (!AcceptClient()) return 0; // We have one on the line... if(c_debuglevel >= 1) cerr << "Received connection..." << endl; sigprocmask(SIG_BLOCK,&signal_set,&original_signal_set); // Are there too many children already? if (ChildCount >= MaxChildren) { // Send init response set to denied cerr << "Session limit exceeded" << endl; // SEND INIT RESPONSE HERE } else { if ((child_pid=fork()) < 0) { perror("Fork error"); } else if (child_pid == 0) { // we are in the first child process, so start up the session signal(SIGCHLD, SIG_IGN); sigprocmask(SIG_SETMASK,&original_signal_set,(sigset_t *)NULL); c_server_tcp->Close(); // Notify the databases that a spawn has occurred IPOSITION Pos; SAPI_DB *Database; Pos = c_dblist.GetHeadPosition(); while ( (Database = (SAPI_DB *)c_dblist.GetNext(&Pos))) { if (Database->Spawn() != GDT_TRUE) { STRING DBName; STRING msg; Database->GetName(&DBName); Database->GetLastErrorString(&msg); cerr << DBName << " was unable to spawn." << endl; cerr << msg << endl; exit(1); } } // Start the session StartSession(); exit(0); } else { // Bottom of first fork(), so it must have succeeded This must // be in the parent because the children have all been picked // off by now. #if !defined(USE_OLDSIGNAL) // Keep track of number of children for load balancing purposes // We decrement the counter when we get the signal that the // child has terminated (see ChildHandler) c_children[ChildCount] = child_pid; ChildCount++; if(c_debuglevel >= 5) { cerr << "Now up to " << ChildCount << " child process(es)" << endl; cerr.flush(); } #endif } // fork() } // test against c_max_sessions sigprocmask(SIG_SETMASK,&original_signal_set,(sigset_t *)NULL); delete c_tcp; // Allocated in AcceptClient() c_tcp = NULL; } // for #if !defined(HAVE_SIGINTERRUPT) && !defined(USE_OLDSIGNAL) // Reinstall the old SIGCHLD handler if (sigaction(SIGCHLD, &oact, (struct sigaction *)NULL) != 0) // Whoops! return 0; #endif } void exit_status(pid_t child_pid, INT status) { #ifdef __AIX if(GlobalDebugLevel >= 1) fprintf(stderr, "\nTermination signal from waitpid for child process %d\n", child_pid); ChildCount--; #else if (WIFEXITED(status)) { fprintf(stderr, "\nNormal termination of child process %d, exit status = %d\n", child_pid, WEXITSTATUS(status)); ChildCount--; } else if (WIFSIGNALED(status)) { if(GlobalDebugLevel >= 1) fprintf(stderr, "\nAbnormal termination of child process %d by signal %d\n", child_pid, WTERMSIG(status)); ChildCount--; } else if (WIFSTOPPED(status)) { if(GlobalDebugLevel >= 1) fprintf(stderr, "\nChild process %d stopped by signal %d\n", child_pid, WSTOPSIG(status)); } else { if(GlobalDebugLevel >= 1) fprintf(stderr, "\nUnknown status %d returned from waitpid for child process %d\n", status,child_pid); } #endif return; } // Called on child process signal #if defined(HAVE_SIGINTERRUPT) || defined(USE_OLDSIGNAL) #ifndef __SUNPRO_CC void ChildHandler(int t) #else void ChildHandler(int t,...) #endif #else void ChildHandler(int sig,...) #endif { pid_t child_pid; INT Status; // ChildCount--; // Reinstall the handler #if defined(HAVE_SIGINTERRUPT) signal(SIGCHLD, ChildHandler); siginterrupt(SIGCHLD, 0); #endif if ( !ChildCount ) return; while ( ChildCount && (child_pid = waitpid(-1,&Status,WNOHANG|WUNTRACED)) > 0) { exit_status(child_pid,Status); if(GlobalDebugLevel >= 5) { fprintf(stderr,"There are now %d child process(es) left\n", ChildCount); fflush(stderr); } } if (child_pid < 0) { fprintf(stderr,"From ChildHandler "); fflush(stderr); perror("Child wait error"); // error } // return value is 0 if no more children return; } INT ZSERVER::Listen(UINT Port) { INT err; CHR msg[128]; c_server_tcp = new TCPSOCK(); if ((err = c_server_tcp->LastError())) { c_server_tcp->ErrorMessage(err, msg, (sizeof(msg) - 1)); fprintf(stderr,"%s\n",msg); return 0; } c_server_tcp->Listen(Port, 5); if ((err = c_server_tcp->LastError())) { c_server_tcp->ErrorMessage(err, msg, (sizeof(msg) - 1)); fprintf(stderr,"Listen failed: %s\n",msg); return 0; } if (c_debuglevel >= 2) fprintf(stderr,"Listening on port %d...\n",Port); if (c_debuglevel >= 7) { fprintf(stderr,"Listening to socket #%d...\n",c_server_tcp->Socket()); } c_server_tcp->BlockingModeON(); return 1; } INT ZSERVER::StartServer(UINT Port) { if (Listen(Port)) { if (c_debuglevel >= 1) { fprintf(stderr,"Single Connection Server Started, Port %d...\n",Port); } if (!AcceptClient()) return 0; // We dont need the main socket anymore, so kill it. delete c_server_tcp; if (c_debuglevel >= 1) fprintf(stderr,"Received connection...\n"); StartSession(); return 1; } else return 0; } // 1 if connection, 0 otherwise. // // Blocks until connection arrives // INT ZSERVER::AcceptClient() { INT err; CHR msg[128]; c_tcp = new TCPSOCK(); if (c_debuglevel >= 5) fprintf(stderr,"\nWaiting to Accept client...\n"); if (c_server_tcp->Accept(c_tcp) > 0) { if (c_debuglevel >= 1) fprintf(stderr,"Connection has arrived...\n"); c_tcp->GetHostname(&c_client_hostname); c_tcp->GetIPAddress(&c_client_ipaddress); // Allocate the tcp stream to be used for this client // connection. if ((err = c_tcp->LastError())) { c_tcp->ErrorMessage(err, msg, (sizeof(msg) - 1)); fprintf(stderr,"%s\n",msg); return 0; } return 1; } if ((err = c_tcp->LastError())) { c_tcp->ErrorMessage(err, msg, (sizeof(msg) - 1)); fprintf(stderr,"%s\n",msg); return 0; } return 0; } void ZSERVER::StartSession() { unsigned short type; INT4 len, BytesSent; CHR TempBuf[64]; UCHR *buf; CHR *TempString; INT Finished = 0; IS_TIME_T Start, Done; time(&Start); do { if((buf = ReadPDU(&len, &type)) == NULL) { // Client closed on us without a proper CLOSE time(&Done); sprintf(TempBuf, "%ld", Done - Start); Log("DISCONNECT", TempBuf, 1, 0); c_tcp->Close(); FreeSapi(); return; // rs leaks here, allocated near line 1108 } switch(type) { case INITREQUEST_TAG: { ZINITREQUEST *rq; // Init Request PDU INT4 Result = 1; // Assume success // Cast buffer to PDU rq = new ZINITREQUEST(buf, len); // Negotiate based on request parameters // // Grab the Reference ID // c_refid = ""; rq->GetReferenceId(&c_refid); if ((c_debuglevel >= 5) && (c_refid.GetLength() > 0)) cerr << "InitRequest: ReferenceID " << c_refid << endl; // // We support versions 2 and 3 (default=2) // rq->GetProtocolVersion(&c_protocol_version); c_protocol_version.UpperCase(); if((!c_protocol_version.Equals("YY")) && (!c_protocol_version.Equals("YYY"))) c_protocol_version = "YY"; // // We support Init, Search and Present. // If you need their options, here they // are. Ours were set in our constructor. // STRING Value; rq->GetOptions(&Value); // // Negotiate preferred message size // rq->GetPreferredMessageSize(&c_prefmsgsize); if(c_prefmsgsize > ZPREFMSGSIZE) c_prefmsgsize = ZPREFMSGSIZE; if(c_prefmsgsize < 1024) c_prefmsgsize = 1024; // // Negotiate maximum message size // rq->GetExceptionalRecordSize(&c_maxrecordsize); if(c_maxrecordsize > ZMAXRECORDSIZE) c_maxrecordsize = ZMAXRECORDSIZE; if(c_maxrecordsize < 1024) c_maxrecordsize = 1024; // // GroupId/UserId/Password? // rq->GetGroupId(&c_groupid); if ((c_debuglevel >= 5) && (c_groupid.GetLength() > 0)) cerr << "InitRequest: GroupID " << c_groupid << endl; rq->GetUserId(&c_userid); if ((c_debuglevel >= 5) && (c_userid.GetLength() > 0)) cerr << "InitRequest: UserID " << c_userid << endl; rq->GetPassword(&c_password); if ((c_debuglevel >= 5) && (c_password.GetLength() > 0)) cerr << "InitRequest: Password " << c_password << endl; // // If you want to do authentication, // do it here. We will add support later. // rq->GetAuthentication(&c_password); if ((c_debuglevel >= 5) && (c_password.GetLength() > 0)) cerr << "InitRequest: Authentication " << c_password << endl; delete rq; // // Build the response // ZINITRESPONSE *rs; rs = new ZINITRESPONSE(c_refid, c_protocol_version, c_options, c_prefmsgsize, c_maxrecordsize, Result, CNIDR_IMP_ID, ZSERVER_NAME, IsiteVersion); if((BytesSent = SendPDU(rs)) == 0) Finished = 1; delete rs; Log("INIT", "-", 1, BytesSent); break; } case SEARCHREQUEST_TAG: { ZSEARCHREQUEST *rq; // Search Request PDU rq = new ZSEARCHREQUEST(buf, len); // // Grab the Reference ID // rq->GetReferenceId(&c_refid); if ((c_debuglevel >= 5) && (c_refid.GetLength() > 0)) cerr << "SearchRequest: ReferenceID " << c_refid << endl; // Perform the search ZSEARCHRESPONSE *rs; // Search Response PDU rs = Search(*rq); delete rq; if((BytesSent = SendPDU(rs)) == 0) Finished = 1; // Get result of search for logging purposes INT4 Result; rs->GetResultCount(&Result); Log("SEARCH", c_dbname, Result, BytesSent); delete rs; break; } case PRESENTREQUEST_TAG: { ZPRESENTREQUEST *rq; // Present Request PDU rq = new ZPRESENTREQUEST(buf, len); // // Grab the Reference ID // rq->GetReferenceId(&c_refid); if ((c_debuglevel >= 5) && (c_refid.GetLength() > 0)) cerr << "PresentRequest: ReferenceID " << c_refid << endl; ZPRESENTRESPONSE *rs; // Present Response PDU rs = Present(*rq); if(!rs) { cerr << "Fatal Error. No PRESENTRESPONSE generated" << endl; exit(1); } delete rq; if((BytesSent = SendPDU(rs)) == 0) Finished = 1; INT4 Status; rs->GetPresentStatus(&Status); Log("PRESENT", c_dbname, Status, BytesSent); delete rs; break; } case EXTENDEDSERVICESREQUEST_TAG: { ZESREQUEST *rq; // Extended Service Request PDU INT4 Function; STRING PackageType; STRING PackageName; STRING UserId; INT4 RetentionTime; STRING Description; INT4 WaitAction; rq = new ZESREQUEST(buf, len); // // Grab the Reference ID // rq->GetReferenceId(&c_refid); if ((c_debuglevel >= 5) && (c_refid.GetLength() > 0)) cerr << "ExtendedServicesRequest: ReferenceID " << c_refid << endl; rq->GetFunction(&Function); rq->GetPackageType(&PackageType); rq->GetPackageName(&PackageName); rq->GetUserId(&UserId); rq->GetRetentionTime(&RetentionTime); rq->GetDescription(&Description); rq->GetWaitAction(&WaitAction); delete rq; ZESTASKPACKAGE *TaskPackage; RetentionTime = 0; TaskPackage = (ZESTASKPACKAGE *)NULL; if ( WaitAction != ES_DONT_RETURN_PACKAGE_WAITACTION ) TaskPackage = new ZESTASKPACKAGE( PackageType, PackageName, UserId, RetentionTime, Description, ES_TP_COMPLETE_TASKSTATUS); ZESRESPONSE *rs;// Extended Service Response PDU if ( PackageType.Equals(ES_UPDATE_OID) ) { TempString = "UPDATE"; if ( Function == ES_CREATE_FUNCTION ) { ZESUPDATEREQUEST *request; request = new ZESUPDATEREQUEST(buf,len); rs = ESUpdate(TaskPackage, *request); delete request; } else { ZDEFAULTDIAGFORMAT *diagrec; diagrec = new ZDEFAULTDIAGFORMAT(219, PackageName, BIB1_DIAG_OID); rs = new ZESRESPONSE("", ES_FAILURE_OPERATIONSTATUS, diagrec,NULL); delete diagrec; } } else { TempString = "UNSUPPORTED"; ZDEFAULTDIAGFORMAT *diagrec; diagrec = new ZDEFAULTDIAGFORMAT(221, PackageType, BIB1_DIAG_OID); rs = new ZESRESPONSE("", ES_FAILURE_OPERATIONSTATUS, diagrec, NULL); delete diagrec; } if ( TaskPackage ) delete TaskPackage; if((BytesSent = SendPDU(rs)) == 0) Finished = 1; INT4 OperationStatus; rs->GetOperationStatus(&OperationStatus); Log("EXTENDSERVICE", TempString, OperationStatus, BytesSent); delete rs; break; } case CLOSE_TAG: { // Cast buffer to PDU ZCLOSE *pdu; pdu = new ZCLOSE(buf, len); // Why did they close? INT4 Reason; pdu->GetCloseReason(&Reason); if(c_debuglevel >= 1) { fprintf(stderr,"Client has closed. Reason: "); switch(Reason) { case 0: fprintf(stderr, "Finished\n"); break; case 1: fprintf(stderr, "Shutdown\n"); break; case 2: fprintf(stderr, "System Problem\n"); break; case 3: fprintf(stderr, "Cost Limit\n"); break; case 4: fprintf(stderr, "Resources\n"); break; case 5: fprintf(stderr, "Security Violation\n"); break; case 6: fprintf(stderr, "ProtocolError\n"); break; case 7: fprintf(stderr, "Lack of Activity\n"); break; case 8: fprintf(stderr, "Peer Abort\n"); break; case 9: fprintf(stderr, "Unspecified\n"); break; default: fprintf(stderr, "Unknown reason\n"); break; } fflush(stderr); } // Done! Finished = 1; time(&Done); sprintf(TempBuf, "%ld", Done - Start); Log("CLOSE", TempBuf, 1, 0); FreeSapi(); delete pdu; break; } default: fprintf(stderr, "Unsupported PDU of %d\n",type); } delete [] buf; } while(!Finished); } ZSEARCHRESPONSE * ZSERVER::Search(ZSEARCHREQUEST & Request) { ZSEARCHRESPONSE *rs; INT hitcount, i; CHR *p; BERBROWSER *b; ZRECORDLIST *Records; ZDEFAULTDIAGFORMAT *diagrec; SAPI_DB *Database; INT4 SmallUpper, LargeLower, MediumCount; STRING rec_syntax, es_name, attr_set; INT start_point=0, num_requested; INT4 SearchStatus, ResultSetStatus, PresentStatus; GDT_BOOLEAN IsNullQuery = GDT_FALSE; IPOSITION Pos; // Peter Schweitzer's changes marked with [PS] // [PS] To understand what's really happening, we need a time stamp // and possibly the pid struct tm *Time; time_t time_clock; CHR when[64]; pid_t pid = getpid(); time (&time_clock); Time = localtime (&time_clock); strftime (when, sizeof(when), "[%d/%b/%Y %H:%M:%S]",Time); cout << "" << endl; // We only support getting a single database name Request.GetDatabaseName(&c_dbname); if (c_dbname.CaseEquals("XXDEFAULT")) c_dbname = c_default_db; // Get the list of local databases already mounted Pos = c_dblist.GetHeadPosition(); // // Here is where we fiddle with databases.... // split the list into a set of databases, and load a fake sapi. // // if it has a plus, do MetaSearch, and Set Database // if (c_dbname.Search(':') >= 1) { Database=MetaSearch(c_dbname); } else if (c_dbname.Search('+') >= 1) { Database=MetaSearch(c_dbname); } else if (c_dbname.Search(',') >= 1) { Database=MetaSearch(c_dbname); } else { // Otherwise, it is a local index, of which we can search just one. while ((Database = (SAPI_DB *)c_dblist.GetNext(&Pos))) { STRING name; Database->GetName(&name); if (c_debuglevel > 5) cerr << "checking against " << name << endl; if (c_dbname.CaseEquals(name)) break; } } if (Database == NULL) { // illegal database name requested if (c_debuglevel >= 2) { cerr << "Illegal database name requested: " << c_dbname; cerr << endl; } // Construct a non-surrogate diagnostic record and return // with response Records = new ZRECORDLIST(NONSURROGATEDIAGNOSTIC_TAG); diagrec = new ZDEFAULTDIAGFORMAT(109, c_dbname, BIB1_DIAG_OID); Records->AddRecord(diagrec); hitcount = 0; num_requested = 0; start_point = 0; SearchStatus = GDT_FALSE; ResultSetStatus = 3; PresentStatus = 5; rs = new ZSEARCHRESPONSE(c_refid, hitcount, num_requested, start_point, SearchStatus, ResultSetStatus, PresentStatus, Records); cout << "" << endl; // [PS] delete diagrec; // delete Records; // segfaults on bad name in destructor return rs; } // If this database supports OPSTACK queries, then perform custom query :-) STRING DbType; ZSQUERY *Query; Database->GetType(&DbType); if (c_debuglevel>=2) cerr << "Searching " << DbType << " Database " << c_dbname << endl; if ((DbType.CaseEquals("ISEARCH")) || (DbType.CaseEquals("VISEARCH"))) { // // If this database supports OPSTACK queries (and Isearch does) // then perform custom query // if (c_debuglevel >= 2) { cerr << "Searching using a OPSTACK based query" << endl; } STRLIST FieldsAvailable; // Figure out which fields are actually available Database->GetFieldNames(&FieldsAvailable); if (FieldsAvailable.GetTotalEntries() == 0) cerr << "No fields available!!" << endl; // ConvertQuery puts BER into a stack b = new BERBROWSER(&Request); Query = new ZSQUERY(); Query->SetDebugLevel(c_debuglevel); Query->ConvertQuery(b, c_mapping_table, FieldsAvailable, c_dbname); delete b; if (Query->Error()) { INT code; STRING addinfo; Query->GetErrorInfo(&code, &addinfo); // build diag rec here Records = new ZRECORDLIST(NONSURROGATEDIAGNOSTIC_TAG); diagrec = new ZDEFAULTDIAGFORMAT(code, addinfo, BIB1_DIAG_OID); Records->AddRecord(diagrec); hitcount = 0; num_requested = 0; start_point = 0; SearchStatus = GDT_FALSE; ResultSetStatus = 3; PresentStatus = 5; rs = new ZSEARCHRESPONSE(c_refid, hitcount, num_requested, start_point, SearchStatus, ResultSetStatus, PresentStatus, Records); cout << "" << endl; // [PS] delete diagrec; delete Query; // delete Records; return rs; } } else if ((DbType.CaseEquals("MYSQL")) || (DbType.CaseEquals("ODBC"))) { // // If this database supports OPSTACK queries (and Isearch does) // then perform custom query // if(c_debuglevel >= 2) { cerr << "Searching using a OPSTACK based query" << endl; } STRLIST FieldsAvailable; // Figure out which fields are actually available Database->GetFieldNames(&FieldsAvailable); if(FieldsAvailable.GetTotalEntries() == 0) cerr << "No fields available!!" << endl; // ConvertQuery puts BER into a stack b = new BERBROWSER(&Request); Query = new ZSQUERY(); Query->SetDebugLevel(c_debuglevel); Query->ConvertQuery(b, c_mapping_table, FieldsAvailable, c_dbname); delete b; if (Query->Error()) { INT code; STRING addinfo; Query->GetErrorInfo(&code, &addinfo); // build diag rec here Records = new ZRECORDLIST(NONSURROGATEDIAGNOSTIC_TAG); diagrec = new ZDEFAULTDIAGFORMAT(code, addinfo, BIB1_DIAG_OID); Records->AddRecord(diagrec); hitcount = 0; num_requested = 0; start_point = 0; SearchStatus = GDT_FALSE; ResultSetStatus = 3; PresentStatus = 5; rs = new ZSEARCHRESPONSE(c_refid, hitcount, num_requested, start_point, SearchStatus, ResultSetStatus, PresentStatus, Records); cout << "" << endl; // [PS] delete diagrec; delete Query; // delete Records; return rs; } } else { // // This is a general RPN query here, so we use the SAPI to actually // process the query // if(c_debuglevel >= 2) { cerr << "Searching using a KWAQS based query" << endl; } // Convert from Z39.50 structure to KWAQS KWAQS_STRING *q; b = new BERBROWSER(&Request); // J. Wehle q = new KWAQS_STRING; q->SetDebugLevel(c_debuglevel); q->Convert(b, Request); delete b; if (q->Error()) { // We got an error converting the query, so act accordingly INT code; STRING addinfo; q->GetErrorInfo(&code, &addinfo); // build diag rec here Records = new ZRECORDLIST(NONSURROGATEDIAGNOSTIC_TAG); diagrec = new ZDEFAULTDIAGFORMAT(code, addinfo, BIB1_DIAG_OID); Records->AddRecord(diagrec); hitcount = 0; num_requested = 0; start_point = 0; SearchStatus = GDT_FALSE; ResultSetStatus = 3; PresentStatus = 5; rs = new ZSEARCHRESPONSE(c_refid, hitcount, num_requested, start_point, SearchStatus, ResultSetStatus, PresentStatus, Records); cout << "" << endl; // [PS] delete diagrec; // delete Records; return rs; } // put KWAQS_STRING q into a query. Should put it in a stack Query = new ZSQUERY(); Query->SetKWAQSTerm(*q); delete q; } Request.GetMediumSetPresentNumber(&MediumCount); Query->SetHitsRequested(MediumCount); c_attr_set = Query->GetAttributeSetID(); attr_set = c_attr_set; STRING Term; Query->GetTerm(&Term); if (Term.GetLength() > 0) { // Do the search if (c_result_set) delete c_result_set; c_result_set = Database->Search(*Query); if ( !c_result_set ) { // No result set for some reason - check the error strings STRING addinfo; Database->GetLastErrorString(&addinfo); Records = new ZRECORDLIST(NONSURROGATEDIAGNOSTIC_TAG); diagrec = new ZDEFAULTDIAGFORMAT(100, addinfo, BIB1_DIAG_OID); Records->AddRecord(diagrec); hitcount = 0; num_requested = 0; start_point = 0; SearchStatus = GDT_FALSE; ResultSetStatus = 3; PresentStatus = 5; rs = new ZSEARCHRESPONSE(c_refid, hitcount, num_requested, start_point, SearchStatus, ResultSetStatus, PresentStatus, Records); cout << "" << endl; // [PS] delete diagrec; delete Query; return rs; } hitcount = c_result_set->GetHitCount(); } else { hitcount = Database->GetTotalRecords(); IsNullQuery = GDT_TRUE; } c_hitcount = hitcount; if (c_debuglevel >= 5) { if (hitcount == 0) cerr << "There were no hits." << endl; else if (hitcount == 1) cerr << "There was 1 hit." << endl; else cerr << "There were " << hitcount << " hits." << endl; } Request.GetSmallSetUpperBound(&SmallUpper); Request.GetLargeSetLowerBound(&LargeLower); Request.GetMediumSetPresentNumber(&MediumCount); Request.GetPreferredRecordSyntax(&rec_syntax); es_name="B"; if (hitcount <= SmallUpper) { start_point = 1; num_requested = hitcount; } else if (hitcount < LargeLower) { start_point = 1; num_requested=((hitcount < MediumCount) ? hitcount : MediumCount); } else { num_requested = 0; } /* ZRESPONSERECORDS records; BuildReturnRecordList(&records,es_name,rec_syntax,start_point, num_requested); rs = new ZSEARCHRESPONSE(c_refid, num_requested, 0, 1, GDT_TRUE, 1, 0, &records); */ if(c_debuglevel >= 5) cerr << "Building return response records." << endl; SearchStatus = GDT_TRUE; ResultSetStatus = 1; PresentStatus = 0; ZRESPONSERECORDS records; // ZRESPONSERECORDS *records; // records = new ZRESPONSERECORDS; if (!(IsNullQuery)) { // We might have return records in this case // BuildReturnRecordList(records, // es_name, // rec_syntax, // start_point, // num_requested); BuildReturnRecordList(&records, es_name, rec_syntax, start_point, num_requested); } // // Grab the Reference ID // if ((c_debuglevel >= 5) && (c_refid.GetLength() > 0)) cerr << "SearchResponse: ReferenceID " << c_refid << endl; // rs = new ZSEARCHRESPONSE(c_refid, // hitcount, // num_requested, // start_point, // SearchStatus, // ResultSetStatus, // PresentStatus, // records); rs = new ZSEARCHRESPONSE(c_refid, hitcount, num_requested, start_point, SearchStatus, ResultSetStatus, PresentStatus, &records); //delete records; delete Query; return rs; } // // After we have loaded all possible combinations of user variables from files // and the command line, copy them from the generic REGISTRY structure // into private class variables for ease of use. If an attribute does not // have a value, a hard-coded default is added. // void ZSERVER::StoreDefaults(const STRING & Group) { //STRLIST Pos, ValList; STRING StrVal; c_defaults->ProfileGetString(Group, "Port", "210", &StrVal); c_port = StrVal.GetInt(); c_defaults->ProfileGetString(Group, "DebugLevel", "1", &StrVal); c_debuglevel = StrVal.GetInt(); c_defaults->ProfileGetString(Group, "PidFile", "/var/run/zserver.pid", &c_pidfile); c_defaults->ProfileGetString(Group, "ServerType", "STANDALONE", &c_server_type); c_defaults->ProfileGetString(Group, "MaxSessions", "10", &StrVal); c_max_sessions = StrVal.GetInt(); c_defaults->ProfileGetString(Group, "TimeOut", "3600", &StrVal); c_timeout = StrVal.GetInt(); c_defaults->ProfileGetString(Group, "Trace", "OFF", &c_trace); c_defaults->ProfileGetString(Group, "TraceLog","/tmp/zserver_trace.log", &c_tracelog); c_defaults->ProfileGetString(Group, "AccessLog", "/tmp/zserver_access.log", &c_accesslog); c_defaults->ProfileGetString(Group, "ReverseNameLookup", "ON", &c_reverse_name_lookup); c_defaults->ProfileGetString(Group, "SAPI", "sapi.ini", &c_sapifile); c_defaults->ProfileGetString(Group, "DiagSetId", "1.2.840.10003.3.1", &c_diagsetid); c_defaults->ProfileGetString(Group, "iImpID", "34", &c_imp_id); c_defaults->ProfileGetString(Group, "iImpName", "CNIDR zserver", &c_imp_name); c_defaults->ProfileGetString(Group, "iImpVersion", IsiteVersion, &c_imp_version); c_defaults->ProfileGetString(Group, "iPreferredMsgSize", "32768", &StrVal); c_preferred_msg_size = StrVal.GetInt(); c_defaults->ProfileGetString(Group, "iMaxRecSize", "8388608", &StrVal); c_max_rec_size = StrVal.GetLong(); } CHR * ResultSet_GetRecord( SAPI_RSET *c_result_set, INT i, LONG c_max_rec_size, LONG *rec_length, const STRING& attr_set, CHR *es_cname, CHR *c_rec_syntax, STRING *actual_rec_syntax) { CHR *rec=(CHR*)NULL; PLIST RecordList; //rec[0]='\0'; STRING esn; STRING cip_rec_syntax; cip_rec_syntax = c_rec_syntax; // cout << "Get Record es: " << es_cname<Present(i,1,attr_set,esn,cip_rec_syntax,&RecordList)) { rec=new CHR[1024]; sprintf(rec,"Record %d failed es = %s MaxSize = %ld",i,es_cname, c_max_rec_size); } else { IPOSITION p; p = RecordList.GetHeadPosition(); MIME_RECORD *r; STRING value; // delete [] rec; if((r = (MIME_RECORD *)RecordList.GetNext(&p))) { r->GetData(&value); rec=value.NewCString(); if((INT)strlen(rec)>c_max_rec_size){ delete rec; rec=new CHR[c_max_rec_size]; value.GetCString(rec,c_max_rec_size-1); } RecordList.RemoveAll(); delete r; } } if (rec) *rec_length=strlen(rec); else *rec_length=0; *actual_rec_syntax = cip_rec_syntax.NewCString(); return rec; } // Someday, this will replace ResultSet_GetRecord. It requests the // the entire result set at one time, so it should increase performance // considerably. I need to change the code so that it returns an array // of strings here (or maybe an STRLIST), then modify BuildReturnRecordList // so that it requests them all at once and builds the list correctly CHR * ResultSet_GetRecords( SAPI_RSET *c_result_set, INT start, INT count, LONG c_max_rec_size, LONG *rec_length, const STRING& attr_set, CHR *es_cname, CHR *c_rec_syntax, STRING *actual_rec_syntax) { CHR *rec=(CHR*)NULL; PLIST RecordList; //rec[0]='\0'; STRING esn; STRING cip_rec_syntax; cip_rec_syntax = c_rec_syntax; // cout << "Get Record es: " << es_cname<Present(start, count, attr_set, esn, cip_rec_syntax, &RecordList)) { rec=new CHR[1024]; sprintf(rec,"Record %d failed es = %s MaxSize = %ld",start,es_cname, c_max_rec_size); } else { IPOSITION p; p = RecordList.GetHeadPosition(); MIME_RECORD *r; STRING value; while((r = (MIME_RECORD *)RecordList.GetNext(&p))) { r->GetData(&value); // This is not correct. We need to build an array of strings here rec=value.NewCString(); if((INT)strlen(rec)>c_max_rec_size){ delete rec; rec=new CHR[c_max_rec_size]; value.GetCString(rec,c_max_rec_size-1); } } } if (rec) *rec_length=strlen(rec); else *rec_length=0; *actual_rec_syntax = cip_rec_syntax.NewCString(); // Might want to reallocate c_rec_syntax here and copy the contents of // actual_rec_syntax into it, just in case someone (incorrectly) tries // to use it after the return. return rec; } ZPRESENTRESPONSE * ZSERVER::Present(ZPRESENTREQUEST & Request) { ZRESPONSERECORDS records; STRING attr_set; // // Version 3 conformance states we must recognize // AdditionalRanges. // if(Request.HasAdditionalRanges()) return PresentResponseWithDiagnostic(BIB1_DIAG_OID, 243, ""); // // What range of records being requested? INT4 start_point, num_requested; Request.GetResultSetStartPoint(&start_point); Request.GetNumberOfRecordsRequested(&num_requested); // Peter Schweitzer's changes marked with [PS] // [PS] To understand what's really happening, we need a time stamp // and possibly the pid struct tm *Time; time_t time_clock; CHR when[64]; pid_t pid = getpid(); time (&time_clock); Time = localtime (&time_clock); strftime (when, sizeof(when), "[%d/%b/%Y %H:%M:%S]",Time); if(c_debuglevel >= 5) { // cerr << "Client has asked for records " << start_point; // cerr << " through " << (start_point + num_requested-1) cerr << "Client has asked for " << num_requested << " records, starting with record #" << start_point << endl; } if(start_point < 1 || (start_point + (num_requested - 1)) > c_hitcount) return PresentResponseWithDiagnostic(BIB1_DIAG_OID, 13, ""); INT i; CHR *record; INT4 rec_length; CHR *es_cname; STRING es_name, rec_syntax; // // Element Set Name? // // We must recognize COMPLEX type // if(Request.HasCompSpec()) return PresentResponseWithDiagnostic(BIB1_DIAG_OID, 244, ""); // // Default to "B" // Request.GetElementSetName(&es_name); if(es_name.Equals("")) es_name = "B"; if(c_debuglevel >= 5) { cerr << "Client has asked for element set name of "; cerr << es_name << endl; } // Preferred Record Syntax? Request.GetPreferredRecordSyntax(&rec_syntax); if(c_debuglevel >= 5) { cerr << "Client has asked for record syntax of "; if (rec_syntax.Equals("")) cerr << "SUTRS (default)" << endl; else cerr << rec_syntax << endl; } // Default to SUTRS if client didnt specify if(rec_syntax.Equals("")) rec_syntax = SUTRS_OID; // c_rec_syntax = rec_syntax.NewCString(); cout << "" << endl; // BuildReturnRecordList(records,es_name,rec_syntax,start_point, // num_requested); BuildReturnRecordList(&records,es_name,rec_syntax,start_point, num_requested); if (c_debuglevel >= 5) { cerr << "Record list built." << endl; } INT NextRecord; NextRecord = start_point + records.GetRecordCount(); if (NextRecord > c_hitcount) NextRecord = 0; // return new ZPRESENTRESPONSE(c_refid, records->GetRecordCount(), // (start_point + records->GetRecordCount()), 0, // records); return new ZPRESENTRESPONSE(c_refid, records.GetRecordCount(), NextRecord, 0, &records); } void ZSERVER::BuildReturnRecordList(ZRESPONSERECORDS *records, STRING es_name, STRING rec_syntax, INT start_point, INT num_requested) { STRING temprecord; CHR *es_cname,*c_rec_syntax; INT i; CHR *record; LONG rec_length; // What happens if we null this instead? STRING actual_rec_syntax = rec_syntax; if (c_debuglevel > 5) { cerr << "Preferred RecSyntax = " << actual_rec_syntax << endl; } // Make sure we only use the most up-to-date OIDs /* if ((actual_rec_syntax.Equals(OLD_HTML_OID)) || (actual_rec_syntax.Equals(CNIDR_HTML_OID))) { actual_rec_syntax = HTML_OID; } else if (actual_rec_syntax.Equals(CNIDR_SGML_OID)) { actual_rec_syntax = SGML_OID; } */ if (actual_rec_syntax.GetLength() == 0) actual_rec_syntax = SUTRS_OID; c_rec_syntax = actual_rec_syntax.NewCString(); for(i=start_point;i < (start_point+num_requested);i++) { es_cname = es_name.NewCString(); // cout << "es: " << es_cname <<" "<" << endl; if(actual_rec_syntax.Equals(USMARC_OID)) { ZUSMARCRECORD *usm; temprecord = record; usm = new ZUSMARCRECORD(temprecord); npr = new ZNAMEPLUSRECORD(c_dbname, usm); } else if (actual_rec_syntax.Equals(HTML_OID)) { ZEXTOCTETALIGNED *s; s = new ZEXTOCTETALIGNED(HTML_OID, record, GDT_TRUE); npr = new ZNAMEPLUSRECORD(c_dbname, s); } else if (actual_rec_syntax.Equals(XML_OID)) { ZEXTOCTETALIGNED *s; s = new ZEXTOCTETALIGNED(XML_OID, record, GDT_TRUE); npr = new ZNAMEPLUSRECORD(c_dbname, s); } else if (actual_rec_syntax.Equals(GRS1_OID)) { ZGRS1RECORD *s; if(c_debuglevel >= 5) cout << "GRS-1 Requested "<= 7) { cout << "No recsyntax specified, SUTRS returned "<AddRecord(npr); delete [] record; delete [] es_cname; } if(c_debuglevel >= 5) { cerr << "Server is returning record syntax of " << actual_rec_syntax << endl; } delete [] c_rec_syntax; } ZESUPDATERESPONSE * ZSERVER::ESUpdate(ZESTASKPACKAGE *TaskPackage, ZESUPDATEREQUEST &Request) { INT4 action; SAPI_DB *Database; ZDEFAULTDIAGFORMAT *diagrec; ZESUPDATERESPONSE *rs; // We only support getting a single database name Request.GetAction(&action); Request.GetDatabaseName(&c_dbname); if ( c_dbname.CaseEquals("xxdefault") ) c_dbname = c_default_db; // Get the list of local databases already mounted IPOSITION Pos; Pos = c_dblist.GetHeadPosition(); // here is where we fiddle with databases.... // split the list into a set of databases, and load a fake sapi. // if it has a plus, do MetaSearch, and Set Database if (c_dbname.Search(':') >= 1) { Database = MetaSearch(c_dbname); } else if (c_dbname.Search('+') >= 1) { Database = MetaSearch(c_dbname); } else { // Otherwise, it is a local index, of which we can search just one. while ( (Database = (SAPI_DB *)c_dblist.GetNext(&Pos)) ) { STRING name; Database->GetName(&name); cout << "checking against " << name << endl; if ( c_dbname.CaseEquals(name) ) break; } } if (Database == NULL) { cerr << "Update request specified an unknown database name: "; cerr << c_dbname << endl; diagrec = new ZDEFAULTDIAGFORMAT(109, c_dbname, BIB1_DIAG_OID); rs = new ZESUPDATERESPONSE("", TaskPackage, action, c_dbname, ES_UPDATE_FAILURE_UPDATESTATUS, diagrec); delete diagrec; return rs; } // How many records to be process are included with the request? ASN1TAGU exttag(ASN1_EXTERNAL); ASN1TAGU oidtag(ASN1_OBJECTIDENTIFIER); BERBROWSER ber(&Request); INT4 count=0; STRING msg; STRING OID; if ( !ber.GetSubdirectory(ES_TASKSPECIFIC_TAG, &ber) || !ber.GetSubdirectory(exttag, &ber) || !ber.GetOID(oidtag, &OID) || OID != ES_UPDATE_OID || !ber.GetSubdirectory(ES_TS_ESREQUEST_TAG, &ber) || !ber.GetSubdirectory(ES_TS_NOTTOKEEP_TAG, &ber) || (count = ber.GetSubdirectoryCount()) == 0) { msg = "Update request didn''t include any records."; cerr << msg << endl; diagrec = new ZDEFAULTDIAGFORMAT(226, msg, BIB1_DIAG_OID); rs = new ZESUPDATERESPONSE("", TaskPackage, action, c_dbname, ES_UPDATE_FAILURE_UPDATESTATUS, diagrec); delete diagrec; return rs; } // Process the records. BERBROWSER rec(ber); BERBROWSER sub(ber); INT4 nupdated; STRING Data; STRING Key; sigset_t original_signal_set; sigset_t signal_set; sigemptyset(&signal_set); sigaddset(&signal_set, SIGHUP); sigaddset(&signal_set, SIGINT); sigaddset(&signal_set, SIGTERM); nupdated = 0; diagrec = (ZDEFAULTDIAGFORMAT*)NULL; for (INT4 i = 0; i < count; i++) { if ( !ber.GetSubdirectoryX(i, &rec) ) continue; Key = ""; Data = ""; if ( rec.GetSubdirectory(ES_UPDATE_RECORDID_TAG, &sub) ) { if ( !sub.HasTag(ES_UPDATE_STRING_RECORDID_TAG) || !sub.GetChar(ES_UPDATE_STRING_RECORDID_TAG, &Key) ) { msg = "Update request included an unusable record key."; cerr << msg << endl; diagrec = new ZDEFAULTDIAGFORMAT(226, msg, BIB1_DIAG_OID); break; } } if ( rec.GetSubdirectory(ES_UPDATE_RECORD_TAG, &sub) ) { if ( !sub.GetSubdirectory(exttag, &sub) || !sub.GetOID(oidtag, &OID) ) { msg = "Update request included an unusable record."; cerr << msg << endl; diagrec = new ZDEFAULTDIAGFORMAT(226, msg, BIB1_DIAG_OID); break; } if ((OID.Equals(SUTRS_OID)) || (OID.Equals(HTML_OID)) || (OID.Equals(OLD_HTML_OID)) || (OID.Equals(CNIDR_HTML_OID)) || (OID.Equals(SGML_OID)) || (OID.Equals(CNIDR_SGML_OID)) || (OID.Equals(XML_OID))) { // // SUTRS isnt encoded very // consistently across servers, // unfortunately. I have to test // for every case here. // if ( sub.HasTag(0) ) { if ( !sub.GetSubdirectory(0, &sub) ) { msg = "Update request included an unusable SUTRS record."; cerr << msg << endl; diagrec = new ZDEFAULTDIAGFORMAT(226, msg, BIB1_DIAG_OID); break; } ASN1TAGU IntTag(ASN1_GENERALSTRING); sub.GetChar(IntTag, &Data); } else if ( sub.HasTag(1) ) { sub.GetChar(1, &Data); } else { msg = "Update request included an unusable SUTRS record."; cerr << msg << endl; diagrec = new ZDEFAULTDIAGFORMAT(226, msg, BIB1_DIAG_OID); break; } } else { msg = "Update request included an unsupported record type."; cerr << msg << endl; diagrec = new ZDEFAULTDIAGFORMAT(226, msg, BIB1_DIAG_OID); break; } } if (action == ES_UPDATE_RECORD_INSERT_ACTION) { sigprocmask(SIG_BLOCK, &signal_set, &original_signal_set); if (Database->Add_Record(Key, Data) != GDT_TRUE) { sigprocmask(SIG_SETMASK, &original_signal_set, (sigset_t *)NULL); Database->GetLastErrorString(&msg); diagrec = new ZDEFAULTDIAGFORMAT(224, msg, BIB1_DIAG_OID); break; } sigprocmask(SIG_SETMASK, &original_signal_set, (sigset_t *)NULL); } else if (action == ES_UPDATE_RECORD_DELETE_ACTION) { if (Key == "") { msg = "Update request didn''t include key to delete."; cerr << msg << endl; diagrec = new ZDEFAULTDIAGFORMAT(224, msg, BIB1_DIAG_OID); break; } sigprocmask(SIG_BLOCK, &signal_set, &original_signal_set); if (Database->Delete_Record(Key) != GDT_TRUE) { sigprocmask(SIG_SETMASK, &original_signal_set, (sigset_t *)NULL); Database->GetLastErrorString(&msg); diagrec = new ZDEFAULTDIAGFORMAT(224, msg, BIB1_DIAG_OID); break; } sigprocmask(SIG_SETMASK, &original_signal_set, (sigset_t *)NULL); } else { msg = "Update request included an unsupported action."; cerr << msg << endl; diagrec = new ZDEFAULTDIAGFORMAT(226, msg, BIB1_DIAG_OID); break; } nupdated++; } INT4 update_status; update_status = (nupdated == count) ? ES_UPDATE_SUCCESS_UPDATESTATUS : nupdated ? ES_UPDATE_PARTIAL_UPDATESTATUS : ES_UPDATE_FAILURE_UPDATESTATUS; rs = new ZESUPDATERESPONSE("", TaskPackage, action, c_dbname, update_status, diagrec); if ( diagrec ) delete diagrec; return rs; } KWAQS_STRING::KWAQS_STRING() : STRING() { c_debuglevel = 0; c_errorcode = 0; c_addinfo = ""; } GDT_BOOLEAN KWAQS_STRING::OpConvert(BERBROWSER *ber) { BERBROWSER sub = *ber; CHR COperator[256],Query[2048]; INT operator_value; ASN1TAG Tag; ber->GetTag(&Tag); if(Tag.Number==RPNRPNOP_TAG) { // it is RPN cout << "Found RPN query OK " << endl; BERBROWSER rpn1 = sub; BERBROWSER rpn2 = sub; BERBROWSER Operator = sub; sub.GetSubdirectoryX(0, &rpn1); sub.GetSubdirectoryX(1, &rpn2); sub.GetSubdirectoryX(2, &Operator); if(Operator.HasTag(AND_TAG)) { if(c_debuglevel >= 5) cerr << "Operator=AND" << endl; operator_value=0; } else if(Operator.HasTag(OR_TAG)) { if(c_debuglevel >= 5) cerr << "Operator=OR" << endl; operator_value=1; } else if(Operator.HasTag(ANDNOT_TAG)) { if(c_debuglevel >= 5) cerr << "Operator=ANDNOT" << endl; operator_value=2; } else if(Operator.HasTag(PROX_TAG)) { if(c_debuglevel >= 5) cerr << "Operator=PROX" << endl; // We dont support this yet. c_errorcode = 129; c_addinfo = "Proximity not supported"; return GDT_FALSE; } else { if(c_debuglevel >= 1) cerr << "Unsupported operator in Boolean query" << endl; c_errorcode = 110; c_addinfo = "Unknown OPERATOR"; return GDT_FALSE; } KWAQS_STRING Term1,Term2; switch(operator_value){ case 0: // and cout << "Got AND " << endl; strcpy(COperator,"AND"); break; case 1: // OR cout << "Got OR "<< endl; strcpy(COperator,"OR"); break; case 2: // and-not cout << "Got ANDNOT "<< endl; strcpy(COperator,"ANDNOT"); break; } Term1.OpConvert(&rpn1); Term2.OpConvert(&rpn2); CHR *t1,*t2; t1=Term1.NewCString(); t2=Term2.NewCString(); sprintf(Query,"%s(%s,%s)",COperator,t1,t2); delete [] t1; delete [] t2; cout << Query << endl; Set((UCHR *)Query,strlen(Query)); return(GDT_TRUE); } else { if(!sub.HasTag(ATTRIBUTESPLUSTERM_TAG)) { // We only support attrplusterm c_errorcode = 3; c_addinfo = "DB only supports attributes plus term"; return GDT_FALSE; } sub.GetSubdirectory(ATTRIBUTESPLUSTERM_TAG, &sub); if(!sub.HasTag(TERMGENERAL_TAG) && !sub.HasTag(TERMCHARSTRING_TAG) && !sub.HasTag(TERMNULL_TAG)) { // We only support attrplusterm c_errorcode = 229; c_addinfo = "Only general term and null term types allowed"; return GDT_FALSE; } STRING query_string; if (sub.HasTag(TERMGENERAL_TAG)) { sub.GetChar(TERMGENERAL_TAG, &query_string); cout << "Query: "<= 9) ber->HexDir(); if(!ber->HasTag(QUERY_TAG)) { cerr << "PROTOCOL ERROR: QUERY_TAG not present" << endl; c_errorcode = 108; c_addinfo = "No query present in search request PDU!"; return GDT_FALSE; } BERBROWSER sub = *ber; ber->GetSubdirectory(QUERY_TAG, &sub); // What type of query? INT query_type = -1; if(sub.HasTag(TYPE0_TAG)) query_type = 0; if(sub.HasTag(TYPE1_TAG)) query_type = 1; if(sub.HasTag(TYPE2_TAG)) query_type = 2; if(sub.HasTag(TYPE100_TAG)) query_type = 100; if(sub.HasTag(TYPE101_TAG)) query_type = 101; if(sub.HasTag(TYPE102_TAG)) query_type = 102; if(c_debuglevel >= 5) cerr << "Query type is " << query_type << endl; if(query_type == 0) { STRING query_string; sub.GetSubdirectory(TYPE0_TAG, &sub); ASN1TAGU tag(ASN1_GENERALSTRING); sub.GetChar(tag, &query_string); if(c_debuglevel >= 5) { cerr << "Type 0 query string is \""; cerr << query_string << "\"" << endl; } UCHR *t; t = (UCHR *)query_string.NewCString(); Set(t, strlen((CHR *)t)); delete [] t; return GDT_FALSE; } else if ((query_type == 1) || (query_type == 101)) { sub.GetSubdirectory(TYPE1_TAG, &sub); // Get the query-global attribute set id STRING attr_set; sub.GetOID(ATTRIBUTESETID_TAG, &attr_set); if(c_debuglevel >= 5) cerr << "Attribute Set ID = " << attr_set << endl; // this should be a different function for recursion // Argh! Ive been Kevin-d sub.GetSubdirectoryX(1, &sub); return OpConvert(&sub); } else { // We dont support the requested query type c_errorcode = 107; return GDT_FALSE; } } GDT_BOOLEAN KWAQS_STRING::Error() { if(c_errorcode == 0) return GDT_FALSE; return GDT_TRUE; } void KWAQS_STRING::GetErrorInfo(INT *ErrorCode, STRING *AddInfo) { if(c_errorcode == 0) return; *AddInfo = c_addinfo; *ErrorCode = c_errorcode; } // Log file entry format (all on single line): // // host pid groupid/userid [datetime gmt_offset] method extra_data // protocol/version status bytes_sent // void ZSERVER::Log(const CHR *Method, const CHR *Extra, INT Status, INT4 BytesSent) { INT fd; FILE *fp; CHR *lbuf; STRING temp; pid_t pid; CHR TempString[128]; pid = getpid(); if((fp = fopen(c_accesslog, "a")) == NULL) { perror(c_accesslog); return; } fd = fileno(fp); // If another process has the file locked already, block // here until the file is unlocked. struct flock lock; lock.l_type = F_WRLCK; lock.l_start = 0; lock.l_whence = SEEK_CUR; lock.l_len = 0; if(fcntl(fd, F_SETLKW, &lock) == -1) { perror(c_accesslog); fclose(fp); return; } // Hostname temp = c_client_hostname; // pid sprintf(TempString, " %d ", pid); temp.Cat(TempString); // Z39.50 GroupId/UserId if(c_groupid.Equals("")) temp.Cat("-/"); else { temp.Cat(c_groupid); temp.Cat("/"); } if(c_userid.Equals("")) temp.Cat("-"); else temp.Cat(c_userid); temp.Cat(" ["); // Date and time struct tm *Time; IS_TIME_T time_clock; time(&time_clock); Time = localtime(&time_clock); strftime(TempString, sizeof(TempString), "%d/%b/%Y:%H:%M:%S] ", Time); temp.Cat(TempString); // Method temp.Cat(Method); temp.Cat(" "); // Extra stuff temp.Cat(Extra); // Protocol/Version // Very peculiar to Z39.50 ProtocolVersion bitstring temp.Cat(" Z39.50/"); sprintf(TempString, "%d ", c_protocol_version.GetLength()); temp.Cat(TempString); // Status and BytesSent. // Status doesnt really apply to us. Make one up for now. // -- say 1 means good, 0 bad sprintf(TempString, "%d %d ", Status, BytesSent); temp.Cat(TempString); temp.Cat("\n"); lbuf = temp.NewCString(); write(fd, lbuf, strlen(lbuf)); delete [] lbuf; lock.l_type = F_UNLCK; if(fcntl(fd, F_SETLK, &lock) == -1) { perror(c_accesslog); fclose(fp); return; } fclose(fp); } void ZSERVER::AddToMappingTable(const STRING &FileToAdd, const STRING &DatabaseName) { STRLIST FromPos, Position, ResultList, ToPos, TempList; STRINGINDEX count, i; STRING Entry; REGISTRY *FromFile; // Build entry into mapping table for the database Position.Clear(); Position.AddEntry(DatabaseName); FromPos.Clear(); FromFile = new REGISTRY("blah"); FromFile->ProfileLoadFromFile(FileToAdd, FromPos); FromPos.AddEntry("Default"); FromFile->GetData(FromPos, &ResultList); #ifdef DEBUG ResultList.Dump(); #endif // Now have a list of all mapping directives ("bib1/4") // Step through each one, build a position list, get the value // of that directive and SetData on the internal mapping table // for the current database. count = ResultList.GetTotalEntries(); if(c_debuglevel >= 5) { cerr << "Adding " << count << " fields from "; cerr << FileToAdd; cerr << " to internal mapping tables..." << endl; } ToPos.AddEntry(DatabaseName); for(i=1;i <= count;i++) { ResultList.GetEntry(i, &Entry); // cout << i << " - " << Entry << endl; FromPos.SetEntry(2, Entry); FromFile->GetData(FromPos, &TempList); ToPos.SetEntry(2, Entry); c_mapping_table->SetData(ToPos, TempList); } delete FromFile; } ZPRESENTRESPONSE * ZSERVER::PresentResponseWithDiagnostic( const STRING & DiagSetOID, const INT4 ErrorCode, const STRING & AddInfo) { ZPRESENTRESPONSE *rs; ZRECORDLIST *RecordList; RecordList = new ZRECORDLIST(NONSURROGATEDIAGNOSTIC_TAG); ZDEFAULTDIAGFORMAT *diagrec; diagrec = new ZDEFAULTDIAGFORMAT(ErrorCode, AddInfo, DiagSetOID); RecordList->AddRecord(diagrec); rs = new ZPRESENTRESPONSE(c_refid, 1, 1, 5, RecordList); delete diagrec; delete RecordList; return rs; } void ZSERVER::FreeSapi() { delete TmpSapi; TmpSapi=(SAPI *)NULL; } SAPI_DB * ZSERVER::MetaSearch(STRING DBN) { char *name; INT LocationCount = 0,Count,i; STRING LocationString; LocationString = "Location="; STRING tempName,a; STRLIST Dbs; char Host[256],Port[256],dbn[256]; char buf[1024],*tempFile; INT iPort; /* * get formatted list in locationstring * It will be a "+" delimited list with entries like * * host:port/dbname+host:port/dbname * * for remote dbs and no punctuation for local ones */ Dbs.Split("+",DBN); Count=Dbs.GetTotalEntries(); INT DBCount=0; for (i=1; i<=Count; i++) { Dbs.GetEntry(i,&a); if(a.Search(':')) { // remote database, add to the list of METABASE dbs if (DBCount>0) LocationString.Cat(","); LocationString.Cat(a); DBCount++; } else { // a local database strcpy(Host,"localhost"); iPort = c_port+1; a.GetCString(dbn,256); sprintf(buf,"%s:%d/%s",Host,iPort,dbn); if (DBCount>0) LocationString.Cat(","); LocationString.Cat(buf); DBCount++; } } STRING SapiIniFile; SapiIniFile="[Default]\nDBList=METABASE\n\n[METABASE]\nType=METABASE\n"; SapiIniFile.Cat(LocationString); CHR MyTmp[]="/tmp/newsapiXXXXXX"; mkstemp(MyTmp); tempFile = MyTmp; //tempFile = tempnam("/tmp", "newsapi"); SapiIniFile.WriteFile(tempFile); INT4 HitCount = 0; TmpSapi = new SAPI(tempFile); SAPI_DB *db; cout << "Getting pointer to database..." << endl; db = TmpSapi->GetDatabasePtr("Metabase"); if(db) { cout << "Initializing connections to database(s)..." << endl; db->SetDebugLevel(9); if(!db->Initialize(c_password)) {// send password cout << "Failed to initialize" << endl; return NULL; } } unlink(tempFile); return(db); }