4dc63cf58f0ac541d11ae716c24f2a5f6232b5e4
[payproc.git] / src / payproc-post.c
1 /* payproc-post.c - Do a posting to the payproc database
2  * Copyright (C) 2015 g10 Code GmbH
3  *
4  * This file is part of Payproc.
5  *
6  * Payproc is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Payproc is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /*
21
22
23  */
24
25
26
27 #include <config.h>
28
29 #include <stdlib.h>
30 #include <string.h>
31 #include <gpg-error.h>
32 #include <assert.h>
33 #include <ctype.h>
34 #include <errno.h>
35 #include <unistd.h>
36 #include <sys/types.h>
37 #include <sys/socket.h>
38 #include <sys/un.h>
39
40 #include "util.h"
41 #include "logging.h"
42 #include "argparse.h"
43 #include "protocol-io.h"
44
45
46 /* Constants to identify the options. */
47 enum opt_values
48   {
49     aNull = 0,
50     oVerbose    = 'v',
51
52     oSeparator  = 500,
53     aPing,
54     aShutdown,
55     aSepa,
56     aSepaPreorder,
57     aGetPreorder,
58     aListPreorder,
59
60     oLive,
61     oTest,
62
63     oLast
64   };
65
66
67 /* The list of commands and options. */
68 static ARGPARSE_OPTS opts[] = {
69   ARGPARSE_group (300, "@Commands:\n "),
70   ARGPARSE_c (aPing, "ping",  "Send a ping"),
71   ARGPARSE_c (aShutdown,  "shutdown",       "Shutdown server"),
72   ARGPARSE_c (aSepa, "sepa",  "Post a SEPA transaction (default)"),
73   ARGPARSE_c (aSepaPreorder, "sepa-preorder",  "Insert a SEPA preorder"),
74   ARGPARSE_c (aGetPreorder,  "get-preorder",   "Read one preorder"),
75   ARGPARSE_c (aListPreorder,  "list-preorder",  "List preorders"),
76
77   ARGPARSE_group (301, "@\nOptions:\n "),
78   ARGPARSE_s_n (oVerbose, "verbose",  "verbose diagnostics"),
79   ARGPARSE_s_n (oLive, "live",  "enable live mode"),
80   ARGPARSE_s_n (oTest, "test",  "enable test mode"),
81
82   ARGPARSE_end ()
83 };
84
85
86 static struct
87 {
88   int verbose;
89   int livemode;
90
91 } opt;
92
93
94 \f
95 /* Local prototypes.  */
96 static gpg_error_t send_request (const char *command,
97                                  keyvalue_t indata, keyvalue_t *outdata);
98 static void post_sepa (const char *refstring, const char *amountstr);
99 static void getpreorder (const char *refstring);
100 static void listpreorder (const char *refstring);
101 static void sepapreorder (const char *amountstr, const char *name,
102                           const char *email, const char *desc);
103
104
105 \f
106 static const char *
107 my_strusage( int level )
108 {
109   const char *p;
110
111   switch (level)
112     {
113     case 11: p = "payproc-post"; break;
114     case 13: p = PACKAGE_VERSION; break;
115     case 19: p = "Please report bugs to bugs@g10code.com.\n"; break;
116     case 1:
117     case 40:
118       p = ("Usage: payproc-post [options] [command] [args] (-h for help)");
119       break;
120     case 41:
121       p = ("Syntax: payproc-post [options] [--sepa] REF AMOUNT\n"
122            "        payproc-post [options] --sepa-preorder AMOUNT\n"
123            "Enter a posting to the payproc journal\n");
124       break;
125     default: p = NULL; break;
126     }
127   return p;
128 }
129
130
131 static void
132 wrong_args (const char *text)
133 {
134   fprintf (stderr, "usage: %s [options] %s\n", strusage (11), text);
135   exit (2);
136 }
137
138
139 int
140 main (int argc, char **argv)
141 {
142   ARGPARSE_ARGS pargs;
143   enum opt_values cmd = 0;
144   int live_or_test = 0;
145
146   /* Set program name etc.  */
147   set_strusage (my_strusage);
148   log_set_prefix ("payproc-proc", JNLIB_LOG_WITH_PREFIX);
149
150   /* Make sure that our subsystems are ready.  */
151   gpgrt_init ();
152
153   /* Parse the command line. */
154   pargs.argc  = &argc;
155   pargs.argv  = &argv;
156   pargs.flags = ARGPARSE_FLAG_KEEP;
157   while (optfile_parse (NULL, NULL, NULL, &pargs, opts))
158     {
159       switch (pargs.r_opt)
160         {
161         case aPing:
162         case aShutdown:
163         case aSepa:
164         case aSepaPreorder:
165         case aGetPreorder:
166         case aListPreorder:
167           if (cmd && cmd != pargs.r_opt)
168             {
169               log_error ("conflicting commands\n");
170               exit (2);
171             }
172           cmd = pargs.r_opt;
173           break;
174
175         case oVerbose: opt.verbose++; break;
176         case oLive: opt.livemode = 1; live_or_test = 1; break;
177         case oTest: opt.livemode = 0; live_or_test = 1; break;
178
179         default: pargs.err = ARGPARSE_PRINT_ERROR; break;
180         }
181     }
182
183   if (log_get_errorcount (0))
184     exit (2);
185
186   if (!cmd)
187     cmd = aSepa; /* Set default.  */
188
189   if (!live_or_test)
190     {
191       log_info ("implicitly using --test\n");
192     }
193
194   if (cmd == aPing)
195     {
196       keyvalue_t dict = NULL;
197
198       send_request ("PING", NULL, &dict);
199       keyvalue_release (dict);
200     }
201   else if (cmd == aShutdown)
202     {
203       keyvalue_t dict = NULL;
204
205       send_request ("SHUTDOWN", NULL, &dict);
206       keyvalue_release (dict);
207     }
208   else if (cmd == aSepa)
209     {
210       if (argc != 2)
211         wrong_args ("--sepa REF AMOUNT[/(RECUR|*)]");
212       ascii_strupr (argv[0]);
213       post_sepa (argv[0], argv[1]);
214     }
215   else if (cmd == aGetPreorder)
216     {
217       if (argc != 1)
218         wrong_args ("--get-preorder REF");
219       ascii_strupr (argv[0]);
220       getpreorder (argv[0]);
221     }
222   else if (cmd == aListPreorder)
223     {
224       if (argc > 1)
225         wrong_args ("--list-preorder [NN]");
226       listpreorder (argc? argv[0] : NULL);
227     }
228   else if (cmd == aSepaPreorder)
229     {
230       if (!argc || argc > 4)
231         wrong_args ("--sepa-preorder AMOUNT[/RECUR] [NAME [EMAIL [DESC]]]");
232       sepapreorder (argv[0],
233                     argc > 1? argv[1] : "",
234                     argc > 2? argv[2] : "",
235                     argc > 3? argv[3] : "");
236     }
237   else
238     usage (1);
239
240
241   return !!log_get_errorcount (0);
242 }
243
244
245 /* Connect to the daemon and return an estream for the connected
246    socket.  On error returns NULL and sets ERRNO.  */
247 static estream_t
248 connect_daemon (const char *name)
249 {
250   int sock;
251   struct sockaddr_un addr_un;
252   struct sockaddr    *addrp;
253   size_t addrlen;
254   estream_t fp;
255
256   if (strlen (name)+1 >= sizeof addr_un.sun_path)
257     {
258       gpg_err_set_errno (EINVAL);
259       return NULL;
260     }
261
262   memset (&addr_un, 0, sizeof addr_un);
263   addr_un.sun_family = AF_LOCAL;
264   strncpy (addr_un.sun_path, name, sizeof (addr_un.sun_path) - 1);
265   addr_un.sun_path[sizeof (addr_un.sun_path) - 1] = 0;
266   addrlen = SUN_LEN (&addr_un);
267   addrp = (struct sockaddr *)&addr_un;
268
269   sock = socket (AF_LOCAL, SOCK_STREAM, 0);
270   if (sock == -1)
271     return NULL;
272
273   if (connect (sock, addrp, addrlen))
274     {
275       int saveerr = errno;
276       close (sock);
277       errno = saveerr;
278       return NULL;
279     }
280
281   fp = es_fdopen (sock, "r+b");
282   if (!fp)
283     {
284       int saveerr = errno;
285       close (sock);
286       gpg_err_set_errno (saveerr);
287       return NULL;
288     }
289
290   return fp;
291 }
292
293
294 /* Send COMMAND and INDATA to the daemon.  On return OUTDATA is updated with the
295    response values.  */
296 static gpg_error_t
297 send_request (const char *command, keyvalue_t indata, keyvalue_t *outdata)
298 {
299   gpg_error_t err;
300   estream_t fp;
301   keyvalue_t kv;
302   const char *s;
303
304   fp = connect_daemon (opt.livemode? PAYPROCD_SOCKET_NAME
305                        /**/        : PAYPROCD_TEST_SOCKET_NAME);
306   if (!fp)
307     {
308       err = gpg_error_from_syserror ();
309       log_error ("Error connecting payprocd: %s\n", gpg_strerror (err));
310       return err;
311     }
312
313   es_fprintf (fp, "%s\n", command);
314   for (kv = indata; kv; kv = kv->next)
315     es_fprintf (fp, "%s: %s\n", kv->name, kv->value);
316
317   es_putc ('\n', fp);
318
319   if (es_ferror (fp) || es_fflush (fp))
320     {
321       err = gpg_error_from_syserror ();
322       log_error ("Error writing to payprocd: %s\n", gpg_strerror (err));
323       exit (1);
324     }
325
326   err = protocol_read_response (fp, outdata);
327   if (err && (s=keyvalue_get (*outdata, "_errdesc")))
328     {
329       log_error ("Command failed: %s %s%s%s\n",
330                  gpg_strerror (err), *s == '('?"":"(", s, *s == '('?"":")");
331       if ((s=keyvalue_get (*outdata, "failure")))
332         log_info("                %s\n", s);
333       if ((s=keyvalue_get (*outdata, "failure-mesg")))
334         log_info("                %s\n", s);
335     }
336   else if (err)
337     log_error ("Error reading from payprocd: %s\n", gpg_strerror (err));
338
339   /* Eat the response for a clean connection shutdown.  */
340   while (es_getc (fp) != EOF)
341     ;
342
343   es_fclose (fp);
344
345   return err;
346 }
347
348
349 static void
350 post_sepa (const char *refstring, const char *amountstr_arg)
351 {
352   gpg_error_t err;
353   keyvalue_t input = NULL;
354   keyvalue_t output = NULL;
355   keyvalue_t kv;
356   char *amountstr;
357   int recur = 0;
358   char *p;
359
360   amountstr = xstrdup (amountstr_arg);
361
362   p = strchr (amountstr, '/');
363   if (p)
364     {
365       *p++ = 0;
366       if (!strcmp (p, "*"))
367         recur = -1;
368       else
369         recur = atoi (p);
370     }
371
372   if (!*amountstr || !convert_amount (amountstr, 2))
373     {
374       log_error ("Syntax error in amount or value is not positive\n");
375       xfree (amountstr);
376       return;
377     }
378
379   switch (recur)
380     {
381     case -1: break;
382     case 0: case 1: case 4: case 12: break;
383     default:
384       log_error ("Syntax error in RECUR suffix - must be 0, 1, 4, 12 or *\n");
385       xfree (amountstr);
386       return;
387     }
388
389   /* Find reference.  */
390   err = keyvalue_put (&input, "Sepa-Ref", refstring);
391   if (!err)
392     err = keyvalue_put (&input, "Amount", amountstr);
393   if (!err)
394     err = keyvalue_put (&input, "Currency", "EUR");
395   if (!err && recur)
396     {
397       if (recur == -1)
398         err = keyvalue_put (&input, "Recur", "*");
399       else
400         err = keyvalue_putf (&input, "Recur", "%d", recur);
401     }
402
403   if (err)
404     log_fatal ("keyvalue_put failed: %s\n", gpg_strerror (err));
405
406   if (!send_request ("COMMITPREORDER", input, &output))
407     {
408       for (kv = output; kv; kv = kv->next)
409         es_printf ("%s: %s\n", kv->name, kv->value);
410     }
411
412   keyvalue_release (input);
413   keyvalue_release (output);
414   xfree (amountstr);
415 }
416
417
418 static void
419 getpreorder (const char *refstring)
420 {
421   gpg_error_t err;
422   keyvalue_t input = NULL;
423   keyvalue_t output = NULL;
424   keyvalue_t kv;
425
426   /* Find reference.  */
427   err = keyvalue_put (&input, "Sepa-Ref", refstring);
428   if (err)
429     log_fatal ("keyvalue_put failed: %s\n", gpg_strerror (err));
430
431   if (!send_request ("GETPREORDER", input, &output))
432     {
433       for (kv = output; kv; kv = kv->next)
434         es_printf ("%s: %s\n", kv->name, kv->value);
435     }
436
437   keyvalue_release (input);
438   keyvalue_release (output);
439 }
440
441
442 static void
443 listpreorder (const char *refstring)
444 {
445   gpg_error_t err;
446   keyvalue_t input = NULL;
447   keyvalue_t output = NULL;
448   unsigned int n, count;
449   char key[30];
450   const char *s, *t;
451   char **tokens;
452   int i;
453   int len;
454
455   if (refstring)
456     {
457       err = keyvalue_put (&input, "Refnn", refstring);
458       if (err)
459         log_fatal ("keyvalue_put failed: %s\n", gpg_strerror (err));
460     }
461
462   if (!send_request ("LISTPREORDER", input, &output))
463     {
464       count = keyvalue_get_uint (output, "Count");
465       es_printf ("Number of records: %u\n", count);
466       for (n=0; n < count; n++)
467         {
468           snprintf (key, sizeof key, "D[%u]", n);
469           s = keyvalue_get_string (output, key);
470           tokens = strtokenize (*s=='|'? s+1:s, "|");
471           if (!tokens)
472             log_fatal ("strtokenize failed: %s\n",
473                        gpg_strerror (gpg_error_from_syserror ()));
474           es_putc ('|', es_stdout);
475           for (i=0; (s = tokens[i]); i++)
476             {
477               if (!*s && !tokens[i+1])
478                 continue; /* Skip an empty last field.  */
479               switch (i)
480                 {
481                 case 1: /* Created - print only date.   */
482                   es_printf (" %10.10s |", s );
483                   break;
484                 case 2: /* Last Paid - print time and date to ease
485                          * sorting.  */
486                   es_printf (" %19.19s |", s );
487                   break;
488                 case 4:
489                   t = strchr (s, '.');
490                   len = t? (t-s) : strlen (s);
491                   es_printf (" %3.*s |", len, s );
492                   break;
493                 case 5: /* Always EUR - don't print.  */
494                   break;
495                 case 6: /* Don't print the description.  */
496                   break;
497                 case 7: /* Email */
498                   es_printf (" %-20s |", s );
499                   break;
500                 default:
501                   es_printf (" %s |", s );
502                   break;
503                 }
504             }
505           es_putc ('\n', es_stdout);
506           xfree (tokens);
507         }
508     }
509
510   keyvalue_release (input);
511   keyvalue_release (output);
512 }
513
514
515 static void
516 sepapreorder (const char *amountstr_arg, const char *name,
517               const char *email, const char *desc)
518 {
519   gpg_error_t err;
520   keyvalue_t input = NULL;
521   keyvalue_t output = NULL;
522   keyvalue_t kv;
523   char *amountstr;
524   char *p;
525   int recur = 0;
526
527   amountstr = xstrdup (amountstr_arg);
528
529
530   p = strchr (amountstr, '/');
531   if (p)
532     {
533       *p++ = 0;
534       recur = atoi (p);
535     }
536
537   if (!*amountstr || !convert_amount (amountstr, 2))
538     {
539       log_error ("Syntax error in amount or value is not positive\n");
540       xfree (amountstr);
541       return;
542     }
543
544   switch (recur)
545     {
546     case 0: case 1: case 4: case 12: break;
547     default:
548       log_error ("Syntax error in RECUR suffix - must be 0, 1, 4, or 12\n");
549       xfree (amountstr);
550       return;
551     }
552
553   /* Find reference.  */
554   err = keyvalue_put (&input, "Amount", amountstr);
555   if (!err)
556     err = keyvalue_putf (&input, "Recur", "%d", recur);
557   if (!err && *name)
558     err = keyvalue_put (&input, "Meta[Name]", name);
559   if (!err && *email)
560     err = keyvalue_put (&input, "Email", email);
561   if (!err && *desc)
562     err = keyvalue_put (&input, "Desc", desc);
563   if (err)
564     log_fatal ("keyvalue_put failed: %s\n", gpg_strerror (err));
565
566   if (!send_request ("SEPAPREORDER", input, &output))
567     {
568       for (kv = output; kv; kv = kv->next)
569         es_printf ("%s: %s\n", kv->name, kv->value);
570     }
571
572   keyvalue_release (input);
573   keyvalue_release (output);
574   xfree (amountstr);
575 }