/* This file is part of the YAZ toolkit. * Copyright (C) Index Data * See the file LICENSE for details. */ /* WARNING - This is work in progress - not at all ready */ /** \file yaz-illclient.c * \brief client for ILL requests (ISO 10161-1) * * This is a test client for handling ISO 10161-1 ILL requests. * Those are not directly Z39.50, but the protocol is quite similar * and yaz already provides the APDUS for it. * * This is not an interactive client like yaz-client, but driven by command- * line arguments. Its output is a return code, and possibly some text on * stdout. * * Exit codes (note, the program exits as soon as it finds a good reason) * 0 ok * 1 errors in arguments * 2 Internal errors in creating objects (comstack, odr...) * mostly programming errors. * 3 could not connect * 4 could not send request * 5 No reponse received * 6 Error decoding response packet * 7 Server returned an error (see log or stdout) * * * * */ #if HAVE_CONFIG_H #include #endif #include #include #if HAVE_UNISTD_H #include #endif #if HAVE_SYS_STAT_H #include #endif #if HAVE_SYS_TIME_H #include #endif #include #include #include #include #include #include #include #include #include /* A structure for holding name-value pairs in a linked list */ struct nameval { char *name; char *val; struct nameval *next; }; /* A structure for storing all the arguments */ struct prog_args { char *host; char *auth_userid; char *auth_passwd; char *oclc_recno; /* record number in oclc-mode */ int oclc_auth; /* 1=use oclc-type auth */ struct nameval* namevals; /* circular list, points to last */ } ; /* Call-back to be called for every field in the ill-request */ /* It can set values to any field, perhaps from the prog_args */ const char *get_ill_element(void *clientData, const char *element) { struct prog_args *args = (struct prog_args *) clientData; struct nameval *nv=args->namevals; char *ret=0; if (!nv) return ""; do { nv=nv->next; /* printf("comparing '%s' with '%s' \n",element, nv->name ); */ if ( strcmp(element, nv->name) == 0 ) ret = nv->val; } while ( ( !ret) && ( nv != args->namevals) ); yaz_log(YLOG_DEBUG,"get_ill_element:'%s'->'%s'", element, ret ); return ret; } /* * * * * * * * * * * * * * * * * */ /** \brief parse a parameter string */ /* string is like 'name=value' */ struct nameval *parse_nameval( char *arg ) { struct nameval *nv = (struct nameval *) xmalloc(sizeof(*nv)); char *p=arg; int len; if (!p || !*p) return 0; /* yeah, leaks a bit of memory. so what */ while ( *p && ( *p != '=' ) ) p++; len = p - arg; if (!len) return 0; nv->name = (char *) xmalloc(len+1); strncpy(nv->name, arg, len); nv->name[len]='\0'; if (*p == '=' ) p++; /* skip the '=' */ else return 0; /* oops, no '=' */ if (!*p) return 0; /* no value */ nv->val=xstrdup(p); nv->next=0; yaz_log(YLOG_DEBUG,"parse_nameval: n='%s' v='%s'", nv->name, nv->val ); return nv; } /** \brief append nv to the list of namevals */ void append_nameval (struct prog_args *args, struct nameval *nv) { if (!nv) return; if (args->namevals) { nv->next=args->namevals->next; /* first */ args->namevals->next=nv; args->namevals=nv; } else { nv->next=nv; args->namevals=nv; } } /* append_nameval */ /** \brief parse a parameter file */ void parse_paramfile(char *arg, struct prog_args *args) { FILE *f=fopen(arg,"r"); #define BUFSIZE 4096 char buf[BUFSIZE]; int len; struct nameval *nv; if (!f) { yaz_log(YLOG_FATAL,"Could not open param file '%s' ", arg); printf("Could not open file '%s' \n",arg); exit(1); } yaz_log(YLOG_DEBUG,"Opened input file '%s' ",arg ); while (fgets(buf,BUFSIZE,f)) { if (buf[0] != '#' ) { len=strlen(buf)-1; if (buf[len] == '\n') buf[len] = '\0' ; nv=parse_nameval(buf); append_nameval(args, nv); } /* not a comment */ } (void) fclose(f); if (0) { nv=args->namevals; printf("nv chain: ================ \n"); printf("(last:) %p: '%s' = '%s' (%p) \n",nv, nv->name, nv->val, nv->next ); do { nv=nv->next; printf("%p: '%s' = '%s' (%p)\n",nv, nv->name, nv->val, nv->next ); } while (nv != args->namevals ); exit(1); } } /* parse_paramfile */ /** \brief Parse program arguments */ void parseargs( int argc, char * argv[], struct prog_args *args) { int ret; char *arg; char *prog=*argv; char version[60]; struct nameval *nv; /* default values */ args->host = 0; /* not known (yet) */ args->namevals=0; /* none set up */ args->oclc_auth=0; args->oclc_recno=0; args->auth_userid = 0; args->auth_passwd = 0; #if 0 /* Example 3 - directly from OCLC, supposed to work on their test server*/ args->auth_userid = "100-310-658" ; /* FIXME - get from cmd line */ args->auth_passwd = "apii2test" ; /* FIXME - get from cmd line */ #endif while ((ret = options("Vov:p:u:D:f:r:l:", argv, argc, &arg)) != -2) { yaz_log(YLOG_DEBUG,"parsing option '%c' '%s'",ret, arg); switch (ret) { case 0: if (!args->host) { args->host = xstrdup (arg); } else { fprintf(stderr, "%s: Specify at most one server address\n", prog); exit(1); } break; case 'v': yaz_log_init(yaz_log_mask_str(arg), "", 0); break; case 'l': yaz_log_init_file(arg); break; case 'V': yaz_version(version, 0); printf("%s %s\n",prog, version); break; case 'D': nv=parse_nameval(arg); append_nameval(args,nv); break; case 'f': parse_paramfile(arg,args); break; case 'o': args->oclc_auth=1; break; case 'u': args->auth_userid=xstrdup(arg); break; case 'p': args->auth_passwd=xstrdup(arg); break; case 'r': args->oclc_recno=xstrdup(arg); break; default: fprintf (stderr, "Usage: %s " " [-f filename]" " [-v loglevel...]" " [-D name=value ]" " [-o -u user -p passwd]" " [-V]" " \n", prog); exit (1); } } } /* parseargs */ /* * * * * * * * * * * */ /** \brief Validate the arguments make sense */ void validateargs( struct prog_args *args) { if (!args->host) { fprintf(stderr, "Specify a connection address, " "as in 'bagel.indexdata.dk:210' \n"); exit(1); } if (args->oclc_auth && ((!args->auth_userid) || (!args->auth_passwd))){ fprintf(stderr, "-o option requires -u and -p \n"); exit(1); } } /* validateargs */ /* * * * * * * * * * * * * * * */ /** \brief Connect to the target */ COMSTACK connect_to( char *hostaddr ){ COMSTACK stack; void *server_address_ip; int status; yaz_log(YLOG_DEBUG,"Connecting to '%s'", hostaddr); stack = cs_create_host(hostaddr, 1, &server_address_ip ); if (!stack) { yaz_log(YLOG_FATAL,"Error in creating the comstack '%s' ", hostaddr ); exit(2); } yaz_log(YLOG_DEBUG,"Created stack ok "); status = cs_connect(stack, server_address_ip); if (status != 0) { yaz_log(YLOG_FATAL|YLOG_ERRNO,"Can not connect '%s' ", hostaddr ); exit(3); } yaz_log(YLOG_DEBUG,"Connected OK to '%s'", hostaddr); return stack; } /* * * * * * * * * * * * * * * */ /* Makes a Z39.50-like prompt package with username and password */ Z_PromptObject1 *makeprompt(struct prog_args *args, ODR odr) { Z_PromptObject1 *p = (Z_PromptObject1 *) odr_malloc(odr, sizeof(*p) ); Z_ResponseUnit1 *ru = (Z_ResponseUnit1 *) odr_malloc(odr, sizeof(*ru) ); p->which=Z_PromptObject1_response; p->u.response = (Z_Response1*) odr_malloc(odr, sizeof(*(p->u.response)) ); p->u.response->num=2; p->u.response->elements= (Z_ResponseUnit1 **) odr_malloc(odr, p->u.response->num*sizeof(*(p->u.response->elements)) ); /* user id, aka "oclc authorization number" */ p->u.response->elements[0] = ru; ru->promptId = (Z_PromptId *) odr_malloc(odr, sizeof(*(ru->promptId) )); ru->promptId->which = Z_PromptId_enumeratedPrompt; ru->promptId->u.enumeratedPrompt = (Z_PromptIdEnumeratedPrompt *) odr_malloc(odr, sizeof(*(ru->promptId->u.enumeratedPrompt) )); ru->promptId->u.enumeratedPrompt->type = odr_intdup(odr,Z_PromptIdEnumeratedPrompt_userId); ru->promptId->u.enumeratedPrompt->suggestedString = 0 ; ru->which = Z_ResponseUnit1_string ; ru->u.string = odr_strdup(odr, args->auth_userid); /* password */ ru = (Z_ResponseUnit1 *) odr_malloc(odr, sizeof(*ru) ); p->u.response->elements[1] = ru; ru->promptId = (Z_PromptId *) odr_malloc(odr, sizeof(*(ru->promptId) )); ru->promptId->which = Z_PromptId_enumeratedPrompt; ru->promptId->u.enumeratedPrompt = (Z_PromptIdEnumeratedPrompt *) odr_malloc(odr, sizeof(*(ru->promptId->u.enumeratedPrompt) )); ru->promptId->u.enumeratedPrompt->type = odr_intdup(odr,Z_PromptIdEnumeratedPrompt_password); ru->promptId->u.enumeratedPrompt->suggestedString = 0 ; ru->which = Z_ResponseUnit1_string ; ru->u.string = odr_strdup(odr, args->auth_passwd); return p; } /* makeprompt */ ILL_Extension *makepromptextension(struct prog_args *args, ODR odr) { ODR odr_ext = odr_createmem(ODR_ENCODE); ODR odr_prt = odr_createmem(ODR_PRINT); ILL_Extension *e = (ILL_Extension *) odr_malloc(odr, sizeof(*e)); Z_PromptObject1 *p = makeprompt(args,odr_ext); char * buf; int siz; Z_External *ext = (Z_External *) odr_malloc(odr, sizeof(*ext)); ext->direct_reference = odr_getoidbystr(odr,"1.2.840.10003.8.1"); ext->indirect_reference=0; ext->descriptor=0; ext->which=Z_External_single; if ( ! z_PromptObject1(odr_ext, &p, 0,0 ) ) { yaz_log(YLOG_FATAL,"Encoding of z_PromptObject1 failed "); exit (6); } printf ("Prompt: \n"); /*!*/ z_PromptObject1(odr_prt, &p, 0,0 ); /*!*/ buf= odr_getbuf(odr_ext,&siz,0); ext->u.single_ASN1_type=(Odr_any *) odr_malloc(odr,sizeof(*ext->u.single_ASN1_type)); ext->u.single_ASN1_type->buf= (char *) odr_malloc(odr, siz); memcpy(ext->u.single_ASN1_type->buf,buf, siz ); ext->u.single_ASN1_type->len = siz; odr_reset(odr_ext); odr_reset(odr_prt); /*!*/ e->identifier = odr_intdup(odr,1); e->critical = odr_booldup(odr,0); e->item = (Odr_any *) odr_malloc(odr,sizeof(*e->item)); if ( ! z_External(odr_ext, &ext,0,0) ) { yaz_log(YLOG_FATAL,"Encoding of z_External failed "); exit (6); } printf("External: \n"); z_External(odr_prt, &ext,0,0); /*!*/ buf= odr_getbuf(odr_ext,&siz,0); e->item->buf= (char *) odr_malloc(odr, siz); memcpy(e->item->buf,buf, siz ); e->item->len = siz; odr_destroy(odr_prt); odr_destroy(odr_ext); return e; } /* makepromptextension */ ILL_Extension *makeoclcextension(struct prog_args *args, ODR odr) { /* The oclc extension is required, but only contains optional */ /* fields. Here we just null them all out */ ODR odr_ext = odr_createmem(ODR_ENCODE); ODR odr_prt = odr_createmem(ODR_PRINT); ILL_Extension *e = (ILL_Extension *) odr_malloc(odr, sizeof(*e)); ILL_OCLCILLRequestExtension *oc = (ILL_OCLCILLRequestExtension *) odr_malloc(odr_ext, sizeof(*oc)); char * buf; int siz; Z_External *ext = (Z_External *) odr_malloc(odr, sizeof(*ext)); oc->clientDepartment = 0; oc->paymentMethod = 0; oc->uniformTitle = 0; oc->dissertation = 0; oc->issueNumber = 0; oc->volume = 0; oc->affiliations = 0; oc->source = 0; ext->direct_reference = odr_getoidbystr(odr,"1.0.10161.13.2"); ext->indirect_reference=0; ext->descriptor=0; ext->which=Z_External_single; if ( ! ill_OCLCILLRequestExtension(odr_ext, &oc, 0,0 ) ) { yaz_log(YLOG_FATAL,"Encoding of ill_OCLCILLRequestExtension failed "); exit (6); } printf ("OCLC: \n"); /*!*/ ill_OCLCILLRequestExtension(odr_prt, &oc, 0,0 ); /*!*/ buf= odr_getbuf(odr_ext,&siz,0); ext->u.single_ASN1_type = (Odr_any*) odr_malloc(odr,sizeof(*ext->u.single_ASN1_type)); ext->u.single_ASN1_type->buf = (char *) odr_malloc(odr, siz); memcpy(ext->u.single_ASN1_type->buf,buf, siz ); ext->u.single_ASN1_type->len = siz; odr_reset(odr_ext); odr_reset(odr_prt); /*!*/ e->identifier = odr_intdup(odr,1); e->critical = odr_booldup(odr,0); e->item = (Odr_any *) odr_malloc(odr,sizeof(*e->item)); if ( ! z_External(odr_ext, &ext,0,0) ) { yaz_log(YLOG_FATAL,"Encoding of z_External failed "); exit (6); } printf("External: \n"); z_External(odr_prt, &ext,0,0); /*!*/ buf= odr_getbuf(odr_ext,&siz,0); e->item->buf= (char *) odr_malloc(odr, siz); memcpy(e->item->buf, buf, siz); e->item->len = siz; odr_destroy(odr_prt); odr_destroy(odr_ext); return e; } /* makeoclcextension */ ILL_APDU *createrequest( struct prog_args *args, ODR odr) { struct ill_get_ctl ctl; ILL_APDU *apdu; ILL_Request *req; ctl.odr = odr; ctl.clientData = args; ctl.f = get_ill_element; apdu = (ILL_APDU *) odr_malloc( odr, sizeof(*apdu) ); apdu->which=ILL_APDU_ILL_Request; req = ill_get_ILLRequest(&ctl, "ill", 0); apdu->u.illRequest=req; if (args->oclc_auth) { req->num_iLL_request_extensions=2; req->iLL_request_extensions= (ILL_Extension **) odr_malloc(odr, req->num_iLL_request_extensions* sizeof(*req->iLL_request_extensions)); req->iLL_request_extensions[0]=makepromptextension(args,odr); req->iLL_request_extensions[1]=makeoclcextension(args,odr); } if (!req) { yaz_log(YLOG_FATAL,"Could not create ill request"); exit(2); } return apdu; } /* createrequest */ /* * * * * * * * * * * * * * * */ /** \brief Send the request */ void sendrequest(ILL_APDU *apdu, ODR odr, COMSTACK stack ) { char *buf_out; int len_out; int res; if (!ill_APDU (odr, &apdu, 0, 0)) { yaz_log(YLOG_FATAL,"ill_Apdu failed"); exit(2); } buf_out = odr_getbuf(odr, &len_out, 0); if (0) { yaz_log(YLOG_DEBUG,"Request PDU Dump"); odr_dumpBER(yaz_log_file(), buf_out, len_out); } if (!buf_out) { yaz_log(YLOG_FATAL,"Encoding failed. Len=%d", len_out); odr_perror(odr, "encoding failed"); exit(2); } yaz_log(YLOG_DEBUG,"About to send the request. Len=%d", len_out); res = cs_put(stack, buf_out, len_out); if ( res<0 ) { yaz_log(YLOG_FATAL,"Could not send packet. code %d",res ); exit (4); } if (1) { FILE *F = fopen("req.apdu","w"); if (!F) { yaz_log(YLOG_FATAL|YLOG_ERRNO, "open req.apdu failed"); } else { if (fwrite ( buf_out, 1, len_out, F) != len_out) yaz_log(YLOG_FATAL|YLOG_ERRNO, "write req.apdu failed"); if (fclose(F)) yaz_log(YLOG_FATAL|YLOG_ERRNO, "write req.apdu failed"); } } } /* sendrequest */ /* * * * * * * * * * * * * * * */ /** \brief Get a response */ ILL_APDU *getresponse( COMSTACK stack, ODR in_odr ){ ILL_APDU *resp; int res; char *buf_in=0; int len_in=0; yaz_log(YLOG_DEBUG,"About to wait for a response"); res = cs_get(stack, &buf_in, &len_in); yaz_log(YLOG_DEBUG,"Got a response of %d bytes at %p. res=%d", len_in,buf_in, res); if (res<0) { yaz_log(YLOG_FATAL,"Could not receive packet. code %d",res ); yaz_log(YLOG_DEBUG,"%02x %02x %02x %02x %02x %02x %02x %02x ...", buf_in[0], buf_in[1], buf_in[2], buf_in[3], buf_in[4], buf_in[5], buf_in[6], buf_in[7] ); yaz_log(YLOG_DEBUG,"PDU Dump:"); odr_dumpBER(yaz_log_file(), buf_in, len_in); exit (5); } odr_setbuf(in_odr, buf_in, res, 0); if (!ill_APDU (in_odr, &resp, 0, 0)) { int x; int err = odr_geterrorx(in_odr, &x); char msg[60]; const char *element = odr_getelement(in_odr); sprintf(msg, "ODR code %d:%d element=%-20s", err, x, element ? element : ""); yaz_log(YLOG_FATAL,"Error decoding incoming packet: %s",msg); yaz_log(YLOG_DEBUG,"%02x %02x %02x %02x %02x %02x %02x %02x ...", buf_in[0], buf_in[1], buf_in[2], buf_in[3], buf_in[4], buf_in[5], buf_in[6], buf_in[7] ); yaz_log(YLOG_DEBUG,"PDU Dump:"); odr_dumpBER(yaz_log_file(), buf_in, len_in); yaz_log(YLOG_FATAL,"Error decoding incoming packet: %s",msg); exit(6); } return resp; } /* getresponse */ /** \brief Dump a apdu */ void dumpapdu( ILL_APDU *apdu) { ODR print_odr = odr_createmem(ODR_PRINT); ill_APDU (print_odr, &apdu, 0, 0); odr_destroy(print_odr); } /* dumpapdu */ /** \brief Check apdu type and extract the status_or_error */ ILL_Status_Or_Error_Report *getstaterr( ILL_APDU *resp, ODR in_odr ) { if (resp->which != ILL_APDU_Status_Or_Error_Report ) { const char *element = odr_getelement(in_odr); if (!element) element="unknown"; printf("Server returned wrong packet type: %d\n", resp->which); yaz_log(YLOG_FATAL,"Server returned a (%d) and " "not a 'Status_Or_Error_Report' (%d) ", resp->which, ILL_APDU_Status_Or_Error_Report); exit(6); } return resp->u.Status_Or_Error_Report; } /* getstaterr */ /** \brief Return a printable string from an ILL_String */ char *getillstring( ILL_String *s) { if (s->which == ILL_String_GeneralString ) return s->u.GeneralString; else if (s->which == ILL_String_EDIFACTString ) return s->u.EDIFACTString; else { yaz_log(YLOG_FATAL,"Invalid ILL_String "); exit (6); } } /* getillstring */ /** \brief Check if the status was an error packet */ /* The presence of an error_report indicates it was an error */ /* Then the problem is to find the right message. We dig around */ /* until we find the first message, print that, and exit the program */ void checkerr( ILL_Status_Or_Error_Report *staterr ) { yaz_log(YLOG_DEBUG, "err= %p ",staterr->error_report ); if (staterr->error_report) { ILL_Error_Report *err= staterr->error_report; if ( err->user_error_report) { ILL_User_Error_Report *uerr= err->user_error_report; switch( uerr->which ) { case ILL_User_Error_Report_already_forwarded: printf("Already forwarded: \n"); break; case ILL_User_Error_Report_intermediary_problem: printf("Intermediary problem: " ODR_INT_PRINTF "\n", *uerr->u.intermediary_problem); break; case ILL_User_Error_Report_security_problem: printf("Security problem: %s\n", getillstring(uerr->u.security_problem)); break; case ILL_User_Error_Report_unable_to_perform: printf("Unable to perform: " ODR_INT_PRINTF "\n", *uerr->u.unable_to_perform); break; default: printf("Unknown problem"); } exit(7); } if ( err->provider_error_report) { ILL_Provider_Error_Report *perr= err->provider_error_report; switch( perr->which ) { case ILL_Provider_Error_Report_general_problem: printf("General Problem: " ODR_INT_PRINTF ":", *perr->u.general_problem); break; case ILL_Provider_Error_Report_transaction_id_problem: printf("Transaction Id Problem: " ODR_INT_PRINTF ":", *perr->u.general_problem); break; case ILL_Provider_Error_Report_state_transition_prohibited: printf("State Transition prohibited:"); break; } /*exit(7);*/ } /* fallbacks */ if ( staterr->note ) printf("%s", getillstring(staterr->note)); else printf("Unknown error type"); printf("\n"); exit(7); } } /* checkerr */ /* * * * * * * * * * * * * * * */ /** \brief Main program * * Parse arguments * Validate arguments * Establish connection * Build a request * Send a request * Get a reply * Parse reply * Produce output */ int main (int argc, char * argv[]) { struct prog_args args; COMSTACK stack; ODR out_odr = odr_createmem(ODR_ENCODE); ODR in_odr = odr_createmem(ODR_DECODE); ILL_APDU *apdu; ILL_APDU *resp; ILL_Status_Or_Error_Report *staterr; parseargs( argc, argv, &args); validateargs(&args); stack = connect_to(args.host); apdu = createrequest(&args, out_odr); if (1) dumpapdu(apdu); sendrequest(apdu, out_odr, stack ); resp = getresponse(stack, in_odr ); if (1) dumpapdu(resp); staterr=getstaterr(resp, in_odr); checkerr(staterr); printf ("Ok\n"); /* while debugging */ exit (0); } /* * Local variables: * c-basic-offset: 4 * c-file-style: "Stroustrup" * indent-tabs-mode: nil * End: * vim: shiftwidth=4 tabstop=8 expandtab */