7b19c0fe5799fb293c487f9505bbd090d0a0172b
[yaz-moved-to-github.git] / util / yaz-illclient.c
1 /*
2  * Copyright (C) 1995-2006, Index Data ApS
3  * See the file LICENSE for details.
4  *
5  * $Id: yaz-illclient.c,v 1.4 2007-04-18 13:21:46 heikki Exp $
6  */
7
8 /* WARNING - This is work in progress - not at all ready */
9
10 /** \file yaz-illclient.c
11  *  \brief client for ILL requests (ISO 10161-1)
12  *
13  *  This is a test client for handling ISO 10161-1 ILL requests.
14  *  Those are not directly Z39.50, but the protocol is quite similar
15  *  and yaz already provides the APDUS for it.
16  *
17  *  This is not an interactive client like yaz-client, but driven by command-
18  *  line arguments. Its output is a return code, and possibly some text on 
19  *  stdout.
20  *
21  *  Exit codes  (note, the program exits as soon as it finds a good reason)
22  *     0   ok
23  *     1   errors in arguments
24  *     2   Internal errors in creating objects (comstack, odr...)
25  *         mostly programming errors.
26  *     3   could not connect
27  *     4   could not send request
28  *     5   No reponse received
29  *     6   Error decoding response packet
30  *     7   Server returned an error (see log or stdout)
31  *
32  *
33  *
34  *
35  */
36
37 #include <stdlib.h>
38 #include <stdio.h>
39
40 #if HAVE_UNISTD_H
41 #include <unistd.h>
42 #endif
43 #if HAVE_SYS_STAT_H
44 #include <sys/stat.h>
45 #endif
46 #if HAVE_SYS_TIME_H
47 #include <sys/time.h>
48 #endif
49
50
51 #include <yaz/yaz-util.h>
52 #include <yaz/proto.h>
53 #include <yaz/comstack.h>
54 #include <yaz/tcpip.h>
55 #include <yaz/unix.h>
56 #include <yaz/odr.h>
57 #include <yaz/log.h>
58 #include <yaz/ill.h>
59 #include <yaz/oclc-ill-req-ext.h>
60
61
62 /* A structure for storing all the arguments */
63 struct prog_args {
64     char *host;
65     char *auth_userid;
66     char *auth_passwd;
67 } ;
68
69
70 /* Call-back to be called for every field in the ill-request */
71 /* It can set values to any field, perhaps from the prog_args */
72 const char *get_ill_element(void *clientData, const char *element) {
73     struct prog_args *args = clientData;
74     char *ret=0;
75     if (!strcmp(element,"foo")) {
76         ret=args->host;
77     } else if (!strcmp(element,"ill,protocol-version-num")) {
78         ret="2";
79  /*
80     } else if (!strcmp(element,"ill,transaction-id,initial-requester-id,person-or-institution-symbol,institution")) {
81         ret="IndexData";
82  */
83     } 
84     yaz_log(YLOG_DEBUG,"get_ill_element: '%s' -> '%s' ", element, ret );
85     return ret;
86 }
87
88
89 /* * * * * * * * * * * * * * * * * */
90 /** \brief  Parse program arguments */
91 void parseargs( int argc, char * argv[],  struct prog_args *args) {
92     int ret;
93     char *arg;
94     char *prog=*argv;
95     char *version="$Id: yaz-illclient.c,v 1.4 2007-04-18 13:21:46 heikki Exp $"; /* from cvs */
96
97     /* default values */
98     args->host = 0; /* not known (yet) */
99 #if 0    
100     args->auth_userid = "100-228-301" ; /* FIXME - get from cmd line */
101     args->auth_passwd = "dxg5magxc" ;   /* FIXME - get from cmd line */
102 #else
103     args->auth_userid = "100070049" ; /* FIXME - get from cmd line */
104     args->auth_passwd = "cowgirl" ;   /* FIXME - get from cmd line */
105 #endif
106
107
108     while ((ret = options("k:c:q:a:b:m:v:p:u:t:Vxd:", argv, argc, &arg)) != -2)
109     {
110         switch (ret)
111         {
112         case 0:
113             if (!args->host)
114             {
115                 args->host = xstrdup (arg);
116             }
117             else
118             {
119                 fprintf(stderr, "%s: Specify most one server address\n",
120                         prog);
121                 exit(1);
122             }
123             break;
124         case 'v':
125             yaz_log_init(yaz_log_mask_str(arg), "", 0);
126             break;
127         case 'V':
128             printf("%s %s",prog, version );
129             break;
130         default:
131             fprintf (stderr, "Usage: %s "
132                      " [-v loglevel...]"
133                      " [-V]"
134                      " <server-addr>\n",
135                      prog);
136             exit (1);
137         }
138     }
139 } /* parseargs */
140
141 /* * * * * * * * * * * */
142 /** \brief  Validate the arguments make sense */
143 void validateargs( struct prog_args *args) {
144     if (!args->host) {
145         fprintf(stderr, "Specify a connection address, "
146                         "as in 'bagel.indexdata.dk:210' \n");
147         exit(1);
148     }
149 } /* validateargs */
150
151
152 /* * * * * * * * * * * * * * * */
153 /** \brief  Connect to the target */
154 COMSTACK connect_to( char *hostaddr ){
155     COMSTACK stack;
156     void *server_address_ip;
157     int status;
158
159     yaz_log(YLOG_DEBUG,"Connecting to '%s'", hostaddr);
160     stack = cs_create_host(hostaddr, 1, &server_address_ip );
161     if (!stack) {
162         yaz_log(YLOG_FATAL,"Error in creating the comstack '%s' ",
163                  hostaddr );
164         exit(2);
165     }
166     
167     yaz_log(YLOG_DEBUG,"Created stack ok ");
168
169     status = cs_connect(stack, server_address_ip);
170     if (status != 0) {
171         yaz_log(YLOG_FATAL|YLOG_ERRNO,"Can not connect '%s' ",
172                  hostaddr );
173         exit(3);
174     }
175     yaz_log(YLOG_DEBUG,"Connected OK to '%s'", hostaddr);
176     return stack;
177 }
178
179
180 /* * * * * * * * * * * * * * * */
181 /* Makes a Z39.50-like prompt package with username and password */
182 Z_PromptObject1 *makeprompt(struct prog_args *args, ODR odr) {
183     Z_PromptObject1 *p = odr_malloc(odr, sizeof(*p) );
184     Z_ResponseUnit1 *ru = odr_malloc(odr, sizeof(*ru) );
185     
186     p->which=Z_PromptObject1_response;
187     p->u.response = odr_malloc(odr, sizeof(*(p->u.response)) );
188     p->u.response->num=2;
189     p->u.response->elements=odr_malloc(odr, 
190              p->u.response->num*sizeof(*(p->u.response->elements)) );
191     /* user id, aka "oclc authorization number" */
192     p->u.response->elements[0] = ru;
193     ru->promptId = odr_malloc(odr, sizeof(*(ru->promptId) ));
194     ru->promptId->which = Z_PromptId_enumeratedPrompt;
195     ru->promptId->u.enumeratedPrompt =  
196         odr_malloc(odr, sizeof(*(ru->promptId->u.enumeratedPrompt) ));
197     ru->promptId->u.enumeratedPrompt->type = 
198          odr_intdup(odr,Z_PromptIdEnumeratedPrompt_userId);
199     ru->promptId->u.enumeratedPrompt->suggestedString = 0 ;
200     ru->which = Z_ResponseUnit1_string ;
201     ru->u.string = odr_strdup(odr, args->auth_userid);
202     /* password */
203     ru = odr_malloc(odr, sizeof(*ru) );
204     p->u.response->elements[1] = ru;
205     ru->promptId = odr_malloc(odr, sizeof(*(ru->promptId) ));
206     ru->promptId->which = Z_PromptId_enumeratedPrompt;
207     ru->promptId->u.enumeratedPrompt =  
208         odr_malloc(odr, sizeof(*(ru->promptId->u.enumeratedPrompt) ));
209     ru->promptId->u.enumeratedPrompt->type = 
210          odr_intdup(odr,Z_PromptIdEnumeratedPrompt_password);
211     ru->promptId->u.enumeratedPrompt->suggestedString = 0 ;
212     ru->which = Z_ResponseUnit1_string ;
213     ru->u.string = odr_strdup(odr, args->auth_passwd);
214     return p;
215 } /* makeprompt */
216
217 ILL_Extension *makepromptextension(struct prog_args *args, ODR odr) {
218     ODR odr_ext = odr_createmem(ODR_ENCODE);
219     ODR odr_prt = odr_createmem(ODR_PRINT);
220     ILL_Extension *e = odr_malloc(odr, sizeof(*e));
221     Z_PromptObject1 *p = makeprompt(args,odr_ext);
222     char * buf;
223     int siz;
224     Z_External *ext = odr_malloc(odr, sizeof(*ext));
225     ext->direct_reference = odr_getoidbystr(odr,"1.2.840.10003.8.1");
226     ext->indirect_reference=0;
227     ext->descriptor=0;
228     ext->which=Z_External_single;
229     if ( ! z_PromptObject1(odr_ext, &p, 0,0 ) ) {
230         yaz_log(YLOG_FATAL,"Encoding of z_PromptObject1 failed ");
231         exit (6);
232     }
233     
234     printf ("Prompt: \n"); /*!*/
235     z_PromptObject1(odr_prt, &p, 0,0 ); /*!*/
236
237     buf= odr_getbuf(odr_ext,&siz,0);
238     ext->u.single_ASN1_type=odr_malloc(odr,sizeof(*ext->u.single_ASN1_type));
239     ext->u.single_ASN1_type->buf= odr_malloc(odr, siz);
240     memcpy(ext->u.single_ASN1_type->buf,buf, siz );
241     ext->u.single_ASN1_type->len = ext->u.single_ASN1_type->size = siz;
242     odr_reset(odr_ext);
243     odr_reset(odr_prt); /*!*/
244
245     e->identifier = odr_intdup(odr,1);
246     e->critical = odr_intdup(odr,0);
247     e->item=odr_malloc(odr,sizeof(*e->item));
248     if ( ! z_External(odr_ext, &ext,0,0) ) {
249         yaz_log(YLOG_FATAL,"Encoding of z_External failed ");
250         exit (6);
251     }
252     printf("External: \n");
253     z_External(odr_prt, &ext,0,0);  /*!*/
254     buf= odr_getbuf(odr_ext,&siz,0); 
255     e->item->buf= odr_malloc(odr, siz);
256     memcpy(e->item->buf,buf, siz );
257     e->item->len = e->item->size = siz;
258
259     odr_destroy(odr_prt);
260     odr_destroy(odr_ext);
261     return e;
262 } /* makepromptextension */
263
264 ILL_Extension *makeoclcextension(struct prog_args *args, ODR odr) {
265     /* The oclc extension is required, but only contains optional */
266     /* fields. Here we just null them all out */
267     ODR odr_ext = odr_createmem(ODR_ENCODE);
268     ODR odr_prt = odr_createmem(ODR_PRINT);
269     ILL_Extension *e = odr_malloc(odr, sizeof(*e));
270     ILL_OCLCILLRequestExtension *oc = odr_malloc(odr_ext, sizeof(*oc));
271     char * buf;
272     int siz;
273     Z_External *ext = odr_malloc(odr, sizeof(*ext));
274     oc->clientDepartment = 0;
275     oc->paymentMethod = 0;
276     oc->uniformTitle = 0;
277     oc->dissertation = 0;
278     oc->issueNumber = 0;
279     oc->volume = 0;
280     oc->affiliations = 0;
281     oc->source = 0;
282     ext->direct_reference = odr_getoidbystr(odr,"1.0.10161.13.2");
283     ext->indirect_reference=0;
284     ext->descriptor=0;
285     ext->which=Z_External_single;
286     if ( ! ill_OCLCILLRequestExtension(odr_ext, &oc, 0,0 ) ) {
287         yaz_log(YLOG_FATAL,"Encoding of ill_OCLCILLRequestExtension failed ");
288         exit (6);
289     }
290     
291     printf ("OCLC: \n"); /*!*/
292     ill_OCLCILLRequestExtension(odr_prt, &oc, 0,0 ); /*!*/
293
294     buf= odr_getbuf(odr_ext,&siz,0);
295     ext->u.single_ASN1_type=odr_malloc(odr,sizeof(*ext->u.single_ASN1_type));
296     ext->u.single_ASN1_type->buf= odr_malloc(odr, siz);
297     memcpy(ext->u.single_ASN1_type->buf,buf, siz );
298     ext->u.single_ASN1_type->len = ext->u.single_ASN1_type->size = siz;
299     odr_reset(odr_ext);
300     odr_reset(odr_prt); /*!*/
301
302     e->identifier = odr_intdup(odr,1);
303     e->critical = odr_intdup(odr,0);
304     e->item=odr_malloc(odr,sizeof(*e->item));
305     if ( ! z_External(odr_ext, &ext,0,0) ) {
306         yaz_log(YLOG_FATAL,"Encoding of z_External failed ");
307         exit (6);
308     }
309     printf("External: \n");
310     z_External(odr_prt, &ext,0,0);  /*!*/
311     buf= odr_getbuf(odr_ext,&siz,0); 
312     e->item->buf= odr_malloc(odr, siz);
313     memcpy(e->item->buf,buf, siz );
314     e->item->len = e->item->size = siz;
315
316     odr_destroy(odr_prt);
317     odr_destroy(odr_ext);
318     return e;
319
320 } /* makeoclcextension */
321
322 ILL_APDU *createrequest( struct prog_args *args, ODR odr) {
323     struct ill_get_ctl ctl;
324     ILL_APDU *apdu;
325     ILL_Request *req;
326
327     ctl.odr = odr;
328     ctl.clientData = & args;
329     ctl.f = get_ill_element;
330     apdu = odr_malloc( odr, sizeof(*apdu) );
331     apdu->which=ILL_APDU_ILL_Request;
332     req = ill_get_ILLRequest(&ctl, "ill", 0);
333     apdu->u.illRequest=req;
334     req->num_iLL_request_extensions=2;
335     req->iLL_request_extensions=
336         odr_malloc(odr, req->num_iLL_request_extensions*sizeof(*req->iLL_request_extensions));
337     req->iLL_request_extensions[0]=makepromptextension(args,odr);
338     req->iLL_request_extensions[1]=makeoclcextension(args,odr);
339     if (!req) {
340         yaz_log(YLOG_FATAL,"Could not create ill request");
341         exit(2);
342     }
343     return apdu;
344 } /* createrequest */
345
346
347 /* * * * * * * * * * * * * * * */
348 /** \brief Send the request */
349 void sendrequest(ILL_APDU *apdu, ODR odr, COMSTACK stack ) {
350     char *buf_out;
351     int len_out;
352     int res;
353     if (!ill_APDU  (odr, &apdu, 0, 0)) { 
354         yaz_log(YLOG_FATAL,"ill_Apdu failed");
355         exit(2);
356     }
357     buf_out = odr_getbuf(odr, &len_out, 0);
358     if (0) {
359         yaz_log(YLOG_DEBUG,"Request PDU Dump");
360         odr_dumpBER(yaz_log_file(), buf_out, len_out);
361     }
362     if (!buf_out) {
363         yaz_log(YLOG_FATAL,"Encoding failed. Len=%d", len_out);
364         odr_perror(odr, "encoding failed");
365         exit(2);
366     }
367     yaz_log(YLOG_DEBUG,"About to send the request. Len=%d", len_out);
368     res = cs_put(stack, buf_out, len_out);
369     if ( res<0 ) {
370         yaz_log(YLOG_FATAL,"Could not send packet. code %d",res );
371         exit (4);
372     }
373     if (1) {
374         FILE *F = fopen("req.apdu","w");
375         fwrite ( buf_out, 1, len_out, F);
376         fclose(F);
377     }
378     
379 } /* sendrequest */
380
381 /* * * * * * * * * * * * * * * */
382 /** \brief  Get a response */
383 ILL_APDU *getresponse( COMSTACK stack, ODR in_odr ){
384     ILL_APDU *resp;
385     int res;
386     char *buf_in=0;
387     int len_in=0;
388     yaz_log(YLOG_DEBUG,"About to wait for a response");
389     res = cs_get(stack, &buf_in, &len_in);
390     yaz_log(YLOG_DEBUG,"Got a response of %d bytes at %p. res=%d", len_in,buf_in, res);
391     if (res<0) {
392         yaz_log(YLOG_FATAL,"Could not receive packet. code %d",res );
393         yaz_log(YLOG_DEBUG,"%02x %02x %02x %02x %02x %02x %02x %02x ...", 
394                 buf_in[0], buf_in[1], buf_in[2], buf_in[3],
395                 buf_in[4], buf_in[5], buf_in[6], buf_in[7]  );
396         yaz_log(YLOG_DEBUG,"PDU Dump:");
397         odr_dumpBER(yaz_log_file(), buf_in, len_in);
398         exit (5);
399     }
400     odr_setbuf(in_odr, buf_in, res, 0);
401     if (!ill_APDU (in_odr, &resp, 0, 0))
402     {
403         int x;
404         int err = odr_geterrorx(in_odr, &x);
405         char msg[60];
406         const char *element = odr_getelement(in_odr);
407         sprintf(msg, "ODR code %d:%d element=%-20s",
408                 err, x, element ? element : "<unknown>");
409         yaz_log(YLOG_FATAL,"Error decoding incoming packet: %s",msg);
410         yaz_log(YLOG_DEBUG,"%02x %02x %02x %02x %02x %02x %02x %02x ...", 
411                 buf_in[0], buf_in[1], buf_in[2], buf_in[3],
412                 buf_in[4], buf_in[5], buf_in[6], buf_in[7]  );
413         yaz_log(YLOG_DEBUG,"PDU Dump:");
414         odr_dumpBER(yaz_log_file(), buf_in, len_in);
415         yaz_log(YLOG_FATAL,"Error decoding incoming packet: %s",msg);
416         exit(6);
417     }
418     return resp;
419 } /* getresponse */
420
421
422 /** \brief Dump a apdu */
423 void dumpapdu( ILL_APDU *apdu) {
424     ODR print_odr = odr_createmem(ODR_PRINT);
425     ill_APDU (print_odr, &apdu, 0, 0);
426     odr_destroy(print_odr);
427 } /* dumpapdu */
428
429 /** \brief  Check apdu type and extract the status_or_error */
430 ILL_Status_Or_Error_Report *getstaterr( ILL_APDU *resp, ODR in_odr ) {
431     if (resp->which != ILL_APDU_Status_Or_Error_Report ) {
432         const char *element = odr_getelement(in_odr);
433         if (!element) 
434             element="unknown";
435         printf("Server returned wrong packet type: %d\n", resp->which);
436         yaz_log(YLOG_FATAL,"Server returned a (%d) and "
437                  "not a 'Status_Or_Error_Report' (%d) ",
438                  resp->which, ILL_APDU_Status_Or_Error_Report);
439         exit(6);
440     }
441     return resp->u.Status_Or_Error_Report;
442 } /* getstaterr */
443
444 /** \brief  Return a printable string from an ILL_String */
445 char *getillstring( ILL_String *s) {
446     if (s->which == ILL_String_GeneralString ) 
447         return s->u.GeneralString;
448     else if (s->which == ILL_String_EDIFACTString ) 
449         return s->u.EDIFACTString;
450     else {
451         yaz_log(YLOG_FATAL,"Invalid ILL_String ");
452         exit (6);
453     }
454 } /* getillstring */
455
456 /** \brief Check if the status was an error packet */
457 /* The presence of an error_report indicates it was an error */
458 /* Then the problem is to find the right message. We dig around */
459 /* until we find the first message, print that, and exit the program */
460 void checkerr( ILL_Status_Or_Error_Report *staterr ) {
461     yaz_log(YLOG_DEBUG, "err= %p ",staterr->error_report );
462     if (staterr->error_report) {
463         ILL_Error_Report *err= staterr->error_report;
464         if ( err->user_error_report) {
465             ILL_User_Error_Report *uerr= err->user_error_report;
466             switch( uerr->which ) {
467                 case ILL_User_Error_Report_already_forwarded:
468                     printf("Already forwarded: \n");
469                     break;
470                 case ILL_User_Error_Report_intermediary_problem:
471                     printf("Intermediary problem: %d\n", 
472                         *uerr->u.intermediary_problem);
473                     break;
474                 case ILL_User_Error_Report_security_problem:
475                     printf("Security problem: %s\n", 
476                         getillstring(uerr->u.security_problem));
477                     break;
478                 case ILL_User_Error_Report_unable_to_perform:
479                     printf("Unable to perform: %d\n", 
480                           *uerr->u.unable_to_perform);
481                     break;
482                 default:
483                     printf("Unknown problem");
484             }
485             exit(7);
486         }
487         if ( err->provider_error_report) {
488             ILL_Provider_Error_Report *perr= err->provider_error_report;
489             switch( perr->which ) {
490                 case ILL_Provider_Error_Report_general_problem:
491                     printf("General Problem: %d\n", 
492                           *perr->u.general_problem);
493                     break;
494                 case ILL_Provider_Error_Report_transaction_id_problem:
495                     printf("Transaction Id Problem: %d\n", 
496                           *perr->u.general_problem);
497                     break;
498                 case ILL_Provider_Error_Report_state_transition_prohibited:
499                     printf("State Transition prohibited \n");
500                     break;
501             }
502             exit(7);
503         } 
504         /* fallbacks */
505         if ( staterr->note ) 
506             printf("%s", getillstring(staterr->note));
507         else 
508             printf("Unknown error type");
509         exit(7);
510     }
511 } /* checkerr */
512
513
514
515 /* * * * * * * * * * * * * * * */
516
517 /** \brief Main program 
518  *
519  * Parse arguments
520  * Validate arguments
521  * Establish connection
522  * Build a request
523  * Send a request
524  * Get a reply
525  * Parse reply
526  * Produce output
527  */
528
529 int main (int argc, char * argv[]) {
530     struct prog_args args;
531     COMSTACK stack;
532     ODR out_odr = odr_createmem(ODR_ENCODE);
533     ODR in_odr = odr_createmem(ODR_DECODE);
534     ILL_APDU *apdu;
535     ILL_APDU *resp;
536     ILL_Status_Or_Error_Report *staterr;
537
538     parseargs( argc, argv,  &args);
539     validateargs(&args);
540     stack = connect_to(args.host);
541     apdu = createrequest(&args, out_odr);
542     if (1) 
543         dumpapdu(apdu);
544     sendrequest(apdu, out_odr, stack ); 
545     resp = getresponse(stack, in_odr );
546     if (1) 
547         dumpapdu(resp);
548     staterr=getstaterr(resp, in_odr);
549     checkerr(staterr);
550
551
552     printf ("Ok\n"); /* while debugging */
553     exit (0);
554 }
555
556 /*
557  * Local variables:
558  * c-basic-offset: 4
559  * indent-tabs-mode: nil
560  * End:
561  * vim: shiftwidth=4 tabstop=8 expandtab
562  */
563